Skip to content
Open
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
20 changes: 20 additions & 0 deletions data/io.elementary.desktop.wm.shell
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
[io.elementary.wingpanel]
launch-on-x=true
args=io.elementary.wingpanel
wait-for-n-panels=1
session-type=desktop

[io.elementary.dock]
launch-on-x=true
args=io.elementary.dock
wait-for-n-panels=1
session-type=desktop

[Greeter wingpanel]
args=io.elementary.wingpanel;-g
session-type=greeter

[Greeter Session Manager]
args=io.elementary.greeter-session-manager
session-type=greeter

[Greeter]
args=io.elementary.greeter
session-type=greeter

[Greeter Settings Daemon]
args=io.elementary.settings-daemon
session-type=greeter
56 changes: 56 additions & 0 deletions lib/SessionSettings.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2026 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

namespace Gala.SessionSettings {
private enum SessionType {
DESKTOP,
GREETER,
INSTALLER;
}

private static SessionType? session_type = null;

private static SessionType get_session_type () {
if (session_type == null) {
var session_type_str = Environment.get_variable ("GALA_SESSION_TYPE") ?? "desktop";
switch (session_type_str) {
case "desktop":
session_type = DESKTOP;
break;
case "greeter":
session_type = GREETER;
break;
case "installer":
session_type = INSTALLER;
break;
default:
warning ("Unknown session type: %s", session_type_str);
session_type = DESKTOP;
break;
}
}

return session_type;
}

public bool is_greeter () {
return get_session_type () != DESKTOP;
}

public string get_shell_clients_type () {
switch (get_session_type ()) {
case DESKTOP:
return "desktop";
case GREETER:
return "greeter";
case INSTALLER:
return "installer";
}

return "desktop";
}
}
1 change: 1 addition & 0 deletions lib/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gala_lib_sources = files(
'Constants.vala',
'DragDropAction.vala',
'Plugin.vala',
'SessionSettings.vala',
'Utils.vala',
'WindowManager.vala',
'AppSystem/App.vala',
Expand Down
29 changes: 29 additions & 0 deletions src/GreeterManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2026 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

public class Gala.GreeterManager : Object {
public Greeter greeter { private get; construct; }

/**
* To be set by the session locker in the future when we have an in session lock screen
*/
public bool manually_locked { get; set; default = false; }

public GreeterManager (Greeter greeter) {
Object (greeter: greeter);
}

construct {
notify["manually-locked"].connect (update_active);
update_active ();
}

private void update_active () {
var active = manually_locked || SessionSettings.is_greeter ();
greeter.set_active.begin (active);
}
}
36 changes: 32 additions & 4 deletions src/ShellClients/ShellClientsManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
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;
private GenericSet<Meta.Window> greeter_windows = new GenericSet<Meta.Window> (null, null);

private ShellClientsManager (WindowManagerGala wm, InputMethod im) {
Object (wm: wm, im: im);
Expand All @@ -50,6 +51,10 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
}

Timeout.add_seconds_once (5, on_failsafe_timeout);

if (SessionSettings.is_greeter ()) {
wm.get_display ().window_created.connect (make_greeter);
}
}

private async void start_clients () {
Expand Down Expand Up @@ -103,15 +108,28 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
}
}

try {
var type = key_file.get_string (group, "session-type");
if (type != SessionSettings.get_shell_clients_type ()) {
continue;
}
} catch (Error e) {
warning ("Failed to check session type for client %s, assuming it should be launched: %s", group, e.message);
}

try {
starting_panels += key_file.get_integer (group, "wait-for-n-panels");
} catch (Error e) {
warning ("Failed to check how many panels should be awaited, assuming 0: %s", e.message);
}

try {
var args = key_file.get_string_list (group, "args");
protocol_clients += new ManagedClient (wm.get_display (), args);
} catch (Error e) {
warning ("Failed to load launch args for client %s: %s", group, e.message);
}
}

starting_panels = protocol_clients.length;
}

private void on_failsafe_timeout () {
Expand Down Expand Up @@ -180,7 +198,11 @@ public class Gala.ShellClientsManager : Object, GestureTarget {

panel_windows[window] = new PanelWindow (wm, window, anchor);

wm.override_window_group (window, DESKTOP_SHELL);
if (SessionSettings.is_greeter ()) {
wm.override_window_group (window, GREETER_SHELL);
} else {
wm.override_window_group (window, DESKTOP_SHELL);
}

InternalUtils.wait_for_window_actor_visible (window, on_panel_ready);

Expand Down Expand Up @@ -273,6 +295,11 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
window.unmanaged.connect_after (() => ibus_candidate_window = null);
}

public void make_greeter (Meta.Window window) {
wm.override_window_group (window, GREETER);
greeter_windows.add (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);
Expand All @@ -289,7 +316,8 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
(window in panel_windows) ||
(window in monitor_label_windows) ||
NotificationStack.is_notification (window) ||
window == ibus_candidate_window?.window
window == ibus_candidate_window?.window ||
(window in greeter_windows)
);
}

