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',