gtk: unify Wayland and X11 platforms

This commit is contained in:
Leah Amelia Chen
2025-01-02 23:53:22 +08:00
committed by Mitchell Hashimoto
parent 6ef757a8f8
commit 03fee2ac33
8 changed files with 304 additions and 179 deletions

View File

@ -36,8 +36,7 @@ const c = @import("c.zig").c;
const version = @import("version.zig"); const version = @import("version.zig");
const inspector = @import("inspector.zig"); const inspector = @import("inspector.zig");
const key = @import("key.zig"); const key = @import("key.zig");
const x11 = @import("x11.zig"); const protocol = @import("protocol.zig");
const wayland = @import("wayland.zig");
const testing = std.testing; const testing = std.testing;
const log = std.log.scoped(.gtk); const log = std.log.scoped(.gtk);
@ -71,11 +70,7 @@ clipboard_confirmation_window: ?*ClipboardConfirmationWindow = null,
/// This is set to false when the main loop should exit. /// This is set to false when the main loop should exit.
running: bool = true, running: bool = true,
/// Xkb state (X11 only). Will be null on Wayland. protocol: protocol.App,
x11_xkb: ?x11.Xkb = null,
/// Wayland app state. Will be null on X11.
wayland: ?wayland.AppState = null,
/// The base path of the transient cgroup used to put all surfaces /// The base path of the transient cgroup used to put all surfaces
/// into their own cgroup. This is only set if cgroups are enabled /// into their own cgroup. This is only set if cgroups are enabled
@ -364,46 +359,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
return error.GtkApplicationRegisterFailed; return error.GtkApplicationRegisterFailed;
} }
// Perform all X11 initialization. This ultimately returns the X11 const app_protocol = try protocol.App.init(display, &config, app_id);
// keyboard state but the block does more than that (i.e. setting up
// WM_CLASS).
const x11_xkb: ?x11.Xkb = x11_xkb: {
if (comptime !build_options.x11) break :x11_xkb null;
if (!x11.is_display(display)) break :x11_xkb null;
// Set the X11 window class property (WM_CLASS) if are are on an X11
// display.
//
// Note that we also set the program name here using g_set_prgname.
// This is how the instance name field for WM_CLASS is derived when
// calling gdk_x11_display_set_program_class; there does not seem to be
// a way to set it directly. It does not look like this is being set by
// our other app initialization routines currently, but since we're
// currently deriving its value from x11-instance-name effectively, I
// feel like gating it behind an X11 check is better intent.
//
// This makes the property show up like so when using xprop:
//
// WM_CLASS(STRING) = "ghostty", "com.mitchellh.ghostty"
//
// Append "-debug" on both when using the debug build.
//
const prgname = if (config.@"x11-instance-name") |pn|
pn
else if (builtin.mode == .Debug)
"ghostty-debug"
else
"ghostty";
c.g_set_prgname(prgname);
c.gdk_x11_display_set_program_class(display, app_id);
// Set up Xkb
break :x11_xkb try x11.Xkb.init(display);
};
// Initialize Wayland state
var wl = wayland.AppState.init(display);
if (wl) |*w| try w.register();
// This just calls the `activate` signal but its part of the normal startup // This just calls the `activate` signal but its part of the normal startup
// routine so we just call it, but only if the config allows it (this allows // routine so we just call it, but only if the config allows it (this allows
@ -429,8 +385,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
.config = config, .config = config,
.ctx = ctx, .ctx = ctx,
.cursor_none = cursor_none, .cursor_none = cursor_none,
.x11_xkb = x11_xkb, .protocol = app_protocol,
.wayland = wl,
.single_instance = single_instance, .single_instance = single_instance,
// If we are NOT the primary instance, then we never want to run. // 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 // This means that another instance of the GTK app is running and

View File

