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')