diff --git a/nix/devshell.nix b/nix/devshell.nix index e612f58fa..e343682bd 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -22,6 +22,7 @@ , expat , fontconfig , freetype +, glib , gtk4 , harfbuzz , libpng @@ -56,6 +57,7 @@ let libXrandr gtk4 + glib ]; in mkShell rec { name = "ghostty"; @@ -108,6 +110,7 @@ in mkShell rec { # Only needed for GTK builds gtk4 + glib ]; # This should be set onto the rpath of the ghostty binary if you want diff --git a/src/App.zig b/src/App.zig index 4efbda3b2..267b4f984 100644 --- a/src/App.zig +++ b/src/App.zig @@ -125,10 +125,11 @@ pub fn destroy(self: *App) void { self.windows.deinit(self.alloc); if (Darwin.enabled) self.darwin.deinit(); self.mailbox.destroy(self.alloc); - self.alloc.destroy(self); // Close our windowing runtime self.runtime.terminate(); + + self.alloc.destroy(self); } /// Wake up the app event loop. This should be called after any messages @@ -164,7 +165,7 @@ pub fn tick(self: *App) !void { i += 1; } - // Drain our mailbox only if we're not quitting. + // // Drain our mailbox only if we're not quitting. if (!self.quit) try self.drainMailbox(); } diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index b678ba180..c8d8c5142 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -4,6 +4,9 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const apprt = @import("../apprt.zig"); +const CoreApp = @import("../App.zig"); +const CoreWindow = @import("../Window.zig"); pub const c = @cImport({ @cInclude("gtk/gtk.h"); @@ -17,29 +20,111 @@ pub const App = struct { id: [:0]const u8 = "com.mitchellh.ghostty", }; + app: *c.GtkApplication, + ctx: *c.GMainContext, + pub fn init(opts: Options) !App { - const app = c.gtk_application_new(opts.id.ptr, c.G_APPLICATION_DEFAULT_FLAGS); + const app = @ptrCast(?*c.GtkApplication, c.gtk_application_new( + opts.id.ptr, + c.G_APPLICATION_DEFAULT_FLAGS, + )) orelse return error.GtkInitFailed; errdefer c.g_object_unref(app); - return .{}; + + // We don't use g_application_run, we want to manually control the + // loop so we have to do the same things the run function does: + // https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533 + const ctx = c.g_main_context_default() orelse return error.GtkContextFailed; + if (c.g_main_context_acquire(ctx) == 0) return error.GtkContextAcquireFailed; + errdefer c.g_main_context_release(ctx); + + const gapp = @ptrCast(*c.GApplication, app); + var err_: ?*c.GError = null; + if (c.g_application_register( + gapp, + null, + @ptrCast([*c][*c]c.GError, &err_), + ) == 0) { + if (err_) |err| { + log.warn("error registering application: {s}", .{err.message}); + c.g_error_free(err); + } + return error.GtkApplicationRegisterFailed; + } + + c.g_application_activate(gapp); + + return .{ .app = app, .ctx = ctx }; } pub fn terminate(self: App) void { - _ = self; + c.g_settings_sync(); + while (c.g_main_context_iteration(self.ctx, 0) != 0) {} + c.g_main_context_release(self.ctx); + c.g_object_unref(self.app); } pub fn wakeup(self: App) !void { _ = self; + c.g_main_context_wakeup(null); } pub fn wait(self: App) !void { - _ = self; + _ = c.g_main_context_iteration(self.ctx, 1); } }; pub const Window = struct { pub const Options = struct {}; + pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window { + _ = app; + _ = core_win; + _ = opts; + + return .{}; + } + pub fn deinit(self: *Window) void { _ = self; } + + pub fn setShouldClose(self: *Window) void { + _ = self; + } + + pub fn shouldClose(self: *const Window) bool { + _ = self; + return false; + } + + pub fn getContentScale(self: *const Window) !apprt.ContentScale { + _ = self; + return .{ .x = 1, .y = 1 }; + } + + pub fn getSize(self: *const Window) !apprt.WindowSize { + _ = self; + return .{ .width = 800, .height = 600 }; + } + + pub fn setSizeLimits(self: *Window, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void { + _ = self; + _ = min; + _ = max_; + } + + pub fn setTitle(self: *Window, slice: [:0]const u8) !void { + _ = self; + _ = slice; + } + + pub fn getClipboardString(self: *const Window) ![:0]const u8 { + _ = self; + return ""; + } + + pub fn setClipboardString(self: *const Window, val: [:0]const u8) !void { + _ = self; + _ = val; + } }; diff --git a/src/main.zig b/src/main.zig index ef75d1f74..b880c2116 100644 --- a/src/main.zig +++ b/src/main.zig @@ -101,9 +101,9 @@ pub fn main() !void { // Run our app with a single initial window to start. var app = try App.create(alloc, .{}, &config); defer app.destroy(); + try app.run(); if (build_config.app_runtime == .gtk) return; _ = try app.newWindow(.{}); - try app.run(); } // Required by tracy/tracy.zig to enable/disable tracy support. diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index b3fa6b99d..2f100e7c3 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -444,6 +444,7 @@ pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void { /// Callback called by renderer.Thread when it exits. pub fn threadExit(self: *const OpenGL) void { _ = self; + if (apprt.runtime == apprt.gtk) @panic("TODO"); gl.glad.unload(); glfw.makeContextCurrent(null); @@ -569,12 +570,14 @@ pub fn render( // Build our devmode draw data const devmode_data = devmode_data: { - if (state.devmode) |dm| { - if (dm.visible) { - imgui.ImplOpenGL3.newFrame(); - imgui.ImplGlfw.newFrame(); - try dm.update(); - break :devmode_data try dm.render(); + if (DevMode.enabled) { + if (state.devmode) |dm| { + if (dm.visible) { + imgui.ImplOpenGL3.newFrame(); + imgui.ImplGlfw.newFrame(); + try dm.update(); + break :devmode_data try dm.render(); + } } } @@ -639,11 +642,14 @@ pub fn render( try self.draw(); // If we have devmode, then render that - if (critical.devmode_data) |data| { - imgui.ImplOpenGL3.renderDrawData(data); + if (DevMode.enabled) { + if (critical.devmode_data) |data| { + imgui.ImplOpenGL3.renderDrawData(data); + } } // Swap our window buffers + if (apprt.runtime == apprt.gtk) @panic("TODO"); win.window.swapBuffers(); }