Skip to content

Commit

Permalink
Merge pull request #4093 from jerch/sgr_pixels
Browse files Browse the repository at this point in the history
SGR-pixels mouse report
  • Loading branch information
jerch authored Sep 1, 2022
2 parents f816ed0 + 70d30f5 commit f9db65c
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 341 deletions.
27 changes: 22 additions & 5 deletions bin/test_mousemodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Script to test different mouse modes in terminal emulators.
* Tests for protocols DECSET 9, 1000, 1002, 1003 with different
* report encodings (default, UTF8, SGR, URXVT).
* report encodings (default, UTF8, SGR, URXVT, SGR-pixels).
*
* VT200 Highlight mode (DECSET 1001) is not implemented.
*
Expand Down Expand Up @@ -152,13 +152,27 @@ const ENC = {
],
'URXVT': [
'\x1b[?1015h',
// format: CSI <button + 32> ; Prow ; Pcol M
// format: CSI <button + 32> ; Prow ; Pcol M
report => {
// strip off introducer + M
const sReport = report.toString().slice(2, -1);
const [button, col, row] = sReport.split(';');
return {state: evalButtonCode(button - 32), row, col};
}
],
'SGR_PIXELS' : [
'\x1b[?1016h',
// format: CSI < Pbutton ; Prow ; Pcol M
report => {
// strip off introducer + M
const sReport = report.toString().slice(3, -1);
const [buttonCode, col, row] = sReport.split(';');
const state = evalButtonCode(buttonCode);
if (report[report.length - 1] === 'm'.charCodeAt(0)) {
state.action = 'release';
}
return {state, row, col};
}
]
}

Expand Down Expand Up @@ -203,9 +217,12 @@ function activate() {

function applyReportData(data) {
let {state, row, col} = ENC[Object.keys(ENC)[activeEnc]][1](data);
console.log('\x1b[2KButton:', state.button, 'Action:', state.action, 'Modifier:', state.modifier, 'row:', row, 'col:', col);
// apply to cursor position
process.stdout.write(`\x1b[${row};${col}H`);
if (Object.keys(ENC)[activeEnc] !== 'SGR_PIXELS') {
console.log('\x1b[2KButton:', state.button, 'Action:', state.action, 'Modifier:', state.modifier, 'row:', row, 'col:', col);
process.stdout.write(`\x1b[${row};${col}H`);
} else {
console.log('\x1b[2KButton:', state.button, 'Action:', state.action, 'Modifier:', state.modifier, 'x:', row, 'y:', col);
}
}

printMenu();
Expand Down
16 changes: 9 additions & 7 deletions src/browser/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// send event to CoreMouseService
function sendEvent(ev: MouseEvent | WheelEvent): boolean {
// get mouse coordinates
const pos = self._mouseService!.getRawByteCoords(ev, self.screenElement!, self.cols, self.rows);
const pos = self._mouseService!.getMouseReportCoords(ev, self.screenElement!);
if (!pos) {
return false;
}
Expand Down Expand Up @@ -699,8 +699,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
}

return self.coreMouseService.triggerMouseEvent({
col: pos.x - 33, // FIXME: why -33 here?
row: pos.y - 33,
col: pos.col,
row: pos.row,
x: pos.x,
y: pos.y,
button: but,
action,
ctrl: ev.ctrlKey,
Expand Down Expand Up @@ -1320,13 +1322,13 @@ export class Terminal extends CoreTerminal implements ITerminal {

switch (type) {
case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:
const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0);
const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0);
const canvasWidth = this._renderService.dimensions.canvasWidth.toFixed(0);
const canvasHeight = this._renderService.dimensions.canvasHeight.toFixed(0);
this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);
break;
case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:
const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0);
const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0);
const cellWidth = this._renderService.dimensions.actualCellWidth.toFixed(0);
const cellHeight = this._renderService.dimensions.actualCellHeight.toFixed(0);
this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/browser/TestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export class MockMouseService implements IMouseService {
throw new Error('Not implemented');
}

