Skip to content

Commit 1400398

Browse files
authored
Merge pull request #5757 from Tyriar/5754
Move bindMouse into MouseService
2 parents 73adfdf + b66128b commit 1400398

8 files changed

Lines changed: 408 additions & 323 deletions

File tree

demo/client/components/window/cellInspectorWindow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class CellInspectorWindow extends BaseWindow implements IControlWindow {
8585

8686
terminal.element.addEventListener('mousemove', (e: MouseEvent) => {
8787
const core = (terminal as any)._core;
88-
const coords = core._mouseService?.getCoords(e, core.screenElement, terminal.cols, terminal.rows);
88+
const coords = core._mouseCoordsService?.getCoords(e, core.screenElement, terminal.cols, terminal.rows);
8989
if (!coords) {
9090
this._clearDisplay();
9191
return;

src/browser/CoreBrowserTerminal.ts

Lines changed: 15 additions & 277 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,17 @@ import { CharSizeService } from 'browser/services/CharSizeService';
3636
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
3737
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
3838
import { LinkProviderService } from 'browser/services/LinkProviderService';
39+
import { MouseCoordsService } from 'browser/services/MouseCoordsService';
3940
import { MouseService } from 'browser/services/MouseService';
4041
import { RenderService } from 'browser/services/RenderService';
4142
import { SelectionService } from 'browser/services/SelectionService';
42-
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
43+
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
4344
import { ThemeService } from 'browser/services/ThemeService';
4445
import { KeyboardService } from 'browser/services/KeyboardService';
4546
import { channels, color, rgb } from 'common/Color';
4647
import { CoreTerminal } from 'common/CoreTerminal';
4748
import * as Browser from 'common/Platform';
48-
import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';
49+
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';
4950
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
5051
import { IBuffer } from 'common/buffer/Types';
5152
import { C0, C1ESCAPED } from 'common/data/EscapeSequences';
@@ -87,6 +88,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
8788
// Optional browser services
8889
private _charSizeService: ICharSizeService | undefined;
8990
private _coreBrowserService: ICoreBrowserService | undefined;
91+
private _mouseCoordsService: IMouseCoordsService | undefined;
9092
private _mouseService: IMouseService | undefined;
9193
private _renderService: IRenderService | undefined;
9294
private _themeService: IThemeService | undefined;
@@ -543,8 +545,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
543545
this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
544546
this._helperContainer.appendChild(this._compositionView);
545547

546-
this._mouseService = this._instantiationService.createInstance(MouseService);
547-
this._instantiationService.setService(IMouseService, this._mouseService);
548+
this._mouseCoordsService = this._instantiationService.createInstance(MouseCoordsService);
549+
this._instantiationService.setService(IMouseCoordsService, this._mouseCoordsService);
548550

549551
const linkifier = this._linkifier.value = this._register(this._instantiationService.createInstance(Linkifier, this.screenElement));
550552

@@ -579,6 +581,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
579581
linkifier
580582
));
581583
this._instantiationService.setService(ISelectionService, this._selectionService);
584+
this._mouseService = this._instantiationService.createInstance(MouseService);
585+
this._mouseService.setCustomWheelEventHandler(this._customWheelEventHandler);
586+
this._instantiationService.setService(IMouseService, this._mouseService);
582587
this._register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
583588
this._register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
584589
this._register(this._selectionService.onRequestRedraw(e => this._renderService!.handleSelectionChanged(e.start, e.end, e.columnSelectMode)));
@@ -638,285 +643,17 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
638643

639644
// Listen for mouse events and translate
640645
// them into terminal mouse protocols.
641-
this.bindMouse();
646+
this._mouseService.bindMouse({
647+
element: this.element!,
648+
screenElement: this.screenElement!,
649+
document: this._document!
650+
}, disposable => this._register(disposable), () => this.focus());
642651
}
643652

644653
private _createRenderer(): IRenderer {
645654
return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier!);
646655
}
647656