Expand Down
65 changes: 65 additions & 0 deletions src/Widgets/Greeter.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2026 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

public class Gala.Greeter : Clutter.Actor {
public WindowManager wm { get; construct; }

public Clutter.Actor window_group { get; private set; }
public Clutter.Actor shell_group { get; private set; }

private bool active;
private ModalProxy? modal_proxy;

public Greeter (WindowManager wm) {
Object (wm: wm);
}

construct {
var background = new BackgroundContainer (wm.get_display ());
background.add_effect (new BlurEffect (background, 18));

window_group = new Clutter.Actor ();
shell_group = new Clutter.Actor ();

add_child (background);
add_child (window_group);
add_child (shell_group);

reactive = true;
visible = true;

active = true;
update_modal ();
}

public async void set_active (bool active) {
if (this.active == active) {
return;
}

this.active = active;
update_modal ();

/* We can and should add a transition here */

visible = active;
}

private void update_modal () {
if (active) {
assert (modal_proxy == null);

modal_proxy = wm.push_modal (this, false);
modal_proxy.allow_actions (MEDIA_KEYS | ZOOM | LOCATE_POINTER);
} else {
assert (modal_proxy != null);

wm.pop_modal (modal_proxy);
modal_proxy = null;
}
}
}
19 changes: 18 additions & 1 deletion src/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ namespace Gala {
public class WindowManagerGala : Meta.Plugin, WindowManager {
public enum WindowGroup {
DESKTOP_SHELL,
GREETER,
GREETER_SHELL,
MODAL,
OVERLAY,
}
Expand Down Expand Up @@ -57,6 +59,8 @@ namespace Gala {

private Clutter.Actor menu_group { get; set; }

private Greeter greeter;

/**
* The group that contains all WindowActors that are system modal.
* See {@link ShellClientsManager.is_system_modal_window}.
Expand Down Expand Up @@ -116,6 +120,8 @@ namespace Gala {

private NotificationStack notification_stack;

private GreeterManager greeter_manager;

private Gee.LinkedList<ModalProxy> modal_stack = new Gee.LinkedList<ModalProxy> ();

private Gee.HashSet<Meta.WindowActor> minimizing = new Gee.HashSet<Meta.WindowActor> ();
Expand Down Expand Up @@ -245,8 +251,11 @@ namespace Gala {
* +-- multitasking view
* +-- window switcher
* +-- window overview
* +-- shell group
* +-- desktop shell group
* +-- menu group
* +-- greeter // NOTE: Everything below greeter can be accessed without authentication
* +---- window group
* +---- shell group
* +-- modal group
* +-- overlay group (e.g. ibus popup, osk, etc.)
* +-- feedback group (e.g. DND icons)
Expand Down Expand Up @@ -316,6 +325,12 @@ namespace Gala {
menu_group = new Clutter.Actor ();
ui_group.add_child (menu_group);

greeter = new Greeter (this);
greeter.add_constraint (new Clutter.BindConstraint (stage, SIZE, 0));
ui_group.add_child (greeter);

greeter_manager = new GreeterManager (greeter);

modal_group = new ModalGroup (this, ShellClientsManager.get_instance ());
modal_group.add_constraint (new Clutter.BindConstraint (stage, SIZE, 0));
ui_group.add_child (modal_group);
Expand Down Expand Up @@ -1073,6 +1088,8 @@ namespace Gala {
private Clutter.Actor get_window_group_actor (WindowGroup group) {
switch (group) {
case DESKTOP_SHELL: return shell_group;
case GREETER: return greeter.window_group;
case GREETER_SHELL: return greeter.shell_group;
case MODAL: return modal_group.window_group;
case OVERLAY: return overlay_group;
default: assert_not_reached ();
Expand Down
2 changes: 2 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ gala_bin_sources = files(
'DBusAccelerator.vala',
'DaemonManager.vala',
'DesktopIntegration.vala',
'GreeterManager.vala',
'InputMethod.vala',
'InternalUtils.vala',
'KeyboardManager.vala',
Expand Down Expand Up @@ -55,6 +56,7 @@ gala_bin_sources = files(
'ShellClients/ShellClientsManager.vala',
'ShellClients/ShellWindow.vala',
'Widgets/DwellClickTimer.vala',
'Widgets/Greeter.vala',
'Widgets/MultitaskingView/MonitorClone.vala',
'Widgets/MultitaskingView/MultitaskingView.vala',
'Widgets/MultitaskingView/StaticWindowClone.vala',
Expand Down