import { bind, idle, timeout, Variable } from 'astal'; import { App, Astal, Gdk, Gtk, Widget } from 'astal/gtk3'; import { register } from 'astal/gobject'; import AstalAuth from 'gi://AstalAuth'; import Lock from 'gi://GtkSessionLock'; import Separator from '../misc/separator'; import { get_hyprland_monitor_desc } from '../../lib'; // This file is generated by Nix import Vars from './vars'; /* Types */ declare global { function authFinger(): void; } @register() class BlurredBox extends Widget.Box { geometry = {} as { w: number, h: number }; } export default () => { const windows = new Map<Gdk.Monitor, Gtk.Window>(); const blurBGs: BlurredBox[] = []; const transition_duration = 1000; const WINDOW_MARGINS = -2; const ENTRY_SPACING = 20; const CLOCK_SPACING = 60; const bgCSS = ({ w = 1, h = 1 } = {}) => ` border: 2px solid rgba(189, 147, 249, 0.8); background: rgba(0, 0, 0, 0.2); min-height: ${h}px; min-width: ${w}px; transition: min-height ${transition_duration / 2}ms, min-width ${transition_duration / 2}ms; `; const lock = Lock.prepare_lock(); const unlock = () => { blurBGs.forEach((b) => { b.set_css(bgCSS({ w: b.geometry.w, h: 1, })); timeout(transition_duration / 2, () => { b.set_css(bgCSS({ w: 1, h: 1, })); }); }); timeout(transition_duration, () => { lock.unlock_and_destroy(); Gdk.Display.get_default()?.sync(); App.quit(); }); }; const Clock = () => { const time = Variable<string>('').poll(1000, () => { return (new Date().toLocaleString([], { hour: 'numeric', minute: 'numeric', hour12: true, }) ?? '') .replace('a.m.', 'AM') .replace('p.m.', 'PM'); }); return ( <label className="lock-clock" label={bind(time)} /> ); }; const PasswordPrompt = (monitor: Gdk.Monitor, visible: boolean) => { const rev = new BlurredBox({ css: bgCSS() }); idle(() => { rev.geometry = { w: monitor.get_geometry().width, h: monitor.get_geometry().height, }; rev.css = bgCSS({ w: rev.geometry.w, h: 1, }); timeout(transition_duration / 2, () => { rev.css = bgCSS({ w: rev.geometry.w, h: rev.geometry.h, }); }); }); blurBGs.push(rev); <window name={`blur-bg-${monitor.get_model()}`} namespace={`blur-bg-${monitor.get_model()}`} gdkmonitor={monitor} layer={Astal.Layer.OVERLAY} anchor={ Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM } margin={WINDOW_MARGINS} exclusivity={Astal.Exclusivity.IGNORE} > <box halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} > {rev} </box> </window>; const label = <label label="Enter password:" /> as Widget.Label; return new Gtk.Window({ child: visible ? ( <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} spacing={16} > <Clock /> <Separator size={CLOCK_SPACING} vertical /> <box halign={Gtk.Align.CENTER} className="avatar" /> <box className="entry-box" vertical > {label} <Separator size={ENTRY_SPACING} vertical /> <entry halign={Gtk.Align.CENTER} xalign={0.5} visibility={false} placeholder_text="password" onRealize={(self) => self.grab_focus()} onActivate={(self) => { self.set_sensitive(false); AstalAuth.Pam.authenticate(self.get_text() ?? '', (_, task) => { try { AstalAuth.Pam.authenticate_finish(task); unlock(); } catch (e) { self.set_text(''); label.set_label((e as Error).message); self.set_sensitive(true); } }); }} /> </box> </box> ) : <box />, }); }; const createWindow = (monitor: Gdk.Monitor) => { const hyprDesc = get_hyprland_monitor_desc(monitor); const entryVisible = Vars.mainMonitor === hyprDesc || Vars.dupeLockscreen; const win = PasswordPrompt(monitor, entryVisible); windows.set(monitor, win); }; const lock_screen = () => { const display = Gdk.Display.get_default(); for (let m = 0; m < (display?.get_n_monitors() ?? 0); m++) { const monitor = display?.get_monitor(m); if (monitor) { createWindow(monitor); } } display?.connect('monitor-added', (_, monitor) => { createWindow(monitor); }); lock.lock_lock(); windows.forEach((win, monitor) => { lock.new_surface(win, monitor); win.show(); }); }; const on_finished = () => { lock.destroy(); Gdk.Display.get_default()?.sync(); App.quit(); }; lock.connect('finished', on_finished); if (Vars.hasFprintd) { globalThis.authFinger = () => AstalAuth.Pam.authenticate('', (_, task) => { try { AstalAuth.Pam.authenticate_finish(task); unlock(); } catch (e) { console.error((e as Error).message); } }); globalThis.authFinger(); } lock_screen(); };