Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/browser/CoreBrowserTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { SelectionService } from 'browser/services/SelectionService';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
import { ThemeService } from 'browser/services/ThemeService';
import { KeyboardService } from 'browser/services/KeyboardService';
import { channels, color } from 'common/Color';
import { channels, color, rgb } from 'common/Color';
import { CoreTerminal } from 'common/CoreTerminal';
import * as Browser from 'common/Platform';
import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';
Expand Down Expand Up @@ -252,6 +252,20 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
}
}

/**
* Reports the current color scheme (dark or light) based on the relative luminance
* of the background and foreground theme colors.
* Sends CSI ? 997 ; 1 n for dark mode or CSI ? 997 ; 2 n for light mode.
*/
private _reportColorScheme(): void {
if (!this._themeService) return;
const bgLuminance = rgb.relativeLuminance(this._themeService.colors.background.rgba >> 8);
const fgLuminance = rgb.relativeLuminance(this._themeService.colors.foreground.rgba >> 8);
// Dark mode = background is darker than foreground (lower luminance)
const colorSchemeMode = bgLuminance < fgLuminance ? 1 : 2;
this.coreService.triggerDataEvent(`${C0.ESC}[?997;${colorSchemeMode}n`);
}

protected _setup(): void {
super._setup();

Expand Down Expand Up @@ -495,6 +509,16 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this._themeService = this._instantiationService.createInstance(ThemeService);
this._instantiationService.setService(IThemeService, this._themeService);

// CSI ? 996 n - color scheme query (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
this._register(this._inputHandler.onRequestColorSchemeQuery(() => this._reportColorScheme()));

// Emit unsolicited color scheme notification on theme change when DECSET 2031 is enabled
this._register(this._themeService.onChangeColors(() => {
if (this.coreService.decPrivateModes.colorSchemeUpdates) {
this._reportColorScheme();
}
}));

this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);