@ -25,7 +25,6 @@ const ResizeOverlay = @import("ResizeOverlay.zig");
const inspector = @import("inspector.zig"); const inspector = @import("inspector.zig");
const gtk_key = @import("key.zig"); const gtk_key = @import("key.zig");
const c = @import("c.zig").c; const c = @import("c.zig").c;
const x11 = @import("x11.zig");
const log = std.log.scoped(.gtk_surface); const log = std.log.scoped(.gtk_surface);
@ -825,9 +824,6 @@ pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
c.g_object_get_property(@ptrCast(@alignCast(settings)), "gtk-xft-dpi", &value); c.g_object_get_property(@ptrCast(@alignCast(settings)), "gtk-xft-dpi", &value);
const gtk_xft_dpi = c.g_value_get_int(&value); const gtk_xft_dpi = c.g_value_get_int(&value);
// As noted above gtk-xft-dpi is multiplied by 1024, so we divide by
// 1024, then divide by the default value (96) to derive a scale. Note
// gtk-xft-dpi can be fractional, so we use floating point math here.
const xft_dpi: f32 = @as(f32, @floatFromInt(gtk_xft_dpi)) / 1024; const xft_dpi: f32 = @as(f32, @floatFromInt(gtk_xft_dpi)) / 1024;
break :xft_scale xft_dpi / 96; break :xft_scale xft_dpi / 96;
}; };
@ -1384,6 +1380,10 @@ fn gtkResize(area: *c.GtkGLArea, width: c.gint, height: c.gint, ud: ?*anyopaque)
return; return;
}; };
if (self.container.window()) |window| window.protocol.onResize() catch |err| {
log.warn("failed to notify X11/Wayland integration of resize={}", .{err});
};
self.resize_overlay.maybeShow(); self.resize_overlay.maybeShow();
} }
} }
@ -1699,11 +1699,10 @@ pub fn keyEvent(
// Get our modifier for the event // Get our modifier for the event
const mods: input.Mods = gtk_key.eventMods( const mods: input.Mods = gtk_key.eventMods(
@ptrCast(self.gl_area),
event, event,
physical_key, physical_key,
gtk_mods, gtk_mods,
if (self.app.x11_xkb) |*xkb| xkb else null, &self.app.protocol,
); );
// Get our consumed modifiers // Get our consumed modifiers

View File

@ -25,7 +25,7 @@ const gtk_key = @import("key.zig");
const Notebook = @import("notebook.zig").Notebook; const Notebook = @import("notebook.zig").Notebook;
const HeaderBar = @import("headerbar.zig").HeaderBar; const HeaderBar = @import("headerbar.zig").HeaderBar;
const version = @import("version.zig"); const version = @import("version.zig");
const wayland = @import("wayland.zig"); const protocol = @import("protocol.zig");
const log = std.log.scoped(.gtk); const log = std.log.scoped(.gtk);
@ -56,7 +56,7 @@ toast_overlay: ?*c.GtkWidget,
/// See adwTabOverviewOpen for why we have this. /// See adwTabOverviewOpen for why we have this.
adw_tab_overview_focus_timer: ?c.guint = null, adw_tab_overview_focus_timer: ?c.guint = null,
wayland: ?wayland.SurfaceState, protocol: protocol.Surface,
pub fn create(alloc: Allocator, app: *App) !*Window { pub fn create(alloc: Allocator, app: *App) !*Window {
// Allocate a fixed pointer for our window. We try to minimize // Allocate a fixed pointer for our window. We try to minimize
@ -82,7 +82,7 @@ pub fn init(self: *Window, app: *App) !void {
.notebook = undefined, .notebook = undefined,
.context_menu = undefined, .context_menu = undefined,
.toast_overlay = undefined, .toast_overlay = undefined,
.wayland = null, .protocol = undefined,
}; };
// Create the window // Create the window
@ -396,14 +396,8 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
c.gtk_widget_add_css_class(@ptrCast(self.window), "background"); c.gtk_widget_add_css_class(@ptrCast(self.window), "background");
} }
if (self.wayland) |*wl| { // Perform protocol-specific config updates
const blurred = switch (config.@"background-blur-radius") { try self.protocol.onConfigUpdate(config);
.false => false,
.true => true,
.radius => |v| v > 0,
};
try wl.setBlur(blurred);
}
} }
/// Sets up the GTK actions for the window scope. Actions are how GTK handles /// Sets up the GTK actions for the window scope. Actions are how GTK handles
@ -443,7 +437,7 @@ fn initActions(self: *Window) void {
pub fn deinit(self: *Window) void { pub fn deinit(self: *Window) void {
c.gtk_widget_unparent(@ptrCast(self.context_menu)); c.gtk_widget_unparent(@ptrCast(self.context_menu));
if (self.wayland) |*wl| wl.deinit(); self.protocol.deinit();
if (self.adw_tab_overview_focus_timer) |timer| { if (self.adw_tab_overview_focus_timer) |timer| {
_ = c.g_source_remove(timer); _ = c.g_source_remove(timer);
@ -584,9 +578,7 @@ pub fn sendToast(self: *Window, title: [:0]const u8) void {
fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
const self = userdataSelf(ud.?); const self = userdataSelf(ud.?);
if (self.app.wayland) |*wl| { self.protocol.init(v, &self.app.protocol, &self.app.config);
self.wayland = wayland.SurfaceState.init(v, wl);
}
self.syncAppearance(&self.app.config) catch |err| { self.syncAppearance(&self.app.config) catch |err| {
log.err("failed to initialize appearance={}", .{err}); log.err("failed to initialize appearance={}", .{err});

View File

@ -2,7 +2,7 @@ const std = @import("std");
const build_options = @import("build_options"); const build_options = @import("build_options");
const input = @import("../../input.zig"); const input = @import("../../input.zig");
const c = @import("c.zig").c; const c = @import("c.zig").c;
const x11 = @import("x11.zig"); const protocol = @import("protocol.zig");
/// Returns a GTK accelerator string from a trigger. /// Returns a GTK accelerator string from a trigger.
pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 { pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 {
@ -105,34 +105,14 @@ pub fn keyvalUnicodeUnshifted(
/// This requires a lot of context because the GdkEvent /// This requires a lot of context because the GdkEvent
/// doesn't contain enough on its own. /// doesn't contain enough on its own.
pub fn eventMods( pub fn eventMods(
widget: *c.GtkWidget,
event: *c.GdkEvent, event: *c.GdkEvent,
physical_key: input.Key, physical_key: input.Key,
gtk_mods: c.GdkModifierType, gtk_mods: c.GdkModifierType,
x11_xkb: ?*x11.Xkb, app_protocol: *protocol.App,
) input.Mods { ) input.Mods {
const device = c.gdk_event_get_device(event); const device = c.gdk_event_get_device(event);
var mods = mods: { var mods = app_protocol.eventMods(device, gtk_mods);
// Add any modifier state events from Xkb if we have them (X11
// only). Null back from the Xkb call means there was no modifier
// event to read. This likely means that the key event did not
// result in a modifier change and we can safely rely on the GDK
// state.
if (comptime build_options.x11) {
const display = c.gtk_widget_get_display(widget);
if (x11_xkb) |xkb| {
if (xkb.modifier_state_from_notify(display)) |x11_mods| break :mods x11_mods;
break :mods translateMods(gtk_mods);
}
}
// On Wayland, we have to use the GDK device because the mods sent
// to this event do not have the modifier key applied if it was
// pressed (i.e. left control).
break :mods translateMods(c.gdk_device_get_modifier_state(device));
};
mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1; mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1;
switch (physical_key) { switch (physical_key) {

149
src/apprt/gtk/protocol.zig Normal file
View File

@ -0,0 +1,149 @@
const std = @import("std");
const x11 = @import("protocol/x11.zig");
const wayland = @import("protocol/wayland.zig");
const c = @import("c.zig").c;
const build_options = @import("build_options");
const input = @import("../../input.zig");
const apprt = @import("../../apprt.zig");
const Config = @import("../../config.zig").Config;
const adwaita = @import("adwaita.zig");
const builtin = @import("builtin");
const key = @import("key.zig");
const log = std.log.scoped(.gtk_platform);
pub const App = struct {
gdk_display: *c.GdkDisplay,
derived_config: DerivedConfig,
inner: union(enum) {
none,
x11: if (build_options.x11) x11.App else void,
wayland: if (build_options.wayland) wayland.App else void,
},
const DerivedConfig = struct {
app_id: [:0]const u8,
x11_program_name: [:0]const u8,
pub fn init(config: *const Config, app_id: [:0]const u8) DerivedConfig {
return .{
.app_id = app_id,
.x11_program_name = if (config.@"x11-instance-name") |pn|
pn
else if (builtin.mode == .Debug)
"ghostty-debug"
else
"ghostty",
};
}
};
pub fn init(display: ?*c.GdkDisplay, config: *const Config, app_id: [:0]const u8) !App {
var self: App = .{
.inner = .none,
.derived_config = DerivedConfig.init(config, app_id),
.gdk_display = display orelse {
// TODO: When does this ever happen...?
std.debug.panic("GDK display is null!", .{});
},
};
// The X11/Wayland init functions set `self.inner` when successful,
// so we only need to keep trying if `self.inner` stays `.none`
if (self.inner == .none and comptime build_options.wayland) try wayland.App.init(&self);
if (self.inner == .none and comptime build_options.x11) try x11.App.init(&self);
// Welp, no integration for you
if (self.inner == .none) {
log.warn(
"neither X11 nor Wayland integrations enabled - lots of features would be missing!",
.{},
);
}
return self;
}
pub fn eventMods(self: *App, device: ?*c.GdkDevice, gtk_mods: c.GdkModifierType) input.Mods {
return switch (self.inner) {
// Add any modifier state events from Xkb if we have them (X11
// only). Null back from the Xkb call means there was no modifier
// event to read. This likely means that the key event did not
// result in a modifier change and we can safely rely on the GDK
// state.
.x11 => |*x| if (comptime build_options.x11)
x.modifierStateFromNotify() orelse key.translateMods(gtk_mods)
else
unreachable,
// On Wayland, we have to use the GDK device because the mods sent
// to this event do not have the modifier key applied if it was
// pressed (i.e. left control).
.wayland, .none => key.translateMods(c.gdk_device_get_modifier_state(device)),
};
}
};
pub const Surface = struct {
app: *App,
gtk_window: *c.GtkWindow,
derived_config: DerivedConfig,
inner: union(enum) {
none,
x11: if (build_options.x11) x11.Surface else void,
wayland: if (build_options.wayland) wayland.Surface else void,
},
pub const DerivedConfig = struct {
blur: Config.BackgroundBlur,
adw_enabled: bool,
pub fn init(config: *const Config) DerivedConfig {
return .{
.blur = config.@"background-blur-radius",
.adw_enabled = adwaita.enabled(config),
};
}
};
pub fn init(self: *Surface, window: *c.GtkWindow, app: *App, config: *const Config) void {
self.* = .{
.app = app,
.derived_config = DerivedConfig.init(config),
.gtk_window = window,
.inner = .none,
};
switch (app.inner) {
.x11 => if (comptime build_options.x11) x11.Surface.init(self) else unreachable,
.wayland => if (comptime build_options.wayland) wayland.Surface.init(self) else unreachable,
.none => {},
}
}
pub fn deinit(self: Surface) void {
switch (self.inner) {
.wayland => |wl| if (comptime build_options.wayland) wl.deinit() else unreachable,
.x11, .none => {},
}
}
pub fn onConfigUpdate(self: *Surface, config: *const Config) !void {
self.derived_config = DerivedConfig.init(config);
switch (self.inner) {
.x11 => |*x| if (comptime build_options.x11) try x.onConfigUpdate() else unreachable,
.wayland => |*wl| if (comptime build_options.wayland) try wl.onConfigUpdate() else unreachable,
.none => {},
}
}
pub fn onResize(self: *Surface) !void {
switch (self.inner) {
.x11 => |*x| if (comptime build_options.x11) try x.onResize() else unreachable,
.wayland, .none => {},
}
}
};

View File

@ -1,106 +1,106 @@
const std = @import("std"); const std = @import("std");
const c = @import("c.zig").c; const c = @import("../c.zig").c;
const wayland = @import("wayland"); const wayland = @import("wayland");
const protocol = @import("../protocol.zig");
const Config = @import("../../../config.zig").Config;
const wl = wayland.client.wl; const wl = wayland.client.wl;
const org = wayland.client.org; const org = wayland.client.org;
const build_options = @import("build_options");
const log = std.log.scoped(.gtk_wayland); const log = std.log.scoped(.gtk_wayland);
/// Wayland state that contains application-wide Wayland objects (e.g. wl_display). /// Wayland state that contains application-wide Wayland objects (e.g. wl_display).
pub const AppState = struct { pub const App = struct {
display: *wl.Display, display: *wl.Display,
blur_manager: ?*org.KdeKwinBlurManager = null, blur_manager: ?*org.KdeKwinBlurManager = null,
pub fn init(display: ?*c.GdkDisplay) ?AppState { pub fn init(common: *protocol.App) !void {
if (comptime !build_options.wayland) return null;
// It should really never be null
const display_ = display orelse return null;
// Check if we're actually on Wayland // Check if we're actually on Wayland
if (c.g_type_check_instance_is_a( if (c.g_type_check_instance_is_a(
@ptrCast(@alignCast(display_)), @ptrCast(@alignCast(common.gdk_display)),
c.gdk_wayland_display_get_type(), c.gdk_wayland_display_get_type(),
) == 0) ) == 0)
return null; return;
const wl_display: *wl.Display = @ptrCast(c.gdk_wayland_display_get_wl_display(display_) orelse return null); var self: App = .{
.display = @ptrCast(c.gdk_wayland_display_get_wl_display(common.gdk_display) orelse return),
return .{
.display = wl_display,
}; };
}
pub fn register(self: *AppState) !void { log.debug("wayland platform init={}", .{self});
const registry = try self.display.getRegistry(); const registry = try self.display.getRegistry();
registry.setListener(*AppState, registryListener, self); registry.setListener(*App, registryListener, &self);
if (self.display.roundtrip() != .SUCCESS) return error.RoundtripFailed; if (self.display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
log.debug("app wayland init={}", .{self}); common.inner = .{ .wayland = self };
} }
}; };
/// Wayland state that contains Wayland objects associated with a window (e.g. wl_surface). /// Wayland state that contains Wayland objects associated with a window (e.g. wl_surface).
pub const SurfaceState = struct { pub const Surface = struct {
app_state: *AppState, common: *const protocol.Surface,
app: *App,
surface: *wl.Surface, surface: *wl.Surface,
/// A token that, when present, indicates that the window is blurred. /// A token that, when present, indicates that the window is blurred.
blur_token: ?*org.KdeKwinBlur = null, blur_token: ?*org.KdeKwinBlur = null,
pub fn init(window: *c.GtkWindow, app_state: *AppState) ?SurfaceState { pub fn init(common: *protocol.Surface) void {
if (comptime !build_options.wayland) return null; const surface = c.gtk_native_get_surface(@ptrCast(common.gtk_window)) orelse return;
const surface = c.gtk_native_get_surface(@ptrCast(window)) orelse return null;
// Check if we're actually on Wayland // Check if we're actually on Wayland
if (c.g_type_check_instance_is_a( if (c.g_type_check_instance_is_a(
@ptrCast(@alignCast(surface)), @ptrCast(@alignCast(surface)),
c.gdk_wayland_surface_get_type(), c.gdk_wayland_surface_get_type(),
) == 0) ) == 0)
return null; return;
const wl_surface: *wl.Surface = @ptrCast(c.gdk_wayland_surface_get_wl_surface(surface) orelse return null); const self: Surface = .{
.common = common,
return .{ .app = &common.app.inner.wayland,
.app_state = app_state, .surface = @ptrCast(c.gdk_wayland_surface_get_wl_surface(surface) orelse return),
.surface = wl_surface,
}; };
common.inner = .{ .wayland = self };
} }
pub fn deinit(self: *SurfaceState) void { pub fn deinit(self: Surface) void {
if (self.blur_token) |blur| blur.release(); if (self.blur_token) |blur| blur.release();
} }
pub fn setBlur(self: *SurfaceState, blurred: bool) !void { pub fn onConfigUpdate(self: *Surface) !void {
log.debug("setting blur={}", .{blurred}); try self.updateBlur();
}
const mgr = self.app_state.blur_manager orelse { fn updateBlur(self: *Surface) !void {
const blur = self.common.derived_config.blur;
log.debug("setting blur={}", .{blur});
const mgr = self.app.blur_manager orelse {
log.warn("can't set blur: org_kde_kwin_blur_manager protocol unavailable", .{}); log.warn("can't set blur: org_kde_kwin_blur_manager protocol unavailable", .{});
return; return;
}; };
if (self.blur_token) |blur| { if (self.blur_token) |tok| {
// Only release token when transitioning from blurred -> not blurred // Only release token when transitioning from blurred -> not blurred
if (!blurred) { if (!blur.enabled()) {
mgr.unset(self.surface); mgr.unset(self.surface);
blur.release(); tok.release();
self.blur_token = null; self.blur_token = null;
} }
} else { } else {
// Only acquire token when transitioning from not blurred -> blurred // Only acquire token when transitioning from not blurred -> blurred
if (blurred) { if (blur.enabled()) {
const blur_token = try mgr.create(self.surface); const tok = try mgr.create(self.surface);
blur_token.commit(); tok.commit();
self.blur_token = blur_token; self.blur_token = tok;
} }
} }
} }
}; };
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *AppState) void { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *App) void {
switch (event) { switch (event) {
.global => |global| { .global => |global| {
log.debug("got global interface={s}", .{global.interface}); log.debug("got global interface={s}", .{global.interface});

View File

@ -1,57 +1,69 @@
/// Utility functions for X11 handling. /// Utility functions for X11 handling.
const std = @import("std"); const std = @import("std");
const build_options = @import("build_options"); const build_options = @import("build_options");
const c = @import("c.zig").c; const c = @import("../c.zig").c;
const input = @import("../../input.zig"); const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config;
const protocol = @import("../protocol.zig");
const adwaita = @import("../adwaita.zig");
const log = std.log.scoped(.gtk_x11); const log = std.log.scoped(.gtk_x11);
/// Returns true if the passed in display is an X11 display. pub const App = struct {
pub fn is_display(display: ?*c.GdkDisplay) bool { common: *protocol.App,
if (comptime !build_options.x11) return false; display: *c.Display,
return c.g_type_check_instance_is_a(
@ptrCast(@alignCast(display orelse return false)),
c.gdk_x11_display_get_type(),
) != 0;
}
/// Returns true if the app is running on X11 base_event_code: c_int = 0,
pub fn is_current_display_server() bool {
if (comptime !build_options.x11) return false;
const display = c.gdk_display_get_default();
return is_display(display);
}
pub const Xkb = struct {
base_event_code: c_int,
/// Initialize an Xkb struct for the given GDK display. If the display isn't /// Initialize an Xkb struct for the given GDK display. If the display isn't
/// backed by X then this will return null. /// backed by X then this will return null.
pub fn init(display_: ?*c.GdkDisplay) !?Xkb { pub fn init(common: *protocol.App) !void {
if (comptime !build_options.x11) return null;
// Display should never be null but we just treat that as a non-X11
// display so that the caller can just ignore it and not unwrap it.
const display = display_ orelse return null;
// If the display isn't X11, then we don't need to do anything. // If the display isn't X11, then we don't need to do anything.
if (!is_display(display)) return null; if (c.g_type_check_instance_is_a(
@ptrCast(@alignCast(common.gdk_display)),
c.gdk_x11_display_get_type(),
) == 0)
return;
log.debug("Xkb.init: initializing Xkb", .{}); var self: App = .{
const xdisplay = c.gdk_x11_display_get_xdisplay(display); .common = common,
var result: Xkb = .{ .display = c.gdk_x11_display_get_xdisplay(common.gdk_display) orelse return,
.base_event_code = 0,
}; };
log.debug("X11 platform init={}", .{self});
// Set the X11 window class property (WM_CLASS) if are are on an X11
// display.
//
// Note that we also set the program name here using g_set_prgname.
// This is how the instance name field for WM_CLASS is derived when
// calling gdk_x11_display_set_program_class; there does not seem to be
// a way to set it directly. It does not look like this is being set by
// our other app initialization routines currently, but since we're
// currently deriving its value from x11-instance-name effectively, I
// feel like gating it behind an X11 check is better intent.
//
// This makes the property show up like so when using xprop:
//
// WM_CLASS(STRING) = "ghostty", "com.mitchellh.ghostty"
//
// Append "-debug" on both when using the debug build.
c.g_set_prgname(common.derived_config.x11_program_name);
c.gdk_x11_display_set_program_class(common.gdk_display, common.derived_config.app_id);
// XKB
log.debug("Xkb.init: initializing Xkb", .{});
log.debug("Xkb.init: running XkbQueryExtension", .{}); log.debug("Xkb.init: running XkbQueryExtension", .{});
var opcode: c_int = 0; var opcode: c_int = 0;
var base_error_code: c_int = 0; var base_error_code: c_int = 0;
var major = c.XkbMajorVersion; var major = c.XkbMajorVersion;
var minor = c.XkbMinorVersion; var minor = c.XkbMinorVersion;
if (c.XkbQueryExtension( if (c.XkbQueryExtension(
xdisplay, self.display,
&opcode, &opcode,
&result.base_event_code, &self.base_event_code,
&base_error_code, &base_error_code,
&major, &major,
&minor, &minor,
@ -62,7 +74,7 @@ pub const Xkb = struct {
log.debug("Xkb.init: running XkbSelectEventDetails", .{}); log.debug("Xkb.init: running XkbSelectEventDetails", .{});
if (c.XkbSelectEventDetails( if (c.XkbSelectEventDetails(
xdisplay, self.display,
c.XkbUseCoreKbd, c.XkbUseCoreKbd,
c.XkbStateNotify, c.XkbStateNotify,
c.XkbModifierStateMask, c.XkbModifierStateMask,
@ -72,7 +84,7 @@ pub const Xkb = struct {
return error.XkbInitializationError; return error.XkbInitializationError;
} }
return result; common.inner = .{ .x11 = self };
} }
/// Checks for an immediate pending XKB state update event, and returns the /// Checks for an immediate pending XKB state update event, and returns the
@ -85,18 +97,13 @@ pub const Xkb = struct {
/// Returns null if there is no event. In this case, the caller should fall /// Returns null if there is no event. In this case, the caller should fall
/// back to the standard GDK modifier state (this likely means the key /// back to the standard GDK modifier state (this likely means the key
/// event did not result in a modifier change). /// event did not result in a modifier change).
pub fn modifier_state_from_notify(self: Xkb, display_: ?*c.GdkDisplay) ?input.Mods { pub fn modifierStateFromNotify(self: App) ?input.Mods {
if (comptime !build_options.x11) return null;
const display = display_ orelse return null;
// Shoutout to Mozilla for figuring out a clean way to do this, this is // Shoutout to Mozilla for figuring out a clean way to do this, this is
// paraphrased from Firefox/Gecko in widget/gtk/nsGtkKeyUtils.cpp. // paraphrased from Firefox/Gecko in widget/gtk/nsGtkKeyUtils.cpp.
const xdisplay = c.gdk_x11_display_get_xdisplay(display); if (c.XEventsQueued(self.display, c.QueuedAfterReading) == 0) return null;
if (c.XEventsQueued(xdisplay, c.QueuedAfterReading) == 0) return null;
var nextEvent: c.XEvent = undefined; var nextEvent: c.XEvent = undefined;
_ = c.XPeekEvent(xdisplay, &nextEvent); _ = c.XPeekEvent(self.display, &nextEvent);
if (nextEvent.type != self.base_event_code) return null; if (nextEvent.type != self.base_event_code) return null;
const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent); const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent);
@ -117,3 +124,38 @@ pub const Xkb = struct {
return mods; return mods;
} }
}; };
pub const Surface = struct {
common: *protocol.Surface,
app: *App,
window: c.Window,
pub fn init(common: *protocol.Surface) void {
const surface = c.gtk_native_get_surface(@ptrCast(common.gtk_window)) orelse return;
// Check if we're actually on X11
if (c.g_type_check_instance_is_a(
@ptrCast(@alignCast(surface)),
c.gdk_x11_surface_get_type(),
) == 0)
return;
common.inner = .{ .x11 = .{
.common = common,
.app = &common.app.inner.x11,
.window = c.gdk_x11_surface_get_xid(surface),
} };
}
pub fn onConfigUpdate(self: *Surface) !void {
_ = self;
}
pub fn onResize(self: *Surface) !void {
_ = self;
}
fn updateBlur(self: *Surface) !void {
_ = self;
}
};

View File

@ -5782,6 +5782,14 @@ pub const BackgroundBlur = union(enum) {
) catch return error.InvalidValue }; ) catch return error.InvalidValue };
} }
pub fn enabled(self: BackgroundBlur) bool {
return switch (self) {
.false => false,
.true => true,
.radius => |v| v > 0,
};
}
pub fn cval(self: BackgroundBlur) u8 { pub fn cval(self: BackgroundBlur) u8 {
return switch (self) { return switch (self) {
.false => 0, .false => 0,