@@ -57,6 +57,7 @@ import { AccessibilityManager } from './AccessibilityManager';
5757import { Linkifier } from './Linkifier' ;
5858import { Emitter , EventUtils , type IEvent } from 'common/Event' ;
5959import { addDisposableListener } from 'browser/Dom' ;
60+ import { Gesture , EventType as GestureEventType , type IGestureEvent } from 'browser/scrollable/touch' ;
6061import { MutableDisposable , toDisposable } from 'common/Lifecycle' ;
6162
6263export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
@@ -78,6 +79,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
7879
7980 private _customKeyEventHandler : CustomKeyEventHandler | undefined ;
8081 private _customWheelEventHandler : CustomWheelEventHandler | undefined ;
82+ private _touchScrollAccumulator : number = 0 ;
8183
8284 // Browser services
8385 private readonly _decorationService : DecorationService ;
@@ -914,8 +916,79 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
914916 return false ;
915917 }
916918 } , { passive : false } ) ) ;
919+
920+ // Touch/gesture scrolling support
921+ this . _register ( Gesture . addTarget ( this . screenElement ! ) ) ;
922+ this . _register ( addDisposableListener ( this . screenElement ! , GestureEventType . START , ( ) => {
923+ this . _touchScrollAccumulator = 0 ;
924+ } ) ) ;
925+ this . _register ( addDisposableListener ( this . screenElement ! , GestureEventType . CHANGE , ( e : IGestureEvent ) => {
926+ e . preventDefault ( ) ;
927+ e . stopPropagation ( ) ;
928+
929+ // When mouse protocol has wheel events active, send as mouse wheel events
930+ if ( requestedEvents . wheel ) {
931+ this . _handleTouchScrollAsWheel ( e , sendEvent ) ;
932+ return ;
933+ }
934+
935+ // When in alt buffer (no scrollback), send up/down key sequences
936+ if ( ! this . buffer . hasScrollback ) {
937+ this . _handleTouchScrollAsKeys ( e ) ;
938+ return ;
939+ }
940+
941+ // Normal scrollback: delegate to viewport
942+ this . _viewport ?. handleTouchScroll ( e . translationY ) ;
943+ } ) ) ;
944+ }
945+
946+
947+ private _handleTouchScrollAsKeys ( e : IGestureEvent ) : void {
948+ const cellHeight = this . _renderService ?. dimensions . css . cell . height ;
949+ if ( ! cellHeight ) return ;
950+
951+ this . _touchScrollAccumulator -= e . translationY ;
952+ const lines = Math . trunc ( this . _touchScrollAccumulator / cellHeight ) ;
953+ if ( lines === 0 ) return ;
954+
955+ this . _touchScrollAccumulator -= lines * cellHeight ;
956+ const sequence = C0 . ESC
957+ + ( this . coreService . decPrivateModes . applicationCursorKeys ? 'O' : '[' )
958+ + ( lines < 0 ? 'A' : 'B' ) ;
959+ for ( let i = 0 ; i < Math . abs ( lines ) ; i ++ ) {
960+ this . coreService . triggerDataEvent ( sequence , true ) ;
961+ }
917962 }
918963
964+ private _handleTouchScrollAsWheel ( e : IGestureEvent , sendEvent : ( ev : MouseEvent | WheelEvent ) => boolean ) : void {
965+ const cellHeight = this . _renderService ?. dimensions . css . cell . height ;
966+ if ( ! cellHeight ) return ;
967+
968+ this . _touchScrollAccumulator -= e . translationY ;
969+ const lines = Math . trunc ( this . _touchScrollAccumulator / cellHeight ) ;
970+ if ( lines === 0 ) return ;
971+
972+ this . _touchScrollAccumulator -= lines * cellHeight ;
973+
974+ // Get coordinates from gesture event
975+ const pos = this . _mouseService ?. getMouseReportCoords ( e , this . screenElement ! ) ;
976+ if ( ! pos ) return ;
977+
978+ for ( let i = 0 ; i < Math . abs ( lines ) ; i ++ ) {
979+ this . coreMouseService . triggerMouseEvent ( {
980+ col : pos . col ,
981+ row : pos . row ,
982+ x : pos . x ,
983+ y : pos . y ,
984+ button : CoreMouseButton . WHEEL ,
985+ action : lines < 0 ? CoreMouseAction . UP : CoreMouseAction . DOWN ,
986+ ctrl : false ,
987+ alt : false ,
988+ shift : false
989+ } ) ;
990+ }
991+ }
919992
920993 /**
921994 * Tells the renderer to refresh terminal content between two rows (inclusive) at the next
0 commit comments