From 26982bd0407f2c917a3be6169c4b742fe17e5ff9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 9 Jul 2024 16:07:19 +0700 Subject: [PATCH] Add lockscreen --- flake.lock | 42 +++++++ flake.nix | 13 +- modules/gui/default.nix | 169 +++++++++++++------------- modules/wm/ags/config.js | 9 +- modules/wm/ags/default.nix | 4 +- modules/wm/ags/lockscreen.js | 196 ++++++++++++++++++++++++++++++ modules/wm/ags/misc/utils.js | 8 +- modules/wm/ags/modules/systray.js | 3 +- modules/wm/ags/modules/wm.js | 5 +- modules/wm/default.nix | 2 + 10 files changed, 356 insertions(+), 95 deletions(-) create mode 100644 modules/wm/ags/lockscreen.js diff --git a/flake.lock b/flake.lock index f4a7bca..aa35fae 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,25 @@ { "nodes": { + "astal-auth": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1718556609, + "narHash": "sha256-EeYnp9HEpQXNGweF/Ea6ZfjTSV6biHFSsZV010nDHaM=", + "owner": "astal-sh", + "repo": "auth", + "rev": "b35a38aa93670f6be06202f2ad3066a227f0a1b9", + "type": "github" + }, + "original": { + "owner": "astal-sh", + "repo": "auth", + "type": "github" + } + }, "astal-river": { "inputs": { "nixpkgs": [ @@ -306,6 +326,26 @@ "type": "github" } }, + "gtk-session-lock": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713713322, + "narHash": "sha256-A9U/BnzdypE1rt53uhw5X4JQkayR8CkD2Qhn/vhmUSU=", + "owner": "Cu3PO42", + "repo": "gtk-session-lock", + "rev": "b9ddb2792b613d14622acada73c64f16a2635b40", + "type": "github" + }, + "original": { + "owner": "Cu3PO42", + "repo": "gtk-session-lock", + "type": "github" + } + }, "hercules-ci-effects": { "inputs": { "flake-parts": "flake-parts_2", @@ -523,9 +563,11 @@ }, "root": { "inputs": { + "astal-auth": "astal-auth", "astal-river": "astal-river", "flood": "flood", "ghostty": "ghostty", + "gtk-session-lock": "gtk-session-lock", "home-manager": "home-manager", "impermanence": "impermanence", "neovim-nightly": "neovim-nightly", diff --git a/flake.nix b/flake.nix index 29dff94..451b732 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,15 @@ flake = false; }; astal-river = { - url ="github:zoriya/astal-river"; + url = "github:zoriya/astal-river"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + astal-auth = { + url = "github:astal-sh/auth"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + gtk-session-lock = { + url = "github:Cu3PO42/gtk-session-lock"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -44,7 +52,6 @@ impermanence, nixos-hardware, nix-index-database, - astal-river, ... } @ inputs: let user = "zoriya"; @@ -81,7 +88,7 @@ home-manager = { useGlobalPkgs = true; useUserPackages = true; - extraSpecialArgs = { inherit inputs; }; + extraSpecialArgs = {inherit inputs;}; users.${user} = { imports = [ ./modules/cli/home.nix diff --git a/modules/gui/default.nix b/modules/gui/default.nix index bb2bd06..6f1298d 100644 --- a/modules/gui/default.nix +++ b/modules/gui/default.nix @@ -40,92 +40,95 @@ in { # For rider FLATPAK_ENABLE_SDK_EXT = "*"; }; - xdg.enable = true; - xdg.mimeApps = { + xdg = { enable = true; - defaultApplications = { - "x-scheme-handler/http" = browser; - "x-scheme-handler/https" = browser; - "x-scheme-handler/about" = browser; - "x-scheme-handler/unknown" = browser; - "x-scheme-handler/magnet" = browser; - "application/oxps" = pdf; - "application/pdf" = pdf; - "application/epub+zip" = pdf; - "application/x-fictionbook+xml" = pdf; - "text/tcl" = editor; - "text/html" = editor; - "text/x-makefile" = editor; - "text/vbscript" = editor; - "text/spreadsheet" = editor; - "text/x-tex" = editor; - "text/x-c++hdr" = editor; - "text/x-pascal" = editor; - "text/x-moc" = editor; - "text/x-chdr" = editor; - "text/tab-separated-values" = editor; - "text/x-python" = editor; - "text/x-csrc" = editor; - "text/x-c++src" = editor; - "text/x-java" = editor; - "text/plain" = editor; - "text/csv" = editor; - "video/x-flic" = player; - "video/mpeg" = player; - "video/x-ms-wmv" = player; - "video/vnd.rn-realvideo" = player; - "video/x-theora+ogg" = player; - "video/dv" = player; - "video/webm" = player; - "video/ogg" = player; - "video/quicktime" = player; - "video/x-flv" = player; - "video/x-ogm+ogg" = player; - "video/3gpp2" = player; - "video/mp2t" = player; - "video/x-msvideo" = player; - "video/3gpp" = player; - "video/x-matroska" = player; - "video/vnd.mpegurl" = player; - "video/mp4" = player; - "audio/aac" = player; - "audio/ac3" = player; - "audio/x-wavpack" = player; - "audio/webm" = player; - "audio/x-ms-wma" = player; - "audio/flac" = player; - "audio/x-scpls" = player; - "audio/mpeg" = player; - "audio/x-mpegurl" = player; - "audio/x-ms-asx" = player; - "audio/vnd.rn-realaudio" = player; - "audio/x-wav" = player; - "audio/vnd.dts" = player; - "audio/x-adpcm" = player; - "audio/x-vorbis+ogg" = player; - "audio/mp4" = player; - "audio/x-tta" = player; - "audio/x-musepack" = player; - "audio/AMR" = player; - "audio/x-matroska" = player; - "audio/x-ape" = player; - "audio/x-aiff" = player; - "audio/vnd.dts.hd" = player; - "audio/ogg" = player; - "audio/mp2" = player; + mime.enable = true; + mimeApps = { + enable = true; + defaultApplications = { + "x-scheme-handler/http" = browser; + "x-scheme-handler/https" = browser; + "x-scheme-handler/about" = browser; + "x-scheme-handler/unknown" = browser; + "x-scheme-handler/magnet" = browser; + "application/oxps" = pdf; + "application/pdf" = pdf; + "application/epub+zip" = pdf; + "application/x-fictionbook+xml" = pdf; + "text/tcl" = editor; + "text/html" = editor; + "text/x-makefile" = editor; + "text/vbscript" = editor; + "text/spreadsheet" = editor; + "text/x-tex" = editor; + "text/x-c++hdr" = editor; + "text/x-pascal" = editor; + "text/x-moc" = editor; + "text/x-chdr" = editor; + "text/tab-separated-values" = editor; + "text/x-python" = editor; + "text/x-csrc" = editor; + "text/x-c++src" = editor; + "text/x-java" = editor; + "text/plain" = editor; + "text/csv" = editor; + "video/x-flic" = player; + "video/mpeg" = player; + "video/x-ms-wmv" = player; + "video/vnd.rn-realvideo" = player; + "video/x-theora+ogg" = player; + "video/dv" = player; + "video/webm" = player; + "video/ogg" = player; + "video/quicktime" = player; + "video/x-flv" = player; + "video/x-ogm+ogg" = player; + "video/3gpp2" = player; + "video/mp2t" = player; + "video/x-msvideo" = player; + "video/3gpp" = player; + "video/x-matroska" = player; + "video/vnd.mpegurl" = player; + "video/mp4" = player; + "audio/aac" = player; + "audio/ac3" = player; + "audio/x-wavpack" = player; + "audio/webm" = player; + "audio/x-ms-wma" = player; + "audio/flac" = player; + "audio/x-scpls" = player; + "audio/mpeg" = player; + "audio/x-mpegurl" = player; + "audio/x-ms-asx" = player; + "audio/vnd.rn-realaudio" = player; + "audio/x-wav" = player; + "audio/vnd.dts" = player; + "audio/x-adpcm" = player; + "audio/x-vorbis+ogg" = player; + "audio/mp4" = player; + "audio/x-tta" = player; + "audio/x-musepack" = player; + "audio/AMR" = player; + "audio/x-matroska" = player; + "audio/x-ape" = player; + "audio/x-aiff" = player; + "audio/vnd.dts.hd" = player; + "audio/ogg" = player; + "audio/mp2" = player; + }; }; - }; - xdg.userDirs = { - enable = true; - download = "${config.home.homeDirectory}/downloads"; - desktop = config.home.homeDirectory; + userDirs = { + enable = true; + download = "${config.home.homeDirectory}/downloads"; + desktop = config.home.homeDirectory; - documents = "${config.home.homeDirectory}/stuff"; - music = "${config.home.homeDirectory}/stuff"; - templates = "${config.home.homeDirectory}/stuff"; - videos = "${config.home.homeDirectory}/stuff"; - pictures = "${config.home.homeDirectory}/stuff"; - publicShare = "${config.home.homeDirectory}/stuff"; + documents = "${config.home.homeDirectory}/stuff"; + music = "${config.home.homeDirectory}/stuff"; + templates = "${config.home.homeDirectory}/stuff"; + videos = "${config.home.homeDirectory}/stuff"; + pictures = "${config.home.homeDirectory}/stuff"; + publicShare = "${config.home.homeDirectory}/stuff"; + }; }; home.file.".face".source = ../../face.png; } diff --git a/modules/wm/ags/config.js b/modules/wm/ags/config.js index 48db407..4d9d361 100644 --- a/modules/wm/ags/config.js +++ b/modules/wm/ags/config.js @@ -1,10 +1,14 @@ +import Gtk from "gi://Gtk?version=3.0"; +import Gdk from "gi://Gdk"; + import { Bar } from "./layouts/bar.js"; import { Notifications } from "./layouts/notifications.js"; import { OSD } from "./layouts/osd.js"; import { Quicksettings } from "./layouts/quicksettings.js"; +import { lockscreen } from "./lockscreen.js"; -import Gtk from "gi://Gtk?version=3.0"; -import Gdk from "gi://Gdk"; +// @ts-ignore +App.lock = lockscreen; /** * @param {Array<(monitor: number) => Gtk.Window>} widgets @@ -26,6 +30,7 @@ export function forMonitors(widgets) { display?.connect("monitor-removed", (disp, monitor) => { App.windows.forEach((win) => { + // @ts-ignore if (win.gdkmonitor === monitor) App.removeWindow(win); }); }); diff --git a/modules/wm/ags/default.nix b/modules/wm/ags/default.nix index 60bf15f..7e2b646 100644 --- a/modules/wm/ags/default.nix +++ b/modules/wm/ags/default.nix @@ -23,6 +23,8 @@ ++ [ pkgs.libdbusmenu-gtk3 inputs.astal-river.packages.x86_64-linux.default + inputs.astal-auth.packages.x86_64-linux.default + inputs.gtk-session-lock.packages.x86_64-linux.default ]; }); in { @@ -43,7 +45,7 @@ in { Service = { Type = "simple"; - ExecStart = "${pkgs.ags}/bin/ags"; + ExecStart = "${ags}/bin/ags"; Restart = "always"; }; diff --git a/modules/wm/ags/lockscreen.js b/modules/wm/ags/lockscreen.js new file mode 100644 index 0000000..3c34a17 --- /dev/null +++ b/modules/wm/ags/lockscreen.js @@ -0,0 +1,196 @@ +import Gdk from "gi://Gdk?version=3.0"; +import Gtk from "gi://Gtk?version=3.0"; +import Lock from "gi://GtkSessionLock"; +import AstalAuth from "gi://AstalAuth"; + +import { Clock } from "./modules/clock.js"; + +export const DELAY = 500; + +export const isLocked = Variable(false); +export const prompt = Variable(""); +export const inputVisible = Variable(false); +export const inputNeeded = Variable(false); +export const error = Variable( + /** @type {{type: "info" | "error" | "fail", message: string} | null} */ ( + null + ), +); + +const auth = new AstalAuth.Pam(); +let lock; +const windows = []; + +let hasInit = false; + +function init() { + if (hasInit) return; + hasInit = true; + auth.connect("auth-prompt-visible", (auth, msg) => { + prompt.setValue(msg); + inputVisible.setValue(true); + inputNeeded.setValue(true); + }); + auth.connect("auth-prompt-hidden", (auth, msg) => { + prompt.setValue(msg); + inputVisible.setValue(false); + inputNeeded.setValue(true); + }); + + auth.connect("auth-error", (_, msg) => { + error.setValue({ message: msg, type: "error" }); + auth.supply_secret(null); + }); + auth.connect("auth-info", (_, msg) => { + error.setValue({ message: msg, type: "info" }); + auth.supply_secret(null); + }); + + auth.connect("success", unlock); + auth.connect("fail", (p, msg) => { + error.setValue({ message: msg, type: "fail" }); + auth.start_authenticate(); + }); + + const display = Gdk.Display.get_default(); + display?.connect("monitor-added", (disp, monitor) => { + if (!isLocked.value) return; + const w = createWindow(monitor); + lock.new_surface(w.window, w.monitor); + w.window.show(); + }); + display?.connect("monitor-removed", (disp, monitor) => { + if (!isLocked.value) return; + windows.forEach((win) => { + lock.unmap_lock_window(win.window); + win.window.destroy(); + }); + }); +} + +export function lockscreen() { + init(); + lock = Lock.prepare_lock(); + lock.connect("locked", () => {}); + lock.connect("finished", unlock); + + const display = Gdk.Display.get_default(); + const n = display?.get_n_monitors() || 1; + for (let m = 0; m < n; m++) { + const monitor = display?.get_monitor(m); + // @ts-ignore + createWindow(monitor); + } + + lock.lock_lock(); + isLocked.value = true; + windows.map((w) => { + lock.new_surface(w.window, w.monitor); + w.window.show(); + }); + auth.start_authenticate(); +} + +/** + * @param {string} password + */ +export function login(password) { + inputNeeded.setValue(false); + auth.supply_secret(password); +} + +function unlock() { + isLocked.value = false; + + // Wait for window's hide animations to finish + Utils.timeout(DELAY, () => { + windows.forEach((w) => { + Lock.unmap_lock_window(w.window); + }); + lock.unlock_and_destroy(); + windows.forEach((w) => { + w.window.destroy(); + }); + lock = undefined; + + Gdk.Display.get_default()?.sync(); + }); +} + +/** + * @param {Gdk.Monitor} monitor + */ +function createWindow(monitor) { + const window = LockWindow(); + const win = { window, monitor }; + windows.push(win); + return win; +} + +const LoginBox = () => + Widget.Box({ + vertical: true, + vpack: "center", + hpack: "center", + spacing: 16, + children: [ + Widget.Box({ + hpack: "center", + className: "avatar", + css: `background-image: url("/home/${Utils.USER}/.face");`, + }), + Widget.Box({ + className: inputNeeded + .bind() + .as((n) => `entry-box ${n ? "" : "hidden"}`), + vertical: true, + children: [ + Widget.Label({ + label: prompt.bind(), + }), + Widget.Separator(), + Widget.Entry({ + hpack: "center", + xalign: 0.5, + visibility: inputVisible.bind(), + sensitive: inputNeeded.bind(), + onAccept: (self) => { + login(self.text || ""); + self.text = ""; + }, + }).on("realize", (entry) => entry.grab_focus()), + Widget.Label({ + label: error.bind().as((x) => x?.message || ""), + visible: error.bind().as((x) => !!x), + }), + ], + }), + ], + }); + +export const LockWindow = () => + new Gtk.Window({ + child: Widget.Box({ + children: [ + Widget.Revealer({ + reveal_child: isLocked.bind(), + transition: "crossfade", + transition_duration: DELAY, + child: Widget.Box({ + css: ` + background-image: url("/home/${Utils.USER}/.cache/current-wallpaper"); + background-position: center; + background-size: cover; + `, + vertical: true, + child: LoginBox(), + }), + }), + // .on("realize", (self) => + // Utils.idle(() => { + // self.reveal_child = true; + // }), + // ), + ], + }), + }); diff --git a/modules/wm/ags/misc/utils.js b/modules/wm/ags/misc/utils.js index 15e52c4..321acdf 100644 --- a/modules/wm/ags/misc/utils.js +++ b/modules/wm/ags/misc/utils.js @@ -2,16 +2,18 @@ import GLib from "gi://GLib?version=2.0"; /** * @param {string | null | undefined} name - * @param {string | null | undefined} fallback + * @param {string | null | undefined} [fallback] */ export function icon(name, fallback) { if (!name) return fallback || ""; - - if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name; + if (typeof name !== "string") return name; const sub = substitutes[name]; if (sub && Utils.lookUpIcon(sub)) return sub; if (Utils.lookUpIcon(name)) return name; + + if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name; + return fallback || ""; } diff --git a/modules/wm/ags/modules/systray.js b/modules/wm/ags/modules/systray.js index e3d5443..393c555 100644 --- a/modules/wm/ags/modules/systray.js +++ b/modules/wm/ags/modules/systray.js @@ -1,3 +1,4 @@ +import { icon } from "../misc/utils.js"; import { ArrowToggleButton, Menu } from "../misc/menu.js"; const systemtray = await Service.import("systemtray"); @@ -36,7 +37,7 @@ const SysTrayItem = (item) => css: "margin: 12px;", child: Widget.Box({ children: [ - Widget.Icon({ icon: item.bind("icon") }), + Widget.Icon({ icon: item.bind("icon").as(icon) }), Widget.Label({ truncate: "end", maxWidthChars: 28, diff --git a/modules/wm/ags/modules/wm.js b/modules/wm/ags/modules/wm.js index 3634037..e9c3021 100644 --- a/modules/wm/ags/modules/wm.js +++ b/modules/wm/ags/modules/wm.js @@ -34,7 +34,8 @@ export const Tags = ({ monitor, labels, ...props }) => ); self.children.forEach((button, i) => { // We need to set this here because assigning children to self calls show_all() and ignore visibility - button.visible = !!(occupied & (1 << i)); + // @ts-ignore + button.visible = button.attribute; }); }, "changed", @@ -51,7 +52,7 @@ export const Tags = ({ monitor, labels, ...props }) => const TagItem = ({ occupied, selected, urgent, i, output, label }) => Widget.EventBox({ classNames: [selected ? "accent" : "", urgent ? "secondary" : ""], - visible: occupied, + attribute: occupied || selected, onPrimaryClickRelease: () => { river.run_command_async(["set-focused-tags", `${1 << i}`], null); }, diff --git a/modules/wm/default.nix b/modules/wm/default.nix index 0e5e0de..c444465 100644 --- a/modules/wm/default.nix +++ b/modules/wm/default.nix @@ -39,4 +39,6 @@ gnome.gnome-bluetooth polkit_gnome ]; + + security.pam.services.astal-auth = {}; }