diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml index fcd0f1758..eaf56cd6a 100644 --- a/protocol/pantheon-desktop-shell-v1.xml +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -151,5 +151,14 @@ by the compositor. + + + + Request to make the surface a monitor label for the given monitor. The surface will be placed + in the top left corner of the monitor and will be kept above other surfaces. + + + + diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi index 778d1ca42..358582049 100644 --- a/protocol/pantheon-desktop-shell.vapi +++ b/protocol/pantheon-desktop-shell.vapi @@ -61,6 +61,7 @@ namespace Pantheon.Desktop { public SetKeepAbove set_keep_above; public MakeCentered make_centered; public Focus focus; + public MakeMonitorLabel make_monitor_label; } [CCode (has_target = false, has_typedef = false)] @@ -88,5 +89,7 @@ namespace Pantheon.Desktop { [CCode (has_target = false, has_typedef = false)] public delegate void MakeCentered (Wl.Client client, Wl.Resource resource); [CCode (has_target = false, has_typedef = false)] + public delegate void MakeMonitorLabel (Wl.Client client, Wl.Resource resource, int monitor_index); + [CCode (has_target = false, has_typedef = false)] public delegate void Destroy (Wl.Client client, Wl.Resource resource); } diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala index 936f6eb57..50af93671 100644 --- a/src/PantheonShell.vala +++ b/src/PantheonShell.vala @@ -53,6 +53,7 @@ namespace Gala { set_keep_above, make_centered, focus_extended_behavior, + make_monitor_label, }; PanelSurface.quark = GLib.Quark.from_string ("-gala-wayland-panel-surface-data"); @@ -376,6 +377,21 @@ namespace Gala { ShellClientsManager.get_instance ().make_centered (window); } + internal static void make_monitor_label (Wl.Client client, Wl.Resource resource, int monitor_index) { + unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); + if (eb_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + eb_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + ShellClientsManager.get_instance ().make_monitor_label (window, monitor_index); + } + internal static void destroy_panel_surface (Wl.Client client, Wl.Resource resource) { resource.destroy (); } diff --git a/src/ShellClients/MonitorLabelWindow.vala b/src/ShellClients/MonitorLabelWindow.vala new file mode 100644 index 000000000..1ad53f5c6 --- /dev/null +++ b/src/ShellClients/MonitorLabelWindow.vala @@ -0,0 +1,23 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.MonitorLabelWindow : PositionedWindow { + private const int MARGIN = 24; + + public int monitor_index { get; construct; } + + public MonitorLabelWindow (Meta.Window window, int monitor_index) { + Object (window: window, monitor_index: monitor_index); + } + + protected override void get_window_position (Mtk.Rectangle window_rect, out int x, out int y) { + var monitor_rect = window.display.get_monitor_geometry (monitor_index); + + x = monitor_rect.x + MARGIN; + y = monitor_rect.y + MARGIN; + } +} diff --git a/src/ShellClients/ShellClientsManager.vala b/src/ShellClients/ShellClientsManager.vala index 988ba69dd..78dd92f73 100644 --- a/src/ShellClients/ShellClientsManager.vala +++ b/src/ShellClients/ShellClientsManager.vala @@ -29,6 +29,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget { private GLib.HashTable panel_windows = new GLib.HashTable (null, null); private GLib.HashTable positioned_windows = new GLib.HashTable (null, null); + private GLib.HashTable monitor_label_windows = new GLib.HashTable (null, null); private ShellClientsManager (WindowManager wm) { Object (wm: wm); @@ -240,6 +241,18 @@ public class Gala.ShellClientsManager : Object, GestureTarget { window.unmanaging.connect_after ((_window) => positioned_windows.remove (_window)); } + public void make_monitor_label (Meta.Window window, int monitor_index) requires (!is_itself_positioned (window)) { + if (monitor_index < 0 || monitor_index > wm.get_display ().get_n_monitors ()) { + warning ("Invalid monitor index provided: %d", monitor_index); + return; + } + + monitor_label_windows[window] = new MonitorLabelWindow (window, monitor_index); + + // connect_after so we make sure that any queued move is unqueued + window.unmanaging.connect_after ((_window) => monitor_label_windows.remove (_window)); + } + public void propagate (UpdateType update_type, GestureAction action, double progress) { foreach (var window in positioned_windows.get_values ()) { window.propagate (update_type, action, progress); @@ -251,7 +264,8 @@ public class Gala.ShellClientsManager : Object, GestureTarget { } public bool is_itself_positioned (Meta.Window window) { - return (window in positioned_windows) || (window in panel_windows) || NotificationStack.is_notification (window); + return (window in positioned_windows) || (window in panel_windows) || + (window in monitor_label_windows) || NotificationStack.is_notification (window); } public bool is_positioned_window (Meta.Window window) { @@ -350,6 +364,15 @@ public class Gala.ShellClientsManager : Object, GestureTarget { set_restore_previous_x11_region (window); break; + case "monitor-label": + int parsed; + if (int.try_parse (val, out parsed)) { + make_monitor_label (window, parsed); + } else { + warning ("Failed to parse %s as monitor label", val); + } + break; + default: break; } diff --git a/src/meson.build b/src/meson.build index d0503f355..66208077e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -45,6 +45,7 @@ gala_bin_sources = files( 'ShellClients/ExtendedBehaviorWindow.vala', 'ShellClients/HideTracker.vala', 'ShellClients/ManagedClient.vala', + 'ShellClients/MonitorLabelWindow.vala', 'ShellClients/NotificationsClient.vala', 'ShellClients/PanelWindow.vala', 'ShellClients/PositionedWindow.vala',