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
|
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:
|
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
|
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,
|
scan by themselves when you come back, and so does gtklock after 4.0.0 (the
|
||||||
which shows the tick once they are gone.
|
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`.
|
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
|
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
|
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
|
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
|
GNOME, by itself on its own lock screen, and with SIGUSR1 for hyprlock,
|
||||||
swaylock. Any other lock screen opens itself, through PAM (see *DESKTOPS*).
|
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
|
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
|
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
|
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.
|
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
|
hyprlock, swaylock and gtklock (after 4.0.0) can also be opened from
|
||||||
them among the user's processes (niri also sets *LockedHint*), scans
|
outside: the agent finds them among the user's processes (niri also
|
||||||
when somebody comes back, and opens them with SIGUSR1. Those lock
|
sets *LockedHint*), scans when somebody comes back, and opens them with
|
||||||
screens cover the bubble, which shows the tick once they are gone.
|
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*.
|
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_
|
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_input, &InputWatcher::input, this, &LockController::onResume);
|
||||||
connect(&m_lock, &LockWatcher::lockedChanged, this, &LockController::onLockedChanged);
|
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"),
|
QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"),
|
||||||
QStringLiteral("/org/freedesktop/login1"),
|
QStringLiteral("/org/freedesktop/login1"),
|
||||||
@@ -58,6 +66,7 @@ void LockController::onLockedChanged(bool locked)
|
|||||||
}
|
}
|
||||||
if (!locked) {
|
if (!locked) {
|
||||||
m_locked = false;
|
m_locked = false;
|
||||||
|
m_lockScanDue = false;
|
||||||
m_armTimer.stop();
|
m_armTimer.stop();
|
||||||
m_input.stop();
|
m_input.stop();
|
||||||
if (m_scan) {
|
if (m_scan) {
|
||||||
@@ -90,6 +99,7 @@ void LockController::onLockedChanged(bool locked)
|
|||||||
|
|
||||||
if (m_config->scanOnLock()) {
|
if (m_config->scanOnLock()) {
|
||||||
QTimer::singleShot(400, this, [this] {
|
QTimer::singleShot(400, this, [this] {
|
||||||
|
m_lockScanDue = m_locked && !m_lock.canUnlock();
|
||||||
startScan(QStringLiteral("lock"));
|
startScan(QStringLiteral("lock"));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ private:
|
|||||||
LockScreenController *m_screen;
|
LockScreenController *m_screen;
|
||||||
bool m_locked = false;
|
bool m_locked = false;
|
||||||
bool m_stopped = false;
|
bool m_stopped = false;
|
||||||
|
// Scan right after locking, once the lock screen can be opened.
|
||||||
|
bool m_lockScanDue = false;
|
||||||
QElapsedTimer m_lockedFor;
|
QElapsedTimer m_lockedFor;
|
||||||
QPointer<DaemonRequest> m_scan;
|
QPointer<DaemonRequest> m_scan;
|
||||||
QTimer m_armTimer;
|
QTimer m_armTimer;
|
||||||
|
|||||||
+13
-4
@@ -5,6 +5,7 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -14,7 +15,7 @@ namespace
|
|||||||
{
|
{
|
||||||
bool isLocker(const QByteArray &name)
|
bool isLocker(const QByteArray &name)
|
||||||
{
|
{
|
||||||
return name == "hyprlock" || name == "swaylock";
|
return name == "hyprlock" || name == "swaylock" || name == "gtklock";
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray readFile(const QString &path)
|
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
|
// 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
|
// 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
|
// of a lock screen (swaylock and gtklock check the password in one) is left
|
||||||
// by the signal before its parent unlocks, it would take the parent down
|
// out: killed by the signal before its parent unlocks, it would take the
|
||||||
// with it, and the screen would stay locked.
|
// parent down with it, and the screen would stay locked.
|
||||||
QList<pid_t> findAll()
|
QList<pid_t> findAll()
|
||||||
{
|
{
|
||||||
QList<pid_t> found;
|
QList<pid_t> found;
|
||||||
@@ -104,6 +105,14 @@ QList<pid_t> Lockers::find(const QByteArray &name)
|
|||||||
return found;
|
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 Lockers::signal(int sig, const QByteArray &name)
|
||||||
{
|
{
|
||||||
int waiting = 0;
|
int waiting = 0;
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
//
|
//
|
||||||
// The lock screens that are programs of their own and take signals from
|
// 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
|
#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
|
// that, the signal kills it, and the compositor keeps the screen locked with
|
||||||
// nobody to open it.
|
// nobody to open it.
|
||||||
int signal(int sig, const QByteArray &name = {});
|
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()
|
void LockWatcher::pollLockers()
|
||||||
{
|
{
|
||||||
m_lockerRunning = !Lockers::find().isEmpty();
|
m_lockerRunning = !Lockers::find().isEmpty();
|
||||||
|
const bool ready = Lockers::ready(SIGUSR1);
|
||||||
|
const bool became = ready && !m_lockerReady;
|
||||||
|
m_lockerReady = ready;
|
||||||
update();
|
update();
|
||||||
|
if (became) {
|
||||||
|
Q_EMIT unlockable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LockWatcher::setOwnLock(SessionLock *lock)
|
void LockWatcher::setOwnLock(SessionLock *lock)
|
||||||
@@ -171,7 +177,7 @@ void LockWatcher::setOwnLock(SessionLock *lock)
|
|||||||
|
|
||||||
bool LockWatcher::canUnlock() const
|
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()
|
void LockWatcher::update()
|
||||||
|
|||||||
+10
-5
@@ -7,11 +7,12 @@
|
|||||||
// GNOME logind's LockedHint. Unlocked through logind.
|
// GNOME logind's LockedHint. Unlocked through logind.
|
||||||
// Niri logind's LockedHint, which niri sets for any lock screen.
|
// Niri logind's LockedHint, which niri sets for any lock screen.
|
||||||
// Hyprland sets no LockedHint, so the lock screen is looked for among this
|
// Hyprland sets no LockedHint, so the lock screen is looked for among this
|
||||||
// user's processes (hyprlock, swaylock) once a second. Only on
|
// user's processes (hyprlock, swaylock, gtklock) once a second.
|
||||||
// compositors where the lock screen is a program of its own
|
// Only on compositors where the lock screen is a program of its
|
||||||
// (ext-session-lock).
|
// 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
|
// 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
|
// 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.
|
// PAM module in its own stack (see src/lib/pam.sh), when Enter is pressed.
|
||||||
@@ -41,13 +42,16 @@ public:
|
|||||||
return m_locked;
|
return m_locked;
|
||||||
}
|
}
|
||||||
// Whether unlock() can open the lock screen that is up: always where
|
// 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;
|
bool canUnlock() const;
|
||||||
void unlock();
|
void unlock();
|
||||||
void setOwnLock(SessionLock *lock);
|
void setOwnLock(SessionLock *lock);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void lockedChanged(bool locked);
|
void lockedChanged(bool locked);
|
||||||
|
// canUnlock() came true: the lock screen that is up now takes SIGUSR1.
|
||||||
|
void unlockable();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onScreenSaver(bool active);
|
void onScreenSaver(bool active);
|
||||||
@@ -63,6 +67,7 @@ private:
|
|||||||
bool m_screenSaver = false;
|
bool m_screenSaver = false;
|
||||||
bool m_lockedHint = false;
|
bool m_lockedHint = false;
|
||||||
bool m_lockerRunning = false;
|
bool m_lockerRunning = false;
|
||||||
|
bool m_lockerReady = false;
|
||||||
bool m_locked = false;
|
bool m_locked = false;
|
||||||
// The compositor's lock screen is a program of its own.
|
// The compositor's lock screen is a program of its own.
|
||||||
bool m_ownLockers = false;
|
bool m_ownLockers = false;
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ int main(int, char **argv)
|
|||||||
pid_t pid = fakeHyprlock(false, &from);
|
pid_t pid = fakeHyprlock(false, &from);
|
||||||
check(Lockers::find("hyprlock") == QList<pid_t>{pid}, "finds the lock screen on this display");
|
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::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");
|
check(Lockers::signal(SIGUSR2, "hyprlock") == 1, "one not ready yet");
|
||||||
usleep(200 * 1000);
|
usleep(200 * 1000);
|
||||||
check(alive(pid), "and it was left alone, alive");
|
check(alive(pid), "and it was left alone, alive");
|
||||||
@@ -103,6 +104,7 @@ int main(int, char **argv)
|
|||||||
close(from);
|
close(from);
|
||||||
|
|
||||||
pid = fakeHyprlock(true, &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(Lockers::signal(SIGUSR2, "hyprlock") == 0, "a ready one gets it");
|
||||||
check(next(from) == 's', "and handles it");
|
check(next(from) == 's', "and handles it");
|
||||||
check(alive(pid), "and lives on");
|
check(alive(pid), "and lives on");
|
||||||
|
|||||||
Reference in new issue
Block a user