mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #117 from mitchellh/config-stuff
Reloadable Configuration
This commit is contained in:
@ -216,6 +216,7 @@ typedef struct {
|
|||||||
} ghostty_surface_config_s;
|
} ghostty_surface_config_s;
|
||||||
|
|
||||||
typedef void (*ghostty_runtime_wakeup_cb)(void *);
|
typedef void (*ghostty_runtime_wakeup_cb)(void *);
|
||||||
|
typedef const ghostty_config_t (*ghostty_runtime_reload_config_cb)(void *);
|
||||||
typedef void (*ghostty_runtime_set_title_cb)(void *, const char *);
|
typedef void (*ghostty_runtime_set_title_cb)(void *, const char *);
|
||||||
typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *);
|
typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *);
|
||||||
typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *);
|
typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *);
|
||||||
@ -226,6 +227,7 @@ typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direc
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
void *userdata;
|
void *userdata;
|
||||||
ghostty_runtime_wakeup_cb wakeup_cb;
|
ghostty_runtime_wakeup_cb wakeup_cb;
|
||||||
|
ghostty_runtime_reload_config_cb reload_config_cb;
|
||||||
ghostty_runtime_set_title_cb set_title_cb;
|
ghostty_runtime_set_title_cb set_title_cb;
|
||||||
ghostty_runtime_read_clipboard_cb read_clipboard_cb;
|
ghostty_runtime_read_clipboard_cb read_clipboard_cb;
|
||||||
ghostty_runtime_write_clipboard_cb write_clipboard_cb;
|
ghostty_runtime_write_clipboard_cb write_clipboard_cb;
|
||||||
@ -241,6 +243,7 @@ int ghostty_init(void);
|
|||||||
|
|
||||||
ghostty_config_t ghostty_config_new();
|
ghostty_config_t ghostty_config_new();
|
||||||
void ghostty_config_free(ghostty_config_t);
|
void ghostty_config_free(ghostty_config_t);
|
||||||
|
void ghostty_config_load_cli_args(ghostty_config_t);
|
||||||
void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
|
void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
|
||||||
void ghostty_config_load_default_files(ghostty_config_t);
|
void ghostty_config_load_default_files(ghostty_config_t);
|
||||||
void ghostty_config_load_recursive_files(ghostty_config_t);
|
void ghostty_config_load_recursive_files(ghostty_config_t);
|
||||||
|
@ -12,12 +12,25 @@ extension Ghostty {
|
|||||||
/// The readiness value of the state.
|
/// The readiness value of the state.
|
||||||
@Published var readiness: AppReadiness = .loading
|
@Published var readiness: AppReadiness = .loading
|
||||||
|
|
||||||
/// The ghostty global configuration.
|
/// The ghostty global configuration. This should only be changed when it is definitely
|
||||||
var config: ghostty_config_t? = nil
|
/// safe to change. It is definite safe to change only when the embedded app runtime
|
||||||
|
/// in Ghostty says so (usually, only in a reload configuration callback).
|
||||||
|
var config: ghostty_config_t? = nil {
|
||||||
|
didSet {
|
||||||
|
// Free the old value whenever we change
|
||||||
|
guard let old = oldValue else { return }
|
||||||
|
ghostty_config_free(old)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
||||||
/// in theory you can have multiple... I don't know why you would...
|
/// in theory you can have multiple... I don't know why you would...
|
||||||
var app: ghostty_app_t? = nil
|
var app: ghostty_app_t? = nil {
|
||||||
|
didSet {
|
||||||
|
guard let old = oldValue else { return }
|
||||||
|
ghostty_app_free(old)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Cached clipboard string for `read_clipboard` callback.
|
/// Cached clipboard string for `read_clipboard` callback.
|
||||||
private var cached_clipboard_string: String? = nil
|
private var cached_clipboard_string: String? = nil
|
||||||
@ -31,29 +44,18 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
guard let cfg = ghostty_config_new() else {
|
guard let cfg = Self.reloadConfig() else {
|
||||||
GhosttyApp.logger.critical("ghostty_config_new failed")
|
|
||||||
readiness = .error
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.config = cfg;
|
self.config = cfg;
|
||||||
|
|
||||||
// Load our configuration files from the home directory.
|
|
||||||
ghostty_config_load_default_files(cfg);
|
|
||||||
ghostty_config_load_recursive_files(cfg);
|
|
||||||
|
|
||||||
// TODO: we'd probably do some config loading here... for now we'd
|
|
||||||
// have to do this synchronously. When we support config updating we can do
|
|
||||||
// this async and update later.
|
|
||||||
|
|
||||||
// Finalize will make our defaults available.
|
|
||||||
ghostty_config_finalize(cfg)
|
|
||||||
|
|
||||||
// Create our "runtime" config. The "runtime" is the configuration that ghostty
|
// Create our "runtime" config. The "runtime" is the configuration that ghostty
|
||||||
// uses to interface with the application runtime environment.
|
// uses to interface with the application runtime environment.
|
||||||
var runtime_cfg = ghostty_runtime_config_s(
|
var runtime_cfg = ghostty_runtime_config_s(
|
||||||
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
||||||
wakeup_cb: { userdata in AppState.wakeup(userdata) },
|
wakeup_cb: { userdata in AppState.wakeup(userdata) },
|
||||||
|
reload_config_cb: { userdata in AppState.reloadConfig(userdata) },
|
||||||
set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) },
|
set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) },
|
||||||
read_clipboard_cb: { userdata in AppState.readClipboard(userdata) },
|
read_clipboard_cb: { userdata in AppState.readClipboard(userdata) },
|
||||||
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
|
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
|
||||||
@ -74,8 +76,32 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
ghostty_app_free(app)
|
// This will force the didSet callbacks to run which free.
|
||||||
ghostty_config_free(config)
|
self.app = nil
|
||||||
|
self.config = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes a new configuration and loads all the values.
|
||||||
|
static func reloadConfig() -> ghostty_config_t? {
|
||||||
|
// Initialize the global configuration.
|
||||||
|
guard let cfg = ghostty_config_new() else {
|
||||||
|
GhosttyApp.logger.critical("ghostty_config_new failed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load our configuration files from the home directory.
|
||||||
|
ghostty_config_load_default_files(cfg);
|
||||||
|
ghostty_config_load_cli_args(cfg);
|
||||||
|
ghostty_config_load_recursive_files(cfg);
|
||||||
|
|
||||||
|
// TODO: we'd probably do some config loading here... for now we'd
|
||||||
|
// have to do this synchronously. When we support config updating we can do
|
||||||
|
// this async and update later.
|
||||||
|
|
||||||
|
// Finalize will make our defaults available.
|
||||||
|
ghostty_config_finalize(cfg)
|
||||||
|
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func appTick() {
|
func appTick() {
|
||||||
@ -140,6 +166,20 @@ extension Ghostty {
|
|||||||
pb.setString(valueStr, forType: .string)
|
pb.setString(valueStr, forType: .string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
||||||
|
guard let newConfig = AppState.reloadConfig() else {
|
||||||
|
GhosttyApp.logger.warning("failed to reload configuration")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the new config. This will automatically free the old config.
|
||||||
|
// It is safe to free the old config from within this function call.
|
||||||
|
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
|
state.config = newConfig
|
||||||
|
|
||||||
|
return newConfig
|
||||||
|
}
|
||||||
|
|
||||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
|
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
|
||||||
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
|
|
||||||
|
32
src/App.zig
32
src/App.zig
@ -18,7 +18,6 @@ const renderer = @import("renderer.zig");
|
|||||||
const font = @import("font/main.zig");
|
const font = @import("font/main.zig");
|
||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const objc = @import("objc");
|
const objc = @import("objc");
|
||||||
const DevMode = @import("DevMode.zig");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
|
|
||||||
@ -30,9 +29,6 @@ alloc: Allocator,
|
|||||||
/// The list of surfaces that are currently active.
|
/// The list of surfaces that are currently active.
|
||||||
surfaces: SurfaceList,
|
surfaces: SurfaceList,
|
||||||
|
|
||||||
// The configuration for the app.
|
|
||||||
config: *const Config,
|
|
||||||
|
|
||||||
/// The mailbox that can be used to send this thread messages. Note
|
/// The mailbox that can be used to send this thread messages. Note
|
||||||
/// this is a blocking queue so if it is full you will get errors (or block).
|
/// this is a blocking queue so if it is full you will get errors (or block).
|
||||||
mailbox: Mailbox.Queue,
|
mailbox: Mailbox.Queue,
|
||||||
@ -45,17 +41,12 @@ quit: bool,
|
|||||||
/// "startup" logic.
|
/// "startup" logic.
|
||||||
pub fn create(
|
pub fn create(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
config: *const Config,
|
|
||||||
) !*App {
|
) !*App {
|
||||||
// If we have DevMode on, store the config so we can show it
|
|
||||||
if (DevMode.enabled) DevMode.instance.config = config;
|
|
||||||
|
|
||||||
var app = try alloc.create(App);
|
var app = try alloc.create(App);
|
||||||
errdefer alloc.destroy(app);
|
errdefer alloc.destroy(app);
|
||||||
app.* = .{
|
app.* = .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.surfaces = .{},
|
.surfaces = .{},
|
||||||
.config = config,
|
|
||||||
.mailbox = .{},
|
.mailbox = .{},
|
||||||
.quit = false,
|
.quit = false,
|
||||||
};
|
};
|
||||||
@ -97,6 +88,16 @@ pub fn tick(self: *App, rt_app: *apprt.App) !bool {
|
|||||||
return self.quit or self.surfaces.items.len == 0;
|
return self.quit or self.surfaces.items.len == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the configuration associated with the app. This can only be
|
||||||
|
/// called from the main thread. The caller owns the config memory. The
|
||||||
|
/// memory can be freed immediately when this returns.
|
||||||
|
pub fn updateConfig(self: *App, config: *const Config) !void {
|
||||||
|
// Go through and update all of the surface configurations.
|
||||||
|
for (self.surfaces.items) |surface| {
|
||||||
|
try surface.core_surface.handleMessage(.{ .change_config = config });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an initialized surface. This is really only for the runtime
|
/// Add an initialized surface. This is really only for the runtime
|
||||||
/// implementations to call and should NOT be called by general app users.
|
/// implementations to call and should NOT be called by general app users.
|
||||||
/// The surface must be from the pool.
|
/// The surface must be from the pool.
|
||||||
@ -123,6 +124,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
|
|||||||
while (self.mailbox.pop()) |message| {
|
while (self.mailbox.pop()) |message| {
|
||||||
log.debug("mailbox message={s}", .{@tagName(message)});
|
log.debug("mailbox message={s}", .{@tagName(message)});
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
.reload_config => try self.reloadConfig(rt_app),
|
||||||
.new_window => |msg| try self.newWindow(rt_app, msg),
|
.new_window => |msg| try self.newWindow(rt_app, msg),
|
||||||
.close => |surface| try self.closeSurface(rt_app, surface),
|
.close => |surface| try self.closeSurface(rt_app, surface),
|
||||||
.quit => try self.setQuit(),
|
.quit => try self.setQuit(),
|
||||||
@ -132,6 +134,14 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reloadConfig(self: *App, rt_app: *apprt.App) !void {
|
||||||
|
log.debug("reloading configuration", .{});
|
||||||
|
if (try rt_app.reloadConfig()) |new| {
|
||||||
|
log.debug("new configuration received, applying", .{});
|
||||||
|
try self.updateConfig(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void {
|
fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void {
|
||||||
if (!self.hasSurface(surface)) return;
|
if (!self.hasSurface(surface)) return;
|
||||||
rt_app.closeSurface(surface.rt_surface);
|
rt_app.closeSurface(surface.rt_surface);
|
||||||
@ -193,6 +203,10 @@ fn hasSurface(self: *App, surface: *Surface) bool {
|
|||||||
|
|
||||||
/// The message types that can be sent to the app thread.
|
/// The message types that can be sent to the app thread.
|
||||||
pub const Message = union(enum) {
|
pub const Message = union(enum) {
|
||||||
|
/// Reload the configuration for the entire app and propagate it to
|
||||||
|
/// all the active surfaces.
|
||||||
|
reload_config: void,
|
||||||
|
|
||||||
/// Create a new terminal window.
|
/// Create a new terminal window.
|
||||||
new_window: NewWindow,
|
new_window: NewWindow,
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ pub var instance: DevMode = .{};
|
|||||||
visible: bool = false,
|
visible: bool = false,
|
||||||
|
|
||||||
/// Our app config
|
/// Our app config
|
||||||
config: ?*const Config = null,
|
config: ?Config = null,
|
||||||
|
|
||||||
/// The surface we're tracking.
|
/// The surface we're tracking.
|
||||||
surface: ?*Surface = null,
|
surface: ?*Surface = null,
|
||||||
|
124
src/Surface.zig
124
src/Surface.zig
@ -19,6 +19,7 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const renderer = @import("renderer.zig");
|
const renderer = @import("renderer.zig");
|
||||||
const termio = @import("termio.zig");
|
const termio = @import("termio.zig");
|
||||||
const objc = @import("objc");
|
const objc = @import("objc");
|
||||||
@ -28,7 +29,7 @@ const font = @import("font/main.zig");
|
|||||||
const Command = @import("Command.zig");
|
const Command = @import("Command.zig");
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
const terminal = @import("terminal/main.zig");
|
const terminal = @import("terminal/main.zig");
|
||||||
const Config = @import("config.zig").Config;
|
const configpkg = @import("config.zig");
|
||||||
const input = @import("input.zig");
|
const input = @import("input.zig");
|
||||||
const DevMode = @import("DevMode.zig");
|
const DevMode = @import("DevMode.zig");
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
@ -70,7 +71,6 @@ renderer_thr: std.Thread,
|
|||||||
|
|
||||||
/// Mouse state.
|
/// Mouse state.
|
||||||
mouse: Mouse,
|
mouse: Mouse,
|
||||||
mouse_interval: u64,
|
|
||||||
|
|
||||||
/// The terminal IO handler.
|
/// The terminal IO handler.
|
||||||
io: termio.Impl,
|
io: termio.Impl,
|
||||||
@ -85,8 +85,10 @@ cell_size: renderer.CellSize,
|
|||||||
/// Explicit padding due to configuration
|
/// Explicit padding due to configuration
|
||||||
padding: renderer.Padding,
|
padding: renderer.Padding,
|
||||||
|
|
||||||
/// The app configuration
|
/// The configuration derived from the main config. We "derive" it so that
|
||||||
config: *const Config,
|
/// we don't have a shared pointer hanging around that we need to worry about
|
||||||
|
/// the lifetime of. This makes updating config at runtime easier.
|
||||||
|
config: DerivedConfig,
|
||||||
|
|
||||||
/// Set to true for a single GLFW key/char callback cycle to cause the
|
/// Set to true for a single GLFW key/char callback cycle to cause the
|
||||||
/// char callback to ignore. GLFW seems to always do key followed by char
|
/// char callback to ignore. GLFW seems to always do key followed by char
|
||||||
@ -123,13 +125,50 @@ const Mouse = struct {
|
|||||||
event_point: terminal.point.Viewport = .{},
|
event_point: terminal.point.Viewport = .{},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The configuration that a surface has, this is copied from the main
|
||||||
|
/// Config struct usually to prevent sharing a single value.
|
||||||
|
const DerivedConfig = struct {
|
||||||
|
arena: ArenaAllocator,
|
||||||
|
|
||||||
|
/// For docs for these, see the associated config they are derived from.
|
||||||
|
original_font_size: u8,
|
||||||
|
keybind: configpkg.Keybinds,
|
||||||
|
clipboard_read: bool,
|
||||||
|
clipboard_write: bool,
|
||||||
|
clipboard_trim_trailing_spaces: bool,
|
||||||
|
mouse_interval: u64,
|
||||||
|
|
||||||
|
pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig {
|
||||||
|
var arena = ArenaAllocator.init(alloc_gpa);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.original_font_size = config.@"font-size",
|
||||||
|
.keybind = try config.keybind.clone(alloc),
|
||||||
|
.clipboard_read = config.@"clipboard-read",
|
||||||
|
.clipboard_write = config.@"clipboard-write",
|
||||||
|
.clipboard_trim_trailing_spaces = config.@"clipboard-trim-trailing-spaces",
|
||||||
|
.mouse_interval = config.@"click-repeat-interval" * 1_000_000, // 500ms
|
||||||
|
|
||||||
|
// Assignments happen sequentially so we have to do this last
|
||||||
|
// so that the memory is captured from allocs above.
|
||||||
|
.arena = arena,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Create a new surface. This must be called from the main thread. The
|
/// Create a new surface. This must be called from the main thread. The
|
||||||
/// pointer to the memory for the surface must be provided and must be
|
/// pointer to the memory for the surface must be provided and must be
|
||||||
/// stable due to interfacing with various callbacks.
|
/// stable due to interfacing with various callbacks.
|
||||||
pub fn init(
|
pub fn init(
|
||||||
self: *Surface,
|
self: *Surface,
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
config: *const Config,
|
config: *const configpkg.Config,
|
||||||
app_mailbox: App.Mailbox,
|
app_mailbox: App.Mailbox,
|
||||||
rt_surface: *apprt.runtime.Surface,
|
rt_surface: *apprt.runtime.Surface,
|
||||||
) !void {
|
) !void {
|
||||||
@ -284,7 +323,7 @@ pub fn init(
|
|||||||
|
|
||||||
// Create our terminal grid with the initial size
|
// Create our terminal grid with the initial size
|
||||||
var renderer_impl = try Renderer.init(alloc, .{
|
var renderer_impl = try Renderer.init(alloc, .{
|
||||||
.config = config,
|
.config = try Renderer.DerivedConfig.init(alloc, config),
|
||||||
.font_group = font_group,
|
.font_group = font_group,
|
||||||
.padding = .{
|
.padding = .{
|
||||||
.explicit = padding,
|
.explicit = padding,
|
||||||
@ -324,7 +363,8 @@ pub fn init(
|
|||||||
var io = try termio.Impl.init(alloc, .{
|
var io = try termio.Impl.init(alloc, .{
|
||||||
.grid_size = grid_size,
|
.grid_size = grid_size,
|
||||||
.screen_size = screen_size,
|
.screen_size = screen_size,
|
||||||
.config = config,
|
.full_config = config,
|
||||||
|
.config = try termio.Impl.DerivedConfig.init(alloc, config),
|
||||||
.renderer_state = &self.renderer_state,
|
.renderer_state = &self.renderer_state,
|
||||||
.renderer_wakeup = render_thread.wakeup,
|
.renderer_wakeup = render_thread.wakeup,
|
||||||
.renderer_mailbox = render_thread.mailbox,
|
.renderer_mailbox = render_thread.mailbox,
|
||||||
@ -361,7 +401,6 @@ pub fn init(
|
|||||||
},
|
},
|
||||||
.renderer_thr = undefined,
|
.renderer_thr = undefined,
|
||||||
.mouse = .{},
|
.mouse = .{},
|
||||||
.mouse_interval = config.@"click-repeat-interval" * 1_000_000, // 500ms
|
|
||||||
.io = io,
|
.io = io,
|
||||||
.io_thread = io_thread,
|
.io_thread = io_thread,
|
||||||
.io_thr = undefined,
|
.io_thr = undefined,
|
||||||
@ -369,7 +408,7 @@ pub fn init(
|
|||||||
.grid_size = grid_size,
|
.grid_size = grid_size,
|
||||||
.cell_size = cell_size,
|
.cell_size = cell_size,
|
||||||
.padding = padding,
|
.padding = padding,
|
||||||
.config = config,
|
.config = try DerivedConfig.init(alloc, config),
|
||||||
|
|
||||||
.imgui_ctx = if (!DevMode.enabled) {} else try imgui.Context.create(),
|
.imgui_ctx = if (!DevMode.enabled) {} else try imgui.Context.create(),
|
||||||
};
|
};
|
||||||
@ -476,6 +515,7 @@ pub fn deinit(self: *Surface) void {
|
|||||||
self.alloc.destroy(self.font_group);
|
self.alloc.destroy(self.font_group);
|
||||||
|
|
||||||
self.alloc.destroy(self.renderer_state.mutex);
|
self.alloc.destroy(self.renderer_state.mutex);
|
||||||
|
self.config.deinit();
|
||||||
log.info("surface closed addr={x}", .{@ptrToInt(self)});
|
log.info("surface closed addr={x}", .{@ptrToInt(self)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,6 +529,8 @@ pub fn close(self: *Surface) void {
|
|||||||
/// 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),
|
||||||
|
|
||||||
.set_title => |*v| {
|
.set_title => |*v| {
|
||||||
// The ptrCast just gets sliceTo to return the proper type.
|
// The ptrCast just gets sliceTo to return the proper type.
|
||||||
// We know that our title should end in 0.
|
// We know that our title should end in 0.
|
||||||
@ -514,6 +556,54 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update our configuration at runtime.
|
||||||
|
fn changeConfig(self: *Surface, config: *const configpkg.Config) !void {
|
||||||
|
// Update our new derived config immediately
|
||||||
|
const derived = DerivedConfig.init(self.alloc, config) catch |err| {
|
||||||
|
// If the derivation fails then we just log and return. We don't
|
||||||
|
// hard fail in this case because we don't want to error the surface
|
||||||
|
// when config fails we just want to keep using the old config.
|
||||||
|
log.err("error updating configuration err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.config.deinit();
|
||||||
|
self.config = derived;
|
||||||
|
|
||||||
|
// We need to store our configs in a heap-allocated pointer so that
|
||||||
|
// our messages aren't huge.
|
||||||
|
var renderer_config_ptr = try self.alloc.create(Renderer.DerivedConfig);
|
||||||
|
errdefer self.alloc.destroy(renderer_config_ptr);
|
||||||
|
var termio_config_ptr = try self.alloc.create(termio.Impl.DerivedConfig);
|
||||||
|
errdefer self.alloc.destroy(termio_config_ptr);
|
||||||
|
|
||||||
|
// Update our derived configurations for the renderer and termio,
|
||||||
|
// then send them a message to update.
|
||||||
|
renderer_config_ptr.* = try Renderer.DerivedConfig.init(self.alloc, config);
|
||||||
|
errdefer renderer_config_ptr.deinit();
|
||||||
|
termio_config_ptr.* = try termio.Impl.DerivedConfig.init(self.alloc, config);
|
||||||
|
errdefer termio_config_ptr.deinit();
|
||||||
|
_ = self.renderer_thread.mailbox.push(.{
|
||||||
|
.change_config = .{
|
||||||
|
.alloc = self.alloc,
|
||||||
|
.ptr = renderer_config_ptr,
|
||||||
|
},
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.change_config = .{
|
||||||
|
.alloc = self.alloc,
|
||||||
|
.ptr = termio_config_ptr,
|
||||||
|
},
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
|
||||||
|
// With mailbox messages sent, we have to wake them up so they process it.
|
||||||
|
self.queueRender() catch |err| {
|
||||||
|
log.warn("failed to notify renderer of config change err={}", .{err});
|
||||||
|
};
|
||||||
|
self.io_thread.wakeup.notify() catch |err| {
|
||||||
|
log.warn("failed to notify io thread of config change err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the x/y coordinate of where the IME (Input Method Editor)
|
/// Returns the x/y coordinate of where the IME (Input Method Editor)
|
||||||
/// keyboard should be rendered.
|
/// keyboard should be rendered.
|
||||||
pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||||
@ -557,7 +647,7 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clipboardRead(self: *const Surface, kind: u8) !void {
|
fn clipboardRead(self: *const Surface, kind: u8) !void {
|
||||||
if (!self.config.@"clipboard-read") {
|
if (!self.config.clipboard_read) {
|
||||||
log.info("application attempted to read clipboard, but 'clipboard-read' setting is off", .{});
|
log.info("application attempted to read clipboard, but 'clipboard-read' setting is off", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -593,7 +683,7 @@ fn clipboardRead(self: *const Surface, kind: u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clipboardWrite(self: *const Surface, data: []const u8) !void {
|
fn clipboardWrite(self: *const Surface, data: []const u8) !void {
|
||||||
if (!self.config.@"clipboard-write") {
|
if (!self.config.clipboard_write) {
|
||||||
log.info("application attempted to write clipboard, but 'clipboard-write' setting is off", .{});
|
log.info("application attempted to write clipboard, but 'clipboard-write' setting is off", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -793,6 +883,12 @@ pub fn keyCallback(
|
|||||||
.unbind => unreachable,
|
.unbind => unreachable,
|
||||||
.ignore => {},
|
.ignore => {},
|
||||||
|
|
||||||
|
.reload_config => {
|
||||||
|
_ = self.app_mailbox.push(.{
|
||||||
|
.reload_config = {},
|
||||||
|
}, .{ .instant = {} });
|
||||||
|
},
|
||||||
|
|
||||||
.csi => |data| {
|
.csi => |data| {
|
||||||
_ = self.io_thread.mailbox.push(.{
|
_ = self.io_thread.mailbox.push(.{
|
||||||
.write_stable = "\x1B[",
|
.write_stable = "\x1B[",
|
||||||
@ -833,7 +929,7 @@ pub fn keyCallback(
|
|||||||
var buf = self.io.terminal.screen.selectionString(
|
var buf = self.io.terminal.screen.selectionString(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
sel,
|
sel,
|
||||||
self.config.@"clipboard-trim-trailing-spaces",
|
self.config.clipboard_trim_trailing_spaces,
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err("error reading selection string err={}", .{err});
|
log.err("error reading selection string err={}", .{err});
|
||||||
return;
|
return;
|
||||||
@ -901,7 +997,7 @@ pub fn keyCallback(
|
|||||||
log.debug("reset font size", .{});
|
log.debug("reset font size", .{});
|
||||||
|
|
||||||
var size = self.font_size;
|
var size = self.font_size;
|
||||||
size.points = self.config.@"font-size";
|
size.points = self.config.original_font_size;
|
||||||
self.setFontSize(size);
|
self.setFontSize(size);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1423,7 +1519,7 @@ pub fn mouseButtonCallback(
|
|||||||
// is less than and our interval and if so, increase the count.
|
// is less than and our interval and if so, increase the count.
|
||||||
if (self.mouse.left_click_count > 0) {
|
if (self.mouse.left_click_count > 0) {
|
||||||
const since = now.since(self.mouse.left_click_time);
|
const since = now.since(self.mouse.left_click_time);
|
||||||
if (since > self.mouse_interval) {
|
if (since > self.config.mouse_interval) {
|
||||||
self.mouse.left_click_count = 0;
|
self.mouse.left_click_count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ const apprt = @import("../apprt.zig");
|
|||||||
const input = @import("../input.zig");
|
const input = @import("../input.zig");
|
||||||
const CoreApp = @import("../App.zig");
|
const CoreApp = @import("../App.zig");
|
||||||
const CoreSurface = @import("../Surface.zig");
|
const CoreSurface = @import("../Surface.zig");
|
||||||
|
const Config = @import("../config.zig").Config;
|
||||||
|
|
||||||
const log = std.log.scoped(.embedded_window);
|
const log = std.log.scoped(.embedded_window);
|
||||||
|
|
||||||
@ -35,6 +36,11 @@ pub const App = struct {
|
|||||||
/// a full tick of the app loop.
|
/// a full tick of the app loop.
|
||||||
wakeup: *const fn (AppUD) callconv(.C) void,
|
wakeup: *const fn (AppUD) callconv(.C) void,
|
||||||
|
|
||||||
|
/// Reload the configuration and return the new configuration.
|
||||||
|
/// The old configuration can be freed immediately when this is
|
||||||
|
/// called.
|
||||||
|
reload_config: *const fn (AppUD) callconv(.C) ?*const Config,
|
||||||
|
|
||||||
/// Called to set the title of the window.
|
/// Called to set the title of the window.
|
||||||
set_title: *const fn (SurfaceUD, [*]const u8) callconv(.C) void,
|
set_title: *const fn (SurfaceUD, [*]const u8) callconv(.C) void,
|
||||||
|
|
||||||
@ -57,16 +63,31 @@ pub const App = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
core_app: *CoreApp,
|
core_app: *CoreApp,
|
||||||
|
config: *const Config,
|
||||||
opts: Options,
|
opts: Options,
|
||||||
|
|
||||||
pub fn init(core_app: *CoreApp, opts: Options) !App {
|
pub fn init(core_app: *CoreApp, config: *const Config, opts: Options) !App {
|
||||||
return .{ .core_app = core_app, .opts = opts };
|
return .{
|
||||||
|
.core_app = core_app,
|
||||||
|
.config = config,
|
||||||
|
.opts = opts,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminate(self: App) void {
|
pub fn terminate(self: App) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reloadConfig(self: *App) !?*const Config {
|
||||||
|
// Reload
|
||||||
|
if (self.opts.reload_config(self.opts.userdata)) |new| {
|
||||||
|
self.config = new;
|
||||||
|
return self.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn wakeup(self: App) void {
|
pub fn wakeup(self: App) void {
|
||||||
self.opts.wakeup(self.opts.userdata);
|
self.opts.wakeup(self.opts.userdata);
|
||||||
}
|
}
|
||||||
@ -143,7 +164,7 @@ pub const Surface = struct {
|
|||||||
// ready to use.
|
// ready to use.
|
||||||
try self.core_surface.init(
|
try self.core_surface.init(
|
||||||
app.core_app.alloc,
|
app.core_app.alloc,
|
||||||
app.core_app.config,
|
app.config,
|
||||||
.{ .rt_app = app, .mailbox = &app.core_app.mailbox },
|
.{ .rt_app = app, .mailbox = &app.core_app.mailbox },
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
@ -338,7 +359,6 @@ pub const Surface = struct {
|
|||||||
// C API
|
// C API
|
||||||
pub const CAPI = struct {
|
pub const CAPI = struct {
|
||||||
const global = &@import("../main.zig").state;
|
const global = &@import("../main.zig").state;
|
||||||
const Config = @import("../config.zig").Config;
|
|
||||||
|
|
||||||
/// Create a new app.
|
/// Create a new app.
|
||||||
export fn ghostty_app_new(
|
export fn ghostty_app_new(
|
||||||
@ -355,13 +375,13 @@ pub const CAPI = struct {
|
|||||||
opts: *const apprt.runtime.App.Options,
|
opts: *const apprt.runtime.App.Options,
|
||||||
config: *const Config,
|
config: *const Config,
|
||||||
) !*App {
|
) !*App {
|
||||||
var core_app = try CoreApp.create(global.alloc, config);
|
var core_app = try CoreApp.create(global.alloc);
|
||||||
errdefer core_app.destroy();
|
errdefer core_app.destroy();
|
||||||
|
|
||||||
// Create our runtime app
|
// Create our runtime app
|
||||||
var app = try global.alloc.create(App);
|
var app = try global.alloc.create(App);
|
||||||
errdefer global.alloc.destroy(app);
|
errdefer global.alloc.destroy(app);
|
||||||
app.* = try App.init(core_app, opts.*);
|
app.* = try App.init(core_app, config, opts.*);
|
||||||
errdefer app.terminate();
|
errdefer app.terminate();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
@ -19,6 +19,8 @@ const Renderer = renderer.Renderer;
|
|||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const CoreApp = @import("../App.zig");
|
const CoreApp = @import("../App.zig");
|
||||||
const CoreSurface = @import("../Surface.zig");
|
const CoreSurface = @import("../Surface.zig");
|
||||||
|
const Config = @import("../config.zig").Config;
|
||||||
|
const DevMode = @import("../DevMode.zig");
|
||||||
|
|
||||||
// Get native API access on certain platforms so we can do more customization.
|
// Get native API access on certain platforms so we can do more customization.
|
||||||
const glfwNative = glfw.Native(.{
|
const glfwNative = glfw.Native(.{
|
||||||
@ -29,6 +31,7 @@ const log = std.log.scoped(.glfw);
|
|||||||
|
|
||||||
pub const App = struct {
|
pub const App = struct {
|
||||||
app: *CoreApp,
|
app: *CoreApp,
|
||||||
|
config: Config,
|
||||||
|
|
||||||
/// Mac-specific state.
|
/// Mac-specific state.
|
||||||
darwin: if (Darwin.enabled) Darwin else void,
|
darwin: if (Darwin.enabled) Darwin else void,
|
||||||
@ -53,14 +56,24 @@ pub const App = struct {
|
|||||||
var darwin = if (Darwin.enabled) try Darwin.init() else {};
|
var darwin = if (Darwin.enabled) try Darwin.init() else {};
|
||||||
errdefer if (Darwin.enabled) darwin.deinit();
|
errdefer if (Darwin.enabled) darwin.deinit();
|
||||||
|
|
||||||
|
// Load our configuration
|
||||||
|
var config = try Config.load(core_app.alloc);
|
||||||
|
errdefer config.deinit();
|
||||||
|
|
||||||
|
// If we have DevMode on, store the config so we can show it. This
|
||||||
|
// is messy because we're copying a thing here. We should clean this
|
||||||
|
// up when we take a pass at cleaning up the dev mode.
|
||||||
|
if (DevMode.enabled) DevMode.instance.config = config;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.app = core_app,
|
.app = core_app,
|
||||||
|
.config = config,
|
||||||
.darwin = darwin,
|
.darwin = darwin,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminate(self: App) void {
|
pub fn terminate(self: *App) void {
|
||||||
_ = self;
|
self.config.deinit();
|
||||||
glfw.terminate();
|
glfw.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +103,23 @@ pub const App = struct {
|
|||||||
glfw.postEmptyEvent();
|
glfw.postEmptyEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reload the configuration. This should return the new configuration.
|
||||||
|
/// The old value can be freed immediately at this point assuming a
|
||||||
|
/// successful return.
|
||||||
|
///
|
||||||
|
/// The returned pointer value is only valid for a stable self pointer.
|
||||||
|
pub fn reloadConfig(self: *App) !?*const Config {
|
||||||
|
// Load our configuration
|
||||||
|
var config = try Config.load(self.app.alloc);
|
||||||
|
errdefer config.deinit();
|
||||||
|
|
||||||
|
// Update the existing config, be sure to clean up the old one.
|
||||||
|
self.config.deinit();
|
||||||
|
self.config = config;
|
||||||
|
|
||||||
|
return &self.config;
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new window for the app.
|
/// Create a new window for the app.
|
||||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||||
_ = try self.newSurface(parent_);
|
_ = try self.newSurface(parent_);
|
||||||
@ -139,7 +169,7 @@ pub const App = struct {
|
|||||||
errdefer surface.deinit();
|
errdefer surface.deinit();
|
||||||
|
|
||||||
// If we have a parent, inherit some properties
|
// If we have a parent, inherit some properties
|
||||||
if (self.app.config.@"window-inherit-font-size") {
|
if (self.config.@"window-inherit-font-size") {
|
||||||
if (parent_) |parent| {
|
if (parent_) |parent| {
|
||||||
surface.core_surface.setFontSize(parent.font_size);
|
surface.core_surface.setFontSize(parent.font_size);
|
||||||
}
|
}
|
||||||
@ -313,7 +343,7 @@ pub const Surface = struct {
|
|||||||
// Initialize our surface now that we have the stable pointer.
|
// Initialize our surface now that we have the stable pointer.
|
||||||
try self.core_surface.init(
|
try self.core_surface.init(
|
||||||
app.app.alloc,
|
app.app.alloc,
|
||||||
app.app.config,
|
&app.config,
|
||||||
.{ .rt_app = app, .mailbox = &app.app.mailbox },
|
.{ .rt_app = app, .mailbox = &app.app.mailbox },
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,7 @@ const apprt = @import("../apprt.zig");
|
|||||||
const input = @import("../input.zig");
|
const input = @import("../input.zig");
|
||||||
const CoreApp = @import("../App.zig");
|
const CoreApp = @import("../App.zig");
|
||||||
const CoreSurface = @import("../Surface.zig");
|
const CoreSurface = @import("../Surface.zig");
|
||||||
|
const Config = @import("../config.zig").Config;
|
||||||
|
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("gtk/gtk.h");
|
@cInclude("gtk/gtk.h");
|
||||||
@ -37,6 +38,7 @@ pub const App = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
core_app: *CoreApp,
|
core_app: *CoreApp,
|
||||||
|
config: Config,
|
||||||
|
|
||||||
app: *c.GtkApplication,
|
app: *c.GtkApplication,
|
||||||
ctx: *c.GMainContext,
|
ctx: *c.GMainContext,
|
||||||
@ -53,6 +55,10 @@ pub const App = struct {
|
|||||||
// rid of this dep.
|
// rid of this dep.
|
||||||
if (!glfw.init(.{})) return error.GlfwInitFailed;
|
if (!glfw.init(.{})) return error.GlfwInitFailed;
|
||||||
|
|
||||||
|
// Load our configuration
|
||||||
|
var config = try Config.load(core_app.alloc);
|
||||||
|
errdefer config.deinit();
|
||||||
|
|
||||||
// Create our GTK Application which encapsulates our process.
|
// Create our GTK Application which encapsulates our process.
|
||||||
const app = @ptrCast(?*c.GtkApplication, c.gtk_application_new(
|
const app = @ptrCast(?*c.GtkApplication, c.gtk_application_new(
|
||||||
null,
|
null,
|
||||||
@ -108,6 +114,7 @@ pub const App = struct {
|
|||||||
return .{
|
return .{
|
||||||
.core_app = core_app,
|
.core_app = core_app,
|
||||||
.app = app,
|
.app = app,
|
||||||
|
.config = config,
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.cursor_default = cursor_default,
|
.cursor_default = cursor_default,
|
||||||
.cursor_ibeam = cursor_ibeam,
|
.cursor_ibeam = cursor_ibeam,
|
||||||
@ -116,7 +123,7 @@ pub const App = struct {
|
|||||||
|
|
||||||
// Terminate the application. The application will not be restarted after
|
// Terminate the application. The application will not be restarted after
|
||||||
// this so all global state can be cleaned up.
|
// this so all global state can be cleaned up.
|
||||||
pub fn terminate(self: App) void {
|
pub fn terminate(self: *App) void {
|
||||||
c.g_settings_sync();
|
c.g_settings_sync();
|
||||||
while (c.g_main_context_iteration(self.ctx, 0) != 0) {}
|
while (c.g_main_context_iteration(self.ctx, 0) != 0) {}
|
||||||
c.g_main_context_release(self.ctx);
|
c.g_main_context_release(self.ctx);
|
||||||
@ -125,9 +132,28 @@ pub const App = struct {
|
|||||||
c.g_object_unref(self.cursor_ibeam);
|
c.g_object_unref(self.cursor_ibeam);
|
||||||
c.g_object_unref(self.cursor_default);
|
c.g_object_unref(self.cursor_default);
|
||||||
|
|
||||||
|
self.config.deinit();
|
||||||
|
|
||||||
glfw.terminate();
|
glfw.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reload the configuration. This should return the new configuration.
|
||||||
|
/// The old value can be freed immediately at this point assuming a
|
||||||
|
/// successful return.
|
||||||
|
///
|
||||||
|
/// The returned pointer value is only valid for a stable self pointer.
|
||||||
|
pub fn reloadConfig(self: *App) !?*const Config {
|
||||||
|
// Load our configuration
|
||||||
|
var config = try Config.load(self.core_app.alloc);
|
||||||
|
errdefer config.deinit();
|
||||||
|
|
||||||
|
// Update the existing config, be sure to clean up the old one.
|
||||||
|
self.config.deinit();
|
||||||
|
self.config = config;
|
||||||
|
|
||||||
|
return &self.config;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn wakeup(self: App) void {
|
pub fn wakeup(self: App) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
c.g_main_context_wakeup(null);
|
c.g_main_context_wakeup(null);
|
||||||
@ -575,7 +601,7 @@ pub const Surface = struct {
|
|||||||
// Initialize our surface now that we have the stable pointer.
|
// Initialize our surface now that we have the stable pointer.
|
||||||
try self.core_surface.init(
|
try self.core_surface.init(
|
||||||
self.app.core_app.alloc,
|
self.app.core_app.alloc,
|
||||||
self.app.core_app.config,
|
&self.app.config,
|
||||||
.{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox },
|
.{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox },
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ const App = @import("../App.zig");
|
|||||||
const Surface = @import("../Surface.zig");
|
const Surface = @import("../Surface.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
|
const Config = @import("../config.zig").Config;
|
||||||
|
|
||||||
/// The message types that can be sent to a single surface.
|
/// The message types that can be sent to a single surface.
|
||||||
pub const Message = union(enum) {
|
pub const Message = union(enum) {
|
||||||
@ -24,6 +25,11 @@ pub const Message = union(enum) {
|
|||||||
/// Write the clipboard contents.
|
/// Write the clipboard contents.
|
||||||
clipboard_write: WriteReq,
|
clipboard_write: WriteReq,
|
||||||
|
|
||||||
|
/// Change the configuration to the given configuration. The pointer is
|
||||||
|
/// not valid after receiving this message so any config must be used
|
||||||
|
/// and derived immediately.
|
||||||
|
change_config: *const Config,
|
||||||
|
|
||||||
/// Close the surface. This will only close the current surface that
|
/// Close the surface. This will only close the current surface that
|
||||||
/// receives this, not the full application.
|
/// receives this, not the full application.
|
||||||
close: void,
|
close: void,
|
||||||
|
353
src/config.zig
353
src/config.zig
@ -1,3 +1,4 @@
|
|||||||
|
const config = @This();
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@ -166,11 +167,65 @@ pub const Config = struct {
|
|||||||
/// This is set by the CLI parser for deinit.
|
/// This is set by the CLI parser for deinit.
|
||||||
_arena: ?ArenaAllocator = null,
|
_arena: ?ArenaAllocator = null,
|
||||||
|
|
||||||
|
/// Key is an enum of all the available configuration keys. This is used
|
||||||
|
/// when paired with diff to determine what fields have changed in a config,
|
||||||
|
/// amongst other things.
|
||||||
|
pub const Key = key: {
|
||||||
|
const field_infos = std.meta.fields(Config);
|
||||||
|
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
inline for (field_infos) |field| {
|
||||||
|
// Ignore fields starting with "_" since they're internal and
|
||||||
|
// not copied ever.
|
||||||
|
if (field.name[0] == '_') continue;
|
||||||
|
|
||||||
|
enumFields[i] = .{
|
||||||
|
.name = field.name,
|
||||||
|
.value = i,
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decls = [_]std.builtin.Type.Declaration{};
|
||||||
|
break :key @Type(.{
|
||||||
|
.Enum = .{
|
||||||
|
.tag_type = std.math.IntFittingRange(0, field_infos.len - 1),
|
||||||
|
.fields = enumFields[0..i],
|
||||||
|
.decls = &decls,
|
||||||
|
.is_exhaustive = true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *Config) void {
|
pub fn deinit(self: *Config) void {
|
||||||
if (self._arena) |arena| arena.deinit();
|
if (self._arena) |arena| arena.deinit();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the configuration according to the default rules:
|
||||||
|
///
|
||||||
|
/// 1. Defaults
|
||||||
|
/// 2. XDG Config File
|
||||||
|
/// 3. CLI flags
|
||||||
|
/// 4. Recursively defined configuration files
|
||||||
|
///
|
||||||
|
pub fn load(alloc_gpa: Allocator) !Config {
|
||||||
|
var result = try default(alloc_gpa);
|
||||||
|
errdefer result.deinit();
|
||||||
|
|
||||||
|
// If we have a configuration file in our home directory, parse that first.
|
||||||
|
try result.loadDefaultFiles(alloc_gpa);
|
||||||
|
|
||||||
|
// Parse the config from the CLI args
|
||||||
|
try result.loadCliArgs(alloc_gpa);
|
||||||
|
|
||||||
|
// Parse the config files that were added from our file and CLI args.
|
||||||
|
try result.loadRecursiveFiles(alloc_gpa);
|
||||||
|
try result.finalize();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||||
// Build up our basic config
|
// Build up our basic config
|
||||||
var result: Config = .{
|
var result: Config = .{
|
||||||
@ -180,6 +235,12 @@ pub const Config = struct {
|
|||||||
const alloc = result._arena.?.allocator();
|
const alloc = result._arena.?.allocator();
|
||||||
|
|
||||||
// Add our default keybindings
|
// Add our default keybindings
|
||||||
|
try result.keybind.set.put(
|
||||||
|
alloc,
|
||||||
|
.{ .key = .space, .mods = .{ .super = true, .alt = true, .ctrl = true } },
|
||||||
|
.{ .reload_config = {} },
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
// On macOS we default to super but Linux ctrl+shift since
|
// On macOS we default to super but Linux ctrl+shift since
|
||||||
// ctrl+c is to kill the process.
|
// ctrl+c is to kill the process.
|
||||||
@ -499,6 +560,14 @@ pub const Config = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load and parse the CLI args.
|
||||||
|
pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
||||||
|
// Parse the config from the CLI args
|
||||||
|
var iter = try std.process.argsWithAllocator(alloc_gpa);
|
||||||
|
defer iter.deinit();
|
||||||
|
try cli_args.parse(Config, alloc_gpa, self, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
/// Load and parse the config files that were added in the "config-file" key.
|
/// Load and parse the config files that were added in the "config-file" key.
|
||||||
pub fn loadRecursiveFiles(self: *Config, alloc: Allocator) !void {
|
pub fn loadRecursiveFiles(self: *Config, alloc: Allocator) !void {
|
||||||
// TODO(mitchellh): we should parse the files form the homedir first
|
// TODO(mitchellh): we should parse the files form the homedir first
|
||||||
@ -605,8 +674,219 @@ pub const Config = struct {
|
|||||||
self.@"click-repeat-interval" = internal_os.clickInterval() orelse 500;
|
self.@"click-repeat-interval" = internal_os.clickInterval() orelse 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of this configuration. This is useful as a starting
|
||||||
|
/// point for modifying a configuration since a config can NOT be
|
||||||
|
/// modified once it is in use by an app or surface.
|
||||||
|
pub fn clone(self: *const Config, alloc_gpa: Allocator) !Config {
|
||||||
|
// Start with an empty config with a new arena we're going
|
||||||
|
// to use for all our copies.
|
||||||
|
var result: Config = .{
|
||||||
|
._arena = ArenaAllocator.init(alloc_gpa),
|
||||||
|
};
|
||||||
|
errdefer result.deinit();
|
||||||
|
const alloc = result._arena.?.allocator();
|
||||||
|
|
||||||
|
inline for (@typeInfo(Config).Struct.fields) |field| {
|
||||||
|
if (!@hasField(Key, field.name)) continue;
|
||||||
|
@field(result, field.name) = try cloneValue(
|
||||||
|
alloc,
|
||||||
|
field.type,
|
||||||
|
@field(self, field.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cloneValue(alloc: Allocator, comptime T: type, src: T) !T {
|
||||||
|
// Do known named types first
|
||||||
|
switch (T) {
|
||||||
|
[]const u8 => return try alloc.dupe(u8, src),
|
||||||
|
[:0]const u8 => return try alloc.dupeZ(u8, src),
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back into types of types
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
inline .Bool,
|
||||||
|
.Int,
|
||||||
|
=> return src,
|
||||||
|
|
||||||
|
.Optional => |info| return try cloneValue(
|
||||||
|
alloc,
|
||||||
|
info.child,
|
||||||
|
src orelse return null,
|
||||||
|
),
|
||||||
|
|
||||||
|
.Struct => return try src.clone(alloc),
|
||||||
|
|
||||||
|
else => {
|
||||||
|
@compileLog(T);
|
||||||
|
@compileError("unsupported field type");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator that goes through each changed field from
|
||||||
|
/// old to new. The order of old or new do not matter.
|
||||||
|
pub fn changeIterator(old: *const Config, new: *const Config) ChangeIterator {
|
||||||
|
return .{
|
||||||
|
.old = old,
|
||||||
|
.new = new,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given key has changed from old to new. This
|
||||||
|
/// requires the key to be comptime known to make this more efficient.
|
||||||
|
pub fn changed(self: *const Config, new: *const Config, comptime key: Key) bool {
|
||||||
|
// Get the field at comptime
|
||||||
|
const field = comptime field: {
|
||||||
|
const fields = std.meta.fields(Config);
|
||||||
|
for (fields) |field| {
|
||||||
|
if (@field(Key, field.name) == key) {
|
||||||
|
break :field field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
};
|
||||||
|
|
||||||
|
const old_value = @field(self, field.name);
|
||||||
|
const new_value = @field(new, field.name);
|
||||||
|
return !equal(field.type, old_value, new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This yields a key for every changed field between old and new.
|
||||||
|
pub const ChangeIterator = struct {
|
||||||
|
old: *const Config,
|
||||||
|
new: *const Config,
|
||||||
|
i: usize = 0,
|
||||||
|
|
||||||
|
pub fn next(self: *ChangeIterator) ?Key {
|
||||||
|
const fields = comptime std.meta.fields(Key);
|
||||||
|
while (self.i < fields.len) {
|
||||||
|
switch (self.i) {
|
||||||
|
inline 0...(fields.len - 1) => |i| {
|
||||||
|
const field = fields[i];
|
||||||
|
const key = @field(Key, field.name);
|
||||||
|
self.i += 1;
|
||||||
|
if (self.old.changed(self.new, key)) return key;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "clone default" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var source = try Config.default(alloc);
|
||||||
|
defer source.deinit();
|
||||||
|
var dest = try source.clone(alloc);
|
||||||
|
defer dest.deinit();
|
||||||
|
|
||||||
|
// Should have no changes
|
||||||
|
var it = source.changeIterator(&dest);
|
||||||
|
try testing.expectEqual(@as(?Key, null), it.next());
|
||||||
|
|
||||||
|
// I want to do this but this doesn't work (the API doesn't work)
|
||||||
|
// try testing.expectEqualDeep(dest, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "changed" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var source = try Config.default(alloc);
|
||||||
|
defer source.deinit();
|
||||||
|
var dest = try source.clone(alloc);
|
||||||
|
defer dest.deinit();
|
||||||
|
dest.@"font-family" = "something else";
|
||||||
|
|
||||||
|
try testing.expect(source.changed(&dest, .@"font-family"));
|
||||||
|
try testing.expect(!source.changed(&dest, .@"font-size"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A config-specific helper to determine if two values of the same
|
||||||
|
/// type are equal. This isn't the same as std.mem.eql or std.testing.equals
|
||||||
|
/// because we expect structs to implement their own equality.
|
||||||
|
///
|
||||||
|
/// This also doesn't support ALL Zig types, because we only add to it
|
||||||
|
/// as we need types for the config.
|
||||||
|
fn equal(comptime T: type, old: T, new: T) bool {
|
||||||
|
// Do known named types first
|
||||||
|
switch (T) {
|
||||||
|
inline []const u8,
|
||||||
|
[:0]const u8,
|
||||||
|
=> return std.mem.eql(u8, old, new),
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back into types of types
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Void => return true,
|
||||||
|
|
||||||
|
inline .Bool,
|
||||||
|
.Int,
|
||||||
|
.Enum,
|
||||||
|
=> return old == new,
|
||||||
|
|
||||||
|
.Optional => |info| {
|
||||||
|
if (old == null and new == null) return true;
|
||||||
|
if (old == null or new == null) return false;
|
||||||
|
return equal(info.child, old.?, new.?);
|
||||||
|
},
|
||||||
|
|
||||||
|
.Struct => |info| {
|
||||||
|
if (@hasDecl(T, "equal")) return old.equal(new);
|
||||||
|
|
||||||
|
// If a struct doesn't declare an "equal" function, we fall back
|
||||||
|
// to a recursive field-by-field compare.
|
||||||
|
inline for (info.fields) |field_info| {
|
||||||
|
if (!equal(
|
||||||
|
field_info.type,
|
||||||
|
@field(old, field_info.name),
|
||||||
|
@field(new, field_info.name),
|
||||||
|
)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Union => |info| {
|
||||||
|
const tag_type = info.tag_type.?;
|
||||||
|
const old_tag = std.meta.activeTag(old);
|
||||||
|
const new_tag = std.meta.activeTag(new);
|
||||||
|
if (old_tag != new_tag) return false;
|
||||||
|
|
||||||
|
inline for (info.fields) |field_info| {
|
||||||
|
if (@field(tag_type, field_info.name) == old_tag) {
|
||||||
|
return equal(
|
||||||
|
field_info.type,
|
||||||
|
@field(old, field_info.name),
|
||||||
|
@field(new, field_info.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
@compileLog(T);
|
||||||
|
@compileError("unsupported field type");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Color represents a color using RGB.
|
/// Color represents a color using RGB.
|
||||||
pub const Color = struct {
|
pub const Color = struct {
|
||||||
r: u8,
|
r: u8,
|
||||||
@ -626,6 +906,16 @@ pub const Color = struct {
|
|||||||
return fromHex(input orelse return error.ValueRequired);
|
return fromHex(input orelse return error.ValueRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deep copy of the struct. Required by Config.
|
||||||
|
pub fn clone(self: Color, _: Allocator) !Color {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Color, other: Color) bool {
|
||||||
|
return std.meta.eql(self, other);
|
||||||
|
}
|
||||||
|
|
||||||
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
||||||
/// is optional.
|
/// is optional.
|
||||||
pub fn fromHex(input: []const u8) !Color {
|
pub fn fromHex(input: []const u8) !Color {
|
||||||
@ -689,6 +979,16 @@ pub const Palette = struct {
|
|||||||
self.value[key] = .{ .r = rgb.r, .g = rgb.g, .b = rgb.b };
|
self.value[key] = .{ .r = rgb.r, .g = rgb.g, .b = rgb.b };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deep copy of the struct. Required by Config.
|
||||||
|
pub fn clone(self: Self, _: Allocator) !Self {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Self, other: Self) bool {
|
||||||
|
return std.meta.eql(self, other);
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
@ -722,6 +1022,23 @@ pub const RepeatableString = struct {
|
|||||||
try self.list.append(alloc, value);
|
try self.list.append(alloc, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deep copy of the struct. Required by Config.
|
||||||
|
pub fn clone(self: *const Self, alloc: Allocator) !Self {
|
||||||
|
return .{
|
||||||
|
.list = try self.list.clone(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Self, other: Self) bool {
|
||||||
|
const itemsA = self.list.items;
|
||||||
|
const itemsB = other.list.items;
|
||||||
|
if (itemsA.len != itemsB.len) return false;
|
||||||
|
for (itemsA, itemsB) |a, b| {
|
||||||
|
if (!std.mem.eql(u8, a, b)) return false;
|
||||||
|
} else return true;
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -766,6 +1083,35 @@ pub const Keybinds = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deep copy of the struct. Required by Config.
|
||||||
|
pub fn clone(self: *const Keybinds, alloc: Allocator) !Keybinds {
|
||||||
|
return .{
|
||||||
|
.set = .{
|
||||||
|
.bindings = try self.set.bindings.clone(alloc),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Keybinds, other: Keybinds) bool {
|
||||||
|
const self_map = self.set.bindings;
|
||||||
|
const other_map = other.set.bindings;
|
||||||
|
if (self_map.count() != other_map.count()) return false;
|
||||||
|
|
||||||
|
var it = self_map.iterator();
|
||||||
|
while (it.next()) |self_entry| {
|
||||||
|
const other_entry = other_map.getEntry(self_entry.key_ptr.*) orelse
|
||||||
|
return false;
|
||||||
|
if (!config.equal(
|
||||||
|
inputpkg.Binding.Action,
|
||||||
|
self_entry.value_ptr.*,
|
||||||
|
other_entry.value_ptr.*,
|
||||||
|
)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -856,6 +1202,13 @@ pub const CAPI = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the configuration from the CLI args.
|
||||||
|
export fn ghostty_config_load_cli_args(self: *Config) void {
|
||||||
|
self.loadCliArgs(global.alloc) catch |err| {
|
||||||
|
log.err("error loading config err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Load the configuration from a string in the same format as
|
/// Load the configuration from a string in the same format as
|
||||||
/// the file-based syntax for the desktop version of the terminal.
|
/// the file-based syntax for the desktop version of the terminal.
|
||||||
export fn ghostty_config_load_string(
|
export fn ghostty_config_load_string(
|
||||||
|
@ -187,6 +187,12 @@ pub const Action = union(enum) {
|
|||||||
/// Focus on a split in a given direction.
|
/// Focus on a split in a given direction.
|
||||||
goto_split: SplitFocusDirection,
|
goto_split: SplitFocusDirection,
|
||||||
|
|
||||||
|
/// Reload the configuration. The exact meaning depends on the app runtime
|
||||||
|
/// in use but this usually involves re-reading the configuration file
|
||||||
|
/// and applying any changes. Note that not all changes can be applied at
|
||||||
|
/// runtime.
|
||||||
|
reload_config: void,
|
||||||
|
|
||||||
/// Close the current "surface", whether that is a window, tab, split,
|
/// Close the current "surface", whether that is a window, tab, split,
|
||||||
/// etc. This only closes ONE surface.
|
/// etc. This only closes ONE surface.
|
||||||
close_surface: void,
|
close_surface: void,
|
||||||
|
23
src/main.zig
23
src/main.zig
@ -14,8 +14,6 @@ const xdg = @import("xdg.zig");
|
|||||||
const apprt = @import("apprt.zig");
|
const apprt = @import("apprt.zig");
|
||||||
|
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const cli_args = @import("cli_args.zig");
|
|
||||||
const Config = @import("config.zig").Config;
|
|
||||||
const Ghostty = @import("main_c.zig").Ghostty;
|
const Ghostty = @import("main_c.zig").Ghostty;
|
||||||
|
|
||||||
/// Global process state. This is initialized in main() for exe artifacts
|
/// Global process state. This is initialized in main() for exe artifacts
|
||||||
@ -29,27 +27,8 @@ pub fn main() !void {
|
|||||||
defer state.deinit();
|
defer state.deinit();
|
||||||
const alloc = state.alloc;
|
const alloc = state.alloc;
|
||||||
|
|
||||||
// Try reading our config
|
|
||||||
var config = try Config.default(alloc);
|
|
||||||
defer config.deinit();
|
|
||||||
|
|
||||||
// If we have a configuration file in our home directory, parse that first.
|
|
||||||
try config.loadDefaultFiles(alloc);
|
|
||||||
|
|
||||||
// Parse the config from the CLI args
|
|
||||||
{
|
|
||||||
var iter = try std.process.argsWithAllocator(alloc);
|
|
||||||
defer iter.deinit();
|
|
||||||
try cli_args.parse(Config, alloc, &config, &iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the config files that were added from our file and CLI args.
|
|
||||||
try config.loadRecursiveFiles(alloc);
|
|
||||||
try config.finalize();
|
|
||||||
//std.log.debug("config={}", .{config});
|
|
||||||
|
|
||||||
// Create our app state
|
// Create our app state
|
||||||
var app = try App.create(alloc, &config);
|
var app = try App.create(alloc);
|
||||||
defer app.destroy();
|
defer app.destroy();
|
||||||
|
|
||||||
// Create our runtime app
|
// Create our runtime app
|
||||||
|
@ -11,6 +11,7 @@ const objc = @import("objc");
|
|||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const imgui = @import("imgui");
|
const imgui = @import("imgui");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
|
const configpkg = @import("../config.zig");
|
||||||
const font = @import("../font/main.zig");
|
const font = @import("../font/main.zig");
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
@ -31,6 +32,9 @@ const log = std.log.scoped(.metal);
|
|||||||
/// Allocator that can be used
|
/// Allocator that can be used
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
/// The configuration we need derived from the main config.
|
||||||
|
config: DerivedConfig,
|
||||||
|
|
||||||
/// The mailbox for communicating with the window.
|
/// The mailbox for communicating with the window.
|
||||||
surface_mailbox: apprt.surface.Mailbox,
|
surface_mailbox: apprt.surface.Mailbox,
|
||||||
|
|
||||||
@ -51,17 +55,6 @@ focused: bool,
|
|||||||
/// blinking.
|
/// blinking.
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default foreground color
|
|
||||||
foreground: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default background color
|
|
||||||
background: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default selection color
|
|
||||||
selection_background: ?terminal.color.RGB,
|
|
||||||
selection_foreground: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The current set of cells to render. This is rebuilt on every frame
|
/// The current set of cells to render. This is rebuilt on every frame
|
||||||
/// but we keep this around so that we don't reallocate. Each set of
|
/// but we keep this around so that we don't reallocate. Each set of
|
||||||
@ -117,6 +110,48 @@ const GPUCellMode = enum(u8) {
|
|||||||
strikethrough = 8,
|
strikethrough = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The configuration for this renderer that is derived from the main
|
||||||
|
/// configuration. This must be exported so that we don't need to
|
||||||
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
|
pub const DerivedConfig = struct {
|
||||||
|
cursor_color: ?terminal.color.RGB,
|
||||||
|
background: terminal.color.RGB,
|
||||||
|
foreground: terminal.color.RGB,
|
||||||
|
selection_background: ?terminal.color.RGB,
|
||||||
|
selection_foreground: ?terminal.color.RGB,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
config: *const configpkg.Config,
|
||||||
|
) !DerivedConfig {
|
||||||
|
_ = alloc_gpa;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.cursor_color = if (config.@"cursor-color") |col|
|
||||||
|
col.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
|
||||||
|
.background = config.background.toTerminalRGB(),
|
||||||
|
.foreground = config.foreground.toTerminalRGB(),
|
||||||
|
|
||||||
|
.selection_background = if (config.@"selection-background") |bg|
|
||||||
|
bg.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
|
||||||
|
.selection_foreground = if (config.@"selection-foreground") |bg|
|
||||||
|
bg.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Returns the hints that we want for this
|
/// Returns the hints that we want for this
|
||||||
pub fn glfwWindowHints() glfw.Window.Hints {
|
pub fn glfwWindowHints() glfw.Window.Hints {
|
||||||
return .{
|
return .{
|
||||||
@ -233,6 +268,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
|
|
||||||
return Metal{
|
return Metal{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
|
.config = options.config,
|
||||||
.surface_mailbox = options.surface_mailbox,
|
.surface_mailbox = options.surface_mailbox,
|
||||||
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
|
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
|
||||||
.screen_size = null,
|
.screen_size = null,
|
||||||
@ -240,17 +276,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.focused = true,
|
.focused = true,
|
||||||
.cursor_visible = true,
|
.cursor_visible = true,
|
||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
.cursor_color = if (options.config.@"cursor-color") |col| col.toTerminalRGB() else null,
|
|
||||||
.background = options.config.background.toTerminalRGB(),
|
|
||||||
.foreground = options.config.foreground.toTerminalRGB(),
|
|
||||||
.selection_background = if (options.config.@"selection-background") |bg|
|
|
||||||
bg.toTerminalRGB()
|
|
||||||
else
|
|
||||||
null,
|
|
||||||
.selection_foreground = if (options.config.@"selection-foreground") |bg|
|
|
||||||
bg.toTerminalRGB()
|
|
||||||
else
|
|
||||||
null,
|
|
||||||
|
|
||||||
// Render state
|
// Render state
|
||||||
.cells_bg = .{},
|
.cells_bg = .{},
|
||||||
@ -286,6 +311,8 @@ pub fn deinit(self: *Metal) void {
|
|||||||
self.font_shaper.deinit();
|
self.font_shaper.deinit();
|
||||||
self.alloc.free(self.font_shaper.cell_buf);
|
self.alloc.free(self.font_shaper.cell_buf);
|
||||||
|
|
||||||
|
self.config.deinit();
|
||||||
|
|
||||||
deinitMTLResource(self.buf_cells_bg);
|
deinitMTLResource(self.buf_cells_bg);
|
||||||
deinitMTLResource(self.buf_cells);
|
deinitMTLResource(self.buf_cells);
|
||||||
deinitMTLResource(self.buf_instance);
|
deinitMTLResource(self.buf_instance);
|
||||||
@ -482,15 +509,15 @@ pub fn render(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Swap bg/fg if the terminal is reversed
|
// Swap bg/fg if the terminal is reversed
|
||||||
const bg = self.background;
|
const bg = self.config.background;
|
||||||
const fg = self.foreground;
|
const fg = self.config.foreground;
|
||||||
defer {
|
defer {
|
||||||
self.background = bg;
|
self.config.background = bg;
|
||||||
self.foreground = fg;
|
self.config.foreground = fg;
|
||||||
}
|
}
|
||||||
if (state.terminal.modes.reverse_colors) {
|
if (state.terminal.modes.reverse_colors) {
|
||||||
self.background = fg;
|
self.config.background = fg;
|
||||||
self.foreground = bg;
|
self.config.foreground = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We used to share terminal state, but we've since learned through
|
// We used to share terminal state, but we've since learned through
|
||||||
@ -516,7 +543,7 @@ pub fn render(
|
|||||||
null;
|
null;
|
||||||
|
|
||||||
break :critical .{
|
break :critical .{
|
||||||
.bg = self.background,
|
.bg = self.config.background,
|
||||||
.devmode = if (state.devmode) |dm| dm.visible else false,
|
.devmode = if (state.devmode) |dm| dm.visible else false,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.screen = screen_copy,
|
.screen = screen_copy,
|
||||||
@ -697,6 +724,11 @@ fn drawCells(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the configuration.
|
||||||
|
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||||
|
self.config = config.*;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resize the screen.
|
/// Resize the screen.
|
||||||
pub fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void {
|
pub fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void {
|
||||||
// Store our screen size
|
// Store our screen size
|
||||||
@ -878,8 +910,8 @@ pub fn updateCell(
|
|||||||
// If we are selected, we our colors are just inverted fg/bg
|
// If we are selected, we our colors are just inverted fg/bg
|
||||||
if (sel.contains(screen_point)) {
|
if (sel.contains(screen_point)) {
|
||||||
break :colors BgFg{
|
break :colors BgFg{
|
||||||
.bg = self.selection_background orelse self.foreground,
|
.bg = self.config.selection_background orelse self.config.foreground,
|
||||||
.fg = self.selection_foreground orelse self.background,
|
.fg = self.config.selection_foreground orelse self.config.background,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -888,13 +920,13 @@ pub fn updateCell(
|
|||||||
// In normal mode, background and fg match the cell. We
|
// In normal mode, background and fg match the cell. We
|
||||||
// un-optionalize the fg by defaulting to our fg color.
|
// un-optionalize the fg by defaulting to our fg color.
|
||||||
.bg = if (cell.attrs.has_bg) cell.bg else null,
|
.bg = if (cell.attrs.has_bg) cell.bg else null,
|
||||||
.fg = if (cell.attrs.has_fg) cell.fg else self.foreground,
|
.fg = if (cell.attrs.has_fg) cell.fg else self.config.foreground,
|
||||||
} else .{
|
} else .{
|
||||||
// In inverted mode, the background MUST be set to something
|
// In inverted mode, the background MUST be set to something
|
||||||
// (is never null) so it is either the fg or default fg. The
|
// (is never null) so it is either the fg or default fg. The
|
||||||
// fg is either the bg or default background.
|
// fg is either the bg or default background.
|
||||||
.bg = if (cell.attrs.has_fg) cell.fg else self.foreground,
|
.bg = if (cell.attrs.has_fg) cell.fg else self.config.foreground,
|
||||||
.fg = if (cell.attrs.has_bg) cell.bg else self.background,
|
.fg = if (cell.attrs.has_bg) cell.bg else self.config.background,
|
||||||
};
|
};
|
||||||
break :colors res;
|
break :colors res;
|
||||||
};
|
};
|
||||||
@ -988,7 +1020,7 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
|
|||||||
screen.cursor.x,
|
screen.cursor.x,
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = self.cursor_color orelse terminal.color.RGB{
|
const color = self.config.cursor_color orelse terminal.color.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
.g = 0xFF,
|
.g = 0xFF,
|
||||||
.b = 0xFF,
|
.b = 0xFF,
|
||||||
|
@ -8,6 +8,7 @@ const assert = std.debug.assert;
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
|
const configpkg = @import("../config.zig");
|
||||||
const font = @import("../font/main.zig");
|
const font = @import("../font/main.zig");
|
||||||
const imgui = @import("imgui");
|
const imgui = @import("imgui");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
@ -43,6 +44,9 @@ const drawMutexZero = if (DrawMutex == void) void{} else .{};
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
/// The configuration we need derived from the main config.
|
||||||
|
config: DerivedConfig,
|
||||||
|
|
||||||
/// Current cell dimensions for this grid.
|
/// Current cell dimensions for this grid.
|
||||||
cell_size: renderer.CellSize,
|
cell_size: renderer.CellSize,
|
||||||
|
|
||||||
@ -84,17 +88,6 @@ font_shaper: font.Shaper,
|
|||||||
/// blinking.
|
/// blinking.
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default foreground color
|
|
||||||
foreground: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default background color
|
|
||||||
background: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default selection color
|
|
||||||
selection_background: ?terminal.color.RGB,
|
|
||||||
selection_foreground: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// True if the window is focused
|
/// True if the window is focused
|
||||||
focused: bool,
|
focused: bool,
|
||||||
@ -232,6 +225,48 @@ const GPUCellMode = enum(u8) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The configuration for this renderer that is derived from the main
|
||||||
|
/// configuration. This must be exported so that we don't need to
|
||||||
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
|
pub const DerivedConfig = struct {
|
||||||
|
cursor_color: ?terminal.color.RGB,
|
||||||
|
background: terminal.color.RGB,
|
||||||
|
foreground: terminal.color.RGB,
|
||||||
|
selection_background: ?terminal.color.RGB,
|
||||||
|
selection_foreground: ?terminal.color.RGB,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
config: *const configpkg.Config,
|
||||||
|
) !DerivedConfig {
|
||||||
|
_ = alloc_gpa;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.cursor_color = if (config.@"cursor-color") |col|
|
||||||
|
col.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
|
||||||
|
.background = config.background.toTerminalRGB(),
|
||||||
|
.foreground = config.foreground.toTerminalRGB(),
|
||||||
|
|
||||||
|
.selection_background = if (config.@"selection-background") |bg|
|
||||||
|
bg.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
|
||||||
|
.selection_foreground = if (config.@"selection-foreground") |bg|
|
||||||
|
bg.toTerminalRGB()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||||
// Create the initial font shaper
|
// Create the initial font shaper
|
||||||
var shape_buf = try alloc.alloc(font.shape.Cell, 1);
|
var shape_buf = try alloc.alloc(font.shape.Cell, 1);
|
||||||
@ -354,6 +389,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
|||||||
|
|
||||||
return OpenGL{
|
return OpenGL{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
|
.config = options.config,
|
||||||
.cells_bg = .{},
|
.cells_bg = .{},
|
||||||
.cells = .{},
|
.cells = .{},
|
||||||
.cells_lru = CellsLRU.init(0),
|
.cells_lru = CellsLRU.init(0),
|
||||||
@ -369,18 +405,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
|||||||
.font_shaper = shaper,
|
.font_shaper = shaper,
|
||||||
.cursor_visible = true,
|
.cursor_visible = true,
|
||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
.cursor_color = if (options.config.@"cursor-color") |col| col.toTerminalRGB() else null,
|
.draw_background = options.config.background,
|
||||||
.background = options.config.background.toTerminalRGB(),
|
|
||||||
.foreground = options.config.foreground.toTerminalRGB(),
|
|
||||||
.draw_background = options.config.background.toTerminalRGB(),
|
|
||||||
.selection_background = if (options.config.@"selection-background") |bg|
|
|
||||||
bg.toTerminalRGB()
|
|
||||||
else
|
|
||||||
null,
|
|
||||||
.selection_foreground = if (options.config.@"selection-foreground") |bg|
|
|
||||||
bg.toTerminalRGB()
|
|
||||||
else
|
|
||||||
null,
|
|
||||||
.focused = true,
|
.focused = true,
|
||||||
.padding = options.padding,
|
.padding = options.padding,
|
||||||
.surface_mailbox = options.surface_mailbox,
|
.surface_mailbox = options.surface_mailbox,
|
||||||
@ -404,6 +429,9 @@ pub fn deinit(self: *OpenGL) void {
|
|||||||
|
|
||||||
self.cells.deinit(self.alloc);
|
self.cells.deinit(self.alloc);
|
||||||
self.cells_bg.deinit(self.alloc);
|
self.cells_bg.deinit(self.alloc);
|
||||||
|
|
||||||
|
self.config.deinit();
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,15 +701,15 @@ pub fn render(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Swap bg/fg if the terminal is reversed
|
// Swap bg/fg if the terminal is reversed
|
||||||
const bg = self.background;
|
const bg = self.config.background;
|
||||||
const fg = self.foreground;
|
const fg = self.config.foreground;
|
||||||
defer {
|
defer {
|
||||||
self.background = bg;
|
self.config.background = bg;
|
||||||
self.foreground = fg;
|
self.config.foreground = fg;
|
||||||
}
|
}
|
||||||
if (state.terminal.modes.reverse_colors) {
|
if (state.terminal.modes.reverse_colors) {
|
||||||
self.background = fg;
|
self.config.background = fg;
|
||||||
self.foreground = bg;
|
self.config.foreground = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build our devmode draw data
|
// Build our devmode draw data
|
||||||
@ -723,7 +751,7 @@ pub fn render(
|
|||||||
null;
|
null;
|
||||||
|
|
||||||
break :critical .{
|
break :critical .{
|
||||||
.gl_bg = self.background,
|
.gl_bg = self.config.background,
|
||||||
.devmode_data = devmode_data,
|
.devmode_data = devmode_data,
|
||||||
.active_screen = state.terminal.active_screen,
|
.active_screen = state.terminal.active_screen,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
@ -944,7 +972,7 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
|||||||
screen.cursor.x,
|
screen.cursor.x,
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = self.cursor_color orelse terminal.color.RGB{
|
const color = self.config.cursor_color orelse terminal.color.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
.g = 0xFF,
|
.g = 0xFF,
|
||||||
.b = 0xFF,
|
.b = 0xFF,
|
||||||
@ -1031,8 +1059,8 @@ pub fn updateCell(
|
|||||||
// If we are selected, we use the selection colors
|
// If we are selected, we use the selection colors
|
||||||
if (sel.contains(screen_point)) {
|
if (sel.contains(screen_point)) {
|
||||||
break :colors BgFg{
|
break :colors BgFg{
|
||||||
.bg = self.selection_background orelse self.foreground,
|
.bg = self.config.selection_background orelse self.config.foreground,
|
||||||
.fg = self.selection_foreground orelse self.background,
|
.fg = self.config.selection_foreground orelse self.config.background,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1041,13 +1069,13 @@ pub fn updateCell(
|
|||||||
// In normal mode, background and fg match the cell. We
|
// In normal mode, background and fg match the cell. We
|
||||||
// un-optionalize the fg by defaulting to our fg color.
|
// un-optionalize the fg by defaulting to our fg color.
|
||||||
.bg = if (cell.attrs.has_bg) cell.bg else null,
|
.bg = if (cell.attrs.has_bg) cell.bg else null,
|
||||||
.fg = if (cell.attrs.has_fg) cell.fg else self.foreground,
|
.fg = if (cell.attrs.has_fg) cell.fg else self.config.foreground,
|
||||||
} else .{
|
} else .{
|
||||||
// In inverted mode, the background MUST be set to something
|
// In inverted mode, the background MUST be set to something
|
||||||
// (is never null) so it is either the fg or default fg. The
|
// (is never null) so it is either the fg or default fg. The
|
||||||
// fg is either the bg or default background.
|
// fg is either the bg or default background.
|
||||||
.bg = if (cell.attrs.has_fg) cell.fg else self.foreground,
|
.bg = if (cell.attrs.has_fg) cell.fg else self.config.foreground,
|
||||||
.fg = if (cell.attrs.has_bg) cell.bg else self.background,
|
.fg = if (cell.attrs.has_bg) cell.bg else self.config.background,
|
||||||
};
|
};
|
||||||
break :colors res;
|
break :colors res;
|
||||||
};
|
};
|
||||||
@ -1204,6 +1232,11 @@ fn gridSize(self: *const OpenGL, screen_size: renderer.ScreenSize) renderer.Grid
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the configuration.
|
||||||
|
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||||
|
self.config = config.*;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the screen size for rendering. This will update the projection
|
/// Set the screen size for rendering. This will update the projection
|
||||||
/// used for the shader so that the scaling of the grid is correct.
|
/// used for the shader so that the scaling of the grid is correct.
|
||||||
pub fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
|
pub fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
|
||||||
|
@ -5,8 +5,8 @@ const font = @import("../font/main.zig");
|
|||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const Config = @import("../config.zig").Config;
|
const Config = @import("../config.zig").Config;
|
||||||
|
|
||||||
/// The app configuration.
|
/// The derived configuration for this renderer implementation.
|
||||||
config: *const Config,
|
config: renderer.Renderer.DerivedConfig,
|
||||||
|
|
||||||
/// The font group that should be used.
|
/// The font group that should be used.
|
||||||
font_group: *font.GroupCache,
|
font_group: *font.GroupCache,
|
||||||
|
@ -255,6 +255,11 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
.screen_size => |size| {
|
.screen_size => |size| {
|
||||||
try self.renderer.setScreenSize(size);
|
try self.renderer.setScreenSize(size);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.change_config => |config| {
|
||||||
|
defer config.alloc.destroy(config.ptr);
|
||||||
|
try self.renderer.changeConfig(config.ptr);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,10 @@ pub const Message = union(enum) {
|
|||||||
|
|
||||||
/// Change the screen size.
|
/// Change the screen size.
|
||||||
screen_size: renderer.ScreenSize,
|
screen_size: renderer.ScreenSize,
|
||||||
|
|
||||||
|
/// The derived configuration to update the renderer with.
|
||||||
|
change_config: struct {
|
||||||
|
alloc: Allocator,
|
||||||
|
ptr: *renderer.Renderer.DerivedConfig,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ const builtin = @import("builtin");
|
|||||||
const build_config = @import("../build_config.zig");
|
const build_config = @import("../build_config.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const EnvMap = std.process.EnvMap;
|
const EnvMap = std.process.EnvMap;
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
@ -19,6 +20,7 @@ const trace = tracy.trace;
|
|||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const fastmem = @import("../fastmem.zig");
|
const fastmem = @import("../fastmem.zig");
|
||||||
const internal_os = @import("../os/main.zig");
|
const internal_os = @import("../os/main.zig");
|
||||||
|
const configpkg = @import("../config.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.io_exec);
|
const log = std.log.scoped(.io_exec);
|
||||||
|
|
||||||
@ -62,9 +64,35 @@ grid_size: renderer.GridSize,
|
|||||||
/// The data associated with the currently running thread.
|
/// The data associated with the currently running thread.
|
||||||
data: ?*EventData,
|
data: ?*EventData,
|
||||||
|
|
||||||
|
/// The configuration for this IO that is derived from the main
|
||||||
|
/// configuration. This must be exported so that we don't need to
|
||||||
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
|
pub const DerivedConfig = struct {
|
||||||
|
palette: terminal.color.Palette,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
config: *const configpkg.Config,
|
||||||
|
) !DerivedConfig {
|
||||||
|
_ = alloc_gpa;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.palette = config.palette.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize the exec implementation. This will also start the child
|
/// Initialize the exec implementation. This will also start the child
|
||||||
/// process.
|
/// process.
|
||||||
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
||||||
|
// Clean up our derived config because we don't need it after this.
|
||||||
|
var config = opts.config;
|
||||||
|
defer config.deinit();
|
||||||
|
|
||||||
// Create our terminal
|
// Create our terminal
|
||||||
var term = try terminal.Terminal.init(
|
var term = try terminal.Terminal.init(
|
||||||
alloc,
|
alloc,
|
||||||
@ -72,7 +100,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
|||||||
opts.grid_size.rows,
|
opts.grid_size.rows,
|
||||||
);
|
);
|
||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
term.color_palette = opts.config.palette.value;
|
term.color_palette = opts.config.palette;
|
||||||
|
|
||||||
var subprocess = try Subprocess.init(alloc, opts);
|
var subprocess = try Subprocess.init(alloc, opts);
|
||||||
errdefer subprocess.deinit();
|
errdefer subprocess.deinit();
|
||||||
@ -189,6 +217,21 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
|
|||||||
data.read_thread.join();
|
data.read_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the configuration.
|
||||||
|
pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
|
||||||
|
defer config.deinit();
|
||||||
|
|
||||||
|
// Update the configuration that we know about.
|
||||||
|
//
|
||||||
|
// Specific things we don't update:
|
||||||
|
// - command, working-directory: we never restart the underlying
|
||||||
|
// process so we don't care or need to know about these.
|
||||||
|
|
||||||
|
// Update the palette. Note this will only apply to new colors drawn
|
||||||
|
// since we decode all palette colors to RGB on usage.
|
||||||
|
self.terminal.color_palette = config.palette;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resize the terminal.
|
/// Resize the terminal.
|
||||||
pub fn resize(
|
pub fn resize(
|
||||||
self: *Exec,
|
self: *Exec,
|
||||||
@ -413,7 +456,7 @@ const Subprocess = struct {
|
|||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
// Determine the path to the binary we're executing
|
// Determine the path to the binary we're executing
|
||||||
const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse
|
const path = (try Command.expandPath(alloc, opts.full_config.command orelse "sh")) orelse
|
||||||
return error.CommandNotFound;
|
return error.CommandNotFound;
|
||||||
|
|
||||||
// On macOS, we launch the program as a login shell. This is a Mac-specific
|
// On macOS, we launch the program as a login shell. This is a Mac-specific
|
||||||
@ -487,10 +530,17 @@ const Subprocess = struct {
|
|||||||
break :args try args.toOwnedSlice();
|
break :args try args.toOwnedSlice();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We have to copy the cwd because there is no guarantee that
|
||||||
|
// pointers in full_config remain valid.
|
||||||
|
var cwd: ?[]u8 = if (opts.full_config.@"working-directory") |cwd|
|
||||||
|
try alloc.dupe(u8, cwd)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.env = env,
|
.env = env,
|
||||||
.cwd = opts.config.@"working-directory",
|
.cwd = cwd,
|
||||||
.path = if (internal_os.isFlatpak()) args[0] else path,
|
.path = if (internal_os.isFlatpak()) args[0] else path,
|
||||||
.args = args,
|
.args = args,
|
||||||
.grid_size = opts.grid_size,
|
.grid_size = opts.grid_size,
|
||||||
|
@ -4,6 +4,7 @@ const xev = @import("xev");
|
|||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const Config = @import("../config.zig").Config;
|
const Config = @import("../config.zig").Config;
|
||||||
|
const termio = @import("../termio.zig");
|
||||||
|
|
||||||
/// The size of the terminal grid.
|
/// The size of the terminal grid.
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
@ -11,8 +12,13 @@ grid_size: renderer.GridSize,
|
|||||||
/// The size of the viewport in pixels.
|
/// The size of the viewport in pixels.
|
||||||
screen_size: renderer.ScreenSize,
|
screen_size: renderer.ScreenSize,
|
||||||
|
|
||||||
/// The app configuration.
|
/// The full app configuration. This is only available during initialization.
|
||||||
config: *const Config,
|
/// The memory it points to is NOT stable after the init call so any values
|
||||||
|
/// in here must be copied.
|
||||||
|
full_config: *const Config,
|
||||||
|
|
||||||
|
/// The derived configuration for this termio implementation.
|
||||||
|
config: termio.Impl.DerivedConfig,
|
||||||
|
|
||||||
/// The render state. The IO implementation can modify anything here. The
|
/// The render state. The IO implementation can modify anything here. The
|
||||||
/// surface thread will setup the initial "terminal" pointer but the IO impl
|
/// surface thread will setup the initial "terminal" pointer but the IO impl
|
||||||
|
@ -150,6 +150,10 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
|
|
||||||
log.debug("mailbox message={}", .{message});
|
log.debug("mailbox message={}", .{message});
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
.change_config => |config| {
|
||||||
|
defer config.alloc.destroy(config.ptr);
|
||||||
|
try self.impl.changeConfig(config.ptr);
|
||||||
|
},
|
||||||
.resize => |v| self.handleResize(v),
|
.resize => |v| self.handleResize(v),
|
||||||
.clear_screen => |v| try self.impl.clearScreen(v.history),
|
.clear_screen => |v| try self.impl.clearScreen(v.history),
|
||||||
.write_small => |v| try self.impl.queueWrite(v.data[0..v.len]),
|
.write_small => |v| try self.impl.queueWrite(v.data[0..v.len]),
|
||||||
|
@ -3,6 +3,7 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
const termio = @import("../termio.zig");
|
||||||
|
|
||||||
/// The messages that can be sent to an IO thread.
|
/// The messages that can be sent to an IO thread.
|
||||||
///
|
///
|
||||||
@ -28,6 +29,13 @@ pub const Message = union(enum) {
|
|||||||
padding: renderer.Padding,
|
padding: renderer.Padding,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The derived configuration to update the implementation with. This
|
||||||
|
/// is allocated via the allocator and is expected to be freed when done.
|
||||||
|
change_config: struct {
|
||||||
|
alloc: Allocator,
|
||||||
|
ptr: *termio.Impl.DerivedConfig,
|
||||||
|
},
|
||||||
|
|
||||||
/// Resize the window.
|
/// Resize the window.
|
||||||
resize: Resize,
|
resize: Resize,
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user