Skip to content

Commit 0965cc0

Browse files
committed
wayland: Add make_modal protocol method
1 parent 47035e4 commit 0965cc0

File tree

8 files changed

+158
-1
lines changed

8 files changed

+158
-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 (Mtk.Rectangle window_rect, out int x, out int y) {
1523
var monitor_rect = window.display.get_monitor_geometry (window.get_monitor ());
1624

src/ShellClients/ShellClientsManager.vala

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
2828
private int starting_panels = 0;
2929

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

3333
private ShellClientsManager (WindowManager wm) {
3434
Object (wm: wm);
@@ -240,6 +240,10 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
240240
window.unmanaging.connect_after ((_window) => positioned_windows.remove (_window));
241241
}
242242

243+
public void make_modal (Meta.Window window, bool dim) requires (window in positioned_windows) {
244+
positioned_windows[window].make_modal (dim);
245+
}
246+
243247
public void propagate (UpdateType update_type, GestureAction action, double progress) {
244248
foreach (var window in positioned_windows.get_values ()) {
245249
window.propagate (update_type, action, progress);
@@ -267,6 +271,27 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
267271
return positioned;
268272
}
269273

274+
private bool is_itself_system_modal (Meta.Window window) {
275+
return (window in positioned_windows) && positioned_windows[window].modal;
276+
}
277+
278+
public bool is_system_modal_window (Meta.Window window) {
279+
var modal = is_itself_system_modal (window);
280+
window.foreach_ancestor ((ancestor) => {
281+
if (is_itself_system_modal (ancestor)) {
282+
modal = true;
283+
}
284+
285+
return !modal;
286+
});
287+
288+
return modal;
289+
}
290+
291+
public bool is_system_modal_dimmed (Meta.Window window) {
292+
return is_itself_system_modal (window) && positioned_windows[window].dim;
293+
}
294+
270295
//X11 only
271296
private void parse_mutter_hints (Meta.Window window) requires (!Meta.Util.is_wayland_compositor ()) {
272297
if (window.mutter_hints == null) {

src/Widgets/ModalGroup.vala

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
#if HAS_MUTTER45
32+
child_added.connect (on_child_added);
33+
child_removed.connect (on_child_removed);
34+
}
35+
36+
private void on_child_added (Clutter.Actor child) {
37+
if (child is Meta.WindowActor && shell_clients.is_system_modal_dimmed (child.meta_window)) {
38+
dimmed.add (child);
39+
}
40+
41+
if (get_n_children () == 1) {
42+
assert (modal_proxy == null);
43+
44+
visible = true;
45+
modal_proxy = wm.push_modal (this, false);
46+
}
47+
48+
if (dimmed.size == 1) {
49+
save_easing_state ();
50+
set_easing_duration (Utils.get_animation_duration (AnimationDuration.OPEN));
51+
background_color = { 0, 0, 0, 200 };
52+
restore_easing_state ();
53+
}
54+
}
55+
56+
private void on_child_removed (Clutter.Actor child) {
57+
dimmed.remove (child);
58+
59+
if (dimmed.size == 0) {
60+
save_easing_state ();
61+
set_easing_duration (Utils.get_animation_duration (AnimationDuration.CLOSE));
62+
background_color = { 0, 0, 0, 0 };
63+
restore_easing_state ();
64+
}
65+
66+
if (get_n_children () == 0) {
67+
wm.pop_modal (modal_proxy);
68+
modal_proxy = null;
69+
70+
var transition = get_transition ("background-color");
71+
if (transition != null) {
72+
transition.completed.connect (() => visible = false);
73+
} else {
74+
visible = false;
75+
}
76+
}
77+
#endif
78+
}
79+
}

src/WindowManager.vala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ namespace Gala {
5151

5252
private Clutter.Actor menu_group { get; set; }
5353

54+
/**
55+
* The group that contains all WindowActors that are system modal.
56+
* See {@link ShellClientsManager.is_system_modal_window}.
57+
*/
58+
public ModalGroup modal_group { get; private set; }
59+
5460
/**
5561
* {@inheritDoc}
5662
*/
@@ -224,6 +230,7 @@ namespace Gala {
224230
* +-- window overview
225231
* +-- shell group
226232
* +-- menu group
233+
* +-- modal group
227234
* +-- feedback group (e.g. DND icons)
228235
* +-- pointer locator
229236
* +-- dwell click timer
@@ -295,6 +302,10 @@ namespace Gala {
295302
menu_group = new Clutter.Actor ();
296303
ui_group.add_child (menu_group);
297304

305+
modal_group = new ModalGroup (this, ShellClientsManager.get_instance ());
306+
modal_group.add_constraint (new Clutter.BindConstraint (stage, ALL, 0));
307+
ui_group.add_child (modal_group);
308+
298309
var feedback_group = display.get_compositor ().get_feedback_group ();
299310
stage.remove_child (feedback_group);
300311
ui_group.add_child (feedback_group);
@@ -1010,6 +1021,12 @@ namespace Gala {
10101021

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

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)