From 60a36ca5cc8e96658ad2349420f4076d21c65c76 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 7 Aug 2023 19:26:57 -0700 Subject: [PATCH] apprt/gtk: enable single instance mode --- src/apprt/gtk.zig | 40 +++++++++++++++++++++++++++------------- src/main.zig | 3 --- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 4c3471979..64d48be5a 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -31,11 +31,7 @@ const log = std.log.scoped(.gtk); /// application frameworks also have this restriction so it simplifies /// the assumptions. pub const App = struct { - pub const Options = struct { - /// GTK app ID. This is currently unused but should remain populated - /// for the future. - id: [:0]const u8 = "com.mitchellh.ghostty", - }; + pub const Options = struct {}; core_app: *CoreApp, config: Config, @@ -62,9 +58,14 @@ pub const App = struct { var config = try Config.load(core_app.alloc); errdefer config.deinit(); + // Our uniqueness ID is based on whether we're in a debug mode or not. + // In debug mode we want to be separate so we can develop Ghostty in + // Ghostty. + const uniqueness_id = "com.mitchellh.ghostty" ++ if (builtin.mode == .Debug) "-debug" else ""; + // Create our GTK Application which encapsulates our process. const app = @as(?*c.GtkApplication, @ptrCast(c.gtk_application_new( - null, + uniqueness_id, // GTK >= 2.74 if (@hasDecl(c, "G_APPLICATION_DEFAULT_FLAGS")) @@ -77,7 +78,7 @@ pub const App = struct { app, "activate", c.G_CALLBACK(&activate), - null, + core_app, null, G_CONNECT_DEFAULT, ); @@ -121,12 +122,19 @@ pub const App = struct { .ctx = ctx, .cursor_default = cursor_default, .cursor_ibeam = cursor_ibeam, + + // If we are NOT the primary instance, then we never want to run. + // This means that another instance of the GTK app is running and + // our "activate" call above will open a window. + .running = c.g_application_get_is_remote(gapp) == 0, }; } // Terminate the application. The application will not be restarted after // this so all global state can be cleaned up. pub fn terminate(self: *App) void { + c.g_signal_emit(self.app, c.g_signal_lookup("shutdown", c.g_application_get_type()), 0); + c.g_settings_sync(); while (c.g_main_context_iteration(self.ctx, 0) != 0) {} c.g_main_context_release(self.ctx); @@ -183,6 +191,9 @@ pub const App = struct { _ = parent_; const alloc = self.core_app.alloc; + // If we're trying to quit, then do not open any new windows. + if (!self.running) return; + // Allocate a fixed pointer for our window. We try to minimize // allocations but windows and other GUI requirements are so minimal // compared to the steady-state terminal operation so we use heap @@ -261,15 +272,18 @@ pub const App = struct { }.callback, null); } + /// This is called by the "activate" signal. This is sent on program + /// startup and also when a secondary instance launches and requests + /// a new window. fn activate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void { _ = app; - _ = ud; - // We purposely don't do anything on activation right now. We have - // this callback because if we don't then GTK emits a warning to - // stderr that we don't want. We emit a debug log just so that we know - // we reached this point. - log.debug("application activated", .{}); + const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return)); + + // Queue a new window + _ = core_app.mailbox.push(.{ + .new_window = .{}, + }, .{ .instant = {} }); } }; diff --git a/src/main.zig b/src/main.zig index 2ba800b0e..1f030a150 100644 --- a/src/main.zig +++ b/src/main.zig @@ -34,9 +34,6 @@ pub fn main() !void { var app_runtime = try apprt.App.init(app, .{}); defer app_runtime.terminate(); - // Create an initial window - try app_runtime.newWindow(null); - // Run the GUI event loop try app_runtime.run(); }