feat: show the bubble on gnome

This commit is contained in:
Felitendo committed 2026-09-26 19:34:17 +02:00
1 parent f232f796fc
commit 2a9b0d3f91
28 files changed
+2278 -1077

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "bubbleservice.h"
#include "bubblecontroller.h"
#include <QDBusConnection>
#include <QDBusError>
BubbleService::BubbleService(BubbleController *bubble, QObject *parent)
: QObject(parent)
, m_bubble(bubble)
{
const auto changed = [this] {
Q_EMIT StateChanged(GetState());
};
connect(bubble, &BubbleController::phaseChanged, this, changed);
connect(bubble, &BubbleController::messageChanged, this, changed);
connect(bubble, &BubbleController::faceSeenChanged, this, changed);
connect(bubble, &BubbleController::styleChanged, this, changed);
connect(bubble, &BubbleController::paceChanged, this, changed);
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.registerService(QStringLiteral("io.github.loonixtools.FaceUnlock"))
|| !bus.registerObject(QStringLiteral("/io/github/loonixtools/FaceUnlock"), this, QDBusConnection::ExportScriptableContents)) {
qWarning("cannot offer the bubble on the session bus: %s", qPrintable(bus.lastError().message()));
}
}
QVariantMap BubbleService::GetState() const
{
return {
{QStringLiteral("phase"), m_bubble->phase()},
{QStringLiteral("message"), m_bubble->message()},
{QStringLiteral("faceSeen"), m_bubble->faceSeen()},
{QStringLiteral("style"), m_bubble->style()},
{QStringLiteral("pace"), m_bubble->pace()},
};
}
+30
View File
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
// The bubble's state on the session bus, for GNOME. GNOME lets no program
// draw above its windows or its lock screen; only a GNOME Shell extension
// may. face-unlock's extension (res/gnome-shell) draws the bubble from what
// this says: io.github.loonixtools.FaceUnlock, /io/github/loonixtools/FaceUnlock.
#pragma once
#include <QObject>
#include <QVariantMap>
class BubbleController;
class BubbleService : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "io.github.loonixtools.FaceUnlock.Bubble")
public:
explicit BubbleService(BubbleController *bubble, QObject *parent = nullptr);
// phase, message, faceSeen, style, pace: what BubbleController has.
Q_SCRIPTABLE QVariantMap GetState() const;
Q_SIGNALS:
Q_SCRIPTABLE void StateChanged(const QVariantMap &state);
private:
BubbleController *m_bubble;
};
+4 -3
View File
@@ -12,9 +12,10 @@
// request has to be made for every new surface role, before it is mapped, so
// it is repeated each time the window is shown again.
//
// Other compositors have no such thing. On Hyprland and Niri the lock screen
// covers the bubble, which shows again with the tick once it is gone. GNOME
// has no layer-shell at all, so there is no bubble there (see main.cpp).
// Other compositors have no such thing: on Hyprland and Niri the lock screen
// covers the bubble, which shows the tick once it is gone. GNOME has no
// layer-shell at all; there face-unlock's GNOME Shell extension draws the
// bubble (BubbleService).
#pragma once
+8 -3
View File
@@ -10,6 +10,7 @@
#include "agentsocket.h"
#include "bubblecontroller.h"
#include "bubbleservice.h"
#include "bubblewindow.h"
#include "enrollcontroller.h"
#include "lockcontroller.h"
@@ -112,8 +113,8 @@ int main(int argc, char **argv)
}
BubbleController bubble(&config);
// The bubble floats above everything as a layer-shell surface. GNOME has
// none, and a normal window cannot stay on top or pick its place, so
// there it does without.
// none, and a normal window cannot stay on top or pick its place: there
// face-unlock's GNOME Shell extension draws it, from what this tells it.
std::unique_ptr<BubbleWindow> window;
if (Wayland::hasGlobal("zwlr_layer_shell_v1")) {
window = std::make_unique<BubbleWindow>(&engine, &bubble);
@@ -121,7 +122,7 @@ int main(int argc, char **argv)
if (parser.isSet(demoOpt)) {
if (!window) {
qWarning("this desktop has no layer-shell, so there is no bubble to show");
qWarning("this desktop has no layer-shell; on GNOME the extension draws the bubble");
return 1;
}
scheduleDemo(&bubble);
@@ -135,6 +136,10 @@ int main(int argc, char **argv)
}
socket.listen();
QObject::connect(&socket, &AgentSocket::scanEvent, &bubble, &BubbleController::daemonEvent);
std::unique_ptr<BubbleService> service;
if (!window) {
service = std::make_unique<BubbleService>(&bubble);
}
LockController lock(&bubble, &config);
return app.exec();
+11
View File
@@ -111,6 +111,16 @@ fu_do_enable() {
[[ $CFG_SUDO == yes ]] && ! fu_pam_enabled sudo && fu_root pam-enable sudo
[[ $CFG_POLKIT == yes ]] && ! fu_pam_enabled polkit-1 && fu_root pam-enable polkit-1
case "$FU_DESKTOP" in
gnome)
fu_gnome_extension_enable
case $? in
1) fu_bad "$(fu_msg "Could not switch on the GNOME extension that shows the bubble.")" ;;
2) fu_note "$(fu_msg "Log out and back in once: then GNOME shows the bubble too.")" ;;
esac
;;
esac
fu_ok "$(fu_msg "Face unlock is on. Lock the screen and look at it to try.")"
if [[ $CFG_SUDO != yes && $CFG_POLKIT != yes ]]; then
fu_note "$(fu_msg "It can do sudo and admin prompts too. See Settings.")"
@@ -120,6 +130,7 @@ fu_do_enable() {
fu_do_disable() {
fu_config_set Enabled no || { fu_bad "$(fu_msg "Could not save the setting.")"; return 1; }
fu_agent_available && fu_agent_disable
[[ $FU_DESKTOP == gnome ]] && fu_gnome_extension_disable
local service
for service in "${FU_PAM_SERVICES[@]}"; do
+8 -9
View File
@@ -276,8 +276,7 @@ fu_ui_status() {
# heading, with only the label after it
# type bool, choice (steps through the choices) or camera
# needs a bool setting this one does nothing without; it is dimmed while
# that is off. Layers is no setting: whether the desktop can show
# the bubble at all (GNOME cannot).
# that is off
FU_SETTINGS=(
"group|Lock screen"
"user|LockScreen|bool|yes|Unlock with your face"
@@ -295,7 +294,7 @@ FU_SETTINGS=(
"sys|Adapt|bool|yes|Learn from every unlock"
"sys|SkipLidClosed|bool|yes|Not when the lid is closed"
"group|Bubble"
"user|Bubble|bool|yes|Show the bubble||Layers"
"user|Bubble|bool|yes|Show the bubble"
"user|BubbleStyle|choice|full|Style|full,minimal|Bubble"
"user|AnimationSpeed|choice|normal|Animation speed|slow,normal,fast|Bubble"
"user|BubbleForPrompts|bool|yes|Also for sudo and admin prompts||Bubble"
@@ -304,10 +303,12 @@ FU_SETTINGS=(
# fu_setting_help <Key> <value>
# What a setting does, shown under the list for the selected one.
fu_setting_help() {
if [[ $FU_DESKTOP == gnome && $1 =~ ^(Bubble|BubbleStyle|AnimationSpeed|BubbleForPrompts)$ ]]; then
fu_msg "GNOME does not let other programs show above its windows, so there is no bubble on GNOME."
return
fi
case "$FU_DESKTOP:$1" in
gnome:Bubble)
fu_msg "On GNOME a small GNOME extension shows the bubble. Turning face unlock on switches it on."
return
;;
esac
case "$1:$2" in
LockScreen:*) fu_msg "Unlocks the lock screen when it sees your face. Off: only your password works there." ;;
ScanOnWake:*) fu_msg "Scans when you press a key or move the mouse on the lock screen, and when the computer wakes up." ;;
@@ -523,8 +524,6 @@ fu_ui_settings() {
values[i]="$FU_SETTING_VALUE"
current[${keys[i]}]="$FU_SETTING_VALUE"
done
current[Layers]=yes
[[ $FU_DESKTOP == gnome ]] && current[Layers]=no current[Bubble]=no
dirty=0
fi
+40
View File
@@ -145,6 +145,46 @@ fu_agent_hint() {
fi
}
# ---------------------------------------------------------------------------
# The bubble's GNOME Shell extension
# ---------------------------------------------------------------------------
# GNOME lets no program draw above its windows; its extensions may. The
# package brings one, and turning face unlock on switches it on.
FU_GNOME_EXTENSION="face-unlock@loonixtools.github.io"
# fu_gnome_extension_enable
# gnome-extensions only knows the extensions GNOME found when the session
# started. One that came with the package later goes straight into the
# setting, and GNOME loads it at the next login: returns 2 then.
fu_gnome_extension_enable() {
local list new
fu_have gsettings || return 1
if gnome-extensions info "$FU_GNOME_EXTENSION" > /dev/null 2>&1; then
gnome-extensions enable "$FU_GNOME_EXTENSION" > /dev/null 2>&1 && return 0
fi
list="$(gsettings get org.gnome.shell enabled-extensions 2>/dev/null)" || return 1
case "$list" in
*"'$FU_GNOME_EXTENSION'"*) return 2 ;;
'@as []'|'[]') new="['$FU_GNOME_EXTENSION']" ;;
*) new="${list%]}, '$FU_GNOME_EXTENSION']" ;;
esac
gsettings set org.gnome.shell enabled-extensions "$new" || return 1
return 2
}
fu_gnome_extension_disable() {
local list
fu_have gsettings || return 0
gnome-extensions disable "$FU_GNOME_EXTENSION" > /dev/null 2>&1 && return 0
list="$(gsettings get org.gnome.shell enabled-extensions 2>/dev/null)" || return 0
list="${list//, \'$FU_GNOME_EXTENSION\'/}"
list="${list//\'$FU_GNOME_EXTENSION\', /}"
list="${list//\'$FU_GNOME_EXTENSION\'/}"
[[ $list == '[]' ]] && list='@as []'
gsettings set org.gnome.shell enabled-extensions "$list" > /dev/null 2>&1 || true
}
fu_agent_disable() {
systemctl --user disable --now "$FU_UNIT_AGENT" > /dev/null 2>&1 || true
}