apprt: action to change conditional state, implement for embedded

This commit is contained in:
Mitchell Hashimoto
2024-11-19 15:35:42 -08:00
parent d2566287e9
commit b7f1eaa145
8 changed files with 97 additions and 25 deletions

View File

@ -567,6 +567,7 @@ typedef enum {
GHOSTTY_ACTION_SECURE_INPUT, GHOSTTY_ACTION_SECURE_INPUT,
GHOSTTY_ACTION_KEY_SEQUENCE, GHOSTTY_ACTION_KEY_SEQUENCE,
GHOSTTY_ACTION_COLOR_CHANGE, GHOSTTY_ACTION_COLOR_CHANGE,
GHOSTTY_ACTION_CONFIG_CHANGE_CONDITIONAL_STATE,
} ghostty_action_tag_e; } ghostty_action_tag_e;
typedef union { typedef union {

View File

@ -94,11 +94,6 @@ keyboard: Keyboard,
/// less important. /// less important.
pressed_key: ?input.KeyEvent = null, pressed_key: ?input.KeyEvent = null,
/// The current color scheme of the GUI element containing this surface.
/// This will default to light until the apprt sends us the actual color
/// scheme. This is used by mode 3031 and CSI 996 n.
color_scheme: apprt.ColorScheme = .light,
/// The hash value of the last keybinding trigger that we performed. This /// The hash value of the last keybinding trigger that we performed. This
/// is only set if the last key input matched a keybinding, consumed it, /// is only set if the last key input matched a keybinding, consumed it,
/// and performed it. This is used to prevent sending release/repeat events /// and performed it. This is used to prevent sending release/repeat events
@ -121,6 +116,12 @@ size: renderer.Size,
/// the lifetime of. This makes updating config at runtime easier. /// the lifetime of. This makes updating config at runtime easier.
config: DerivedConfig, config: DerivedConfig,
/// The conditional state of the configuration. This can affect
/// how certain configurations take effect such as light/dark mode.
/// This is managed completely by Ghostty core but an apprt action
/// is sent whenever this changes.
config_conditional_state: configpkg.ConditionalState,
/// This is set to true if our IO thread notifies us our child exited. /// This is set to true if our IO thread notifies us our child exited.
/// This is used to determine if we need to confirm, hold open, etc. /// This is used to determine if we need to confirm, hold open, etc.
child_exited: bool = false, child_exited: bool = false,
@ -480,6 +481,7 @@ pub fn init(
.io_thr = undefined, .io_thr = undefined,
.size = size, .size = size,
.config = derived_config, .config = derived_config,
.config_conditional_state = .{},
}; };
// The command we're going to execute // The command we're going to execute
@ -777,7 +779,7 @@ pub fn needsConfirmQuit(self: *Surface) bool {
/// surface. /// surface.
pub fn handleMessage(self: *Surface, msg: Message) !void { pub fn handleMessage(self: *Surface, msg: Message) !void {
switch (msg) { switch (msg) {
.change_config => |config| try self.changeConfig(config), .change_config => |config| try self.updateConfig(config),
.set_title => |*v| { .set_title => |*v| {
// We ignore the message in case the title was set via config. // We ignore the message in case the title was set via config.
@ -935,7 +937,7 @@ fn passwordInput(self: *Surface, v: bool) !void {
/// Sends a DSR response for the current color scheme to the pty. /// Sends a DSR response for the current color scheme to the pty.
fn reportColorScheme(self: *Surface) !void { fn reportColorScheme(self: *Surface) !void {
const output = switch (self.color_scheme) { const output = switch (self.config_conditional_state.theme) {
.light => "\x1B[?997;2n", .light => "\x1B[?997;2n",
.dark => "\x1B[?997;1n", .dark => "\x1B[?997;1n",
}; };
@ -1058,8 +1060,25 @@ fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
}; };
} }
/// Update our configuration at runtime. /// This should be called anytime `config_conditional_state` changes
fn changeConfig(self: *Surface, config: *const configpkg.Config) !void { /// so that the apprt can reload the configuration.
fn notifyConfigConditionalState(self: *Surface) void {
self.rt_app.performAction(
.{ .surface = self },
.config_change_conditional_state,
{},
) catch |err| {
log.warn("failed to notify app of config state change err={}", .{err});
};
}
/// Update our configuration at runtime. This can be called by the apprt
/// to set a surface-specific configuration that differs from the app
/// or other surfaces.
pub fn updateConfig(
self: *Surface,
config: *const configpkg.Config,
) !void {
// Update our new derived config immediately // Update our new derived config immediately
const derived = DerivedConfig.init(self.alloc, config) catch |err| { const derived = DerivedConfig.init(self.alloc, config) catch |err| {
// If the derivation fails then we just log and return. We don't // If the derivation fails then we just log and return. We don't
@ -3590,11 +3609,17 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
crash.sentry.thread_state = self.crashThreadState(); crash.sentry.thread_state = self.crashThreadState();
defer crash.sentry.thread_state = null; defer crash.sentry.thread_state = null;
// If our scheme didn't change, then we don't do anything. const new_scheme: configpkg.ConditionalState.Theme = switch (scheme) {
if (self.color_scheme == scheme) return; .light => .light,
.dark => .dark,
};
// Set our new scheme // If our scheme didn't change, then we don't do anything.
self.color_scheme = scheme; if (self.config_conditional_state.theme == new_scheme) return;
// 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. // If mode 2031 is on, then we report the change live.
const report = report: { const report = report: {

View File

@ -193,6 +193,13 @@ pub const Action = union(Key) {
/// such as OSC 10/11. /// such as OSC 10/11.
color_change: ColorChange, color_change: ColorChange,
/// The state of conditionals in the configuration has changed, so
/// the configuration should be reloaded. The apprt doesn't need
/// to do a full physical reload; it should call the
/// `changeConditionalState` function and then `updateConfig`
/// on the app or surface.
config_change_conditional_state,
/// Sync with: ghostty_action_tag_e /// Sync with: ghostty_action_tag_e
pub const Key = enum(c_int) { pub const Key = enum(c_int) {
new_window, new_window,
@ -228,6 +235,7 @@ pub const Action = union(Key) {
secure_input, secure_input,
key_sequence, key_sequence,
color_change, color_change,
config_change_conditional_state,
}; };
/// Sync with: ghostty_action_u /// Sync with: ghostty_action_u

View File

@ -430,6 +430,28 @@ pub const App = struct {
comptime action: apprt.Action.Key, comptime action: apprt.Action.Key,
value: apprt.Action.Value(action), value: apprt.Action.Value(action),
) !void { ) !void {
// Special case certain actions before they are sent to the
// embedded apprt.
self.performPreAction(target, action, value);
log.debug("dispatching action target={s} action={} value={}", .{
@tagName(target),
action,
value,
});
self.opts.action(
self,
target.cval(),
@unionInit(apprt.Action, @tagName(action), value).cval(),
);
}
fn performPreAction(
self: *App,
target: apprt.Target,
comptime action: apprt.Action.Key,
value: apprt.Action.Value(action),
) void {
// Special case certain actions before they are sent to the embedder // Special case certain actions before they are sent to the embedder
switch (action) { switch (action) {
.set_title => switch (target) { .set_title => switch (target) {
@ -443,19 +465,32 @@ pub const App = struct {
}, },
}, },
.config_change_conditional_state => switch (target) {
.app => {},
.surface => |surface| action: {
// Build our new configuration. We can free the memory
// immediately after because the surface will derive any
// values it needs to.
var new_config = self.config.changeConditionalState(
surface.config_conditional_state,
) catch |err| {
// Not a big deal if we error... we just don't update
// the config. We log the error and move on.
log.warn("error changing config conditional state err={}", .{err});
break :action;
};
defer new_config.deinit();
// Update our surface.
surface.updateConfig(&new_config) catch |err| {
log.warn("error updating surface config for state change err={}", .{err});
break :action;
};
},
},
else => {}, else => {},
} }
log.debug("dispatching action target={s} action={} value={}", .{
@tagName(target),
action,
value,
});
self.opts.action(
self,
target.cval(),
@unionInit(apprt.Action, @tagName(action), value).cval(),
);
} }
}; };

View File

@ -225,6 +225,7 @@ pub const App = struct {
.renderer_health, .renderer_health,
.color_change, .color_change,
.pwd, .pwd,
.config_change_conditional_state,
=> log.info("unimplemented action={}", .{action}), => log.info("unimplemented action={}", .{action}),
} }
} }

View File

@ -487,6 +487,7 @@ pub fn performAction(
.render_inspector, .render_inspector,
.renderer_health, .renderer_health,
.color_change, .color_change,
.config_change_conditional_state,
=> log.warn("unimplemented action={}", .{action}), => log.warn("unimplemented action={}", .{action}),
} }
} }

View File

@ -7,6 +7,7 @@ pub const string = @import("config/string.zig");
pub const edit = @import("config/edit.zig"); pub const edit = @import("config/edit.zig");
pub const url = @import("config/url.zig"); pub const url = @import("config/url.zig");
pub const ConditionalState = conditional.State;
pub const FileFormatter = formatter.FileFormatter; pub const FileFormatter = formatter.FileFormatter;
pub const entryFormatter = formatter.entryFormatter; pub const entryFormatter = formatter.entryFormatter;
pub const formatEntry = formatter.formatEntry; pub const formatEntry = formatter.formatEntry;

View File

@ -2583,7 +2583,7 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
/// deleted a file that was referenced in the configuration, then the /// deleted a file that was referenced in the configuration, then the
/// configuration can still be reloaded. /// configuration can still be reloaded.
pub fn changeConditionalState( pub fn changeConditionalState(
self: *Config, self: *const Config,
new: conditional.State, new: conditional.State,
) !Config { ) !Config {
// Create our new configuration // Create our new configuration