diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 8116a65aa..e932c6f08 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -4,6 +4,7 @@ import { type CursorStyle, DebugOverlayCorner, type RenderContext, + type ThemeMode, type ViewportBounds, type WidthMethod, } from "./types" @@ -429,6 +430,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { private _cachedPalette: TerminalColors | null = null private _paletteDetectionPromise: Promise | null = null private _onDestroy?: () => void + private _themeMode: ThemeMode | null = null private inputHandlers: ((sequence: string) => boolean)[] = [] private prependedInputHandlers: ((sequence: string) => boolean)[] = [] @@ -848,6 +850,10 @@ export class CliRenderer extends EventEmitter implements RenderContext { return this._capabilities } + public get themeMode(): ThemeMode | null { + return this._themeMode + } + public getDebugInputs(): Array<{ timestamp: string; sequence: string }> { return [...this._debugInputs] } @@ -1065,6 +1071,24 @@ export class CliRenderer extends EventEmitter implements RenderContext { return false }).bind(this) + private themeModeHandler: (sequence: string) => boolean = ((sequence: string) => { + if (sequence === "\x1b[?997;1n") { + if (this._themeMode !== "dark") { + this._themeMode = "dark" + this.emit("theme_mode", "dark") + } + return true + } + if (sequence === "\x1b[?997;2n") { + if (this._themeMode !== "light") { + this._themeMode = "light" + this.emit("theme_mode", "light") + } + return true + } + return false + }).bind(this) + private setupInput(): void { for (const handler of this.prependedInputHandlers) { this.addInputHandler(handler) @@ -1083,6 +1107,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { }) this.addInputHandler(this.capabilityHandler) this.addInputHandler(this.focusHandler) + this.addInputHandler(this.themeModeHandler) this.addInputHandler((sequence: string) => { return this._keyHandler.processInput(sequence) }) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index e948de941..b671c894e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -28,6 +28,8 @@ export function getBaseAttributes(attr: number): number { return attr & ATTRIBUTE_BASE_MASK } +export type ThemeMode = "dark" | "light" + export type CursorStyle = "block" | "line" | "underline" export interface CursorStyleOptions { @@ -50,6 +52,7 @@ export interface RendererEvents { "memory:snapshot": (snapshot: { heapUsed: number; heapTotal: number; arrayBuffers: number }) => void selection: (selection: Selection) => void "debugOverlay:toggle": (enabled: boolean) => void + theme_mode: (mode: ThemeMode) => void } export interface RenderContext extends EventEmitter { diff --git a/packages/core/src/zig/terminal.zig b/packages/core/src/zig/terminal.zig index 9467c852a..c927632e2 100644 --- a/packages/core/src/zig/terminal.zig +++ b/packages/core/src/zig/terminal.zig @@ -166,8 +166,7 @@ pub fn resetState(self: *Terminal, tty: anytype) !void { } if (self.state.color_scheme_updates) { - try tty.writeAll(ansi.ANSI.colorSchemeReset); - self.state.color_scheme_updates = false; + try self.setColorSchemeUpdates(tty, false); } self.setTerminalTitle(tty, ""); @@ -271,6 +270,11 @@ pub fn enableDetectedFeatures(self: *Terminal, tty: anytype, use_kitty_keyboard: if (self.caps.focus_tracking) { try self.setFocusTracking(tty, true); } + + if (!self.state.color_scheme_updates) { + try self.setColorSchemeUpdates(tty, true); + try tty.writeAll(ansi.ANSI.colorSchemeRequest); + } } fn checkEnvironmentOverrides(self: *Terminal) void { @@ -491,6 +495,12 @@ pub fn setModifyOtherKeys(self: *Terminal, tty: anytype, enable: bool) !void { self.state.modify_other_keys = enable; } +pub fn setColorSchemeUpdates(self: *Terminal, tty: anytype, enable: bool) !void { + const seq = if (enable) ansi.ANSI.colorSchemeSet else ansi.ANSI.colorSchemeReset; + try tty.writeAll(seq); + self.state.color_scheme_updates = enable; +} + /// Re-send all currently-active terminal mode escape sequences unconditionally. /// /// When the terminal loses and regains focus (e.g. alt-tab, tab switch, minimize), @@ -562,7 +572,7 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { if (std.mem.indexOf(u8, response, "2027;2$y")) |_| { self.caps.unicode = .unicode; } - if (std.mem.indexOf(u8, response, "2031;2$y")) |_| { + if (std.mem.indexOf(u8, response, "2031;1$y") != null or std.mem.indexOf(u8, response, "2031;2$y") != null) { self.caps.color_scheme_updates = true; } if (std.mem.indexOf(u8, response, "1004;1$y") != null or std.mem.indexOf(u8, response, "1004;2$y") != null) {