Add lockscreen

This commit is contained in:
2024-07-09 16:07:19 +07:00
parent 7a5889047a
commit 26982bd040
10 changed files with 356 additions and 95 deletions
Generated
+42
View File
@@ -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",
+10 -3
View File
@@ -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
+86 -83
View File
@@ -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;
}
+7 -2
View File
@@ -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);
});
});
+3 -1
View File
@@ -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";
};
+196
View File
@@ -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;
// }),
// ),
],
}),
});
+5 -3
View File
@@ -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 || "";
}
+2 -1
View File
@@ -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,
+3 -2
View File
@@ -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);
},
+2
View File
@@ -39,4 +39,6 @@
gnome.gnome-bluetooth
polkit_gnome
];
security.pam.services.astal-auth = {};
}