diff --git a/data/application.css b/data/application.css
index 0f123b46..8494a9f6 100644
--- a/data/application.css
+++ b/data/application.css
@@ -38,6 +38,7 @@ window,
.notification .draw-area {
margin: 16px;
+ background: alpha(@bg_color, 0.8);
}
.notification:not(.confirmation) .draw-area image {
diff --git a/meson.build b/meson.build
index 25e1b25d..872aea3b 100644
--- a/meson.build
+++ b/meson.build
@@ -16,6 +16,11 @@ css_gresource = gnome.compile_resources(
source_dir: 'data'
)
+subdir('protocol')
+subdir('data')
+subdir('demo')
+subdir('po')
+
executable(
meson.project_name(),
'src/AbstractBubble.vala',
@@ -28,20 +33,20 @@ executable(
'src/Widgets/MaskedImage.vala',
css_gresource,
dependencies: [
- dependency ('libcanberra'),
- dependency ('libcanberra-gtk3'),
- dependency ('glib-2.0'),
- dependency ('gobject-2.0'),
- dependency ('gio-2.0'),
- dependency ('granite', version: '>=5.4.0'),
- dependency ('gtk+-3.0'),
- dependency ('libhandy-1')
+ dependency('libcanberra'),
+ dependency('libcanberra-gtk3'),
+ dependency('glib-2.0'),
+ dependency('gobject-2.0'),
+ dependency('gio-2.0'),
+ dependency('granite', version: '>=5.4.0'),
+ dependency('gdk-wayland-3.0'),
+ dependency('gdk-x11-3.0'),
+ dependency('gtk+-3.0'),
+ dependency('libhandy-1'),
+ dependency('wayland-client'),
+ pantheon_desktop_shell_dep
],
install : true
)
-subdir('data')
-subdir('demo')
-subdir('po')
-
gnome.post_install(glib_compile_schemas: true)
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 00000000..083e9675
--- /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 00000000..fcd0f175
--- /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 00000000..8289bf8e
--- /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 00000000..1206d622
--- /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 f5029d35..e60ed15e 100644
--- a/src/AbstractBubble.vala
+++ b/src/AbstractBubble.vala
@@ -32,6 +32,10 @@ public class Notifications.AbstractBubble : Gtk.Window {
private Gtk.EventControllerMotion motion_controller;
private uint timeout_id;
+ private double current_swipe_progress = 1.0;
+ private Pantheon.Desktop.Shell? desktop_shell;
+ private Pantheon.Desktop.Panel? desktop_panel;
+
construct {
content_area = new Gtk.Stack () {
transition_type = Gtk.StackTransitionType.SLIDE_DOWN,
@@ -95,12 +99,34 @@ public class Notifications.AbstractBubble : Gtk.Window {
close_button.clicked.connect (() => closed (Notifications.Server.CloseReason.DISMISSED));
closed.connect (close);
+ carousel.get_swipe_tracker ().update_swipe.connect ((progress) => {
+ current_swipe_progress = progress;
+
+ if (Gdk.Display.get_default () is Gdk.Wayland.Display) {
+ int left, right;
+ get_blur_margins (out left, out right);
+
+ desktop_panel.add_blur (left, right, 16, 16, 9);
+ } else {
+ init_x ();
+ }
+ });
+
motion_controller = new Gtk.EventControllerMotion (carousel) {
propagation_phase = TARGET
};
motion_controller.enter.connect (pointer_enter);
motion_controller.leave.connect (pointer_leave);
+ 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 ();
+ }
+ });
+
var a11y_object = get_accessible ();
a11y_object.accessible_role = NOTIFICATION;
}
@@ -151,4 +177,58 @@ 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_allocated_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.Window) get_window ()).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 window = get_window ();
+ if (window is Gdk.Wayland.Window) {
+ unowned var wl_surface = ((Gdk.Wayland.Window) window).get_wl_surface ();
+ desktop_panel = desktop_shell.get_panel (wl_surface);
+ desktop_panel.add_blur (16, 16, 16, 16, 9);
+ }
+ }
+ }
}
diff --git a/vapi/gdk-wayland-3.0.vapi b/vapi/gdk-wayland-3.0.vapi
new file mode 100644
index 00000000..f38aab43
--- /dev/null
+++ b/vapi/gdk-wayland-3.0.vapi
@@ -0,0 +1,16 @@
+[CCode (cheader_filename = "gdk/gdkwayland.h")]
+namespace Gdk.Wayland {
+ [CCode (type_id = "GDK_TYPE_WAYLAND_WINDOW", type_check_function = "GDK_IS_WAYLAND_WINDOW")]
+ public class Window : Gdk.Window {
+ protected Window ();
+
+ public unowned Wl.Surface get_wl_surface ();
+ }
+
+ [CCode (type_id = "GDK_TYPE_WAYLAND_DISPLAY", type_check_function = "GDK_IS_WAYLAND_DISPLAY")]
+ public class Display : Gdk.Display {
+ protected Display ();
+
+ public unowned Wl.Display get_wl_display ();
+ }
+}