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 (); + } +}