Skip to content

Commit 209ac16

Browse files
committed
wayland: Add make_modal protocol method
1 parent 00da2f0 commit 209ac16

File tree

8 files changed

+156
-1
lines changed

8 files changed

+156
-1
lines changed

protocol/pantheon-desktop-shell-v1.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,13 @@
151151
by the compositor.
152152
</description>
153153
</request>
154+
155+
<request name="make_modal">
156+
<description summary="requests to make a surface system modal">
157+
This will block all user input outside the surface and most system shortcuts.
158+
</description>
159+
160+
<arg name="dim" type="uint" summary="1 to dim, 0 to not dim"/>
161+
</request>
154162
</interface>
155163
</protocol>

protocol/pantheon-desktop-shell.vapi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ namespace Pantheon.Desktop {
6161
public SetKeepAbove set_keep_above;
6262
public MakeCentered make_centered;
6363
public Focus focus;
64+
public MakeModal make_modal;
6465
}
6566

6667
[CCode (has_target = false, has_typedef = false)]
@@ -88,5 +89,7 @@ namespace Pantheon.Desktop {
8889
[CCode (has_target = false, has_typedef = false)]
8990
public delegate void MakeCentered (Wl.Client client, Wl.Resource resource);
9091
[CCode (has_target = false, has_typedef = false)]
92+
public delegate void MakeModal (Wl.Client client, Wl.Resource resource, uint dim);
93+
[CCode (has_target = false, has_typedef = false)]
9194
public delegate void Destroy (Wl.Client client, Wl.Resource resource);
9295
}

src/PantheonShell.vala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ namespace Gala {
5353
set_keep_above,
5454
make_centered,
5555
focus_extended_behavior,
56+
make_modal,
5657
};
5758

5859
PanelSurface.quark = GLib.Quark.from_string ("-gala-wayland-panel-surface-data");
@@ -376,6 +377,21 @@ namespace Gala {
376377
ShellClientsManager.get_instance ().make_centered (window);
377378
}
378379

380+
internal static void make_modal (Wl.Client client, Wl.Resource resource, uint dim) {
381+
unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data<ExtendedBehaviorSurface> ();
382+
if (eb_surface.wayland_surface == null) {
383+
return;
384+
}
385+
386+
Meta.Window? window;
387+
eb_surface.wayland_surface.get ("window", out window, null);
388+
if (window == null) {
389+
return;
390+
}
391+
392+
ShellClientsManager.get_instance ().make_modal (window, dim == 1);
393+
}
394+
379395
internal static void destroy_panel_surface (Wl.Client client, Wl.Resource resource) {
380396
resource.destroy ();
381397
}

src/ShellClients/ExtendedBehaviorWindow.vala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66
*/
77

88
public class Gala.ExtendedBehaviorWindow : ShellWindow {
9+
public bool modal { get; private set; default = false; }
10+
public bool dim { get; private set; default = false; }
11+
912
public ExtendedBehaviorWindow (Meta.Window window) {
1013
var target = new PropertyTarget (CUSTOM, window.get_compositor_private (), "opacity", typeof (uint), 255u, 0u);
1114
Object (window: window, hide_target: target);
1215
}
1316

17+
public void make_modal (bool dim) {
18+
modal = true;
19+
this.dim = dim;
20+
}
21+
1422
protected override void get_window_position (
1523
Mtk.Rectangle window_rect, Mtk.Rectangle monitor_rect, out int x, out int y
1624
) {

src/ShellClients/ShellClientsManager.vala

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
2626
private ManagedClient[] protocol_clients = {};
2727

2828
private GLib.HashTable<Meta.Window, PanelWindow> panel_windows = new GLib.HashTable<Meta.Window, PanelWindow> (null, null);
29-
private GLib.HashTable<Meta.Window, ShellWindow> positioned_windows = new GLib.HashTable<Meta.Window, ShellWindow> (null, null);
29+
private GLib.HashTable<Meta.Window, ExtendedBehaviorWindow> positioned_windows = new GLib.HashTable<Meta.Window, ExtendedBehaviorWindow> (null, null);
3030

3131
private ShellClientsManager (WindowManager wm) {
3232
Object (wm: wm);
@@ -205,6 +205,10 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
205205
window.unmanaging.connect_after ((_window) => positioned_windows.remove (_window));
206206
}
207207

208+
public void make_modal (Meta.Window window, bool dim) requires (window in positioned_windows) {
209+
positioned_windows[window].make_modal (dim);
210+
}
211+
208212
public void propagate (UpdateType update_type, GestureAction action, double progress) {
209213
foreach (var window in positioned_windows.get_values ()) {
210214
window.propagate (update_type, action, progress);
@@ -232,6 +236,27 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
232236
return positioned;
233237
}
234238

239+
private bool is_itself_system_modal (Meta.Window window) {
240+
return (window in positioned_windows) && positioned_windows[window].modal;
241+
}
242+
243+
public bool is_system_modal_window (Meta.Window window) {
244+
var modal = is_itself_system_modal (window);
245+
window.foreach_ancestor ((ancestor) => {
246+
if (is_itself_system_modal (ancestor)) {
247+
modal = true;
248+
}
249+
250+
return !modal;
251+
});
252+
253+
return modal;
254+
}
255+
256+
public bool is_system_modal_dimmed (Meta.Window window) {
257+
return is_itself_system_modal (window) && positioned_windows[window].dim;
258+
}
259+
235260
//X11 only
236261
private void parse_mutter_hints (Meta.Window window) requires (!Meta.Util.is_wayland_compositor ()) {
237262
if (window.mutter_hints == null) {

src/Widgets/ModalGroup.vala

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2025 elementary, Inc. (https://elementary.io)
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*
5+
* Authored by: Leonhard Kargl <[email protected]>
6+
*/
7+
8+
/**
9+
* This class allows to make windows system modal i.e. dim
10+
* the desktop behind them and only allow interaction with them.
11+
* Not to be confused with WindowManager.push_modal which only
12+
* works for our own Clutter.Actors.
13+
*/
14+
public class Gala.ModalGroup : Clutter.Actor {
15+
public WindowManager wm { private get; construct; }
16+
public ShellClientsManager shell_clients { private get; construct; }
17+
18+
private Gee.Set<Clutter.Actor> dimmed;
19+
private ModalProxy? modal_proxy = null;
20+
21+
public ModalGroup (WindowManager wm, ShellClientsManager shell_clients) {
22+
Object (wm: wm, shell_clients: shell_clients);
23+
}
24+
25+
construct {
26+
dimmed = new Gee.HashSet<Clutter.Actor> ();
27+
28+
visible = false;
29+
reactive = true;
30+
31+
child_added.connect (on_child_added);
32+
child_removed.connect (on_child_removed);
33+
}
34+
35+
private void on_child_added (Clutter.Actor child) {
36+
if (child is Meta.WindowActor && shell_clients.is_system_modal_dimmed (child.meta_window)) {
37+
dimmed.add (child);
38+
}
39+
40+
if (get_n_children () == 1) {
41+
assert (modal_proxy == null);
42+
43+
visible = true;
44+
modal_proxy = wm.push_modal (this, false);
45+
}
46+
47+
if (dimmed.size == 1) {
48+
save_easing_state ();
49+
set_easing_duration (Utils.get_animation_duration (AnimationDuration.OPEN));
50+
background_color = { 0, 0, 0, 200 };
51+
restore_easing_state ();
52+
}
53+
}
54+
55+
private void on_child_removed (Clutter.Actor child) {
56+
dimmed.remove (child);
57+
58+
if (dimmed.size == 0) {
59+
save_easing_state ();
60+
set_easing_duration (Utils.get_animation_duration (AnimationDuration.CLOSE));
61+
background_color = { 0, 0, 0, 0 };
62+
restore_easing_state ();
63+
}
64+
65+
if (get_n_children () == 0) {
66+
wm.pop_modal (modal_proxy);
67+
modal_proxy = null;
68+
69+
var transition = get_transition ("background-color");
70+
if (transition != null) {
71+
transition.completed.connect (() => visible = false);
72+
} else {
73+
visible = false;
74+
}
75+
}
76+
}
77+
}

src/WindowManager.vala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ namespace Gala {
4949
*/
5050
public Clutter.Actor shell_group { get; private set; }
5151

52+
/**
53+
* The group that contains all WindowActors that are system modal.
54+
* See {@link ShellClientsManager.is_system_modal_window}.
55+
*/
56+
public ModalGroup modal_group { get; private set; }
57+
5258
/**
5359
* {@inheritDoc}
5460
*/
@@ -223,6 +229,7 @@ namespace Gala {
223229
* +-- window switcher
224230
* +-- window overview
225231
* +-- shell group
232+
* +-- modal group
226233
* +-- feedback group (e.g. DND icons)
227234
* +-- pointer locator
228235
* +-- dwell click timer
@@ -291,6 +298,10 @@ namespace Gala {
291298
shell_group = new Clutter.Actor ();
292299
ui_group.add_child (shell_group);
293300

301+
modal_group = new ModalGroup (this, ShellClientsManager.get_instance ());
302+
modal_group.add_constraint (new Clutter.BindConstraint (stage, ALL, 0));
303+
ui_group.add_child (modal_group);
304+
294305
var feedback_group = display.get_compositor ().get_feedback_group ();
295306
stage.remove_child (feedback_group);
296307
ui_group.add_child (feedback_group);
@@ -1011,6 +1022,12 @@ namespace Gala {
10111022

10121023
private void check_shell_window (Meta.WindowActor actor) {
10131024
unowned var window = actor.get_meta_window ();
1025+
1026+
if (ShellClientsManager.get_instance ().is_system_modal_window (window)) {
1027+
InternalUtils.clutter_actor_reparent (actor, modal_group);
1028+
return;
1029+
}
1030+
10141031
if (ShellClientsManager.get_instance ().is_positioned_window (window)) {
10151032
InternalUtils.clutter_actor_reparent (actor, shell_group);
10161033
}

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ gala_bin_sources = files(
6060
'Widgets/MultitaskingView/WindowCloneContainer.vala',
6161
'Widgets/MultitaskingView/WorkspaceClone.vala',
6262
'Widgets/MultitaskingView/WorkspaceRow.vala',
63+
'Widgets/ModalGroup.vala',
6364
'Widgets/PixelPicker.vala',
6465
'Widgets/PointerLocator.vala',
6566
'Widgets/SessionLocker.vala',

0 commit comments

Comments
 (0)