Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ font_grid_set: font.SharedGridSet,
last_notification_time: ?std.Io.Timestamp = null,
last_notification_digest: u64 = 0,

/// The current system color scheme. This is kept separate from conditional
/// configuration state so mode 2031 remains correct for non-conditional
/// themes.
system_color_scheme: std.atomic.Value(apprt.ColorScheme) = .init(.light),

/// The conditional state of the configuration. See the equivalent field
/// in the Surface struct for more information. In this case, this applies
/// to the app-level config and as a default for new surfaces.
Expand Down Expand Up @@ -392,6 +397,11 @@ pub fn colorSchemeEvent(
rt_app: *apprt.App,
scheme: apprt.ColorScheme,
) !void {
// Cache the system color scheme and notify every surface when it changes.
if (self.system_color_scheme.swap(scheme, .monotonic) != scheme) {
try self.performAllAction(rt_app, .system_color_scheme_changed);
}

const new_scheme: configpkg.ConditionalState.Theme = switch (scheme) {
.light => .light,
.dark => .dark,
Expand Down
12 changes: 9 additions & 3 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ pub fn init(
.size = size,
.full_config = config,
.config = try termio.Termio.DerivedConfig.init(alloc, config),
.system_color_scheme = &app.system_color_scheme,
.backend = .{ .exec = io_exec },
.mailbox = io_mailbox,
.renderer_state = &self.renderer_state,
Expand Down Expand Up @@ -4756,9 +4757,6 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
// Setup our conditional state which has the current color theme.
self.config_conditional_state.theme = new_scheme;
self.notifyConfigConditionalState();

// If mode 2031 is on, then we report the change live.
self.queueIo(.{ .color_scheme_report = .{ .force = false } }, .unlocked);
}

pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate {
Expand Down Expand Up @@ -4845,6 +4843,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
}

switch (action.scoped(.surface).?) {
.system_color_scheme_changed => {
// Termio suppresses this report unless mode 2031 is enabled.
self.queueIo(
.{ .color_scheme_report = .{ .force = false } },
.unlocked,
);
},

.csi, .esc => |data| {
// We need to send the CSI/ESC sequence as a single write request.
// If you split it across two then the shell can interpret it
Expand Down
38 changes: 32 additions & 6 deletions src/apprt/gtk/class/application.zig
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ pub const Application = extern struct {
/// only be set by the main loop thread.
running: bool = false,

/// Whether the current configuration reload is a soft reload.
config_reload_soft: bool = false,

/// The timer used to quit the application after the last window is
/// closed. Even if there is no quit delay set, this is the state
/// used to determine to close the app.
Expand Down Expand Up @@ -243,6 +246,11 @@ pub const Application = extern struct {
return gobject.ext.cast(Self, app).?;
}

/// Returns whether the current configuration reload is soft.
pub fn configReloadIsSoft(self: *Self) bool {
return self.private().config_reload_soft;
}

/// Creates a new Application instance.
///
/// This does a lot more work than a typical class instantiation,
Expand Down Expand Up @@ -1280,6 +1288,10 @@ pub const Application = extern struct {
const priv = self.private();
priv.config.unref();
priv.config = config.ref();

// Apply GTK appearance settings from the new config.
self.syncStyleManager();

self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);

// Show our errors if we have any
Expand Down Expand Up @@ -1421,15 +1433,12 @@ pub const Application = extern struct {
}
}

/// Setup the style manager on startup. The primary task here is to
/// setup our initial light/dark mode based on the configuration and
/// setup listeners for changes to the style manager.
fn startupStyleManager(self: *Self) void {
/// Sync the configured window theme.
fn syncStyleManager(self: *Self) void {
const priv = self.private();
const config = priv.config.get();

// Setup our initial light/dark
const style = self.as(adw.Application).getStyleManager();

style.setColorScheme(switch (config.@"window-theme") {
.auto, .ghostty => auto: {
const lum = config.background.toTerminalRGB().perceivedLuminance();
Expand All @@ -1442,6 +1451,16 @@ pub const Application = extern struct {
.dark => .force_dark,
.light => .force_light,
});
}

/// Setup the style manager on startup. The primary task here is to
/// setup our initial light/dark mode based on the configuration and
/// setup listeners for changes to the style manager.
fn startupStyleManager(self: *Self) void {
const style = self.as(adw.Application).getStyleManager();

// Setup our initial light/dark.
self.syncStyleManager();

// Setup color change notifications
_ = gobject.Object.signals.notify.connect(
Expand Down Expand Up @@ -2721,6 +2740,13 @@ const Action = struct {
};
defer config.unref();

// Track whether this config propagation came from an internal soft
// reload so windows can avoid showing a duplicate reload toast.
const priv = self.private();
const previous_soft = priv.config_reload_soft;
priv.config_reload_soft = opts.soft;
defer priv.config_reload_soft = previous_soft;

// Update the proper target. This will trigger a `config_change`
// apprt action which will propagate the config properly to our
// property system.
Expand Down
10 changes: 6 additions & 4 deletions src/apprt/gtk/class/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1119,10 +1119,12 @@ pub const Window = extern struct {
self: *Self,
) callconv(.c) void {
const priv = self.private();
if (priv.config) |config_obj| {
const config = config_obj.get();
if (config.@"app-notifications".@"config-reload") {
self.addToast(i18n._("Reloaded the configuration"));
if (!Application.default().configReloadIsSoft()) {
if (priv.config) |config_obj| {
const config = config_obj.get();
if (config.@"app-notifications".@"config-reload") {
self.addToast(i18n._("Reloaded the configuration"));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/apprt/structs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub const ClipboardRequest = union(ClipboardRequestType) {
};

/// The color scheme in use (light vs dark).
pub const ColorScheme = enum(u2) {
pub const ColorScheme = enum(u8) {
light = 0,
dark = 1,
};
Expand Down
4 changes: 4 additions & 0 deletions src/input/Binding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,9 @@ pub const Action = union(enum) {
/// this will report performable as false.
deactivate_all_key_tables,

/// Notify every terminal that the system color scheme changed.
system_color_scheme_changed,

/// Quit Ghostty.
quit,

Expand Down Expand Up @@ -1365,6 +1368,7 @@ pub const Action = union(enum) {
.start_search,
.end_search,
.reset,
.system_color_scheme_changed,
.copy_to_clipboard,
.copy_url_to_clipboard,
.copy_title_to_clipboard,
Expand Down
1 change: 1 addition & 0 deletions src/input/command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ fn actionCommands(action: Action.Key) []const Command {
=> comptime &.{},

// No commands for obvious reasons
.system_color_scheme_changed,
.ignore,
.unbind,
=> comptime &.{},
Expand Down
4 changes: 4 additions & 0 deletions src/termio/Options.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The options that are used to configure a terminal IO implementation.

const std = @import("std");
const xev = @import("../global.zig").xev;
const apprt = @import("../apprt.zig");
const renderer = @import("../renderer.zig");
Expand All @@ -17,6 +18,9 @@ full_config: *const Config,
/// The derived configuration for this termio implementation.
config: termio.Termio.DerivedConfig,

/// The system color scheme used for mode 2031 reports.
system_color_scheme: *const std.atomic.Value(apprt.ColorScheme),

/// The backend for termio that implements where reads/writes are sourced.
backend: termio.Backend,

Expand Down
13 changes: 9 additions & 4 deletions src/termio/Termio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ backend: termio.Backend,
/// The derived configuration for this termio implementation.
config: DerivedConfig,

/// The system color scheme used for mode 2031 reports.
system_color_scheme: *const std.atomic.Value(apprt.ColorScheme),

/// The terminal emulator internal state. This is the abstract "terminal"
/// that manages input, grid updating, etc. and is renderer-agnostic. It
/// just stores internal state about a grid.
Expand Down Expand Up @@ -300,6 +303,7 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
.alloc = alloc,
.terminal = term,
.config = opts.config,
.system_color_scheme = opts.system_color_scheme,
.renderer_state = opts.renderer_state,
.renderer_wakeup = opts.renderer_wakeup,
.renderer_mailbox = opts.renderer_mailbox,
Expand Down Expand Up @@ -715,10 +719,11 @@ pub fn colorSchemeReportLocked(self: *Termio, td: *ThreadData, force: bool) !voi
if (!force and !self.renderer_state.terminal.modes.get(.report_color_scheme)) {
return;
}
const scheme: terminalpkg.device_status.ColorScheme = switch (self.config.conditional_state.theme) {
.light => .light,
.dark => .dark,
};
const scheme: terminalpkg.device_status.ColorScheme =
switch (self.system_color_scheme.load(.monotonic)) {
.light => .light,
.dark => .dark,
};

var buf: [terminalpkg.device_status.max_color_scheme_report_encode_size]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
Expand Down
Loading