feat(ags4): implement lockscreen
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2025-01-25 02:28:34 -05:00
parent 8bd0b732e7
commit 557e4c7a52
32 changed files with 281 additions and 12 deletions

View file

@ -2,7 +2,8 @@ import { App } from 'astal/gtk4';
import style from './style.scss';
import Bar from './widget/bar';
// import Bar from './widgets/bar';
import Lockscreen from './widgets/lockscreen';
App.start({
@ -10,6 +11,7 @@ App.start({
instanceName: 'gtk4',
main() {
Bar();
// Bar();
Lockscreen();
},
});

View file

@ -2,7 +2,7 @@ import { idle } from 'astal';
import { App, Gdk, Gtk } from 'astal/gtk4';
/* Types */
import PopupWindow from '../widget/misc/popup-window';
import PopupWindow from '../widgets/misc/popup-window';
export interface Layer {
address: string

View file

@ -1,3 +1,5 @@
@use './widgets/lockscreen';
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};

View file

@ -0,0 +1,9 @@
window,
viewport {
all: unset;
}
.lock-clock {
font-size: 80pt;
font-family: 'Ubuntu Mono';
}

View file

@ -0,0 +1,253 @@
import { idle, timeout, Variable } from 'astal';
import { App, Astal, Gdk, Gtk } from 'astal/gtk4';
import { register } from 'astal/gobject';
import AstalAuth from 'gi://AstalAuth';
import Gtk4SessionLock from 'gi://Gtk4SessionLock';
import { Box, BoxClass, Entry, Label, Window } from '../subclasses';
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 BoxClass {
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 = Gtk4SessionLock.get_singleton();
const unlock = () => {
blurBGs.forEach((b) => {
b.css = bgCSS({
w: b.geometry.w,
h: 1,
});
timeout(transition_duration / 2, () => {
b.css = bgCSS({
w: 1,
h: 1,
});
});
});
timeout(transition_duration, () => {
Gtk4SessionLock.unlock();
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({
cssClasses: ['lock-clock'],
label: 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,
visible: true,
anchor: Astal.WindowAnchor.TOP |
Astal.WindowAnchor.LEFT |
Astal.WindowAnchor.RIGHT |
Astal.WindowAnchor.BOTTOM,
margin: WINDOW_MARGINS,
exclusivity: Astal.Exclusivity.IGNORE,
child: Box({
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
children: [rev],
}),
});
const label = Label({ label: 'Enter password:' });
return new Gtk.Window({
child: visible ?
Box({
vertical: true,
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
spacing: 16,
children: [
Clock(),
Separator({
size: CLOCK_SPACING,
vertical: true,
}),
Box({
halign: Gtk.Align.CENTER,
cssClasses: ['avatar'],
}),
Box({
cssClasses: ['entry-box'],
vertical: true,
children: [
label,
Separator({
size: ENTRY_SPACING,
vertical: true,
}),
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(),
});
};
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_monitors().get_n_items() ?? 0); m++) {
const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;
if (monitor) {
createWindow(monitor);
}
}
display?.get_monitors()?.connect('items-changed', () => {
for (let m = 0; m < (display?.get_monitors().get_n_items() ?? 0); m++) {
const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;
if (monitor && !windows.has(monitor)) {
createWindow(monitor);
}
}
});
Gtk4SessionLock.lock();
windows.forEach((win, monitor) => {
Gtk4SessionLock.assign_window_to_monitor(win, monitor);
win.show();
});
};
const on_finished = () => {
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();
};

View file

@ -129,6 +129,16 @@ in {
buildNodeModules
buildGirTypes
;
lockscreenVars =
# javascript
''
export default {
mainMonitor: '${cfgDesktop.mainMonitor}',
dupeLockscreen: ${boolToString cfgDesktop.displayManager.duplicateScreen},
hasFprintd: ${boolToString (hostName == "wim")},
};
'';
in (
(buildGirTypes {
pname = "ags";
@ -159,15 +169,8 @@ in {
source = buildNodeModules ./config (import ./config).npmDepsHash;
};
"${cfg.configDir}/widgets/lockscreen/vars.ts".text =
# javascript
''
export default {
mainMonitor: '${cfgDesktop.mainMonitor}',
dupeLockscreen: ${boolToString cfgDesktop.displayManager.duplicateScreen},
hasFprintd: ${boolToString (hostName == "wim")},
};
'';
"${cfg.configDir}/widgets/lockscreen/vars.ts".text = lockscreenVars;
"${gtk4ConfigDir}/widgets/lockscreen/vars.ts".text = lockscreenVars;
}
// optionalAttrs cfgDesktop.isTouchscreen {
".config/fcitx5/conf/virtualkeyboardadapter.conf".text = ''