From 8bc257b476c8571dc6c36c854e92e3368d4ad5d3 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 9 Jan 2025 22:30:58 -0600 Subject: [PATCH] apprt/gtk: add support for _NET_WM_STATE --- src/apprt/gtk/Surface.zig | 31 +++++++++++++++++++++++++++++ src/apprt/gtk/Window.zig | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 35932ac5e..dce2517ab 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -856,6 +856,37 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void @intCast(width), @intCast(height), ); + + // TODO: move this check somewhere else so we can reuse it + // If the current WM doesn't support _NET_WM_STATE, we shouldn't try to set it. + const gdk_surface = c.gtk_native_get_surface(@ptrCast(window.window)); + if (c.g_type_check_instance_is_a(@ptrCast(@alignCast(gdk_surface)), c.gdk_x11_surface_get_type()) == 0) return; // X11 only, sorry Wayland + + if (c.XInternAtom(c.gdk_x11_display_get_xdisplay(c.gdk_surface_get_display(gdk_surface)), "_NET_WM_STATE", 1) == 0) { + log.warn("current WM does not support _NET_WM_STATE", .{}); + return; + } + + const workarea = self.getWorkarea(gdk_surface) orelse return; + if (height >= workarea.height and width >= workarea.width) { + c.gtk_window_maximize(@ptrCast(window.window)); + } +} + +fn getWorkarea(self: *const Surface, gdk_surface: ?*c.GdkSurface) ?c.GdkRectangle { + const monitor = c.gdk_display_get_monitor_at_surface( + c.gdk_display_get_default(), + gdk_surface, + ); + + var workarea: c.GdkRectangle = std.mem.zeroes(c.GdkRectangle); + if (self.app.wayland != null) { + c.gdk_monitor_get_geometry(monitor, &workarea); + } else { + c.gdk_x11_monitor_get_workarea(monitor, &workarea); + } + + return workarea; } pub fn setSizeLimits(self: *const Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void { diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 0f44cee7b..e18e85c0e 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -211,6 +211,7 @@ pub fn init(self: *Window, app: *App) !void { } _ = c.g_signal_connect_data(gtk_window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT); + _ = c.g_signal_connect_data(gtk_window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT); // If we are disabling decorations then disable them right away. if (!app.config.@"window-decoration") { @@ -613,6 +614,46 @@ fn gtkWindowNotifyDecorated( } } +/// Handle setting or removing the _NET_WM_STATE property on the window. +/// This is used to notify the window manager if the window is maximized. +/// Though, this depends on the window manager supporting _NET_WM_STATE. +fn gtkWindowNotifyMaximized( + window: *c.GtkWindow, + _: *c.GParamSpec, + _: ?*anyopaque, +) callconv(.C) void { + const gdk_surface = c.gtk_native_get_surface(@ptrCast(window)); + if (c.g_type_check_instance_is_a(@ptrCast(@alignCast(gdk_surface)), c.gdk_x11_surface_get_type()) == 0) return; // X11 only, sorry Wayland + + const xdisplay = c.gdk_x11_display_get_xdisplay(c.gdk_surface_get_display(gdk_surface)) orelse return; + + // If the WM doesn't support _NET_WM_STATE, no need to continue. + if (c.XInternAtom(xdisplay, "_NET_WM_STATE", 1) == 0) { + log.warn("current WM does not support _NET_WM_STATE", .{}); + return; + } + + // https://tronche.com/gui/x/xlib/events/client-communication/client-message.html#XClientMessageEvent + var client_message_event: c.XClientMessageEvent = std.mem.zeroes(c.XClientMessageEvent); + client_message_event.type = c.ClientMessage; + client_message_event.window = c.gdk_x11_surface_get_xid(gdk_surface); + client_message_event.message_type = c.XInternAtom(xdisplay, "_NET_WM_STATE", 0); + client_message_event.format = 32; + client_message_event.data.l[0] = c.gtk_window_is_maximized(window); + client_message_event.data.l[1] = @intCast(c.XInternAtom(xdisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0)); + + // https://tronche.com/gui/x/xlib/event-handling/XSendEvent.html + _ = c.XSendEvent( + xdisplay, + c.DefaultRootWindow(xdisplay), + 0, + c.SubstructureRedirectMask | c.SubstructureNotifyMask, + @ptrCast(&client_message_event), + ); + + _ = c.XFlush(xdisplay); +} + // Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab // sends an undefined value. fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {