Skip to content

Commit f1564b3

Browse files
committed
Add background blur
1 parent 90474b0 commit f1564b3

File tree

7 files changed

+353
-1
lines changed

7 files changed

+353
-1
lines changed

data/application.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ window,
4444

4545
.notification .draw-area {
4646
margin: 16px;
47+
background: alpha(@bg_color, 0.8);
4748
}
4849

4950
.notification:not(.confirmation) .draw-area image {

meson.build

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ css_gresource = gnome.compile_resources(
1616
source_dir: 'data'
1717
)
1818

19+
subdir('protocol')
20+
1921
executable(
2022
meson.project_name(),
2123
'src/AbstractBubble.vala',
@@ -35,8 +37,12 @@ executable(
3537
dependency ('gio-2.0'),
3638
dependency ('granite-7', version: '>=7.7.0'),
3739
dependency ('gtk4'),
40+
dependency('gtk4-wayland'),
41+
dependency('gtk4-x11'),
3842
dependency ('gtk4-x11'),
39-
dependency ('libadwaita-1')
43+
dependency ('libadwaita-1'),
44+
dependency('wayland-client'),
45+
pantheon_desktop_shell_dep
4046
],
4147
install : true
4248
)

protocol/meson.build

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
dep_scanner = dependency('wayland-scanner', native: true)
2+
prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner'))
3+
4+
protocol_file = files('pantheon-desktop-shell-v1.xml')
5+
6+
pantheon_desktop_shell_sources = []
7+
pantheon_desktop_shell_sources += custom_target(
8+
'pantheon-desktop-shell-client-protocol.h',
9+
command: [ prog_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ],
10+
input: protocol_file,
11+
output: 'pantheon-desktop-shell-client-protocol.h',
12+
)
13+
14+
output_type = 'private-code'
15+
if dep_scanner.version().version_compare('< 1.14.91')
16+
output_type = 'code'
17+
endif
18+
pantheon_desktop_shell_sources += custom_target(
19+
'pantheon-desktop-shell-protocol.c',
20+
command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ],
21+
input: protocol_file,
22+
output: 'pantheon-desktop-shell-protocol.c',
23+
)
24+
25+
pantheon_desktop_shell_dep = declare_dependency(
26+
dependencies: [
27+
meson.get_compiler('vala').find_library('pantheon-desktop-shell', dirs: meson.current_source_dir()),
28+
dependency('wayland-client'),
29+
],
30+
include_directories: include_directories('.'),
31+
sources: pantheon_desktop_shell_sources
32+
)
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<protocol name="pantheon_shell_v1">
3+
<copyright><![CDATA[
4+
SPDX-FileCopyrightText: 2023 Corentin Noël <[email protected]>
5+
6+
SPDX-License-Identifier: LGPL-2.1-or-later
7+
]]></copyright>
8+
9+
<interface name="io_elementary_pantheon_shell_v1" version="1">
10+
<description summary="create panel, widget and get more control">
11+
This interface is used by the Pantheon Wayland shell to communicate with
12+
the compositor.
13+
</description>
14+
15+
<request name="get_panel">
16+
<description summary="create a panel surface from a surface">
17+
Create a panel surface from an existing surface.
18+
</description>
19+
<arg name="output" type="new_id" interface="io_elementary_pantheon_panel_v1"/>
20+
<arg name="surface" type="object" interface="wl_surface"/>
21+
</request>
22+
23+
<request name="get_widget">
24+
<description summary="create a widget surface from a surface">
25+
Create a desktop widget surface from an existing surface.
26+
</description>
27+
<arg name="output" type="new_id" interface="io_elementary_pantheon_widget_v1"/>
28+
<arg name="surface" type="object" interface="wl_surface"/>
29+
</request>
30+
31+
<request name="get_extended_behavior">
32+
<description summary="create a desktop-specific surface from a surface">
33+
Create a desktop-specific surface from an existing surface.
34+
</description>
35+
<arg name="output" type="new_id" interface="io_elementary_pantheon_extended_behavior_v1"/>
36+
<arg name="surface" type="object" interface="wl_surface"/>
37+
</request>
38+
</interface>
39+
40+
<interface name="io_elementary_pantheon_panel_v1" version="1">
41+
<request name="destroy" type="destructor"/>
42+
43+
<enum name="anchor">
44+
<description summary="anchor">
45+
The anchor is a placement hint to the compositor.
46+
</description>
47+
<entry name="top" value="0" summary="the top edge of the screen"/>
48+
<entry name="bottom" value="1" summary="the bottom edge of the screen"/>
49+
<entry name="left" value="2" summary="the left edge of the screen"/>
50+
<entry name="right" value="3" summary="the right edge of the screen"/>
51+
</enum>
52+
53+
<enum name="hide_mode">
54+
<description summary="hide mode">
55+
How the shell should handle the window.
56+
</description>
57+
<entry name="never" value="0" summary="make the surface exclusive"/>
58+
<entry name="maximized_focus_window" value="1" summary="hide when the focused window is maximized"/>
59+
<entry name="overlapping_focus_window" value="2" summary="hide when the focused window overlaps the surface"/>
60+
<entry name="overlapping_window" value="3" summary="hide when any window overlaps the surface"/>
61+
<entry name="always" value="4" summary="always hide and only show if requested by the user"/>
62+
</enum>
63+
64+
<request name="set_anchor">
65+
<description summary="set panel edge anchor">
66+
Tell the shell which side of the screen the panel is
67+
located. This is so that new windows do not overlap the panel
68+
and maximized windows maximize properly.
69+
</description>
70+
71+
<arg name="anchor" type="uint" enum="anchor" summary="anchor"/>
72+
</request>
73+
74+
<request name="focus">
75+
<description summary="request keyboard focus">
76+
Request keyboard focus, taking it away from any other window.
77+
Keyboard focus must always be manually be requested and is
78+
- in contrast to normal windows - never automatically granted
79+
by the compositor.
80+
</description>
81+
</request>
82+
83+
<request name="set_size">
84+
<description summary="set size">
85+
The given size is only used for exclusive zones and
86+
collision tracking for auto hide. By default and if set
87+
to -1 the size of the surface is used.
88+
</description>
89+
90+
<arg name="width" type="int"/>
91+
<arg name="height" type="int"/>
92+
</request>
93+
94+
<request name="set_hide_mode">
95+
<description summary="set panel hide mode">
96+
Tell the shell when to hide the panel.
97+
</description>
98+
99+
<arg name="hide_mode" type="uint" enum="hide_mode" summary="hide mode"/>
100+
</request>
101+
102+
<request name="request_visible_in_multitasking_view">
103+
<description summary="request visible in multitasking view">
104+
Tell the shell that the panel would like to be visible in the multitasking view.
105+
</description>
106+
</request>
107+
108+
<request name="add_blur">
109+
<description summary="add blur">
110+
Tell the window manager to add background blur.
111+
</description>
112+
113+
<arg name="left" type="uint"/>
114+
<arg name="right" type="uint"/>
115+
<arg name="top" type="uint"/>
116+
<arg name="bottom" type="uint"/>
117+
<arg name="clip_radius" type="uint"/>
118+
</request>
119+
120+
<request name="remove_blur">
121+
<description summary="remove blur">
122+
Tell the window manager to remove blur that was set in set_blur_region.
123+
</description>
124+
</request>
125+
</interface>
126+
127+
<interface name="io_elementary_pantheon_widget_v1" version="1">
128+
<request name="destroy" type="destructor"/>
129+
</interface>
130+
131+
<interface name="io_elementary_pantheon_extended_behavior_v1" version="1">
132+
<request name="destroy" type="destructor"/>
133+
<request name="set_keep_above">
134+
<description summary="set keep above">
135+
Tell the shell to keep the surface above on all workspaces
136+
</description>
137+
</request>
138+
139+
<request name="make_centered">
140+
<description summary="requests to keep the surface centered">
141+
Request to keep the surface centered. This will cause keyboard focus
142+
to not be granted automatically but having to be requested via focus.
143+
</description>
144+
</request>
145+
146+
<request name="focus">
147+
<description summary="request keyboard focus">
148+
Request keyboard focus, taking it away from any other window.
149+
Keyboard focus must always be manually be requested and is
150+
- in contrast to normal windows - never automatically granted
151+
by the compositor.
152+
</description>
153+
</request>
154+
</interface>
155+
</protocol>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
wayland-client
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2023 elementary, Inc. <https://elementary.io>
3+
* Copyright 2023 Corentin Noël <[email protected]>
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
namespace Pantheon.Desktop {
8+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_shell_v1", cprefix = "io_elementary_pantheon_shell_v1_")]
9+
public class Shell : Wl.Proxy {
10+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_shell_v1_interface")]
11+
public static Wl.Interface iface;
12+
public void set_user_data (void* user_data);
13+
public void* get_user_data ();
14+
public uint32 get_version ();
15+
public void destroy ();
16+
public Pantheon.Desktop.Panel get_panel (Wl.Surface surface);
17+
public Pantheon.Desktop.Widget get_widget (Wl.Surface surface);
18+
public Pantheon.Desktop.ExtendedBehavior get_extended_behavior (Wl.Surface surface);
19+
20+
}
21+
[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)]
22+
public enum Anchor {
23+
TOP,
24+
BOTTOM,
25+
LEFT,
26+
RIGHT,
27+
}
28+
29+
[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)]
30+
public enum HideMode {
31+
NEVER,
32+
MAXIMIZED_FOCUS_WINDOW,
33+
OVERLAPPING_FOCUS_WINDOW,
34+
OVERLAPPING_WINDOW,
35+
ALWAYS
36+
}
37+
38+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_panel_v1", cprefix = "io_elementary_pantheon_panel_v1_")]
39+
public class Panel : Wl.Proxy {
40+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_panel_v1_interface")]
41+
public static Wl.Interface iface;
42+
public void set_user_data (void* user_data);
43+
public void* get_user_data ();
44+
public uint32 get_version ();
45+
public void destroy ();
46+
public void set_anchor (Pantheon.Desktop.Anchor anchor);
47+
public void focus ();
48+
public void set_size (int width, int height);
49+
public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode);
50+
public void request_visible_in_multitasking_view ();
51+
public void add_blur (uint left, uint right, uint top, uint bottom, uint clip_radius);
52+
public void remove_blur ();
53+
}
54+
55+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_widget_v1", cprefix = "io_elementary_pantheon_widget_v1_")]
56+
public class Widget : Wl.Proxy {
57+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_widget_v1_interface")]
58+
public static Wl.Interface iface;
59+
public void set_user_data (void* user_data);
60+
public void* get_user_data ();
61+
public uint32 get_version ();
62+
public void destroy ();
63+
}
64+
65+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1", cprefix = "io_elementary_pantheon_extended_behavior_v1_")]
66+
public class ExtendedBehavior : Wl.Proxy {
67+
[CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_extended_behavior_v1_interface")]
68+
public static Wl.Interface iface;
69+
public void set_user_data (void* user_data);
70+
public void* get_user_data ();
71+
public uint32 get_version ();
72+
public void destroy ();
73+
public void set_keep_above ();
74+
}
75+
}

src/AbstractBubble.vala

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public class Notifications.AbstractBubble : Gtk.Window {
3232
private Gtk.EventControllerMotion motion_controller;
3333
private uint timeout_id;
3434

35+
private double current_swipe_progress = 1.0;
36+
private Pantheon.Desktop.Shell? desktop_shell;
37+
private Pantheon.Desktop.Panel? desktop_panel;
38+
39+
3540
construct {
3641
content_area = new Gtk.Stack () {
3742
transition_type = Gtk.StackTransitionType.SLIDE_DOWN,
@@ -107,6 +112,28 @@ public class Notifications.AbstractBubble : Gtk.Window {
107112
carousel.add_controller (motion_controller);
108113

109114
close_request.connect (on_close);
115+
116+
child.realize.connect (() => {
117+
if (Gdk.Display.get_default () is Gdk.Wayland.Display) {
118+
// We have to wrap in Idle otherwise the Meta.Window of the WaylandSurface in Gala is still null
119+
Idle.add_once (init_wl);
120+
} else {
121+
init_x ();
122+
}
123+
});
124+
125+
carousel.notify["position"].connect (() => {
126+
current_swipe_progress = carousel.position;
127+
128+
if (desktop_panel != null) {
129+
int left, right;
130+
get_blur_margins (out left, out right);
131+
132+
desktop_panel.add_blur (left, right, 16, 16, 9);
133+
} else if (Gdk.Display.get_default () is Gdk.X11.Display) {
134+
init_x ();
135+
}
136+
});
110137
}
111138

112139
private bool on_close (Gtk.Window window) {
@@ -154,4 +181,59 @@ public class Notifications.AbstractBubble : Gtk.Window {
154181
closed (Notifications.Server.CloseReason.EXPIRED);
155182
return Source.REMOVE;
156183
}
184+
185+
186+
private void get_blur_margins (out int left, out int right) {
187+
var width = get_width ();
188+
var distance = (1 - current_swipe_progress) * width;
189+
left = (int) (16 + distance).clamp (0, width);
190+
right = (int) (16 - distance).clamp (0, width);
191+
}
192+
193+
private void init_x () {
194+
var display = Gdk.Display.get_default ();
195+
if (display is Gdk.X11.Display) {
196+
unowned var xdisplay = ((Gdk.X11.Display) display).get_xdisplay ();
197+
198+
var window = ((Gdk.X11.Surface) get_surface ()).get_xid ();
199+
var prop = xdisplay.intern_atom ("_MUTTER_HINTS", false);
200+
201+
int left, right;
202+
get_blur_margins (out left, out right);
203+
204+
var value = "blur=%d,%d,16,16,9".printf (left, right);
205+
206+
xdisplay.change_property (window, prop, X.XA_STRING, 8, 0, (uchar[]) value, value.length);
207+
}
208+
}
209+
210+
private static Wl.RegistryListener registry_listener;
211+
private void init_wl () {
212+
registry_listener.global = registry_handle_global;
213+
unowned var display = Gdk.Display.get_default ();
214+
if (display is Gdk.Wayland.Display) {
215+
unowned var wl_display = ((Gdk.Wayland.Display) display).get_wl_display ();
216+
var wl_registry = wl_display.get_registry ();
217+
wl_registry.add_listener (
218+
registry_listener,
219+
this
220+
);
221+
222+
if (wl_display.roundtrip () < 0) {
223+
return;
224+
}
225+
}
226+
}
227+
228+
public void registry_handle_global (Wl.Registry wl_registry, uint32 name, string @interface, uint32 version) {
229+
if (@interface == "io_elementary_pantheon_shell_v1") {
230+
desktop_shell = wl_registry.bind<Pantheon.Desktop.Shell> (name, ref Pantheon.Desktop.Shell.iface, uint32.min (version, 1));
231+
unowned var surface = get_surface ();
232+
if (surface is Gdk.Wayland.Surface) {
233+
unowned var wl_surface = ((Gdk.Wayland.Surface) surface).get_wl_surface ();
234+
desktop_panel = desktop_shell.get_panel (wl_surface);
235+
desktop_panel.add_blur (16, 16, 16, 16, 9);
236+
}
237+
}
238+
}
157239
}

0 commit comments

Comments
 (0)