55
66import { addDisposableListener } from 'browser/Dom' ;
77import { IBufferService , IMouseStateService , ICoreService , ILogService , IOptionsService } from 'common/services/Services' ;
8- import { CoreMouseAction , CoreMouseButton , CoreMouseEventType , IDisposable } from 'common/Types' ;
8+ import { CoreMouseAction , CoreMouseButton , CoreMouseEventType , ICoreMouseEvent , IDisposable } from 'common/Types' ;
99import { C0 } from 'common/data/EscapeSequences' ;
1010import { toDisposable } from 'common/Lifecycle' ;
1111import { ICoreBrowserService , IMouseCoordsService , IMouseService , IMouseServiceTarget , IRenderService , ISelectionService } from './Services' ;
@@ -21,6 +21,9 @@ interface IMouseBindContext {
2121export class MouseService implements IMouseService {
2222 public serviceBrand : undefined ;
2323
24+ private _lastEvent : ICoreMouseEvent | null = null ;
25+ private _wheelPartialScroll : number = 0 ;
26+
2427 constructor (
2528 @IRenderService private readonly _renderService : IRenderService ,
2629 @IMouseCoordsService private readonly _mouseCoordsService : IMouseCoordsService ,
@@ -123,7 +126,7 @@ export class MouseService implements IMouseService {
123126 if ( deltaY === 0 ) {
124127 return false ;
125128 }
126- const lines = this . _mouseStateService . consumeWheelEvent (
129+ const lines = this . _consumeWheelEvent (
127130 ev as WheelEvent ,
128131 this . _renderService ?. dimensions ?. device ?. cell ?. height ,
129132 this . _coreBrowserService ?. dpr
@@ -145,7 +148,7 @@ export class MouseService implements IMouseService {
145148 return false ;
146149 }
147150
148- return this . _mouseStateService . triggerMouseEvent ( {
151+ return this . _triggerMouseEvent ( {
149152 col : pos . col ,
150153 row : pos . row ,
151154 x : pos . x ,
@@ -241,7 +244,7 @@ export class MouseService implements IMouseService {
241244 return false ;
242245 }
243246
244- const lines = this . _mouseStateService . consumeWheelEvent (
247+ const lines = this . _consumeWheelEvent (
245248 ev ,
246249 this . _renderService ?. dimensions ?. device ?. cell ?. height ,
247250 this . _coreBrowserService ?. dpr
@@ -261,13 +264,18 @@ export class MouseService implements IMouseService {
261264 }
262265 }
263266
267+ public reset ( ) : void {
268+ this . _lastEvent = null ;
269+ this . _wheelPartialScroll = 0 ;
270+ }
271+
264272 private _handleProtocolChange ( ctx : IMouseBindContext , eventListeners : Record < 'mouseup' | 'wheel' | 'mousedrag' | 'mousemove' , EventListener > , events : CoreMouseEventType ) : void {
265273 const { element, document } = ctx . target ;
266274 const { requestedEvents } = ctx ;
267275 // apply global changes on events
268276 if ( events ) {
269277 if ( this . _optionsService . rawOptions . logLevel === 'debug' ) {
270- this . _logService . debug ( 'Binding to mouse events:' , this . _mouseStateService . explainEvents ( events ) ) ;
278+ this . _logService . debug ( 'Binding to mouse events:' , this . _explainEvents ( events ) ) ;
271279 }
272280 element . classList . add ( 'enable-mouse-events' ) ;
273281 this . _selectionService . disable ( ) ;
@@ -317,4 +325,131 @@ export class MouseService implements IMouseService {
317325 }
318326 }
319327
328+ private _applyScrollModifier ( amount : number , ev : WheelEvent ) : number {
329+ // Multiply the scroll speed when the modifier key is pressed
330+ if ( ev . altKey || ev . ctrlKey || ev . shiftKey ) {
331+ return amount * this . _optionsService . rawOptions . fastScrollSensitivity * this . _optionsService . rawOptions . scrollSensitivity ;
332+ }
333+ return amount * this . _optionsService . rawOptions . scrollSensitivity ;
334+ }
335+
336+ /**
337+ * Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
338+ * This prevents hyper-sensitive scrolling in alt buffer.
339+ */
340+ private _consumeWheelEvent ( ev : WheelEvent , cellHeight ?: number , dpr ?: number ) : number {
341+ // Do nothing if it's not a vertical scroll event
342+ if ( ev . deltaY === 0 || ev . shiftKey ) {
343+ return 0 ;
344+ }
345+
346+ if ( cellHeight === undefined || dpr === undefined ) {
347+ return 0 ;
348+ }
349+
350+ const targetWheelEventPixels = cellHeight / dpr ;
351+ let amount = this . _applyScrollModifier ( ev . deltaY , ev ) ;
352+
353+ if ( ev . deltaMode === WheelEvent . DOM_DELTA_PIXEL ) {
354+ amount /= ( targetWheelEventPixels + 0.0 ) ; // Prevent integer division
355+
356+ const isLikelyTrackpad = Math . abs ( ev . deltaY ) < 50 ;
357+ if ( isLikelyTrackpad ) {
358+ amount *= 0.3 ;
359+ }
360+
361+ this . _wheelPartialScroll += amount ;
362+ amount = Math . floor ( Math . abs ( this . _wheelPartialScroll ) ) * ( this . _wheelPartialScroll > 0 ? 1 : - 1 ) ;
363+ this . _wheelPartialScroll %= 1 ;
364+ } else if ( ev . deltaMode === WheelEvent . DOM_DELTA_PAGE ) {
365+ amount *= this . _bufferService . rows ;
366+ }
367+ return amount ;
368+ }
369+
370+ /**
371+ * Triggers a mouse event to be sent.
372+ *
373+ * Returns true if the event passed all protocol restrictions and a report
374+ * was sent, otherwise false. The return value may be used to decide whether
375+ * the default event action in the browser component should be omitted.
376+ *
377+ * Note: The method will change values of the given event object
378+ * to fulfill protocol and encoding restrictions.
379+ */
380+ private _triggerMouseEvent ( e : ICoreMouseEvent ) : boolean {
381+ // range check for col/row
382+ if ( e . col < 0 || e . col >= this . _bufferService . cols
383+ || e . row < 0 || e . row >= this . _bufferService . rows ) {
384+ return false ;
385+ }
386+
387+ // filter nonsense combinations of button + action
388+ if ( e . button === CoreMouseButton . WHEEL && e . action === CoreMouseAction . MOVE ) {
389+ return false ;
390+ }
391+ if ( e . button === CoreMouseButton . NONE && e . action !== CoreMouseAction . MOVE ) {
392+ return false ;
393+ }
394+ if ( e . button !== CoreMouseButton . WHEEL && ( e . action === CoreMouseAction . LEFT || e . action === CoreMouseAction . RIGHT ) ) {
395+ return false ;
396+ }
397+
398+ // report 1-based coords
399+ e . col ++ ;
400+ e . row ++ ;
401+
402+ // debounce move events at grid or pixel level
403+ if ( e . action === CoreMouseAction . MOVE
404+ && this . _lastEvent
405+ && this . _equalEvents ( this . _lastEvent , e , this . _mouseStateService . isPixelEncoding )
406+ ) {
407+ return false ;
408+ }
409+
410+ // apply protocol restrictions
411+ if ( ! this . _mouseStateService . restrictMouseEvent ( e ) ) {
412+ return false ;
413+ }
414+
415+ // encode report and send
416+ const report = this . _mouseStateService . encodeMouseEvent ( e ) ;
417+ if ( report ) {
418+ if ( this . _mouseStateService . isDefaultEncoding ) {
419+ this . _coreService . triggerBinaryEvent ( report ) ;
420+ } else {
421+ this . _coreService . triggerDataEvent ( report , true ) ;
422+ }
423+ }
424+
425+ this . _lastEvent = e ;
426+ return true ;
427+ }
428+
429+ private _explainEvents ( events : CoreMouseEventType ) : { [ event : string ] : boolean } {
430+ return {
431+ down : ! ! ( events & CoreMouseEventType . DOWN ) ,
432+ up : ! ! ( events & CoreMouseEventType . UP ) ,
433+ drag : ! ! ( events & CoreMouseEventType . DRAG ) ,
434+ move : ! ! ( events & CoreMouseEventType . MOVE ) ,
435+ wheel : ! ! ( events & CoreMouseEventType . WHEEL )
436+ } ;
437+ }
438+
439+ private _equalEvents ( e1 : ICoreMouseEvent , e2 : ICoreMouseEvent , pixels : boolean ) : boolean {
440+ if ( pixels ) {
441+ if ( e1 . x !== e2 . x ) return false ;
442+ if ( e1 . y !== e2 . y ) return false ;
443+ } else {
444+ if ( e1 . col !== e2 . col ) return false ;
445+ if ( e1 . row !== e2 . row ) return false ;
446+ }
447+ if ( e1 . button !== e2 . button ) return false ;
448+ if ( e1 . action !== e2 . action ) return false ;
449+ if ( e1 . ctrl !== e2 . ctrl ) return false ;
450+ if ( e1 . alt !== e2 . alt ) return false ;
451+ if ( e1 . shift !== e2 . shift ) return false ;
452+ return true ;
453+ }
454+
320455}
0 commit comments