gtk: request user attention on bell (#7482)

I'm not sure if this should be enabled by default like the tab
animation, but on KDE at least this is unintrusive enough for me to
always enable by default. Alacritty appears to agree with me as well.

Fixes #7124
This commit is contained in:
Leah Amelia Chen
2025-05-30 23:33:12 +02:00
committed by GitHub
7 changed files with 95 additions and 24 deletions

View File

@@ -2447,6 +2447,11 @@ pub fn ringBell(self: *Surface) !void {
// Need attention if we're not the currently selected tab
if (page.getSelected() == 0) page.setNeedsAttention(@intFromBool(true));
}
// Request user attention
window.winproto.setUrgent(true) catch |err| {
log.err("failed to request user attention={}", .{err});
};
}
/// Handle a stream that is in an error state.

View File

@@ -816,11 +816,15 @@ fn gtkWindowNotifyIsActive(
_: *gobject.ParamSpec,
self: *Window,
) callconv(.c) void {
if (!self.isQuickTerminal()) return;
self.winproto.setUrgent(false) catch |err| {
log.err("failed to unrequest user attention={}", .{err});
};
// Hide when we're unfocused
if (self.config.quick_terminal_autohide and self.window.as(gtk.Window).isActive() == 0) {
self.toggleVisibility();
if (self.isQuickTerminal()) {
// Hide when we're unfocused
if (self.config.quick_terminal_autohide and self.window.as(gtk.Window).isActive() == 0) {
self.toggleVisibility();
}
}
}

View File

@@ -146,4 +146,10 @@ pub const Window = union(Protocol) {
inline else => |*v| try v.addSubprocessEnv(env),
}
}
pub fn setUrgent(self: *Window, urgent: bool) !void {
switch (self.*) {
inline else => |*v| try v.setUrgent(urgent),
}
}
};

View File

@@ -70,4 +70,6 @@ pub const Window = struct {
}
pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {}
pub fn setUrgent(_: *Window, _: bool) !void {}
};

View File

