From 1b316638659bcc09633618ff499bd198cb86bb62 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Sep 2024 17:00:38 -0700 Subject: [PATCH] apprt/embedded: new_window can be called without a parent --- .../Features/Terminal/TerminalManager.swift | 6 +++++ macos/Sources/Ghostty/Ghostty.App.swift | 6 ++++- src/App.zig | 1 + src/Surface.zig | 27 ++++++++++--------- src/apprt/embedded.zig | 18 ++++++++----- src/input/Binding.zig | 9 +++++-- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index 8b9ed3cad..3930012df 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -78,6 +78,12 @@ class TerminalManager { window.toggleFullScreen(nil) } + // If our app isn't active, we make it active. All new_window actions + // force our app to be active. + if !NSApp.isActive { + NSApp.activate(ignoringOtherApps: true) + } + // We're dispatching this async because otherwise the lastCascadePoint doesn't // take effect. Our best theory is there is some next-event-loop-tick logic // that Cocoa is doing that we need to be after. diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 7b8c5688f..30efb289e 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -631,7 +631,11 @@ extension Ghostty { } static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) { - let surface = self.surfaceUserdata(from: userdata) + let surface: SurfaceView? = if let userdata { + self.surfaceUserdata(from: userdata) + } else { + nil + } NotificationCenter.default.post( name: Notification.ghosttyNewWindow, diff --git a/src/App.zig b/src/App.zig index 31f3e451b..d93e00a2a 100644 --- a/src/App.zig +++ b/src/App.zig @@ -317,6 +317,7 @@ pub fn performAction( .unbind => unreachable, .ignore => {}, .quit => try self.setQuit(), + .new_window => try self.newWindow(rt_app, .{ .parent = null }), .open_config => try self.openConfig(rt_app), .reload_config => try self.reloadConfig(rt_app), .close_all_windows => { diff --git a/src/Surface.zig b/src/Surface.zig index b5f7b9293..85a5face0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3416,19 +3416,22 @@ fn showMouse(self: *Surface) void { /// will ever return false. We can expand this in the future if it becomes /// useful. We did previous/next tab so we could implement #498. pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool { - // Handle app-scoped bindings by sending it to the app. - switch (action.scope()) { - .app => { - try self.app.performAction( + // Forward app-scoped actions to the app. Some app-scoped actions are + // special-cased here because they do some special things when performed + // from the surface. + if (action.scoped(.app)) |app_action| { + switch (app_action) { + .new_window => try self.app.newWindow( + self.rt_app, + .{ .parent = self }, + ), + + else => try self.app.performAction( self.rt_app, action.scoped(.app).?, - ); - - return true; - }, - - // Surface fallthrough and handle - .surface => {}, + ), + } + return true; } switch (action.scoped(.surface).?) { @@ -3653,8 +3656,6 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool v, ), - .new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }), - .new_tab => { if (@hasDecl(apprt.Surface, "newTab")) { try self.rt_surface.newTab(); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index e0eecf8cc..01e3d0743 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -86,7 +86,8 @@ pub const App = struct { /// New tab with options. new_tab: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null, - /// New window with options. + /// New window with options. The surface may be null if there is no + /// target surface. new_window: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null, /// Control the inspector visibility @@ -495,14 +496,19 @@ pub const App = struct { } pub fn newWindow(self: *App, parent: ?*CoreSurface) !void { - _ = self; - - // Right now we only support creating a new window with a parent - // through this code. - // The other case is handled by the embedding runtime. + // If we have a parent, the surface logic handles it. if (parent) |surface| { try surface.rt_surface.newWindow(); + return; } + + // No parent, call the new window callback. + const func = self.opts.new_window orelse { + log.info("runtime embedder does not support new_window", .{}); + return; + }; + + func(null, .{}); } }; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 93e046d1a..8f129065d 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -279,7 +279,8 @@ pub const Action = union(enum) { /// available values. write_selection_file: WriteScreenAction, - /// Open a new window. + /// Open a new window. If the application isn't currently focused, + /// this will bring it to the front. new_window: void, /// Open a new tab. @@ -562,6 +563,10 @@ pub const Action = union(enum) { .quit, => .app, + // These are app but can be special-cased in a surface context. + .new_window, + => .app, + // Obviously surface actions. .csi, .esc, @@ -593,12 +598,12 @@ pub const Action = union(enum) { .toggle_window_decorations, .toggle_secure_input, .crash, + => .surface, // These are less obvious surface actions. They're surface // actions because they are relevant to the surface they // come from. For example `new_window` needs to be sourced to // a surface so inheritance can be done correctly. - .new_window, .new_tab, .previous_tab, .next_tab,