From 8f7425f78c5d284bf149cc43847af2d998fa067f Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Tue, 4 Mar 2025 21:01:04 +0100 Subject: [PATCH] gtk: implement quick terminal slide animation Yet another protocol that as far as I know only KWin implements. Oh well, might as well let KDE users such as myself enjoy it OOTB --- src/apprt/gtk/Window.zig | 18 ++++------ src/apprt/gtk/winproto.zig | 6 ---- src/apprt/gtk/winproto/noop.zig | 2 -- src/apprt/gtk/winproto/wayland.zig | 56 ++++++++++++++++++++++++++++-- src/apprt/gtk/winproto/x11.zig | 2 -- src/build/SharedDeps.zig | 2 ++ 6 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 6af20118b..c77a15e03 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -377,9 +377,14 @@ pub fn present(self: *Window) void { pub fn toggleVisibility(self: *Window) void { const window: *gtk.Widget = @ptrCast(self.window); + window.setVisible(@intFromBool(window.isVisible() == 0)); } +pub fn isQuickTerminal(self: *Window) bool { + return self.app.quick_terminal == self; +} + pub fn updateConfig( self: *Window, config: *const configpkg.Config, @@ -420,7 +425,7 @@ pub fn syncAppearance(self: *Window) !void { if (!csd_enabled) break :visible false; // Never display the header bar as a quick terminal. - if (self.app.quick_terminal == self) break :visible false; + if (self.isQuickTerminal()) break :visible false; // Unconditionally disable the header bar when fullscreened. if (self.config.fullscreen) break :visible false; @@ -471,12 +476,6 @@ pub fn syncAppearance(self: *Window) !void { self.winproto.syncAppearance() catch |err| { log.warn("failed to sync winproto appearance error={}", .{err}); }; - - if (self.app.quick_terminal == self) { - self.winproto.syncQuickTerminal() catch |err| { - log.warn("failed to sync quick terminal appearance error={}", .{err}); - }; - } } fn toggleCssClass( @@ -802,7 +801,7 @@ pub fn close(self: *Window) void { const window: *gtk.Window = @ptrCast(self.window); // Unset the quick terminal on the app level - if (self.app.quick_terminal == self) self.app.quick_terminal = null; + if (self.isQuickTerminal()) self.app.quick_terminal = null; window.destroy(); } @@ -812,9 +811,6 @@ fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { log.debug("window close request", .{}); const self = userdataSelf(ud.?); - // This path should never occur, but this is here as a safety measure. - if (self.app.quick_terminal == self) return true; - // If none of our surfaces need confirmation, we can just exit. for (self.app.core_app.surfaces.items) |surface| { if (surface.container.window()) |window| { diff --git a/src/apprt/gtk/winproto.zig b/src/apprt/gtk/winproto.zig index e99a5fb2b..01587a226 100644 --- a/src/apprt/gtk/winproto.zig +++ b/src/apprt/gtk/winproto.zig @@ -133,12 +133,6 @@ pub const Window = union(Protocol) { } } - pub fn syncQuickTerminal(self: *Window) !void { - switch (self.*) { - inline else => |*v| try v.syncQuickTerminal(), - } - } - pub fn clientSideDecorationEnabled(self: Window) bool { return switch (self) { inline else => |v| v.clientSideDecorationEnabled(), diff --git a/src/apprt/gtk/winproto/noop.zig b/src/apprt/gtk/winproto/noop.zig index d030d884e..c71394e7a 100644 --- a/src/apprt/gtk/winproto/noop.zig +++ b/src/apprt/gtk/winproto/noop.zig @@ -59,8 +59,6 @@ pub const Window = struct { pub fn syncAppearance(_: *Window) !void {} - pub fn syncQuickTerminal(_: *Window) !void {} - /// This returns true if CSD is enabled for this window. This /// should be the actual present state of the window, not the /// desired state. diff --git a/src/apprt/gtk/winproto/wayland.zig b/src/apprt/gtk/winproto/wayland.zig index f9ea7c7de..e27b6bf77 100644 --- a/src/apprt/gtk/winproto/wayland.zig +++ b/src/apprt/gtk/winproto/wayland.zig @@ -27,6 +27,8 @@ pub const App = struct { // https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398 kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null, + kde_slide_manager: ?*org.KdeKwinSlideManager = null, + default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null, }; @@ -117,13 +119,26 @@ pub const App = struct { global, )) |blur_manager| { context.kde_blur_manager = blur_manager; - } else if (registryBind( + return; + } + + if (registryBind( org.KdeKwinServerDecorationManager, registry, global, )) |deco_manager| { context.kde_decoration_manager = deco_manager; deco_manager.setListener(*Context, decoManagerListener, context); + return; + } + + if (registryBind( + org.KdeKwinSlideManager, + registry, + global, + )) |slide_manager| { + context.kde_slide_manager = slide_manager; + return; } }, @@ -188,6 +203,10 @@ pub const Window = struct { /// of the window. decoration: ?*org.KdeKwinServerDecoration, + /// Object that controls the slide-in/slide-out animations of the + /// quick terminal. Always null for windows other than the quick terminal. + slide: ?*org.KdeKwinSlide, + pub fn init( alloc: Allocator, app: *App, @@ -232,6 +251,7 @@ pub const Window = struct { .app_context = app.context, .blur_token = null, .decoration = deco, + .slide = null, }; } @@ -239,6 +259,7 @@ pub const Window = struct { _ = alloc; if (self.blur_token) |blur| blur.release(); if (self.decoration) |deco| deco.release(); + if (self.slide) |slide| slide.release(); } pub fn resizeEvent(_: *Window) !void {} @@ -250,6 +271,12 @@ pub const Window = struct { self.syncDecoration() catch |err| { log.err("failed to sync blur={}", .{err}); }; + + if (self.apprt_window.isQuickTerminal()) { + self.syncQuickTerminal() catch |err| { + log.warn("failed to sync quick terminal appearance={}", .{err}); + }; + } } pub fn clientSideDecorationEnabled(self: Window) bool { @@ -308,7 +335,7 @@ pub const Window = struct { }; } - pub fn syncQuickTerminal(self: *Window) !void { + fn syncQuickTerminal(self: *Window) !void { if (comptime !build_options.layer_shell) return; const window = self.apprt_window.window; @@ -339,6 +366,22 @@ pub const Window = struct { .top, .bottom, .center => c.gtk_window_set_default_size(window, 800, 400), .left, .right => c.gtk_window_set_default_size(window, 400, 800), } + + if (self.apprt_window.isQuickTerminal()) { + if (self.slide) |slide| slide.release(); + + self.slide = if (anchored_edge) |anchored| slide: { + const mgr = self.app_context.kde_slide_manager orelse break :slide null; + + const slide = mgr.create(self.surface) catch |err| { + log.warn("could not create slide object={}", .{err}); + break :slide null; + }; + slide.setLocation(@intCast(@intFromEnum(anchored.toKdeSlideLocation()))); + slide.commit(); + break :slide slide; + } else null; + } } }; @@ -347,4 +390,13 @@ const LayerShellEdge = enum(c_uint) { right = c.GTK_LAYER_SHELL_EDGE_RIGHT, top = c.GTK_LAYER_SHELL_EDGE_TOP, bottom = c.GTK_LAYER_SHELL_EDGE_BOTTOM, + + fn toKdeSlideLocation(self: LayerShellEdge) org.KdeKwinSlide.Location { + return switch (self) { + .left => .left, + .top => .top, + .right => .right, + .bottom => .bottom, + }; + } }; diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index 6d20e6e8b..c8f5385dd 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -228,8 +228,6 @@ pub const Window = struct { }; } - pub fn syncQuickTerminal(_: *Window) !void {} - pub fn clientSideDecorationEnabled(self: Window) bool { return switch (self.config.window_decoration) { .auto, .client => true, diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 26c4d84c5..7b2d10c63 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -474,10 +474,12 @@ pub fn add( // FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398 scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml")); scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/server-decoration.xml")); + scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/slide.xml")); scanner.generate("wl_compositor", 1); scanner.generate("org_kde_kwin_blur_manager", 1); scanner.generate("org_kde_kwin_server_decoration_manager", 1); + scanner.generate("org_kde_kwin_slide_manager", 1); step.root_module.addImport("wayland", wayland); step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));