@@ -16,6 +16,7 @@ const ApprtWindow = @import("../Window.zig");
const wl = wayland.client.wl;
const org = wayland.client.org;
const xdg = wayland.client.xdg;
const log = std.log.scoped(.winproto_wayland);
@@ -34,6 +35,8 @@ pub const App = struct {
kde_slide_manager: ?*org.KdeKwinSlideManager = null,
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
xdg_activation: ?*xdg.ActivationV1 = null,
};
pub fn init(
@@ -150,6 +153,15 @@ pub const App = struct {
context.kde_slide_manager = slide_manager;
return;
}
if (registryBind(
xdg.ActivationV1,
registry,
global,
)) |activation| {
context.xdg_activation = activation;
return;
}
},
// We don't handle removal events
@@ -207,15 +219,19 @@ pub const Window = struct {
app_context: *App.Context,
/// A token that, when present, indicates that the window is blurred.
blur_token: ?*org.KdeKwinBlur,
blur_token: ?*org.KdeKwinBlur = null,
/// Object that controls the decoration mode (client/server/auto)
/// of the window.
decoration: ?*org.KdeKwinServerDecoration,
decoration: ?*org.KdeKwinServerDecoration = null,
/// 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,
slide: ?*org.KdeKwinSlide = null,
/// Object that, when present, denotes that the window is currently
/// requesting attention from the user.
activation_token: ?*xdg.ActivationTokenV1 = null,
pub fn init(
alloc: Allocator,
@@ -268,9 +284,7 @@ pub const Window = struct {
.apprt_window = apprt_window,
.surface = wl_surface,
.app_context = app.context,
.blur_token = null,
.decoration = deco,
.slide = null,
};
}
@@ -315,6 +329,21 @@ pub const Window = struct {
_ = env;
}
pub fn setUrgent(self: *Window, urgent: bool) !void {
const activation = self.app_context.xdg_activation orelse return;
// If there already is a token, destroy and unset it
if (self.activation_token) |token| token.destroy();
self.activation_token = if (urgent) token: {
const token = try activation.getActivationToken();
token.setSurface(self.surface);
token.setListener(*Window, onActivationTokenEvent, self);
token.commit();
break :token token;
} else null;
}
/// Update the blur state of the window.
fn syncBlur(self: *Window) !void {
const manager = self.app_context.kde_blur_manager orelse return;
@@ -440,4 +469,26 @@ pub const Window = struct {
window.setDefaultSize(@intCast(dims.width), @intCast(dims.height));
}
fn onActivationTokenEvent(
token: *xdg.ActivationTokenV1,
event: xdg.ActivationTokenV1.Event,
self: *Window,
) void {
const activation = self.app_context.xdg_activation orelse return;
const current_token = self.activation_token orelse return;
if (token.getId() != current_token.getId()) {
log.warn("received event for unknown activation token; ignoring", .{});
return;
}
switch (event) {
.done => |done| {
activation.activate(done.token, self.surface);
token.destroy();
self.activation_token = null;
},
}
}
};

View File

@@ -176,8 +176,8 @@ pub const App = struct {
pub const Window = struct {
app: *App,
config: *const ApprtWindow.DerivedConfig,
window: xlib.Window,
gtk_window: *adw.ApplicationWindow,
x11_surface: *gdk_x11.X11Surface,
blur_region: Region = .{},
@@ -192,13 +192,6 @@ pub const Window = struct {
gtk.Native,
).getSurface() orelse return error.NotX11Surface;
// Check if we're actually on X11
if (gobject.typeCheckInstanceIsA(
surface.as(gobject.TypeInstance),
gdk_x11.X11Surface.getGObjectType(),
) == 0)
return error.NotX11Surface;
const x11_surface = gobject.ext.cast(
gdk_x11.X11Surface,
surface,
@@ -207,8 +200,8 @@ pub const Window = struct {
return .{
.app = app,
.config = &apprt_window.config,
.window = x11_surface.getXid(),
.gtk_window = apprt_window.window,
.x11_surface = x11_surface,
};
}
@@ -279,7 +272,7 @@ pub const Window = struct {
const blur = self.config.background_blur;
log.debug("set blur={}, window xid={}, region={}", .{
blur,
self.window,
self.x11_surface.getXid(),
self.blur_region,
});
@@ -335,11 +328,19 @@ pub const Window = struct {
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
var buf: [64]u8 = undefined;
const window_id = try std.fmt.bufPrint(&buf, "{}", .{self.window});
const window_id = try std.fmt.bufPrint(
&buf,
"{}",
.{self.x11_surface.getXid()},
);
try env.put("WINDOWID", window_id);
}
pub fn setUrgent(self: *Window, urgent: bool) !void {
self.x11_surface.setUrgencyHint(@intFromBool(urgent));
}
fn getWindowProperty(
self: *Window,
comptime T: type,
@@ -363,7 +364,7 @@ pub const Window = struct {
const code = c.XGetWindowProperty(
@ptrCast(@alignCast(self.app.display)),
self.window,
self.x11_surface.getXid(),
name,
options.offset,
options.length,
@@ -401,7 +402,7 @@ pub const Window = struct {
const status = c.XChangeProperty(
@ptrCast(@alignCast(self.app.display)),
self.window,
self.x11_surface.getXid(),
name,
typ,
@intFromEnum(format),
@@ -419,7 +420,7 @@ pub const Window = struct {
fn deleteProperty(self: *Window, name: c.Atom) X11Error!void {
const status = c.XDeleteProperty(
@ptrCast(@alignCast(self.app.display)),
self.window,
self.x11_surface.getXid(),
name,
);
if (status == 0) return error.RequestFailed;

View File

@@ -609,21 +609,23 @@ fn addGTK(
.wayland_protocols = wayland_protocols_dep.path(""),
});
// FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
scanner.addCustomProtocol(
plasma_wayland_protocols_dep.path("src/protocols/blur.xml"),
);
// FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
scanner.addCustomProtocol(
plasma_wayland_protocols_dep.path("src/protocols/server-decoration.xml"),
);
scanner.addCustomProtocol(
plasma_wayland_protocols_dep.path("src/protocols/slide.xml"),
);
scanner.addSystemProtocol("staging/xdg-activation/xdg-activation-v1.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);
scanner.generate("xdg_activation_v1", 1);
step.root_module.addImport("wayland", b.createModule(.{
.root_source_file = scanner.result,