Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions data/io.elementary.desktop.wm.shell
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -20,6 +21,7 @@ session-type=greeter

[Greeter]
args=io.elementary.greeter
wait-for-n-windows=1
session-type=greeter

[Greeter Settings Daemon]
Expand Down
1 change: 1 addition & 0 deletions lib/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace Gala {

public enum WindowGroup {
DESKTOP_SHELL,
LOCK_SCREEN_APPLICATION,
LOCK_SCREEN,
LOCK_SCREEN_SHELL,
MODAL,
Expand Down
10 changes: 10 additions & 0 deletions protocol/pantheon-desktop-shell-v1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
<request name="destroy" type="destructor"/>
</interface>

<interface name="io_elementary_pantheon_greeter_v1" version="1">
<request name="destroy" type="destructor"/>

<request name="init">
<description summary="init greeter">
Init Greeter
</description>
</request>
</interface>

<interface name="io_elementary_pantheon_extended_behavior_v1" version="1">
<request name="destroy" type="destructor"/>
<request name="set_keep_above">
Expand Down
12 changes: 12 additions & 0 deletions protocol/pantheon-desktop-shell.vapi
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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);
Expand All @@ -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);
Expand Down
76 changes: 76 additions & 0 deletions src/PantheonShell.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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");

Expand Down Expand Up @@ -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<unowned GLib.Object> (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;
Expand Down Expand Up @@ -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<GLib.Object> ();
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<GLib.Object> ();
ExtendedBehaviorSurface? eb_surface = wayland_surface.get_qdata (ExtendedBehaviorSurface.quark);
Expand Down Expand Up @@ -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<GreeterSurface> ();
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<ExtendedBehaviorSurface> ();
if (eb_surface.wayland_surface == null) {
Expand Down Expand Up @@ -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 ();
}
Expand Down
30 changes: 30 additions & 0 deletions src/ShellClients/GreeterWindow.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2026 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leo "lenemter" <lenemter@gmail.com>
*/

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;
}
}
2 changes: 1 addition & 1 deletion src/ShellClients/PanelWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

public class Gala.PanelWindow : ShellWindow, RootTarget {
public class Gala.PanelWindow : ShellWindow, RootTarget, WindowWithStartAnimation {
private static HashTable<Meta.Window, Meta.Strut?> window_struts = new HashTable<Meta.Window, Meta.Strut?> (null, null);

public Clutter.Actor? actor { get { return (Clutter.Actor) window.get_compositor_private (); } }
Expand Down
60 changes: 41 additions & 19 deletions src/ShellClients/ShellClientsManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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<Meta.Window, unowned WindowWithStartAnimation> windows_waiting_for_start = new GLib.HashTable<Meta.Window, unowned WindowWithStartAnimation> (null, null);
private GLib.HashTable<Meta.Window, PanelWindow> panel_windows = new GLib.HashTable<Meta.Window, PanelWindow> (null, null);
private GLib.HashTable<Meta.Window, GreeterWindow> greeter_windows = new GLib.HashTable<Meta.Window, GreeterWindow> (null, null);
private GLib.HashTable<Meta.Window, ExtendedBehaviorWindow> positioned_windows = new GLib.HashTable<Meta.Window, ExtendedBehaviorWindow> (null, null);
private GLib.HashTable<Meta.Window, MonitorLabelWindow> monitor_label_windows = new GLib.HashTable<Meta.Window, MonitorLabelWindow> (null, null);
private IBusCandidateWindow? ibus_candidate_window = null;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -217,32 +225,33 @@ 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);
} else {
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 ();
}
}

Expand Down Expand Up @@ -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);

Expand Down
10 changes: 10 additions & 0 deletions src/ShellClients/WindowWithStartAnimation.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2026 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leo "lenemter" <lenemter@gmail.com>
*/

public interface Gala.WindowWithStartAnimation {
public abstract void animate_start ();
}
Loading
Loading