From 1e9d9c77fcee92350d550a95e591054658f04f0e Mon Sep 17 00:00:00 2001 From: Jason Perlow Date: Mon, 12 Oct 2026 21:44:43 -0400 Subject: [PATCH] fix(session): start one lockscreen at a time Every lock_screen() call spawned another singularity-lockscreen, so an idle lock and an org.freedesktop.ScreenSaver.Lock request arriving together ran two lock clients at once. Keep the spawned client and ignore further requests while it runs, and expose that state as the read-only locked property. Assisted-by: Claude Code:claude-opus-5-5 AI-Scope: tracked the spawned lockscreen subprocess in SessionManager.lock_screen() and added a power_test case for repeated lock requests. (cherry picked from commit 1b0266c618ef7f6af1e5a3cb8a2ac6a3fc1eba70) --- src/system/session_manager.vala | 18 +++++++++++++++++- tests/power_test.vala | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/system/session_manager.vala b/src/system/session_manager.vala index ebb74b7..164c12a 100644 --- a/src/system/session_manager.vala +++ b/src/system/session_manager.vala @@ -25,12 +25,28 @@ namespace Singularity { */ public signal void session_ending(); + private Subprocess? lockscreen_process = null; + + public bool locked { get; private set; default = false; } + public void lock_screen() { + if (lockscreen_process != null) return; + // 1. Native singularity-lockscreen (ext-session-lock-v1 + PAM) string? lockscreen = find_singularity_binary("singularity-lockscreen"); if (lockscreen != null) { try { - Process.spawn_command_line_async(lockscreen); + var process = new Subprocess(SubprocessFlags.NONE, lockscreen); + lockscreen_process = process; + locked = true; + process.wait_async.begin(null, (obj, res) => { + try { + process.wait_async.end(res); + } catch (Error e) { } + if (lockscreen_process != process) return; + lockscreen_process = null; + locked = false; + }); return; } catch (Error e) { warning("singularity-lockscreen failed: %s", e.message); diff --git a/tests/power_test.vala b/tests/power_test.vala index 52dc591..d561056 100644 --- a/tests/power_test.vala +++ b/tests/power_test.vala @@ -227,6 +227,33 @@ private void test_charge_limit() { assert(replaced.charge_limit_available); } +private void test_lock_screen_single_instance() { + string count = Path.build_filename(fixture_root, "lock-starts"); + string release = Path.build_filename(fixture_root, "lock-release"); + script("singularity-lockscreen", + "echo x >> '%s'\nwhile [ ! -e '%s' ]; do sleep 0.05; done\n".printf(count, release)); + Environment.set_variable("PATH", + Path.build_filename(fixture_root, "bin") + ":" + (Environment.get_variable("PATH") ?? "/usr/bin:/bin"), true); + + var session = Singularity.SessionManager.get_default(); + session.lock_screen(); + session.lock_screen(); + assert(session.locked); + wait_for(() => read_file(count) != ""); + assert(read_file(count) == "x"); + + write_file(release, ""); + wait_for(() => !session.locked); + assert(!session.locked); + FileUtils.remove(release); + + session.lock_screen(); + wait_for(() => read_file(count) == "x\nx"); + assert(read_file(count) == "x\nx"); + write_file(release, ""); + wait_for(() => !session.locked); +} + public static int main(string[] args) { Test.init(ref args); try { @@ -238,5 +265,6 @@ public static int main(string[] args) { Test.add_func("/power/command-suspend", test_command_suspend); Test.add_func("/power/battery-scan", test_battery_scan); Test.add_func("/power/charge-limit", test_charge_limit); + Test.add_func("/power/lock-screen-single-instance", test_lock_screen_single_instance); return Test.run(); }