const std = @import("std"); const build_options = @import("build_options"); const Allocator = std.mem.Allocator; const gdk = @import("gdk"); const Config = @import("../../config.zig").Config; const input = @import("../../input.zig"); const key = @import("key.zig"); const ApprtWindow = @import("class/window.zig").Window; const GlobalShortcuts = @import("class/global_shortcuts.zig").GlobalShortcuts; pub const noop = @import("winproto/noop.zig"); pub const x11 = @import("winproto/x11.zig"); pub const wayland = @import("winproto/wayland.zig"); pub const Protocol = enum { none, wayland, x11, }; /// App-state for the underlying windowing protocol. There should be one /// instance of this struct per application. pub const App = union(Protocol) { none: noop.App, wayland: if (build_options.wayland) wayland.App else noop.App, x11: if (build_options.x11) x11.App else noop.App, pub fn init( alloc: Allocator, gdk_display: *gdk.Display, app_id: [:0]const u8, config: *const Config, ) !App { inline for (@typeInfo(App).@"union".fields) |field| { if (try field.type.init( alloc, gdk_display, app_id, config, )) |v| { return @unionInit(App, field.name, v); } } return .{ .none = .{} }; } pub fn deinit(self: *App) void { switch (self.*) { inline else => |*v| v.deinit(), } } pub fn eventMods( self: *App, device: ?*gdk.Device, gtk_mods: gdk.ModifierType, ) input.Mods { return switch (self.*) { inline else => |*v| v.eventMods(device, gtk_mods), } orelse key.translateMods(gtk_mods); } pub fn supportsQuickTerminal(self: App) bool { return switch (self) { inline else => |v| v.supportsQuickTerminal(), }; } /// Set up necessary support for the quick terminal that must occur /// *before* the window-level winproto object is created. /// /// Only has an effect on the Wayland backend, where the gtk4-layer-shell /// library is initialized. pub fn initQuickTerminal(self: *App, apprt_window: *ApprtWindow) !void { switch (self.*) { inline else => |*v| try v.initQuickTerminal(apprt_window), } } /// Bind all global keybinds through a mechanism native to the /// windowing protocol. Returns false if there is none; the caller /// should then fall back to the XDG desktop portal. pub fn bindGlobalShortcuts( self: *App, shortcuts: *GlobalShortcuts, config: *const Config, ) bool { return switch (self.*) { inline else => |*v| v.bindGlobalShortcuts(shortcuts, config), }; } /// Unbind all global keybinds bound via bindGlobalShortcuts. pub fn clearGlobalShortcuts(self: *App) void { switch (self.*) { inline else => |*v| v.clearGlobalShortcuts(), } } }; /// Per-Window state for the underlying windowing protocol. /// /// In Wayland, the terminology used is "Surface" and for it, this is /// really "Surface"-specific state. But Ghostty uses the term "Surface" /// heavily to mean something completely different, so we use "Window" here /// to better match what it generally maps to in the Ghostty codebase. pub const Window = union(Protocol) { none: noop.Window, wayland: if (build_options.wayland) wayland.Window else noop.Window, x11: if (build_options.x11) x11.Window else noop.Window, pub fn init( alloc: Allocator, app: *App, apprt_window: *ApprtWindow, ) !Window { return switch (app.*) { inline else => |*v, tag| { inline for (@typeInfo(Window).@"union".fields) |field| { if (comptime std.mem.eql( u8, field.name, @tagName(tag), )) return @unionInit( Window, field.name, try field.type.init( alloc, v, apprt_window, ), ); } }, }; } pub fn deinit(self: *Window) void { switch (self.*) { inline else => |*v| v.deinit(), } } pub fn resizeEvent(self: *Window) !void { switch (self.*) { inline else => |*v| try v.resizeEvent(), } } pub fn syncAppearance(self: *Window) !void { switch (self.*) { inline else => |*v| try v.syncAppearance(), } } pub fn clientSideDecorationEnabled(self: Window) bool { return switch (self) { inline else => |v| v.clientSideDecorationEnabled(), }; } pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void { switch (self.*) { inline else => |*v| try v.addSubprocessEnv(env), } } pub fn setUrgent(self: *Window, urgent: bool) !void { switch (self.*) { inline else => |*v| try v.setUrgent(urgent), } } };