mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
gtk: various blur-related fixes (#10727)
This commit is contained in:
@@ -91,8 +91,8 @@
|
||||
.lazy = true,
|
||||
},
|
||||
.wayland_protocols = .{
|
||||
.url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
|
||||
.hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
|
||||
.url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
|
||||
.hash = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
|
||||
.lazy = true,
|
||||
},
|
||||
.plasma_wayland_protocols = .{
|
||||
|
||||
5
build.zig.zon.json
generated
5
build.zig.zon.json
generated
@@ -139,6 +139,11 @@
|
||||
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
|
||||
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
|
||||
},
|
||||
"N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": {
|
||||
"name": "wayland_protocols",
|
||||
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
|
||||
"hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="
|
||||
},
|
||||
"N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": {
|
||||
"name": "wuffs",
|
||||
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
|
||||
8
build.zig.zon.nix
generated
8
build.zig.zon.nix
generated
@@ -306,6 +306,14 @@ in
|
||||
hash = "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA";
|
||||
path = fetchZigArtifact {
|
||||
name = "wayland_protocols";
|
||||
url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz";
|
||||
hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs";
|
||||
path = fetchZigArtifact {
|
||||
|
||||
1
build.zig.zon.txt
generated
1
build.zig.zon.txt
generated
@@ -34,3 +34,4 @@ https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
|
||||
https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
|
||||
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz
|
||||
|
||||
@@ -167,6 +167,12 @@
|
||||
"dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
|
||||
"sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
|
||||
"sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
|
||||
@@ -1071,21 +1071,6 @@ pub const Window = extern struct {
|
||||
self.syncAppearance();
|
||||
}
|
||||
|
||||
fn propGdkSurfaceHeight(
|
||||
_: *gdk.Surface,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// X11 needs to fix blurring on resize, but winproto implementations
|
||||
// could do anything.
|
||||
self.private().winproto.resizeEvent() catch |err| {
|
||||
log.warn(
|
||||
"winproto resize event failed error={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn propIsActive(
|
||||
_: *gtk.Window,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -1111,7 +1096,7 @@ pub const Window = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn propGdkSurfaceWidth(
|
||||
fn propGdkSurfaceDims(
|
||||
_: *gdk.Surface,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
@@ -1250,7 +1235,7 @@ pub const Window = extern struct {
|
||||
fn finalize(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
priv.tab_bindings.unref();
|
||||
priv.winproto.deinit(Application.default().allocator());
|
||||
priv.winproto.deinit();
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
@@ -1282,14 +1267,14 @@ pub const Window = extern struct {
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
gdk_surface,
|
||||
*Self,
|
||||
propGdkSurfaceWidth,
|
||||
propGdkSurfaceDims,
|
||||
self,
|
||||
.{ .detail = "width" },
|
||||
);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
gdk_surface,
|
||||
*Self,
|
||||
propGdkSurfaceHeight,
|
||||
propGdkSurfaceDims,
|
||||
self,
|
||||
.{ .detail = "height" },
|
||||
);
|
||||
|
||||
@@ -117,9 +117,9 @@ pub const Window = union(Protocol) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Window, alloc: Allocator) void {
|
||||
pub fn deinit(self: *Window) void {
|
||||
switch (self.*) {
|
||||
inline else => |*v| v.deinit(alloc),
|
||||
inline else => |*v| v.deinit(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
187
src/apprt/gtk/winproto/BlurRegion.zig
Normal file
187
src/apprt/gtk/winproto/BlurRegion.zig
Normal file
@@ -0,0 +1,187 @@
|
||||
const BlurRegion = @This();
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gobject = @import("gobject");
|
||||
const gdk = @import("gdk");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const Window = @import("../winproto.zig").Window;
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
|
||||
slices: std.ArrayList(Slice),
|
||||
|
||||
/// A rectangular slice of the blur region.
|
||||
// Marked `extern` since we want to be able to use this in X11 directly.
|
||||
pub const Slice = extern struct {
|
||||
x: Pos,
|
||||
y: Pos,
|
||||
width: Pos,
|
||||
height: Pos,
|
||||
};
|
||||
|
||||
// X11 compatibility. Ideally this should just be an `i32` like Wayland,
|
||||
// but XLib sucks
|
||||
const Pos = c_long;
|
||||
|
||||
pub const empty: BlurRegion = .{
|
||||
.slices = .empty,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *BlurRegion, alloc: Allocator) void {
|
||||
self.slices.deinit(alloc);
|
||||
self.slices = .empty;
|
||||
}
|
||||
|
||||
// Calculate the blur regions for a window.
|
||||
//
|
||||
// Since we have rounded corners by default, we need to carve out the
|
||||
// pixels on each corner to avoid the "korners bug".
|
||||
// (cf. https://github.com/cutefishos/fishui/blob/41d4ba194063a3c7fff4675619b57e6ac0504f06/src/platforms/linux/blurhelper/windowblur.cpp#L134)
|
||||
pub fn calcForWindow(
|
||||
alloc: Allocator,
|
||||
window: *ApprtWindow,
|
||||
csd: bool,
|
||||
to_device_coordinates: bool,
|
||||
) Allocator.Error!BlurRegion {
|
||||
const native = window.as(gtk.Native);
|
||||
const surface = native.getSurface() orelse return .empty;
|
||||
|
||||
var slices: std.ArrayList(Slice) = .empty;
|
||||
errdefer slices.deinit(alloc);
|
||||
|
||||
// Calculate the primary blur region
|
||||
// (the one that covers most of the screen).
|
||||
// It's easier to do this inside a vector since we have to scale
|
||||
// everything by the scale factor anyways.
|
||||
|
||||
// NOTE(pluiedev): CSDs are a f--king mistake.
|
||||
// Please, GNOME, stop this nonsense of making a window ~30% bigger
|
||||
// internally than how they really are just for your shadows and
|
||||
// rounded corners and all that fluff. Please. I beg of you.
|
||||
const x: Pos, const y: Pos = off: {
|
||||
var x: f64 = 0;
|
||||
var y: f64 = 0;
|
||||
native.getSurfaceTransform(&x, &y);
|
||||
// Slightly inset the corners if we're using CSDs
|
||||
if (csd) {
|
||||
x += 1;
|
||||
y += 1;
|
||||
}
|
||||
break :off .{ @intFromFloat(x), @intFromFloat(y) };
|
||||
};
|
||||
|
||||
var width = @as(Pos, surface.getWidth());
|
||||
var height = @as(Pos, surface.getHeight());
|
||||
|
||||
// Trim off the offsets. Be careful not to get negative.
|
||||
width -= x * 2;
|
||||
height -= y * 2;
|
||||
if (width <= 0 or height <= 0) return .empty;
|
||||
|
||||
// Empirically determined.
|
||||
const are_corners_rounded = rounded: {
|
||||
// This cast should always succeed as all of our windows
|
||||
// should be toplevel. If this fails, something very strange
|
||||
// is going on.
|
||||
const toplevel = gobject.ext.cast(
|
||||
gdk.Toplevel,
|
||||
surface,
|
||||
) orelse break :rounded false;
|
||||
|
||||
const state = toplevel.getState();
|
||||
if (state.fullscreen or state.maximized or state.tiled)
|
||||
break :rounded false;
|
||||
|
||||
break :rounded csd;
|
||||
};
|
||||
|
||||
const new_slices = try approxRoundedRect(
|
||||
alloc,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
// See https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/css-variables.html#window-radius
|
||||
if (are_corners_rounded) 15 else 0,
|
||||
);
|
||||
|
||||
if (to_device_coordinates) {
|
||||
// Transform surface coordinates to device coordinates.
|
||||
const sf = surface.getScaleFactor();
|
||||
for (new_slices.items) |*s| {
|
||||
s.x *= sf;
|
||||
s.y *= sf;
|
||||
s.width *= sf;
|
||||
s.height *= sf;
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .slices = new_slices };
|
||||
}
|
||||
|
||||
/// Whether two sets of blur regions are equal.
|
||||
pub fn eql(self: BlurRegion, other: BlurRegion) bool {
|
||||
if (self.slices.items.len != other.slices.items.len) return false;
|
||||
for (self.slices.items, other.slices.items) |this, that| {
|
||||
if (!std.meta.eql(this, that)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Approximate a rounded rectangle with many smaller rectangles.
|
||||
fn approxRoundedRect(
|
||||
alloc: Allocator,
|
||||
x: Pos,
|
||||
y: Pos,
|
||||
width: Pos,
|
||||
height: Pos,
|
||||
radius: Pos,
|
||||
) Allocator.Error!std.ArrayList(Slice) {
|
||||
const r_f: f32 = @floatFromInt(radius);
|
||||
|
||||
var slices: std.ArrayList(Slice) = .empty;
|
||||
errdefer slices.deinit(alloc);
|
||||
|
||||
// Add the central rectangle
|
||||
try slices.append(alloc, .{
|
||||
.x = x,
|
||||
.y = y + radius,
|
||||
.width = width,
|
||||
.height = height - 2 * radius,
|
||||
});
|
||||
|
||||
// Add the corner rows. This is honestly quite cursed.
|
||||
var row: Pos = 0;
|
||||
while (row < radius) : (row += 1) {
|
||||
// y distance from this row to the center corner circle
|
||||
const dy = @as(f32, @floatFromInt(radius - row)) - 0.5;
|
||||
|
||||
// x distance - as given by the definition of a circle
|
||||
const dx = @sqrt(r_f * r_f - dy * dy);
|
||||
|
||||
// How much each row should be offset, rounded to an integer
|
||||
const row_x: Pos = @intFromFloat(r_f - @round(dx + 0.5));
|
||||
|
||||
// Remove the offset from both ends
|
||||
const row_w = width - 2 * row_x;
|
||||
|
||||
// Top slice
|
||||
try slices.append(alloc, .{
|
||||
.x = x + row_x,
|
||||
.y = y + row,
|
||||
.width = row_w,
|
||||
.height = 1,
|
||||
});
|
||||
|
||||
// Bottom slice
|
||||
try slices.append(alloc, .{
|
||||
.x = x + row_x,
|
||||
.y = y + height - 1 - row,
|
||||
.width = row_w,
|
||||
.height = 1,
|
||||
});
|
||||
}
|
||||
|
||||
return slices;
|
||||
}
|
||||
@@ -46,9 +46,8 @@ pub const Window = struct {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Window, alloc: Allocator) void {
|
||||
pub fn deinit(self: *Window) void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
}
|
||||
|
||||
pub fn updateConfigEvent(
|
||||
|
||||
@@ -10,6 +10,7 @@ const layer_shell = @import("gtk4-layer-shell");
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const ext = wayland.client.ext;
|
||||
const kde = wayland.client.kde;
|
||||
const org = wayland.client.org;
|
||||
const xdg = wayland.client.xdg;
|
||||
@@ -18,6 +19,7 @@ const Config = @import("../../../config.zig").Config;
|
||||
const Globals = @import("wayland/Globals.zig");
|
||||
const input = @import("../../../input.zig");
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
const BlurRegion = @import("BlurRegion.zig");
|
||||
|
||||
const log = std.log.scoped(.winproto_wayland);
|
||||
|
||||
@@ -96,8 +98,8 @@ pub const Window = struct {
|
||||
/// The context from the app where we can load our Wayland interfaces.
|
||||
globals: *Globals,
|
||||
|
||||
/// A token that, when present, indicates that the window is blurred.
|
||||
blur_token: ?*org.KdeKwinBlur = null,
|
||||
/// Object that controls background effects like background blur.
|
||||
bg_effect: ?*ext.BackgroundEffectSurfaceV1 = null,
|
||||
|
||||
/// Object that controls the decoration mode (client/server/auto)
|
||||
/// of the window.
|
||||
@@ -111,6 +113,8 @@ pub const Window = struct {
|
||||
/// requesting attention from the user.
|
||||
activation_token: ?*xdg.ActivationTokenV1 = null,
|
||||
|
||||
blur_region: BlurRegion = .empty,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
app: *App,
|
||||
@@ -148,6 +152,20 @@ pub const Window = struct {
|
||||
break :deco deco;
|
||||
};
|
||||
|
||||
const bg_effect: ?*ext.BackgroundEffectSurfaceV1 = bg: {
|
||||
const mgr = app.globals.get(.ext_background_effect) orelse
|
||||
break :bg null;
|
||||
|
||||
const bg_effect: *ext.BackgroundEffectSurfaceV1 = mgr.getBackgroundEffect(
|
||||
wl_surface,
|
||||
) catch |err| {
|
||||
log.warn("could not create background effect object={}", .{err});
|
||||
break :bg null;
|
||||
};
|
||||
|
||||
break :bg bg_effect;
|
||||
};
|
||||
|
||||
if (apprt_window.isQuickTerminal()) {
|
||||
_ = gdk.Surface.signals.enter_monitor.connect(
|
||||
gdk_surface,
|
||||
@@ -163,24 +181,29 @@ pub const Window = struct {
|
||||
.surface = wl_surface,
|
||||
.globals = app.globals,
|
||||
.decoration = deco,
|
||||
.bg_effect = bg_effect,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Window, alloc: Allocator) void {
|
||||
_ = alloc;
|
||||
if (self.blur_token) |blur| blur.release();
|
||||
pub fn deinit(self: *Window) void {
|
||||
self.blur_region.deinit(self.globals.alloc);
|
||||
if (self.bg_effect) |bg| bg.destroy();
|
||||
if (self.decoration) |deco| deco.release();
|
||||
if (self.slide) |slide| slide.release();
|
||||
}
|
||||
|
||||
pub fn resizeEvent(_: *Window) !void {}
|
||||
pub fn resizeEvent(self: *Window) !void {
|
||||
self.syncBlur() catch |err| {
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn syncAppearance(self: *Window) !void {
|
||||
self.syncBlur() catch |err| {
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
};
|
||||
self.syncDecoration() catch |err| {
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
log.err("failed to sync decoration={}", .{err});
|
||||
};
|
||||
|
||||
if (self.apprt_window.isQuickTerminal()) {
|
||||
@@ -224,28 +247,47 @@ pub const Window = struct {
|
||||
|
||||
/// Update the blur state of the window.
|
||||
fn syncBlur(self: *Window) !void {
|
||||
const manager = self.globals.get(.kde_blur_manager) orelse return;
|
||||
const compositor = self.globals.get(.compositor) orelse return;
|
||||
const bg = self.bg_effect orelse return;
|
||||
if (!self.globals.state.bg_effect_capabilities.blur) return;
|
||||
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return;
|
||||
const blur = config.@"background-blur";
|
||||
|
||||
if (self.blur_token) |tok| {
|
||||
// Only release token when transitioning from blurred -> not blurred
|
||||
if (!blur.enabled()) {
|
||||
manager.unset(self.surface);
|
||||
tok.release();
|
||||
self.blur_token = null;
|
||||
}
|
||||
} else {
|
||||
// Only acquire token when transitioning from not blurred -> blurred
|
||||
if (blur.enabled()) {
|
||||
const tok = try manager.create(self.surface);
|
||||
tok.commit();
|
||||
self.blur_token = tok;
|
||||
}
|
||||
if (!blur.enabled()) {
|
||||
self.blur_region.deinit(self.globals.alloc);
|
||||
bg.setBlurRegion(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var region: BlurRegion = try .calcForWindow(
|
||||
self.globals.alloc,
|
||||
self.apprt_window,
|
||||
self.clientSideDecorationEnabled(),
|
||||
false,
|
||||
);
|
||||
errdefer region.deinit(self.globals.alloc);
|
||||
|
||||
if (region.eql(self.blur_region)) {
|
||||
// Region didn't change. Don't do anything.
|
||||
region.deinit(self.globals.alloc);
|
||||
return;
|
||||
}
|
||||
|
||||
const wl_region = try compositor.createRegion();
|
||||
errdefer if (wl_region) |r| r.destroy();
|
||||
for (region.slices.items) |s| wl_region.add(
|
||||
@intCast(s.x),
|
||||
@intCast(s.y),
|
||||
@intCast(s.width),
|
||||
@intCast(s.height),
|
||||
);
|
||||
|
||||
bg.setBlurRegion(wl_region);
|
||||
self.blur_region = region;
|
||||
}
|
||||
|
||||
fn syncDecoration(self: *Window) !void {
|
||||
|
||||
@@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const ext = wayland.client.ext;
|
||||
const kde = wayland.client.kde;
|
||||
const org = wayland.client.org;
|
||||
const xdg = wayland.client.xdg;
|
||||
@@ -26,7 +27,8 @@ const Binding = struct {
|
||||
};
|
||||
|
||||
pub const Tag = enum {
|
||||
kde_blur_manager,
|
||||
compositor,
|
||||
ext_background_effect,
|
||||
kde_decoration_manager,
|
||||
kde_slide_manager,
|
||||
kde_output_order,
|
||||
@@ -34,7 +36,8 @@ pub const Tag = enum {
|
||||
|
||||
fn Type(comptime self: Tag) type {
|
||||
return switch (self) {
|
||||
.kde_blur_manager => org.KdeKwinBlurManager,
|
||||
.compositor => wl.Compositor,
|
||||
.ext_background_effect => ext.BackgroundEffectManagerV1,
|
||||
.kde_decoration_manager => org.KdeKwinServerDecorationManager,
|
||||
.kde_slide_manager => org.KdeKwinSlideManager,
|
||||
.kde_output_order => kde.OutputOrderV1,
|
||||
@@ -56,6 +59,8 @@ pub const State = struct {
|
||||
|
||||
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
||||
|
||||
bg_effect_capabilities: ext.BackgroundEffectManagerV1.Capability = .{},
|
||||
|
||||
/// Reset cached state derived from kde_output_order_v1.
|
||||
fn resetOutputOrder(self: *State, alloc: Allocator) void {
|
||||
if (self.primary_output_name) |name| alloc.free(name);
|
||||
@@ -102,6 +107,11 @@ fn onGlobalAttached(self: *Globals, comptime tag: Tag) void {
|
||||
// keeps listener setup and object lifetime in one
|
||||
// place and also supports globals that appear later.
|
||||
switch (tag) {
|
||||
.ext_background_effect => {
|
||||
const v = self.get(tag) orelse return;
|
||||
v.setListener(*Globals, bgEffectListener, self);
|
||||
self.needs_roundtrip = true;
|
||||
},
|
||||
.kde_decoration_manager => {
|
||||
const v = self.get(tag) orelse return;
|
||||
v.setListener(*Globals, decoManagerListener, self);
|
||||
@@ -179,6 +189,18 @@ fn registryListener(
|
||||
}
|
||||
}
|
||||
|
||||
fn bgEffectListener(
|
||||
_: *ext.BackgroundEffectManagerV1,
|
||||
event: ext.BackgroundEffectManagerV1.Event,
|
||||
self: *Globals,
|
||||
) void {
|
||||
switch (event) {
|
||||
.capabilities => |cap| {
|
||||
self.state.bg_effect_capabilities = cap.flags;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn decoManagerListener(
|
||||
_: *org.KdeKwinServerDecorationManager,
|
||||
event: org.KdeKwinServerDecorationManager.Event,
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const c = @cImport({
|
||||
const input = @import("../../../input.zig");
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
const BlurRegion = @import("BlurRegion.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_x11);
|
||||
|
||||
@@ -169,13 +170,13 @@ pub const Window = struct {
|
||||
app: *App,
|
||||
apprt_window: *ApprtWindow,
|
||||
x11_surface: *gdk_x11.X11Surface,
|
||||
alloc: Allocator,
|
||||
|
||||
blur_region: Region = .{},
|
||||
blur_region: BlurRegion = .empty,
|
||||
|
||||
// Cache last applied values to avoid redundant X11 property updates.
|
||||
// Redundant property updates seem to cause some visual glitches
|
||||
// with some window managers: https://github.com/ghostty-org/ghostty/pull/8075
|
||||
last_applied_blur_region: ?Region = null,
|
||||
last_applied_decoration_hints: ?MotifWMHints = null,
|
||||
|
||||
pub fn init(
|
||||
@@ -183,8 +184,6 @@ pub const Window = struct {
|
||||
app: *App,
|
||||
apprt_window: *ApprtWindow,
|
||||
) !Window {
|
||||
_ = alloc;
|
||||
|
||||
const surface = apprt_window.as(gtk.Native).getSurface() orelse
|
||||
return error.NotX11Surface;
|
||||
|
||||
@@ -195,49 +194,31 @@ pub const Window = struct {
|
||||
|
||||
return .{
|
||||
.app = app,
|
||||
.alloc = alloc,
|
||||
.apprt_window = apprt_window,
|
||||
.x11_surface = x11_surface,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Window, alloc: Allocator) void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
pub fn deinit(self: *Window) void {
|
||||
self.blur_region.deinit(self.alloc);
|
||||
}
|
||||
|
||||
pub fn resizeEvent(self: *Window) !void {
|
||||
// The blur region must update with window resizes
|
||||
try self.syncBlur();
|
||||
self.syncBlur() catch |err| {
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn syncAppearance(self: *Window) !void {
|
||||
// The user could have toggled between CSDs and SSDs,
|
||||
// therefore we need to recalculate the blur region offset.
|
||||
self.blur_region = blur: {
|
||||
// NOTE(pluiedev): CSDs are a f--king mistake.
|
||||
// Please, GNOME, stop this nonsense of making a window ~30% bigger
|
||||
// internally than how they really are just for your shadows and
|
||||
// rounded corners and all that fluff. Please. I beg of you.
|
||||
var x: f64 = 0;
|
||||
var y: f64 = 0;
|
||||
|
||||
self.apprt_window.as(gtk.Native).getSurfaceTransform(&x, &y);
|
||||
|
||||
// Transform surface coordinates to device coordinates.
|
||||
const scale: f64 = @floatFromInt(self.apprt_window.as(gtk.Widget).getScaleFactor());
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
|
||||
break :blur .{
|
||||
.x = @intFromFloat(x),
|
||||
.y = @intFromFloat(y),
|
||||
};
|
||||
};
|
||||
self.syncBlur() catch |err| {
|
||||
log.err("failed to synchronize blur={}", .{err});
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
};
|
||||
self.syncDecorations() catch |err| {
|
||||
log.err("failed to synchronize decorations={}", .{err});
|
||||
log.err("failed to sync decorations={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,53 +230,49 @@ pub const Window = struct {
|
||||
}
|
||||
|
||||
fn syncBlur(self: *Window) !void {
|
||||
// FIXME: This doesn't currently factor in rounded corners on Adwaita,
|
||||
// which means that the blur region will grow slightly outside of the
|
||||
// window borders. Unfortunately, actually calculating the rounded
|
||||
// region can be quite complex without having access to existing APIs
|
||||
// (cf. https://github.com/cutefishos/fishui/blob/41d4ba194063a3c7fff4675619b57e6ac0504f06/src/platforms/linux/blurhelper/windowblur.cpp#L134)
|
||||
// and I think it's not really noticeable enough to justify the effort.
|
||||
// (Wayland also has this visual artifact anyway...)
|
||||
|
||||
const gtk_widget = self.apprt_window.as(gtk.Widget);
|
||||
const config = if (self.apprt_window.getConfig()) |v| v.get() else return;
|
||||
|
||||
// When blur is disabled, remove the property if it was previously set
|
||||
const blur = config.@"background-blur";
|
||||
if (!blur.enabled()) {
|
||||
if (self.last_applied_blur_region != null) {
|
||||
try self.deleteProperty(self.app.atoms.kde_blur);
|
||||
self.last_applied_blur_region = null;
|
||||
}
|
||||
|
||||
var region: BlurRegion = if (blur.enabled())
|
||||
try .calcForWindow(
|
||||
self.alloc,
|
||||
self.apprt_window,
|
||||
self.clientSideDecorationEnabled(),
|
||||
true,
|
||||
)
|
||||
else
|
||||
.empty;
|
||||
errdefer region.deinit(self.alloc);
|
||||
|
||||
// Only update X11 properties when the blur region actually changes
|
||||
if (region.eql(self.blur_region)) {
|
||||
region.deinit(self.alloc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transform surface coordinates to device coordinates.
|
||||
const scale = gtk_widget.getScaleFactor();
|
||||
self.blur_region.width = gtk_widget.getWidth() * scale;
|
||||
self.blur_region.height = gtk_widget.getHeight() * scale;
|
||||
if (region.slices.items.len > 0) {
|
||||
log.debug("set blur={}, window xid={}, region={}", .{
|
||||
blur,
|
||||
self.x11_surface.getXid(),
|
||||
region,
|
||||
});
|
||||
|
||||
// Only update X11 properties when the blur region actually changes
|
||||
if (self.last_applied_blur_region) |last| {
|
||||
if (std.meta.eql(self.blur_region, last)) return;
|
||||
try self.changeProperty(
|
||||
BlurRegion.Slice,
|
||||
self.app.atoms.kde_blur,
|
||||
c.XA_CARDINAL,
|
||||
._32,
|
||||
.{ .mode = .replace },
|
||||
region.slices.items,
|
||||
);
|
||||
} else {
|
||||
try self.deleteProperty(self.app.atoms.kde_blur);
|
||||
}
|
||||
|
||||
log.debug("set blur={}, window xid={}, region={}", .{
|
||||
blur,
|
||||
self.x11_surface.getXid(),
|
||||
self.blur_region,
|
||||
});
|
||||
|
||||
try self.changeProperty(
|
||||
Region,
|
||||
self.app.atoms.kde_blur,
|
||||
c.XA_CARDINAL,
|
||||
._32,
|
||||
.{ .mode = .replace },
|
||||
&self.blur_region,
|
||||
);
|
||||
self.last_applied_blur_region = self.blur_region;
|
||||
self.blur_region.deinit(self.alloc);
|
||||
self.blur_region = region;
|
||||
}
|
||||
|
||||
fn syncDecorations(self: *Window) !void {
|
||||
@@ -335,7 +312,7 @@ pub const Window = struct {
|
||||
self.app.atoms.motif_wm_hints,
|
||||
._32,
|
||||
.{ .mode = .replace },
|
||||
&hints,
|
||||
&.{hints},
|
||||
);
|
||||
self.last_applied_decoration_hints = hints;
|
||||
}
|
||||
@@ -410,9 +387,11 @@ pub const Window = struct {
|
||||
options: struct {
|
||||
mode: PropertyChangeMode,
|
||||
},
|
||||
value: *T,
|
||||
values: []const T,
|
||||
) X11Error!void {
|
||||
const data: format.bufferType() = @ptrCast(value);
|
||||
const data: format.bufferType() = @ptrCast(@constCast(values));
|
||||
// The number of "words" that each element `T` occupies.
|
||||
const words_per_elem = @divExact(@sizeOf(T), @sizeOf(format.elemType()));
|
||||
|
||||
const status = c.XChangeProperty(
|
||||
@ptrCast(@alignCast(self.app.display)),
|
||||
@@ -422,7 +401,7 @@ pub const Window = struct {
|
||||
@intFromEnum(format),
|
||||
@intFromEnum(options.mode),
|
||||
data,
|
||||
@divExact(@sizeOf(T), @sizeOf(format.elemType())),
|
||||
@intCast(words_per_elem * values.len),
|
||||
);
|
||||
|
||||
// For some godforsaken reason Xlib alternates between
|
||||
@@ -498,13 +477,6 @@ const PropertyFormat = enum(c_int) {
|
||||
}
|
||||
};
|
||||
|
||||
const Region = extern struct {
|
||||
x: c_long = 0,
|
||||
y: c_long = 0,
|
||||
width: c_long = 0,
|
||||
height: c_long = 0,
|
||||
};
|
||||
|
||||
// See Xm/MwmUtil.h, packaged with the Motif Window Manager
|
||||
const MotifWMHints = extern struct {
|
||||
flags: packed struct(c_ulong) {
|
||||
|
||||
@@ -626,9 +626,6 @@ fn addGtkNg(
|
||||
.wayland_protocols = wayland_protocols_dep.path(""),
|
||||
});
|
||||
|
||||
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"),
|
||||
@@ -640,13 +637,14 @@ fn addGtkNg(
|
||||
plasma_wayland_protocols_dep.path("src/protocols/kde-output-order-v1.xml"),
|
||||
);
|
||||
scanner.addSystemProtocol("staging/xdg-activation/xdg-activation-v1.xml");
|
||||
scanner.addSystemProtocol("staging/ext-background-effect/ext-background-effect-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("kde_output_order_v1", 1);
|
||||
scanner.generate("xdg_activation_v1", 1);
|
||||
scanner.generate("ext_background_effect_manager_v1", 1);
|
||||
|
||||
step.root_module.addImport("wayland", b.createModule(.{
|
||||
.root_source_file = scanner.result,
|
||||
|
||||
Reference in New Issue
Block a user