renderer: track if fg/bg/cursor color is explicitly set by an OSC (#3228)

The renderer must track if the foreground, background, and cursor colors
are explicitly set by an OSC so that changes are not overridden when the
config file is reloaded.

Fixes: https://github.com/ghostty-org/ghostty/issues/2795
This commit is contained in:
Mitchell Hashimoto
2024-12-29 09:05:37 -08:00
committed by GitHub
4 changed files with 148 additions and 82 deletions

View File

@ -76,18 +76,30 @@ size: renderer.Size,
/// True if the window is focused /// True if the window is focused
focused: bool, focused: bool,
/// The actual foreground color. May differ from the config foreground color if /// The foreground color set by an OSC 10 sequence. If unset then
/// changed by a terminal application /// default_foreground_color is used.
foreground_color: terminal.color.RGB, foreground_color: ?terminal.color.RGB,
/// The actual background color. May differ from the config background color if /// Foreground color set in the user's config file.
/// changed by a terminal application default_foreground_color: terminal.color.RGB,
background_color: terminal.color.RGB,
/// The actual cursor color. May differ from the config cursor color if changed /// The background color set by an OSC 11 sequence. If unset then
/// by a terminal application /// default_background_color is used.
background_color: ?terminal.color.RGB,
/// Background color set in the user's config file.
default_background_color: terminal.color.RGB,
/// The cursor color set by an OSC 12 sequence. If unset then
/// default_cursor_color is used.
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
/// Default cursor color when no color is set explicitly by an OSC 12 command.
/// This is cursor color as set in the user's config, if any. If no cursor color
/// is set in the user's config, then the cursor color is determined by the
/// current foreground color.
default_cursor_color: ?terminal.color.RGB,
/// When `cursor_color` is null, swap the foreground and background colors of /// When `cursor_color` is null, swap the foreground and background colors of
/// the cell under the cursor for the cursor color. Otherwise, use the default /// the cell under the cursor for the cursor color. Otherwise, use the default
/// foreground color as the cursor color. /// foreground color as the cursor color.
@ -629,9 +641,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.grid_metrics = font_critical.metrics, .grid_metrics = font_critical.metrics,
.size = options.size, .size = options.size,
.focused = true, .focused = true,
.foreground_color = options.config.foreground, .foreground_color = null,
.background_color = options.config.background, .default_foreground_color = options.config.foreground,
.cursor_color = options.config.cursor_color, .background_color = null,
.default_background_color = options.config.background,
.cursor_color = null,
.default_cursor_color = options.config.cursor_color,
.cursor_invert = options.config.cursor_invert, .cursor_invert = options.config.cursor_invert,
.current_background_color = options.config.background, .current_background_color = options.config.background,
@ -919,15 +934,34 @@ pub fn updateFrame(
} }
// Swap bg/fg if the terminal is reversed // Swap bg/fg if the terminal is reversed
const bg = self.background_color; const bg = self.background_color orelse self.default_background_color;
const fg = self.foreground_color; const fg = self.foreground_color orelse self.default_foreground_color;
defer { defer {
self.background_color = bg; if (self.background_color) |*c| {
self.foreground_color = fg; c.* = bg;
} else {
self.default_background_color = bg;
}
if (self.foreground_color) |*c| {
c.* = fg;
} else {
self.default_foreground_color = fg;
}
} }
if (state.terminal.modes.get(.reverse_colors)) { if (state.terminal.modes.get(.reverse_colors)) {
self.background_color = fg; if (self.background_color) |*c| {
self.foreground_color = bg; c.* = fg;
} else {
self.default_background_color = fg;
}
if (self.foreground_color) |*c| {
c.* = bg;
} else {
self.default_foreground_color = bg;
}
} }
// If our terminal screen size doesn't match our expected renderer // If our terminal screen size doesn't match our expected renderer
@ -1029,7 +1063,7 @@ pub fn updateFrame(
} }
break :critical .{ break :critical .{
.bg = self.background_color, .bg = self.background_color orelse self.default_background_color,
.screen = screen_copy, .screen = screen_copy,
.screen_type = state.terminal.active_screen, .screen_type = state.terminal.active_screen,
.mouse = state.mouse, .mouse = state.mouse,
@ -1957,10 +1991,10 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
self.uniforms.min_contrast = config.min_contrast; self.uniforms.min_contrast = config.min_contrast;
// Set our new colors // Set our new colors
self.background_color = config.background; self.default_background_color = config.background;
self.foreground_color = config.foreground; self.default_foreground_color = config.foreground;
self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null;
self.cursor_invert = config.cursor_invert; self.cursor_invert = config.cursor_invert;
self.cursor_color = if (!config.cursor_invert) config.cursor_color else null;
self.config.deinit(); self.config.deinit();
self.config = config.*; self.config = config.*;
@ -2246,12 +2280,12 @@ fn rebuildCells(
.extend => if (y == 0) { .extend => if (y == 0) {
self.uniforms.padding_extend.up = !row.neverExtendBg( self.uniforms.padding_extend.up = !row.neverExtendBg(
color_palette, color_palette,
self.background_color, self.background_color orelse self.default_background_color,
); );
} else if (y == self.cells.size.rows - 1) { } else if (y == self.cells.size.rows - 1) {
self.uniforms.padding_extend.down = !row.neverExtendBg( self.uniforms.padding_extend.down = !row.neverExtendBg(
color_palette, color_palette,
self.background_color, self.background_color orelse self.default_background_color,
); );
}, },
} }
@ -2360,7 +2394,7 @@ fn rebuildCells(
false; false;
const bg_style = style.bg(cell, color_palette); const bg_style = style.bg(cell, color_palette);
const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color;
// The final background color for the cell. // The final background color for the cell.
const bg = bg: { const bg = bg: {
@ -2380,7 +2414,7 @@ fn rebuildCells(
// If we don't have invert selection fg/bg set then we // If we don't have invert selection fg/bg set then we
// just use the selection background if set, otherwise // just use the selection background if set, otherwise
// the default fg color. // the default fg color.
break :bg self.config.selection_background orelse self.foreground_color; break :bg self.config.selection_background orelse self.foreground_color orelse self.default_foreground_color;
} }
// Not selected // Not selected
@ -2402,7 +2436,7 @@ fn rebuildCells(
// If we don't have invert selection fg/bg set // If we don't have invert selection fg/bg set
// then we just use the selection foreground if // then we just use the selection foreground if
// set, otherwise the default bg color. // set, otherwise the default bg color.
break :fg self.config.selection_foreground orelse self.background_color; break :fg self.config.selection_foreground orelse self.background_color orelse self.default_background_color;
} }
// Whether we need to use the bg color as our fg color: // Whether we need to use the bg color as our fg color:
@ -2411,7 +2445,7 @@ fn rebuildCells(
// Note: if selected then invert sel fg / bg must be // Note: if selected then invert sel fg / bg must be
// false since we separately handle it if true above. // false since we separately handle it if true above.
break :fg if (style.flags.inverse != selected) break :fg if (style.flags.inverse != selected)
bg_style orelse self.background_color bg_style orelse self.background_color orelse self.default_background_color
else else
fg_style; fg_style;
}; };
@ -2438,7 +2472,7 @@ fn rebuildCells(
// If we have a background and its not the default background // If we have a background and its not the default background
// then we apply background opacity // then we apply background opacity
if (style.bg(cell, color_palette) != null and !rgb.eql(self.background_color)) { if (style.bg(cell, color_palette) != null and !rgb.eql(self.background_color orelse self.default_background_color)) {
break :bg_alpha default; break :bg_alpha default;
} }
@ -2601,12 +2635,12 @@ fn rebuildCells(
// Prepare the cursor cell contents. // Prepare the cursor cell contents.
const style = cursor_style_ orelse break :cursor; const style = cursor_style_ orelse break :cursor;
const cursor_color = self.cursor_color orelse color: { const cursor_color = self.cursor_color orelse self.default_cursor_color orelse color: {
if (self.cursor_invert) { if (self.cursor_invert) {
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color;
} else { } else {
break :color self.foreground_color; break :color self.foreground_color orelse self.default_foreground_color;
} }
}; };
@ -2634,11 +2668,11 @@ fn rebuildCells(
const uniform_color = if (self.cursor_invert) blk: { const uniform_color = if (self.cursor_invert) blk: {
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color; break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color;
} else if (self.config.cursor_text) |txt| } else if (self.config.cursor_text) |txt|
txt txt
else else
self.background_color; self.background_color orelse self.default_background_color;
self.uniforms.cursor_color = .{ self.uniforms.cursor_color = .{
uniform_color.r, uniform_color.r,
@ -2928,8 +2962,8 @@ fn addPreeditCell(
coord: terminal.Coordinate, coord: terminal.Coordinate,
) !void { ) !void {
// Preedit is rendered inverted // Preedit is rendered inverted
const bg = self.foreground_color; const bg = self.foreground_color orelse self.default_foreground_color;
const fg = self.background_color; const fg = self.background_color orelse self.default_background_color;
// Render the glyph for our preedit text // Render the glyph for our preedit text
const render_ = self.font_grid.renderCodepoint( const render_ = self.font_grid.renderCodepoint(

View File

@ -89,18 +89,30 @@ texture_color_resized: usize = 0,
/// True if the window is focused /// True if the window is focused
focused: bool, focused: bool,
/// The actual foreground color. May differ from the config foreground color if /// The foreground color set by an OSC 10 sequence. If unset then the default
/// changed by a terminal application /// value from the config file is used.
foreground_color: terminal.color.RGB, foreground_color: ?terminal.color.RGB,
/// The actual background color. May differ from the config background color if /// Foreground color set in the user's config file.
/// changed by a terminal application default_foreground_color: terminal.color.RGB,
background_color: terminal.color.RGB,
/// The actual cursor color. May differ from the config cursor color if changed /// The background color set by an OSC 11 sequence. If unset then the default
/// by a terminal application /// value from the config file is used.
background_color: ?terminal.color.RGB,
/// Background color set in the user's config file.
default_background_color: terminal.color.RGB,
/// The cursor color set by an OSC 12 sequence. If unset then
/// default_cursor_color is used.
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
/// Default cursor color when no color is set explicitly by an OSC 12 command.
/// This is cursor color as set in the user's config, if any. If no cursor color
/// is set in the user's config, then the cursor color is determined by the
/// current foreground color.
default_cursor_color: ?terminal.color.RGB,
/// When `cursor_color` is null, swap the foreground and background colors of /// When `cursor_color` is null, swap the foreground and background colors of
/// the cell under the cursor for the cursor color. Otherwise, use the default /// the cell under the cursor for the cursor color. Otherwise, use the default
/// foreground color as the cursor color. /// foreground color as the cursor color.
@ -386,9 +398,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
.font_shaper_cache = font.ShaperCache.init(), .font_shaper_cache = font.ShaperCache.init(),
.draw_background = options.config.background, .draw_background = options.config.background,
.focused = true, .focused = true,
.foreground_color = options.config.foreground, .foreground_color = null,
.background_color = options.config.background, .default_foreground_color = options.config.foreground,
.cursor_color = options.config.cursor_color, .background_color = null,
.default_background_color = options.config.background,
.cursor_color = null,
.default_cursor_color = options.config.cursor_color,
.cursor_invert = options.config.cursor_invert, .cursor_invert = options.config.cursor_invert,
.surface_mailbox = options.surface_mailbox, .surface_mailbox = options.surface_mailbox,
.deferred_font_size = .{ .metrics = grid.metrics }, .deferred_font_size = .{ .metrics = grid.metrics },
@ -701,15 +716,34 @@ pub fn updateFrame(
} }
// Swap bg/fg if the terminal is reversed // Swap bg/fg if the terminal is reversed
const bg = self.background_color; const bg = self.background_color orelse self.default_background_color;
const fg = self.foreground_color; const fg = self.foreground_color orelse self.default_foreground_color;
defer { defer {
self.background_color = bg; if (self.background_color) |*c| {
self.foreground_color = fg; c.* = bg;
} else {
self.default_background_color = bg;
}
if (self.foreground_color) |*c| {
c.* = fg;
} else {
self.default_foreground_color = fg;
}
} }
if (state.terminal.modes.get(.reverse_colors)) { if (state.terminal.modes.get(.reverse_colors)) {
self.background_color = fg; if (self.background_color) |*c| {
self.foreground_color = bg; c.* = fg;
} else {
self.default_background_color = fg;
}
if (self.foreground_color) |*c| {
c.* = bg;
} else {
self.default_foreground_color = bg;
}
} }
// If our terminal screen size doesn't match our expected renderer // If our terminal screen size doesn't match our expected renderer
@ -820,7 +854,7 @@ pub fn updateFrame(
break :critical .{ break :critical .{
.full_rebuild = full_rebuild, .full_rebuild = full_rebuild,
.gl_bg = self.background_color, .gl_bg = self.background_color orelse self.default_background_color,
.screen = screen_copy, .screen = screen_copy,
.screen_type = state.terminal.active_screen, .screen_type = state.terminal.active_screen,
.mouse = state.mouse, .mouse = state.mouse,
@ -1298,12 +1332,12 @@ pub fn rebuildCells(
.extend => if (y == 0) { .extend => if (y == 0) {
self.padding_extend_top = !row.neverExtendBg( self.padding_extend_top = !row.neverExtendBg(
color_palette, color_palette,
self.background_color, self.background_color orelse self.default_background_color,
); );
} else if (y == self.size.grid().rows - 1) { } else if (y == self.size.grid().rows - 1) {
self.padding_extend_bottom = !row.neverExtendBg( self.padding_extend_bottom = !row.neverExtendBg(
color_palette, color_palette,
self.background_color, self.background_color orelse self.default_background_color,
); );
}, },
} }
@ -1412,7 +1446,7 @@ pub fn rebuildCells(
false; false;
const bg_style = style.bg(cell, color_palette); const bg_style = style.bg(cell, color_palette);
const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color;
// The final background color for the cell. // The final background color for the cell.
const bg = bg: { const bg = bg: {
@ -1432,7 +1466,7 @@ pub fn rebuildCells(
// If we don't have invert selection fg/bg set then we // If we don't have invert selection fg/bg set then we
// just use the selection background if set, otherwise // just use the selection background if set, otherwise
// the default fg color. // the default fg color.
break :bg self.config.selection_background orelse self.foreground_color; break :bg self.config.selection_background orelse self.foreground_color orelse self.default_foreground_color;
} }
// Not selected // Not selected
@ -1454,7 +1488,7 @@ pub fn rebuildCells(
// If we don't have invert selection fg/bg set // If we don't have invert selection fg/bg set
// then we just use the selection foreground if // then we just use the selection foreground if
// set, otherwise the default bg color. // set, otherwise the default bg color.
break :fg self.config.selection_foreground orelse self.background_color; break :fg self.config.selection_foreground orelse self.background_color orelse self.default_background_color;
} }
// Whether we need to use the bg color as our fg color: // Whether we need to use the bg color as our fg color:
@ -1463,7 +1497,7 @@ pub fn rebuildCells(
// Note: if selected then invert sel fg / bg must be // Note: if selected then invert sel fg / bg must be
// false since we separately handle it if true above. // false since we separately handle it if true above.
break :fg if (style.flags.inverse != selected) break :fg if (style.flags.inverse != selected)
bg_style orelse self.background_color bg_style orelse self.background_color orelse self.default_background_color
else else
fg_style; fg_style;
}; };
@ -1490,7 +1524,7 @@ pub fn rebuildCells(
// If we have a background and its not the default background // If we have a background and its not the default background
// then we apply background opacity // then we apply background opacity
if (style.bg(cell, color_palette) != null and !rgb.eql(self.background_color)) { if (style.bg(cell, color_palette) != null and !rgb.eql(self.background_color orelse self.default_background_color)) {
break :bg_alpha default; break :bg_alpha default;
} }
@ -1699,12 +1733,12 @@ pub fn rebuildCells(
break :cursor_style; break :cursor_style;
} }
const cursor_color = self.cursor_color orelse color: { const cursor_color = self.cursor_color orelse self.default_cursor_color orelse color: {
if (self.cursor_invert) { if (self.cursor_invert) {
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color;
} else { } else {
break :color self.foreground_color; break :color self.foreground_color orelse self.default_foreground_color;
} }
}; };
@ -1713,11 +1747,11 @@ pub fn rebuildCells(
if (cell.mode.isFg() and cell.mode != .fg_color) { if (cell.mode.isFg() and cell.mode != .fg_color) {
const cell_color = if (self.cursor_invert) blk: { const cell_color = if (self.cursor_invert) blk: {
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color; break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color;
} else if (self.config.cursor_text) |txt| } else if (self.config.cursor_text) |txt|
txt txt
else else
self.background_color; self.background_color orelse self.default_background_color;
cell.r = cell_color.r; cell.r = cell_color.r;
cell.g = cell_color.g; cell.g = cell_color.g;
@ -1742,8 +1776,8 @@ fn addPreeditCell(
y: usize, y: usize,
) !void { ) !void {
// Preedit is rendered inverted // Preedit is rendered inverted
const bg = self.foreground_color; const bg = self.foreground_color orelse self.default_foreground_color;
const fg = self.background_color; const fg = self.background_color orelse self.default_background_color;
// Render the glyph for our preedit text // Render the glyph for our preedit text
const render_ = self.font_grid.renderCodepoint( const render_ = self.font_grid.renderCodepoint(
@ -2122,10 +2156,10 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
self.font_shaper_cache = font_shaper_cache; self.font_shaper_cache = font_shaper_cache;
// Set our new colors // Set our new colors
self.background_color = config.background; self.default_background_color = config.background;
self.foreground_color = config.foreground; self.default_foreground_color = config.foreground;
self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null;
self.cursor_invert = config.cursor_invert; self.cursor_invert = config.cursor_invert;
self.cursor_color = if (!config.cursor_invert) config.cursor_color else null;
// Update our uniforms // Update our uniforms
self.deferred_config = .{}; self.deferred_config = .{};

View File

@ -42,13 +42,11 @@ pub const Message = union(enum) {
old_key: font.SharedGridSet.Key, old_key: font.SharedGridSet.Key,
}, },
/// Change the foreground color. This can be done separately from changing /// Change the foreground color as set by an OSC 10 command, if any.
/// the config file in response to an OSC 10 command. foreground_color: ?terminal.color.RGB,
foreground_color: terminal.color.RGB,
/// Change the background color. This can be done separately from changing /// Change the background color as set by an OSC 11 command, if any.
/// the config file in response to an OSC 11 command. background_color: ?terminal.color.RGB,
background_color: terminal.color.RGB,
/// Change the cursor color. This can be done separately from changing the /// Change the cursor color. This can be done separately from changing the
/// config file in response to an OSC 12 command. /// config file in response to an OSC 12 command.

View File

@ -1347,7 +1347,7 @@ pub const StreamHandler = struct {
.foreground => { .foreground => {
self.foreground_color = null; self.foreground_color = null;
_ = self.renderer_mailbox.push(.{ _ = self.renderer_mailbox.push(.{
.foreground_color = self.default_foreground_color, .foreground_color = self.foreground_color,
}, .{ .forever = {} }); }, .{ .forever = {} });
self.surfaceMessageWriter(.{ .color_change = .{ self.surfaceMessageWriter(.{ .color_change = .{
@ -1358,7 +1358,7 @@ pub const StreamHandler = struct {
.background => { .background => {
self.background_color = null; self.background_color = null;
_ = self.renderer_mailbox.push(.{ _ = self.renderer_mailbox.push(.{
.background_color = self.default_background_color, .background_color = self.background_color,
}, .{ .forever = {} }); }, .{ .forever = {} });
self.surfaceMessageWriter(.{ .color_change = .{ self.surfaceMessageWriter(.{ .color_change = .{
@ -1370,7 +1370,7 @@ pub const StreamHandler = struct {
self.cursor_color = null; self.cursor_color = null;
_ = self.renderer_mailbox.push(.{ _ = self.renderer_mailbox.push(.{
.cursor_color = self.default_cursor_color, .cursor_color = self.cursor_color,
}, .{ .forever = {} }); }, .{ .forever = {} });
if (self.default_cursor_color) |color| { if (self.default_cursor_color) |color| {
@ -1490,15 +1490,15 @@ pub const StreamHandler = struct {
const msg: renderer.Message = switch (special) { const msg: renderer.Message = switch (special) {
.foreground => msg: { .foreground => msg: {
self.foreground_color = null; self.foreground_color = null;
break :msg .{ .foreground_color = self.default_foreground_color }; break :msg .{ .foreground_color = self.foreground_color };
}, },
.background => msg: { .background => msg: {
self.background_color = null; self.background_color = null;
break :msg .{ .background_color = self.default_background_color }; break :msg .{ .background_color = self.background_color };
}, },
.cursor => msg: { .cursor => msg: {
self.cursor_color = null; self.cursor_color = null;
break :msg .{ .cursor_color = self.default_cursor_color }; break :msg .{ .cursor_color = self.cursor_color };
}, },
else => { else => {
log.warn( log.warn(