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