public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
throw new Error('Not implemented');
}
}
Expand Down
14 changes: 0 additions & 14 deletions src/browser/input/Mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,3 @@ export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: {clie

return coords;
}

/**
* Gets coordinates within the terminal for a particular mouse event, wrapping
* them to the bounds of the terminal and adding 32 to both the x and y values
* as expected by xterm.
*/
export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined {
if (!coords) {
return undefined;
}

// xterm sends raw bytes and starts at 32 (SP) for each.
return { x: coords[0] + 32, y: coords[1] + 32 };
}
24 changes: 20 additions & 4 deletions src/browser/services/MouseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { ICharSizeService, IRenderService, IMouseService } from './Services';
import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';

export class MouseService implements IMouseService {
public serviceBrand: undefined;
Expand All @@ -29,8 +29,24 @@ export class MouseService implements IMouseService {
);
}

public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
const coords = this.getCoords(event, element, colCount, rowCount);
return getRawByteCoords(coords);
public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
const coords = getCoordsRelativeToElement(window, event, element);

// due to rounding issues in zoom states pixel values might be negative or overflow actual canvas
// ignore those events effectively narrowing mouse area a tiny bit at the edges
if (!this._charSizeService.hasValidSize
|| coords[0] < 0
|| coords[1] < 0
|| coords[0] >= this._renderService.dimensions.canvasWidth
|| coords[1] >= this._renderService.dimensions.canvasHeight) {
return undefined;
}

return {
col: Math.floor(coords[0] / this._renderService.dimensions.actualCellWidth),
row: Math.floor(coords[1] / this._renderService.dimensions.actualCellHeight),
x: Math.floor(coords[0]),
y: Math.floor(coords[1])
};
}
}
2 changes: 1 addition & 1 deletion src/browser/services/Services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface IMouseService {
serviceBrand: undefined;

getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined;
}

export const IRenderService = createDecorator<IRenderService>('RenderService');
Expand Down
8 changes: 8 additions & 0 deletions src/common/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 1005 | Enable UTF-8 Mouse Mode. | #N |
* | 1006 | Enable SGR Mouse Mode. | #Y |
* | 1015 | Enable urxvt Mouse Mode. | #N |
* | 1016 | Enable SGR-Pixels Mouse Mode. | #Y |
* | 1047 | Use Alternate Screen Buffer. | #Y |
* | 1048 | Save cursor as in DECSC. | #Y |
* | 1049 | Save cursor and switch to alternate buffer clearing it. | #P[Does not clear the alternate buffer.] |
Expand Down Expand Up @@ -1989,6 +1990,9 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECSET 1015 not supported (see #2507)');
break;
case 1016: // sgr pixels mode mouse
this._coreMouseService.activeEncoding = 'SGR_PIXELS';
break;
case 25: // show cursor
this._coreService.isCursorHidden = false;
break;
Expand Down Expand Up @@ -2149,6 +2153,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 1005 | Disable UTF-8 Mouse Mode. | #N |
* | 1006 | Disable SGR Mouse Mode. | #Y |
* | 1015 | Disable urxvt Mouse Mode. | #N |
* | 1006 | Disable SGR-Pixels Mouse Mode. | #Y |
* | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | #Y |
* | 1048 | Restore cursor as in DECRC. | #Y |
* | 1049 | Use Normal Screen Buffer and restore cursor. | #Y |
Expand Down Expand Up @@ -2210,6 +2215,9 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECRST 1015 not supported (see #2507)');
break;
case 1006: // sgr pixels mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT';
break;
case 25: // hide cursor
this._coreService.isCursorHidden = true;
break;
Expand Down
3 changes: 3 additions & 0 deletions src/common/Types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ export interface ICoreMouseEvent {
col: number;
/** row (zero based). */
row: number;
/** xy pixel positions. */
x: number;
y: number;
/**
* Button the action occured. Due to restrictions of the tracking protocols
* it is not possible to report multiple buttons at once.
Expand Down
Loading

0 comments on commit f9db65c

Please sign in to comment.