feat: open gtklock from outside once it can
This commit is contained in:
1 parent
c228291520
commit
5699158bb1
9 files changed
+61
-19
No files matched your search
@@ -83,8 +83,9 @@ switches it on; right after installing, log out and back in once.
|
||||
On Hyprland and Niri the lock screen is a program of your choice, and only it
|
||||
can open itself. So face-unlock goes into its password check, as with sudo:
|
||||
press Enter on the empty password field to scan. hyprlock and swaylock also
|
||||
scan by themselves when you come back. Those lock screens cover the bubble,
|
||||
which shows the tick once they are gone.
|
||||
scan by themselves when you come back, and so does gtklock after 4.0.0 (the
|
||||
first to open from outside). Those lock screens cover the bubble, which shows
|
||||
the tick once they are gone. gtklock shows face unlock's messages.
|
||||
|
||||
For the bubble on the lock screen, use face-unlock's own: `face-unlock lock`.
|
||||
It shows the wallpaper of your desktop (from swaybg, awww, hyprpaper or
|
||||
|
||||
+10
-6
@@ -201,8 +201,9 @@ does not start a scan with every key.
|
||||
|
||||
When the face matches, the agent unlocks the session the way its lock screen
|
||||
wants it: through logind, as *loginctl unlock-session* does, on Plasma and
|
||||
GNOME, by itself on its own lock screen, and with SIGUSR1 for hyprlock and
|
||||
swaylock. Any other lock screen opens itself, through PAM (see *DESKTOPS*).
|
||||
GNOME, by itself on its own lock screen, and with SIGUSR1 for hyprlock,
|
||||
swaylock and gtklock after 4.0.0. Any other lock screen opens itself, through
|
||||
PAM (see *DESKTOPS*).
|
||||
|
||||
Unlocked through logind, Plasma's lock screen cuts off its own password prompt
|
||||
and counts that as a wrong password. After a face unlock the daemon resets the
|
||||
@@ -241,10 +242,13 @@ so does the bubble, above the lock screen too.
|
||||
open itself. hyprlock asks PAM the moment it starts; that first time
|
||||
is skipped, so a screen locked on purpose does not open again at once.
|
||||
|
||||
hyprlock and swaylock can also be opened from outside: the agent finds
|
||||
them among the user's processes (niri also sets *LockedHint*), scans
|
||||
when somebody comes back, and opens them with SIGUSR1. Those lock
|
||||
screens cover the bubble, which shows the tick once they are gone.
|
||||
hyprlock, swaylock and gtklock (after 4.0.0) can also be opened from
|
||||
outside: the agent finds them among the user's processes (niri also
|
||||
sets *LockedHint*), scans when somebody comes back, and opens them with
|
||||
SIGUSR1. It only does so once the lock screen handles SIGUSR1: before
|
||||
it has locked, the signal would kill it. Those lock screens cover the
|
||||
bubble, which shows the tick once they are gone. gtklock shows the
|
||||
messages of the PAM module.
|
||||
|
||||
For the bubble on the lock screen there is face-unlock's own: *lock*.
|
||||
It shows the time, a password field (PAM service _face-unlock-lock_
|
||||
|
||||
@@ -42,6 +42,14 @@ LockController::LockController(BubbleController *bubble, UserConfig *config, Ses
|
||||
});
|
||||
connect(&m_input, &InputWatcher::input, this, &LockController::onResume);
|
||||
connect(&m_lock, &LockWatcher::lockedChanged, this, &LockController::onLockedChanged);
|
||||
// A lock screen that is only starting cannot be opened yet: the scan
|
||||
// that was due when it locked follows once it can.
|
||||
connect(&m_lock, &LockWatcher::unlockable, this, [this] {
|
||||
if (m_lockScanDue) {
|
||||
m_lockScanDue = false;
|
||||
startScan(QStringLiteral("lock"));
|
||||
}
|
||||
});
|
||||
|
||||
QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"),
|
||||
QStringLiteral("/org/freedesktop/login1"),
|
||||
@@ -58,6 +66,7 @@ void LockController::onLockedChanged(bool locked)
|
||||
}
|
||||
if (!locked) {
|
||||
m_locked = false;
|
||||
m_lockScanDue = false;
|
||||
m_armTimer.stop();
|
||||
m_input.stop();
|
||||
if (m_scan) {
|
||||
@@ -90,6 +99,7 @@ void LockController::onLockedChanged(bool locked)
|
||||
|
||||
if (m_config->scanOnLock()) {
|
||||
QTimer::singleShot(400, this, [this] {
|
||||
m_lockScanDue = m_locked && !m_lock.canUnlock();
|
||||
startScan(QStringLiteral("lock"));
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -59,6 +59,8 @@ private:
|
||||
LockScreenController *m_screen;
|
||||
bool m_locked = false;
|
||||
bool m_stopped = false;
|
||||
// Scan right after locking, once the lock screen can be opened.
|
||||
bool m_lockScanDue = false;
|
||||
QElapsedTimer m_lockedFor;
|
||||
QPointer<DaemonRequest> m_scan;
|
||||
QTimer m_armTimer;
|
||||
|
||||
+13
-4
@@ -5,6 +5,7 @@
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -14,7 +15,7 @@ namespace
|
||||
{
|
||||
bool isLocker(const QByteArray &name)
|
||||
{
|
||||
return name == "hyprlock" || name == "swaylock";
|
||||
return name == "hyprlock" || name == "swaylock" || name == "gtklock";
|
||||
}
|
||||
|
||||
QByteArray readFile(const QString &path)
|
||||
@@ -33,9 +34,9 @@ pid_t parentOf(pid_t pid)
|
||||
|
||||
// The lock screens of this user on this display. Another session of the same
|
||||
// user has a display of its own, and its lock screen is left alone. A child
|
||||
// of a lock screen (swaylock checks the password in one) is left out: killed
|
||||
// by the signal before its parent unlocks, it would take the parent down
|
||||
// with it, and the screen would stay locked.
|
||||
// of a lock screen (swaylock and gtklock check the password in one) is left
|
||||
// out: killed by the signal before its parent unlocks, it would take the
|
||||
// parent down with it, and the screen would stay locked.
|
||||
QList<pid_t> findAll()
|
||||
{
|
||||
QList<pid_t> found;
|
||||
@@ -104,6 +105,14 @@ QList<pid_t> Lockers::find(const QByteArray &name)
|
||||
return found;
|
||||
}
|
||||
|
||||
bool Lockers::ready(int sig)
|
||||
{
|
||||
const QList<pid_t> lockers = find();
|
||||
return std::any_of(lockers.cbegin(), lockers.cend(), [sig](pid_t pid) {
|
||||
return catches(pid, sig);
|
||||
});
|
||||
}
|
||||
|
||||
int Lockers::signal(int sig, const QByteArray &name)
|
||||
{
|
||||
int waiting = 0;
|
||||
|
||||
+4
-1
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// The lock screens that are programs of their own and take signals from
|
||||
// outside: hyprlock and swaylock open on SIGUSR1.
|
||||
// outside: hyprlock, swaylock and gtklock open on SIGUSR1.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -19,4 +19,7 @@ QList<pid_t> find(const QByteArray &name = {});
|
||||
// that, the signal kills it, and the compositor keeps the screen locked with
|
||||
// nobody to open it.
|
||||
int signal(int sig, const QByteArray &name = {});
|
||||
// Whether one of them handles sig: gtklock only opens on SIGUSR1 since 2025,
|
||||
// and none does before it has locked.
|
||||
bool ready(int sig);
|
||||
}
|
||||
@@ -160,7 +160,13 @@ void LockWatcher::onSessionProperties(const QString &interface, const QVariantMa
|
||||
void LockWatcher::pollLockers()
|
||||
{
|
||||
m_lockerRunning = !Lockers::find().isEmpty();
|
||||
const bool ready = Lockers::ready(SIGUSR1);
|
||||
const bool became = ready && !m_lockerReady;
|
||||
m_lockerReady = ready;
|
||||
update();
|
||||
if (became) {
|
||||
Q_EMIT unlockable();
|
||||
}
|
||||
}
|
||||
|
||||
void LockWatcher::setOwnLock(SessionLock *lock)
|
||||
@@ -171,7 +177,7 @@ void LockWatcher::setOwnLock(SessionLock *lock)
|
||||
|
||||
bool LockWatcher::canUnlock() const
|
||||
{
|
||||
return (m_own && m_own->isLocked()) || !m_ownLockers || m_lockerRunning;
|
||||
return (m_own && m_own->isLocked()) || !m_ownLockers || m_lockerReady;
|
||||
}
|
||||
|
||||
void LockWatcher::update()
|
||||
|
||||
+10
-5
@@ -7,11 +7,12 @@
|
||||
// GNOME logind's LockedHint. Unlocked through logind.
|
||||
// Niri logind's LockedHint, which niri sets for any lock screen.
|
||||
// Hyprland sets no LockedHint, so the lock screen is looked for among this
|
||||
// user's processes (hyprlock, swaylock) once a second. Only on
|
||||
// compositors where the lock screen is a program of its own
|
||||
// (ext-session-lock).
|
||||
// user's processes (hyprlock, swaylock, gtklock) once a second.
|
||||
// Only on compositors where the lock screen is a program of its
|
||||
// own (ext-session-lock).
|
||||
//
|
||||
// hyprlock and swaylock do not listen to logind. They unlock on SIGUSR1.
|
||||
// hyprlock, swaylock and gtklock do not listen to logind. They unlock on
|
||||
// SIGUSR1.
|
||||
// face-unlock's own lock screen (SessionLock) is simply told to. Any other
|
||||
// lock screen of that kind only opens itself: it gets the face through the
|
||||
// PAM module in its own stack (see src/lib/pam.sh), when Enter is pressed.
|
||||
@@ -41,13 +42,16 @@ public:
|
||||
return m_locked;
|
||||
}
|
||||
// Whether unlock() can open the lock screen that is up: always where
|
||||
// logind unlocks (Plasma, GNOME), only hyprlock and swaylock elsewhere.
|
||||
// logind unlocks (Plasma, GNOME), elsewhere only hyprlock, swaylock and
|
||||
// gtklock, once they take SIGUSR1.
|
||||
bool canUnlock() const;
|
||||
void unlock();
|
||||
void setOwnLock(SessionLock *lock);
|
||||
|
||||
Q_SIGNALS:
|
||||
void lockedChanged(bool locked);
|
||||
// canUnlock() came true: the lock screen that is up now takes SIGUSR1.
|
||||
void unlockable();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onScreenSaver(bool active);
|
||||
@@ -63,6 +67,7 @@ private:
|
||||
bool m_screenSaver = false;
|
||||
bool m_lockedHint = false;
|
||||
bool m_lockerRunning = false;
|
||||
bool m_lockerReady = false;
|
||||
bool m_locked = false;
|
||||
// The compositor's lock screen is a program of its own.
|
||||
bool m_ownLockers = false;
|
||||
|
||||
@@ -95,6 +95,7 @@ int main(int, char **argv)
|
||||
pid_t pid = fakeHyprlock(false, &from);
|
||||
check(Lockers::find("hyprlock") == QList<pid_t>{pid}, "finds the lock screen on this display");
|
||||
check(Lockers::find("swaylock").isEmpty(), "and only by that name");
|
||||
check(!Lockers::ready(SIGUSR2), "not ready while it does not handle the signal");
|
||||
check(Lockers::signal(SIGUSR2, "hyprlock") == 1, "one not ready yet");
|
||||
usleep(200 * 1000);
|
||||
check(alive(pid), "and it was left alone, alive");
|
||||
@@ -103,6 +104,7 @@ int main(int, char **argv)
|
||||
close(from);
|
||||
|
||||
pid = fakeHyprlock(true, &from);
|
||||
check(Lockers::ready(SIGUSR2), "ready once it does");
|
||||
check(Lockers::signal(SIGUSR2, "hyprlock") == 0, "a ready one gets it");
|
||||
check(next(from) == 's', "and handles it");
|
||||
check(alive(pid), "and lives on");
|
||||
|
||||
Reference in new issue
Block a user