648-
/**
649-
* Bind certain mouse events to the terminal.
650-
* By default only 3 button + wheel up/down is ativated. For higher buttons
651-
* no mouse report will be created. Typically the standard actions will be active.
652-
*
653-
* There are several reasons not to enable support for higher buttons/wheel:
654-
* - Button 4 and 5 are typically used for history back and forward navigation,
655-
* there is no straight forward way to supress/intercept those standard actions.
656-
* - Support for higher buttons does not work in some platform/browser combinations.
657-
* - Left/right wheel was not tested.
658-
* - Emulators vary in mouse button support, typically only 3 buttons and
659-
* wheel up/down work reliable.
660-
*
661-
* TODO: Move mouse event code into its own file.
662-
*/
663-
public bindMouse(): void {
664-
const self = this;
665-
const el = this.element!;
666-
667-
// send event to CoreMouseService
668-
function sendEvent(ev: MouseEvent | WheelEvent): boolean {
669-
// Get mouse coordinates
670-
const pos = self._mouseService?.getMouseReportCoords(ev, self.screenElement!);
671-
if (!pos) {
672-
return false;
673-
}
674-
675-
let but: CoreMouseButton;
676-
let action: CoreMouseAction | undefined;
677-
switch ((ev as any).overrideType || ev.type) {
678-
case 'mousemove':
679-
action = CoreMouseAction.MOVE;
680-
if (ev.buttons === undefined) {
681-
// buttons is not supported on macOS, try to get a value from button instead
682-
but = CoreMouseButton.NONE;
683-
if (ev.button !== undefined) {
684-
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
685-
}
686-
} else {
687-
// according to MDN buttons only reports up to button 5 (AUX2)
688-
but = ev.buttons & 1 ? CoreMouseButton.LEFT :
689-
ev.buttons & 4 ? CoreMouseButton.MIDDLE :
690-
ev.buttons & 2 ? CoreMouseButton.RIGHT :
691-
CoreMouseButton.NONE; // fallback to NONE
692-
}
693-
break;
694-
case 'mouseup':
695-
action = CoreMouseAction.UP;
696-
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
697-
break;
698-
case 'mousedown':
699-
action = CoreMouseAction.DOWN;
700-
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
701-
break;
702-
case 'wheel':
703-
if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {
704-
return false;
705-
}
706-
const deltaY = (ev as WheelEvent).deltaY;
707-
if (deltaY === 0) {
708-
return false;
709-
}
710-
const lines = self.coreMouseService.consumeWheelEvent(
711-
ev as WheelEvent,
712-
self._renderService?.dimensions?.device?.cell?.height,
713-
self._coreBrowserService?.dpr
714-
);
715-
if (lines === 0) {
716-
return false;
717-
}
718-
action = deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
719-
but = CoreMouseButton.WHEEL;
720-
break;
721-
default:
722-
// dont handle other event types by accident
723-
return false;
724-
}
725-
726-
// exit if we cannot determine valid button/action values
727-
// do nothing for higher buttons than wheel
728-
if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
729-
return false;
730-
}
731-
732-
return self.coreMouseService.triggerMouseEvent({
733-
col: pos.col,
734-
row: pos.row,
735-
x: pos.x,
736-
y: pos.y,
737-
button: but,
738-
action,
739-
ctrl: ev.ctrlKey,
740-
alt: ev.altKey,
741-
shift: ev.shiftKey
742-
});
743-
}
744-
745-
/**
746-
* Event listener state handling.
747-
* We listen to the onProtocolChange event of CoreMouseService and put
748-
* requested listeners in `requestedEvents`. With this the listeners
749-
* have all bits to do the event listener juggling.
750-
* Note: 'mousedown' currently is "always on" and not managed
751-
* by onProtocolChange.
752-
*/
753-
const requestedEvents: { [key: string]: ((ev: MouseEvent | WheelEvent) => void) | null } = {
754-
mouseup: null,
755-
wheel: null,
756-
mousedrag: null,
757-
mousemove: null
758-
};
759-
const eventListeners: { [key: string]: (ev: any) => void | boolean } = {
760-
mouseup: (ev: MouseEvent) => {
761-
sendEvent(ev);
762-
if (!ev.buttons) {
763-
// if no other button is held remove global handlers
764-
this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);
765-
if (requestedEvents.mousedrag) {
766-
this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);
767-
}
768-
}
769-
},
770-
wheel: (ev: WheelEvent) => {
771-
sendEvent(ev);
772-
ev.preventDefault();
773-
ev.stopPropagation();
774-
return false;
775-
},
776-
mousedrag: (ev: MouseEvent) => {
777-
// deal only with move while a button is held
778-
if (ev.buttons) {
779-
sendEvent(ev);
780-
}
781-
},
782-
mousemove: (ev: MouseEvent) => {
783-
// deal only with move without any button
784-
if (!ev.buttons) {
785-
sendEvent(ev);
786-
}
787-
}
788-
};
789-
this._register(this.coreMouseService.onProtocolChange(events => {
790-
// apply global changes on events
791-
if (events) {
792-
if (this.optionsService.rawOptions.logLevel === 'debug') {
793-
this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events));
794-
}
795-
this.element!.classList.add('enable-mouse-events');
796-
this._selectionService!.disable();
797-
} else {
798-
this._logService.debug('Unbinding from mouse events.');
799-
this.element!.classList.remove('enable-mouse-events');
800-
this._selectionService!.enable();
801-
}
802-
803-
// add/remove handlers from requestedEvents
804-
805-
if (!(events & CoreMouseEventType.MOVE)) {
806-
el.removeEventListener('mousemove', requestedEvents.mousemove!);
807-
requestedEvents.mousemove = null;
808-
} else if (!requestedEvents.mousemove) {
809-
el.addEventListener('mousemove', eventListeners.mousemove);
810-
requestedEvents.mousemove = eventListeners.mousemove;
811-
}
812-
813-
if (!(events & CoreMouseEventType.WHEEL)) {
814-
el.removeEventListener('wheel', requestedEvents.wheel!);
815-
requestedEvents.wheel = null;
816-
} else if (!requestedEvents.wheel) {
817-
el.addEventListener('wheel', eventListeners.wheel, { passive: false });
818-
requestedEvents.wheel = eventListeners.wheel;
819-
}
820-
821-
if (!(events & CoreMouseEventType.UP)) {
822-
this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);
823-
requestedEvents.mouseup = null;
824-
} else {
825-
requestedEvents.mouseup ??= eventListeners.mouseup;
826-
}
827-
828-
if (!(events & CoreMouseEventType.DRAG)) {
829-
this._document!.removeEventListener('mousemove', requestedEvents.mousedrag!);
830-
requestedEvents.mousedrag = null;
831-
} else {
832-
requestedEvents.mousedrag ??= eventListeners.mousedrag;
833-
}
834-
}));
835-
// force initial onProtocolChange so we dont miss early mouse requests
836-
this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol;
837-
838-
// Ensure document-level listeners are removed on dispose
839-
this._register(toDisposable(() => {
840-
if (requestedEvents.mouseup) {
841-
this._document!.removeEventListener('mouseup', requestedEvents.mouseup);
842-
}
843-
if (requestedEvents.mousedrag) {
844-
this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);
845-
}
846-
}));
847-
848-
/**
849-
* "Always on" event listeners.
850-
*/
851-
this._register(addDisposableListener(el, 'mousedown', (ev: MouseEvent) => {
852-
ev.preventDefault();
853-
this.focus();
854-
855-
// Don't send the mouse button to the pty if mouse events are disabled or
856-
// if the selection manager is having selection forced (ie. a modifier is
857-
// held).
858-
if (!this.coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) {
859-
return;
860-
}
861-
862-
sendEvent(ev);
863-
864-
// Register additional global handlers which should keep reporting outside
865-
// of the terminal element.
866-
// Note: Other emulators also do this for 'mousedown' while a button
867-
// is held, we currently limit 'mousedown' to the terminal only.
868-
if (requestedEvents.mouseup) {
869-
this._document!.addEventListener('mouseup', requestedEvents.mouseup);
870-
}
871-
if (requestedEvents.mousedrag) {
872-
this._document!.addEventListener('mousemove', requestedEvents.mousedrag);
873-
}
874-
}));
875-
876-
this._register(addDisposableListener(el, 'wheel', (ev: WheelEvent) => {
877-
// do nothing, if app side handles wheel itself
878-
if (requestedEvents.wheel) return;
879-
880-
if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {
881-
return false;
882-
}
883-
884-
if (!this.buffer.hasScrollback) {
885-
// Convert wheel events into up/down events when the buffer does not have scrollback, this
886-
// enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse
887-
// events are not enabled.
888-
// This used implementation used get the actual lines/partial lines scrolled from the
889-
// viewport but since moving to the new viewport implementation has been simplified to
890-
// simply send a single up or down sequence.
891-
892-
// Do nothing if there's no vertical scroll
893-
const deltaY = (ev as WheelEvent).deltaY;
894-
if (deltaY === 0) {
895-
return false;
896-
}
897-
898-
const lines = self.coreMouseService.consumeWheelEvent(
899-
ev as WheelEvent,
900-
self._renderService?.dimensions?.device?.cell?.height,
901-
self._coreBrowserService?.dpr
902-
);
903-
if (lines === 0) {
904-
ev.preventDefault();
905-
ev.stopPropagation();
906-
return false;
907-
}
908-
909-
// Construct and send sequences
910-
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
911-
this.coreService.triggerDataEvent(sequence, true);
912-
ev.preventDefault();
913-
ev.stopPropagation();
914-
return false;
915-
}
916-
}, { passive: false }));
917-
}
918-
919-
920657
/**
921658
* Tells the renderer to refresh terminal content between two rows (inclusive) at the next
922659
* opportunity.
@@ -991,6 +728,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
991728

992729
public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
993730
this._customWheelEventHandler = customWheelEventHandler;
731+
this._mouseService?.setCustomWheelEventHandler(customWheelEventHandler);
994732
}
995733

996734
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {

0 commit comments

Comments
 (0)