diff --git a/data/io.elementary.desktop.wm.shell b/data/io.elementary.desktop.wm.shell index 9f98f1c5c..d2a975b10 100644 --- a/data/io.elementary.desktop.wm.shell +++ b/data/io.elementary.desktop.wm.shell @@ -1,17 +1,18 @@ [io.elementary.wingpanel] launch-on-x=true args=io.elementary.wingpanel -wait-for-n-panels=1 +wait-for-n-windows=1 session-type=desktop [io.elementary.dock] launch-on-x=true args=io.elementary.dock -wait-for-n-panels=1 +wait-for-n-windows=1 session-type=desktop [Greeter wingpanel] args=io.elementary.wingpanel;-g +wait-for-n-windows=1 session-type=greeter [Greeter Session Manager] @@ -20,6 +21,7 @@ session-type=greeter [Greeter] args=io.elementary.greeter +wait-for-n-windows=1 session-type=greeter [Greeter Settings Daemon] diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala index 705df6bc5..bcc682326 100644 --- a/lib/WindowManager.vala +++ b/lib/WindowManager.vala @@ -40,6 +40,7 @@ namespace Gala { public enum WindowGroup { DESKTOP_SHELL, + LOCK_SCREEN_APPLICATION, LOCK_SCREEN, LOCK_SCREEN_SHELL, MODAL, diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml index 210d3a2f5..805548fe1 100644 --- a/protocol/pantheon-desktop-shell-v1.xml +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -136,6 +136,16 @@ + + + + + + Init Greeter + + + + diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi index ba95c891e..fded6c848 100644 --- a/protocol/pantheon-desktop-shell.vapi +++ b/protocol/pantheon-desktop-shell.vapi @@ -53,6 +53,14 @@ namespace Pantheon.Desktop { public Destroy destroy; } + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_greeter_v1_interface")] + public struct GreeterInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_greeter_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + public Init init; + } + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1_interface")] public struct ExtendedBehaviorInterface { [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_extended_behavior_v1_interface")] @@ -78,6 +86,8 @@ namespace Pantheon.Desktop { [CCode (has_target = false, has_typedef = false)] public delegate void GetWidget (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); [CCode (has_target = false, has_typedef = false)] + public delegate void GetGreeter (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] public delegate void GetExtendedBehavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); [CCode (has_target = false, has_typedef = false)] public delegate void GetGreeter (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); @@ -96,6 +106,8 @@ namespace Pantheon.Desktop { [CCode (has_target = false, has_typedef = false)] public delegate void RemoveBlur (Wl.Client client, Wl.Resource resource); [CCode (has_target = false, has_typedef = false)] + public delegate void Init (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource); [CCode (has_target = false, has_typedef = false)] public delegate void MakeCentered (Wl.Client client, Wl.Resource resource); diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala index 2d187916a..6b6be0b18 100644 --- a/src/PantheonShell.vala +++ b/src/PantheonShell.vala @@ -17,6 +17,7 @@ namespace Gala { private static Pantheon.Desktop.ShellInterface wayland_pantheon_shell_interface; private static Pantheon.Desktop.PanelInterface wayland_pantheon_panel_interface; private static Pantheon.Desktop.WidgetInterface wayland_pantheon_widget_interface; + private static Pantheon.Desktop.GreeterInterface wayland_pantheon_greeter_interface; private static Pantheon.Desktop.ExtendedBehaviorInterface wayland_pantheon_extended_behavior_interface; private static Pantheon.Desktop.GreeterInterface wayland_pantheon_greeter_interface; private static Wl.Global shell_global; @@ -50,6 +51,11 @@ namespace Gala { destroy_widget_surface, }; + wayland_pantheon_greeter_interface = { + destroy_greeter_surface, + init_greeter, + }; + wayland_pantheon_extended_behavior_interface = { destroy_extended_behavior_surface, set_keep_above, @@ -66,6 +72,7 @@ namespace Gala { PanelSurface.quark = GLib.Quark.from_string ("-gala-wayland-panel-surface-data"); WidgetSurface.quark = GLib.Quark.from_string ("-gala-wayland-widget-surface-data"); + GreeterSurface.quark = GLib.Quark.from_string ("-gala-wayland-greeter-surface-data"); ExtendedBehaviorSurface.quark = GLib.Quark.from_string ("-gala-wayland-extended-behavior-surface-data"); GreeterSurface.quark = GLib.Quark.from_string ("-gala-wayland-greeter-surface-data"); @@ -113,6 +120,25 @@ namespace Gala { } } + public class GreeterSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public GreeterSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~GreeterSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + public class ExtendedBehaviorSurface : GLib.Object { public static GLib.Quark quark = 0; public unowned GLib.Object? wayland_surface; @@ -213,6 +239,35 @@ namespace Gala { ); } + internal static void get_greeter (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + GreeterSurface? greeter_surface = wayland_surface.get_qdata (GreeterSurface.quark); + if (greeter_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_greeter already requested" + ); + return; + } + + greeter_surface = new GreeterSurface (wayland_surface); + unowned var greeter_resource = client.create_resource ( + ref Pantheon.Desktop.GreeterInterface.iface, + resource.get_version (), + output + ); + greeter_resource.set_implementation ( + &wayland_pantheon_greeter_interface, + greeter_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + GreeterSurface.quark, + greeter_surface, + (GLib.DestroyNotify) GreeterSurface.on_wayland_surface_disposed + ); + } + internal static void get_extended_behavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); ExtendedBehaviorSurface? eb_surface = wayland_surface.get_qdata (ExtendedBehaviorSurface.quark); @@ -404,6 +459,23 @@ namespace Gala { BlurManager.get_instance ().remove_blur (window); } + internal static void init_greeter (Wl.Client client, Wl.Resource resource) { + unowned GreeterSurface? greeter_surface = resource.get_user_data (); + if (greeter_surface.wayland_surface == null) { + warning ("Window tried to init greeter but wayland surface is null."); + return; + } + + Meta.Window? window; + greeter_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to init greeter but wayland surface had no associated window."); + return; + } + + ShellClientsManager.get_instance ().init_greeter (window); + } + internal static void set_keep_above (Wl.Client client, Wl.Resource resource) { unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); if (eb_surface.wayland_surface == null) { @@ -489,6 +561,10 @@ namespace Gala { resource.destroy (); } + internal static void destroy_greeter_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } + internal static void destroy_extended_behavior_surface (Wl.Client client, Wl.Resource resource) { resource.destroy (); } diff --git a/src/ShellClients/GreeterWindow.vala b/src/ShellClients/GreeterWindow.vala new file mode 100644 index 000000000..1e4269d2e --- /dev/null +++ b/src/ShellClients/GreeterWindow.vala @@ -0,0 +1,30 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leo "lenemter" + */ + +public class Gala.GreeterWindow : PositionedWindow, WindowWithStartAnimation { + + public GreeterWindow (Meta.Window window) { + Object (window: window); + } + + public void animate_start () { + InternalUtils.wait_for_window_actor (window, (window_actor) => { + window_actor.opacity = 0; + window_actor.save_easing_state (); + window_actor.set_easing_mode (Clutter.AnimationMode.EASE_IN); + window_actor.set_easing_duration (Utils.get_animation_duration (200u)); + window_actor.opacity = 255; + window_actor.restore_easing_state (); + }); + } + + protected override void get_window_position (Mtk.Rectangle window_rect, out int x, out int y) { + // We assume Greeter window is maximized + x = window_rect.x; + y = window_rect.y; + } +} diff --git a/src/ShellClients/PanelWindow.vala b/src/ShellClients/PanelWindow.vala index a80076704..9a3d75b33 100644 --- a/src/ShellClients/PanelWindow.vala +++ b/src/ShellClients/PanelWindow.vala @@ -5,7 +5,7 @@ * Authored by: Leonhard Kargl */ -public class Gala.PanelWindow : ShellWindow, RootTarget { +public class Gala.PanelWindow : ShellWindow, RootTarget, WindowWithStartAnimation { private static HashTable window_struts = new HashTable (null, null); public Clutter.Actor? actor { get { return (Clutter.Actor) window.get_compositor_private (); } } diff --git a/src/ShellClients/ShellClientsManager.vala b/src/ShellClients/ShellClientsManager.vala index 4f9f2d9fb..f8e6719dd 100644 --- a/src/ShellClients/ShellClientsManager.vala +++ b/src/ShellClients/ShellClientsManager.vala @@ -26,9 +26,11 @@ public class Gala.ShellClientsManager : Object, GestureTarget { private NotificationsClient notifications_client; private ManagedClient[] protocol_clients = {}; - private int starting_panels = 0; + private int starting_windows = 0; + private GLib.HashTable windows_waiting_for_start = new GLib.HashTable (null, null); private GLib.HashTable panel_windows = new GLib.HashTable (null, null); + private GLib.HashTable greeter_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 IBusCandidateWindow? ibus_candidate_window = null; @@ -113,7 +115,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget { } try { - starting_panels += key_file.get_integer (group, "wait-for-n-panels"); + starting_windows += key_file.get_integer (group, "wait-for-n-windows"); } catch (Error e) { warning ("Failed to check how many panels should be awaited, assuming 0: %s", e.message); } @@ -128,16 +130,22 @@ public class Gala.ShellClientsManager : Object, GestureTarget { } private void on_failsafe_timeout () { - if (starting_panels > 0) { - warning ("%d panels failed to start in time, showing the others", starting_panels); + if (starting_windows > 0) { + warning ("%d staring windows failed to start in time, showing the others", starting_windows); - starting_panels = 0; - foreach (var window in panel_windows.get_values ()) { - window.animate_start (); - } + animate_all_starting_windows (); } } + private void animate_all_starting_windows () { + starting_windows = 0; + foreach (var window in windows_waiting_for_start.get_values ()) { + window?.animate_start (); + } + + windows_waiting_for_start.remove_all (); + } + private void make_dock (Meta.Window window) { #if HAS_MUTTER49 window.set_type (Meta.WindowType.DOCK); @@ -217,6 +225,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget { // TODO: Return if requested by window that's not a trusted client? panel_windows[window] = new PanelWindow (wm, window, anchor); + windows_waiting_for_start[window] = panel_windows[window]; if (SessionSettings.is_greeter ()) { wm.override_window_group (window, LOCK_SCREEN_SHELL); @@ -224,25 +233,25 @@ public class Gala.ShellClientsManager : Object, GestureTarget { wm.override_window_group (window, DESKTOP_SHELL); } - InternalUtils.wait_for_window_actor_visible (window, on_panel_ready); + InternalUtils.wait_for_window_actor_visible (window, on_starting_window_ready); // connect_after so we make sure the PanelWindow can destroy its barriers and struts window.unmanaging.connect_after ((_window) => panel_windows.remove (_window)); } - private void on_panel_ready (Meta.WindowActor actor) { - if (starting_panels == 0) { - panel_windows[actor.meta_window].animate_start (); + private void on_starting_window_ready (Meta.WindowActor actor) { + if (starting_windows == 0) { + unowned var window = actor.meta_window; + windows_waiting_for_start[window]?.animate_start (); + windows_waiting_for_start.remove (window); return; } - starting_panels--; - assert (starting_panels >= 0); + starting_windows--; + assert (starting_windows >= 0); - if (starting_panels == 0) { - foreach (var window in panel_windows.get_values ()) { - window.animate_start (); - } + if (starting_windows == 0) { + animate_all_starting_windows (); } } @@ -273,13 +282,26 @@ public class Gala.ShellClientsManager : Object, GestureTarget { public void request_visible_in_multitasking_view (Meta.Window window) { if (!(window in panel_windows)) { - warning ("Set anchor for window before visible in mutltiasking view."); + warning ("Set anchor for window before visible in multitasking view."); return; } panel_windows[window].request_visible_in_multitasking_view (); } + public void init_greeter (Meta.Window window) { + make_desktop (window); + + greeter_windows[window] = new GreeterWindow (window); + windows_waiting_for_start[window] = greeter_windows[window]; + + wm.override_window_group (window, LOCK_SCREEN_APPLICATION); + + InternalUtils.wait_for_window_actor_visible (window, on_starting_window_ready); + + window.unmanaging.connect_after ((_window) => greeter_windows.remove (_window)); + } + public void make_centered (Meta.Window window) requires (!is_itself_shell_window (window)) { positioned_windows[window] = new ExtendedBehaviorWindow (window); diff --git a/src/ShellClients/WindowWithStartAnimation.vala b/src/ShellClients/WindowWithStartAnimation.vala new file mode 100644 index 000000000..22a921d19 --- /dev/null +++ b/src/ShellClients/WindowWithStartAnimation.vala @@ -0,0 +1,10 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leo "lenemter" + */ + +public interface Gala.WindowWithStartAnimation { + public abstract void animate_start (); +} diff --git a/src/Widgets/LockScreen.vala b/src/Widgets/LockScreen.vala index b7e9325fa..457f50471 100644 --- a/src/Widgets/LockScreen.vala +++ b/src/Widgets/LockScreen.vala @@ -6,10 +6,11 @@ */ public class Gala.LockScreen : Clutter.Actor { - private const WindowGroup[] ALLOWED_WINDOW_GROUPS = { LOCK_SCREEN, LOCK_SCREEN_SHELL, OVERLAY }; + private const WindowGroup[] ALLOWED_WINDOW_GROUPS = { LOCK_SCREEN_APPLICATION, LOCK_SCREEN, LOCK_SCREEN_SHELL, OVERLAY }; public WindowManager wm { get; construct; } + public Clutter.Actor lock_application_group { get; private set; } public Clutter.Actor window_group { get; private set; } public Clutter.Actor shell_group { get; private set; } @@ -24,10 +25,12 @@ public class Gala.LockScreen : Clutter.Actor { var background = new BackgroundContainer (wm.get_display ()); background.add_effect (new BlurEffect (background, 18)); + lock_application_group = new Clutter.Actor (); window_group = new Clutter.Actor (); shell_group = new Clutter.Actor (); add_child (background); + add_child (lock_application_group); add_child (window_group); add_child (shell_group); diff --git a/src/WindowManager.vala b/src/WindowManager.vala index bb00be4b5..5712e30f0 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -1086,6 +1086,7 @@ namespace Gala { private Clutter.Actor get_window_group_actor (WindowGroup group) { switch (group) { case DESKTOP_SHELL: return shell_group; + case LOCK_SCREEN_APPLICATION: return lock_screen.lock_application_group; case LOCK_SCREEN: return lock_screen.window_group; case LOCK_SCREEN_SHELL: return lock_screen.shell_group; case MODAL: return modal_group.window_group; diff --git a/src/meson.build b/src/meson.build index f4e36e4c6..765912326 100644 --- a/src/meson.build +++ b/src/meson.build @@ -46,6 +46,7 @@ gala_bin_sources = files( 'Misc/WorkspaceManager.vala', 'Misc/Zoom.vala', 'ShellClients/ExtendedBehaviorWindow.vala', + 'ShellClients/GreeterWindow.vala', 'ShellClients/HideTracker.vala', 'ShellClients/IBusCandidateWindow.vala', 'ShellClients/ManagedClient.vala', @@ -55,6 +56,7 @@ gala_bin_sources = files( 'ShellClients/PositionedWindow.vala', 'ShellClients/ShellClientsManager.vala', 'ShellClients/ShellWindow.vala', + 'ShellClients/WindowWithStartAnimation.vala', 'Widgets/DwellClickTimer.vala', 'Widgets/LockScreen.vala', 'Widgets/MultitaskingView/MonitorClone.vala',