mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
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:
@@ -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.
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -70,4 +70,6 @@ pub const Window = struct {
|
||||
}
|
||||
|
||||
pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {}
|
||||
|
||||
pub fn setUrgent(_: *Window, _: bool) !void {}
|
||||
};
|
||||
|
@@ -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;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user