Skip to content
Merged
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the installer type necessary? I don't see it used anywhere, and is also rolled into greeter type when is_greeter is called.

@leolost2605 leolost2605 Jun 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The greeter compositor launches different clients depending on whether it's a normal greeter session or a installer session (it doesn't launch the greeter, session manager, etc. in the installer session) that's why I added it here but I'm not familiar with the installer session and I haven't tested it 🤷

}

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";
}
}
17 changes: 17 additions & 0 deletions lib/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ namespace Gala {
MEDIA_KEYS
}

public enum WindowGroup {
DESKTOP_SHELL,
LOCK_SCREEN,
LOCK_SCREEN_SHELL,
MODAL,
OVERLAY,
}

/**
* A minimal class mostly used to identify your call to {@link WindowManager.push_modal} and used
* to end your modal mode again with {@link WindowManager.pop_modal}
Expand All @@ -46,6 +54,7 @@ namespace Gala {
public Clutter.Grab? grab { get; set; }

private ModalActions allowed_actions;
private WindowGroup[] allowed_window_groups;

public ModalProxy () {
}
Expand All @@ -57,6 +66,14 @@ namespace Gala {
public bool filter_action (ModalActions action) {
return !(action in allowed_actions);
}

public void allow_window_groups (WindowGroup[] window_groups) requires (grab == null) {
allowed_window_groups = window_groups;
}

public bool is_window_group_allowed (WindowGroup window_group) {
return window_group in allowed_window_groups;
}
}

public interface WindowManager : Meta.Plugin {
Expand Down
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/LockScreenManager.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.LockScreenManager : Object {
public LockScreen lock_screen { 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 LockScreenManager (LockScreen lock_screen) {
Object (lock_screen: lock_screen);
}

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

private void update_active () {
var active = manually_locked || SessionSettings.is_greeter ();
lock_screen.set_active.begin (active);
}
}
23 changes: 20 additions & 3 deletions src/ShellClients/ShellClientsManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,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 +193,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, LOCK_SCREEN_SHELL);
} else {
wm.override_window_group (window, DESKTOP_SHELL);
}

InternalUtils.wait_for_window_actor_visible (window, on_panel_ready);

Expand Down
68 changes: 68 additions & 0 deletions src/Widgets/LockScreen.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.LockScreen : Clutter.Actor {
private const WindowGroup[] ALLOWED_WINDOW_GROUPS = { LOCK_SCREEN, LOCK_SCREEN_SHELL, OVERLAY };

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 LockScreen (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);
modal_proxy.allow_window_groups (ALLOWED_WINDOW_GROUPS);
} else {
assert (modal_proxy != null);

wm.pop_modal (modal_proxy);
modal_proxy = null;
}
}
}
3 changes: 3 additions & 0 deletions src/Widgets/ModalGroup.vala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* instead to {@link window_group}.
*/
public class Gala.ModalGroup : Clutter.Actor {
private const WindowGroup[] ALLOWED_WINDOW_GROUPS = { MODAL, OVERLAY };

public WindowManager wm { private get; construct; }
public ShellClientsManager shell_clients { private get; construct; }

Expand Down Expand Up @@ -71,6 +73,7 @@ public class Gala.ModalGroup : Clutter.Actor {
visible = true;
modal_proxy = wm.push_modal (this, false);
modal_proxy.allow_actions (ZOOM | LOCATE_POINTER | SCREENSHOT | SCREENSHOT_AREA | SCREENSHOT_WINDOW);
modal_proxy.allow_window_groups (ALLOWED_WINDOW_GROUPS);
}

if (dimmed.size == 1) {
Expand Down
2 changes: 2 additions & 0 deletions src/Widgets/MultitaskingView/MultitaskingView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/
public class Gala.MultitaskingView : Root, RootTarget, ActivatableComponent {
public const int ANIMATION_DURATION = 250;
private const WindowGroup[] ALLOWED_WINDOW_GROUPS = { DESKTOP_SHELL, OVERLAY };

private GestureController workspaces_gesture_controller;
private GestureController multitasking_gesture_controller;
Expand Down Expand Up @@ -234,6 +235,7 @@ public class Gala.MultitaskingView : Root, RootTarget, ActivatableComponent {

modal_proxy = wm.push_modal (get_stage (), false);
modal_proxy.allow_actions (MULTITASKING_VIEW | SWITCH_WORKSPACE | ZOOM | LOCATE_POINTER | MEDIA_KEYS | SCREENSHOT | SCREENSHOT_AREA);
modal_proxy.allow_window_groups (ALLOWED_WINDOW_GROUPS);
} else if (action == MULTITASKING_VIEW) {
DragDropAction.cancel_all_by_id ("multitaskingview-window");
}
Expand Down
Loading
Loading