feat: GNOME Shell extension for lock screen / GDM camera overlay#1120
feat: GNOME Shell extension for lock screen / GDM camera overlay#1120HackOverflow404 wants to merge 228 commits into
Conversation
Signed-off-by: MusiKid <musikid@outlook.com>
Signed-off-by: MusiKid <musikid@outlook.com>
Signed-off-by: MusiKid <musikid@outlook.com>
Signed-off-by: MusiKid <musikid@outlook.com>
Signed-off-by: MusiKid <musikid@outlook.com>
Add python-opencv as a dependency for ArchLinux
Fix some bugs on GUI
inverted error message
Update datetime since datetime.utcnow() was deprecated since Python 3.12.
…lation Since pam_python depends on python2 and python2 cannot be installed through fedoras repositories the installation will fail. The beta of Howdy has fixed this issue. This problem is also explained in the copr repo. But it requires some searching.
Fix datetime.datetime.utcnow()
Remove use of Environment Python
fix(pam): use environ variable when getenv doesn't work
Fix unwanted unlocks when auth client subprocesses exit with 0
Fix polkit policy
Fix things regarding translations
Connect to signals of the shown window
Update README.md for Fedora 41 users
The current EDITOR handling is a bit lacking. Improvements: - check that the editor exists before trying to spawn a subprocess - add vi as nano alternative - if everything else fails, perhaps the user is using sudo. Suggest using 'sudo -E howdy [...]', as otherwise the command would run in a non-interactive shell (i.e. without sourcing the initialization files), and no environment variable is carried over (including EDITOR) unless explicitly preserved
…ndling feat(config): better EDITOR handling
Update compare.py from boltgolt#968
Release 3.0.0
The OpenCV window from compare.py can't draw above the GNOME lock-screen shield or on the Wayland GDM greeter, so add a GNOME Shell extension (howdy-screen@howdy) that renders the howdy camera feed there instead. Extension (howdy-gnome-extension/): - metadata.json declares session-modes ["user","unlock-dialog","gdm"]; without "unlock-dialog" GNOME disables the extension the moment the screen locks, which is why it never showed on the lock screen. - extension.js loads each frame via GdkPixbuf -> St.ImageContent instead of St.Icon + Gio.FileIcon, whose StTextureCache froze the feed on the first frame. Overlay is gated to non-user session modes so the logged-in case keeps using compare.py's OpenCV window. - install.sh deploys to ~/.local (session + lock screen) and, via sudo, to /usr/share + a gdm dconf keyfile (the greeter runs as user gdm and can't read ~/.local). compare.py: - Decouple JPEG frame-writing (/tmp/howdy-frame.jpg, consumed by the extension) from cv2.imshow via a do_imshow flag and a new "overlay" config option (default on). The greeter has no usable X display for cv2.imshow but still needs the frames. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The extension skipped reloading a frame unless the JPEG's mtime changed, but it read mtime at whole-second resolution (to_unix()). compare.py writes ~3 frames/sec, so frames sharing a second were treated as unchanged and the overlay refreshed only ~1x/sec. Compare time::modified + time::modified-usec for microsecond resolution so every written frame is picked up. Bump version to 3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erlay Feat/lock screen camera overlay
| : Cogl.PixelFormat.RGB_888; | ||
|
|
||
| const content = St.ImageContent.new_with_preferred_size(w, h); | ||
| const ok = content.set_bytes( |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
| ? Cogl.PixelFormat.RGBA_8888 | ||
| : Cogl.PixelFormat.RGB_888; | ||
|
|
||
| const content = St.ImageContent.new_with_preferred_size(w, h); |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
| const w = pixbuf.get_width(); | ||
| const h = pixbuf.get_height(); | ||
| const fmt = pixbuf.get_has_alpha() | ||
| ? Cogl.PixelFormat.RGBA_8888 |
There was a problem hiding this comment.
Misleading line break before '?'; readers may interpret this as an expression boundary.
|
|
||
| const w = pixbuf.get_width(); | ||
| const h = pixbuf.get_height(); | ||
| const fmt = pixbuf.get_has_alpha() |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
| this._frameTime = stamp; | ||
|
|
||
| const w = pixbuf.get_width(); | ||
| const h = pixbuf.get_height(); |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
| this._pollId = null; | ||
| } | ||
| if (this._overlay) { | ||
| this._overlay.get_parent()?.remove_child(this._overlay); |
There was a problem hiding this comment.
Expected ':' and instead saw 'remove_child'.
Expected an assignment or function call and instead saw an expression.
Expected an identifier and instead saw '.'.
| this._overlay.set_position(40, 80); | ||
|
|
||
| this._frameTime = 0; | ||
| this._pollId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, POLL_MS, () => { |
There was a problem hiding this comment.
'arrow function syntax (=>)' is only available in ES6 (use 'esversion: 6').
|
|
||
| // screenShieldGroup sits above the lock-screen shield (and exists in | ||
| // the gdm greeter too); fall back to uiGroup just in case. | ||
| const parent = Main.layoutManager.screenShieldGroup ?? Main.layoutManager.uiGroup; |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
Expected ':' and instead saw 'Main'.
Expected an assignment or function call and instead saw an expression.
Expected an identifier and instead saw '.'.
Expected an identifier and instead saw '?'.
Missing semicolon.
| reactive: false, | ||
| }); | ||
|
|
||
| const label = new St.Label({ |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
| } | ||
|
|
||
| _modeAllowsOverlay() { | ||
| const mode = Main.sessionMode?.currentMode; |
There was a problem hiding this comment.
'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
Expected ':' and instead saw 'currentMode'.
Expected an identifier and instead saw '.'.
Expected an identifier and instead saw ';'.
Missing semicolon.
What and why
When Howdy authenticates at the lock screen or GDM login screen, there is no visual feedback; the user stares at a static screen with no indication that the camera is active, whether their face is in frame, or if anything is happening at all. On the desktop (sudo/polkit), compare.py already opens an OpenCV window, but that approach doesn't work on the lock screen because Mutter blocks X11 windows from appearing above the lock shield.
This PR adds a GNOME Shell extension that shows the live annotated camera feed above the lock screen and GDM greeter while authentication is running.
How it works
compare.pywrites two files to/tmpwhile the auth is in progress:/tmp/howdy-active/tmp/howdy-frame.jpgos.replaceThe GNOME Shell extension (
howdy-screen@howdy) polls/tmp/howdy-activeat 250 ms intervals. When it appears, and the session is locked, or at the greeter, a small overlay panel is shown, and the JPEG is reloaded ~10×/s usingGdkPixbuf+St.ImageContent.Why file-based IPC: compare.py runs as root inside a PAM transaction. The extension runs as the user (or
gdm). A Unix socket would work, but requires privilege management. Files in/tmpare simpler, atomic, and work across all session modes.Why
St.ImageContentand notSt.Icon:StTextureCachecaches gicons by URI with no file-change monitor, causing the feed to freeze on the first frame.GdkPixbuf+St.ImageContentbypasses the cache entirely.Why
session-modes: ["unlock-dialog", "gdm"]: Withoutunlock-dialoginmetadata.json, GNOME Shell disables the extension the instant the screen locks. This is the key insight that makes the lock-screen case work.Changes
howdy/src/compare.pyrender_frame_to_window()- new function that draws face annotations and writes the JPEG. Runs on every non-black frame; the OpenCV window (cv2.imshow) is gated separately bydo_imshowso JPEG frames are produced even at the Wayland GDM greeter, where no X display exists.get_compositor()- scans the authenticating user's/procenvironment to detect the running Wayland compositor (sway, Hyprland, KDE, GNOME, or generic X11/Wayland). Used byraise_window_once()to pick the right tool.raise_window_once()- dispatches toswaymsg(sway),hyprctl(Hyprland), orxdotool(X11 / KDE / GNOME / unknown). Falls back to xdotool if the compositor-specific tool is not installed. Safe to call multiple times./tmp/howdy-active,/tmp/howdy-frame.jpg) are cleaned up inexit()so the overlay closes as soon as auth ends.howdy/src/config.iniThree new options in
[video](all default tofalse, zero behaviour change for existing installs):howdy-gnome-extension/(new directory)howdy-screen@howdy/extension.js- the GNOME Shell extension (GNOME 45+)howdy-screen@howdy/metadata.jsoninstall.sh- deploys to~/.local/share/(user session + lock screen) and/usr/share/+ a gdm dconf keyfile (greeter). Re-run after edits to redeploy.README.md- full setup docs including Wayland compatibility table, GNOME version requirements, PrivateTmp notes for distro maintainers, and optional dependency info.Compatibility
show_windowuses XWayland and works on all compositors.swaymsg), Hyprland (hyprctl), KDE + X11 (xdotool), GNOME (xdotool; overlay extension preferred)xdotoolfor X11/KDE/GNOME window raising;swaymsg/hyprctlship with their compositorsSecurity note
/tmp/howdy-frame.jpgis written world-readable (0644, owned by root). Thegdmuser is not root and must be able to read the file for the greeter overlay to work. This means any local user can read camera frames from that path while authentication is in progress. This is a deliberate design choice. Users who consider this unacceptable can leaveoverlay = false(the default).Testing
cd howdy-gnome-extension && ./install.shsudo howdy config→ setoverlay = truein[video]Super+L) → move face in front of camera, overlay should appearshow_window = true→ runsudo ls, OpenCV popup should appearLogs:
sudo cat /tmp/howdy-debug.log(written only whenoverlay=true).