From 8a2a849cc8a2977fc8449d39069a0ad849a7a467 Mon Sep 17 00:00:00 2001 From: Collin Dickert Date: Thu, 4 Jan 2024 21:15:12 -0500 Subject: [PATCH 01/15] add CodeNewRoman Nerd Font to quirks --- src/quirks.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quirks.zig b/src/quirks.zig index a43001e5a..e521eeb56 100644 --- a/src/quirks.zig +++ b/src/quirks.zig @@ -23,6 +23,7 @@ pub fn disableDefaultFontFeatures(face: *const font.Face) bool { // looks really bad in terminal grids, so we want to disable ligatures // by default for these faces. return std.mem.eql(u8, name, "CodeNewRoman") or + std.mem.eql(u8, name, "CodeNewRoman Nerd Font") or std.mem.eql(u8, name, "Menlo") or std.mem.eql(u8, name, "Monaco"); } From cc29f0686379187733fe8824a2bea3ec63d93448 Mon Sep 17 00:00:00 2001 From: widberg Date: Thu, 4 Jan 2024 21:24:15 -0500 Subject: [PATCH 02/15] Don't build with LTO on Windows --- build.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.zig b/build.zig index 8856eb766..bed0bf0e0 100644 --- a/build.zig +++ b/build.zig @@ -251,6 +251,10 @@ pub fn build(b: *std.Build) !void { ); } + // Building with LTO on Windows is broken. + // https://github.com/ziglang/zig/issues/15958 + if (target.isWindows()) exe.want_lto = false; + // If we're installing, we get the install step so we can add // additional dependencies to it. const install_step = if (app_runtime != .none) step: { From 2e79b5c63dbb1ce8950e16990a8951d0c5e325fb Mon Sep 17 00:00:00 2001 From: widberg Date: Thu, 4 Jan 2024 21:24:24 -0500 Subject: [PATCH 03/15] Use `cmd.exe` instead of `sh` on Windows --- src/termio/Exec.zig | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index be8a6678f..04ea9293b 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1090,14 +1090,21 @@ const Subprocess = struct { break :args try args.toOwnedSlice(); } - // We run our shell wrapped in `/bin/sh` so that we don't have - // to parse the commadnd line ourselves if it has arguments. - // Additionally, some environments (NixOS, I found) use /bin/sh - // to setup some environment variables that are important to - // have set. - try args.append("/bin/sh"); - if (internal_os.isFlatpak()) try args.append("-l"); - try args.append("-c"); + if (builtin.os.tag == .windows) { + // We run our shell wrapped in `cmd.exe` so that we don't have + // to parse the command line ourselves if it has arguments. + try args.append("C:\\Windows\\System32\\cmd.exe"); + try args.append("/C"); + } else { + // We run our shell wrapped in `/bin/sh` so that we don't have + // to parse the command line ourselves if it has arguments. + // Additionally, some environments (NixOS, I found) use /bin/sh + // to setup some environment variables that are important to + // have set. + try args.append("/bin/sh"); + if (internal_os.isFlatpak()) try args.append("-l"); + try args.append("-c"); + } try args.append(opts.full_config.command orelse default_path); break :args try args.toOwnedSlice(); }; From 38c5258d6e88e7d5e1ff7ae52e3bd8d3dd4b0a87 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Jan 2024 19:48:08 -0800 Subject: [PATCH 04/15] termio/exec: small change --- src/termio/Exec.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 04ea9293b..8c55fa114 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1090,7 +1090,7 @@ const Subprocess = struct { break :args try args.toOwnedSlice(); } - if (builtin.os.tag == .windows) { + if (comptime builtin.os.tag == .windows) { // We run our shell wrapped in `cmd.exe` so that we don't have // to parse the command line ourselves if it has arguments. try args.append("C:\\Windows\\System32\\cmd.exe"); @@ -1105,6 +1105,7 @@ const Subprocess = struct { if (internal_os.isFlatpak()) try args.append("-l"); try args.append("-c"); } + try args.append(opts.full_config.command orelse default_path); break :args try args.toOwnedSlice(); }; From 698954031a812175e79bdb87a85fb5d8c54dceaa Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 4 Jan 2024 23:29:33 -0600 Subject: [PATCH 05/15] Add 'Open Config' menu to the GTK runtime and synchronize config-related keybindings across platforms. --- src/apprt/gtk/App.zig | 14 ++++++++++++++ src/config/Config.zig | 19 ++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 3d30d9ba5..46a982348 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -293,6 +293,7 @@ fn updateConfigErrors(self: *App) !void { fn syncActionAccelerators(self: *App) !void { try self.syncActionAccelerator("app.quit", .{ .quit = {} }); + try self.syncActionAccelerator("app.open_config", .{ .open_config = {} }); try self.syncActionAccelerator("app.reload_config", .{ .reload_config = {} }); try self.syncActionAccelerator("app.toggle_inspector", .{ .inspector = .toggle }); try self.syncActionAccelerator("win.close", .{ .close_surface = {} }); @@ -479,6 +480,17 @@ fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void { }, .{ .forever = {} }); } +fn gtkActionOpenConfig( + _: *c.GSimpleAction, + _: *c.GVariant, + ud: ?*anyopaque, +) callconv(.C) void { + const self: *App = @ptrCast(@alignCast(ud orelse return)); + _ = self.core_app.mailbox.push(.{ + .open_config = {}, + }, .{ .forever = {} }); +} + fn gtkActionReloadConfig( _: *c.GSimpleAction, _: *c.GVariant, @@ -507,6 +519,7 @@ fn gtkActionQuit( fn initActions(self: *App) void { const actions = .{ .{ "quit", >kActionQuit }, + .{ "open_config", >kActionOpenConfig }, .{ "reload_config", >kActionReloadConfig }, }; @@ -545,6 +558,7 @@ fn initMenu(self: *App) void { defer c.g_object_unref(section); c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section))); c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector"); + c.g_menu_append(section, "Open Configuration", "app.open_config"); c.g_menu_append(section, "Reload Configuration", "app.reload_config"); c.g_menu_append(section, "About Ghostty", "win.about"); } diff --git a/src/config/Config.zig b/src/config/Config.zig index 5b816c676..025d09412 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -946,11 +946,18 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { const alloc = result._arena.?.allocator(); // Add our default keybindings + + // keybinds for opening and reloading config try result.keybind.set.put( alloc, - .{ .key = .space, .mods = .{ .super = true, .alt = true, .ctrl = true } }, + .{ .key = .comma, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) }, .{ .reload_config = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .comma, .mods = inputpkg.ctrlOrSuper(.{}) }, + .{ .open_config = {} }, + ); { // On macOS we default to super but Linux ctrl+shift since @@ -1210,16 +1217,6 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .q, .mods = .{ .super = true } }, .{ .quit = {} }, ); - try result.keybind.set.put( - alloc, - .{ .key = .comma, .mods = .{ .super = true, .shift = true } }, - .{ .reload_config = {} }, - ); - try result.keybind.set.put( - alloc, - .{ .key = .comma, .mods = .{ .super = true } }, - .{ .open_config = {} }, - ); try result.keybind.set.put( alloc, .{ .key = .k, .mods = .{ .super = true } }, From be176bb8bd933f1258445c9906fdd34f42f0cf16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Jan 2024 21:47:12 -0800 Subject: [PATCH 06/15] renderer: reload background, foreground, cursor-color at runtime Fixes #1212 --- src/renderer/Metal.zig | 5 +++++ src/renderer/OpenGL.zig | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 00104ffa8..81518c6e6 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1305,6 +1305,11 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // Set our new minimum contrast self.uniforms.min_contrast = config.min_contrast; + // Set our new colors + self.background_color = config.background; + self.foreground_color = config.foreground; + self.cursor_color = config.cursor_color; + self.config.deinit(); self.config = config.*; } diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index de7b3ffbf..aea6991df 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1658,6 +1658,11 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void { self.font_shaper = font_shaper; } + // Set our new colors + self.background_color = config.background; + self.foreground_color = config.foreground; + self.cursor_color = config.cursor_color; + // Update our uniforms self.deferred_config = .{}; From 929552354c3eae6ee140bf1585c9bad4d5b66ad1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 07:32:53 -0800 Subject: [PATCH 07/15] update README about zig version --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8518fbba7..8719a7bff 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,12 @@ version when I get closer to generally releasing this to ease downstream packagers. You can find binary releases of nightly builds on the [Zig downloads page](https://ziglang.org/download/). +Under some conditions, the very latest Zig nightly may not work (for example, +when Zig introduces breaking changes that Ghostty or our dependencies haven't +been upated for). To be sure what Zig version will work, see the `build.zig` +file which has a constant `required_zig`. Ghostty plans to pin to Zig 0.12 +once it is released, which will make all of this much easier. + With Zig and necessary dependencies installed, a binary can be built using `zig build`: From 3a93831e395b06c62d7eecd1fc8687928f015d84 Mon Sep 17 00:00:00 2001 From: Evan Boehs Date: Fri, 5 Jan 2024 10:36:26 -0500 Subject: [PATCH 08/15] Fix #1213 (create dir if needed for settings) --- src/config/edit.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/config/edit.zig b/src/config/edit.zig index c673991df..a28cfc362 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -5,9 +5,23 @@ const internal_os = @import("../os/main.zig"); /// Open the configuration in the OS default editor according to the default /// paths the main config file could be in. pub fn open(alloc_gpa: Allocator) !void { + // default dir + const config_dir = try internal_os.xdg.config(alloc_gpa, .{ .subdir = "ghostty" }); // default location const config_path = try internal_os.xdg.config(alloc_gpa, .{ .subdir = "ghostty/config" }); - defer alloc_gpa.free(config_path); + + defer { + alloc_gpa.free(config_path); + alloc_gpa.free(config_dir); + } + + // Check if the directory exists, create it if it doesn't + _ = std.fs.makeDirAbsolute(config_dir) catch |err| { + switch (err) { + error.PathAlreadyExists => {}, + else => return err, + } + }; // Try to create file and go on if it already exists _ = std.fs.createFileAbsolute( From 5fe2d03e96c94b031a4ae2a5bb315a21e9844117 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Fri, 5 Jan 2024 09:21:34 -0600 Subject: [PATCH 09/15] cli: strip CR in line iterator --- src/cli/args.zig | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/cli/args.zig b/src/cli/args.zig index 6444400a7..c226493f9 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -712,8 +712,8 @@ pub fn LineIterator(comptime ReaderType: type) type { unreachable; } orelse return null; - // Trim any whitespace around it - const trim = std.mem.trim(u8, entry, whitespace); + // Trim any whitespace (including CR) around it + const trim = std.mem.trim(u8, entry, whitespace ++ "\r"); if (trim.len != entry.len) { std.mem.copyForwards(u8, entry, trim); entry = entry[0..trim.len]; @@ -833,3 +833,14 @@ test "LineIterator spaces around '='" { try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next()); } + +test "LineIterator with CRLF line endings" { + const testing = std.testing; + var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n"); + + var iter = lineIterator(fbs.reader()); + try testing.expectEqualStrings("--A", iter.next().?); + try testing.expectEqualStrings("--B=C", iter.next().?); + try testing.expectEqual(@as(?[]const u8, null), iter.next()); + try testing.expectEqual(@as(?[]const u8, null), iter.next()); +} From a62061aec6b59327b6d9e2e5a85acf2ae17e9293 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 07:54:20 -0800 Subject: [PATCH 10/15] config: use dirname so we don't have to alloc a new dir for edit --- src/config/edit.zig | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/config/edit.zig b/src/config/edit.zig index a28cfc362..00c8df718 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -5,24 +5,22 @@ const internal_os = @import("../os/main.zig"); /// Open the configuration in the OS default editor according to the default /// paths the main config file could be in. pub fn open(alloc_gpa: Allocator) !void { - // default dir - const config_dir = try internal_os.xdg.config(alloc_gpa, .{ .subdir = "ghostty" }); // default location const config_path = try internal_os.xdg.config(alloc_gpa, .{ .subdir = "ghostty/config" }); + defer alloc_gpa.free(config_path); - defer { - alloc_gpa.free(config_path); - alloc_gpa.free(config_dir); + // Check if the directory exists, create it if it doesn't. This dirname + // call should always succeed but we still check for errors just in case, + // no harm if we skip this step the open may just fail. + if (std.fs.path.dirname(config_path)) |config_dir| { + _ = std.fs.makeDirAbsolute(config_dir) catch |err| { + switch (err) { + error.PathAlreadyExists => {}, + else => return err, + } + }; } - // Check if the directory exists, create it if it doesn't - _ = std.fs.makeDirAbsolute(config_dir) catch |err| { - switch (err) { - error.PathAlreadyExists => {}, - else => return err, - } - }; - // Try to create file and go on if it already exists _ = std.fs.createFileAbsolute( config_path, From cf18e23256b56b4b5bb154de2fc3bc2c3740270f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 09:01:30 -0800 Subject: [PATCH 11/15] config: create the config dir recursively for edit --- src/config/edit.zig | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/config/edit.zig b/src/config/edit.zig index 00c8df718..38d9f2b7f 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -9,16 +9,9 @@ pub fn open(alloc_gpa: Allocator) !void { const config_path = try internal_os.xdg.config(alloc_gpa, .{ .subdir = "ghostty/config" }); defer alloc_gpa.free(config_path); - // Check if the directory exists, create it if it doesn't. This dirname - // call should always succeed but we still check for errors just in case, - // no harm if we skip this step the open may just fail. + // Create config directory recursively. if (std.fs.path.dirname(config_path)) |config_dir| { - _ = std.fs.makeDirAbsolute(config_dir) catch |err| { - switch (err) { - error.PathAlreadyExists => {}, - else => return err, - } - }; + try std.fs.cwd().makePath(config_dir); } // Try to create file and go on if it already exists From 9bf13c62368185e0eb32b6e7cb21dc37cd6826b1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 09:42:50 -0800 Subject: [PATCH 12/15] macos: set initial window size in controller window init Fixes #1227 Fixes #1206 This moves the logic that respects `window-width` and `window-height` to the controller window initialization. This is where it should've been all the time but we previously didn't use a controller architecture so we did our best to use heuristics in the view to do this. This location now ensures that this only happens on window initialization and only if we have one surface. --- .../Terminal/TerminalController.swift | 17 ++++++++++ macos/Sources/Ghostty/SurfaceView.swift | 34 ------------------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 02cd2de9c..a96e56eb8 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -164,6 +164,23 @@ class TerminalController: NSWindowController, NSWindowDelegate, // covered in thie GitHub issue: https://github.com/mitchellh/ghostty/pull/376 window.colorSpace = NSColorSpace.sRGB + // If we have only a single surface (no splits) and that surface requested + // an initial size then we set it here now. + if case let .leaf(leaf) = surfaceTree { + if let initialSize = leaf.surface.initialSize { + // Setup our frame. We need to first subtract the views frame so that we can + // just get the chrome frame so that we only affect the surface view size. + var frame = window.frame + frame.size.width -= leaf.surface.frame.size.width + frame.size.height -= leaf.surface.frame.size.height + frame.size.width += initialSize.width + frame.size.height += initialSize.height + + // We have no tabs and we are not a split, so set the initial size of the window. + window.setFrame(frame, display: true) + } + } + // Center the window to start, we'll move the window frame automatically // when cascading. window.center() diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 11ddb6dd5..bd4a2b0a5 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -534,9 +534,6 @@ extension Ghostty { override func viewDidMoveToWindow() { // Set our background blur if requested setWindowBackgroundBlur(window) - - // Try to set the initial window size if we have one - setInitialWindowSize() } /// This function sets the window background to blur if it is configured on the surface. @@ -567,37 +564,6 @@ extension Ghostty { // If we have a blur, set the blur ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque()) } - - /// Sets the initial window size requested by the Ghostty config. - /// - /// This only works under certain conditions: - /// - The window must be "uninitialized" - /// - The window must have no tabs - /// - Ghostty must have requested an initial size - /// - private func setInitialWindowSize() { - guard let initialSize = initialSize else { return } - - // If we have tabs, then do not change the window size - guard let window = self.window else { return } - guard let windowControllerRaw = window.windowController else { return } - guard let windowController = windowControllerRaw as? TerminalController else { return } - guard case .leaf = windowController.surfaceTree else { return } - - // If our window is full screen, we do not set the frame - guard !window.styleMask.contains(.fullScreen) else { return } - - // Setup our frame. We need to first subtract the views frame so that we can - // just get the chrome frame so that we only affect the surface view size. - var frame = window.frame - frame.size.width -= self.frame.size.width - frame.size.height -= self.frame.size.height - frame.size.width += initialSize.width - frame.size.height += initialSize.height - - // We have no tabs and we are not a split, so set the initial size of the window. - window.setFrame(frame, display: true) - } override func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() From 3aad646be2f3991e4fa6181439c00f921175810a Mon Sep 17 00:00:00 2001 From: Vivek Roy Date: Sat, 6 Jan 2024 00:35:24 +0530 Subject: [PATCH 13/15] macos: Address window spawning and ordering issues from service. --- macos/Sources/AppDelegate.swift | 18 ++++++++++++------ .../Features/Terminal/TerminalManager.swift | 4 ++-- macos/Sources/Ghostty/AppState.swift | 3 +++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 1b6e5bd29..ded539fba 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -104,12 +104,6 @@ class AppDelegate: NSObject, // Initial config loading configDidReload(ghostty) - // Let's launch our first window. We only do this if we have no other windows. It - // is possible to have other windows if we're opening a URL since `application(_:openFile:)` - // is called before this. - if (terminalManager.windows.count == 0) { - terminalManager.newWindow() - } // Register our service provider. This must happen after everything // else is initialized. @@ -132,6 +126,18 @@ class AppDelegate: NSObject, center.delegate = self } + func applicationDidBecomeActive(_ notification: Notification) { + // Let's launch our first window. We only do this if we have no other windows. It + // is possible to have other windows if we're opening a URL since `application(_:openFile:)` + // is called before this. + if (ghostty.firstLaunch) { + if terminalManager.windows.count == 0 { + terminalManager.newWindow() + } + ghostty.firstLaunch = false + } + } + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return ghostty.shouldQuitAfterLastWindowClosed } diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index 745bfdf24..65ba5b03a 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -33,8 +33,8 @@ class TerminalManager { } } - // If we have no main window, just use the first window. - return windows.first + // If we have no main window, just use the last window. + return windows.last } init(_ ghostty: Ghostty.AppState) { diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 0f4570e5c..240d09ac7 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -36,6 +36,9 @@ extension Ghostty { /// Optional delegate weak var delegate: GhosttyAppStateDelegate? + /// True when application is first launched. Immidiately set to false after first window is shown. + var firstLaunch: Bool = true + /// The ghostty global configuration. This should only be changed when it is definitely /// 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). From 42c10de81479766f6fc5a3831ca05ae25f66ac9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 12:32:18 -0800 Subject: [PATCH 14/15] macos: move active state to delegate --- macos/Sources/AppDelegate.swift | 19 +++++++++++-------- macos/Sources/Ghostty/AppState.swift | 3 --- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index ded539fba..c582b4628 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -57,6 +57,9 @@ class AppDelegate: NSObject, /// The dock menu private var dockMenu: NSMenu = NSMenu() + /// This is only true before application has become active. + private var applicationHasBecomeActive: Bool = false + /// The ghostty global state. Only one per process. let ghostty: Ghostty.AppState = Ghostty.AppState() @@ -104,7 +107,6 @@ class AppDelegate: NSObject, // Initial config loading configDidReload(ghostty) - // Register our service provider. This must happen after everything // else is initialized. NSApp.servicesProvider = ServiceProvider() @@ -127,14 +129,15 @@ class AppDelegate: NSObject, } func applicationDidBecomeActive(_ notification: Notification) { + guard !applicationHasBecomeActive else { return } + applicationHasBecomeActive = true + // Let's launch our first window. We only do this if we have no other windows. It - // is possible to have other windows if we're opening a URL since `application(_:openFile:)` - // is called before this. - if (ghostty.firstLaunch) { - if terminalManager.windows.count == 0 { - terminalManager.newWindow() - } - ghostty.firstLaunch = false + // is possible to have other windows in a few scenarios: + // - if we're opening a URL since `application(_:openFile:)` is called before this. + // - if we're restoring from persisted state + if terminalManager.windows.count == 0 { + terminalManager.newWindow() } } diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 240d09ac7..0f4570e5c 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -36,9 +36,6 @@ extension Ghostty { /// Optional delegate weak var delegate: GhosttyAppStateDelegate? - /// True when application is first launched. Immidiately set to false after first window is shown. - var firstLaunch: Bool = true - /// The ghostty global configuration. This should only be changed when it is definitely /// 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). From 48f316ebd29bf9a2bee38a48e598dd7e44c58ae5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 5 Jan 2024 14:27:12 -0800 Subject: [PATCH 15/15] termio: support XDG data dirs greater than 4k for fish shell integration Fixes #1228 --- src/termio/Exec.zig | 1 + src/termio/shell_integration.zig | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 8c55fa114..a694ebc19 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1137,6 +1137,7 @@ const Subprocess = struct { const dir = opts.resources_dir orelse break :shell null; break :shell try shell_integration.setup( + gpa, dir, path, &env, diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index 08733f6ee..296c95db6 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const EnvMap = std.process.EnvMap; const config = @import("../config.zig"); @@ -14,7 +15,13 @@ pub const Shell = enum { /// integrated shell integration. This returns true if shell /// integration was successful. False could mean many things: /// the shell type wasn't detected, etc. +/// +/// The allocator is only used for temporary values, so it should +/// be given a general purpose allocator. No allocated memory remains +/// after this function returns except anything allocated by the +/// EnvMap. pub fn setup( + alloc: Allocator, resource_dir: []const u8, command_path: []const u8, env: *EnvMap, @@ -28,7 +35,7 @@ pub fn setup( const shell: Shell = shell: { if (std.mem.eql(u8, "fish", exe)) { - try setupFish(resource_dir, env); + try setupFish(alloc, resource_dir, env); break :shell .fish; } @@ -51,6 +58,7 @@ pub fn setup( /// Fish will automatically load configuration in XDG_DATA_DIRS /// "fish/vendor_conf.d/*.fish". fn setupFish( + alloc_gpa: Allocator, resource_dir: []const u8, env: *EnvMap, ) !void { @@ -71,17 +79,19 @@ fn setupFish( if (env.get("XDG_DATA_DIRS")) |old| { // We have an old value, We need to prepend our value to it. - // We use a 4K buffer to hold our XDG_DATA_DIR value. The stack - // on macOS is at least 512K and Linux is 8MB or more. So this - // should fit. If the user has a XDG_DATA_DIR value that is longer - // than this then it will fail... and we will cross that bridge - // when we actually get there. This avoids us needing an allocator. - var buf: [4096]u8 = undefined; - const prepended = try std.fmt.bufPrint( - &buf, + // We attempt to avoid allocating by using the stack up to 4K. + // Max stack size is considerably larger on macOS and Linux but + // 4K is a reasonable size for this for most cases. However, env + // vars can be significantly larger so if we have to we fall + // back to a heap allocated value. + var stack_alloc = std.heap.stackFallback(4096, alloc_gpa); + const alloc = stack_alloc.get(); + const prepended = try std.fmt.allocPrint( + alloc, "{s}{c}{s}", .{ integ_dir, std.fs.path.delimiter, old }, ); + defer alloc.free(prepended); try env.put("XDG_DATA_DIRS", prepended); } else {