diff --git a/blur-protocol/meson.build b/blur-protocol/meson.build
new file mode 100644
index 000000000..5bd29c288
--- /dev/null
+++ b/blur-protocol/meson.build
@@ -0,0 +1,41 @@
+dep_scanner = dependency('wayland-scanner', native: true)
+prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner'))
+
+blur_protocol_file = files('pantheon-blur-v1.xml')
+
+pantheon_blur_sources = []
+pantheon_blur_sources += custom_target(
+ 'pantheon-blur-client-protocol.h',
+ command: [ prog_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ],
+ input: blur_protocol_file,
+ output: 'pantheon-blur-client-protocol.h',
+)
+
+output_type = 'private-code'
+if dep_scanner.version().version_compare('< 1.14.91')
+ output_type = 'code'
+endif
+pantheon_blur_sources += custom_target(
+ 'pantheon-blur-protocol.c',
+ command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ],
+ input: blur_protocol_file,
+ output: 'pantheon-blur-protocol.c',
+)
+
+pantheon_blur_lib = static_library(
+ 'pantheon-blur',
+ pantheon_blur_sources,
+ dependencies: [
+ wayland_client_dep
+ ],
+)
+
+pantheon_blur_dep = declare_dependency(
+ link_with: pantheon_blur_lib,
+ dependencies: [
+ meson.get_compiler('vala').find_library('pantheon-blur', dirs: meson.current_source_dir()),
+ wayland_client_dep,
+ ],
+ include_directories: include_directories('.'),
+ sources: pantheon_blur_sources
+)
diff --git a/blur-protocol/pantheon-blur-v1.xml b/blur-protocol/pantheon-blur-v1.xml
new file mode 100644
index 000000000..e833337c9
--- /dev/null
+++ b/blur-protocol/pantheon-blur-v1.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ SPDX-License-Identifier: LGPL-2.1-or-later
+ ]]>
+
+
+
+ This protocol provides a way for Pantheon applications to ask for background blur.
+
+
+
+
+ Get blur object for the given wl_surface to blur background behind it.
+ If the given wl_surface already has a io_elementary_pantheon_blur_v1 object associated,
+ an error will be raised.
+
+
+
+
+
+
+
+
+ This interface provides a way for Pantheon applications to specify region of the window to be blurred.
+ By default the region to blur is considered to be null.
+
+
+
+
+
+
+ Tell the window manager to crop blur to a given region.
+ The coordinates must be relative to the associated surface.
+
+
+
+
+
+
+
+
+
+
diff --git a/blur-protocol/pantheon-blur.deps b/blur-protocol/pantheon-blur.deps
new file mode 100644
index 000000000..62acb1e0a
--- /dev/null
+++ b/blur-protocol/pantheon-blur.deps
@@ -0,0 +1 @@
+wayland-server
diff --git a/blur-protocol/pantheon-blur.vapi b/blur-protocol/pantheon-blur.vapi
new file mode 100644
index 000000000..53efb9d34
--- /dev/null
+++ b/blur-protocol/pantheon-blur.vapi
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 elementary, Inc.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+namespace PantheonBlur {
+ [CCode (cheader_filename = "pantheon-blur-client-protocol.h", cname = "struct io_elementary_pantheon_blur_manager_v1", cprefix = "io_elementary_pantheon_blur_manager_v1_")]
+ public class BlurManager : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-blur-client-protocol.h", cname = "io_elementary_pantheon_blur_manager_v1_interface")]
+ public static Wl.Interface iface;
+ public void set_user_data (void* user_data);
+ public void* get_user_data ();
+ public uint32 get_version ();
+ public void destroy ();
+ public PantheonBlur.Blur get_blur (Wl.Surface surface);
+
+ }
+
+ [CCode (cheader_filename = "pantheon-blur-client-protocol.h", cname = "struct io_elementary_pantheon_blur_v1", cprefix = "io_elementary_pantheon_blur_v1_")]
+ public class Blur : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-blur-client-protocol.h", cname = "io_elementary_pantheon_blur_v1_interface")]
+ public static Wl.Interface iface;
+ public void set_user_data (void* user_data);
+ public void* get_user_data ();
+ public uint32 get_version ();
+ public void destroy ();
+ public void set_region (uint x, uint y, uint width, uint height, uint clip_radius);
+ }
+}
diff --git a/lib/BlurSurface.vala b/lib/BlurSurface.vala
new file mode 100644
index 000000000..ddb110909
--- /dev/null
+++ b/lib/BlurSurface.vala
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2025 elementary, Inc.
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+/**
+ * This interface is used for asking window manager to provide background blur for the surface.
+ */
+public interface Granite.BlurSurface : Gtk.Widget, Gtk.Native {
+ /**
+ * Returns whether application is running with Wayland backend
+ */
+ public bool is_wayland () {
+ return Gdk.Display.get_default () is Gdk.Wayland.Display;
+ }
+
+ /**
+ * Initializes blur support. Uses default `blur_registry_handle_global` and `get_x11_blur_hints`.
+ * Use if you are not using other Wayland/X11 protocols.
+ */
+ public void simple_blur_init () {
+ if (is_wayland ()) {
+ init_wayland (blur_registry_handle_global);
+ } else {
+ update_x11_hints (get_x11_blur_hints (0, 0, 0, 0, 0));
+ }
+ }
+
+ private static Wl.RegistryListener registry_listener;
+
+ /**
+ * Initializes blur support on Wayland.
+ * Use this method only if you need to manually initialize support of multiple
+ * Wayland protocols. Otherwise use `simple_blur_init`.
+ */
+ public void init_wayland (Wl.RegistryListenerGlobal registry_handle_global) requires (is_wayland ()) {
+ registry_listener.global = registry_handle_global;
+ unowned var display = (Gdk.Wayland.Display) Gdk.Display.get_default ();
+ unowned var wl_display = display.get_wl_display ();
+ var wl_registry = wl_display.get_registry ();
+ wl_registry.add_listener (
+ registry_listener,
+ this
+ );
+
+ if (wl_display.roundtrip () < 0) {
+ return;
+ }
+ }
+
+ /**
+ * Registers the window as user of blur protocol. Use with `init_wayland` method.
+ */
+ public void blur_registry_handle_global (Wl.Registry wl_registry, uint32 name, string @interface, uint32 version) {
+ if (@interface == "io_elementary_pantheon_blur_manager_v1") {
+ var blur_manager = wl_registry.bind (name, ref PantheonBlur.BlurManager.iface, uint32.min (version, 1));
+ unowned var surface = get_surface ();
+ if (surface is Gdk.Wayland.Surface) {
+ unowned var wl_surface = ((Gdk.Wayland.Surface) surface).get_wl_surface ();
+ set_data ("-pantheon-wayland-blur", blur_manager.get_blur (wl_surface));
+ }
+ }
+ }
+
+ /**
+ * Request background blur. Use if you use blur Wayland/X11 protocol only.
+ * Otherwise manually request blur using `request_blur_wayland` and `update_x11_hints` with `get_x11_blur_hints`.
+ */
+ public void simple_request_blur (uint x, uint y, uint width, uint height, uint clip_radius) {
+ if (is_wayland ()) {
+ request_blur_wayland (x, y, width, height, clip_radius);
+ } else {
+ update_x11_hints (get_x11_blur_hints (x, y, width, height, clip_radius));
+ }
+ }
+
+ /**
+ * Request background blur on wayland.
+ * Use this method only if you use multiple Wayland protocols. Otherwise use `simple_request_blur`.
+ */
+ public void request_blur_wayland (uint x, uint y, uint width, uint height, uint clip_radius) {
+ if (!is_wayland ()) {
+ warning ("BlurSurface.request_blur_wayland: Ignoring, not on Wayland");
+ }
+
+ unowned PantheonBlur.Blur? blur = get_data ("-pantheon-wayland-blur");
+ if (blur != null) {
+ blur.set_region (x, y, width, height, clip_radius);
+ } else {
+ debug ("Couldn't request blur: Blur surface was null. Did you forget to register blur interface?");
+ }
+ }
+
+ /**
+ * Updates X11 hints that Gala (Pantheon window manager) uses for its protocols for X11 windows.
+ * Use only if you support multiple X11 Gala protocols.
+ */
+ public void update_x11_hints (string value) requires (!is_wayland ()) {
+ unowned var display = (Gdk.X11.Display) Gdk.Display.get_default ();
+ unowned var xdisplay = display.get_xdisplay ();
+ var xid = ((Gdk.X11.Surface) get_surface ()).get_xid ();
+ var prop = xdisplay.intern_atom ("_MUTTER_HINTS", false);
+ xdisplay.change_property (xid, prop, X.XA_STRING, 8, 0, (uchar[]) value, value.length);
+ }
+
+ /**
+ * Returns string that can be used in `update_x11_hints` to request blur.
+ * Use only if you support multiple X11 Gala protocols.
+ */
+ public string get_x11_blur_hints (uint x, uint y, uint width, uint height, uint clip_radius) {
+ return "blur=%u,%u,%u,%u,%u:".printf (x, y, width, height, clip_radius);
+ }
+}
diff --git a/lib/meson.build b/lib/meson.build
index e92fd7881..3fe49aef0 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -2,8 +2,9 @@ subdir('Icons')
subdir('Styles')
libgranite_sources = files(
- 'DateTime.vala',
+ 'BlurSurface.vala',
'Constants.vala',
+ 'DateTime.vala',
'Init.vala',
'Services/Application.vala',
@@ -70,7 +71,8 @@ libgranite = library(
dependencies: [
libgranite_deps,
meson.get_compiler('c').find_library('m'),
- meson.get_compiler('vala').find_library('posix')
+ meson.get_compiler('vala').find_library('posix'),
+ pantheon_blur_dep
],
vala_header: 'granite-7.h',
diff --git a/meson.build b/meson.build
index 65ec20cb4..705c5f198 100644
--- a/meson.build
+++ b/meson.build
@@ -43,6 +43,8 @@ add_project_arguments(
language: ['vala']
)
+wayland_client_dep = dependency('wayland-client')
+
libgranite_deps = [
dependency('gee-0.8'),
dependency('gio-2.0', version: '>=' + glib_min_version),
@@ -50,12 +52,16 @@ libgranite_deps = [
dependency('glib-2.0', version: '>=' + glib_min_version),
dependency('gobject-2.0', version: '>=' + glib_min_version),
dependency('gtk4', version: '>=4.4'),
+ dependency('gtk4-wayland', version: '>=4.4'),
+ dependency('gtk4-x11', version: '>=4.4'),
+ wayland_client_dep
]
pkgconfig = import('pkgconfig')
gnome = import('gnome')
i18n = import('i18n')
+subdir('blur-protocol')
subdir('lib')
subdir('data')
subdir('po')