diff --git a/data/application.css b/data/application.css
index 2a39f1a..37a6d38 100644
--- a/data/application.css
+++ b/data/application.css
@@ -44,6 +44,12 @@ window,
.notification .draw-area {
margin: 16px;
+ background: alpha(@base_color, 0.8);
+}
+
+.notification.reduce-transparency .draw-area {
+ margin: 16px;
+ background: @base_color;
}
.notification:not(.confirmation) .draw-area image {
diff --git a/meson.build b/meson.build
index c7426d9..41b6299 100644
--- a/meson.build
+++ b/meson.build
@@ -15,6 +15,8 @@ css_gresource = gnome.compile_resources(
source_dir: 'data'
)
+subdir('protocol')
+
executable(
meson.project_name(),
'src/AbstractBubble.vala',
@@ -34,8 +36,12 @@ executable(
dependency ('gio-2.0'),
dependency ('granite-7', version: '>=7.7.0'),
dependency ('gtk4'),
+ dependency('gtk4-wayland'),
+ dependency('gtk4-x11'),
dependency ('gtk4-x11'),
- dependency ('libadwaita-1')
+ dependency ('libadwaita-1'),
+ dependency('wayland-client'),
+ pantheon_desktop_shell_dep
],
install : true
)
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 0000000..083e967
--- /dev/null
+++ b/protocol/meson.build
@@ -0,0 +1,32 @@
+dep_scanner = dependency('wayland-scanner', native: true)
+prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner'))
+
+protocol_file = files('pantheon-desktop-shell-v1.xml')
+
+pantheon_desktop_shell_sources = []
+pantheon_desktop_shell_sources += custom_target(
+ 'pantheon-desktop-shell-client-protocol.h',
+ command: [ prog_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ],
+ input: protocol_file,
+ output: 'pantheon-desktop-shell-client-protocol.h',
+)
+
+output_type = 'private-code'
+if dep_scanner.version().version_compare('< 1.14.91')
+ output_type = 'code'
+endif
+pantheon_desktop_shell_sources += custom_target(
+ 'pantheon-desktop-shell-protocol.c',
+ command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ],
+ input: protocol_file,
+ output: 'pantheon-desktop-shell-protocol.c',
+)
+
+pantheon_desktop_shell_dep = declare_dependency(
+ dependencies: [
+ meson.get_compiler('vala').find_library('pantheon-desktop-shell', dirs: meson.current_source_dir()),
+ dependency('wayland-client'),
+ ],
+ include_directories: include_directories('.'),
+ sources: pantheon_desktop_shell_sources
+)
diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml
new file mode 100644
index 0000000..fcd0f17
--- /dev/null
+++ b/protocol/pantheon-desktop-shell-v1.xml
@@ -0,0 +1,155 @@
+
+
+
+
+ SPDX-License-Identifier: LGPL-2.1-or-later
+ ]]>
+
+
+
+ This interface is used by the Pantheon Wayland shell to communicate with
+ the compositor.
+
+
+
+
+ Create a panel surface from an existing surface.
+
+
+
+
+
+
+
+ Create a desktop widget surface from an existing surface.
+
+
+
+
+
+
+
+ Create a desktop-specific surface from an existing surface.
+
+
+
+
+
+
+
+
+
+
+
+ The anchor is a placement hint to the compositor.
+
+
+
+
+
+
+
+
+
+ How the shell should handle the window.
+
+
+
+
+
+
+
+
+
+
+ Tell the shell which side of the screen the panel is
+ located. This is so that new windows do not overlap the panel
+ and maximized windows maximize properly.
+
+
+
+
+
+
+
+ Request keyboard focus, taking it away from any other window.
+ Keyboard focus must always be manually be requested and is
+ - in contrast to normal windows - never automatically granted
+ by the compositor.
+
+
+
+
+
+ The given size is only used for exclusive zones and
+ collision tracking for auto hide. By default and if set
+ to -1 the size of the surface is used.
+
+
+
+
+
+
+
+
+ Tell the shell when to hide the panel.
+
+
+
+
+
+
+
+ Tell the shell that the panel would like to be visible in the multitasking view.
+
+
+
+
+
+ Tell the window manager to add background blur.
+
+
+
+
+
+
+
+
+
+
+
+ Tell the window manager to remove blur that was set in set_blur_region.
+
+
+
+
+
+
+
+
+
+
+
+
+ Tell the shell to keep the surface above on all workspaces
+
+
+
+
+
+ Request to keep the surface centered. This will cause keyboard focus
+ to not be granted automatically but having to be requested via focus.
+
+
+
+
+
+ Request keyboard focus, taking it away from any other window.
+ Keyboard focus must always be manually be requested and is
+ - in contrast to normal windows - never automatically granted
+ by the compositor.
+
+
+
+
diff --git a/protocol/pantheon-desktop-shell.deps b/protocol/pantheon-desktop-shell.deps
new file mode 100644
index 0000000..8289bf8
--- /dev/null
+++ b/protocol/pantheon-desktop-shell.deps
@@ -0,0 +1 @@
+wayland-client
diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi
new file mode 100644
index 0000000..1206d62
--- /dev/null
+++ b/protocol/pantheon-desktop-shell.vapi
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 elementary, Inc.
+ * Copyright 2023 Corentin Noël
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+namespace Pantheon.Desktop {
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_shell_v1", cprefix = "io_elementary_pantheon_shell_v1_")]
+ public class Shell : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_shell_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 Pantheon.Desktop.Panel get_panel (Wl.Surface surface);
+ public Pantheon.Desktop.Widget get_widget (Wl.Surface surface);
+ public Pantheon.Desktop.ExtendedBehavior get_extended_behavior (Wl.Surface surface);
+
+ }
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_anchor", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_ANCHOR_", has_type_id = false)]
+ public enum Anchor {
+ TOP,
+ BOTTOM,
+ LEFT,
+ RIGHT,
+ }
+
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_hide_mode", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_HIDE_MODE_", has_type_id = false)]
+ public enum HideMode {
+ NEVER,
+ MAXIMIZED_FOCUS_WINDOW,
+ OVERLAPPING_FOCUS_WINDOW,
+ OVERLAPPING_WINDOW,
+ ALWAYS
+ }
+
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_panel_v1", cprefix = "io_elementary_pantheon_panel_v1_")]
+ public class Panel : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_panel_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_anchor (Pantheon.Desktop.Anchor anchor);
+ public void focus ();
+ public void set_size (int width, int height);
+ public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode);
+ public void request_visible_in_multitasking_view ();
+ public void add_blur (uint left, uint right, uint top, uint bottom, uint clip_radius);
+ public void remove_blur ();
+ }
+
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_widget_v1", cprefix = "io_elementary_pantheon_widget_v1_")]
+ public class Widget : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_widget_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 ();
+ }
+
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1", cprefix = "io_elementary_pantheon_extended_behavior_v1_")]
+ public class ExtendedBehavior : Wl.Proxy {
+ [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_extended_behavior_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_keep_above ();
+ }
+}
diff --git a/src/AbstractBubble.vala b/src/AbstractBubble.vala
index 446c26a..50bd92f 100644
--- a/src/AbstractBubble.vala
+++ b/src/AbstractBubble.vala
@@ -27,11 +27,24 @@ public class Notifications.AbstractBubble : Gtk.Window {
protected Gtk.Stack content_area;
+ private static Settings? transparency_settings;
+
private Gtk.Revealer close_revealer;
private Gtk.Box draw_area;
private uint timeout_id;
+ private double current_swipe_progress = 1.0;
+ private Pantheon.Desktop.Shell? desktop_shell;
+ private Pantheon.Desktop.Panel? desktop_panel;
+
+ static construct {
+ var transparency_schema = SettingsSchemaSource.get_default ().lookup ("io.elementary.desktop.wingpanel", true);
+ if (transparency_schema != null && transparency_schema.has_key ("use-transparency")) {
+ transparency_settings = new Settings ("io.elementary.desktop.wingpanel");
+ }
+ }
+
construct {
content_area = new Gtk.Stack () {
transition_type = Gtk.StackTransitionType.SLIDE_DOWN,
@@ -90,6 +103,39 @@ public class Notifications.AbstractBubble : Gtk.Window {
motion_controller.enter.connect (pointer_enter);
motion_controller.leave.connect (pointer_leave);
carousel.add_controller (motion_controller);
+
+ child.realize.connect (() => {
+ if (Gdk.Display.get_default () is Gdk.Wayland.Display) {
+ // We have to wrap in Idle otherwise the Meta.Window of the WaylandSurface in Gala is still null
+ Idle.add_once (init_wl);
+ } else {
+ init_x ();
+ }
+ });
+
+ carousel.notify["position"].connect (() => {
+ current_swipe_progress = carousel.position;
+
+ if (desktop_panel != null) {
+ int left, right;
+ get_blur_margins (out left, out right);
+
+ desktop_panel.add_blur (left, right, 16, 16, 9);
+ } else if (Gdk.Display.get_default () is Gdk.X11.Display) {
+ init_x ();
+ }
+ });
+
+ transparency_settings.changed["use-transparency"].connect (update_transparency);
+ update_transparency ();
+ }
+
+ private void update_transparency () requires (transparency_settings != null) {
+ if (transparency_settings.get_boolean ("use-transparency")) {
+ remove_css_class ("reduce-transparency");
+ } else {
+ add_css_class ("reduce-transparency");
+ }
}
public new void present () {
@@ -126,4 +172,59 @@ public class Notifications.AbstractBubble : Gtk.Window {
closed (Notifications.Server.CloseReason.EXPIRED);
return Source.REMOVE;
}
+
+
+ private void get_blur_margins (out int left, out int right) {
+ var width = get_width ();
+ var distance = (1 - current_swipe_progress) * width;
+ left = (int) (16 + distance).clamp (0, width);
+ right = (int) (16 - distance).clamp (0, width);
+ }
+
+ private void init_x () {
+ var display = Gdk.Display.get_default ();
+ if (display is Gdk.X11.Display) {
+ unowned var xdisplay = ((Gdk.X11.Display) display).get_xdisplay ();
+
+ var window = ((Gdk.X11.Surface) get_surface ()).get_xid ();
+ var prop = xdisplay.intern_atom ("_MUTTER_HINTS", false);
+
+ int left, right;
+ get_blur_margins (out left, out right);
+
+ var value = "blur=%d,%d,16,16,9".printf (left, right);
+
+ xdisplay.change_property (window, prop, X.XA_STRING, 8, 0, (uchar[]) value, value.length);
+ }
+ }
+
+ private static Wl.RegistryListener registry_listener;
+ private void init_wl () {
+ registry_listener.global = registry_handle_global;
+ unowned var display = Gdk.Display.get_default ();
+ if (display is Gdk.Wayland.Display) {
+ unowned var wl_display = ((Gdk.Wayland.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;
+ }
+ }
+ }
+
+ public void registry_handle_global (Wl.Registry wl_registry, uint32 name, string @interface, uint32 version) {
+ if (@interface == "io_elementary_pantheon_shell_v1") {
+ desktop_shell = wl_registry.bind (name, ref Pantheon.Desktop.Shell.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 ();
+ desktop_panel = desktop_shell.get_panel (wl_surface);
+ desktop_panel.add_blur (16, 16, 16, 16, 9);
+ }
+ }
+ }
}