apprt/gtk: enable single instance mode

This commit is contained in:
Mitchell Hashimoto
2023-08-07 19:26:57 -07:00
parent 23935c3563
commit 60a36ca5cc
2 changed files with 27 additions and 16 deletions

View File

@ -31,11 +31,7 @@ const log = std.log.scoped(.gtk);
/// application frameworks also have this restriction so it simplifies /// application frameworks also have this restriction so it simplifies
/// the assumptions. /// the assumptions.
pub const App = struct { pub const App = struct {
pub const Options = 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",
};
core_app: *CoreApp, core_app: *CoreApp,
config: Config, config: Config,
@ -62,9 +58,14 @@ pub const App = struct {
var config = try Config.load(core_app.alloc); var config = try Config.load(core_app.alloc);
errdefer config.deinit(); 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. // Create our GTK Application which encapsulates our process.
const app = @as(?*c.GtkApplication, @ptrCast(c.gtk_application_new( const app = @as(?*c.GtkApplication, @ptrCast(c.gtk_application_new(
null, uniqueness_id,
// GTK >= 2.74 // GTK >= 2.74
if (@hasDecl(c, "G_APPLICATION_DEFAULT_FLAGS")) if (@hasDecl(c, "G_APPLICATION_DEFAULT_FLAGS"))
@ -77,7 +78,7 @@ pub const App = struct {
app, app,
"activate", "activate",
c.G_CALLBACK(&activate), c.G_CALLBACK(&activate),
null, core_app,
null, null,
G_CONNECT_DEFAULT, G_CONNECT_DEFAULT,
); );
@ -121,12 +122,19 @@ pub const App = struct {
.ctx = ctx, .ctx = ctx,
.cursor_default = cursor_default, .cursor_default = cursor_default,
.cursor_ibeam = cursor_ibeam, .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 // 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_signal_emit(self.app, c.g_signal_lookup("shutdown", c.g_application_get_type()), 0);
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);
@ -183,6 +191,9 @@ pub const App = struct {
_ = parent_; _ = parent_;
const alloc = self.core_app.alloc; 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 // Allocate a fixed pointer for our window. We try to minimize
// allocations but windows and other GUI requirements are so minimal // allocations but windows and other GUI requirements are so minimal
// compared to the steady-state terminal operation so we use heap // compared to the steady-state terminal operation so we use heap
@ -261,15 +272,18 @@ pub const App = struct {
}.callback, null); }.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 { fn activate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
_ = app; _ = app;
_ = ud;
// We purposely don't do anything on activation right now. We have const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
// 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 // Queue a new window
// we reached this point. _ = core_app.mailbox.push(.{
log.debug("application activated", .{}); .new_window = .{},
}, .{ .instant = {} });
} }
}; };

View File

@ -34,9 +34,6 @@ pub fn main() !void {
var app_runtime = try apprt.App.init(app, .{}); var app_runtime = try apprt.App.init(app, .{});
defer app_runtime.terminate(); defer app_runtime.terminate();
// Create an initial window
try app_runtime.newWindow(null);
// Run the GUI event loop // Run the GUI event loop
try app_runtime.run(); try app_runtime.run();
} }