Expand Down
20 changes: 20 additions & 0 deletions src/common/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,26 @@ describe('InputHandler', () => {
inputHandler.resetModePrivate(Params.fromArray([2004]));
assert.equal(coreService.decPrivateModes.bracketedPasteMode, false);
});
it('should toggle colorSchemeUpdates (DECSET 2031)', () => {
const coreService = new MockCoreService();
const optionsService = new MockOptionsService();
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
// Set color scheme updates mode (default colorSchemeQuery=true)
inputHandler.setModePrivate(Params.fromArray([2031]));
assert.equal(coreService.decPrivateModes.colorSchemeUpdates, true);
// Reset color scheme updates mode
inputHandler.resetModePrivate(Params.fromArray([2031]));
assert.equal(coreService.decPrivateModes.colorSchemeUpdates, false);
});
it('should not toggle colorSchemeUpdates when colorSchemeQuery is disabled', () => {
const coreService = new MockCoreService();
const optionsService = new MockOptionsService();
optionsService.rawOptions.vtExtensions = { colorSchemeQuery: false };
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
// Attempt to set color scheme updates mode
inputHandler.setModePrivate(Params.fromArray([2031]));
assert.equal(coreService.decPrivateModes.colorSchemeUpdates, false);
});
});
describe('regression tests', function (): void {
function termContent(bufferService: IBufferService, trim: boolean): string[] {
Expand Down
18 changes: 18 additions & 0 deletions src/common/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ export class InputHandler extends Disposable implements IInputHandler {
public readonly onTitleChange = this._onTitleChange.event;
private readonly _onColor = this._register(new Emitter<IColorEvent>());
public readonly onColor = this._onColor.event;
private readonly _onRequestColorSchemeQuery = this._register(new Emitter<void>());
public readonly onRequestColorSchemeQuery = this._onRequestColorSchemeQuery.event;

private _parseStack: IParseStack = {
paused: false,
Expand Down Expand Up @@ -2026,6 +2028,11 @@ export class InputHandler extends Disposable implements IInputHandler {
case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)
this._coreService.decPrivateModes.synchronizedOutput = true;
break;
case 2031: // color scheme updates (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {
this._coreService.decPrivateModes.colorSchemeUpdates = true;
}
break;
case 9001: // win32-input-mode (https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md)
if (this._optionsService.rawOptions.vtExtensions?.win32InputMode) {
this._coreService.decPrivateModes.win32InputMode = true;
Expand Down Expand Up @@ -2271,6 +2278,11 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.decPrivateModes.synchronizedOutput = false;
this._onRequestRefreshRows.fire(undefined);
break;
case 2031: // color scheme updates (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {
this._coreService.decPrivateModes.colorSchemeUpdates = false;
}
break;
case 9001: // win32-input-mode
if (this._optionsService.rawOptions.vtExtensions?.win32InputMode) {
this._coreService.decPrivateModes.win32InputMode = false;
Expand Down Expand Up @@ -2774,6 +2786,12 @@ export class InputHandler extends Disposable implements IInputHandler {
// no dec locator/mouse
// this.handler(C0.ESC + '[?50n');
break;
case 996:
// color scheme query (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {
this._onRequestColorSchemeQuery.fire();
}
break;
}
return true;
}
Expand Down
1 change: 1 addition & 0 deletions src/common/TestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class MockCoreService implements ICoreService {
applicationCursorKeys: false,
applicationKeypad: false,
bracketedPasteMode: false,
colorSchemeUpdates: false,
cursorBlink: undefined,
cursorStyle: undefined,
origin: false,
Expand Down
1 change: 1 addition & 0 deletions src/common/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export interface IDecPrivateModes {
applicationCursorKeys: boolean;
applicationKeypad: boolean;
bracketedPasteMode: boolean;
colorSchemeUpdates: boolean;
cursorBlink: boolean | undefined;
cursorStyle: CursorStyle | undefined;
origin: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/common/services/CoreService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
applicationCursorKeys: false,
applicationKeypad: false,
bracketedPasteMode: false,
colorSchemeUpdates: false,
cursorBlink: undefined,
cursorStyle: undefined,
origin: false,
Expand Down
1 change: 1 addition & 0 deletions src/common/services/Services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export interface IVtExtensions {
kittyKeyboard?: boolean;
kittySgrBoldFaintControl?: boolean;
win32InputMode?: boolean;
colorSchemeQuery?: boolean;
}

export const IOscLinkService = createDecorator<IOscLinkService>('OscLinkService');
Expand Down
26 changes: 24 additions & 2 deletions test/playwright/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1234,8 +1234,30 @@ test.describe('InputHandler Integration Tests', () => {
test.skip('CSI > Ps n - Disable key modifier options, xterm', () => {
// TODO: Implement
});
test.describe.skip('CSI ? Ps n - DSR: Device Status Report (DEC-specific).', () => {
// TODO: Implement
test.describe('CSI ? Ps n - DECDSR: Device Status Report (DEC-specific)', () => {
test('Color Scheme Query - CSI ? 996 n (dark theme)', async () => {
// Default theme has dark background (#000000) and light foreground (#ffffff)
await ctx.proxy.write('\x1b[?996n');
deepStrictEqual(recordedData, ['\x1b[?997;1n']);
});

test('Color Scheme Query - CSI ? 996 n (light theme)', async () => {
recordedData.length = 0;
await ctx.page.evaluate(`window.term.options.theme = { background: '#ffffff', foreground: '#000000' }`);
await ctx.proxy.write('\x1b[?996n');
deepStrictEqual(recordedData, ['\x1b[?997;2n']);
// Restore default theme
await ctx.page.evaluate(`window.term.options.theme = { background: '#000000', foreground: '#ffffff' }`);
});

test('Color Scheme Query disabled via vtExtensions.colorSchemeQuery', async () => {
recordedData.length = 0;
await ctx.page.evaluate(`window.term.options.vtExtensions = { colorSchemeQuery: false }`);
await ctx.proxy.write('\x1b[?996n');
deepStrictEqual(recordedData, []);
// Re-enable
await ctx.page.evaluate(`window.term.options.vtExtensions = { colorSchemeQuery: true }`);
});
});
test.skip('CSI > Ps p - XTSMPOINTER: Set resource value pointerMode, xterm', () => {
// TODO: Implement
Expand Down
14 changes: 13 additions & 1 deletion typings/xterm-headless.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ declare module '@xterm/headless' {
windowOptions?: IWindowOptions;

/**
* Enable various VT extensions. All extensions are disabled by default.
* Enable various VT extensions.
*/
vtExtensions?: IVtExtensions;
}
Expand Down Expand Up @@ -356,6 +356,18 @@ declare module '@xterm/headless' {
* [0]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
*/
win32InputMode?: boolean;

/**
* Whether [color scheme query and notification][0] (`CSI ? 996 n` and
* `DECSET 2031`) is enabled. When enabled, the terminal will respond to
* color scheme queries with `CSI ? 997 ; 1 n` (dark) or `CSI ? 997 ; 2 n`
* (light) based on the relative luminance of the background and foreground
* theme colors. Programs can enable unsolicited notifications via
* `CSI ? 2031 h`. The default is true.
*
* [0]: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
*/
colorSchemeQuery?: boolean;
}

/**
Expand Down
14 changes: 13 additions & 1 deletion typings/xterm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ declare module '@xterm/xterm' {
theme?: ITheme;

/**
* Enable various VT extensions. All extensions are disabled by default.
* Enable various VT extensions.
*/
vtExtensions?: IVtExtensions;

Expand Down Expand Up @@ -473,6 +473,18 @@ declare module '@xterm/xterm' {
* [0]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
*/
win32InputMode?: boolean;

/**
* Whether [color scheme query and notification][0] (`CSI ? 996 n` and
* `DECSET 2031`) is enabled. When enabled, the terminal will respond to
* color scheme queries with `CSI ? 997 ; 1 n` (dark) or `CSI ? 997 ; 2 n`
* (light) based on the relative luminance of the background and foreground
* theme colors. Programs can enable unsolicited notifications via
* `CSI ? 2031 h`. The default is true.
*
* [0]: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
*/
colorSchemeQuery?: boolean;
}

/**
Expand Down
Loading