summaryrefslogtreecommitdiff
path: root/accounts
diff options
context:
space:
mode:
Diffstat (limited to 'accounts')
-rw-r--r--accounts/gkleen@eostre.nix8
-rw-r--r--accounts/gkleen@installer.nix8
-rw-r--r--accounts/gkleen@sif/default.nix278
-rw-r--r--accounts/gkleen@sif/emacs.el4
-rw-r--r--accounts/gkleen@sif/niri/default.nix546
-rw-r--r--accounts/gkleen@sif/niri/mako.nix55
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix65
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix347
-rw-r--r--accounts/gkleen@sif/shell/default.nix115
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt168
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp83
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp55
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp102
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp52
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp18
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp21
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp198
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp147
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h7
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/default.nix30
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml28
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml445
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml146
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml817
-rw-r--r--accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml172
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Bar.qml117
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml136
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml117
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml84
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Clock.qml295
-rw-r--r--accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml112
-rw-r--r--accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml47
-rw-r--r--accounts/gkleen@sif/shell/quickshell/LockSurface.qml227
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Lockscreen.qml132
-rw-r--r--accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml35
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NiriIdle.qml30
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml340
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml266
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml483
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml49
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml75
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml18
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml22
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml194
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml162
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml63
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/SystemTray.qml201
-rw-r--r--accounts/gkleen@sif/shell/quickshell/UnixIPC.qml97
-rw-r--r--accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml163
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml89
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml56
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml204
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml120
-rw-r--r--accounts/gkleen@sif/shell/quickshell/displaymanager.qml115
-rw-r--r--accounts/gkleen@sif/shell/quickshell/shell.qml53
-rw-r--r--accounts/gkleen@sif/synadm/default.nix9
-rw-r--r--accounts/gkleen@sif/synadm/synadm_yaml15
-rw-r--r--accounts/gkleen@sif/systemd.nix45
-rw-r--r--accounts/gkleen@sif/utils/async-yt-dlp.nix57
-rw-r--r--accounts/gkleen@sif/utils/pdf2pdf.nix8
-rw-r--r--accounts/gkleen@sif/utils/sieve-edit.nix24
-rw-r--r--accounts/gkleen@sif/zshrc140
-rw-r--r--accounts/gkleen@surtr.nix8
-rw-r--r--accounts/gkleen@vidhar.nix4
-rw-r--r--accounts/mherold@eostre.nix6
-rw-r--r--accounts/root@installer.nix8
-rw-r--r--accounts/root@sif.nix8
-rw-r--r--accounts/root@surtr.nix8
-rw-r--r--accounts/root@vidhar.nix9
71 files changed, 7553 insertions, 829 deletions
diff --git a/accounts/gkleen@eostre.nix b/accounts/gkleen@eostre.nix
index 72818d44..28daf3fd 100644
--- a/accounts/gkleen@eostre.nix
+++ b/accounts/gkleen@eostre.nix
@@ -1,16 +1,16 @@
1{ flake, userName, pkgs, ... }: 1{ flake, userName, pkgs, ... }:
2{ 2{
3 imports = with flake.nixosModules.userProfiles.${userName}; [ 3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh utils tmux 4 utils
5 ]; 5 ];
6 6
7 config = { 7 config = {
8 home-manager.users.${userName} = { 8 home-manager.users.${userName} = {
9 home.stateVersion = "20.09"; 9 home.stateVersion = "20.09";
10 10
11 nixpkgs.config = { 11 # nixpkgs.config = {
12 allowUnfree = true; 12 # allowUnfree = true;
13 }; 13 # };
14 14
15 home.packages = with pkgs; [ 15 home.packages = with pkgs; [
16 thunderbird libreoffice element-desktop keepassxc vlc 16 thunderbird libreoffice element-desktop keepassxc vlc
diff --git a/accounts/gkleen@installer.nix b/accounts/gkleen@installer.nix
index c7a418f8..5fe1db38 100644
--- a/accounts/gkleen@installer.nix
+++ b/accounts/gkleen@installer.nix
@@ -1,7 +1,11 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2 2
3{ 3{
4 home-manager.users.${userName} = { config, ... } : { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = { config, ... } : {
5 home.stateVersion = config.home.version.release; 9 home.stateVersion = config.home.version.release;
6 }; 10 };
7} 11}
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index 11f24f31..36b722e4 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -49,10 +49,22 @@ let
49 ]; 49 ];
50 }; 50 };
51 51
52 lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service"; 52 # lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service";
53 lockCommand = "${lib.getExe' cfg.programs.quickshell.package "qs"} ipc call Lockscreen setLocked true";
54
55 editor = pkgs.symlinkJoin {
56 inherit (cfg.services.emacs.package) name;
57 buildInputs = with pkgs; [ makeWrapper ];
58 paths = [ cfg.services.emacs.package ];
59 postBuild = ''
60 wrapProgram $out/bin/emacsclient \
61 --inherit-argv0 \
62 --add-flags ${lib.escapeShellArg (lib.escapeShellArgs cfg.services.emacs.client.arguments)}
63 '';
64 };
53in { 65in {
54 imports = with flake.nixosModules.userProfiles.${userName}; [ 66 imports = with flake.nixosModules.userProfiles.${userName}; [
55 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 67 zsh tmux mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
56 ]; 68 ];
57 69
58 config = { 70 config = {
@@ -60,16 +72,18 @@ in {
60 imports = [ 72 imports = [
61 ./libvirt 73 ./libvirt
62 ./niri 74 ./niri
63 flakeInputs.nix-index-database.hmModules.nix-index 75 ./shell
76 ./synadm
77 flakeInputs.nix-index-database.homeModules.nix-index
64 flakeInputs.impermanence.nixosModules.home-manager.impermanence 78 flakeInputs.impermanence.nixosModules.home-manager.impermanence
65 ]; 79 ];
66 80
67 home.stateVersion = "20.09"; 81 home.stateVersion = "20.09";
68 82
69 nixpkgs.config = { 83 # nixpkgs.config = {
70 allowUnfree = true; 84 # allowUnfree = true;
71 zathura.useMupdf = false; 85 # zathura.useMupdf = false;
72 }; 86 # };
73 87
74 nix.registry = { 88 nix.registry = {
75 "flk" = { 89 "flk" = {
@@ -160,6 +174,7 @@ in {
160 }; 174 };
161 }; 175 };
162 }; 176 };
177 chromium.enable = true;
163 178
164 zathura = { 179 zathura = {
165 enable = true; 180 enable = true;
@@ -175,13 +190,93 @@ in {
175 gpu-api = "vulkan"; 190 gpu-api = "vulkan";
176 }; 191 };
177 192
178 zsh.initExtra = '' 193 zsh.initContent = let
179 source ${./zshrc} 194 zshrc = pkgs.resholve.mkDerivation {
195 pname = "zshrc";
196 version = "0.0.0";
197
198 src = ./zshrc;
199
200 dontUnpack = true;
201 dontConfigure = true;
202 dontBuild = true;
203
204 installPhase = ''
205 mkdir -p $out/share
206 install "$src" $out/share/zshrc
207 '';
208
209 solutions = {
210 default = {
211 scripts = [ "share/zshrc" ];
212 interpreter = "none";
213 inputs = with pkgs; [
214 coreutils
215 rpm
216 binutils
217 squashfsTools
218 unzip
219 cfg.programs.git.package
220 magickWrapped
221 curl
222 file
223 gnutar
224 cpio
225 magic-wormhole
226 cfg.programs.zsh.package
227 fuse
228 util-linux
229 findutils
230 qrencode
231 tty-clock
232 cfg.programs.jq.package
233 eza
234 less
235 config.systemd.package
236 config.programs.ssh.package
237 gnused
238 miniserve
239 p7zip
240 ];
241 execer = with pkgs; [
242 "cannot:${lib.getExe' rpm "rpm2cpio"}"
243 "cannot:${lib.getExe' squashfsTools "unsquashfs"}"
244 "cannot:${lib.getExe' unzip "unzip"}"
245 "cannot:${lib.getExe cfg.programs.git.package}"
246 "cannot:${lib.getExe cpio}"
247 "cannot:${lib.getExe' magic-wormhole "wormhole"}"
248 "cannot:${lib.getExe' fuse "fusermount"}"
249 "cannot:${lib.getExe less}"
250 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
251 "cannot:${lib.getExe config.programs.ssh.package}"
252 "cannot:${lib.getExe' p7zip "7z"}"
253 ];
254 wrapper = with pkgs; [
255 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
256 ];
257 fake = {
258 builtin = ["print"];
259 external = ["sudo" "umount"];
260 };
261 };
262 };
263 };
264 magickWrapped = pkgs.symlinkJoin {
265 inherit (pkgs.imagemagick) name;
266 paths = [ pkgs.imagemagick ];
267
268 buildInputs = with pkgs; [ makeWrapper ];
269 postBuild = ''
270 wrapProgram $out/bin/magick \
271 --prefix PATH : ${lib.makeBinPath (with pkgs; [ ghostscript ])}
272 '';
273 };
274 in ''
275 source ${zshrc}/share/zshrc
180 ''; 276 '';
181 zsh.dirHashes = let 277 zsh.dirHashes = let
182 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs; 278 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs;
183 inputNames = { 279 inputNames = {
184 "nixpkgs" = "nixos";
185 }; 280 };
186 in flakeHashes // { 281 in flakeHashes // {
187 u2w = "$HOME/projects/uni2work"; 282 u2w = "$HOME/projects/uni2work";
@@ -193,6 +288,16 @@ in {
193 pro = "$HOME/projects/pro"; 288 pro = "$HOME/projects/pro";
194 media = "$HOME/media"; 289 media = "$HOME/media";
195 }; 290 };
291 jq.colors = {
292 arrays = "1;37";
293 "false" = "0;37";
294 "null" = "2;37";
295 numbers = "0;37";
296 objectKeys = "1;34";
297 objects = "1;37";
298 strings = "0;32";
299 "true" = "0;37";
300 };
196 301
197 obs-studio = { 302 obs-studio = {
198 enable = true; 303 enable = true;
@@ -202,7 +307,7 @@ in {
202 gh = { 307 gh = {
203 enable = true; 308 enable = true;
204 settings = { 309 settings = {
205 editor = lib.getExe' config.home-manager.users.${userName}.programs.emacs.package "emacsclient"; 310 editor = lib.getExe' editor "emacsclient";
206 gitProtocol = "ssh"; 311 gitProtocol = "ssh";
207 }; 312 };
208 }; 313 };
@@ -228,16 +333,10 @@ in {
228 # notify_on_cmd_finish = "invisible 120"; 333 # notify_on_cmd_finish = "invisible 120";
229 }; 334 };
230 keybindings = { 335 keybindings = {
231 "kitty_mod+n" = "detach_window"; 336 "kitty_mod+n" = "new_os_window_with_cwd";
232 "kitty_mod+m" = "detach_window ask"; 337 "kitty_mod+m" = "detach_window ask";
233 }; 338 "kitty_mod+enter" = "new_window_with_cwd";
234 }; 339 "kitty_mod+t" = "new_tab_with_cwd";
235 wpaperd = {
236 enable = true;
237 settings.default = {
238 path = "~/.wallpapers";
239 duration = "15m";
240 mode = "center";
241 }; 340 };
242 }; 341 };
243 fuzzel = { 342 fuzzel = {
@@ -250,7 +349,7 @@ in {
250 font = "Fira Sans"; 349 font = "Fira Sans";
251 }; 350 };
252 colors = { 351 colors = {
253 background = "000000aa"; 352 background = "000000cc";
254 text = "cdd6f4ff"; 353 text = "cdd6f4ff";
255 match = "94e2d5ff"; 354 match = "94e2d5ff";
256 selection = "585b70ff"; 355 selection = "585b70ff";
@@ -267,15 +366,28 @@ in {
267 enable = true; 366 enable = true;
268 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."]; 367 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."];
269 }; 368 };
369 nushell = {
370 enable = true;
371 settings.show_banner = false;
372 };
373 fd.enable = true;
270 }; 374 };
271 375
272 services = { 376 services = {
377 wpaperd = {
378 enable = false;
379 settings.default = {
380 path = "~/.wallpapers";
381 duration = "15m";
382 mode = "center";
383 };
384 };
273 emacs = { 385 emacs = {
274 enable = true; 386 enable = true;
275 socketActivation.enable = true; 387 socketActivation.enable = true;
276 client = { 388 client = {
277 enable = true; 389 enable = true;
278 arguments = mkForce ["--reuse-frame" "--alternate-editor" "\"\""]; 390 arguments = mkForce ["--create-frame" "--alternate-editor" (lib.getExe cfg.services.emacs.package)];
279 }; 391 };
280 }; 392 };
281 gpg-agent = { 393 gpg-agent = {
@@ -338,20 +450,6 @@ in {
338 serverUrl = "https://etesync.yggdrasil.li"; 450 serverUrl = "https://etesync.yggdrasil.li";
339 }; 451 };
340 452
341 swayidle = {
342 enable = true;
343 events = [
344 { event = "before-sleep"; command = lockCommand; }
345 { event = "lock"; command = lockCommand; }
346 ];
347 timeouts = [
348 { timeout = 600; command = lockCommand; }
349 ];
350 extraArgs = [
351 "-w"
352 "idlehint" "30"
353 ];
354 };
355 poweralertd.enable = true; 453 poweralertd.enable = true;
356 }; 454 };
357 455
@@ -383,6 +481,15 @@ in {
383 name = "Paper-Mono-Dark"; 481 name = "Paper-Mono-Dark";
384 }; 482 };
385 }; 483 };
484 qt.enable = true;
485 qt.platformTheme.name = "gtk";
486
487 qt.kde.settings = {
488 kwalletrc = {
489 KSecretD.Enabled = false;
490 Wallet."Default Wallet" = "store";
491 };
492 };
386 493
387 xsession.preferStatusNotifierItems = true; 494 xsession.preferStatusNotifierItems = true;
388 495
@@ -392,20 +499,19 @@ in {
392 packages = with pkgs; [ 499 packages = with pkgs; [
393 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 500 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
394 mumble pulseaudio-ctl pamixer libnotify screen-message 501 mumble pulseaudio-ctl pamixer libnotify screen-message
395 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 502 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
396 thunderbird zoom-us xdg-desktop-portal steam steam-run 503 thunderbird zoom-us xdg-desktop-portal steam steam-run
397 wireshark virt-manager rclone cached-nix-shell worktime 504 wireshark virt-manager rclone cached-nix-shell worktime
398 fira-code-symbols libreoffice xournalpp google-chrome 505 fira-code-symbols libreoffice xournalpp
399 nixos-shell virt-viewer freerdp gnome-icon-theme 506 nixos-shell virt-viewer freerdp gnome-icon-theme
400 paper-icon-theme sshpassSecret weechat element-desktop 507 paper-icon-theme sshpassSecret weechat element-desktop
401 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs 508 sieve-connect gimp3 inkscape udiskie glab nitrokey-app
402 sieve-connect gimp inkscape udiskie glab nitrokey-app
403 pynitrokey gtklock wlrctl remmina openscad spice-record 509 pynitrokey gtklock wlrctl remmina openscad spice-record
404 libguestfs-with-appliance nerd-fonts.fira-mono 510 nerd-fonts.fira-mono
405 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 511 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
406 swtpm (hunspellWithDicts (with hunspellDicts; [en_GB-large de_DE])) 512 swtpm (hunspell.withDicts (dicts: with dicts; [en_GB-large de_DE]))
407 # synadm 513 libation libqalculate
408 ]; 514 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; });
409 515
410 file = { 516 file = {
411 ".backup-munin".source = ./backup-patterns; 517 ".backup-munin".source = ./backup-patterns;
@@ -422,15 +528,12 @@ in {
422 sessionVariables = { 528 sessionVariables = {
423 # GDK_SCALE = 96.0 / 282.0; 529 # GDK_SCALE = 96.0 / 282.0;
424 # QT_AUTO_SCREEN_SCALE_FACTOR = 1; 530 # QT_AUTO_SCREEN_SCALE_FACTOR = 1;
425 QT_QPA_PLATFORMTHEME = "qt5ct"; 531 QT_QPA_PLATFORMTHEME = lib.mkForce "gtk3";
426 LIBVIRT_DEFAULT_URI = "qemu:///system"; 532 LIBVIRT_DEFAULT_URI = "qemu:///system";
427 STACK_XDG = 1; 533 STACK_XDG = 1;
428 EDITOR = pkgs.writeShellScript "editor" '' 534 EDITOR = lib.getExe' editor "emacsclient";
429 args=("--reuse-frame" "--alternate-editor" "")
430 args+=("$@")
431 exec -a emacsclient ${lib.getExe' cfg.services.emacs.package "emacsclient"} "''${args[@]}"
432 '';
433 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone"; 535 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
536 SYSTEMD_TINT_BACKGROUND = "false";
434 }; 537 };
435 538
436 extraProfileCommands = '' 539 extraProfileCommands = ''
@@ -467,13 +570,21 @@ in {
467 General = { 570 General = {
468 dot_as_separator = 0; 571 dot_as_separator = 0;
469 }; 572 };
573 Mode = {
574 calculate_as_you_type = 1;
575 };
470 }; 576 };
471 }; 577 };
472 "emacs/init.el".source = ./emacs.el; 578 "emacs/init.el".source = pkgs.substitute {
473 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = '' 579 src = ./emacs.el;
474 [Unit] 580 substitutions = [
475 After=graphical-session.target 581 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
476 ''; 582 ];
583 };
584 # "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
585 # [Unit]
586 # After=graphical-session.target
587 # '';
477 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = '' 588 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = ''
478 [Unit] 589 [Unit]
479 Before=graphical-session-pre.target 590 Before=graphical-session-pre.target
@@ -487,6 +598,8 @@ in {
487 xdg.dataFile = { 598 xdg.dataFile = {
488 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 599 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
489 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 600 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service";
601 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
602 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
490 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 603 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
491 inherit (sources.emoji-data) pname src; 604 inherit (sources.emoji-data) pname src;
492 version = lib.removePrefix "v" sources.emoji-data.version; 605 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -574,11 +687,11 @@ in {
574 exec -- \ 687 exec -- \
575 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \ 688 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
576 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 689 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
577 --property 'Environment=DSCP=46' \ 690 -E DSCP=46 -E NIXOS_OZONE_WL \
578 -- ${lib.getExe pkgs.dscp} ${lib.getExe' pkgs.google-chrome "google-chrome-stable"} \ 691 -- ${lib.getExe pkgs.dscp} ${lib.getExe cfg.programs.chromium.package} \
579 --class=Rainbow \ 692 --class=Rainbow \
580 --kiosk "https://web.openrainbow.com" \ 693 --app="https://web.openrainbow.com" \
581 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 694 --user-data-dir=''${HOME}/.config/chromium-rainbow
582 ''); 695 '');
583 icon = pkgs.fetchurl { 696 icon = pkgs.fetchurl {
584 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 697 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
@@ -588,6 +701,53 @@ in {
588 StartupWMClass = "Rainbow"; 701 StartupWMClass = "Rainbow";
589 }; 702 };
590 }; 703 };
704 kimai = {
705 name = "Kimai";
706 exec = toString (pkgs.writeShellScript "kimai" ''
707 exec -- \
708 ${lib.getExe cfg.programs.chromium.package} \
709 --class=Kimai \
710 --app="https://kimai.yggdrasil.li" \
711 --user-data-dir=''${HOME}/.config/chromium-kimai
712 '');
713 icon = pkgs.fetchurl {
714 url = "https://www.kimai.org/images/kimai_logo.png";
715 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
716 };
717 settings = {
718 StartupWMClass = "Kimai";
719 StartupNotify = "true";
720 };
721 };
722 audiobookshelf = {
723 name = "Audiobookshelf";
724 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
725 exec -- \
726 ${lib.getExe cfg.programs.chromium.package} \
727 --class=Audiobookshelf \
728 --app="https://audiobookshelf.yggdrasil.li" \
729 --user-data-dir=''${HOME}/.config/chromium-audiobookshelf
730 '');
731 icon = pkgs.fetchurl {
732 url = "https://www.audiobookshelf.org/Logo.png";
733 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
734 };
735 settings = {
736 StartupWMClass = "Audiobookshelf";
737 StartupNotify = "true";
738 };
739 };
740 thunderbird-lmu = {
741 name = "Thunderbird (LMU)";
742 exec = "thunderbird --name thunderbird -P lmu %U";
743 icon = "thunderbird";
744 genericName = "Email Client";
745 categories = [ "Network" "Chat" "Email" "Feed" "GTK" "News" ];
746 settings = {
747 StartupWMClass = "thunderbird";
748 StartupNotify = "true";
749 };
750 };
591 }; 751 };
592 752
593 fonts = { 753 fonts = {
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index 5cee16b0..3beefba6 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -51,7 +51,7 @@
51 51
52;; (require 'scratch) 52;; (require 'scratch)
53(global-set-key (kbd "C-x B") 'scratch-create) 53(global-set-key (kbd "C-x B") 'scratch-create)
54(setq initial-major-mode 'scratch-mode) 54;; (setq initial-major-mode 'scratch-mode)
55(setq initial-scratch-message "") 55(setq initial-scratch-message "")
56 56
57(global-set-key (kbd "C-x K") 'kill-current-buffer) 57(global-set-key (kbd "C-x K") 'kill-current-buffer)
@@ -254,3 +254,5 @@ necessarily running."
254(bind-key "C-x C-m" #'move-file) 254(bind-key "C-x C-m" #'move-file)
255 255
256(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock)) 256(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock))
257(setenv "SSH_ASKPASS_REQUIRE" "prefer")
258(setenv "SSH_ASKPASS" "@ksshaskpass@")
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 216a98ea..5ae372c1 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -3,13 +3,10 @@ let
3 cfg = config.programs.niri; 3 cfg = config.programs.niri;
4 4
5 kdl = flakeInputs.niri-flake.lib.kdl; 5 kdl = flakeInputs.niri-flake.lib.kdl;
6 sleaf = name: arg: kdl.node name [arg] [];
6 7
7 niri = cfg.package; 8 niri = cfg.package;
8 terminal = lib.getExe config.programs.kitty.package; 9 terminal = lib.getExe config.programs.kitty.package;
9 makoctl = lib.getExe' config.services.mako.package "makoctl";
10 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
11 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
12 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
13 10
14 focus_or_spawn = pkgs.writeShellApplication { 11 focus_or_spawn = pkgs.writeShellApplication {
15 name = "focus-or-spawn"; 12 name = "focus-or-spawn";
@@ -35,7 +32,11 @@ let
35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then 32 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
36 niri msg action focus-workspace-previous 33 niri msg action focus-workspace-previous
37 else 34 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 35 if [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].is_focused' <<<"$workspaces_json") != "true" ]] && [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].id' <<<"$workspaces_json") = $(jq -r '.workspace_id' <<<"$window_json") ]]; then
36 niri msg action focus-workspace "$workspace_name"
37 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
39 fi
39 fi 40 fi
40 exit 0 41 exit 0
41 fi 42 fi
@@ -45,7 +46,6 @@ let
45 ''; 46 '';
46 }; 47 };
47 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn); 48 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
48 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
49 49
50 with_adjacent_workspace = pkgs.writeShellApplication { 50 with_adjacent_workspace = pkgs.writeShellApplication {
51 name = "with-adjacent-workspace"; 51 name = "with-adjacent-workspace";
@@ -84,7 +84,7 @@ let
84 }; 84 };
85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$"; 85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
88 88
89 with_unnamed_workspace = pkgs.writeShellApplication { 89 with_unnamed_workspace = pkgs.writeShellApplication {
90 name = "with-unnamed-workspace"; 90 name = "with-unnamed-workspace";
@@ -131,7 +131,7 @@ let
131 131
132 windows_json="$(niri msg -j windows)" 132 windows_json="$(niri msg -j windows)"
133 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")" 133 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
134 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu --index)" 134 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --width=60 --log-level=warning --dmenu --index)"
135 # shellcheck disable=SC2016 135 # shellcheck disable=SC2016
136 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")" 136 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
137 137
@@ -141,13 +141,26 @@ let
141 ''; 141 '';
142 }; 142 };
143 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window); 143 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
144in {
145 imports = [
146 ./waybar.nix
147 ./mako.nix
148 ./swayosd.nix
149 ];
150 144
145 with_predicate_window = pred: pkgs.writeShellApplication {
146 name = "with-predicate-window";
147 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
148 text = ''
149 action="$1"
150 shift
151
152 windows_json="$(niri msg -j windows)"
153 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
154
155 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
156
157 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
158 '';
159 };
160
161 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
162 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
163in {
151 options = { 164 options = {
152 programs.niri.scratchspaces = lib.mkOption { 165 programs.niri.scratchspaces = lib.mkOption {
153 type = lib.types.listOf (lib.types.submodule ({ config, ... }: { 166 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
@@ -171,6 +184,17 @@ in {
171 type = lib.types.nullOr lib.types.str; 184 type = lib.types.nullOr lib.types.str;
172 default = null; 185 default = null;
173 }; 186 };
187 moveKey = lib.mkOption {
188 type = lib.types.nullOr lib.types.str;
189 default = let
190 keys = lib.splitString "+" config.key;
191 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
192 (lib.take (lib.length keys - 1) keys)
193 ["Shift"]
194 (lib.takeEnd 1 keys)
195 ]);
196 in if config.key == null then null else defMoveKey;
197 };
174 spawn = lib.mkOption { 198 spawn = lib.mkOption {
175 type = lib.types.nullOr (lib.types.listOf lib.types.str); 199 type = lib.types.nullOr (lib.types.listOf lib.types.str);
176 default = null; 200 default = null;
@@ -197,36 +221,7 @@ in {
197 }; 221 };
198 222
199 config = { 223 config = {
200 systemd.user.services.xwayland-satellite = { 224 home.packages = [ pkgs.xwayland-satellite-unstable ];
201 Unit = {
202 BindsTo = [ "graphical-session.target" ];
203 PartOf = [ "graphical-session.target" ];
204 After = [ "graphical-session.target" ];
205 Requisite = [ "graphical-session.target" ];
206 };
207 Service = {
208 Type = "notify";
209 NotifyAccess = "all";
210 Environment = [ "DISPLAY=:0" ];
211 ExecStart = ''${lib.getExe pkgs.xwayland-satellite-unstable} ''${DISPLAY}'';
212 ExecStartPre = "${systemctl} --user import-environment DISPLAY";
213 StandardOutput = "journal";
214 };
215 Install = {
216 WantedBy = [ "graphical-session.target" ];
217 };
218 };
219
220 services.swayidle = {
221 events = [
222 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
223 ];
224 timeouts = [
225 { timeout = 540;
226 command = "${lib.getExe niri} msg action power-off-monitors";
227 }
228 ];
229 };
230 225
231 systemd.user.sockets.niri-workspace-history = { 226 systemd.user.sockets.niri-workspace-history = {
232 Socket = { 227 Socket = {
@@ -245,11 +240,11 @@ in {
245 Service = { 240 Service = {
246 Type = "simple"; 241 Type = "simple";
247 Sockets = [ "niri-workspace-history.socket" ]; 242 Sockets = [ "niri-workspace-history.socket" ];
248 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} '' 243 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
249 import os 244 import os
250 import socket 245 import socket
251 import json 246 import json
252 import sys 247 # import sys
253 from collections import defaultdict 248 from collections import defaultdict
254 from threading import Thread, Lock 249 from threading import Thread, Lock
255 from socketserver import StreamRequestHandler, ThreadingTCPServer 250 from socketserver import StreamRequestHandler, ThreadingTCPServer
@@ -273,11 +268,9 @@ in {
273 workspaces = list() 268 workspaces = list()
274 269
275 def focus_workspace(output, workspace): 270 def focus_workspace(output, workspace):
276 global workspace_history, history_lock
277
278 with history_lock: 271 with history_lock:
279 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501 272 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
280 print(json.dumps(workspace_history), file=sys.stderr) 273 # print(json.dumps(workspace_history), file=sys.stderr)
281 274
282 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 275 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
283 sock.connect(os.environ["NIRI_SOCKET"]) 276 sock.connect(os.environ["NIRI_SOCKET"])
@@ -299,16 +292,14 @@ in {
299 292
300 class RequestHandler(StreamRequestHandler): 293 class RequestHandler(StreamRequestHandler):
301 def handle(self): 294 def handle(self):
302 global workspace_history, history_lock 295 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
303
304 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
305 with history_lock: 296 with history_lock:
306 json.dump(workspace_history, out) 297 json.dump(workspace_history, out)
307 298
308 299
309 class Server(ThreadingTCPServer): 300 class Server(ThreadingTCPServer):
310 def __init__(self): 301 def __init__(self):
311 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501 302 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
312 self.socket = socket.fromfd(3, self.address_family, self.socket_type) 303 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
313 304
314 305
@@ -334,6 +325,79 @@ in {
334 ''; 325 '';
335 }; 326 };
336 }; 327 };
328 systemd.user.services.niri-workspace-sort = {
329 Unit = {
330 BindsTo = [ "niri.service" ];
331 After = [ "niri.service" ];
332 };
333 Install = {
334 WantedBy = [ "niri.service" ];
335 };
336 Service = {
337 Type = "simple";
338 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
339 import os
340 import sys
341 import socket
342 import json
343
344 outputs = None
345 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
346
347
348 class Niri(socket.socket):
349 def __init__(self):
350 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
351 super().connect(os.environ["NIRI_SOCKET"])
352 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
353
354 def cmd(self, obj):
355 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
356
357 def event_stream(self):
358 self.cmd("EventStream")
359 return self.fh
360
361
362 with Niri() as niri, Niri().event_stream() as niri_stream:
363 for line in niri_stream:
364 workspaces = None
365 if line_json := json.loads(line):
366 if "WorkspacesChanged" in line_json:
367 workspaces = line_json["WorkspacesChanged"]["workspaces"]
368
369 if workspaces is None:
370 continue
371
372 old_outputs = outputs
373 outputs = {ws["output"] for ws in workspaces}
374 if old_outputs is None:
375 print("Initial outputs: {}".format(outputs), file=sys.stderr)
376 continue
377
378 new_outputs = outputs - old_outputs
379 if not new_outputs:
380 continue
381 print("New outputs: {}".format(new_outputs), file=sys.stderr)
382
383 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
384 target_output = next(iter(outputs - set(only.keys())))
385 if not target_output:
386 continue
387 for ws in relevant_workspaces:
388 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
389 if ws["output"] not in set(only.keys()):
390 continue
391 if ws_ident in only[ws["output"]]:
392 continue
393
394 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
395 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
396 '';
397 Restart = "on-failure";
398 RestartSec = 10;
399 };
400 };
337 401
338 programs.niri.scratchspaces = [ 402 programs.niri.scratchspaces = [
339 { name = "pwctl"; 403 { name = "pwctl";
@@ -347,8 +411,8 @@ in {
347 { title = "^Access Request.*"; } 411 { title = "^Access Request.*"; }
348 { title = ".*Passkey credentials$"; } 412 { title = ".*Passkey credentials$"; }
349 ]; 413 ];
350 windowRuleExtra = [ 414 windowRuleExtra = with kdl; [
351 (kdl.leaf "open-focused" false) 415 (sleaf "open-focused" false)
352 ]; 416 ];
353 key = "Mod+Control+P"; 417 key = "Mod+Control+P";
354 app-id = "org.keepassxc.KeePassXC"; 418 app-id = "org.keepassxc.KeePassXC";
@@ -375,6 +439,20 @@ in {
375 app-id = "com.github.wwmm.easyeffects"; 439 app-id = "com.github.wwmm.easyeffects";
376 spawn = [ "easyeffects" ]; 440 spawn = [ "easyeffects" ];
377 } 441 }
442 { name = "time";
443 key = "Mod+Control+K";
444 app-id = "chrome-kimai.yggdrasil.li__-Default";
445 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
446 interpreter = pkgs.runtimeShell;
447 inputs = [ pkgs.dex ];
448 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
449 } ''
450 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
451 '')) ];
452 windowRuleExtra = with kdl; [
453 (sleaf "block-out-from" "screencast")
454 ];
455 }
378 ]; 456 ];
379 programs.niri.config = 457 programs.niri.config =
380 let 458 let
@@ -384,10 +462,12 @@ in {
384 then v 462 then v
385 else null; 463 else null;
386 opt-props = lib.filterAttrs (lib.const (value: value != null)); 464 opt-props = lib.filterAttrs (lib.const (value: value != null));
465 normalize-nodes = nodes: lib.remove null (lib.flatten nodes);
387 in 466 in
388 [ (flag "prefer-no-csd") 467 normalize-nodes [
468 (flag "prefer-no-csd")
389 469
390 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png") 470 (sleaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
391 471
392 (plain "hotkey-overlay" [ 472 (plain "hotkey-overlay" [
393 (flag "skip-at-startup") 473 (flag "skip-at-startup")
@@ -395,80 +475,88 @@ in {
395 475
396 (plain "input" [ 476 (plain "input" [
397 (plain "keyboard" [ 477 (plain "keyboard" [
398 (leaf "repeat-delay" 300) 478 (sleaf "repeat-delay" 300)
399 (leaf "repeat-rate" 50) 479 (sleaf "repeat-rate" 50)
400 480
401 (plain "xkb" [ 481 (plain "xkb" [
402 (leaf "layout" "us,us") 482 (sleaf "layout" "us,us")
403 (leaf "variant" "dvp,") 483 (sleaf "variant" "dvp,")
404 (leaf "options" "compose:caps,grp:win_space_toggle") 484 (sleaf "options" "compose:caps,grp:win_space_toggle")
405 ]) 485 ])
406 ]) 486 ])
407 487
408 (flag "workspace-auto-back-and-forth") 488 (flag "workspace-auto-back-and-forth")
409 # (leaf "focus-follows-mouse" {}) 489 # (sleaf "focus-follows-mouse" {})
410 # (flag "warp-mouse-to-focus") 490 # (flag "warp-mouse-to-focus")
411 491
412 # (plain "touchpad" [ (flag "off") ]) 492 # (plain "touchpad" [ (flag "off") ])
413 (plain "trackball" [ 493 (plain "trackball" [
414 (leaf "scroll-method" "on-button-down") 494 (sleaf "scroll-method" "on-button-down")
415 (leaf "scroll-button" 278) 495 (sleaf "scroll-button" 278)
416 ]) 496 ])
417 (plain "touch" [ 497 (plain "touch" [
418 (leaf "map-to-output" "eDP-1") 498 (sleaf "map-to-output" "eDP-1")
419 ]) 499 ])
420 ]) 500 ])
421 501
422 (plain "environment" (lib.mapAttrsToList leaf { 502 (plain "gestures" [
503 (plain "hot-corners" [(flag "off")])
504 ])
505
506 (plain "environment" (lib.mapAttrsToList sleaf {
423 NIXOS_OZONE_WL = "1"; 507 NIXOS_OZONE_WL = "1";
424 QT_QPA_PLATFORM = "wayland"; 508 QT_QPA_PLATFORM = "wayland";
425 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; 509 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
426 GDK_BACKEND = "wayland"; 510 GDK_BACKEND = "wayland";
427 SDL_VIDEODRIVER = "wayland"; 511 SDL_VIDEODRIVER = "wayland";
428 DISPLAY = ":0"; 512 DISPLAY = ":0";
513 ELECTRON_OZONE_PLATFORM_HINT = "auto";
514 SSH_ASKPASS_REQUIRE = "prefer";
515 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
516 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
429 })) 517 }))
430 518
431 (node "output" "eDP-1" [ 519 (node "output" ["eDP-1"] [
432 (leaf "scale" 1.5) 520 (sleaf "scale" 1.5)
433 (leaf "position" { x = 0; y = 0; }) 521 (sleaf "position" { x = 0; y = 0; })
434 ]) 522 ])
435 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [ 523 (node "output" ["Ancor Communications Inc ASUS PB287Q 0x0000DD9B"] [
436 (leaf "scale" 1.5) 524 (sleaf "scale" 1.5)
437 (leaf "position" { x = 2560; y = 0; }) 525 (sleaf "position" { x = 2560; y = 0; })
438 ]) 526 ])
439 (node "output" "HP Inc. HP 727pu CN4417143K" [ 527 (node "output" ["HP Inc. HP 727pu CN4417143K"] [
440 (leaf "mode" "2560x1440@119.998") 528 (sleaf "mode" "2560x1440@119.998")
441 (leaf "scale" 1) 529 (sleaf "scale" 1)
442 (leaf "position" { x = 2560; y = 0; }) 530 (sleaf "position" { x = 2560; y = 0; })
443 (flag "variable-refresh-rate") 531 (flag "variable-refresh-rate")
444 ]) 532 ])
445 533
446 (plain "debug" [ 534 (plain "debug" [
447 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render") 535 (sleaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
448 ]) 536 ])
449 537
450 (plain "animations" [ 538 (plain "animations" [
451 (leaf "slowdown" 0.5) 539 (sleaf "slowdown" 0.5)
452 (plain "workspace-switch" [(flag "off")]) 540 (plain "workspace-switch" [(flag "off")])
453 ]) 541 ])
454 542
455 (plain "layout" [ 543 (plain "layout" [
456 (leaf "gaps" 8) 544 (sleaf "gaps" 8)
457 (plain "struts" [ 545 (plain "struts" [
458 (leaf "left" 0) 546 (sleaf "left" 26)
459 (leaf "right" 0) 547 (sleaf "right" 26)
460 (leaf "top" 0) 548 (sleaf "top" 0)
461 (leaf "bottom" 0) 549 (sleaf "bottom" 0)
462 ]) 550 ])
463 (plain "border" [ 551 (plain "border" [
464 (leaf "width" 2) 552 (sleaf "width" 2)
465 (leaf "active-gradient" { 553 (sleaf "active-gradient" {
466 from = "hsla(195 100% 45% 1)"; 554 from = "hsla(195 100% 45% 1)";
467 to = "hsla(155 100% 37.5% 1)"; 555 to = "hsla(155 100% 37.5% 1)";
468 angle = 29; 556 angle = 29;
469 relative-to = "workspace-view"; 557 relative-to = "workspace-view";
470 }) 558 })
471 (leaf "inactive-gradient" { 559 (sleaf "inactive-gradient" {
472 from = "hsla(0 0% 27.7% 1)"; 560 from = "hsla(0 0% 27.7% 1)";
473 to = "hsla(0 0% 23% 1)"; 561 to = "hsla(0 0% 23% 1)";
474 angle = 29; 562 angle = 29;
@@ -479,29 +567,29 @@ in {
479 (flag "off") 567 (flag "off")
480 ]) 568 ])
481 569
482 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [ 570 (plain "preset-column-widths" (map (prop: sleaf "proportion" prop) [
483 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.) 571 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
484 ])) 572 ]))
485 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ]) 573 (plain "default-column-width" [ (sleaf "proportion" (1. / 2.)) ])
486 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [ 574 (plain "preset-window-heights" (map (prop: sleaf "proportion" prop) [
487 (1. / 3.) (1. / 2.) (2. / 3.) (1.) 575 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
488 ])) 576 ]))
489 577
490 (flag "always-center-single-column") 578 (flag "always-center-single-column")
491 579
492 (plain "tab-indicator" [ 580 (plain "tab-indicator" [
493 (leaf "gap" 4) 581 (sleaf "gap" 4)
494 (leaf "width" 8) 582 (sleaf "width" 8)
495 (leaf "gaps-between-tabs" 4) 583 (sleaf "gaps-between-tabs" 4)
496 (flag "place-within-column") 584 (flag "place-within-column")
497 (leaf "length" { total-proportion = 1.; }) 585 (sleaf "length" { total-proportion = 1.; })
498 (leaf "active-gradient" { 586 (sleaf "active-gradient" {
499 from = "hsla(195 100% 60% 0.75)"; 587 from = "hsla(195 100% 60% 0.75)";
500 to = "hsla(155 100% 50% 0.75)"; 588 to = "hsla(155 100% 50% 0.75)";
501 angle = 29; 589 angle = 29;
502 relative-to = "workspace-view"; 590 relative-to = "workspace-view";
503 }) 591 })
504 (leaf "inactive-gradient" { 592 (sleaf "inactive-gradient" {
505 from = "hsla(0 0% 42% 0.66)"; 593 from = "hsla(0 0% 42% 0.66)";
506 to = "hsla(0 0% 35% 0.66)"; 594 to = "hsla(0 0% 35% 0.66)";
507 angle = 29; 595 angle = 29;
@@ -515,127 +603,140 @@ in {
515 ]) 603 ])
516 604
517 (map (name: 605 (map (name:
518 (node "workspace" name [ 606 (node "workspace" [name] [
519 (leaf "open-on-output" "eDP-1") 607 (sleaf "open-on-output" "eDP-1")
520 ]) 608 ])
521 ) (map ({name, ...}: name) cfg.scratchspaces)) 609 ) (map ({name, ...}: name) cfg.scratchspaces))
522 (map (name: 610 (map (name:
523 (leaf "workspace" name) 611 (sleaf "workspace" name)
524 ) ["comm" "web" "vid" "bmr"]) 612 ) ["comm" "web" "vid" "bmr"])
525 613
526 (plain "window-rule" [ 614 (plain "window-rule" [
527 (leaf "clip-to-geometry" true) 615 (sleaf "clip-to-geometry" true)
528 ]) 616 ])
529 617
530 (plain "window-rule" [ 618 (plain "window-rule" [
531 (leaf "match" { is-floating = true; }) 619 (sleaf "match" { is-floating = true; })
532 (leaf "geometry-corner-radius" 8) 620 (sleaf "geometry-corner-radius" 8)
533 (plain "shadow" [ (flag "on") ]) 621 (plain "shadow" [ (flag "on") ])
534 ]) 622 ])
535 623
536 (plain "window-rule" [ 624 (plain "window-rule" [
537 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; }) 625 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
538 (leaf "block-out-from" "screencast") 626 (sleaf "block-out-from" "screencast")
539 ]) 627 ])
540 (plain "window-rule" [ 628 (plain "window-rule" (normalize-nodes [
541 (map (title: 629 (map (title:
542 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; }) 630 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
543 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"]) 631 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
544 (leaf "open-focused" true) 632 (sleaf "open-focused" true)
545 (leaf "open-floating" true) 633 (sleaf "open-floating" true)
546 ]) 634 ]))
547 635
548 (map ({ name, match, exclude, windowRuleExtra, ... }: 636 (map ({ name, match, exclude, windowRuleExtra, ... }:
549 (optional-node (match != []) (plain "window-rule" [ 637 (optional-node (match != []) (plain "window-rule" (normalize-nodes [
550 (map (leaf "match") match) 638 (map (sleaf "match") match)
551 (map (leaf "exclude") exclude) 639 (map (sleaf "exclude") exclude)
552 (leaf "open-on-workspace" name) 640 (sleaf "open-on-workspace" name)
553 (leaf "open-maximized" true) 641 (sleaf "open-maximized" true)
554 windowRuleExtra 642 windowRuleExtra
555 ])) 643 ])))
556 ) cfg.scratchspaces) 644 ) cfg.scratchspaces)
557 645
558 (plain "window-rule" [ 646 (plain "window-rule" [
559 (leaf "match" { app-id = "^emacs$"; }) 647 (sleaf "match" { app-id = "^emacs$"; })
560 (leaf "match" { app-id = "^firefox$"; }) 648 (sleaf "match" { app-id = "^firefox$"; })
561 (plain "default-column-width" [(leaf "proportion" (2. / 3.))]) 649 (plain "default-column-width" [(sleaf "proportion" (2. / 3.))])
562 ]) 650 ])
563 (plain "window-rule" [ 651 (plain "window-rule" [
564 (leaf "match" { app-id = "^kitty$"; }) 652 (sleaf "match" { app-id = "^kitty$"; })
565 (leaf "match" { app-id = "^kitty-play$"; }) 653 (sleaf "match" { app-id = "^kitty-play$"; })
566 (plain "default-column-width" [(leaf "proportion" (1. / 3.))]) 654 (plain "default-column-width" [(sleaf "proportion" (1. / 3.))])
567 ]) 655 ])
568 656
569 (plain "window-rule" [ 657 (plain "window-rule" [
570 (leaf "match" { app-id = "^thunderbird$"; }) 658 (sleaf "match" { app-id = "^thunderbird$"; })
571 (leaf "match" { app-id = "^Element$"; }) 659 (sleaf "match" { app-id = "^Element$"; })
572 (leaf "match" { app-id = "^Rainbow$"; }) 660 (sleaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
573 (leaf "open-on-workspace" "comm") 661 (sleaf "open-on-workspace" "comm")
574 ]) 662 ])
575 (plain "window-rule" [ 663 (plain "window-rule" [
576 (leaf "match" { app-id = "^firefox$"; }) 664 (sleaf "match" { app-id = "^firefox$"; })
577 (leaf "open-on-workspace" "web") 665 (sleaf "open-on-workspace" "web")
578 (leaf "open-maximized" true) 666 (sleaf "open-maximized" true)
579 ]) 667 ])
580 (plain "window-rule" [ 668 (plain "window-rule" [
581 (leaf "match" { app-id = "^mpv$"; }) 669 (sleaf "match" { app-id = "^mpv$"; })
582 (leaf "open-on-workspace" "vid") 670 (sleaf "open-on-workspace" "vid")
583 (plain "default-column-width" [(leaf "proportion" 1.)]) 671 (plain "default-column-width" [(sleaf "proportion" 1.)])
584 ]) 672 ])
585 (plain "window-rule" [ 673 (plain "window-rule" [
586 (leaf "match" { app-id = "^kitty-play$"; }) 674 (sleaf "match" { app-id = "^kitty-play$"; })
587 (leaf "open-on-workspace" "vid") 675 (sleaf "open-on-workspace" "vid")
588 (leaf "open-focused" false) 676 (sleaf "open-focused" false)
589 ]) 677 ])
590 (plain "window-rule" [ 678 (plain "window-rule" [
591 (leaf "match" { app-id = "^pdfpc$"; }) 679 (sleaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
592 (plain "default-column-width" [(leaf "proportion" 1.)]) 680 (sleaf "match" { app-id = "^YouTube Music Desktop App$"; })
681 (sleaf "open-on-workspace" "vid")
593 ]) 682 ])
594 (plain "window-rule" [ 683 (plain "window-rule" [
595 (leaf "match" { app-id = "^pdfpc$"; title = "^pdfpc - presentation$"; }) 684 (sleaf "match" { app-id = "^pdfpc$"; })
596 (plain "default-column-width" [(leaf "proportion" 1.)]) 685 (plain "default-column-width" [(sleaf "proportion" 1.)])
597 (leaf "open-fullscreen" true)
598 (leaf "open-on-workspace" "bmr")
599 (leaf "open-focused" false)
600 ]) 686 ])
601 (plain "window-rule" [ 687 (plain "window-rule" [
602 (map (leaf "match") [ 688 (sleaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
689 (plain "default-column-width" [(sleaf "proportion" 1.)])
690 (sleaf "open-fullscreen" true)
691 (sleaf "open-on-workspace" "bmr")
692 (sleaf "open-focused" false)
693 ])
694 (plain "window-rule" (normalize-nodes [
695 (map (sleaf "match") [
603 { app-id = "^Gimp-"; title = "^Quit GIMP$"; } 696 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
604 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; } 697 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
605 { app-id = "^xdg-desktop-portal-gtk$"; } 698 { app-id = "^xdg-desktop-portal-gtk$"; }
606 ]) 699 ])
607 (leaf "open-floating" true) 700 (sleaf "open-floating" true)
608 ]) 701 ]))
609 (plain "window-rule" [ 702 (plain "window-rule" [
610 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; }) 703 (sleaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
611 (leaf "match" { app-id = "^evince$"; }) 704 (sleaf "match" { app-id = "^evince$"; })
612 (leaf "default-column-display" "tabbed") 705 (sleaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
706 (sleaf "default-column-display" "tabbed")
613 ]) 707 ])
614 708
615 (plain "layer-rule" [ 709 (plain "layer-rule" [
616 (leaf "match" { namespace = "^notifications$"; }) 710 (sleaf "match" { namespace = "^notifications$"; })
617 (leaf "match" { namespace = "^waybar$"; }) 711 (sleaf "match" { namespace = "^bar$"; })
618 (leaf "match" { namespace = "^launcher$"; }) 712 (sleaf "match" { namespace = "^launcher$"; })
619 (leaf "block-out-from" "screencast") 713 (sleaf "block-out-from" "screencast")
620 ]) 714 ])
621 715
622 (plain "binds" 716 (plain "binds"
623 (let 717 (let
624 bind = name: cfg: node name (opt-props { 718 bind = name: cfg: node name [(lib.removeAttrs cfg ["action"])] (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
625 cooldown-ms = cfg.cooldown-ms or null;
626 }
627 // (lib.optionalAttrs (!(cfg.repeat or true)) {
628 repeat = false;
629 })
630 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
631 allow-when-locked = true;
632 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
633 in 719 in
634 [ 720 normalize-nodes [
635 (lib.mapAttrsToList bind (with config.lib.niri.actions; { 721 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
636 "Mod+Slash".action = show-hotkey-overlay; 722 "Mod+Slash".action = show-hotkey-overlay;
637 723
638 "Mod+Return".action = spawn terminal; 724 "Mod+Return".action = spawn terminal;
725 "Mod+Shift+Return".action =
726 let
727 nushellKitty = pkgs.symlinkJoin {
728 name = "nushell-kitty";
729 paths = [ config.programs.kitty.package ];
730 buildInputs = [ pkgs.makeWrapper ];
731 postBuild = ''
732 wrapProgram $out/bin/kitty \
733 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
734 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
735 shell ${lib.getExe config.programs.nushell.package}
736 ''}"
737 '';
738 };
739 in spawn (lib.getExe' nushellKitty "kitty");
639 "Mod+Q".action = close-window; 740 "Mod+Q".action = close-window;
640 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package); 741 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
641 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path"; 742 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
@@ -671,12 +772,12 @@ in {
671 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat) 772 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
672 $FOUND || echo 773 $FOUND || echo
673 } 774 }
674 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $? 775 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
675 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then 776 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
676 QALC_RES="$FUZZEL_RES" 777 QALC_RES="$FUZZEL_RES"
677 QALC_RET=0 778 QALC_RET=0
678 else 779 else
679 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1) 780 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
680 QALC_RET=$? 781 QALC_RET=$?
681 fi 782 fi
682 [[ -n "$QALC_RES" ]] || exit 1 783 [[ -n "$QALC_RES" ]] || exit 1
@@ -696,18 +797,33 @@ in {
696 notify-send "$QALC_RES" 797 notify-send "$QALC_RES"
697 ''; 798 '';
698 })); 799 }));
800 "Mod+Shift+U".action =
801 let
802 qalcKitty = pkgs.symlinkJoin {
803 name = "qalc-kitty";
804 paths = [ config.programs.kitty.package ];
805 buildInputs = [ pkgs.makeWrapper ];
806 postBuild = ''
807 wrapProgram $out/bin/kitty \
808 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
809 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
810 shell ${lib.getExe pkgs.libqalculate}
811 ''}"
812 '';
813 };
814 in spawn (lib.getExe' qalcKitty "kitty");
699 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication { 815 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
700 name = "emoji-fuzzel"; 816 name = "emoji-fuzzel";
701 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ]; 817 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
702 text = '' 818 text = ''
703 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $? 819 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
704 [[ -n "$FUZZEL_RES" ]] || exit 1 820 [[ -n "$FUZZEL_RES" ]] || exit 1
705 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste 821 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
706 ''; 822 '';
707 })); 823 }));
708 "Print".action = screenshot; 824 "Print".action = screenshot;
709 "Control+Print".action = screenshot-window; 825 "Control+Print".action = screenshot-window;
710 # "Shift+Print".action = screenshot-screen; 826 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
711 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}"; 827 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
712 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}"; 828 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
713 829
@@ -746,22 +862,22 @@ in {
746 "Mod+Shift+Control+C".action = move-workspace-up; 862 "Mod+Shift+Control+C".action = move-workspace-up;
747 863
748 "Mod+ParenLeft".action = focus-workspace "comm"; 864 "Mod+ParenLeft".action = focus-workspace "comm";
749 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm"; 865 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
750 866
751 "Mod+ParenRight".action = focus-workspace "web"; 867 "Mod+ParenRight".action = focus-workspace "web";
752 "Mod+Shift+ParenRight".action = move-column-to-workspace "web"; 868 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
753 869
754 "Mod+BraceRight".action = focus-workspace "read"; 870 "Mod+BraceRight".action = focus-workspace "read";
755 "Mod+Shift+BraceRight".action = move-column-to-workspace "read"; 871 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
756 872
757 "Mod+BraceLeft".action = focus-workspace "mon"; 873 "Mod+BraceLeft".action = focus-workspace "mon";
758 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon"; 874 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
759 875
760 "Mod+Asterisk".action = focus-workspace "vid"; 876 "Mod+Asterisk".action = focus-workspace "vid";
761 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid"; 877 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
762 878
763 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 879 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
764 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 880 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
765 881
766 "Mod+M".action = consume-or-expel-window-left; 882 "Mod+M".action = consume-or-expel-window-left;
767 "Mod+W".action = consume-or-expel-window-right; 883 "Mod+W".action = consume-or-expel-window-right;
@@ -769,10 +885,11 @@ in {
769 "Mod+Shift+M".action = toggle-column-tabbed-display; 885 "Mod+Shift+M".action = toggle-column-tabbed-display;
770 886
771 "Mod+R".action = switch-preset-column-width; 887 "Mod+R".action = switch-preset-column-width;
772 "Mod+Shift+R".action = switch-preset-window-height; 888 "Mod+Shift+R".action = maximize-column;
889 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
773 "Mod+F".action = center-column; 890 "Mod+F".action = center-column;
774 "Mod+Shift+F".action = maximize-column; 891 "Mod+Shift+F".action = toggle-windowed-fullscreen;
775 "Mod+Shift+Ctrl+F".action = fullscreen-window; 892 "Mod+Ctrl+Shift+F".action = fullscreen-window;
776 893
777 "Mod+V".action = switch-focus-between-floating-and-tiling; 894 "Mod+V".action = switch-focus-between-floating-and-tiling;
778 "Mod+Shift+V".action = toggle-window-floating; 895 "Mod+Shift+V".action = toggle-window-floating;
@@ -783,58 +900,77 @@ in {
783 "Mod+Right".action = set-column-width "+10%"; 900 "Mod+Right".action = set-column-width "+10%";
784 901
785 "Mod+Shift+Z" = { 902 "Mod+Shift+Z" = {
786 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors"; 903 action = power-off-monitors;
787 allow-when-locked = true; 904 allow-when-locked = true;
788 }; 905 };
789 "Mod+Shift+L".action = spawn loginctl "lock-session";
790 "Mod+Shift+E".action = quit; 906 "Mod+Shift+E".action = quit;
791 "Mod+Shift+Minus" = { 907
792 action = spawn systemctl "suspend"; 908 # "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
909 # "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
910 # "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
911 # "Mod+Comma".action = spawn makoctl "restore";
912
913 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
914 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
915
916 "Mod+X".action = set-dynamic-cast-window;
917 "Mod+Shift+X".action = set-dynamic-cast-monitor;
918 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
919
920 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
921 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
922
923 "Mod+K".action = spawn (lib.getExe' pkgs.worktime "worktime-ui");
924 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
925 }))
926 (lib.mapAttrsToList (name: cfg: node name [(lib.removeAttrs cfg ["action"])] [cfg.action]) (let
927 shell = obj: leaf "send-unix" [
928 { path = ''''${XDG_RUNTIME_DIR}/shell.sock''; }
929 (builtins.toJSON obj + "\n")
930 ];
931 in {
932 "XF86AudioRaiseVolume" = {
793 allow-when-locked = true; 933 allow-when-locked = true;
934 action = shell { Volume.volume = "up"; };
794 }; 935 };
795 "Mod+Shift+Control+Minus" = { 936 "XF86AudioLowerVolume" = {
796 action = spawn systemctl "hibernate";
797 allow-when-locked = true; 937 allow-when-locked = true;
938 action = shell { Volume.volume = "down"; };
798 }; 939 };
799 "Mod+Shift+P" = { 940 "XF86AudioMute" = {
800 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
801 allow-when-locked = true; 941 allow-when-locked = true;
942 action = shell { Volume.muted = "toggle"; };
802 }; 943 };
803 944 "XF86AudioMicMute" = {
804 "XF86MonBrightnessUp" = {
805 action = spawn swayosd-client "--brightness" "raise";
806 allow-when-locked = true; 945 allow-when-locked = true;
946 action = shell { Volume."mic-muted" = "toggle"; };
807 }; 947 };
808 "XF86MonBrightnessDown" = { 948 "XF86MonBrightnessUp" = {
809 action = spawn swayosd-client "--brightness" "lower"; 949 action = shell { Brightness = "up"; };
810 allow-when-locked = true; 950 allow-when-locked = true;
811 }; 951 };
812 "XF86AudioRaiseVolume" = { 952 "XF86MonBrightnessDown" = {
813 action = spawn swayosd-client "--output-volume" "raise"; 953 action = shell { Brightness = "down"; };
814 allow-when-locked = true; 954 allow-when-locked = true;
815 }; 955 };
816 "XF86AudioLowerVolume" = { 956 "Mod+Shift+L".action = shell { LockSession = {}; };
817 action = spawn swayosd-client "--output-volume" "lower"; 957 "Mod+Shift+Minus" = {
958 action = shell { Suspend = {}; };
818 allow-when-locked = true; 959 allow-when-locked = true;
819 }; 960 };
820 "XF86AudioMute" = { 961 "Mod+Shift+Control+Minus" = {
821 action = spawn swayosd-client "--output-volume" "mute-toggle"; 962 action = shell { Hibernate = {}; };
822 allow-when-locked = true; 963 allow-when-locked = true;
823 }; 964 };
824 "XF86AudioMicMute" = { 965 "Mod+Shift+P" = {
825 action = spawn swayosd-client "--input-volume" "mute-toggle"; 966 action = shell { Mpris = { PauseAll = {}; }; };
826 allow-when-locked = true; 967 allow-when-locked = true;
827 }; 968 };
828 969 "Mod+Semicolon".action = shell { Notifications = { DismissGroup = {}; }; };
829 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group"; 970 "Mod+Shift+Semicolon".action = shell { Notifications = { DismissAll = {}; }; };
830 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
831 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
832 "Mod+Comma".action = spawn makoctl "restore";
833
834 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
835 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
836 })) 971 }))
837 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces) 972 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces)
973 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
838 ] 974 ]
839 )) 975 ))
840 ]; 976 ];
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
index 2788fb82..3d246d96 100644
--- a/accounts/gkleen@sif/niri/mako.nix
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -1,39 +1,32 @@
1{ config, lib, pkgs, ... }: 1{ config, lib, pkgs, ... }:
2{ 2{
3 config = { 3 config = lib.mkIf false {
4 services.mako = { 4 services.mako = {
5 enable = true; 5 enable = true;
6 font = "Fira Sans 10"; 6 settings = {
7 format = "<i>%s</i>\\n%b"; 7 font = "Fira Sans 10";
8 margin = "2"; 8 format = "<i>%s</i>\\n%b";
9 maxVisible = -1; 9 margin = "2";
10 backgroundColor = "#000000dd"; 10 max-visible = -1;
11 progressColor = "source #223544ff"; 11 background-color = "#000000dd";
12 width = 384; 12 progress-color = "source #223544ff";
13 extraConfig = '' 13 width = 384;
14 outer-margin=1 14 outer-margin = 1;
15 max-history=100 15 max-history = 100;
16 max-icon-size=48 16 max-icon-size = 48;
17 17
18 [grouped] 18 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
19 format=<b>(%g)</b> <i>%s</i>\n%b 19 "urgency=low".text-color = "#999999ff";
20 20 "urgency=critical".background-color = "#900000dd";
21 [urgency=low] 21 "app-name=Element".group-by = "summary";
22 text-color=#999999ff 22 "app-name=poweralertd" = {
23 23 history = false;
24 [urgency=critical] 24 ignore-timeout = true;
25 background-color=#900000dd 25 default-timeout = 2000;
26 26 };
27 [app-name=Element] 27 "app-name=worktime".history = false;
28 group-by=summary 28 "mode=silent".invisible = true;
29 29 };
30 [app-name=poweralertd]
31 ignore-timeout=1
32 default-timeout=2000
33
34 [mode=silent]
35 invisible=1
36 '';
37 package = pkgs.symlinkJoin { 30 package = pkgs.symlinkJoin {
38 name = "${pkgs.mako.name}-wrapped"; 31 name = "${pkgs.mako.name}-wrapped";
39 paths = with pkgs; [ mako ]; 32 paths = with pkgs; [ mako ];
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
deleted file mode 100644
index 984927c2..00000000
--- a/accounts/gkleen@sif/niri/swayosd.nix
+++ /dev/null
@@ -1,65 +0,0 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.946154;
7 stylePath = pkgs.runCommand "style.css" {
8 src = pkgs.writeText "style.scss" ''
9 window#osd {
10 padding: 12px 20px;
11 border-radius: 999px;
12 border: none;
13 background: rgba(0, 0, 0, 0.87);
14
15 #container {
16 margin: 16px;
17 }
18
19 image,
20 label {
21 color: rgb(255, 255, 255);
22
23 &:disabled {
24 opacity: 1;
25 color: rgb(84, 84, 84);
26 }
27 }
28
29 progressbar {
30 min-height: 6px;
31 border-radius: 999px;
32 background: transparent;
33 border: none;
34
35 trough, progress {
36 min-height: inherit;
37 border-radius: inherit;
38 border: none;
39 }
40
41 trough {
42 background: rgb(127, 127, 127);
43 }
44 progress {
45 background: rgb(255, 255, 255);
46 }
47
48 &:disabled {
49 opacity: 1;
50
51 trough {
52 background: rgb(19, 19, 19);
53 }
54 progress {
55 background: rgb(38, 38, 38);
56 }
57 }
58 }
59 }
60 '';
61 buildInputs = with pkgs; [sass];
62 } "scss -C --sourcemap=none --style=compact $src $out";
63 };
64 };
65}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
deleted file mode 100644
index bae818f6..00000000
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ /dev/null
@@ -1,347 +0,0 @@
1{ lib, config, pkgs, ... }:
2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
5 config = {
6 programs.waybar = {
7 enable = true;
8 systemd = {
9 enable = true;
10 target = "graphical-session.target";
11 };
12 settings = let
13 windowRewrites = {
14 "(.*) — Mozilla Firefox" = "$1";
15 "(.*) - Mozilla Thunderbird" = "$1";
16 "(.*) - mpv" = "$1";
17 };
18 iconSize = 11;
19 in [
20 {
21 layer = "top";
22 position = "top";
23 height = 14;
24 output = [ "eDP-1" "DP-2" "DP-3" ];
25 modules-left = [ "niri/workspaces" ];
26 modules-center = [ "niri/window" ];
27 modules-right = [ "custom/worktime" "custom/worktime-today"
28 "custom/weather"
29 "custom/keymap"
30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
31
32 "custom/mako" = {
33 format = "{}";
34 return-type = "json";
35 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
36 from dbus_next.aio import MessageBus
37
38 import asyncio
39
40 import json
41
42
43 loop = asyncio.new_event_loop()
44 asyncio.set_event_loop(loop)
45
46
47 async def main():
48 bus = await MessageBus().connect()
49 # the introspection xml would normally be included in your project, but
50 # this is convenient for development
51 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
52
53 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
54 mako = obj.get_interface('fr.emersion.Mako')
55 properties = obj.get_interface('org.freedesktop.DBus.Properties')
56
57 async def print_mode():
58 modes = await mako.get_modes()
59 is_silent = "silent" in modes
60 icon = "&#xf009b;" if is_silent else "&#xf009a;"
61 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
62 if is_silent:
63 text = f"<span color=\"#ffffff\">{text}</span>"
64 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
65
66 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
67 if "Modes" not in invalidated_properties:
68 return
69
70 await print_mode()
71
72 properties.on_properties_changed(on_properties_changed)
73 await print_mode()
74
75 await loop.create_future()
76
77
78 loop.run_until_complete(main())
79 '';
80 on-click = "makoctl mode -t silent";
81 };
82 "custom/weather" = {
83 format = "{}";
84 tooltip = true;
85 interval = 3600;
86 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
87 return-type = "json";
88 };
89 "custom/keymap" = {
90 format = "{}";
91 tooltip = true;
92 return-type = "json";
93 exec = pkgs.writers.writePython3 "keymap" {} ''
94 import os
95 import socket
96 import json
97
98
99 def output(keymap):
100 short = keymap
101 if keymap == "English (programmer Dvorak)":
102 short = "dvp"
103 elif keymap == "English (US)":
104 short = "<span color=\"#ffffff\">us</span>"
105 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
106
107
108 keyboard_layouts = []
109
110 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
111 sock.connect(os.environ["NIRI_SOCKET"])
112 sock.send(b"\"EventStream\"\n")
113 for line in sock.makefile(buffering=1, encoding='utf-8'):
114 if line_json := json.loads(line):
115 if "KeyboardLayoutsChanged" in line_json:
116 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
117 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
118 if "KeyboardLayoutSwitched" in line_json:
119 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
120 '';
121 on-click = "niri msg action switch-layout next";
122 };
123 "custom/worktime" = {
124 interval = 60;
125 exec = "${lib.getExe pkgs.worktime} time --waybar";
126 return-type = "json";
127 };
128 "custom/worktime-today" = {
129 interval = 60;
130 exec = "${lib.getExe pkgs.worktime} today --waybar";
131 return-type = "json";
132 };
133 "niri/workspaces" = {
134 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
135 };
136 "niri/window" = {
137 separate-outputs = true;
138 icon = true;
139 icon-size = 14;
140 rewrite = windowRewrites;
141 };
142 clock = {
143 interval = 1;
144 # timezone = "Europe/Berlin";
145 format = "W{:%V-%u %F %H:%M:%S%Ez}";
146 tooltip-format = "<tt><small>{calendar}</small></tt>";
147 calendar = {
148 mode = "year";
149 mode-mon-col = 3;
150 weeks-pos = "left";
151 on-scroll = 1;
152 format = {
153 months = "<span color='#ffead3'><b>{}</b></span>";
154 days = "{}";
155 weeks = "<span color='#99ffdd'><b>{}</b></span>";
156 weekdays = "<span color='#ffcc66'><b>{}</b></span>";
157 today = "<span color='#ff6699'><b>{}</b></span>";
158 };
159 };
160 };
161 battery = {
162 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
163 icon-size = iconSize - 2;
164 states = { warning = 30; critical = 15; };
165 format-icons = ["&#xf008e;" "&#xf007a;" "&#xf007b;" "&#xf007c;" "&#xf007d;" "&#xf007e;" "&#xf007f;" "&#xf0080;" "&#xf0081;" "&#xf0082;" "&#xf0079;" ];
166 format-charging = "&#xf0084;";
167 format-plugged = "&#xf06a5;";
168 tooltip-format = "{capacity}% {timeTo}";
169 interval = 20;
170 };
171 tray = {
172 icon-size = 16;
173 # show-passive-items = true;
174 spacing = 1;
175 };
176 privacy = {
177 icon-spacing = 7;
178 icon-size = iconSize;
179 modules = [
180 { type = "screenshare"; }
181 { type = "audio-in"; }
182 ];
183 };
184 idle_inhibitor = {
185 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
186 icon-size = iconSize;
187 format-icons = { activated = "&#xf0208;"; deactivated = "&#xf0209;"; };
188 timeout = 120;
189 };
190 backlight = {
191 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
192 icon-size = iconSize;
193 tooltip-format = "{percent}%";
194 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
195 on-scroll-up = "${swayosd-client} --brightness raise";
196 on-scroll-down = "${swayosd-client} --brightness lower";
197 };
198 wireplumber = {
199 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
200 icon-size = iconSize;
201 tooltip-format = "{volume}% {node_name}";
202 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
203 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
204 # ignored-sinks = ["Easy Effects Sink"];
205 on-scroll-up = "${swayosd-client} --output-volume raise";
206 on-scroll-down = "${swayosd-client} --output-volume lower";
207 on-click = "${swayosd-client} --output-volume mute-toggle";
208 };
209 }
210 {
211 layer = "top";
212 position = "top";
213 height = 14;
214 output = [ "!eDP-1" "!DP-2" "!DP-3" ];
215 modules-left = [ "niri/workspaces" ];
216 modules-center = [ "niri/window" ];
217 modules-right = [ "clock" ];
218
219 "niri/workspaces" = {
220 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
221 };
222 "niri/window" = {
223 separate-outputs = true;
224 icon = true;
225 icon-size = 14;
226 rewrite = windowRewrites;
227 };
228 clock = {
229 interval = 1;
230 # timezone = "Europe/Berlin";
231 format = "{:%H:%M}";
232 tooltip-format = "W{:%V-%u %F %H:%M:%S%Ez}";
233 };
234 }
235 ];
236 style = ''
237 @define-color white #ffffff;
238 @define-color grey #555555;
239 @define-color blue #1a8fff;
240 @define-color green #23fd00;
241 @define-color orange #f28a21;
242 @define-color red #f2201f;
243
244 * {
245 border: none;
246 font-family: "Fira Sans";
247 font-size: 10pt;
248 min-height: 0;
249 }
250
251 window#waybar {
252 background-color: rgba(0, 0, 0, 0.66);
253 color: @white;
254 }
255
256 .modules-left {
257 margin-left: 12px;
258 }
259 .modules-right {
260 margin-right: 12px;
261 }
262
263 .module {
264 margin: 0 5px;
265 }
266
267 #workspaces button {
268 color: @white;
269 padding: 2px 5px;
270 }
271 #workspaces button.empty {
272 color: @grey;
273 }
274 #workspaces button.active {
275 color: @green;
276 }
277 #workspaces button.urgent {
278 color: @red;
279 }
280
281 #custom-weather, #custom-keymap, #custom-worktime, #custom-worktime-today {
282 color: @grey;
283 margin: 0 5px;
284 }
285 #custom-weather {
286 margin-right: 3px;
287 }
288 #custom-keymap {
289 margin-left: 3px;
290 margin-right: 3px;
291 }
292
293 #tray {
294 margin: 0;
295 }
296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
297 color: @grey;
298 margin: 0 5px 0 2px;
299 }
300 #idle_inhibitor {
301 margin-right: 4px;
302 margin-left: 6px;
303 }
304 #custom-mako {
305 margin-right: 2px;
306 margin-left: 3px;
307 }
308 #battery {
309 margin-right: 3px;
310 }
311 #battery.discharging {
312 color: @white;
313 }
314 #battery.warning {
315 color: @orange;
316 }
317 #battery.critical {
318 color: @red;
319 }
320 #battery.charging {
321 color: @white;
322 }
323 #idle_inhibitor.activated {
324 color: @white;
325 }
326 #custom-worktime.running, #custom-worktime-today.running {
327 color: @white;
328 }
329 #custom-worktime.over, #custom-worktime-today.over {
330 color: @orange;
331 }
332
333 #idle_inhibitor {
334 padding-top: 1px;
335 }
336
337 #privacy {
338 color: @red;
339 margin: -1px 4px 0px 3px;
340 }
341 #clock {
342 /* margin-right: 5px; */
343 }
344 '';
345 };
346 };
347}
diff --git a/accounts/gkleen@sif/shell/default.nix b/accounts/gkleen@sif/shell/default.nix
new file mode 100644
index 00000000..44462865
--- /dev/null
+++ b/accounts/gkleen@sif/shell/default.nix
@@ -0,0 +1,115 @@
1{ config, pkgs, lib, ... }:
2
3{
4 config = {
5 programs.quickshell = {
6 enable = true;
7 package = pkgs.symlinkJoin {
8 pname = pkgs.quickshell.pname + "-wrapped";
9 inherit (pkgs.quickshell) version meta;
10 paths = [ pkgs.quickshell ];
11 buildInputs = [ pkgs.makeWrapper ];
12 postBuild = ''
13 for binary in quickshell qs; do
14 wrapProgram $out/bin/$binary \
15 --prefix QML_IMPORT_PATH : ${pkgs.qt6Packages.callPackage ./quickshell-plugins {}}/${pkgs.qt6.qtbase.qtQmlPrefix}
16 done
17 '';
18 };
19 config = {
20 src = ./quickshell;
21 replacements = {
22 ignore_workspaces = builtins.toJSON (map ({ name, ... }: name) config.programs.niri.scratchspaces);
23 wallpapers = builtins.toJSON (pkgs.stdenvNoCC.mkDerivation {
24 name = "wallpapers";
25 srcs = [
26 (pkgs.fetchurl {
27 url = "https://esawebb.org/media/archives/images/publicationtiff10k/carinanebula3.tif";
28 hash = "sha256-YxZEweDKJfvfrdxb/QFmgJhcZDEJYxotoHrG+RRn1tw=";
29 })
30 (pkgs.fetchurl {
31 url = "https://esawebb.org/media/archives/images/original/pillarsofcreation_composite.tif";
32 hash = "sha256-qRiODxR0lZWdxgYXna0fNRFFDErpBJDwOJuQl6sNjRc=";
33 })
34 (pkgs.fetchurl {
35 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2212a.tif";
36 hash = "sha256-l2fqE/z//C1a0xkvZwsnwPbTSb+WuA11h+SUl3E1dhw=";
37 })
38 (pkgs.fetchurl {
39 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2415a.tif";
40 hash = "sha256-onBy7cPoUpDuzQStbY2E+qmlGgSLXPwFCLX53ukAb4c=";
41 })
42 (pkgs.fetchurl {
43 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2330a.tif";
44 hash = "sha256-nn0ZtjZIrPcpj3YcLTsrL7XiXvyh3QYgCSmdDMD+3OM=";
45 })
46 (pkgs.fetchurl {
47 url = "https://esawebb.org/media/archives/images/original/weic2426a.tif";
48 hash = "sha256-EDnfPn3GE9jt6XPqiGInP7E2F3Az7d25NqATSWltDv0=";
49 })
50 (pkgs.fetchurl {
51 url = "https://esawebb.org/media/archives/images/original/weic2503a.tif";
52 hash = "sha256-3/RX6RQp8naELcgReHPd5/zhJkoCjnA10w5BEnNo+qI=";
53 })
54 (pkgs.fetchurl {
55 url = "https://esawebb.org/media/archives/images/original/weic2506a.tif";
56 hash = "sha256-aDld0aoY1owRxDVf7Jcyw71TH42M1foYotxn2thyFYw=";
57 })
58 (pkgs.fetchurl {
59 url = "https://esawebb.org/media/archives/images/original/weic2514a.tif";
60 hash = "sha256-jTi1G1Ofo5xsF6ggrbtYJHxqLaCQ7edM5B3uORiVQtg=";
61 })
62 (pkgs.fetchurl {
63 url = "https://esawebb.org/media/archives/images/original/weic2425c.tif";
64 hash = "sha256-oaEOexSJHEGj090dJF3ct5HAoR+Y5gRiPrUlxdvnTRo=";
65 })
66 ];
67
68 dontUnpack = true;
69
70 buildInputs = [ pkgs.imagemagick ];
71 buildPhase = ''
72 runHook preBuild
73
74 typeset sources=($srcs)
75
76 mkdir -p $out
77 magick ''${sources[0]} -crop 10000x5625+0+79 +repage -define jpeg:extent=10MB $out/carinanebula3.jpeg
78 magick ''${sources[1]} -crop 6716x3778+329+80 +repage -define jpeg:extent=10MB $out/pillarsofcreation_composite.jpeg
79 magick ''${sources[2]} -crop 10000x5625+0+79 +repage -define jpeg:extent=10MB $out/weic2212a.jpeg
80 magick ''${sources[3]} -crop 7650x4302+1166+389 +repage -define jpeg:extent=10MB $out/weic2415a.jpeg
81 magick ''${sources[4]} -crop 8732x4912+0+434 +repage -define jpeg:extent=10MB $out/weic2330a.jpeg
82 magick ''${sources[5]} -crop 5302x2982+636+0 +repage -define jpeg:extent=10MB $out/weic2426a.jpeg
83 magick ''${sources[6]} -crop 4328x2434+0+906 +repage -define jpeg:extent=10MB $out/weic2503a.jpeg
84 magick ''${sources[7]} -crop 4152x2335+0+666 +repage -define jpeg:extent=10MB $out/weic2506a.jpeg
85 magick ''${sources[8]} -crop 4320x2430+0+0 +repage -define jpeg:extent=10MB $out/weic2514a.jpeg
86 magick ''${sources[9]} -crop 5863x3298+0+477 +repage -define jpeg:extent=10MB $out/weic2425c.jpeg
87
88 runHook postBuild
89 '';
90 });
91 niri_session = builtins.toJSON [
92 (pkgs.writeShellScript "niri-session" ''
93 exec ${lib.getExe pkgs.dex} -w ${config.programs.niri.package}/share/wayland-sessions/niri.desktop &>/tmp/niri-session-$$.log
94 '')
95 # (lib.getExe pkgs.dex)
96 # "${config.programs.niri.package}/share/wayland-sessions/niri.desktop"
97 ];
98 username = builtins.toJSON config.home.username;
99 mdi = builtins.toJSON (pkgs.fetchFromGitHub {
100 owner = "Templarian";
101 repo = "MaterialDesign";
102 rev = "2424e748e0cc63ab7b9c095a099b9fe239b737c0";
103 hash = "sha256-QMGl7soAhErrrnY3aKOZpt49yebkSNzy10p/v5OaqQ0=";
104 });
105 worktime = builtins.toJSON (lib.getExe pkgs.worktime);
106 };
107 };
108 };
109 systemd.user.services.quickshell = {
110 Service = {
111 RuntimeDirectory = "quickshell";
112 };
113 };
114 };
115}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
new file mode 100644
index 00000000..020c0515
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
@@ -0,0 +1,168 @@
1set(INSTALL_QMLDIR "" CACHE STRING "QML install dir")
2set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix")
3
4# There doesn't seem to be a standard cross-distro qml install path.
5if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "")
6 message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.")
7else()
8 if ("${INSTALL_QMLDIR}" STREQUAL "")
9 set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}")
10 else()
11 set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}")
12 endif()
13
14 message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}")
15endif()
16
17# Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem
18# to be an official way to do it.
19# see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160
20function(install_qml_module arg_TARGET)
21 if (NOT DEFINED QML_FULL_INSTALLDIR)
22 return()
23 endif()
24
25 qt_query_qml_module(${arg_TARGET}
26 URI module_uri
27 VERSION module_version
28 PLUGIN_TARGET module_plugin_target
29 TARGET_PATH module_target_path
30 QMLDIR module_qmldir
31 TYPEINFO module_typeinfo
32 QML_FILES module_qml_files
33 RESOURCES module_resources
34 )
35
36 set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}")
37
38 if (NOT TARGET "${module_plugin_target}")
39 message(FATAL_ERROR "install_qml_modules called for a target without a plugin")
40 endif()
41
42 get_target_property(target_type "${arg_TARGET}" TYPE)
43 if (NOT "${target_type}" STREQUAL "STATIC_LIBRARY")
44 install(
45 TARGETS "${arg_TARGET}"
46 LIBRARY DESTINATION "${module_dir}"
47 RUNTIME DESTINATION "${module_dir}"
48 )
49
50 install(
51 TARGETS "${module_plugin_target}"
52 LIBRARY DESTINATION "${module_dir}"
53 RUNTIME DESTINATION "${module_dir}"
54 )
55 endif()
56
57 install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
58 install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
59
60 # Install QML files
61 list(LENGTH module_qml_files num_files)
62 if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
63 qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths)
64
65 math(EXPR last_index "${num_files} - 1")
66 foreach(i RANGE 0 ${last_index})
67 list(GET module_qml_files ${i} src_file)
68 list(GET qml_files_deploy_paths ${i} deploy_path)
69 get_filename_component(dst_name "${deploy_path}" NAME)
70 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
71 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
72 endforeach()
73 endif()
74
75 # Install resources
76 list(LENGTH module_resources num_files)
77 if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
78 qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths)
79
80 math(EXPR last_index "${num_files} - 1")
81 foreach(i RANGE 0 ${last_index})
82 list(GET module_resources ${i} src_file)
83 list(GET resources_deploy_paths ${i} deploy_path)
84 get_filename_component(dst_name "${deploy_path}" NAME)
85 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
86 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
87 endforeach()
88 endif()
89endfunction()
90
91
92cmake_minimum_required(VERSION 3.20)
93project(custom LANGUAGES CXX)
94
95find_package(Qt6 REQUIRED COMPONENTS Core Qml DBus)
96
97qt_standard_project_setup(REQUIRES 6.6)
98
99qt6_policy(SET QTP0001 NEW)
100qt6_add_qml_module(customplugin
101 URI "Custom"
102 PLUGIN_TARGET customplugin
103)
104
105set_source_files_properties(org.keepassxc.KeePassXC.MainWindow.xml PROPERTIES
106 CLASSNAME DBusKeePassXC
107 NO_NAMESPACE TRUE
108)
109qt_add_dbus_interface(DBUS_INTERFACES
110 org.keepassxc.KeePassXC.MainWindow.xml
111 dbus_keepassxc
112)
113
114set_source_files_properties(org.freedesktop.systemd1.Manager.xml PROPERTIES
115 CLASSNAME DBusSystemdManager
116 NO_NAMESPACE TRUE
117)
118qt_add_dbus_interface(DBUS_INTERFACES
119 org.freedesktop.systemd1.Manager.xml
120 dbus_systemd_manager
121)
122
123set_source_files_properties(org.freedesktop.login1.Manager.xml PROPERTIES
124 CLASSNAME DBusLogindManager
125 NO_NAMESPACE TRUE
126)
127qt_add_dbus_interface(DBUS_INTERFACES
128 org.freedesktop.login1.Manager.xml
129 dbus_logind_manager
130)
131
132set_source_files_properties(org.freedesktop.login1.Session.xml PROPERTIES
133 CLASSNAME DBusLogindSession
134 NO_NAMESPACE TRUE
135)
136qt_add_dbus_interface(DBUS_INTERFACES
137 org.freedesktop.login1.Session.xml
138 dbus_logind_session
139)
140
141set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES
142 CLASSNAME DBusProperties
143 NO_NAMESPACE TRUE
144)
145qt_add_dbus_interface(DBUS_INTERFACES
146 org.freedesktop.DBus.Properties.xml
147 dbus_properties
148)
149
150include_directories(${CMAKE_SOURCE_DIR}/build)
151
152target_compile_features(customplugin PUBLIC cxx_std_26)
153
154target_link_libraries(customplugin PRIVATE
155 Qt6::Core
156 Qt6::Qml
157 Qt6::DBus
158)
159
160target_sources(customplugin PRIVATE
161 Chrono.cpp Chrono.hpp
162 FileSelector.cpp FileSelector.hpp
163 KeePassXC.cpp KeePassXC.hpp
164 Systemd.cpp Systemd.hpp
165 ${DBUS_INTERFACES}
166)
167
168install_qml_module(customplugin)
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
new file mode 100644
index 00000000..22b3469b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
@@ -0,0 +1,83 @@
1#include "Chrono.hpp"
2
3#include <format>
4#include <iostream>
5#include <cmath>
6
7Chrono::Chrono(QObject* parent): QObject(parent) {
8 QObject::connect(&this->timer, &QTimer::timeout, this, &Chrono::onTimeout);
9 this->update();
10}
11
12bool Chrono::enabled() const { return this->mEnabled; }
13
14void Chrono::setEnabled(bool enabled) {
15 if (enabled == this->mEnabled) return;
16 this->mEnabled = enabled;
17 emit this->enabledChanged();
18 this->update();
19}
20
21Chrono::Precision Chrono::precision() const { return this->mPrecision; }
22
23void Chrono::setPrecision(Chrono::Precision precision) {
24 if (precision == this->mPrecision) return;
25 this->mPrecision = precision;
26 emit this->precisionChanged();
27 this->update();
28}
29
30void Chrono::onTimeout() {
31 this->setTime(this->targetTime);
32 this->schedule(this->targetTime);
33}
34
35void Chrono::update() {
36 if (this->mEnabled) {
37 this->setTime(std::chrono::time_point<std::chrono::system_clock>());
38 this->schedule(std::chrono::time_point<std::chrono::system_clock>());
39 } else {
40 this->timer.stop();
41 }
42}
43
44void Chrono::setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
45 using namespace std::chrono_literals;
46
47 auto currentTime = std::chrono::system_clock::now();
48 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
49 this->currentTime = abs(offset) < 500ms ? targetTime : currentTime;
50
51 switch (this->mPrecision) {
52 case Chrono::Hours: this->currentTime = std::chrono::time_point_cast<std::chrono::hours>(this->currentTime);
53 case Chrono::Minutes: this->currentTime = std::chrono::time_point_cast<std::chrono::minutes>(this->currentTime);
54 case Chrono::Seconds: this->currentTime = std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime);
55 }
56
57 emit this->dateChanged();
58}
59
60void Chrono::schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
61 using namespace std::chrono_literals;
62
63 auto currentTime = std::chrono::system_clock::now();
64 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
65 auto nextTime = abs(offset) < 500ms ? targetTime : currentTime;
66
67 switch (this->mPrecision) {
68 case Chrono::Hours: nextTime = std::chrono::time_point_cast<std::chrono::hours>(nextTime) + 1h;
69 case Chrono::Minutes: nextTime = std::chrono::time_point_cast<std::chrono::minutes>(nextTime) + 1min;
70 case Chrono::Seconds: nextTime = std::chrono::time_point_cast<std::chrono::seconds>(nextTime) + 1s;
71 }
72
73 this->targetTime = nextTime;
74 this->timer.start(std::chrono::duration_cast<std::chrono::milliseconds>(nextTime - currentTime));
75}
76
77QString Chrono::format(const QString& fmt) const {
78 return QString::fromStdString(std::format(std::runtime_format(fmt.toStdString()), std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime))));
79}
80
81QDateTime Chrono::date() const {
82 return QDateTime::fromStdTimePoint(std::chrono::time_point_cast<std::chrono::milliseconds>(this->currentTime));
83}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
new file mode 100644
index 00000000..04080187
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
@@ -0,0 +1,55 @@
1#pragma once
2
3#include <chrono>
4
5#include <QDateTime>
6#include <QObject>
7#include <QTimer>
8
9#include <qqmlintegration.h>
10
11class Chrono : public QObject {
12 Q_OBJECT;
13 Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
14 Q_PROPERTY(Chrono::Precision precision READ precision WRITE setPrecision NOTIFY precisionChanged);
15 Q_PROPERTY(QDateTime date READ date NOTIFY dateChanged)
16 QML_ELEMENT;
17
18public:
19 enum Precision : quint8 {
20 Hours = 1,
21 Minutes = 2,
22 Seconds = 3,
23 };
24 Q_ENUM(Precision);
25
26 explicit Chrono(QObject* parent = nullptr);
27
28 bool enabled() const;
29 void setEnabled(bool enabled);
30
31 Chrono::Precision precision() const;
32 void setPrecision(Chrono::Precision precision);
33
34 Q_INVOKABLE QString format(const QString& fmt) const;
35
36 QDateTime date() const;
37
38signals:
39 void enabledChanged();
40 void precisionChanged();
41 void dateChanged();
42
43private slots:
44 void onTimeout();
45
46private:
47 bool mEnabled = true;
48 Chrono::Precision mPrecision = Chrono::Seconds;
49 QTimer timer;
50 std::chrono::time_point<std::chrono::system_clock> currentTime, targetTime;
51
52 void update();
53 void setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
54 void schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
55};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp
new file mode 100644
index 00000000..d7051d2a
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp
@@ -0,0 +1,102 @@
1#include "FileSelector.hpp"
2
3#include <sstream>
4#include <vector>
5#include <random>
6#include <algorithm>
7
8#include <iostream>
9#include <format>
10
11namespace fs = std::filesystem;
12
13FileSelector::FileSelector(QObject* parent): QObject(parent) {
14 QObject::connect(&this->timer, &QTimer::timeout, this, &FileSelector::onTimeout);
15 this->timer.setTimerType(Qt::PreciseTimer);
16}
17
18QString FileSelector::directory() const {
19 return QString::fromStdString(this->mDirectory->string());
20}
21void FileSelector::setDirectory(QString directory) {
22 this->mDirectory = directory.toStdString();
23 if (this->mDirectory && this->mEpoch)
24 this->update();
25 emit this->directoryChanged();
26}
27
28unsigned int FileSelector::epoch() const {
29 return std::chrono::duration_cast<std::chrono::milliseconds>(*this->mEpoch).count();
30}
31void FileSelector::setEpoch(unsigned int epoch) {
32 this->mEpoch = std::chrono::milliseconds{epoch};
33 if (this->mDirectory && this->mEpoch)
34 this->update();
35 emit this->epochChanged();
36}
37
38QString FileSelector::seed() const {
39 return this->mSeed;
40}
41void FileSelector::setSeed(QString seed) {
42 this->mSeed = seed;
43 emit this->seedChanged();
44 if (this->mDirectory && this->mEpoch)
45 emit this->selectedChanged();
46}
47
48QString FileSelector::selected() const {
49 if (!this->mDirectory || !this->mEpoch)
50 return QString();
51
52 std::vector<fs::path> shuffled(this->mFiles.begin(), this->mFiles.end());
53 std::sort(shuffled.begin(), shuffled.end());
54
55 auto currentTime = std::chrono::system_clock::now();
56 uint64_t currentEpoch = currentTime.time_since_epoch() / *this->mEpoch;
57 std::chrono::milliseconds timeInEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime.time_since_epoch()) % *this->mEpoch;
58
59 std::ostringstream seed;
60 seed << this->mSeed.size() << ";" << this->mSeed.toStdString() << ";";
61 seed << *this->mEpoch << ";";
62 seed << currentEpoch << ";";
63 seed << this->mDirectory->string().size() << ";" << *this->mDirectory << ";";
64 seed << this->mFiles.size() << ";";
65 for (const fs::path& p: this->mFiles)
66 seed << p.string().size() << ";" << p << ";";
67
68 std::vector<std::seed_seq::result_type> v;
69 v.reserve(seed.str().size());
70 for (const char& c: seed.str())
71 v.push_back(c);
72
73 std::seed_seq engine_seed(v.begin(), v.end());
74 std::mt19937 g(engine_seed);
75 std::shuffle(shuffled.begin(), shuffled.end(), g);
76
77 std::vector<fs::path>::size_type ix = shuffled.size() * timeInEpoch / *this->mEpoch;
78 return QString::fromStdString((*this->mDirectory / shuffled[ix]).string());
79}
80
81void FileSelector::onTimeout() {
82 if (!this->mFiles.size())
83 return;
84
85 auto currentTime = std::chrono::system_clock::now();
86 uint64_t currentMinorEpoch = currentTime.time_since_epoch() / (*this->mEpoch / this->mFiles.size());
87 auto nextTime = std::chrono::time_point<std::chrono::system_clock>((currentMinorEpoch + 1) * (*this->mEpoch / this->mFiles.size()));
88 this->timer.start(std::chrono::duration_cast<std::chrono::milliseconds>(nextTime - currentTime));
89
90 emit this->selectedChanged();
91}
92
93void FileSelector::update() {
94 this->mFiles = std::set<fs::path>{};
95 for (const fs::directory_entry& entry:
96 fs::recursive_directory_iterator(*this->mDirectory, fs::directory_options::follow_directory_symlink))
97 {
98 this->mFiles.insert(fs::relative(entry, *this->mDirectory));
99 }
100
101 this->onTimeout();
102}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp
new file mode 100644
index 00000000..72c4f2a7
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp
@@ -0,0 +1,52 @@
1#pragma once
2
3#include <filesystem>
4#include <chrono>
5#include <set>
6#include <optional>
7
8#include <QObject>
9#include <QTimer>
10
11#include <qqmlintegration.h>
12
13class FileSelector : public QObject {
14 Q_OBJECT;
15 Q_PROPERTY(QString directory READ directory WRITE setDirectory NOTIFY directoryChanged REQUIRED);
16 Q_PROPERTY(unsigned int epoch READ epoch WRITE setEpoch NOTIFY epochChanged REQUIRED);
17 Q_PROPERTY(QString seed READ seed WRITE setSeed NOTIFY seedChanged);
18 Q_PROPERTY(QString selected READ selected NOTIFY selectedChanged);
19 QML_ELEMENT;
20
21public:
22 explicit FileSelector(QObject* parent = nullptr);
23
24 QString directory() const;
25 void setDirectory(QString directory);
26
27 unsigned int epoch() const;
28 void setEpoch(unsigned int epoch);
29
30 QString seed() const;
31 void setSeed(QString seed);
32
33 QString selected() const;
34
35signals:
36 void directoryChanged();
37 void epochChanged();
38 void seedChanged();
39 void selectedChanged();
40
41private slots:
42 void onTimeout();
43
44private:
45 std::optional<std::filesystem::path> mDirectory;
46 std::optional<std::chrono::milliseconds> mEpoch;
47 std::set<std::filesystem::path> mFiles;
48 QString mSeed;
49 QTimer timer;
50
51 void update();
52};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
new file mode 100644
index 00000000..f6e4dd6e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
@@ -0,0 +1,18 @@
1#include "KeePassXC.hpp"
2
3#include <QDBusConnection>
4
5KeePassXC::KeePassXC() {
6 this->service = new DBusKeePassXC(DBusKeePassXC::staticInterfaceName(), "/keepassxc", QDBusConnection::sessionBus(), this);
7}
8KeePassXC::~KeePassXC() {
9 if (this->service)
10 delete this->service;
11}
12
13void KeePassXC::lockAllDatabases() {
14 if (!this->service)
15 return;
16
17 this->service->lockAllDatabases();
18}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
new file mode 100644
index 00000000..c4cd71e0
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
@@ -0,0 +1,21 @@
1#pragma once
2
3#include "dbus_keepassxc.h"
4
5#include <QObject>
6#include <QtQmlIntegration/qqmlintegration.h>
7
8class KeePassXC : public QObject {
9 Q_OBJECT;
10 QML_SINGLETON;
11 QML_ELEMENT;
12
13public:
14 explicit KeePassXC();
15 ~KeePassXC();
16
17 Q_INVOKABLE void lockAllDatabases();
18
19private:
20 DBusKeePassXC* service = nullptr;
21};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
new file mode 100644
index 00000000..308659e9
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
@@ -0,0 +1,198 @@
1#include "Systemd.hpp"
2
3#include <sstream>
4
5#include <QDBusConnection>
6#include <QDBusReply>
7#include <QDebug>
8#include <QDBusUnixFileDescriptor>
9#include <QDBusObjectPath>
10
11Systemd::Systemd(QObject* parent) : QObject(parent) {
12 this->systemdManager = new DBusSystemdManager(
13 "org.freedesktop.systemd1",
14 "/org/freedesktop/systemd1",
15 QDBusConnection::sessionBus(),
16 this
17 );
18 this->logindManager = new DBusLogindManager(
19 "org.freedesktop.login1",
20 "/org/freedesktop/login1",
21 QDBusConnection::systemBus(),
22 this
23 );
24
25 QDBusReply<QDBusObjectPath> sessionPath = this->logindManager->GetSession("auto");
26 qDebug() << sessionPath;
27 this->logindSession = new DBusLogindSession(
28 "org.freedesktop.login1",
29 sessionPath.value().path(),
30 QDBusConnection::systemBus(),
31 this
32 );
33 this->logindSessionProperties = new DBusProperties(
34 "org.freedesktop.login1",
35 sessionPath.value().path(),
36 QDBusConnection::systemBus(),
37 this
38 );
39
40 QObject::connect(this->logindManager, &DBusLogindManager::PrepareForShutdown, this, &Systemd::shutdown);
41 QObject::connect(this->logindManager, &DBusLogindManager::PrepareForSleep, this, &Systemd::sleep);
42 QObject::connect(this->logindSession, &DBusLogindSession::Lock, this, &Systemd::lock);
43 QObject::connect(this->logindSession, &DBusLogindSession::Unlock, this, &Systemd::unlock);
44 QObject::connect(this->logindSessionProperties, &DBusProperties::PropertiesChanged, this, &Systemd::onLogindSessionPropertiesChanged);
45}
46
47void Systemd::onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties) {
48 if (changed_properties.contains("IdleHint") || invalidated_properties.contains("IdleHint"))
49 emit this->idleHintChanged();
50 if (changed_properties.contains("LockedHint") || invalidated_properties.contains("LockedHint"))
51 emit this->lockedHintChanged();
52}
53
54void Systemd::stopUserUnit(const QString& unit, const QString& mode) { this->systemdManager->StopUnit(unit, mode); }
55
56void Systemd::setBrightness(const QString& subsystem, const QString& name, quint32 brightness) { this->logindSession->SetBrightness(subsystem, name, brightness); }
57
58bool Systemd::idleHint() { return this->logindSession->idleHint(); }
59void Systemd::setIdleHint(bool idle) { this->logindSession->SetIdleHint(idle); }
60bool Systemd::lockedHint() { return this->logindSession->lockedHint(); }
61void Systemd::setLockedHint(bool locked) { this->logindSession->SetLockedHint(locked); }
62void Systemd::lockSession() { this->logindSession->call("Lock"); }
63void Systemd::suspend() { this->logindManager->Suspend(true); }
64void Systemd::hibernate() { this->logindManager->Hibernate(true); }
65
66std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::WhatItem what) {
67 if (what == SystemdInhibitorParams::Shutdown)
68 return "shutdown";
69 else if (what == SystemdInhibitorParams::Sleep)
70 return "sleep";
71 else if (what == SystemdInhibitorParams::Idle)
72 return "idle";
73 else if (what == SystemdInhibitorParams::HandlePowerKey)
74 return "handle-power-key";
75 else if (what == SystemdInhibitorParams::HandleSuspendKey)
76 return "handle-suspend-key";
77 else if (what == SystemdInhibitorParams::HandleHibernateKey)
78 return "handle-hibernate-key";
79 else if (what == SystemdInhibitorParams::HandleLidSwitch)
80 return "handle-lid-switch";
81 return "";
82}
83
84std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::What what) {
85 std::ostringstream res;
86 bool first = true;
87 for (const WhatItem& item: SystemdInhibitorParams::allWhatItems) {
88 if (!(what & item))
89 continue;
90
91 if (!first)
92 res << ":";
93 else
94 first = false;
95 res << SystemdInhibitorParams::toString(item);
96 }
97 return res.str();
98}
99
100std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::Mode mode) {
101 if (mode == SystemdInhibitorParams::Block)
102 return "block";
103 else if (mode == SystemdInhibitorParams::BlockWeak)
104 return "block-weak";
105 else if (mode == SystemdInhibitorParams::Delay)
106 return "delay";
107 return "";
108}
109
110bool SystemdInhibitor::enabled() const { return static_cast<bool>(this->activeInhibitor); }
111void SystemdInhibitor::setEnabled(bool enabled) {
112 this->mEnabled = enabled;
113
114 if (enabled)
115 this->update();
116 else
117 this->release();
118}
119
120SystemdInhibitorParams::What SystemdInhibitor::what() const { return this->mWhat; }
121void SystemdInhibitor::setWhat(SystemdInhibitorParams::What what) {
122 this->mWhat = what;
123 this->update();
124}
125
126QString SystemdInhibitor::who() const { return this->mWho; }
127void SystemdInhibitor::setWho(QString who) {
128 this->mWho = who;
129 this->update();
130}
131
132QString SystemdInhibitor::why() const { return this->mWhy; }
133void SystemdInhibitor::setWhy(QString why) {
134 this->mWhy = why;
135 this->update();
136}
137
138SystemdInhibitorParams::Mode SystemdInhibitor::mode() const { return this->mMode; }
139void SystemdInhibitor::setMode(SystemdInhibitorParams::Mode mode) {
140 this->mMode = mode;
141 this->update();
142}
143
144SystemdInhibitor::ActiveSystemdInhibitor::ActiveSystemdInhibitor(SystemdInhibitorParams::What what_, QString who_, QString why_, SystemdInhibitorParams::Mode mode_): what(what_), who(who_), why(why_), mode(mode_) {
145 DBusLogindManager logindManager(
146 "org.freedesktop.login1",
147 "/org/freedesktop/login1",
148 QDBusConnection::systemBus()
149 );
150 QDBusReply<QDBusUnixFileDescriptor> fd_ = logindManager.Inhibit(QString::fromStdString(SystemdInhibitorParams::toString(what)), who, why, QString::fromStdString(SystemdInhibitorParams::toString(mode)));
151 if (fd_.error().isValid())
152 throw fd_.error();
153 this->fd = ::dup(fd_.value().fileDescriptor());
154}
155SystemdInhibitor::ActiveSystemdInhibitor::~ActiveSystemdInhibitor() {
156 if (this->fd != -1)
157 ::close(this->fd);
158}
159
160void SystemdInhibitor::release() {
161 if (!this->activeInhibitor)
162 return;
163
164 this->activeInhibitor.reset();
165 emit this->enabledChanged();
166}
167
168void SystemdInhibitor::update() {
169 if (!this->mWhat || this->mWho.isEmpty() || this->mWhy.isEmpty() || !this->mMode || !this->mEnabled)
170 if (this->activeInhibitor)
171 this->release();
172 else
173 return;
174
175 if (this->activeInhibitor && this->mWhat == this->activeInhibitor->what && this->mWho == this->activeInhibitor->who && this->mWhy == this->activeInhibitor->why && this->mMode == this->activeInhibitor->mode)
176 return;
177
178 std::unique_ptr<ActiveSystemdInhibitor> otherInhibitor;
179 try {
180 otherInhibitor.reset(new SystemdInhibitor::ActiveSystemdInhibitor(this->mWhat, this->mWho, this->mWhy, this->mMode));
181 } catch (const QDBusError& err) {
182 qCritical().noquote()
183 << err.name().toStdString() << " " << err.message().toStdString();
184 return;
185 }
186 this->activeInhibitor.swap(otherInhibitor);
187
188 if (!otherInhibitor && this->activeInhibitor)
189 emit this->enabledChanged();
190 if (otherInhibitor && otherInhibitor->what != this->activeInhibitor->what)
191 emit this->whatChanged();
192 if (otherInhibitor && otherInhibitor->who != this->activeInhibitor->who)
193 emit this->whoChanged();
194 if (otherInhibitor && otherInhibitor->why != this->activeInhibitor->why)
195 emit this->whyChanged();
196 if (otherInhibitor && otherInhibitor->mode != this->activeInhibitor->mode)
197 emit this->modeChanged();
198}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
new file mode 100644
index 00000000..615024d2
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
@@ -0,0 +1,147 @@
1#pragma once
2
3#include <cstdint>
4#include <memory>
5#include <string>
6
7#include <QObject>
8#include <QDBusInterface>
9#include <QtQmlIntegration/qqmlintegration.h>
10
11#include "dbus_systemd_manager.h"
12#include "dbus_logind_manager.h"
13#include "dbus_logind_session.h"
14#include "dbus_properties.h"
15
16class Systemd : public QObject {
17 Q_OBJECT;
18 QML_SINGLETON;
19 QML_ELEMENT;
20
21public:
22 explicit Systemd(QObject* parent = nullptr);
23
24 Q_PROPERTY(bool idleHint READ idleHint WRITE setIdleHint NOTIFY idleHintChanged)
25 Q_PROPERTY(bool lockedHint READ lockedHint WRITE setLockedHint NOTIFY lockedHintChanged)
26
27 Q_INVOKABLE void stopUserUnit(const QString& unit, const QString& mode);
28 Q_INVOKABLE void setBrightness(const QString& subsystem, const QString& name, quint32 brightness);
29 Q_INVOKABLE void setIdleHint(bool idle);
30 Q_INVOKABLE void setLockedHint(bool locked);
31 Q_INVOKABLE void lockSession();
32 Q_INVOKABLE void suspend();
33 Q_INVOKABLE void hibernate();
34
35 bool idleHint();
36 bool lockedHint();
37
38signals:
39 void shutdown(bool before);
40 void sleep(bool before);
41 void lock();
42 void unlock();
43 void idleHintChanged();
44 void lockedHintChanged();
45
46private slots:
47 void onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties);
48
49private:
50 DBusSystemdManager* systemdManager;
51 DBusLogindManager* logindManager;
52 DBusLogindSession* logindSession;
53 DBusProperties* logindSessionProperties;
54};
55
56class SystemdInhibitorParams : public QObject {
57 Q_OBJECT;
58 QML_ELEMENT;
59 QML_SINGLETON;
60
61public:
62 enum WhatItem : uint8_t {
63 Shutdown = 0b1,
64 Sleep = 0b10,
65 Idle = 0b100,
66 HandlePowerKey = 0b1000,
67 HandleSuspendKey = 0b10000,
68 HandleHibernateKey = 0b100000,
69 HandleLidSwitch = 0b1000000,
70 };
71 Q_ENUM(WhatItem);
72 Q_DECLARE_FLAGS(What, WhatItem);
73
74 enum Mode : uint8_t {
75 Block = 1,
76 BlockWeak = 2,
77 Delay = 3,
78 };
79 Q_ENUM(Mode);
80
81 Q_INVOKABLE static std::string toString(WhatItem what);
82 Q_INVOKABLE static std::string toString(What what);
83 Q_INVOKABLE static std::string toString(Mode mode);
84
85 static constexpr WhatItem allWhatItems[] = { Shutdown, Sleep, Idle, HandlePowerKey, HandleSuspendKey, HandleHibernateKey, HandleLidSwitch };
86};
87Q_DECLARE_OPERATORS_FOR_FLAGS(SystemdInhibitorParams::What)
88
89class SystemdInhibitor : public QObject {
90 Q_OBJECT;
91 Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
92 Q_PROPERTY(SystemdInhibitorParams::What what READ what WRITE setWhat NOTIFY whatChanged);
93 Q_PROPERTY(QString who READ who WRITE setWho NOTIFY whoChanged);
94 Q_PROPERTY(QString why READ why WRITE setWhy NOTIFY whyChanged);
95 Q_PROPERTY(SystemdInhibitorParams::Mode mode READ mode WRITE setMode NOTIFY modeChanged);
96 QML_ELEMENT;
97
98public:
99 explicit SystemdInhibitor(QObject* parent = nullptr): QObject(parent) {}
100
101 bool enabled() const;
102 void setEnabled(bool enabled);
103
104 SystemdInhibitorParams::What what() const;
105 void setWhat(SystemdInhibitorParams::What what);
106
107 QString who() const;
108 void setWho(QString who);
109
110 QString why() const;
111 void setWhy(QString why);
112
113 SystemdInhibitorParams::Mode mode() const;
114 void setMode(SystemdInhibitorParams::Mode mode);
115
116 Q_INVOKABLE void release();
117
118signals:
119 void enabledChanged();
120 void whatChanged();
121 void whoChanged();
122 void whyChanged();
123 void modeChanged();
124
125private:
126 class ActiveSystemdInhibitor {
127 public:
128 uint32_t fd = -1;
129 SystemdInhibitorParams::What what;
130 QString who;
131 QString why;
132 SystemdInhibitorParams::Mode mode;
133
134 ActiveSystemdInhibitor(SystemdInhibitorParams::What what_, QString who_, QString why_, SystemdInhibitorParams::Mode mode_);
135 ~ActiveSystemdInhibitor();
136 };
137
138 void update();
139
140 bool mEnabled = true;
141 std::unique_ptr<ActiveSystemdInhibitor> activeInhibitor;
142 SystemdInhibitorParams::What mWhat = static_cast<SystemdInhibitorParams::What>(0);
143 QString mWho;
144 QString mWhy;
145 SystemdInhibitorParams::Mode mMode = static_cast<SystemdInhibitorParams::Mode>(0);
146};
147
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
new file mode 100644
index 00000000..e66ba9e3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
@@ -0,0 +1,7 @@
1#include <QQmlEngineExtensionPlugin>
2
3class CustomPlugin : public QQmlEngineExtensionPlugin
4{
5 Q_OBJECT
6 Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
7};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
new file mode 100644
index 00000000..20a195eb
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
@@ -0,0 +1,30 @@
1{ lib
2, stdenv
3, cmake
4, qt6
5, fmt
6, keepassxc
7, systemd
8}:
9
10stdenv.mkDerivation rec {
11 name = "quickshell-custom";
12
13 src = ./.;
14
15 prePatch = ''
16 cp ${keepassxc.src}/src/gui/org.keepassxc.KeePassXC.MainWindow.xml .
17 '';
18
19 nativeBuildInputs = [ cmake qt6.wrapQtAppsHook ];
20 buildInputs = [
21 qt6.qtbase
22 qt6.qtdeclarative
23 ];
24
25 cmakeFlags = [
26 (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
27 ];
28
29 LC_ALL = "C.UTF-8";
30}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml
new file mode 100644
index 00000000..7588e7a5
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml
@@ -0,0 +1,28 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.DBus.Properties">
5 <method name="Get">
6 <arg name="interface_name" direction="in" type="s"/>
7 <arg name="property_name" direction="in" type="s"/>
8 <arg name="value" direction="out" type="v"/>
9 </method>
10 <method name="GetAll">
11 <arg name="interface_name" direction="in" type="s"/>
12 <arg name="props" direction="out" type="a{sv}"/>
13 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
14 </method>
15 <method name="Set">
16 <arg name="interface_name" direction="in" type="s"/>
17 <arg name="property_name" direction="in" type="s"/>
18 <arg name="value" direction="in" type="v"/>
19 </method>
20 <signal name="PropertiesChanged">
21 <arg type="s" name="interface_name"/>
22 <arg type="a{sv}" name="changed_properties"/>
23 <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
24 <arg type="as" name="invalidated_properties"/>
25 </signal>
26 </interface>
27</node>
28
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml
new file mode 100644
index 00000000..120a06d9
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml
@@ -0,0 +1,445 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.login1.Manager">
5 <property name="EnableWallMessages" type="b" access="readwrite">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
7 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
8 </property>
9 <property name="WallMessage" type="s" access="readwrite">
10 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
11 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
12 </property>
13 <property name="NAutoVTs" type="u" access="read">
14 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
15 </property>
16 <property name="KillOnlyUsers" type="as" access="read">
17 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
18 </property>
19 <property name="KillExcludeUsers" type="as" access="read">
20 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
21 </property>
22 <property name="KillUserProcesses" type="b" access="read">
23 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
24 </property>
25 <property name="RebootParameter" type="s" access="read">
26 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
27 </property>
28 <property name="RebootToFirmwareSetup" type="b" access="read">
29 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
30 </property>
31 <property name="RebootToBootLoaderMenu" type="t" access="read">
32 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
33 </property>
34 <property name="RebootToBootLoaderEntry" type="s" access="read">
35 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
36 </property>
37 <property name="BootLoaderEntries" type="as" access="read">
38 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
39 </property>
40 <property name="IdleHint" type="b" access="read">
41 </property>
42 <property name="IdleSinceHint" type="t" access="read">
43 </property>
44 <property name="IdleSinceHintMonotonic" type="t" access="read">
45 </property>
46 <property name="BlockInhibited" type="s" access="read">
47 </property>
48 <property name="BlockWeakInhibited" type="s" access="read">
49 </property>
50 <property name="DelayInhibited" type="s" access="read">
51 </property>
52 <property name="InhibitDelayMaxUSec" type="t" access="read">
53 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
54 </property>
55 <property name="UserStopDelayUSec" type="t" access="read">
56 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
57 </property>
58 <property name="SleepOperation" type="as" access="read">
59 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
60 </property>
61 <property name="HandlePowerKey" type="s" access="read">
62 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
63 </property>
64 <property name="HandlePowerKeyLongPress" type="s" access="read">
65 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
66 </property>
67 <property name="HandleRebootKey" type="s" access="read">
68 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
69 </property>
70 <property name="HandleRebootKeyLongPress" type="s" access="read">
71 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
72 </property>
73 <property name="HandleSuspendKey" type="s" access="read">
74 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
75 </property>
76 <property name="HandleSuspendKeyLongPress" type="s" access="read">
77 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
78 </property>
79 <property name="HandleHibernateKey" type="s" access="read">
80 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
81 </property>
82 <property name="HandleHibernateKeyLongPress" type="s" access="read">
83 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
84 </property>
85 <property name="HandleLidSwitch" type="s" access="read">
86 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
87 </property>
88 <property name="HandleLidSwitchExternalPower" type="s" access="read">
89 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
90 </property>
91 <property name="HandleLidSwitchDocked" type="s" access="read">
92 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
93 </property>
94 <property name="HandleSecureAttentionKey" type="s" access="read">
95 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
96 </property>
97 <property name="HoldoffTimeoutUSec" type="t" access="read">
98 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
99 </property>
100 <property name="IdleAction" type="s" access="read">
101 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
102 </property>
103 <property name="IdleActionUSec" type="t" access="read">
104 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
105 </property>
106 <property name="PreparingForShutdown" type="b" access="read">
107 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
108 </property>
109 <property name="PreparingForShutdownWithMetadata" type="a{sv}" access="read">
110 <annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
111 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
112 </property>
113 <property name="PreparingForSleep" type="b" access="read">
114 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
115 </property>
116 <!-- <property name="ScheduledShutdown" type="(st)" access="read"> -->
117 <!-- </property> -->
118 <property name="DesignatedMaintenanceTime" type="s" access="read">
119 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
120 </property>
121 <property name="Docked" type="b" access="read">
122 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
123 </property>
124 <property name="LidClosed" type="b" access="read">
125 </property>
126 <property name="OnExternalPower" type="b" access="read">
127 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
128 </property>
129 <property name="RemoveIPC" type="b" access="read">
130 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
131 </property>
132 <property name="RuntimeDirectorySize" type="t" access="read">
133 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
134 </property>
135 <property name="RuntimeDirectoryInodesMax" type="t" access="read">
136 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
137 </property>
138 <property name="InhibitorsMax" type="t" access="read">
139 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
140 </property>
141 <property name="NCurrentInhibitors" type="t" access="read">
142 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
143 </property>
144 <property name="SessionsMax" type="t" access="read">
145 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
146 </property>
147 <property name="NCurrentSessions" type="t" access="read">
148 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
149 </property>
150 <property name="StopIdleSessionUSec" type="t" access="read">
151 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
152 </property>
153 <method name="GetSession">
154 <arg type="s" name="session_id" direction="in"/>
155 <arg type="o" name="object_path" direction="out"/>
156 </method>
157 <method name="GetSessionByPID">
158 <arg type="u" name="pid" direction="in"/>
159 <arg type="o" name="object_path" direction="out"/>
160 </method>
161 <method name="GetUser">
162 <arg type="u" name="uid" direction="in"/>
163 <arg type="o" name="object_path" direction="out"/>
164 </method>
165 <method name="GetUserByPID">
166 <arg type="u" name="pid" direction="in"/>
167 <arg type="o" name="object_path" direction="out"/>
168 </method>
169 <method name="GetSeat">
170 <arg type="s" name="seat_id" direction="in"/>
171 <arg type="o" name="object_path" direction="out"/>
172 </method>
173 <!-- <method name="ListSessions"> -->
174 <!-- <arg type="a(susso)" name="sessions" direction="out"/> -->
175 <!-- </method> -->
176 <!-- <method name="ListSessionsEx"> -->
177 <!-- <arg type="a(sussussbto)" name="sessions" direction="out"/> -->
178 <!-- </method> -->
179 <!-- <method name="ListUsers"> -->
180 <!-- <arg type="a(uso)" name="users" direction="out"/> -->
181 <!-- </method> -->
182 <!-- <method name="ListSeats"> -->
183 <!-- <arg type="a(so)" name="seats" direction="out"/> -->
184 <!-- </method> -->
185 <!-- <method name="ListInhibitors"> -->
186 <!-- <arg type="a(ssssuu)" name="inhibitors" direction="out"/> -->
187 <!-- </method> -->
188 <!-- <method name="CreateSession"> -->
189 <!-- <arg type="u" name="uid" direction="in"/> -->
190 <!-- <arg type="u" name="pid" direction="in"/> -->
191 <!-- <arg type="s" name="service" direction="in"/> -->
192 <!-- <arg type="s" name="type" direction="in"/> -->
193 <!-- <arg type="s" name="class" direction="in"/> -->
194 <!-- <arg type="s" name="desktop" direction="in"/> -->
195 <!-- <arg type="s" name="seat_id" direction="in"/> -->
196 <!-- <arg type="u" name="vtnr" direction="in"/> -->
197 <!-- <arg type="s" name="tty" direction="in"/> -->
198 <!-- <arg type="s" name="display" direction="in"/> -->
199 <!-- <arg type="b" name="remote" direction="in"/> -->
200 <!-- <arg type="s" name="remote_user" direction="in"/> -->
201 <!-- <arg type="s" name="remote_host" direction="in"/> -->
202 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
203 <!-- <arg type="s" name="session_id" direction="out"/> -->
204 <!-- <arg type="o" name="object_path" direction="out"/> -->
205 <!-- <arg type="s" name="runtime_path" direction="out"/> -->
206 <!-- <arg type="h" name="fifo_fd" direction="out"/> -->
207 <!-- <arg type="u" name="uid" direction="out"/> -->
208 <!-- <arg type="s" name="seat_id" direction="out"/> -->
209 <!-- <arg type="u" name="vtnr" direction="out"/> -->
210 <!-- <arg type="b" name="existing" direction="out"/> -->
211 <!-- <annotation name="org.freedesktop.systemd1.Privileged" value="true"/> -->
212 <!-- </method> -->
213 <!-- <method name="CreateSessionWithPIDFD"> -->
214 <!-- <arg type="u" name="uid" direction="in"/> -->
215 <!-- <arg type="h" name="pidfd" direction="in"/> -->
216 <!-- <arg type="s" name="service" direction="in"/> -->
217 <!-- <arg type="s" name="type" direction="in"/> -->
218 <!-- <arg type="s" name="class" direction="in"/> -->
219 <!-- <arg type="s" name="desktop" direction="in"/> -->
220 <!-- <arg type="s" name="seat_id" direction="in"/> -->
221 <!-- <arg type="u" name="vtnr" direction="in"/> -->
222 <!-- <arg type="s" name="tty" direction="in"/> -->
223 <!-- <arg type="s" name="display" direction="in"/> -->
224 <!-- <arg type="b" name="remote" direction="in"/> -->
225 <!-- <arg type="s" name="remote_user" direction="in"/> -->
226 <!-- <arg type="s" name="remote_host" direction="in"/> -->
227 <!-- <arg type="t" name="flags" direction="in"/> -->
228 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
229 <!-- <arg type="s" name="session_id" direction="out"/> -->
230 <!-- <arg type="o" name="object_path" direction="out"/> -->
231 <!-- <arg type="s" name="runtime_path" direction="out"/> -->
232 <!-- <arg type="h" name="fifo_fd" direction="out"/> -->
233 <!-- <arg type="u" name="uid" direction="out"/> -->
234 <!-- <arg type="s" name="seat_id" direction="out"/> -->
235 <!-- <arg type="u" name="vtnr" direction="out"/> -->
236 <!-- <arg type="b" name="existing" direction="out"/> -->
237 <!-- <annotation name="org.freedesktop.systemd1.Privileged" value="true"/> -->
238 <!-- </method> -->
239 <method name="ReleaseSession">
240 <arg type="s" name="session_id" direction="in"/>
241 </method>
242 <method name="ActivateSession">
243 <arg type="s" name="session_id" direction="in"/>
244 </method>
245 <method name="ActivateSessionOnSeat">
246 <arg type="s" name="session_id" direction="in"/>
247 <arg type="s" name="seat_id" direction="in"/>
248 </method>
249 <method name="LockSession">
250 <arg type="s" name="session_id" direction="in"/>
251 </method>
252 <method name="UnlockSession">
253 <arg type="s" name="session_id" direction="in"/>
254 </method>
255 <method name="LockSessions">
256 </method>
257 <method name="UnlockSessions">
258 </method>
259 <method name="KillSession">
260 <arg type="s" name="session_id" direction="in"/>
261 <arg type="s" name="whom" direction="in"/>
262 <arg type="i" name="signal_number" direction="in"/>
263 </method>
264 <method name="KillUser">
265 <arg type="u" name="uid" direction="in"/>
266 <arg type="i" name="signal_number" direction="in"/>
267 </method>
268 <method name="TerminateSession">
269 <arg type="s" name="session_id" direction="in"/>
270 </method>
271 <method name="TerminateUser">
272 <arg type="u" name="uid" direction="in"/>
273 </method>
274 <method name="TerminateSeat">
275 <arg type="s" name="seat_id" direction="in"/>
276 </method>
277 <method name="SetUserLinger">
278 <arg type="u" name="uid" direction="in"/>
279 <arg type="b" name="enable" direction="in"/>
280 <arg type="b" name="interactive" direction="in"/>
281 </method>
282 <method name="AttachDevice">
283 <arg type="s" name="seat_id" direction="in"/>
284 <arg type="s" name="sysfs_path" direction="in"/>
285 <arg type="b" name="interactive" direction="in"/>
286 </method>
287 <method name="FlushDevices">
288 <arg type="b" name="interactive" direction="in"/>
289 </method>
290 <method name="PowerOff">
291 <arg type="b" name="interactive" direction="in"/>
292 </method>
293 <method name="PowerOffWithFlags">
294 <arg type="t" name="flags" direction="in"/>
295 </method>
296 <method name="Reboot">
297 <arg type="b" name="interactive" direction="in"/>
298 </method>
299 <method name="RebootWithFlags">
300 <arg type="t" name="flags" direction="in"/>
301 </method>
302 <method name="Halt">
303 <arg type="b" name="interactive" direction="in"/>
304 </method>
305 <method name="HaltWithFlags">
306 <arg type="t" name="flags" direction="in"/>
307 </method>
308 <method name="Suspend">
309 <arg type="b" name="interactive" direction="in"/>
310 </method>
311 <method name="SuspendWithFlags">
312 <arg type="t" name="flags" direction="in"/>
313 </method>
314 <method name="Hibernate">
315 <arg type="b" name="interactive" direction="in"/>
316 </method>
317 <method name="HibernateWithFlags">
318 <arg type="t" name="flags" direction="in"/>
319 </method>
320 <method name="HybridSleep">
321 <arg type="b" name="interactive" direction="in"/>
322 </method>
323 <method name="HybridSleepWithFlags">
324 <arg type="t" name="flags" direction="in"/>
325 </method>
326 <method name="SuspendThenHibernate">
327 <arg type="b" name="interactive" direction="in"/>
328 </method>
329 <method name="SuspendThenHibernateWithFlags">
330 <arg type="t" name="flags" direction="in"/>
331 </method>
332 <method name="Sleep">
333 <arg type="t" name="flags" direction="in"/>
334 </method>
335 <method name="CanPowerOff">
336 <arg type="s" name="result" direction="out"/>
337 </method>
338 <method name="CanReboot">
339 <arg type="s" name="result" direction="out"/>
340 </method>
341 <method name="CanHalt">
342 <arg type="s" name="result" direction="out"/>
343 </method>
344 <method name="CanSuspend">
345 <arg type="s" name="result" direction="out"/>
346 </method>
347 <method name="CanHibernate">
348 <arg type="s" name="result" direction="out"/>
349 </method>
350 <method name="CanHybridSleep">
351 <arg type="s" name="result" direction="out"/>
352 </method>
353 <method name="CanSuspendThenHibernate">
354 <arg type="s" name="result" direction="out"/>
355 </method>
356 <method name="CanSleep">
357 <arg type="s" name="result" direction="out"/>
358 </method>
359 <method name="ScheduleShutdown">
360 <arg type="s" name="type" direction="in"/>
361 <arg type="t" name="usec" direction="in"/>
362 </method>
363 <method name="CancelScheduledShutdown">
364 <arg type="b" name="cancelled" direction="out"/>
365 </method>
366 <method name="Inhibit">
367 <arg type="s" name="what" direction="in"/>
368 <arg type="s" name="who" direction="in"/>
369 <arg type="s" name="why" direction="in"/>
370 <arg type="s" name="mode" direction="in"/>
371 <arg type="h" name="pipe_fd" direction="out"/>
372 </method>
373 <method name="CanRebootParameter">
374 <arg type="s" name="result" direction="out"/>
375 </method>
376 <method name="SetRebootParameter">
377 <arg type="s" name="parameter" direction="in"/>
378 </method>
379 <method name="CanRebootToFirmwareSetup">
380 <arg type="s" name="result" direction="out"/>
381 </method>
382 <method name="SetRebootToFirmwareSetup">
383 <arg type="b" name="enable" direction="in"/>
384 </method>
385 <method name="CanRebootToBootLoaderMenu">
386 <arg type="s" name="result" direction="out"/>
387 </method>
388 <method name="SetRebootToBootLoaderMenu">
389 <arg type="t" name="timeout" direction="in"/>
390 </method>
391 <method name="CanRebootToBootLoaderEntry">
392 <arg type="s" name="result" direction="out"/>
393 </method>
394 <method name="SetRebootToBootLoaderEntry">
395 <arg type="s" name="boot_loader_entry" direction="in"/>
396 </method>
397 <method name="SetWallMessage">
398 <arg type="s" name="wall_message" direction="in"/>
399 <arg type="b" name="enable" direction="in"/>
400 </method>
401 <signal name="SecureAttentionKey">
402 <arg type="s" name="seat_id"/>
403 <arg type="o" name="object_path"/>
404 </signal>
405 <signal name="SessionNew">
406 <arg type="s" name="session_id"/>
407 <arg type="o" name="object_path"/>
408 </signal>
409 <signal name="SessionRemoved">
410 <arg type="s" name="session_id"/>
411 <arg type="o" name="object_path"/>
412 </signal>
413 <signal name="UserNew">
414 <arg type="u" name="uid"/>
415 <arg type="o" name="object_path"/>
416 </signal>
417 <signal name="UserRemoved">
418 <arg type="u" name="uid"/>
419 <arg type="o" name="object_path"/>
420 </signal>
421 <signal name="SeatNew">
422 <arg type="s" name="seat_id"/>
423 <arg type="o" name="object_path"/>
424 </signal>
425 <signal name="SeatRemoved">
426 <arg type="s" name="seat_id"/>
427 <arg type="o" name="object_path"/>
428 </signal>
429 <signal name="PrepareForShutdown">
430 <arg type="b" name="start"/>
431 </signal>
432 <signal name="PrepareForShutdownWithMetadata">
433 <arg type="b" name="start"/>
434 <arg type="a{sv}" name="metadata"/>
435 <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
436 </signal>
437 <signal name="PrepareForSleep">
438 <arg type="b" name="start"/>
439 </signal>
440 </interface>
441 <node name="user"/>
442 <node name="session"/>
443 <node name="seat"/>
444</node>
445
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml
new file mode 100644
index 00000000..7d6fc8ee
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml
@@ -0,0 +1,146 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.login1.Session">
5 <property name="Id" type="s" access="read">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
7 </property>
8 <property name="User" type="o" access="read">
9 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
10 </property>
11 <property name="Name" type="s" access="read">
12 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
13 </property>
14 <property name="Timestamp" type="t" access="read">
15 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
16 </property>
17 <property name="TimestampMonotonic" type="t" access="read">
18 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
19 </property>
20 <property name="VTNr" type="u" access="read">
21 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
22 </property>
23 <property name="Seat" type="o" access="read">
24 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
25 </property>
26 <property name="TTY" type="s" access="read">
27 </property>
28 <property name="Display" type="s" access="read">
29 </property>
30 <property name="Remote" type="b" access="read">
31 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
32 </property>
33 <property name="RemoteHost" type="s" access="read">
34 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
35 </property>
36 <property name="RemoteUser" type="s" access="read">
37 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
38 </property>
39 <property name="Service" type="s" access="read">
40 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
41 </property>
42 <property name="Desktop" type="s" access="read">
43 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
44 </property>
45 <property name="Scope" type="s" access="read">
46 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
47 </property>
48 <property name="Leader" type="u" access="read">
49 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
50 </property>
51 <property name="Audit" type="u" access="read">
52 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
53 </property>
54 <property name="Type" type="s" access="read">
55 </property>
56 <!-- <property name="Class" type="s" access="read"> -->
57 <!-- </property> -->
58 <property name="Active" type="b" access="read">
59 </property>
60 <property name="State" type="s" access="read">
61 </property>
62 <property name="IdleHint" type="b" access="read">
63 </property>
64 <property name="IdleSinceHint" type="t" access="read">
65 </property>
66 <property name="IdleSinceHintMonotonic" type="t" access="read">
67 </property>
68 <property name="CanIdle" type="b" access="read">
69 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
70 </property>
71 <property name="CanLock" type="b" access="read">
72 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
73 </property>
74 <property name="LockedHint" type="b" access="read">
75 </property>
76 <method name="Terminate">
77 </method>
78 <method name="Activate">
79 </method>
80 <!-- <method name="Lock"> -->
81 <!-- </method> -->
82 <!-- <method name="Unlock"> -->
83 <!-- </method> -->
84 <method name="SetIdleHint">
85 <arg type="b" name="idle" direction="in"/>
86 </method>
87 <method name="SetLockedHint">
88 <arg type="b" name="locked" direction="in"/>
89 </method>
90 <method name="Kill">
91 <arg type="s" name="whom" direction="in"/>
92 <arg type="i" name="signal_number" direction="in"/>
93 </method>
94 <method name="TakeControl">
95 <arg type="b" name="force" direction="in"/>
96 </method>
97 <method name="ReleaseControl">
98 </method>
99 <method name="SetType">
100 <arg type="s" name="type" direction="in"/>
101 </method>
102 <!-- <method name="SetClass"> -->
103 <!-- <arg type="s" name="class" direction="in"/> -->
104 <!-- </method> -->
105 <method name="SetDisplay">
106 <arg type="s" name="display" direction="in"/>
107 </method>
108 <method name="SetTTY">
109 <arg type="h" name="tty_fd" direction="in"/>
110 </method>
111 <method name="TakeDevice">
112 <arg type="u" name="major" direction="in"/>
113 <arg type="u" name="minor" direction="in"/>
114 <arg type="h" name="fd" direction="out"/>
115 <arg type="b" name="inactive" direction="out"/>
116 </method>
117 <method name="ReleaseDevice">
118 <arg type="u" name="major" direction="in"/>
119 <arg type="u" name="minor" direction="in"/>
120 </method>
121 <method name="PauseDeviceComplete">
122 <arg type="u" name="major" direction="in"/>
123 <arg type="u" name="minor" direction="in"/>
124 </method>
125 <method name="SetBrightness">
126 <arg type="s" name="subsystem" direction="in"/>
127 <arg type="s" name="name" direction="in"/>
128 <arg type="u" name="brightness" direction="in"/>
129 </method>
130 <signal name="PauseDevice">
131 <arg type="u" name="major"/>
132 <arg type="u" name="minor"/>
133 <arg type="s" name="type"/>
134 </signal>
135 <signal name="ResumeDevice">
136 <arg type="u" name="major"/>
137 <arg type="u" name="minor"/>
138 <arg type="h" name="fd"/>
139 </signal>
140 <signal name="Lock">
141 </signal>
142 <signal name="Unlock">
143 </signal>
144 </interface>
145</node>
146
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml
new file mode 100644
index 00000000..b4f84a13
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml
@@ -0,0 +1,817 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.systemd1.Manager">
5 <property name="Version" type="s" access="read">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
7 </property>
8 <property name="Features" type="s" access="read">
9 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
10 </property>
11 <property name="Virtualization" type="s" access="read">
12 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
13 </property>
14 <property name="ConfidentialVirtualization" type="s" access="read">
15 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
16 </property>
17 <property name="Architecture" type="s" access="read">
18 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
19 </property>
20 <property name="Tainted" type="s" access="read">
21 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
22 </property>
23 <property name="FirmwareTimestamp" type="t" access="read">
24 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
25 </property>
26 <property name="FirmwareTimestampMonotonic" type="t" access="read">
27 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
28 </property>
29 <property name="LoaderTimestamp" type="t" access="read">
30 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
31 </property>
32 <property name="LoaderTimestampMonotonic" type="t" access="read">
33 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
34 </property>
35 <property name="KernelTimestamp" type="t" access="read">
36 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
37 </property>
38 <property name="KernelTimestampMonotonic" type="t" access="read">
39 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
40 </property>
41 <property name="InitRDTimestamp" type="t" access="read">
42 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
43 </property>
44 <property name="InitRDTimestampMonotonic" type="t" access="read">
45 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
46 </property>
47 <property name="UserspaceTimestamp" type="t" access="read">
48 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
49 </property>
50 <property name="UserspaceTimestampMonotonic" type="t" access="read">
51 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
52 </property>
53 <property name="FinishTimestamp" type="t" access="read">
54 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
55 </property>
56 <property name="FinishTimestampMonotonic" type="t" access="read">
57 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
58 </property>
59 <property name="ShutdownStartTimestamp" type="t" access="read">
60 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
61 </property>
62 <property name="ShutdownStartTimestampMonotonic" type="t" access="read">
63 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
64 </property>
65 <property name="SecurityStartTimestamp" type="t" access="read">
66 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
67 </property>
68 <property name="SecurityStartTimestampMonotonic" type="t" access="read">
69 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
70 </property>
71 <property name="SecurityFinishTimestamp" type="t" access="read">
72 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
73 </property>
74 <property name="SecurityFinishTimestampMonotonic" type="t" access="read">
75 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
76 </property>
77 <property name="GeneratorsStartTimestamp" type="t" access="read">
78 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
79 </property>
80 <property name="GeneratorsStartTimestampMonotonic" type="t" access="read">
81 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
82 </property>
83 <property name="GeneratorsFinishTimestamp" type="t" access="read">
84 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
85 </property>
86 <property name="GeneratorsFinishTimestampMonotonic" type="t" access="read">
87 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
88 </property>
89 <property name="UnitsLoadStartTimestamp" type="t" access="read">
90 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
91 </property>
92 <property name="UnitsLoadStartTimestampMonotonic" type="t" access="read">
93 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
94 </property>
95 <property name="UnitsLoadFinishTimestamp" type="t" access="read">
96 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
97 </property>
98 <property name="UnitsLoadFinishTimestampMonotonic" type="t" access="read">
99 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
100 </property>
101 <property name="UnitsLoadTimestamp" type="t" access="read">
102 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
103 </property>
104 <property name="UnitsLoadTimestampMonotonic" type="t" access="read">
105 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
106 </property>
107 <property name="InitRDSecurityStartTimestamp" type="t" access="read">
108 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
109 </property>
110 <property name="InitRDSecurityStartTimestampMonotonic" type="t" access="read">
111 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
112 </property>
113 <property name="InitRDSecurityFinishTimestamp" type="t" access="read">
114 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
115 </property>
116 <property name="InitRDSecurityFinishTimestampMonotonic" type="t" access="read">
117 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
118 </property>
119 <property name="InitRDGeneratorsStartTimestamp" type="t" access="read">
120 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
121 </property>
122 <property name="InitRDGeneratorsStartTimestampMonotonic" type="t" access="read">
123 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
124 </property>
125 <property name="InitRDGeneratorsFinishTimestamp" type="t" access="read">
126 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
127 </property>
128 <property name="InitRDGeneratorsFinishTimestampMonotonic" type="t" access="read">
129 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
130 </property>
131 <property name="InitRDUnitsLoadStartTimestamp" type="t" access="read">
132 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
133 </property>
134 <property name="InitRDUnitsLoadStartTimestampMonotonic" type="t" access="read">
135 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
136 </property>
137 <property name="InitRDUnitsLoadFinishTimestamp" type="t" access="read">
138 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
139 </property>
140 <property name="InitRDUnitsLoadFinishTimestampMonotonic" type="t" access="read">
141 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
142 </property>
143 <property name="LogLevel" type="s" access="readwrite">
144 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
145 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
146 </property>
147 <property name="LogTarget" type="s" access="readwrite">
148 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
149 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
150 </property>
151 <property name="NNames" type="u" access="read">
152 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
153 </property>
154 <property name="NFailedUnits" type="u" access="read">
155 </property>
156 <property name="NJobs" type="u" access="read">
157 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
158 </property>
159 <property name="NInstalledJobs" type="u" access="read">
160 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
161 </property>
162 <property name="NFailedJobs" type="u" access="read">
163 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
164 </property>
165 <property name="Progress" type="d" access="read">
166 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
167 </property>
168 <property name="Environment" type="as" access="read">
169 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
170 </property>
171 <property name="ConfirmSpawn" type="b" access="read">
172 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
173 </property>
174 <property name="ShowStatus" type="b" access="read">
175 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
176 </property>
177 <property name="UnitPath" type="as" access="read">
178 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
179 </property>
180 <property name="DefaultStandardOutput" type="s" access="read">
181 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
182 </property>
183 <property name="DefaultStandardError" type="s" access="read">
184 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
185 </property>
186 <property name="WatchdogDevice" type="s" access="read">
187 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
188 </property>
189 <property name="WatchdogLastPingTimestamp" type="t" access="read">
190 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
191 </property>
192 <property name="WatchdogLastPingTimestampMonotonic" type="t" access="read">
193 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
194 </property>
195 <property name="RuntimeWatchdogUSec" type="t" access="readwrite">
196 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
197 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
198 </property>
199 <property name="RuntimeWatchdogPreUSec" type="t" access="readwrite">
200 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
201 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
202 </property>
203 <property name="RuntimeWatchdogPreGovernor" type="s" access="readwrite">
204 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
205 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
206 </property>
207 <property name="RebootWatchdogUSec" type="t" access="readwrite">
208 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
209 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
210 </property>
211 <property name="KExecWatchdogUSec" type="t" access="readwrite">
212 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
213 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
214 </property>
215 <property name="ServiceWatchdogs" type="b" access="readwrite">
216 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
217 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
218 </property>
219 <property name="ControlGroup" type="s" access="read">
220 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
221 </property>
222 <property name="SystemState" type="s" access="read">
223 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
224 </property>
225 <property name="ExitCode" type="y" access="read">
226 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
227 </property>
228 <property name="DefaultTimerAccuracyUSec" type="t" access="read">
229 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
230 </property>
231 <property name="DefaultTimeoutStartUSec" type="t" access="read">
232 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
233 </property>
234 <property name="DefaultTimeoutStopUSec" type="t" access="read">
235 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
236 </property>
237 <property name="DefaultTimeoutAbortUSec" type="t" access="read">
238 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
239 </property>
240 <property name="DefaultDeviceTimeoutUSec" type="t" access="read">
241 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
242 </property>
243 <property name="DefaultRestartUSec" type="t" access="read">
244 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
245 </property>
246 <property name="DefaultStartLimitIntervalUSec" type="t" access="read">
247 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
248 </property>
249 <property name="DefaultStartLimitBurst" type="u" access="read">
250 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
251 </property>
252 <property name="DefaultCPUAccounting" type="b" access="read">
253 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
254 </property>
255 <property name="DefaultBlockIOAccounting" type="b" access="read">
256 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
257 </property>
258 <property name="DefaultIOAccounting" type="b" access="read">
259 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
260 </property>
261 <property name="DefaultIPAccounting" type="b" access="read">
262 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
263 </property>
264 <property name="DefaultMemoryAccounting" type="b" access="read">
265 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
266 </property>
267 <property name="DefaultTasksAccounting" type="b" access="read">
268 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
269 </property>
270 <property name="DefaultLimitCPU" type="t" access="read">
271 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
272 </property>
273 <property name="DefaultLimitCPUSoft" type="t" access="read">
274 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
275 </property>
276 <property name="DefaultLimitFSIZE" type="t" access="read">
277 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
278 </property>
279 <property name="DefaultLimitFSIZESoft" type="t" access="read">
280 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
281 </property>
282 <property name="DefaultLimitDATA" type="t" access="read">
283 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
284 </property>
285 <property name="DefaultLimitDATASoft" type="t" access="read">
286 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
287 </property>
288 <property name="DefaultLimitSTACK" type="t" access="read">
289 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
290 </property>
291 <property name="DefaultLimitSTACKSoft" type="t" access="read">
292 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
293 </property>
294 <property name="DefaultLimitCORE" type="t" access="read">
295 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
296 </property>
297 <property name="DefaultLimitCORESoft" type="t" access="read">
298 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
299 </property>
300 <property name="DefaultLimitRSS" type="t" access="read">
301 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
302 </property>
303 <property name="DefaultLimitRSSSoft" type="t" access="read">
304 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
305 </property>
306 <property name="DefaultLimitNOFILE" type="t" access="read">
307 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
308 </property>
309 <property name="DefaultLimitNOFILESoft" type="t" access="read">
310 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
311 </property>
312 <property name="DefaultLimitAS" type="t" access="read">
313 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
314 </property>
315 <property name="DefaultLimitASSoft" type="t" access="read">
316 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
317 </property>
318 <property name="DefaultLimitNPROC" type="t" access="read">
319 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
320 </property>
321 <property name="DefaultLimitNPROCSoft" type="t" access="read">
322 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
323 </property>
324 <property name="DefaultLimitMEMLOCK" type="t" access="read">
325 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
326 </property>
327 <property name="DefaultLimitMEMLOCKSoft" type="t" access="read">
328 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
329 </property>
330 <property name="DefaultLimitLOCKS" type="t" access="read">
331 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
332 </property>
333 <property name="DefaultLimitLOCKSSoft" type="t" access="read">
334 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
335 </property>
336 <property name="DefaultLimitSIGPENDING" type="t" access="read">
337 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
338 </property>
339 <property name="DefaultLimitSIGPENDINGSoft" type="t" access="read">
340 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
341 </property>
342 <property name="DefaultLimitMSGQUEUE" type="t" access="read">
343 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
344 </property>
345 <property name="DefaultLimitMSGQUEUESoft" type="t" access="read">
346 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
347 </property>
348 <property name="DefaultLimitNICE" type="t" access="read">
349 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
350 </property>
351 <property name="DefaultLimitNICESoft" type="t" access="read">
352 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
353 </property>
354 <property name="DefaultLimitRTPRIO" type="t" access="read">
355 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
356 </property>
357 <property name="DefaultLimitRTPRIOSoft" type="t" access="read">
358 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
359 </property>
360 <property name="DefaultLimitRTTIME" type="t" access="read">
361 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
362 </property>
363 <property name="DefaultLimitRTTIMESoft" type="t" access="read">
364 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
365 </property>
366 <property name="DefaultTasksMax" type="t" access="read">
367 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
368 </property>
369 <property name="DefaultMemoryPressureThresholdUSec" type="t" access="read">
370 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
371 </property>
372 <property name="DefaultMemoryPressureWatch" type="s" access="read">
373 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
374 </property>
375 <property name="TimerSlackNSec" type="t" access="read">
376 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
377 </property>
378 <property name="DefaultOOMPolicy" type="s" access="read">
379 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
380 </property>
381 <property name="DefaultOOMScoreAdjust" type="i" access="read">
382 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
383 </property>
384 <property name="CtrlAltDelBurstAction" type="s" access="read">
385 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
386 </property>
387 <property name="SoftRebootsCount" type="u" access="read">
388 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
389 </property>
390 <method name="GetUnit">
391 <arg type="s" name="name" direction="in"/>
392 <arg type="o" name="unit" direction="out"/>
393 </method>
394 <method name="GetUnitByPID">
395 <arg type="u" name="pid" direction="in"/>
396 <arg type="o" name="unit" direction="out"/>
397 </method>
398 <method name="GetUnitByInvocationID">
399 <arg type="ay" name="invocation_id" direction="in"/>
400 <arg type="o" name="unit" direction="out"/>
401 </method>
402 <method name="GetUnitByControlGroup">
403 <arg type="s" name="cgroup" direction="in"/>
404 <arg type="o" name="unit" direction="out"/>
405 </method>
406 <method name="GetUnitByPIDFD">
407 <arg type="h" name="pidfd" direction="in"/>
408 <arg type="o" name="unit" direction="out"/>
409 <arg type="s" name="unit_id" direction="out"/>
410 <arg type="ay" name="invocation_id" direction="out"/>
411 </method>
412 <method name="LoadUnit">
413 <arg type="s" name="name" direction="in"/>
414 <arg type="o" name="unit" direction="out"/>
415 </method>
416 <method name="StartUnit">
417 <arg type="s" name="name" direction="in"/>
418 <arg type="s" name="mode" direction="in"/>
419 <arg type="o" name="job" direction="out"/>
420 </method>
421 <method name="StartUnitWithFlags">
422 <arg type="s" name="name" direction="in"/>
423 <arg type="s" name="mode" direction="in"/>
424 <arg type="t" name="flags" direction="in"/>
425 <arg type="o" name="job" direction="out"/>
426 </method>
427 <method name="StartUnitReplace">
428 <arg type="s" name="old_unit" direction="in"/>
429 <arg type="s" name="new_unit" direction="in"/>
430 <arg type="s" name="mode" direction="in"/>
431 <arg type="o" name="job" direction="out"/>
432 </method>
433 <method name="StopUnit">
434 <arg type="s" name="name" direction="in"/>
435 <arg type="s" name="mode" direction="in"/>
436 <arg type="o" name="job" direction="out"/>
437 </method>
438 <method name="ReloadUnit">
439 <arg type="s" name="name" direction="in"/>
440 <arg type="s" name="mode" direction="in"/>
441 <arg type="o" name="job" direction="out"/>
442 </method>
443 <method name="RestartUnit">
444 <arg type="s" name="name" direction="in"/>
445 <arg type="s" name="mode" direction="in"/>
446 <arg type="o" name="job" direction="out"/>
447 </method>
448 <method name="TryRestartUnit">
449 <arg type="s" name="name" direction="in"/>
450 <arg type="s" name="mode" direction="in"/>
451 <arg type="o" name="job" direction="out"/>
452 </method>
453 <method name="ReloadOrRestartUnit">
454 <arg type="s" name="name" direction="in"/>
455 <arg type="s" name="mode" direction="in"/>
456 <arg type="o" name="job" direction="out"/>
457 </method>
458 <method name="ReloadOrTryRestartUnit">
459 <arg type="s" name="name" direction="in"/>
460 <arg type="s" name="mode" direction="in"/>
461 <arg type="o" name="job" direction="out"/>
462 </method>
463 <!-- <method name="EnqueueUnitJob"> -->
464 <!-- <arg type="s" name="name" direction="in"/> -->
465 <!-- <arg type="s" name="job_type" direction="in"/> -->
466 <!-- <arg type="s" name="job_mode" direction="in"/> -->
467 <!-- <arg type="u" name="job_id" direction="out"/> -->
468 <!-- <arg type="o" name="job_path" direction="out"/> -->
469 <!-- <arg type="s" name="unit_id" direction="out"/> -->
470 <!-- <arg type="o" name="unit_path" direction="out"/> -->
471 <!-- <arg type="s" name="job_type" direction="out"/> -->
472 <!-- <arg type="a(uosos)" name="affected_jobs" direction="out"/> -->
473 <!-- </method> -->
474 <method name="KillUnit">
475 <arg type="s" name="name" direction="in"/>
476 <arg type="s" name="whom" direction="in"/>
477 <arg type="i" name="signal" direction="in"/>
478 </method>
479 <method name="QueueSignalUnit">
480 <arg type="s" name="name" direction="in"/>
481 <arg type="s" name="whom" direction="in"/>
482 <arg type="i" name="signal" direction="in"/>
483 <arg type="i" name="value" direction="in"/>
484 </method>
485 <method name="CleanUnit">
486 <arg type="s" name="name" direction="in"/>
487 <arg type="as" name="mask" direction="in"/>
488 </method>
489 <method name="FreezeUnit">
490 <arg type="s" name="name" direction="in"/>
491 </method>
492 <method name="ThawUnit">
493 <arg type="s" name="name" direction="in"/>
494 </method>
495 <method name="ResetFailedUnit">
496 <arg type="s" name="name" direction="in"/>
497 </method>
498 <!-- <method name="SetUnitProperties"> -->
499 <!-- <arg type="s" name="name" direction="in"/> -->
500 <!-- <arg type="b" name="runtime" direction="in"/> -->
501 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
502 <!-- </method> -->
503 <method name="BindMountUnit">
504 <arg type="s" name="name" direction="in"/>
505 <arg type="s" name="source" direction="in"/>
506 <arg type="s" name="destination" direction="in"/>
507 <arg type="b" name="read_only" direction="in"/>
508 <arg type="b" name="mkdir" direction="in"/>
509 </method>
510 <!-- <method name="MountImageUnit"> -->
511 <!-- <arg type="s" name="name" direction="in"/> -->
512 <!-- <arg type="s" name="source" direction="in"/> -->
513 <!-- <arg type="s" name="destination" direction="in"/> -->
514 <!-- <arg type="b" name="read_only" direction="in"/> -->
515 <!-- <arg type="b" name="mkdir" direction="in"/> -->
516 <!-- <arg type="a(ss)" name="options" direction="in"/> -->
517 <!-- </method> -->
518 <method name="RefUnit">
519 <arg type="s" name="name" direction="in"/>
520 </method>
521 <method name="UnrefUnit">
522 <arg type="s" name="name" direction="in"/>
523 </method>
524 <!-- <method name="StartTransientUnit"> -->
525 <!-- <arg type="s" name="name" direction="in"/> -->
526 <!-- <arg type="s" name="mode" direction="in"/> -->
527 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
528 <!-- <arg type="a(sa(sv))" name="aux" direction="in"/> -->
529 <!-- <arg type="o" name="job" direction="out"/> -->
530 <!-- </method> -->
531 <!-- <method name="GetUnitProcesses"> -->
532 <!-- <arg type="s" name="name" direction="in"/> -->
533 <!-- <arg type="a(sus)" name="processes" direction="out"/> -->
534 <!-- </method> -->
535 <!-- <method name="AttachProcessesToUnit"> -->
536 <!-- <arg type="s" name="unit_name" direction="in"/> -->
537 <!-- <arg type="s" name="subcgroup" direction="in"/> -->
538 <!-- <arg type="au" name="pids" direction="in"/> -->
539 <!-- </method> -->
540 <method name="AbandonScope">
541 <arg type="s" name="name" direction="in"/>
542 </method>
543 <method name="GetJob">
544 <arg type="u" name="id" direction="in"/>
545 <arg type="o" name="job" direction="out"/>
546 </method>
547 <!-- <method name="GetJobAfter"> -->
548 <!-- <arg type="u" name="id" direction="in"/> -->
549 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
550 <!-- </method> -->
551 <!-- <method name="GetJobBefore"> -->
552 <!-- <arg type="u" name="id" direction="in"/> -->
553 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
554 <!-- </method> -->
555 <method name="CancelJob">
556 <arg type="u" name="id" direction="in"/>
557 </method>
558 <method name="ClearJobs">
559 </method>
560 <method name="ResetFailed">
561 </method>
562 <method name="SetShowStatus">
563 <arg type="s" name="mode" direction="in"/>
564 </method>
565 <!-- <method name="ListUnits"> -->
566 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
567 <!-- </method> -->
568 <!-- <method name="ListUnitsFiltered"> -->
569 <!-- <arg type="as" name="states" direction="in"/> -->
570 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
571 <!-- </method> -->
572 <!-- <method name="ListUnitsByPatterns"> -->
573 <!-- <arg type="as" name="states" direction="in"/> -->
574 <!-- <arg type="as" name="patterns" direction="in"/> -->
575 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
576 <!-- </method> -->
577 <!-- <method name="ListUnitsByNames"> -->
578 <!-- <arg type="as" name="names" direction="in"/> -->
579 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
580 <!-- </method> -->
581 <!-- <method name="ListJobs"> -->
582 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
583 <!-- </method> -->
584 <method name="Subscribe">
585 </method>
586 <method name="Unsubscribe">
587 </method>
588 <method name="Dump">
589 <arg type="s" name="output" direction="out"/>
590 </method>
591 <method name="DumpUnitsMatchingPatterns">
592 <arg type="as" name="patterns" direction="in"/>
593 <arg type="s" name="output" direction="out"/>
594 </method>
595 <method name="DumpByFileDescriptor">
596 <arg type="h" name="fd" direction="out"/>
597 </method>
598 <method name="DumpUnitsMatchingPatternsByFileDescriptor">
599 <arg type="as" name="patterns" direction="in"/>
600 <arg type="h" name="fd" direction="out"/>
601 </method>
602 <method name="Reload">
603 </method>
604 <method name="Reexecute">
605 <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
606 </method>
607 <method name="Exit">
608 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
609 </method>
610 <method name="Reboot">
611 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
612 </method>
613 <method name="SoftReboot">
614 <arg type="s" name="new_root" direction="in"/>
615 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
616 </method>
617 <method name="PowerOff">
618 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
619 </method>
620 <method name="Halt">
621 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
622 </method>
623 <method name="KExec">
624 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
625 </method>
626 <method name="SwitchRoot">
627 <arg type="s" name="new_root" direction="in"/>
628 <arg type="s" name="init" direction="in"/>
629 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
630 </method>
631 <method name="SetEnvironment">
632 <arg type="as" name="assignments" direction="in"/>
633 </method>
634 <method name="UnsetEnvironment">
635 <arg type="as" name="names" direction="in"/>
636 </method>
637 <method name="UnsetAndSetEnvironment">
638 <arg type="as" name="names" direction="in"/>
639 <arg type="as" name="assignments" direction="in"/>
640 </method>
641 <method name="EnqueueMarkedJobs">
642 <arg type="ao" name="jobs" direction="out"/>
643 </method>
644 <!-- <method name="ListUnitFiles"> -->
645 <!-- <arg type="a(ss)" name="unit_files" direction="out"/> -->
646 <!-- </method> -->
647 <!-- <method name="ListUnitFilesByPatterns"> -->
648 <!-- <arg type="as" name="states" direction="in"/> -->
649 <!-- <arg type="as" name="patterns" direction="in"/> -->
650 <!-- <arg type="a(ss)" name="unit_files" direction="out"/> -->
651 <!-- </method> -->
652 <method name="GetUnitFileState">
653 <arg type="s" name="file" direction="in"/>
654 <arg type="s" name="state" direction="out"/>
655 </method>
656 <!-- <method name="EnableUnitFiles"> -->
657 <!-- <arg type="as" name="files" direction="in"/> -->
658 <!-- <arg type="b" name="runtime" direction="in"/> -->
659 <!-- <arg type="b" name="force" direction="in"/> -->
660 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
661 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
662 <!-- </method> -->
663 <!-- <method name="DisableUnitFiles"> -->
664 <!-- <arg type="as" name="files" direction="in"/> -->
665 <!-- <arg type="b" name="runtime" direction="in"/> -->
666 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
667 <!-- </method> -->
668 <!-- <method name="EnableUnitFilesWithFlags"> -->
669 <!-- <arg type="as" name="files" direction="in"/> -->
670 <!-- <arg type="t" name="flags" direction="in"/> -->
671 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
672 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
673 <!-- </method> -->
674 <!-- <method name="DisableUnitFilesWithFlags"> -->
675 <!-- <arg type="as" name="files" direction="in"/> -->
676 <!-- <arg type="t" name="flags" direction="in"/> -->
677 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
678 <!-- </method> -->
679 <!-- <method name="DisableUnitFilesWithFlagsAndInstallInfo"> -->
680 <!-- <arg type="as" name="files" direction="in"/> -->
681 <!-- <arg type="t" name="flags" direction="in"/> -->
682 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
683 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
684 <!-- </method> -->
685 <!-- <method name="ReenableUnitFiles"> -->
686 <!-- <arg type="as" name="files" direction="in"/> -->
687 <!-- <arg type="b" name="runtime" direction="in"/> -->
688 <!-- <arg type="b" name="force" direction="in"/> -->
689 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
690 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
691 <!-- </method> -->
692 <!-- <method name="LinkUnitFiles"> -->
693 <!-- <arg type="as" name="files" direction="in"/> -->
694 <!-- <arg type="b" name="runtime" direction="in"/> -->
695 <!-- <arg type="b" name="force" direction="in"/> -->
696 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
697 <!-- </method> -->
698 <!-- <method name="PresetUnitFiles"> -->
699 <!-- <arg type="as" name="files" direction="in"/> -->
700 <!-- <arg type="b" name="runtime" direction="in"/> -->
701 <!-- <arg type="b" name="force" direction="in"/> -->
702 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
703 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
704 <!-- </method> -->
705 <!-- <method name="PresetUnitFilesWithMode"> -->
706 <!-- <arg type="as" name="files" direction="in"/> -->
707 <!-- <arg type="s" name="mode" direction="in"/> -->
708 <!-- <arg type="b" name="runtime" direction="in"/> -->
709 <!-- <arg type="b" name="force" direction="in"/> -->
710 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
711 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
712 <!-- </method> -->
713 <!-- <method name="MaskUnitFiles"> -->
714 <!-- <arg type="as" name="files" direction="in"/> -->
715 <!-- <arg type="b" name="runtime" direction="in"/> -->
716 <!-- <arg type="b" name="force" direction="in"/> -->
717 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
718 <!-- </method> -->
719 <!-- <method name="UnmaskUnitFiles"> -->
720 <!-- <arg type="as" name="files" direction="in"/> -->
721 <!-- <arg type="b" name="runtime" direction="in"/> -->
722 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
723 <!-- </method> -->
724 <!-- <method name="RevertUnitFiles"> -->
725 <!-- <arg type="as" name="files" direction="in"/> -->
726 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
727 <!-- </method> -->
728 <!-- <method name="SetDefaultTarget"> -->
729 <!-- <arg type="s" name="name" direction="in"/> -->
730 <!-- <arg type="b" name="force" direction="in"/> -->
731 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
732 <!-- </method> -->
733 <method name="GetDefaultTarget">
734 <arg type="s" name="name" direction="out"/>
735 </method>
736 <!-- <method name="PresetAllUnitFiles"> -->
737 <!-- <arg type="s" name="mode" direction="in"/> -->
738 <!-- <arg type="b" name="runtime" direction="in"/> -->
739 <!-- <arg type="b" name="force" direction="in"/> -->
740 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
741 <!-- </method> -->
742 <!-- <method name="AddDependencyUnitFiles"> -->
743 <!-- <arg type="as" name="files" direction="in"/> -->
744 <!-- <arg type="s" name="target" direction="in"/> -->
745 <!-- <arg type="s" name="type" direction="in"/> -->
746 <!-- <arg type="b" name="runtime" direction="in"/> -->
747 <!-- <arg type="b" name="force" direction="in"/> -->
748 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
749 <!-- </method> -->
750 <method name="GetUnitFileLinks">
751 <arg type="s" name="name" direction="in"/>
752 <arg type="b" name="runtime" direction="in"/>
753 <arg type="as" name="links" direction="out"/>
754 </method>
755 <method name="SetExitCode">
756 <arg type="y" name="number" direction="in"/>
757 </method>
758 <method name="LookupDynamicUserByName">
759 <arg type="s" name="name" direction="in"/>
760 <arg type="u" name="uid" direction="out"/>
761 </method>
762 <method name="LookupDynamicUserByUID">
763 <arg type="u" name="uid" direction="in"/>
764 <arg type="s" name="name" direction="out"/>
765 </method>
766 <!-- <method name="GetDynamicUsers"> -->
767 <!-- <arg type="a(us)" name="users" direction="out"/> -->
768 <!-- </method> -->
769 <!-- <method name="DumpUnitFileDescriptorStore"> -->
770 <!-- <arg type="s" name="name" direction="in"/> -->
771 <!-- <arg type="a(suuutuusu)" name="entries" direction="out"/> -->
772 <!-- </method> -->
773 <!-- <method name="StartAuxiliaryScope"> -->
774 <!-- <arg type="s" name="name" direction="in"/> -->
775 <!-- <arg type="ah" name="pidfds" direction="in"/> -->
776 <!-- <arg type="t" name="flags" direction="in"/> -->
777 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
778 <!-- <arg type="o" name="job" direction="out"/> -->
779 <!-- <annotation name="org.freedesktop.DBus.Deprecated" value="true"/> -->
780 <!-- </method> -->
781 <signal name="UnitNew">
782 <arg type="s" name="id"/>
783 <arg type="o" name="unit"/>
784 </signal>
785 <signal name="UnitRemoved">
786 <arg type="s" name="id"/>
787 <arg type="o" name="unit"/>
788 </signal>
789 <signal name="JobNew">
790 <arg type="u" name="id"/>
791 <arg type="o" name="job"/>
792 <arg type="s" name="unit"/>
793 </signal>
794 <signal name="JobRemoved">
795 <arg type="u" name="id"/>
796 <arg type="o" name="job"/>
797 <arg type="s" name="unit"/>
798 <arg type="s" name="result"/>
799 </signal>
800 <signal name="StartupFinished">
801 <arg type="t" name="firmware"/>
802 <arg type="t" name="loader"/>
803 <arg type="t" name="kernel"/>
804 <arg type="t" name="initrd"/>
805 <arg type="t" name="userspace"/>
806 <arg type="t" name="total"/>
807 </signal>
808 <signal name="UnitFilesChanged">
809 </signal>
810 <signal name="Reloading">
811 <arg type="b" name="active"/>
812 </signal>
813 </interface>
814 <node name="unit"/>
815 <node name="job"/>
816</node>
817
diff --git a/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
new file mode 100644
index 00000000..dcc23279
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
@@ -0,0 +1,172 @@
1import QtQuick
2import qs.Services
3import Quickshell
4import Quickshell.Widgets
5
6Item {
7 id: activeWindowDisplay
8
9 required property int maxWidth
10 required property var screen
11
12 property var activeWindow: {
13 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
14 return ws.output === screen.name && ws.is_active;
15 })?.active_window_id;
16
17 return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null;
18 }
19 property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null
20
21 anchors.verticalCenter: parent.verticalCenter
22 width: activeWindowDisplayContent.width
23 height: parent.height
24
25 WrapperMouseArea {
26 id: widgetMouseArea
27
28 anchors.fill: parent
29
30 hoverEnabled: true
31
32 Item {
33 anchors.fill: parent
34
35 Row {
36 id: activeWindowDisplayContent
37
38 width: childrenRect.width
39 height: parent.height
40 anchors.verticalCenter: parent.verticalCenter
41 spacing: 8
42
43 IconImage {
44 id: activeWindowIcon
45
46 implicitSize: 14
47
48 anchors.verticalCenter: parent.verticalCenter
49
50 source: {
51 let icon = activeWindowDisplay.windowEntry?.icon
52 if (typeof icon === 'string' || icon instanceof String) {
53 if (icon.includes("?path=")) {
54 const split = icon.split("?path=")
55 if (split.length !== 2)
56 return icon
57 const name = split[0]
58 const path = split[1]
59 const fileName = name.substring(
60 name.lastIndexOf("/") + 1)
61 return `file://${path}/${fileName}`
62 } else
63 icon = Quickshell.iconPath(icon);
64 return icon
65 }
66 return ""
67 }
68 asynchronous: true
69 smooth: true
70 mipmap: true
71 }
72
73 Text {
74 id: windowTitle
75
76 width: Math.min(implicitWidth, activeWindowDisplay.maxWidth - activeWindowIcon.width - activeWindowDisplayContent.spacing)
77
78 property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" }
79
80 elide: Text.ElideRight
81 maximumLineCount: 1
82 color: "white"
83 anchors.verticalCenter: parent.verticalCenter
84 text: {
85 if (!activeWindowDisplay.activeWindow)
86 return "";
87
88 var title = activeWindowDisplay.activeWindow.title;
89 var appName = activeWindowDisplay.windowEntry?.name;
90 if (appAliases[appName])
91 appName = appAliases[appName];
92 if (appName && title.endsWith(appName)) {
93 const oldTitle = title;
94 title = title.substring(0, title.length - appName.length);
95 title = title.replace(/\s*(—|-)\s*$/, "");
96 if (!title)
97 title = oldTitle;
98 }
99 return title;
100 }
101 }
102 }
103 }
104 }
105
106 Loader {
107 id: tooltipLoader
108
109 active: false
110
111 Connections {
112 target: widgetMouseArea
113 function onContainsMouseChanged() {
114 if (widgetMouseArea.containsMouse)
115 tooltipLoader.active = true;
116 }
117 }
118
119 PopupWindow {
120 id: tooltip
121
122 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
123
124 anchor {
125 item: widgetMouseArea
126 edges: Edges.Bottom | Edges.Left
127 }
128 visible: false
129
130 onNextVisibleChanged: hangTimer.restart()
131
132 Timer {
133 id: hangTimer
134 interval: tooltip.visible ? 100 : 500
135 onTriggered: {
136 tooltip.visible = tooltip.nextVisible;
137 if (!tooltip.visible)
138 tooltipLoader.active = false;
139 }
140 }
141
142 implicitWidth: widgetTooltipText.contentWidth + 16
143 implicitHeight: widgetTooltipText.contentHeight + 16
144 color: "black"
145
146 WrapperMouseArea {
147 id: tooltipMouseArea
148
149 hoverEnabled: true
150 enabled: true
151
152 anchors.fill: parent
153
154 Item {
155 anchors.fill: parent
156
157 Text {
158 id: widgetTooltipText
159
160 anchors.centerIn: parent
161
162 font.pointSize: 10
163 font.family: "Fira Mono"
164 color: "white"
165
166 text: JSON.stringify(Object.assign({}, activeWindowDisplay.activeWindow), null, 2)
167 }
168 }
169 }
170 }
171 }
172}
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml
new file mode 100644
index 00000000..9210066c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -0,0 +1,117 @@
1import Quickshell
2import Quickshell.Wayland
3import QtQuick
4
5PanelWindow {
6 id: bar
7
8 required property var modelData
9 screen: modelData
10
11 WlrLayershell.namespace: "bar"
12
13 anchors {
14 top: true
15 left: true
16 right: true
17 }
18 margins {
19 left: 26 + 8
20 right: 26 + 8
21 }
22
23 implicitHeight: 21
24 color: "transparent"
25
26 Rectangle {
27 color: Qt.rgba(0, 0, 0, 0.75)
28 anchors.fill: parent
29 // bottomLeftRadius: 8
30 // bottomRightRadius: 8
31 }
32
33 Row {
34 id: left
35
36 height: parent.height
37 width: childrenRect.width
38 anchors.left: parent.left
39 anchors.leftMargin: 8
40 anchors.verticalCenter: parent.verticalCenter
41 spacing: 8
42
43 WorkspaceSwitcher {
44 screen: bar.screen
45 }
46 }
47
48 Row {
49 id: center
50
51 height: parent.height
52 width: childrenRect.width
53 anchors.centerIn: parent
54 spacing: 5
55
56 ActiveWindowDisplay {
57 screen: bar.screen
58 maxWidth: bar.width - 2*Math.max(left.width, right.width) - 2*8
59 }
60 }
61
62 Row {
63 id: right
64
65 height: parent.height
66 width: childrenRect.width
67 anchors.right: parent.right
68 anchors.rightMargin: 8
69 anchors.verticalCenter: parent.verticalCenter
70 spacing: 0
71
72 // WorktimeWidget { command: "time"; }
73
74 // WorktimeWidget { command: "today"; }
75
76 KeyboardLayout {}
77
78 Item {
79 visible: privacy.visible
80 height: parent.height
81 width: 8 - 4
82 }
83
84 PrivacyWidget {
85 id: privacy
86 }
87
88 Item {
89 visible: privacy.visible
90 height: parent.height
91 width: 8 - 4
92 }
93
94 SystemTray {}
95
96 PipewireWidget {}
97
98 BrightnessWidget {}
99
100 BatteryWidget {}
101
102 WaylandInhibitorWidget {
103 window: bar
104 }
105
106 NotificationInhibitorWidget {}
107
108 LidSwitchInhibitorWidget {}
109
110 Item {
111 height: parent.height
112 width: 8 - 4
113 }
114
115 Clock {}
116 }
117} \ No newline at end of file
diff --git a/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml
new file mode 100644
index 00000000..da17df2a
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml
@@ -0,0 +1,136 @@
1import QtQuick
2import Quickshell
3import Quickshell.Widgets
4import Quickshell.Services.UPower
5
6Item {
7 id: root
8
9 height: parent.height
10 width: batteryIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 property var batteryDevice: Array.from(UPower.devices.values).find(dev => dev.isLaptopBattery)
14
15 WrapperMouseArea {
16 id: widgetMouseArea
17
18 anchors.fill: parent
19
20 hoverEnabled: true
21
22 Item {
23 anchors.fill: parent
24
25 MaterialDesignIcon {
26 id: batteryIcon
27
28 implicitSize: 14
29 anchors.centerIn: parent
30
31 icon: {
32 if (!root.batteryDevice?.ready)
33 return "battery-unknown";
34
35 if (root.batteryDevice.state == UPowerDeviceState.FullyCharged)
36 return "power-plug-battery";
37
38 const perdec = 10 * Math.max(1, Math.ceil(root.batteryDevice.percentage * 10));
39 if (root.batteryDevice.state == UPowerDeviceState.Charging)
40 return `battery-charging-${perdec}`;
41 if (perdec == 100)
42 return "battery";
43 return `battery-${perdec}`;
44 }
45 color: {
46 if (!root.batteryDevice?.ready)
47 return "#555";
48
49 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 20 * 60)
50 return "#f2201f";
51 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 40 * 60)
52 return "#f28a21";
53 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged)
54 return "#fff";
55 return "#555";
56 }
57 }
58 }
59 }
60
61 PopupWindow {
62 id: tooltip
63
64 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
65
66 anchor {
67 item: widgetMouseArea
68 edges: Edges.Bottom | Edges.Left
69 }
70 visible: false
71
72 onNextVisibleChanged: hangTimer.restart()
73
74 Timer {
75 id: hangTimer
76 interval: 100
77 onTriggered: tooltip.visible = tooltip.nextVisible
78 }
79
80 implicitWidth: widgetTooltipText.contentWidth + 16
81 implicitHeight: widgetTooltipText.contentHeight + 16
82 color: "black"
83
84 WrapperMouseArea {
85 id: tooltipMouseArea
86
87 hoverEnabled: true
88 enabled: true
89
90 anchors.fill: parent
91
92 Item {
93 anchors.fill: parent
94
95 Text {
96 id: widgetTooltipText
97
98 anchors.centerIn: parent
99
100 font.pointSize: 10
101 font.family: "Fira Sans"
102 color: "white"
103
104 text: {
105 const stateStr = UPowerDeviceState.toString(root.batteryDevice.state);
106 var outStr = stateStr;
107 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged)
108 outStr += ` ${Math.round(root.batteryDevice.percentage * 100)}%`;
109
110 function formatTime(t) {
111 var res = "";
112 for (const unit of [{ "s": "h", "v": 3600 }, { "s": "m", "v": 60 }, { "s": "s", "v": 1 }]) {
113 if (t < unit.v)
114 continue;
115 res += Math.floor(t / unit.v) + unit.s;
116 t %= unit.v;
117 }
118 return res;
119 }
120 if (root.batteryDevice.timeToEmpty != 0) {
121 const tStr = formatTime(Math.floor(root.batteryDevice.timeToEmpty / 60) * 60);
122 if (tStr)
123 outStr += " " + tStr;
124 } else if (root.batteryDevice.timeToFull != 0) {
125 const tStr = formatTime(Math.ceil(root.batteryDevice.timeToFull / 60) * 60);
126 if (tStr)
127 outStr += " " + tStr;
128 }
129
130 return outStr;
131 }
132 }
133 }
134 }
135 }
136}
diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml
new file mode 100644
index 00000000..a432179e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml
@@ -0,0 +1,117 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Widgets
5import qs.Services
6
7Scope {
8 id: root
9
10 property bool show: false
11 property bool inhibited: true
12
13 Connections {
14 target: Brightness
15
16 function onCurrBrightnessChanged() {
17 root.show = true;
18 hideTimer.restart();
19 }
20 }
21
22 onShowChanged: {
23 if (show)
24 hideTimer.restart();
25 }
26
27 Timer {
28 id: hideTimer
29 interval: 1000
30 onTriggered: root.show = false
31 }
32
33 Timer {
34 id: startInhibit
35 interval: 100
36 running: true
37 onTriggered: {
38 root.show = false;
39 root.inhibited = false;
40 }
41 }
42
43 LazyLoader {
44 active: root.show && !root.inhibited
45
46 Variants {
47 model: Quickshell.screens
48
49 delegate: Scope {
50 id: screenScope
51
52 required property var modelData
53
54 PanelWindow {
55 id: window
56
57 screen: screenScope.modelData
58
59 anchors.top: true
60 margins.top: screen.height / 2 - 50 + 3.5
61 exclusiveZone: 0
62 exclusionMode: ExclusionMode.Ignore
63
64 implicitWidth: 400
65 implicitHeight: 50
66
67 mask: Region {}
68
69 color: "transparent"
70
71 Rectangle {
72 anchors.fill: parent
73 color: Qt.rgba(0, 0, 0, 0.75)
74 }
75
76 RowLayout {
77 id: layout
78
79 anchors.centerIn: parent
80
81 height: 50 - 8*2
82 width: 400 - 8*2
83
84 MaterialDesignIcon {
85 id: volumeIcon
86
87 implicitWidth: parent.height
88 implicitHeight: parent.height
89
90 icon: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}`
91 }
92
93 Rectangle {
94 Layout.fillWidth: true
95
96 implicitHeight: 10
97
98 color: "#50ffffff"
99
100 Rectangle {
101 anchors {
102 left: parent.left
103 top: parent.top
104 bottom: parent.bottom
105 }
106
107 color: "white"
108
109 implicitWidth: parent.width * Brightness.currBrightness
110 }
111 }
112 }
113 }
114 }
115 }
116 }
117}
diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml
new file mode 100644
index 00000000..3bb5a80e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml
@@ -0,0 +1,84 @@
1import QtQuick
2import Quickshell
3import Quickshell.Widgets
4import qs.Services
5
6Item {
7 height: parent.height
8 width: brightnessIcon.width + 8
9 anchors.verticalCenter: parent.verticalCenter
10
11 WrapperMouseArea {
12 id: widgetMouseArea
13
14 anchors.fill: parent
15
16 hoverEnabled: true
17
18 property real sensitivity: (1 / 50) / 120
19 onWheel: event => Brightness.currBrightness += event.angleDelta.y * sensitivity
20
21 Item {
22 anchors.fill: parent
23
24 MaterialDesignIcon {
25 id: brightnessIcon
26
27 implicitSize: 14
28 anchors.centerIn: parent
29
30 icon: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}`
31 color: "#555"
32 }
33 }
34 }
35
36 PopupWindow {
37 id: tooltip
38
39 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
40
41 anchor {
42 item: widgetMouseArea
43 edges: Edges.Bottom | Edges.Left
44 }
45 visible: false
46
47 onNextVisibleChanged: hangTimer.restart()
48
49 Timer {
50 id: hangTimer
51 interval: 100
52 onTriggered: tooltip.visible = tooltip.nextVisible
53 }
54
55 implicitWidth: widgetTooltipText.contentWidth + 16
56 implicitHeight: widgetTooltipText.contentHeight + 16
57 color: "black"
58
59 WrapperMouseArea {
60 id: tooltipMouseArea
61
62 hoverEnabled: true
63 enabled: true
64
65 anchors.fill: parent
66
67 Item {
68 anchors.fill: parent
69
70 Text {
71 id: widgetTooltipText
72
73 anchors.centerIn: parent
74
75 font.pointSize: 10
76 font.family: "Fira Sans"
77 color: "white"
78
79 text: `${Math.round(Brightness.currBrightness * 100)}%`
80 }
81 }
82 }
83 }
84}
diff --git a/accounts/gkleen@sif/shell/quickshell/Clock.qml b/accounts/gkleen@sif/shell/quickshell/Clock.qml
new file mode 100644
index 00000000..b7004528
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Clock.qml
@@ -0,0 +1,295 @@
1import QtQml
2import QtQuick
3import Quickshell
4import Custom as Custom
5import QtQuick.Controls
6import QtQuick.Layouts
7import Quickshell.Widgets
8
9Item {
10 id: clockItem
11
12 property bool calendarPopup: true
13
14 width: clock.contentWidth
15 height: parent.height
16 anchors.verticalCenter: parent.verticalCenter
17
18 WrapperMouseArea {
19 id: clockMouseArea
20
21 anchors.fill: parent
22 hoverEnabled: true
23 enabled: clockItem.calendarPopup
24
25 Item {
26 anchors.fill: parent
27
28 Text {
29 id: clock
30 color: "white"
31
32 anchors.verticalCenter: parent.verticalCenter
33
34 Custom.Chrono {
35 id: chrono
36
37 onDateChanged: clock.text = format("W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}")
38 }
39
40 font.pointSize: 10
41 font.family: "Fira Sans"
42 font.features: { "tnum": 1 }
43 }
44 }
45 }
46
47 Loader {
48 id: tooltipLoader
49
50 active: false
51
52 Connections {
53 target: clockMouseArea
54 function onContainsMouseChanged() {
55 if (clockMouseArea.containsMouse)
56 tooltipLoader.active = true;
57 }
58 }
59
60 sourceComponent: PopupWindow {
61 id: tooltip
62
63 property bool nextVisible: clockMouseArea.containsMouse || tooltipMouseArea.containsMouse
64
65 anchor {
66 item: clockMouseArea
67 edges: Edges.Bottom | Edges.Left
68 }
69 visible: false
70
71 onNextVisibleChanged: hangTimer.restart()
72
73 Timer {
74 id: hangTimer
75 interval: 100
76 onTriggered: {
77 tooltip.visible = tooltip.nextVisible;
78 if (!tooltip.visible)
79 tooltipLoader.active = false;
80 }
81 }
82
83 implicitWidth: tooltipLayout.childrenRect.width + 16
84 implicitHeight: tooltipLayout.childrenRect.height + 16
85 color: "black"
86
87 onVisibleChanged: {
88 yearCalendar.year = chrono.date.getFullYear();
89 yearCalendar.angleRem = 0;
90 }
91
92 WrapperMouseArea {
93 id: tooltipMouseArea
94
95 hoverEnabled: true
96 enabled: true
97
98 onWheel: event => yearCalendar.scrollYear(event)
99
100 anchors.fill: parent
101
102 Item {
103 id: clockTooltipContent
104
105 anchors.fill: parent
106
107 ColumnLayout {
108 id: tooltipLayout
109
110 anchors {
111 left: parent.left
112 top: parent.top
113 leftMargin: 8
114 topMargin: 8
115 }
116
117 Text {
118 id: yearLabel
119
120 horizontalAlignment: Text.AlignHCenter
121
122 font.pointSize: 14
123 font.family: "Fira Sans"
124 font.features: { "tnum": 1 }
125 color: "white"
126
127 text: yearCalendar.year
128
129 Layout.fillWidth: true
130 Layout.bottomMargin: 8
131 }
132
133 GridLayout {
134 property int year: chrono.date.getFullYear()
135
136 id: yearCalendar
137
138 columns: 3
139 columnSpacing: 16
140 rowSpacing: 16
141
142 Layout.alignment: Qt.AlignHCenter
143 Layout.fillWidth: false
144
145 property real angleRem: 0
146 property real sensitivity: 1 / 120
147
148 function scrollYear(event) {
149 angleRem += event.angleDelta.y;
150 const d = Math.round(angleRem * sensitivity);
151 yearCalendar.year += d;
152 angleRem -= d / sensitivity;
153 }
154
155 Connections {
156 target: clockMouseArea
157 function onWheel(event) { yearCalendar.scrollYear(event); }
158 }
159
160 Repeater {
161 model: 12
162
163 GridLayout {
164 columns: 2
165
166 required property int index
167 property int month: index
168
169 id: monthCalendar
170
171 Layout.alignment: Qt.AlignTop | Qt.AlignRight
172 Layout.fillWidth: false
173
174 Text {
175 Layout.column: 1
176 Layout.fillWidth: true
177
178 horizontalAlignment: Text.AlignHCenter
179
180 font.pointSize: 10
181 font.family: "Fira Sans"
182
183 text: new Date(yearCalendar.year, monthCalendar.month, 1).toLocaleString(Qt.locale("en_DK"), "MMMM")
184
185 color: "#ffead3"
186 }
187
188 DayOfWeekRow {
189 locale: grid.locale
190
191 Layout.row: 1
192 Layout.column: 1
193 Layout.fillWidth: true
194
195 delegate: WrapperItem {
196 required property string shortName
197
198 width: dowLabel.contentWidth + 6
199
200 Text {
201 id: dowLabel
202
203 anchors.fill: parent
204
205 font.pointSize: 10
206 font.family: "Fira Sans"
207
208 text: parent.shortName
209 color: "#ffcc66"
210
211 horizontalAlignment: Text.AlignHCenter
212 verticalAlignment: Text.AlignVCenter
213 }
214 }
215 }
216
217 WeekNumberColumn {
218 month: grid.month
219 year: grid.year
220 locale: grid.locale
221
222 Layout.fillHeight: true
223
224 delegate: Text {
225 required property int weekNumber
226
227 opacity: {
228 const simple = new Date(weekNumber == 1 && monthCalendar.month == 12 ? yearCalendar.year + 1 : yearCalendar.year, 0, 1 + (weekNumber - 1) * 7);
229 const dayOfWeek = simple.getDay();
230 const isoWeekStart = simple;
231
232 isoWeekStart.setDate(simple.getDate() - dayOfWeek + 1);
233 if (dayOfWeek > 4) {
234 isoWeekStart.setDate(isoWeekStart.getDate() + 7);
235 }
236
237 for (let i = 0; i < 7; i++) {
238 const dayInWeek = new Date(isoWeekStart);
239 dayInWeek.setDate(dayInWeek.getDate() + i);
240 if (dayInWeek.getMonth() == monthCalendar.month)
241 return 1;
242 }
243
244 return 0;
245 }
246
247 font.pointSize: 10
248 font.family: "Fira Sans"
249 font.features: { "tnum": 1 }
250
251 text: weekNumber
252 color: "#99ffdd"
253
254 horizontalAlignment: Text.AlignRight
255 verticalAlignment: Text.AlignVCenter
256 }
257 }
258
259 MonthGrid {
260 id: grid
261
262 year: yearCalendar.year
263 month: monthCalendar.month
264 locale: Qt.locale("en_DK")
265
266 Layout.fillWidth: true
267 Layout.fillHeight: true
268
269 delegate: Text {
270 required property var model
271
272 opacity: model.month === monthCalendar.month ? 1 : 0
273
274 font.pointSize: 10
275 font.family: "Fira Sans"
276 font.features: { "tnum": 1 }
277
278 property bool today: chrono.date.getFullYear() == model.year && chrono.date.getMonth() == model.month && chrono.date.getDate() == model.day
279
280 text: model.day
281 color: today ? "#ff6699" : "white"
282
283 horizontalAlignment: Text.AlignRight
284 verticalAlignment: Text.AlignVCenter
285 }
286 }
287 }
288 }
289 }
290 }
291 }
292 }
293 }
294 }
295}
diff --git a/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
new file mode 100644
index 00000000..46302e54
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
@@ -0,0 +1,112 @@
1import Quickshell
2import QtQuick
3import qs.Services
4import Quickshell.Widgets
5
6Item {
7 width: kbdLabel.contentWidth + 8
8 height: parent.height
9 anchors.verticalCenter: parent.verticalCenter
10
11 WrapperMouseArea {
12 id: kbdMouseArea
13
14 anchors.fill: parent
15
16 hoverEnabled: true
17 cursorShape: Qt.PointingHandCursor
18 enabled: true
19 onClicked: {
20 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {})
21 }
22 onWheel: event => {
23 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": event.angleDelta > 0 ? "Next" : "Prev" } } }, _ => {})
24 }
25
26 Rectangle {
27 id: kbdWidget
28
29 property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" }
30
31 anchors.fill: parent
32 color: {
33 if (kbdMouseArea.containsMouse) {
34 return "#33808080";
35 }
36 return "transparent";
37 }
38
39 Text {
40 id: kbdLabel
41
42 font.pointSize: 10
43 font.family: "Fira Sans"
44 color: {
45 if (NiriService.keyboardLayouts?.current_idx === 0)
46 return "#555";
47 return "white";
48 }
49 anchors.centerIn: parent
50
51 text: {
52 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
53 if (!currentLayout)
54 return "";
55 return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout;
56 }
57 }
58 }
59 }
60
61 PopupWindow {
62 id: tooltip
63
64 property bool nextVisible: kbdMouseArea.containsMouse || tooltipMouseArea.containsMouse
65
66 anchor {
67 item: kbdMouseArea
68 edges: Edges.Bottom | Edges.Left
69 }
70 visible: false
71
72 onNextVisibleChanged: hangTimer.restart()
73
74 Timer {
75 id: hangTimer
76 interval: 100
77 onTriggered: tooltip.visible = tooltip.nextVisible
78 }
79
80 implicitWidth: kbdTooltipText.contentWidth + 16
81 implicitHeight: kbdTooltipText.contentHeight + 16
82 color: "black"
83
84 WrapperMouseArea {
85 id: tooltipMouseArea
86
87 hoverEnabled: true
88 enabled: true
89
90 anchors.fill: parent
91
92 Item {
93 anchors.fill: parent
94
95 Text {
96 id: kbdTooltipText
97
98 anchors.centerIn: parent
99
100 font.pointSize: 10
101 font.family: "Fira Sans"
102 color: "white"
103
104 text: {
105 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
106 return currentLayout || "";
107 }
108 }
109 }
110 }
111 }
112}
diff --git a/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml
new file mode 100644
index 00000000..8410dcda
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml
@@ -0,0 +1,47 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import qs.Services
5
6Item {
7 id: root
8
9 width: icon.width + 8
10 height: parent.height
11 anchors.verticalCenter: parent.verticalCenter
12
13 WrapperMouseArea {
14 id: widgetMouseArea
15
16 anchors.fill: parent
17
18 hoverEnabled: true
19 cursorShape: Qt.PointingHandCursor
20
21 onClicked: InhibitorState.lidSwitchInhibited = !InhibitorState.lidSwitchInhibited
22
23 Rectangle {
24 anchors.fill: parent
25 color: {
26 if (widgetMouseArea.containsMouse) {
27 return "#33808080";
28 }
29 return "transparent";
30 }
31
32 Item {
33 anchors.fill: parent
34
35 MaterialDesignIcon {
36 id: icon
37
38 implicitSize: 14
39 anchors.centerIn: parent
40
41 icon: InhibitorState.lidSwitchInhibited ? "laptop-off" : "laptop"
42 color: InhibitorState.lidSwitchInhibited ? "#f28a21" : "#555"
43 }
44 }
45 }
46 }
47}
diff --git a/accounts/gkleen@sif/shell/quickshell/LockSurface.qml b/accounts/gkleen@sif/shell/quickshell/LockSurface.qml
new file mode 100644
index 00000000..f4f8f0cd
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/LockSurface.qml
@@ -0,0 +1,227 @@
1import Quickshell.Widgets
2import QtQuick.Effects
3import QtQuick.Layouts
4import QtQuick
5import QtQuick.Controls
6import QtQuick.Controls.Fusion
7import qs.Services
8import QtQml
9
10Item {
11 id: lockSurface
12
13 property var screen
14 property list<var> messages: []
15 property bool responseRequired: false
16 property bool responseVisible: false
17 property string currentText: ""
18 property bool authRunning: false
19
20 signal response(string responseText)
21
22 anchors.fill: parent
23
24 Item {
25 id: background
26
27 anchors.fill: parent
28
29 property Img current: one
30 property string source: selector.selected
31
32 WallpaperSelector {
33 id: selector
34 seed: lockSurface.screen?.name || ""
35 }
36
37 onSourceChanged: {
38 if (!source)
39 current = null;
40 else if (current === one)
41 two.update()
42 else
43 one.update()
44 }
45
46 Img { id: one }
47 Img { id: two }
48
49 component Img: Item {
50 id: img
51
52 property string source
53
54 function update() {
55 source = background.source || ""
56 }
57
58 anchors.fill: parent
59
60 Image {
61 id: imageSource
62
63 source: img.source
64 sourceSize: Qt.size(parent.width, parent.height)
65 fillMode: Image.PreserveAspectCrop
66 smooth: true
67 visible: false
68 asynchronous: true
69 cache: false
70
71 onStatusChanged: {
72 if (status === Image.Ready) {
73 background.current = img
74 }
75 }
76 }
77
78 MultiEffect {
79 id: imageEffect
80
81 source: imageSource
82 anchors.fill: parent
83 blurEnabled: true
84 blur: 1
85 blurMax: 64
86 blurMultiplier: 2
87
88 opacity: 0
89
90 states: State {
91 name: "visible"
92 when: background.current === img
93
94 PropertyChanges {
95 imageEffect.opacity: 1
96 }
97 StateChangeScript {
98 name: "unloadOther"
99 script: {
100 if (img === one)
101 two.source = ""
102 if (img === two)
103 one.source = ""
104 }
105 }
106 }
107
108 transitions: Transition {
109 SequentialAnimation {
110 NumberAnimation {
111 target: imageEffect
112 properties: "opacity"
113 duration: {
114 if (img === one && two.source == "" || img === two && one.source == "")
115 return 0;
116 return 5000;
117 }
118 easing.type: Easing.OutCubic
119 }
120 ScriptAction {
121 scriptName: "unloadOther"
122 }
123 }
124 }
125 }
126 }
127 }
128
129 Item {
130 anchors {
131 top: lockSurface.top
132 left: lockSurface.left
133 right: lockSurface.right
134 }
135
136 implicitWidth: lockSurface.width
137 implicitHeight: 21
138
139 Rectangle {
140 anchors.fill: parent
141 color: Qt.rgba(0, 0, 0, 0.75)
142 }
143
144 Clock {
145 anchors.centerIn: parent
146 calendarPopup: false
147 }
148 }
149
150 WrapperRectangle {
151 id: unlockUi
152
153 Keys.onPressed: event => {
154 if (!lockSurface.authRunning) {
155 event.accepted = true;
156 lockSurface.authRunning = true;
157 }
158 }
159 focus: !passwordBox.visible
160
161 visible: lockSurface.authRunning
162
163 color: Qt.rgba(0, 0, 0, 0.75)
164 margin: 8
165
166 anchors.centerIn: parent
167
168 ColumnLayout {
169 spacing: 4
170
171 BusyIndicator {
172 visible: running
173 running: !Array.from(lockSurface.messages).length && !lockSurface.responseRequired
174 }
175
176 Repeater {
177 model: lockSurface.messages
178
179 Text {
180 required property var modelData
181
182 font.pointSize: 10
183 font.family: "Fira Sans"
184 color: modelData.error ? "#f28a21" : "#ffffff"
185
186 text: String(modelData.text).trim()
187
188 Layout.fillWidth: true
189 horizontalAlignment: Text.AlignHCenter
190 }
191 }
192
193 TextField {
194 id: passwordBox
195
196 visible: lockSurface.responseRequired
197 echoMode: lockSurface.responseVisible ? TextInput.Normal : TextInput.Password
198 inputMethodHints: Qt.ImhSensitiveData
199
200 onTextChanged: lockSurface.currentText = passwordBox.text
201 onAccepted: {
202 passwordBox.readOnly = true;
203 lockSurface.response(lockSurface.currentText);
204 }
205
206 Connections {
207 target: lockSurface
208 function onCurrentTextChanged() {
209 passwordBox.text = lockSurface.currentText
210 }
211 }
212 Connections {
213 target: lockSurface
214 function onResponseRequiredChanged() {
215 if (lockSurface.responseRequired)
216 passwordBox.readOnly = false;
217 passwordBox.focus = true;
218 passwordBox.selectAll();
219 }
220 }
221
222 Layout.topMargin: 4
223 Layout.fillWidth: true
224 }
225 }
226 }
227}
diff --git a/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
new file mode 100644
index 00000000..996fd41b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
@@ -0,0 +1,132 @@
1import Quickshell
2import Quickshell.Wayland
3import Quickshell.Io
4import Quickshell.Services.Pam
5import Quickshell.Services.Mpris
6import Custom as Custom
7import qs.Services
8import QtQml
9
10Scope {
11 id: lockscreen
12
13 property string currentText: ""
14
15 PamContext {
16 id: pam
17
18 property list<var> messages: []
19
20 config: "quickshell"
21 onCompleted: result => {
22 if (result === PamResult.Success) {
23 lock.locked = false;
24 }
25 }
26 onPamMessage: {
27 messages = Array.from(messages).concat([{ "text": pam.message, "error": pam.messageIsError }])
28 }
29 onActiveChanged: {
30 messages = [];
31 }
32 }
33
34 IpcHandler {
35 target: "Lockscreen"
36
37 function setLocked(locked: bool): void { lock.locked = locked; }
38 function getLocked(): bool { return lock.locked; }
39 }
40
41 Connections {
42 target: Custom.Systemd
43 function onSleep(before: bool) {
44 console.log(`received prepare for sleep ${before}`);
45 if (before)
46 lock.locked = true;
47 }
48 function onLock() { lock.locked = true; }
49 function onUnlock() { lock.locked = false; }
50 }
51
52 IdleMonitor {
53 id: idleMonitor
54 enabled: !lock.secure
55 timeout: 600
56 respectInhibitors: true
57
58 onIsIdleChanged: {
59 if (idleMonitor.isIdle)
60 lock.locked = true;
61 }
62 }
63
64 Custom.SystemdInhibitor {
65 enabled: !lock.secure
66
67 what: Custom.SystemdInhibitorParams.Sleep
68 who: "quickshell"
69 why: "Lock session"
70 mode: Custom.SystemdInhibitorParams.Delay
71 }
72
73 Binding {
74 target: NotificationManager
75 property: "lockscreenActive"
76 value: lock.locked
77 }
78
79 WlSessionLock {
80 id: lock
81
82 onLockStateChanged: {
83 if (!locked && pam.active)
84 pam.abort();
85
86 if (locked) {
87 NiriService.sendCommand({ "Action": { "PowerOffMonitors": {} } }, _ => {});
88 Custom.KeePassXC.lockAllDatabases();
89 Array.from(MprisProxy.players).forEach(player => {
90 if (player.canPause && player.isPlaying)
91 player.pause();
92 });
93 // Custom.Systemd.stopUserUnit("gpg-agent.service", "replace");
94 GpgAgent.reloadAgent();
95 }
96 }
97 Component.onCompleted: { (_ => {})(MprisProxy.players); }
98
99 onSecureStateChanged: Custom.Systemd.lockedHint = lock.secure
100
101 WlSessionLockSurface {
102 id: lockSurface
103
104 color: "black"
105
106 LockSurface {
107 id: surfaceContent
108
109 onResponse: responseText => pam.respond(responseText)
110 onAuthRunningChanged: {
111 if (authRunning)
112 pam.start();
113 }
114 Connections {
115 target: pam
116 function onMessagesChanged() { surfaceContent.messages = pam.messages; }
117 function onResponseRequiredChanged() { surfaceContent.responseRequired = pam.responseRequired; }
118 function onActiveChanged() { surfaceContent.authRunning = pam.active; }
119 }
120 onCurrentTextChanged: lockscreen.currentText = currentText
121 Connections {
122 target: lockscreen
123 function onCurrentTextChanged() { surfaceContent.currentText = lockscreen.currentText; }
124 }
125 Connections {
126 target: lockSurface
127 function onScreenChanged() { surfaceContent.screen = lockSurface.screen; }
128 }
129 }
130 }
131 }
132}
diff --git a/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
new file mode 100644
index 00000000..155a009e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
@@ -0,0 +1,35 @@
1import QtQuick
2import QtQuick.Effects
3
4Item {
5 id: root
6
7 required property string icon
8 property color color: "white"
9
10 property real implicitSize: 0
11
12 readonly property real actualSize: Math.min(root.width, root.height)
13
14 implicitWidth: root.implicitSize
15 implicitHeight: root.implicitSize
16
17 Image {
18 id: sourceImage
19 source: "file://" + @mdi@ + "/svg/" + root.icon + ".svg"
20 anchors.fill: parent
21 fillMode: Image.PreserveAspectFit
22
23 sourceSize.width: root.actualSize
24 sourceSize.height: root.actualSize
25
26 layer.enabled: true
27 layer.effect: MultiEffect {
28 id: effect
29
30 brightness: 1
31 colorization: 1
32 colorizationColor: root.color
33 }
34 }
35}
diff --git a/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml b/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml
new file mode 100644
index 00000000..beff205c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml
@@ -0,0 +1,30 @@
1import QtQml
2import Quickshell
3import Quickshell.Wayland
4import qs.Services
5import Custom as Custom
6
7Scope {
8 IdleMonitor {
9 id: idleMonitor30
10 timeout: 30
11
12 onIsIdleChanged: Custom.Systemd.setIdleHint(idleMonitor30.isIdle)
13 }
14 IdleMonitor {
15 id: idleMonitor540
16 timeout: 540
17
18 onIsIdleChanged: {
19 if (idleMonitor540.isIdle)
20 NiriService.sendCommand({ "Action": { "PowerOffMonitors": {} } }, _ => {});
21 }
22 }
23 Connections {
24 target: Custom.Systemd
25 function onSleep(before: bool) {
26 if (!before)
27 NiriService.sendCommand({ "Action": { "PowerOnMonitors": {} } }, _ => {});
28 }
29 }
30}
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
new file mode 100644
index 00000000..cc0e49b1
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
@@ -0,0 +1,340 @@
1import QtQml
2import QtQml.Models
3import QtQuick
4import Quickshell
5import Quickshell.Widgets
6import Quickshell.Wayland
7import qs.Services
8import QtQuick.Layouts
9import Quickshell.Services.Notifications
10
11Scope {
12 id: root
13
14 property var activeScreen: Array.from(Quickshell.screens).find(screen => screen.name === Array.from(NiriService.workspaces).find(ws => ws.is_focused)?.output) ?? null
15
16 Instantiator {
17 id: notifsRepeater
18
19 model: ScriptModel {
20 values: NotificationManager.groups
21 }
22
23 delegate: PanelWindow {
24 id: notifWindow
25
26 visible: NotificationManager.active
27
28 screen: root.activeScreen
29
30 WlrLayershell.namespace: "notifications"
31
32 required property var modelData
33 required property var index
34
35 property int activeIx: modelData.length - 1
36 onModelDataChanged: {
37 notifWindow.activeIx = modelData.length - 1;
38 }
39
40 property color textColor: {
41 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Low)
42 return "#ff999999";
43 return "white";
44 }
45 property color backgroundColor: {
46 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Critical)
47 return "#dd900000";
48 return "black";
49 }
50
51 anchors {
52 right: true
53 top: true
54 }
55
56 readonly property real spaceAbove: {
57 var res = 0;
58 for (let i = 0; i < notifWindow.index; i++) {
59 (_ => {})(notifsRepeater.objectAt(i).modelData);
60 res += notifsRepeater.objectAt(i).height + 8;
61 }
62 return res;
63 }
64
65 margins {
66 right: 26 + 8
67 top: 8 + spaceAbove
68 }
69
70 color: "transparent"
71
72 implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + (notifTime.visible ? 8 + notifTime.contentHeight : 0) + 16
73 implicitWidth: 400
74
75 WrapperMouseArea {
76 enabled: true
77
78 anchors.fill: parent
79
80 cursorShape: Qt.PointingHandCursor
81
82 onClicked: {
83 for (const notif of notifWindow.modelData)
84 notif.dismiss();
85 }
86
87 property real angleRem: 0
88 property real sensitivity: 1 / 120
89 onWheel: event => {
90 angleRem += event.angleDelta.y;
91 const d = Math.round(angleRem * sensitivity);
92 angleRem -= d / sensitivity;
93 notifWindow.activeIx = ((notifWindow.modelData?.length ?? 1) + notifWindow.activeIx - d) % (notifWindow.modelData?.length ?? 1);
94 }
95
96 Rectangle {
97 color: notifWindow.backgroundColor
98 anchors.fill: parent
99 border {
100 color: Qt.hsla(195/360, 1, 0.45, 1)
101 width: 2
102 }
103
104 GridLayout {
105 id: notifLayout
106
107 width: 400 - 16
108 anchors.fill: parent
109 anchors.margins: 8
110 columnSpacing: 8
111 rowSpacing: 8
112
113 columns: notifImage.visible ? 3 : 2
114 rows: {
115 var res = 1;
116 if (notifBody.visible)
117 res += 1;
118 if (notifActions.visible)
119 res += 1;
120 if (notifTime.visible)
121 res += 1;
122 return res;
123 }
124
125 Text {
126 id: notifCount
127
128 visible: notifWindow.modelData?.length > 1 ?? false
129 text: `${notifWindow.activeIx + 1}/${notifWindow.modelData?.length ?? ""}`
130
131 font.pointSize: 10
132 font.family: "Fira Sans"
133 font.bold: true
134 font.features: { "tnum": 1 }
135 color: notifWindow.textColor
136 maximumLineCount: 1
137
138 Layout.fillWidth: false
139 Layout.row: 0
140 Layout.column: notifImage.visible ? 1 : 0
141 }
142
143 Text {
144 id: notifSummary
145
146 text: notifWindow.modelData?.[notifWindow.activeIx]?.summary ?? ""
147
148 font.pointSize: 10
149 font.family: "Fira Sans"
150 font.italic: true
151 color: notifWindow.textColor
152 maximumLineCount: 1
153 elide: Text.ElideRight
154
155 Layout.fillWidth: true
156 Layout.row: 0
157 Layout.column: (notifCount.visible ? 1 : 0) + (notifImage.visible ? 1 : 0)
158 Layout.columnSpan: notifCount.visible ? 1 : 2
159 }
160
161 Image {
162 id: notifImage
163
164 visible: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? false
165
166 onStatusChanged: {
167 if (notifImage.status == Image.Error)
168 notifImage.visible = false;
169 }
170
171 source: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? ""
172 fillMode: Image.PreserveAspectFit
173 asynchronous: true
174 smooth: true
175 mipmap: true
176
177 Layout.maximumWidth: 50
178 Layout.column: 0
179 Layout.row: 0
180 Layout.fillHeight: true
181 Layout.rowSpan: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0)
182 }
183
184 Text {
185 id: notifBody
186
187 visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false
188 text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? ""
189 textFormat: Text.RichText
190 wrapMode: Text.Wrap
191
192 font.pointSize: 10
193 font.family: "Fira Sans"
194 color: notifWindow.textColor
195
196 Layout.fillWidth: true
197 Layout.row: 1
198 Layout.column: notifImage.visible ? 1 : 0
199 Layout.columnSpan: notifCount.visible ? 2 : 1
200 }
201
202 Text {
203 id: notifTime
204
205 Connections {
206 target: NotificationManager.clock
207 function onDateChanged() {
208 notifTime.text = NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime);
209 }
210 }
211
212 visible: notifTime.text && notifTime.text !== "now"
213 text: NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime)
214
215 font.pointSize: 8
216 font.family: "Fira Sans"
217 font.italic: true
218 color: "#555"
219 maximumLineCount: 1
220 horizontalAlignment: Text.AlignRight
221
222 Layout.fillWidth: true
223 Layout.row: notifBody.visible ? 2 : 1
224 Layout.column: notifImage.visible ? 1 : 0
225 Layout.columnSpan: 2
226 }
227
228 RowLayout {
229 id: notifActions
230
231 visible: notifWindow.modelData?.[notifWindow.activeIx]?.actions.length > 0 ?? false
232
233 spacing: 8
234 uniformCellSizes: true
235
236 width: 400 - 16
237 Layout.row: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0)
238 Layout.column: 0
239 Layout.columnSpan: 2 + (notifImage.visible ? 1 : 0)
240
241 Repeater {
242 model: ScriptModel {
243 values: notifWindow.modelData?.[notifWindow.activeIx]?.actions
244 }
245
246 delegate: WrapperMouseArea {
247 id: actionMouseArea
248
249 required property var modelData
250
251 height: actionLabelWrapper.implicitHeight
252 Layout.fillWidth: true
253 Layout.horizontalStretchFactor: 1
254
255 hoverEnabled: true
256 cursorShape: Qt.PointingHandCursor
257
258 onClicked: actionMouseArea.modelData?.invoke()
259
260 Rectangle {
261 anchors.fill: parent
262
263 color: actionMouseArea.containsMouse ? "#20ffffff" : "transparent"
264
265 border {
266 width: 2
267 color: "#20ffffff"
268 }
269
270 WrapperItem {
271 id: actionLabelWrapper
272
273 margin: 8
274 anchors.centerIn: parent
275
276 RowLayout {
277 id: actionLabelLayout
278
279 spacing: 8
280
281 IconImage {
282 id: actionIcon
283
284 visible: notifWindow.modelData?.[notifWindow.activeIx]?.hasActionIcons
285
286 onStatusChanged: {
287 if (actionIcon.status == Image.Error)
288 actionIcon.visible = false;
289 }
290
291 implicitSize: 16
292 source: {
293 if (!actionIcon.visible)
294 return "";
295
296 let icon = actionMouseArea.modelData?.identifier ?? ""
297 if (icon.includes("?path=")) {
298 const split = icon.split("?path=")
299 if (split.length !== 2)
300 return icon
301 const name = split[0]
302 const path = split[1]
303 const fileName = name.substring(
304 name.lastIndexOf("/") + 1)
305 return `file://${path}/${fileName}`
306 }
307 return icon
308 }
309 asynchronous: true
310 smooth: true
311 mipmap: true
312
313 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
314 }
315
316 Text {
317 id: actionLabel
318
319 visible: actionMouseArea.modelData?.text ?? false
320
321 text: actionMouseArea.modelData?.text ?? ""
322
323 font.pointSize: 10
324 font.family: "Fira Sans"
325 color: notifWindow.textColor
326 maximumLineCount: 1
327 elide: Text.ElideRight
328 }
329 }
330 }
331 }
332 }
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340}
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml
new file mode 100644
index 00000000..b58467b3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml
@@ -0,0 +1,266 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import qs.Services
5import QtQuick.Controls
6import QtQuick.Layouts
7import QtQuick.Shapes
8
9Item {
10 id: root
11
12 width: icon.width + 8
13 height: parent.height
14 anchors.verticalCenter: parent.verticalCenter
15
16 WrapperMouseArea {
17 id: widgetMouseArea
18
19 anchors.fill: parent
20
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: NotificationManager.displayInhibited = !NotificationManager.displayInhibited
25
26 Rectangle {
27 anchors.fill: parent
28 color: {
29 if (widgetMouseArea.containsMouse) {
30 return "#33808080";
31 }
32 return "transparent";
33 }
34
35 Item {
36 anchors.fill: parent
37
38 MaterialDesignIcon {
39 id: icon
40
41 implicitSize: 14
42 anchors.centerIn: parent
43
44 icon: NotificationManager.active ? "message" : "message-off"
45 color: {
46 if (!NotificationManager.active && !NotificationManager.displayInhibited)
47 return "#f28a21";
48 if (NotificationManager.displayInhibited)
49 return "white";
50 return "#555";
51 }
52 }
53 }
54 }
55 }
56
57 Loader {
58 id: tooltipLoader
59
60 active: false
61
62 Connections {
63 target: widgetMouseArea
64 function onContainsMouseChanged() {
65 if (widgetMouseArea.containsMouse)
66 tooltipLoader.active = true;
67 }
68 }
69
70 sourceComponent: PopupWindow {
71 id: tooltip
72
73 property bool nextVisible: NotificationManager.active && (widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse)
74
75 anchor {
76 item: widgetMouseArea
77 edges: Edges.Bottom | Edges.Left
78 }
79 visible: false
80
81 onNextVisibleChanged: hangTimer.restart()
82
83 Timer {
84 id: hangTimer
85 interval: tooltip.visible ? 100 : 500
86 onTriggered: {
87 tooltip.visible = tooltip.nextVisible;
88 if (!tooltip.visible)
89 tooltipLoader.active = false;
90 }
91 }
92
93 implicitWidth: 400
94 implicitHeight: Math.min(tooltip.screen.height * 0.66, Math.max(100, scroll.contentHeight + 16))
95 color: "black"
96
97 WrapperMouseArea {
98 id: tooltipMouseArea
99
100 hoverEnabled: true
101 enabled: true
102
103 anchors.fill: parent
104
105 WrapperItem {
106 margin: 8
107
108 ScrollView {
109 id: scroll
110
111 contentWidth: availableWidth
112 // ScrollBar.vertical.policy: ScrollBar.AlwaysOn
113
114 ColumnLayout {
115 id: historyLayout
116 anchors {
117 left: parent.left
118 right: parent.right
119 }
120
121 spacing: 8
122
123 Repeater {
124 model: ScriptModel {
125 values: [...NotificationManager.history].reverse().map(o => o.notification)
126 }
127
128 delegate: GridLayout {
129 id: notif
130
131 Layout.fillWidth: true
132 Layout.preferredHeight: notifSummary.contentHeight + (notifBody.visible ? notifBody.contentHeight + 8 : 0) + (notifSep.visible ? notifSep.height + 8 : 0) + notifTime.contentHeight + 8
133
134 required property var modelData
135 required property int index
136
137 columnSpacing: 8
138 rowSpacing: 8
139
140 columns: notifImage.visible ? 2 : 1
141 rows: {
142 var res = 2;
143 if (notifBody.visible)
144 res += 1;
145 if (notifSep.visible)
146 res += 1;
147 return res;
148 }
149
150 Shape {
151 id: notifSep
152
153 visible: notif.index != 0
154
155 height: 2
156 width: 400 - 32
157
158 ShapePath {
159 strokeWidth: 2
160 strokeColor: "#20ffffff"
161 startX: 0; startY: 0;
162 PathLine { x: 400 - 32; y: 0; }
163 }
164
165 Layout.row: 0
166 Layout.column: 0
167 Layout.columnSpan: notifImage.visible ? 2 : 1
168 Layout.alignment: Qt.AlignHCenter
169 }
170
171 Text {
172 id: notifSummary
173
174 text: notif.modelData?.summary ?? ""
175
176 font.pointSize: 10
177 font.family: "Fira Sans"
178 font.italic: true
179 color: "white"
180 maximumLineCount: 1
181 elide: Text.ElideRight
182
183 Layout.fillWidth: true
184 Layout.row: notifSep.visible ? 1 : 0
185 Layout.column: notifImage.visible ? 1 : 0
186 }
187
188 Image {
189 id: notifImage
190
191 visible: (notif.modelData?.image || notif.modelData?.appIcon) ?? false
192
193 onStatusChanged: {
194 if (notifImage.status == Image.Error)
195 notifImage.visible = false;
196 }
197
198 source: (notif.modelData?.image || notif.modelData?.appIcon) ?? ""
199 fillMode: Image.PreserveAspectFit
200 asynchronous: true
201 smooth: true
202 mipmap: true
203
204 Layout.maximumWidth: 50
205 Layout.column: 0
206 Layout.row: notifSep.visible ? 1 : 0
207 Layout.fillHeight: true
208 Layout.rowSpan: notifBody.visible ? 3 : 2
209 }
210
211 Text {
212 id: notifBody
213
214 visible: notif.modelData?.body ?? false
215 text: notif.modelData?.body ?? ""
216 textFormat: Text.RichText
217 wrapMode: Text.Wrap
218
219 font.pointSize: 10
220 font.family: "Fira Sans"
221 color: "white"
222
223 Layout.fillWidth: true
224 Layout.row: notifSep.visible ? 2 : 1
225 Layout.column: notifImage.visible ? 1 : 0
226 }
227
228 Text {
229 id: notifTime
230
231 Connections {
232 target: NotificationManager.clock
233 function onDateChanged() {
234 notifTime.text = NotificationManager.formatTime(notif.modelData?.receivedTime);
235 }
236 }
237
238 text: NotificationManager.formatTime(notif.modelData?.receivedTime)
239
240 font.pointSize: 8
241 font.family: "Fira Sans"
242 font.italic: true
243 color: "#555"
244 maximumLineCount: 1
245 horizontalAlignment: Text.AlignRight
246
247 Layout.fillWidth: true
248 Layout.row: {
249 var res = 1;
250 if (notifSep.visible)
251 res += 1;
252 if (notifBody.visible)
253 res += 1;
254 return res;
255 }
256 Layout.column: notifImage.visible ? 1 : 0
257 }
258 }
259 }
260 }
261 }
262 }
263 }
264 }
265 }
266}
diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
new file mode 100644
index 00000000..9c6b65a4
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
@@ -0,0 +1,483 @@
1import QtQuick
2import QtQuick.Layouts
3import QtQuick.Controls.Fusion
4import Quickshell
5import Quickshell.Services.Pipewire
6import Quickshell.Widgets
7
8Item {
9 height: parent.height
10 width: volumeIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 PwObjectTracker {
14 objects: [Pipewire.defaultAudioSink]
15 }
16
17 WrapperMouseArea {
18 id: widgetMouseArea
19
20 anchors.fill: parent
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: {
25 if (!Pipewire.defaultAudioSink)
26 return;
27 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
28 }
29
30 property real sensitivity: (1 / 40) / 120
31 onWheel: event => {
32 if (!Pipewire.defaultAudioSink)
33 return;
34 Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity;
35 }
36
37 Rectangle {
38 id: volumeWidget
39
40 anchors.fill: parent
41 color: {
42 if (widgetMouseArea.containsMouse)
43 return "#33808080";
44 return "transparent";
45 }
46
47 Item {
48 anchors.fill: parent
49
50 MaterialDesignIcon {
51 id: volumeIcon
52
53 implicitSize: 14
54 anchors.centerIn: parent
55
56 icon: {
57 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
58 return "volume-off";
59 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
60 return "volume-low";
61 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
62 return "volume-medium";
63 return "volume-high";
64 }
65 color: "#555"
66 }
67 }
68 }
69 }
70
71 Loader {
72 id: tooltipLoader
73
74 active: false
75
76 Connections {
77 target: widgetMouseArea
78 function onContainsMouseChanged() {
79 if (widgetMouseArea.containsMouse)
80 tooltipLoader.active = true;
81 }
82 }
83
84 PwObjectTracker {
85 objects: Pipewire.devices
86 }
87 PwObjectTracker {
88 objects: Pipewire.nodes
89 }
90
91 sourceComponent: PopupWindow {
92 id: tooltip
93
94 property bool openPopup: false
95 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup
96
97 anchor {
98 item: widgetMouseArea
99 edges: Edges.Bottom | Edges.Left
100 }
101 visible: false
102
103 onNextVisibleChanged: hangTimer.restart()
104
105 Timer {
106 id: hangTimer
107 interval: 100
108 onTriggered: {
109 tooltip.visible = tooltip.nextVisible;
110 if (!tooltip.visible)
111 tooltipLoader.active = false;
112 }
113 }
114
115 implicitWidth: tooltipContent.width
116 implicitHeight: tooltipContent.height
117 color: "transparent"
118
119 Rectangle {
120 width: tooltip.width
121 height: tooltipLayout.childrenRect.height + 16
122 color: "black"
123 }
124
125 WrapperItem {
126 id: tooltipContent
127
128 bottomMargin: Math.max(0, 200 - tooltipLayout.implicitHeight)
129
130 WrapperMouseArea {
131 id: tooltipMouseArea
132
133 hoverEnabled: true
134 enabled: true
135
136 WrapperItem {
137 margin: 8
138 bottomMargin: 8
139
140 GridLayout {
141 id: tooltipLayout
142
143 columns: 4
144
145 Repeater {
146 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
147
148 Item {
149 id: descItem
150
151 required property var modelData
152 required property int index
153
154 Layout.column: 0
155 Layout.row: index
156
157 implicitWidth: descText.contentWidth
158 implicitHeight: descText.contentHeight
159
160 Text {
161 id: descText
162
163 color: "white"
164 font.pointSize: 10
165 font.family: "Fira Sans"
166
167 text: descItem.modelData.description
168 }
169 }
170 }
171
172 Repeater {
173 id: defaultSinkRepeater
174
175 model: {
176 Array.from(Pipewire.devices.values)
177 .filter(dev => dev.type == "Audio/Device")
178 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id ));
179 }
180
181 Item {
182 id: defaultSinkItem
183
184 required property var modelData
185 required property int index
186
187 visible: Boolean(modelData)
188
189 PwObjectTracker {
190 objects: [defaultSinkItem.modelData]
191 }
192
193 Layout.column: 1
194 Layout.row: index
195
196 Layout.fillHeight: true
197
198 implicitWidth: 16 + 8
199
200 WrapperMouseArea {
201 id: defaultSinkMouseArea
202
203 anchors.fill: parent
204 hoverEnabled: true
205 cursorShape: Qt.PointingHandCursor
206
207 onClicked: {
208 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData
209 }
210
211 onWheel: event => scrollVolume(event);
212 property real sensitivity: (1 / 40) / 120
213 function scrollVolume(event) {
214 defaultSinkItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
215 }
216
217 Rectangle {
218 id: defaultSinkWidget
219
220 anchors.fill: parent
221 color: {
222 if (defaultSinkMouseArea.containsMouse)
223 return "#33808080";
224 return "transparent";
225 }
226
227 MaterialDesignIcon {
228 width: 16
229 height: 16
230 anchors.centerIn: parent
231
232 icon: {
233 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id)
234 return "speaker";
235 return "speaker-off";
236 }
237 color: icon == "speaker" ? "white" : "#555"
238 }
239 }
240 }
241
242 PopupWindow {
243 id: volumeTooltip
244
245 property bool nextVisible: defaultSinkMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
246
247 anchor {
248 item: defaultSinkMouseArea
249 edges: Edges.Bottom | Edges.Left
250 }
251 visible: false
252
253 onNextVisibleChanged: volumeHangTimer.restart()
254
255 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
256
257 Timer {
258 id: volumeHangTimer
259 interval: 100
260 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
261 }
262
263 implicitWidth: volumeTooltipText.contentWidth + 16
264 implicitHeight: volumeTooltipText.contentHeight + 16
265 color: "black"
266
267 WrapperMouseArea {
268 id: volumeTooltipMouseArea
269
270 hoverEnabled: true
271 enabled: true
272
273 onWheel: event => defaultSinkMouseArea.scrollVolume(event);
274
275 anchors.fill: parent
276
277 Item {
278 anchors.fill: parent
279
280 Text {
281 id: volumeTooltipText
282
283 anchors.centerIn: parent
284
285 font.pointSize: 10
286 font.family: "Fira Sans"
287 color: "white"
288
289 text: `${Math.round(defaultSinkItem.modelData?.audio?.volume * 100)}%`
290 }
291 }
292 }
293 }
294 }
295 }
296
297 Repeater {
298 id: defaultSourceRepeater
299
300 model: {
301 Array.from(Pipewire.devices.values)
302 .filter(dev => dev.type == "Audio/Device")
303 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id ));
304 }
305
306 Item {
307 id: defaultSourceItem
308
309 required property var modelData
310 required property int index
311
312 visible: Boolean(modelData)
313
314 PwObjectTracker {
315 objects: [defaultSourceItem.modelData]
316 }
317
318 Layout.column: 2
319 Layout.row: index
320
321 Layout.fillHeight: true
322
323 implicitWidth: 16 + 8
324
325 WrapperMouseArea {
326 id: defaultSourceMouseArea
327
328 anchors.fill: parent
329 hoverEnabled: true
330 cursorShape: Qt.PointingHandCursor
331
332 onClicked: {
333 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData
334 }
335
336 onWheel: event => scrollVolume(event);
337 property real sensitivity: (1 / 40) / 120
338 function scrollVolume(event) {
339 defaultSourceItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
340 }
341
342 Rectangle {
343 id: defaultSourceWidget
344
345 anchors.fill: parent
346 color: {
347 if (defaultSourceMouseArea.containsMouse)
348 return "#33808080";
349 return "transparent";
350 }
351
352 MaterialDesignIcon {
353 width: 16
354 height: 16
355 anchors.centerIn: parent
356
357 icon: {
358 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id)
359 return "microphone";
360 return "microphone-off";
361 }
362 color: icon == "microphone" ? "white" : "#555"
363 }
364 }
365 }
366
367 PopupWindow {
368 id: volumeTooltip
369
370 property bool nextVisible: defaultSourceMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
371
372 anchor {
373 item: defaultSourceMouseArea
374 edges: Edges.Bottom | Edges.Left
375 }
376 visible: false
377
378 onNextVisibleChanged: volumeHangTimer.restart()
379
380 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
381
382 Timer {
383 id: volumeHangTimer
384 interval: 100
385 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
386 }
387
388 implicitWidth: volumeTooltipText.contentWidth + 16
389 implicitHeight: volumeTooltipText.contentHeight + 16
390 color: "black"
391
392 WrapperMouseArea {
393 id: volumeTooltipMouseArea
394
395 hoverEnabled: true
396 enabled: true
397
398 onWheel: event => defaultSourceMouseArea.scrollVolume(event);
399
400 anchors.fill: parent
401
402 Item {
403 anchors.fill: parent
404
405 Text {
406 id: volumeTooltipText
407
408 anchors.centerIn: parent
409
410 font.pointSize: 10
411 font.family: "Fira Sans"
412 color: "white"
413
414 text: `${Math.round(defaultSourceItem.modelData?.audio?.volume * 100)}%`
415 }
416 }
417 }
418 }
419 }
420 }
421
422 Repeater {
423 id: profileRepeater
424
425 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
426
427 Item {
428 id: profileItem
429
430 required property var modelData
431 required property int index
432
433 PwObjectTracker {
434 objects: [profileItem.modelData]
435 }
436
437 Layout.column: 3
438 Layout.row: index
439
440 Layout.fillWidth: true
441
442 implicitWidth: Math.max(profileBox.implicitWidth, 300)
443 implicitHeight: profileBox.height
444
445 ComboBox {
446 id: profileBox
447
448 model: profileItem.modelData.profiles
449
450 textRole: "description"
451 valueRole: "index"
452 onActivated: profileItem.modelData.setProfile(currentValue)
453
454 anchors.fill: parent
455
456 implicitContentWidthPolicy: ComboBox.WidestText
457
458 Connections {
459 target: profileItem.modelData
460 function onCurrentProfileChanged() {
461 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
462 }
463 }
464 Component.onCompleted: {
465 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
466 }
467
468 Connections {
469 target: profileBox.popup
470 function onVisibleChanged() {
471 tooltip.openPopup = profileBox.popup.visible
472 }
473 }
474 }
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482 }
483}
diff --git a/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml
new file mode 100644
index 00000000..d7ffadfe
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml
@@ -0,0 +1,49 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Widgets
5import qs.Services
6
7Item {
8 height: parent.height
9 width: layout.childrenRect.width
10 anchors.verticalCenter: parent.verticalCenter
11
12 visible: Array.from(Privacy.activeItems).length > 0
13
14 RowLayout {
15 id: layout
16
17 anchors.fill: parent
18
19 spacing: 8
20
21 Repeater {
22 model: Privacy.activeItems
23
24 Item {
25 id: privacyItem
26
27 required property var modelData;
28
29 height: parent.height
30 width: icon.width
31
32 MaterialDesignIcon {
33 id: icon
34
35 implicitSize: 14
36 anchors.centerIn: parent
37
38 icon: {
39 if (privacyItem.modelData == Privacy.Item.Microphone)
40 return "microphone";
41 if (privacyItem.modelData == Privacy.Item.Screensharing)
42 return "monitor-share";
43 }
44 color: "#f2201f"
45 }
46 }
47 }
48 }
49}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml
new file mode 100644
index 00000000..8318df50
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml
@@ -0,0 +1,75 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Io
6import Custom as Custom
7
8Singleton {
9 id: root
10
11 property string subsystem: "backlight"
12 property string device: "intel_backlight"
13
14 property real currBrightness
15 property real exponent: 4
16
17 function calcCurrBrightness() {
18 if (!currFile.loaded || !maxFile.loaded)
19 return undefined;
20 const curr = Number(currFile.text());
21 const max = Number(maxFile.text());
22 const val = Math.pow(curr / max, 1 / root.exponent);
23 return val;
24 }
25
26 Connections {
27 target: currFile
28 function onLoaded() {
29 const b = root.calcCurrBrightness();
30 if (typeof b !== 'undefined')
31 root.currBrightness = b;
32 }
33 }
34 Connections {
35 target: maxFile
36 function onLoaded() {
37 const b = root.calcCurrBrightness();
38 if (typeof b !== 'undefined')
39 root.currBrightness = b;
40 }
41 }
42
43 onCurrBrightnessChanged: {
44 root.currBrightness = Math.max(0, Math.min(1, root.currBrightness));
45
46 const prev = root.calcCurrBrightness();
47 if (typeof prev === 'undefined' || Math.abs(root.currBrightness - prev) < 0.01)
48 return;
49
50 const max = Number(maxFile.text());
51 const actual = Number(currFile.text());
52 let curr = Math.max(0, Math.min(max, Math.pow(root.currBrightness, root.exponent) * max));
53 if (Math.round(curr) == actual && curr < actual)
54 curr = Math.max(0, actual - 1);
55 else if (Math.round(curr) == actual && curr > actual)
56 curr = Math.min(max, actual + 1);
57 // root.currBrightness = Math.pow(curr / max, 1 / root.exponent);
58 Custom.Systemd.setBrightness(root.subsystem, root.device, Math.round(curr));
59 }
60
61 FileView {
62 id: currFile
63 path: `/sys/class/${root.subsystem}/${root.device}/brightness`
64 blockAllReads: true
65 watchChanges: true
66 onFileChanged: reload()
67 }
68 FileView {
69 id: maxFile
70 path: `/sys/class/${root.subsystem}/${root.device}/max_brightness`
71 blockAllReads: true
72 watchChanges: true
73 onFileChanged: reload()
74 }
75}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
new file mode 100644
index 00000000..3de69535
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
@@ -0,0 +1,18 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5
6Singleton {
7 id: root
8
9 Socket {
10 id: agentSocket
11 connected: true
12 path: `${Quickshell.env("XDG_RUNTIME_DIR")}/gnupg/S.gpg-agent`
13 }
14
15 function reloadAgent() {
16 agentSocket.write("RELOADAGENT\n")
17 }
18}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml b/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml
new file mode 100644
index 00000000..fe48fd7f
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml
@@ -0,0 +1,22 @@
1pragma Singleton
2
3import Quickshell
4import Custom as Custom
5
6Singleton {
7 id: inhibitorState
8
9 property bool waylandIdleInhibited: false
10 property alias lidSwitchInhibited: lidSwitchInhibitor.enabled
11
12 Custom.SystemdInhibitor {
13 id: lidSwitchInhibitor
14
15 enabled: false
16
17 what: Custom.SystemdInhibitorParams.HandleLidSwitch
18 who: "quickshell"
19 why: "User request"
20 mode: Custom.SystemdInhibitorParams.BlockWeak
21 }
22}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml b/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml
new file mode 100644
index 00000000..e3ab9755
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml
@@ -0,0 +1,8 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Services.Mpris
5
6Scope {
7 property list<var> players: Mpris.players.values
8}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
new file mode 100644
index 00000000..cce614eb
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
@@ -0,0 +1,194 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5import QtQuick
6
7Singleton {
8 id: root
9
10 property var workspaces: []
11 property var outputs: {}
12 property var keyboardLayouts: {}
13 property var windows: []
14 readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
15
16 function refreshOutputs() {
17 commandSocket.sendCommand("Outputs", data => {
18 outputs = data.Ok.Outputs;
19 });
20 }
21
22 function sendCommand(command, callback) {
23 commandSocket.sendCommand(command, callback);
24 }
25
26 Socket {
27 id: eventStreamSocket
28 path: root.socketPath
29 connected: true
30
31 property bool acked: false
32
33 onConnectionStateChanged: {
34 if (connected) {
35 acked = false;
36 write('"EventStream"\n');
37 }
38 }
39
40 parser: SplitParser {
41 onRead: line => {
42 try {
43 const event = JSON.parse(line)
44
45 // console.log(JSON.stringify(event))
46
47 if (event.WorkspacesChanged) {
48 root.workspaces = event.WorkspacesChanged.workspaces
49 root.refreshOutputs();
50 } else if (event.WorkspaceActivated)
51 eventWorkspaceActivated(event.WorkspaceActivated);
52 else if (event.WorkspaceUrgencyChanged)
53 eventWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
54 else if (event.WorkspaceActiveWindowChanged)
55 eventWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged);
56 else if (event.KeyboardLayoutsChanged)
57 root.keyboardLayouts = event.KeyboardLayoutsChanged.keyboard_layouts;
58 else if (event.KeyboardLayoutSwitched)
59 root.keyboardLayouts = Object.assign({}, root.keyboardLayouts, {"current_idx": event.KeyboardLayoutSwitched.idx });
60 else if (event.WindowsChanged)
61 root.windows = event.WindowsChanged.windows
62 else if (event.WindowOpenedOrChanged)
63 eventWindowOpenedOrChanged(event.WindowOpenedOrChanged);
64 else if (event.WindowClosed)
65 eventWindowClosed(event.WindowClosed);
66 else if (event.WindowFocusChanged)
67 eventWindowFocusChanged(event.WindowFocusChanged);
68 else if (event.WindowUrgencyChanged)
69 eventWindowUrgencyChanged(event.WindowUrgencyChanged);
70 else if (event.WindowLayoutsChanged)
71 eventWindowLayoutsChanged(event.WindowLayoutsChanged);
72 else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; }
73 else if (event.OverviewOpenedOrClosed) {}
74 else if (event.ConfigLoaded) {}
75 else
76 console.log(JSON.stringify(event));
77 } catch (e) {
78 console.warn("NiriService: Failed to parse event:", line, e)
79 }
80 }
81 }
82 }
83
84 Socket {
85 id: commandSocket
86 path: root.socketPath
87 connected: true
88
89 property var awaitingAnswer: null
90 property var cmdQueue: []
91
92 parser: SplitParser {
93 onRead: line => {
94 if (commandSocket.awaitingAnswer === null)
95 return;
96
97 try {
98 const response = JSON.parse(line);
99 commandSocket.awaitingAnswer.callback(response);
100 commandSocket.awaitingAnswer = null;
101 } catch (e) {
102 console.warn("NiriService: Failed to parse response:", line, e)
103 }
104 commandSocket._handleQueue();
105 }
106 }
107
108 onCmdQueueChanged: {
109 _handleQueue();
110 }
111 onAwaitingAnswerChanged: {
112 _handleQueue();
113 }
114
115 function _handleQueue() {
116 if (cmdQueue.length <= 0 || awaitingAnswer !== null)
117 return;
118
119 let localQueue = Array.from(cmdQueue);
120 awaitingAnswer = localQueue.shift();
121 cmdQueue = localQueue;
122 write(JSON.stringify(awaitingAnswer.command) + '\n');
123 }
124
125 function sendCommand(command, callback) {
126 cmdQueue = Array.from(cmdQueue).concat([{ "command": command, "callback": callback }])
127 }
128 }
129
130 function eventWorkspaceActivated(data) {
131 let relevant_output = null;
132 Array.from(root.workspaces).forEach(ws => {
133 if (data.id === ws.id)
134 relevant_output = ws.output;
135 });
136 root.workspaces = Array.from(root.workspaces).map(ws => {
137 if (data.focused)
138 ws.is_focused = false;
139 if (ws.output === relevant_output)
140 ws.is_active = false;
141 if (data.id === ws.id) {
142 ws.is_active = true;
143 ws.is_focused = data.focused;
144 }
145 return ws;
146 });
147 }
148 function eventWorkspaceUrgencyChanged(data) {
149 root.workspaces = Array.from(root.workspaces).map(ws => {
150 if (data.id == ws.id)
151 ws.is_urgent = data.urgent;
152 return ws;
153 });
154 }
155 function eventWorkspaceActiveWindowChanged(data) {
156 root.workspaces = Array.from(root.workspaces).map(ws => {
157 if (data.workspace_id === ws.id)
158 ws.active_window_id = data.active_window_id;
159 return ws;
160 });
161 }
162 function eventWindowOpenedOrChanged(data) {
163 root.windows = Array.from(root.windows).map(win => {
164 if (data.window.is_focused)
165 win.is_focused = false;
166 return win;
167 }).filter(win => win.id !== data.window.id).concat([data.window]);
168 }
169 function eventWindowClosed(data) {
170 root.windows = Array.from(root.windows).filter(win => win.id !== data.id);
171 }
172 function eventWindowFocusChanged(data) {
173 root.windows = Array.from(root.windows).map(win => {
174 win.is_focused = win.id === data.id;
175 return win;
176 });
177 }
178 function eventWindowUrgencyChanged(data) {
179 root.windows = Array.from(root.windows).map(win => {
180 if (win.id === data.id)
181 win.is_urgent = data.urgent;
182 return win;
183 });
184 }
185 function eventWindowLayoutsChanged(data) {
186 root.windows = Array.from(root.windows).map(win => {
187 Array.from(data.changes).forEach(change => {
188 if (win.id === change[0])
189 win.layout = change[1];
190 });
191 return win;
192 });
193 }
194}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml
new file mode 100644
index 00000000..f02d1695
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml
@@ -0,0 +1,162 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Services.Notifications
6
7Singleton {
8 id: root
9
10 readonly property bool active: !root.lockscreenActive && !root.displayInhibited
11 property bool lockscreenActive: false
12 property bool displayInhibited: false
13 property alias trackedNotifications: server.trackedNotifications
14 readonly property var groups: {
15 function matchesGroupKey(notif, groupKey) {
16 var matches = true;
17 for (const prop in groupKey.test) {
18 if (notif[prop] !== groupKey.test[prop]) {
19 matches = false;
20 break;
21 }
22 }
23 return matches;
24 }
25
26 var groups = new Map();
27 var notifs = new Array();
28 for (const [ix, notif] of server.trackedNotifications.values.entries()) {
29 var didGroup = false;
30 for (const groupKey of root.groupKeys) {
31 if (!matchesGroupKey(notif, groupKey))
32 continue;
33
34 const key = JSON.stringify({
35 "key": groupKey,
36 "values": Object.assign({}, ...(Array.from(groupKey["group-by"]).map(prop => {
37 var res = {};
38 res[prop] = notif[prop];
39 return res;
40 })))
41 });
42 if (!groups.has(key))
43 groups.set(key, new Array());
44 groups.get(key).push({ "ix": ix, "notif": notif });
45 didGroup = true;
46 break;
47 }
48
49 if (!didGroup)
50 notifs.push([{ "ix": ix, "notif": notif }]);
51 }
52 notifs.push(...groups.values());
53 notifs.sort((as, bs) => Math.min(...(as.map(o => o.ix))) - Math.min(...(bs.map(o => o.ix))));
54 return notifs.map(ns => ns.map(n => n.notif));
55 }
56
57 property var groupKeys: [
58 { "test": { "appName": "Element" }, "group-by": [ "summary" ] }
59 ];
60
61 property int historyLimit: 100
62 property var history: []
63
64 Component {
65 id: expirationTimer
66
67 QtObject {
68 id: timer
69
70 required property QtObject parent
71 required property int expirationTime
72
73 property list<QtObject> data: [
74 Timer {
75 running: root.active && !timer.expired
76 interval: timer.expirationTime
77 onTriggered: {
78 timer.parent.expirationTimer.destroy();
79 timer.parent.expirationTimer = null;
80 timer.parent.expire();
81 }
82 }
83 ]
84 }
85 }
86
87 Component {
88 id: notificationLock
89
90 RetainableLock {}
91 }
92
93 readonly property SystemClock clock: SystemClock {
94 precision: SystemClock.Minutes
95 }
96
97 function formatTime(time) {
98 const now = root.clock.date;
99 const diff = now - time;
100 const minutes = Math.ceil(diff / 60000);
101 const hours = Math.floor(minutes / 60);
102
103 if (hours < 1) {
104 if (minutes < 1)
105 return "now";
106 if (minutes == 1)
107 return "1 minute";
108 return `${minutes} minutes`;
109 }
110
111 const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
112 const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate())
113 const days = Math.floor((nowDate - timeDate) / (1000 * 86400))
114
115 const timeStr = time.toLocaleTimeString(Qt.locale(), "HH:mm");
116
117 if (days === 0)
118 return timeStr;
119 if (days === 1)
120 return `yesterday ${timeStr}`;
121
122 const dateStr = time.toLocaleTimeString(Qt.locale(), "YYYY-MM-DD");
123 return `${dateStr} ${timeStr}`;
124 }
125
126 NotificationServer {
127 id: server
128
129 bodySupported: true
130 actionsSupported: true
131 actionIconsSupported: true
132 imageSupported: true
133 bodyMarkupSupported: true
134 bodyImagesSupported: true
135
136 onNotification: notification => {
137 var timeout = notification.expireTimeout * 1000;
138 if (notification.appName == "poweralertd")
139 timeout = 2000;
140 if (timeout > 0) {
141 Object.defineProperty(notification, "expirationTimer", { configurable: true, enumerable: true, writable: true });
142 notification.expirationTimer = expirationTimer.createObject(notification, { parent: notification, expirationTime: timeout });
143 }
144 Object.defineProperty(notification, "receivedTime", { configurable: true, enumerable: true, writable: true });
145 notification.receivedTime = root.clock.date;
146 notification.closed.connect((reason) => server.onNotificationClosed(notification, reason));
147 notification.tracked = true;
148 }
149
150 function onNotificationClosed(notification, reason) {
151 while (root.history.length >= root.historyLimit) {
152 root.history[0].lock.locked = false;
153 root.history.shift();
154 }
155
156 root.history.push({
157 lock: notificationLock.createObject(root, { locked: true, object: notification }),
158 notification: notification
159 });
160 }
161 }
162}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml
new file mode 100644
index 00000000..9c813e49
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml
@@ -0,0 +1,63 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Services.Pipewire
6
7Singleton {
8 id: root
9
10 PwObjectTracker {
11 objects: Pipewire.nodes.values
12 }
13
14 enum Item {
15 Microphone,
16 Screensharing
17 }
18
19 readonly property list<var> activeItems: {
20 var items = [];
21 if (microphoneActive)
22 items.push(Privacy.Item.Microphone);
23 if (screensharingActive)
24 items.push(Privacy.Item.Screensharing);
25 return items;
26 }
27
28 readonly property bool microphoneActive: {
29 if (!Pipewire.ready || !Pipewire.nodes?.values) {
30 return false
31 }
32
33 for (const node of Pipewire.nodes.values) {
34 if (!node || (node.type & PwNodeType.AudioInStream) != PwNodeType.AudioInStream)
35 continue;
36
37 if (node.properties?.["stream.monitor"] === "true")
38 continue;
39
40 if (node.audio?.muted)
41 continue;
42
43 return true;
44 }
45
46 return false;
47 }
48
49 readonly property bool screensharingActive: {
50 if (!Pipewire.ready || !Pipewire.nodes?.values) {
51 return false
52 }
53
54 for (const node of Pipewire.nodes.values) {
55 if (!node || (node.type & PwNodeType.VideoInStream) != PwNodeType.VideoInStream)
56 continue;
57
58 return true;
59 }
60
61 return false;
62 }
63}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml b/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml
new file mode 100644
index 00000000..3c524955
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml
@@ -0,0 +1,8 @@
1import Custom as Custom
2
3Custom.FileSelector {
4 id: root
5
6 directory: @wallpapers@
7 epoch: 72000000
8}
diff --git a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
new file mode 100644
index 00000000..f7b4ed96
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
@@ -0,0 +1,201 @@
1import QtQuick
2import QtQuick.Effects
3import Quickshell
4import Quickshell.Widgets
5import Quickshell.Services.SystemTray
6
7Item {
8 anchors.verticalCenter: parent.verticalCenter
9 width: systemTrayRow.childrenRect.width
10 height: parent.height
11 clip: true
12
13 Row {
14 id: systemTrayRow
15 anchors.centerIn: parent
16 width: childrenRect.width
17 height: parent.height
18 spacing: 0
19
20 Repeater {
21 model: ScriptModel {
22 values: {
23 var trayItems = Array.from(SystemTray.items.values).filter(item => item.status !== Status.Passive);
24 trayItems.sort((a, b) => a.category !== b.category ? b.category - a.category : a.id.localeCompare(b.id))
25 return trayItems;
26 }
27 }
28
29 delegate: Item {
30 id: trayItemWrapper
31
32 required property var modelData
33 required property int index
34
35 property var trayItem: modelData
36 property string iconSource: {
37 let icon = trayItem && trayItem.icon
38 if (typeof icon === 'string' || icon instanceof String) {
39 if (icon.includes("?path=")) {
40 const split = icon.split("?path=")
41 if (split.length !== 2)
42 return icon
43 const name = split[0]
44 const path = split[1]
45 const fileName = name.substring(
46 name.lastIndexOf("/") + 1)
47 return `file://${path}/${fileName}`
48 }
49 return icon
50 }
51 return ""
52 }
53
54 width: icon.width + 6
55 height: parent.height
56 anchors.verticalCenter: parent.verticalCenter
57
58 WrapperMouseArea {
59 id: trayItemArea
60
61 anchors.fill: parent
62 acceptedButtons: Qt.LeftButton | Qt.RightButton
63 hoverEnabled: true
64 cursorShape: trayItem.onlyMenu ? Qt.ArrowCursor : Qt.PointingHandCursor
65 onClicked: mouse => {
66 if (!trayItem)
67 return
68
69 if (mouse.button === Qt.LeftButton
70 && !trayItem.onlyMenu) {
71 trayItem.activate()
72 return
73 }
74
75 if (trayItem.hasMenu) {
76 var globalPos = mapToGlobal(0, 0)
77 var currentScreen = screen || Screen
78 var screenX = currentScreen.x || 0
79 var relativeX = globalPos.x - screenX
80 menuAnchor.menu = trayItem.menu
81 menuAnchor.anchor.window = bar
82 menuAnchor.anchor.rect = Qt.rect(
83 relativeX,
84 21,
85 parent.width, 1)
86 menuAnchor.open()
87 }
88 }
89
90 Rectangle {
91 anchors.fill: parent
92 color: {
93 if (trayItemArea.containsMouse)
94 return "#33808080";
95 return "transparent";
96 }
97
98 Item {
99 anchors.fill: parent
100
101 layer.enabled: true
102 layer.effect: MultiEffect {
103 colorization: 1
104 colorizationColor: "#555"
105 }
106
107 IconImage {
108 id: icon
109
110 anchors.centerIn: parent
111 implicitSize: 16
112 source: trayItemWrapper.iconSource
113 asynchronous: true
114 smooth: true
115 mipmap: true
116
117 layer.enabled: true
118 layer.effect: MultiEffect {
119 id: effect
120
121 brightness: 1
122 }
123 }
124 }
125 }
126 }
127
128 PopupWindow {
129 id: tooltip
130
131 property bool nextVisible: (trayItem.tooltipTitle || trayItem.tooltipDescription) && (trayItemArea.containsMouse || tooltipMouseArea.containsMouse) && !menuAnchor.visible
132
133 anchor {
134 item: trayItemArea
135 edges: Edges.Bottom
136 }
137
138 visible: false
139 onNextVisibleChanged: hangTimer.restart()
140
141 Timer {
142 id: hangTimer
143 interval: 100
144 onTriggered: tooltip.visible = tooltip.nextVisible
145 }
146
147 color: "black"
148
149 implicitWidth: Math.max(tooltipTitle.contentWidth, tooltipDescription.contentWidth) + 16
150 implicitHeight: (trayItem.tooltipTitle ? tooltipTitle.contentHeight : 0) + (trayItem.tooltipDescription ? tooltipDescription.contentHeight : 0) + 16
151
152 WrapperMouseArea {
153 id: tooltipMouseArea
154
155 hoverEnabled: true
156 enabled: true
157
158 margin: 4
159
160 anchors.fill: parent
161
162 Item {
163 anchors.fill: parent
164
165 Column {
166 anchors.centerIn: parent
167 Text {
168 id: tooltipTitle
169
170 enabled: trayItem.tooltipTitle
171
172 font.pointSize: 10
173 font.family: "Fira Sans"
174 font.bold: true
175 color: "white"
176
177 text: trayItem.tooltipTitle
178 }
179
180 Text {
181 id: tooltipDescription
182
183 enabled: trayItem.tooltipDescription
184
185 font.pointSize: 10
186 font.family: "Fira Sans"
187 color: "white"
188
189 text: trayItem.tooltipDescription
190 }
191 }
192 }
193 }
194 }
195 }
196 }
197 }
198 QsMenuAnchor {
199 id: menuAnchor
200 }
201}
diff --git a/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml
new file mode 100644
index 00000000..05a40dbc
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml
@@ -0,0 +1,97 @@
1import Quickshell
2import Quickshell.Io
3import Quickshell.Services.Pipewire
4import Quickshell.Services.Mpris
5import qs.Services
6import Custom as Custom
7import QtQml
8
9Scope {
10 id: root
11
12 SocketServer {
13 active: true
14 path: `${Quickshell.env("XDG_RUNTIME_DIR")}/shell.sock`
15 handler: Socket {
16 parser: SplitParser {
17 onRead: line => {
18 const command = (() => {
19 try {
20 return JSON.parse(line);
21 } catch (e) {
22 console.warn("UnixIPC: Failed to parse command:", line, e);
23 }
24 })();
25 if (!command)
26 return;
27
28 if (command.Volume)
29 root.onCommandVolume(command.Volume);
30 else if (command.Brightness)
31 root.onCommandBrightness(command.Brightness);
32 else if (command.LockSession)
33 Custom.Systemd.lockSession();
34 else if (command.Suspend)
35 Custom.Systemd.suspend();
36 else if (command.Hibernate)
37 Custom.Systemd.hibernate();
38 else if (command.Mpris)
39 root.onCommandMpris(command.Mpris);
40 else if (command.Notifications)
41 root.onCommandNotifications(command.Notifications);
42 else
43 console.warn("UnixIPC: Command not handled:", JSON.stringify(command));
44 }
45 }
46
47 onError: e => {
48 if (e == 1)
49 return;
50 console.warn("QLocalSocket::LocalSocketError", e);
51 }
52 }
53 }
54
55 PwObjectTracker {
56 objects: [ Pipewire.defaultAudioSink, Pipewire.defaultAudioSource ]
57 }
58 function onCommandVolume(command) {
59 if (command.muted === "toggle")
60 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
61 if (command.volume === "up")
62 Pipewire.defaultAudioSink.audio.volume += 0.02;
63 if (command.volume === "down")
64 Pipewire.defaultAudioSink.audio.volume -= 0.02;
65
66 if (command["mic-muted"] === "toggle")
67 Pipewire.defaultAudioSource.audio.muted = !Pipewire.defaultAudioSource.audio.muted;
68 }
69
70 function onCommandBrightness(command) {
71 if (command === "up")
72 Brightness.currBrightness += 0.02
73 if (command === "down")
74 Brightness.currBrightness -= 0.02
75 }
76
77 function onCommandMpris(command) {
78 if (command.PauseAll)
79 Array.from(MprisProxy.players).forEach(player => {
80 if (player.canPause && player.isPlaying)
81 player.pause();
82 });
83 }
84 Component.onCompleted: { (_ => {})(MprisProxy.players); }
85
86 function onCommandNotifications(command) {
87 if (command.DismissGroup && NotificationManager.active) {
88 if (NotificationManager.groups.length > 0)
89 for (const notif of [...NotificationManager.groups[0]])
90 notif.dismiss();
91 }
92 if (command.DismissAll && NotificationManager.active) {
93 for (const notif of [...NotificationManager.trackedNotifications.values])
94 notif.dismiss();
95 }
96 }
97}
diff --git a/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
new file mode 100644
index 00000000..653f4763
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
@@ -0,0 +1,163 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Services.Pipewire
5import Quickshell.Widgets
6
7Scope {
8 id: root
9
10 property string show: ""
11 property bool inhibited: true
12
13 PwObjectTracker {
14 objects: [ Pipewire.defaultAudioSink, Pipewire.defaultAudioSource ]
15 }
16
17 Connections {
18 enabled: Pipewire.defaultAudioSink
19 target: Pipewire.defaultAudioSink?.audio
20
21 function onVolumeChanged() {
22 root.show = "sink";
23 hideTimer.restart();
24 }
25 function onMutedChanged() {
26 root.show = "sink";
27 hideTimer.restart();
28 }
29 }
30
31 Connections {
32 enabled: Pipewire.defaultAudioSource
33 target: Pipewire.defaultAudioSource?.audio
34
35 function onVolumeChanged() {
36 root.show = "source";
37 hideTimer.restart();
38 }
39 function onMutedChanged() {
40 root.show = "source";
41 hideTimer.restart();
42 }
43 }
44
45 onShowChanged: {
46 if (show)
47 hideTimer.restart();
48 }
49
50 Timer {
51 id: hideTimer
52 interval: 1000
53 onTriggered: root.show = ""
54 }
55
56 Timer {
57 id: startInhibit
58 interval: 100
59 running: true
60 onTriggered: {
61 root.show = "";
62 root.inhibited = false;
63 }
64 }
65
66 LazyLoader {
67 active: root.show && !root.inhibited
68
69 Variants {
70 model: Quickshell.screens
71
72 delegate: Scope {
73 id: screenScope
74
75 required property var modelData
76
77 PanelWindow {
78 id: window
79
80 screen: screenScope.modelData
81
82 anchors.top: true
83 margins.top: screen.height / 2 - 50 + 3.5
84 exclusiveZone: 0
85 exclusionMode: ExclusionMode.Ignore
86
87 implicitWidth: 400
88 implicitHeight: 50
89
90 mask: Region {}
91
92 color: "transparent"
93
94 Rectangle {
95 anchors.fill: parent
96 color: Qt.rgba(0, 0, 0, 0.75)
97 }
98
99 RowLayout {
100 id: layout
101
102 anchors.centerIn: parent
103
104 height: 50 - 8*2
105 width: 400 - 8*2
106
107 MaterialDesignIcon {
108 id: volumeIcon
109
110 implicitWidth: parent.height
111 implicitHeight: parent.height
112
113 icon: {
114 if (root.show == "sink") {
115 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
116 return "volume-off";
117 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
118 return "volume-low";
119 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
120 return "volume-medium";
121 return "volume-high";
122 } else if (root.show == "source") {
123 if (!Pipewire.defaultAudioSource || Pipewire.defaultAudioSource.audio.muted)
124 return "microphone-off";
125 if (Pipewire.defaultAudioSource.audio.volume > 1)
126 return "microphone-plus";
127 return "microphone";
128 }
129 return "volume-high";
130 }
131 }
132
133 Rectangle {
134 Layout.fillWidth: true
135
136 implicitHeight: 10
137
138 color: "#50ffffff"
139
140 Rectangle {
141 anchors {
142 left: parent.left
143 top: parent.top
144 bottom: parent.bottom
145 }
146
147 color: Pipewire.defaultAudioSink?.audio.muted ? "#70ffffff" : "white"
148
149 implicitWidth: {
150 if (root.show == "sink")
151 return parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0);
152 else if (root.show == "source")
153 return parent.width * Math.min(1, (Pipewire.defaultAudioSource?.audio.volume ?? 0));
154 return 0;
155 }
156 }
157 }
158 }
159 }
160 }
161 }
162 }
163}
diff --git a/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml b/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml
new file mode 100644
index 00000000..4f85a900
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml
@@ -0,0 +1,89 @@
1import QtQuick
2import Quickshell
3import qs.Services
4
5Item {
6 id: root
7
8 anchors.fill: parent
9
10 required property string screen
11
12 property Img current: one
13 property string source: selector.selected
14
15 WallpaperSelector {
16 id: selector
17 seed: screen
18 }
19
20 onSourceChanged: {
21 if (!source)
22 current = null;
23 else if (current === one)
24 two.update()
25 else
26 one.update()
27 }
28
29 Img { id: one }
30 Img { id: two }
31
32 component Img: Image {
33 id: img
34
35 function update() {
36 source = root.source || ""
37 }
38
39 anchors.fill: parent
40 fillMode: Image.PreserveAspectCrop
41 smooth: true
42 asynchronous: true
43 cache: false
44
45 opacity: 0
46
47 onStatusChanged: {
48 if (status === Image.Ready) {
49 root.current = this
50 }
51 }
52
53 states: State {
54 name: "visible"
55 when: root.current === img
56
57 PropertyChanges {
58 img.opacity: 1
59 }
60 StateChangeScript {
61 name: "unloadOther"
62 script: {
63 if (img === one)
64 two.source = ""
65 if (img === two)
66 one.source = ""
67 }
68 }
69 }
70
71 transitions: Transition {
72 SequentialAnimation {
73 NumberAnimation {
74 target: img
75 properties: "opacity"
76 duration: {
77 if (img === one && two.source == "" || img === two && one.source == "")
78 return 0;
79 return 5000;
80 }
81 easing.type: Easing.OutCubic
82 }
83 ScriptAction {
84 scriptName: "unloadOther"
85 }
86 }
87 }
88 }
89}
diff --git a/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml
new file mode 100644
index 00000000..0512ff51
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml
@@ -0,0 +1,56 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import Quickshell.Wayland
5import qs.Services
6
7Item {
8 id: root
9
10 required property var window
11
12 width: icon.width + 8
13 height: parent.height
14 anchors.verticalCenter: parent.verticalCenter
15
16 IdleInhibitor {
17 id: inhibitor
18 enabled: InhibitorState.waylandIdleInhibited
19 window: root.window
20 }
21
22 WrapperMouseArea {
23 id: widgetMouseArea
24
25 anchors.fill: parent
26
27 hoverEnabled: true
28 cursorShape: Qt.PointingHandCursor
29
30 onClicked: InhibitorState.waylandIdleInhibited = !InhibitorState.waylandIdleInhibited
31
32 Rectangle {
33 anchors.fill: parent
34 color: {
35 if (widgetMouseArea.containsMouse) {
36 return "#33808080";
37 }
38 return "transparent";
39 }
40
41 Item {
42 anchors.fill: parent
43
44 MaterialDesignIcon {
45 id: icon
46
47 implicitSize: 14
48 anchors.centerIn: parent
49
50 icon: inhibitor.enabled ? "eye" : "eye-off"
51 color: inhibitor.enabled ? "white" : "#555"
52 }
53 }
54 }
55 }
56}
diff --git a/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
new file mode 100644
index 00000000..3ae94346
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
@@ -0,0 +1,204 @@
1import Quickshell
2import QtQuick
3import qs.Services
4import Quickshell.Widgets
5import QtQuick.Layouts
6
7Row {
8 id: workspaces
9
10 required property var screen
11
12 property var ignoreWorkspaces: @ignore_workspaces@
13
14 height: parent.height
15 anchors.verticalCenter: parent.verticalCenter
16 spacing: 0
17
18 Repeater {
19 model: ScriptModel {
20 values: {
21 let currWorkspaces = NiriService.workspaces;
22 const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces);
23 currWorkspaces = currWorkspaces.filter(ws => ws.output == workspaces.screen.name).filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name));
24 currWorkspaces.sort((a, b) => {
25 if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x)
26 return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x
27 if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y)
28 return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y
29 return a.idx - b.idx;
30 });
31 return currWorkspaces;
32 }
33 }
34
35 Item {
36 id: wsItem
37
38 property var workspaceData: modelData
39
40 width: wsLabel.contentWidth + 8
41 height: parent.height
42 anchors.verticalCenter: parent.verticalCenter
43
44 WrapperMouseArea {
45 id: mouseArea
46
47 anchors.fill: parent
48
49 hoverEnabled: true
50 cursorShape: Qt.PointingHandCursor
51 enabled: true
52 onClicked: {
53 NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {});
54 }
55
56 Rectangle {
57 anchors.fill: parent
58
59 color: {
60 if (mouseArea.containsMouse) {
61 return "#33808080";
62 }
63 return "transparent";
64 }
65
66 Text {
67 id: wsLabel
68
69 anchors.centerIn: parent
70
71 font.pointSize: 10
72 font.family: "Fira Sans"
73 color: {
74 if (workspaceData.is_active)
75 return "#23fd00";
76 if (workspaceData.active_window_id === null)
77 return "#555";
78 return "white";
79 }
80
81 text: workspaceData.name ? workspaceData.name : workspaceData.idx
82 }
83 }
84 }
85
86 PopupWindow {
87 id: tooltip
88
89 property bool nextVisible: (mouseArea.containsMouse || tooltipMouseArea.containsMouse) && [...windowsModel.values].length > 0
90
91 anchor {
92 item: mouseArea
93 edges: Edges.Bottom | Edges.Left
94 }
95 visible: false
96
97 onNextVisibleChanged: hangTimer.restart()
98
99 Timer {
100 id: hangTimer
101 interval: 100
102 onTriggered: tooltip.visible = tooltip.nextVisible
103 }
104
105 implicitWidth: tooltipContent.implicitWidth
106 implicitHeight: tooltipContent.implicitHeight
107 color: "black"
108
109 WrapperMouseArea {
110 id: tooltipMouseArea
111
112 hoverEnabled: true
113 enabled: true
114
115 anchors.fill: parent
116
117 WrapperItem {
118 id: tooltipContent
119
120 margin: 0
121
122 ColumnLayout {
123 spacing: 0
124
125 Repeater {
126 model: ScriptModel {
127 id: windowsModel
128
129 values: {
130 let currWindows = Array.from(NiriService.windows).filter(win => win.workspace_id == wsItem.workspaceData.id);
131 currWindows.sort((a, b) => {
132 if (a.is_floating !== b.is_floating)
133 return b.is_floating - a.is_floating;
134 if (a.layout.tile_pos_in_workspace_view?.[0] !== b.layout.tile_pos_in_workspace_view?.[0])
135 return a.layout.tile_pos_in_workspace_view?.[0] - b.layout.tile_pos_in_workspace_view?.[0]
136 if (a.layout.tile_pos_in_workspace_view?.[1] !== b.layout.tile_pos_in_workspace_view?.[1])
137 return a.layout.tile_pos_in_workspace_view?.[1] - b.layout.tile_pos_in_workspace_view?.[1]
138 if (a.layout.pos_in_scrolling_layout?.[0] !== b.layout.pos_in_scrolling_layout?.[0])
139 return a.layout.pos_in_scrolling_layout?.[0] - b.layout.pos_in_scrolling_layout?.[0]
140 if (a.layout.pos_in_scrolling_layout?.[1] !== b.layout.pos_in_scrolling_layout?.[1])
141 return a.layout.pos_in_scrolling_layout?.[1] - b.layout.pos_in_scrolling_layout?.[1]
142 if (a.app_id !== b.app_id)
143 return a.app_id.localeCompare(b.app_id);
144
145 return a.title.localeCompare(b.title);
146 });
147 return currWindows;
148 }
149 }
150
151 WrapperMouseArea {
152 id: windowMouseArea
153
154 required property int index
155 required property var modelData
156 property var windowData: modelData
157
158 hoverEnabled: true
159 cursorShape: Qt.PointingHandCursor
160 enabled: true
161
162 Layout.fillWidth: true
163
164 onClicked: {
165 NiriService.sendCommand({ "Action": { "FocusWindow": { "id": windowData.id } } }, _ => {})
166 }
167
168 WrapperRectangle {
169 color: windowMouseArea.containsMouse ? "#33808080" : "transparent";
170
171 WrapperItem {
172 rightMargin: 8
173 leftMargin: 8
174 topMargin: windowMouseArea.index == 0 ? 8 : 4
175 bottomMargin: windowMouseArea.index == windowsModel.values.length - 1 ? 8 : 4
176
177 Text {
178 id: windowLabel
179
180 font.pointSize: 10
181 font.family: "Fira Sans"
182 color: {
183 if (windowData.is_focused)
184 return "#23fd00";
185 if (NiriService.workspaces.find(ws => ws.id == windowData.workspace_id)?.active_window_id == windowData.id)
186 return "white";
187 return "#555";
188 }
189
190 text: windowData.title
191
192 horizontalAlignment: Text.AlignLeft
193 }
194 }
195 }
196 }
197 }
198 }
199 }
200 }
201 }
202 }
203 }
204}
diff --git a/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml
new file mode 100644
index 00000000..04bcc581
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml
@@ -0,0 +1,120 @@
1import QtQml
2import Quickshell
3import Quickshell.Io
4import QtQuick
5import Quickshell.Widgets
6
7Item {
8 id: root
9
10 required property string command
11 property var state: null
12
13 height: parent.height
14 width: label.contentWidth + 8
15 anchors.verticalCenter: parent.verticalCenter
16
17 Process {
18 id: process
19 running: true
20 command: [ @worktime@, root.command, "--waybar" ]
21 stdout: StdioCollector {
22 id: processCollector
23 onStreamFinished: {
24 try {
25 root.state = JSON.parse(processCollector.text);
26 } catch (e) {
27 console.warn("Worktime: Failed to parse output:", processCollector.text, e);
28 }
29 }
30 }
31 }
32
33 Timer {
34 running: true
35 interval: 60
36 repeat: true
37 onTriggered: process.running = true
38 }
39
40 WrapperMouseArea {
41 id: mouseArea
42
43 anchors.fill: parent
44
45 enabled: true
46 hoverEnabled: true
47
48 Item {
49 anchors.fill: parent
50
51 Text {
52 id: label
53
54 anchors.centerIn: parent
55
56 visible: root.state?.text ?? false
57 text: root.state?.text ?? ""
58
59 font.pointSize: 10
60 font.family: "Fira Sans"
61 color: {
62 if (root.state?.class == "running")
63 return "white";
64 if (root.state?.class == "over")
65 return "#f28a21";
66 return "#555";
67 }
68 }
69 }
70 }
71
72 PopupWindow {
73 id: tooltip
74
75 property bool nextVisible: Boolean(root.state?.tooltip ?? false) && (mouseArea.containsMouse || tooltipMouseArea.containsMouse)
76
77 anchor {
78 item: mouseArea
79 edges: Edges.Bottom | Edges.Left
80 }
81 visible: false
82
83 onNextVisibleChanged: hangTimer.restart()
84
85 Timer {
86 id: hangTimer
87 interval: 100
88 onTriggered: tooltip.visible = tooltip.nextVisible
89 }
90
91 implicitWidth: tooltipText.contentWidth + 16
92 implicitHeight: tooltipText.contentHeight + 16
93 color: "black"
94
95 WrapperMouseArea {
96 id: tooltipMouseArea
97
98 enabled: true
99 hoverEnabled: true
100
101 anchors.fill: parent
102
103 Item {
104 anchors.fill: parent
105
106 Text {
107 id: tooltipText
108
109 anchors.centerIn: parent
110
111 font.pointSize: 10
112 font.family: "Fira Sans"
113 color: "white"
114
115 text: root.state?.tooltip ?? ""
116 }
117 }
118 }
119 }
120}
diff --git a/accounts/gkleen@sif/shell/quickshell/displaymanager.qml b/accounts/gkleen@sif/shell/quickshell/displaymanager.qml
new file mode 100644
index 00000000..b452c03d
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/displaymanager.qml
@@ -0,0 +1,115 @@
1//@ pragma UseQApplication
2
3import Quickshell
4import Quickshell.Wayland
5import Quickshell.Io
6import Quickshell.Services.Greetd
7import QtQml
8
9
10ShellRoot {
11 id: displaymanager
12
13 settings.watchFiles: false
14
15 property string currentText: ""
16 property string username: @username@
17 property list<string> command: @niri_session@
18 property list<var> messages: []
19 property bool responseRequired: false
20 property bool responseVisible: false
21
22 signal startAuth()
23
24 onStartAuth: {
25 if (Greetd.state !== GreetdState.Inactive)
26 Greetd.cancelSession();
27 displaymanager.messages = [];
28 Greetd.createSession(displaymanager.username);
29 }
30
31 Connections {
32 target: Greetd
33 function onStateChanged() {
34 console.log("greetd state: ", GreetdState.toString(Greetd.state));
35 if (Greetd.state === GreetdState.ReadyToLaunch)
36 Greetd.launch(displaymanager.command);
37 }
38 function onAuthMessage(message: string, error: bool, responseRequired: bool, echoResponse: bool) {
39 displaymanager.responseVisible = echoResponse;
40 displaymanager.responseRequired = responseRequired;
41 displaymanager.messages = Array.from(displaymanager.messages).concat([{ "text": message, "error": error }]);
42 }
43 function onAuthFailure(message: string) {
44 displaymanager.responseRequired = false;
45 displaymanager.messages = Array.from(displaymanager.messages).concat([{ "text": message, "error": true }]);
46 }
47 }
48
49 Component.onCompleted: {
50 if (Greetd.state !== GreetdState.Inactive)
51 Greetd.cancelSession();
52 }
53
54 Variants {
55 model: Quickshell.screens
56
57 delegate: Scope {
58 id: screenScope
59
60 required property var modelData
61
62 PanelWindow {
63 color: "black"
64
65 screen: screenScope.modelData
66
67 WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
68
69 anchors.top: true
70 anchors.bottom: true
71 anchors.left: true
72 anchors.right: true
73
74 LockSurface {
75 id: surfaceContent
76
77 screen: screenScope.modelData
78
79 onCurrentTextChanged: displaymanager.currentText = currentText
80 Connections {
81 target: displaymanager
82 function onCurrentTextChanged() { surfaceContent.currentText = displaymanager.currentText; }
83 function onMessagesChanged() { surfaceContent.messages = Array.from(displaymanager.messages); }
84 function onResponseRequiredChanged() { surfaceContent.responseRequired = displaymanager.responseRequired; }
85 function onResponseVisibleChanged() { surfaceContent.responseVisible = displaymanager.responseVisible; }
86 }
87
88 onResponse: responseText => Greetd.respond(responseText);
89 Connections {
90 target: Greetd
91 function onStateChanged() {
92 if (Greetd.state === GreetdState.Authenticating) {
93 surfaceContent.authRunning = true;
94 } else {
95 surfaceContent.authRunning = false;
96 }
97 }
98 }
99
100 onAuthRunningChanged: {
101 if (surfaceContent.authRunning && Greetd.state !== GreetdState.Authenticating)
102 displaymanager.startAuth();
103 }
104 Component.onCompleted: {
105 surfaceContent.authRunning = Greetd.state === GreetdState.Authenticating
106 surfaceContent.messages = Array.from(displaymanager.messages);
107 surfaceContent.responseVisible = displaymanager.responseVisible;
108 surfaceContent.responseRequired = displaymanager.responseRequired;
109 surfaceContent.currentText = displaymanager.currentText;
110 }
111 }
112 }
113 }
114 }
115}
diff --git a/accounts/gkleen@sif/shell/quickshell/shell.qml b/accounts/gkleen@sif/shell/quickshell/shell.qml
new file mode 100644
index 00000000..fb8b16dc
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/shell.qml
@@ -0,0 +1,53 @@
1//@ pragma UseQApplication
2
3import Quickshell
4import Quickshell.Wayland
5
6ShellRoot {
7 settings.watchFiles: false
8
9 Variants {
10 model: Quickshell.screens
11
12 delegate: Scope {
13 id: screenScope
14
15 required property var modelData
16
17 PanelWindow {
18 id: bgWindow
19
20 screen: screenScope.modelData
21
22 WlrLayershell.layer: WlrLayer.Background
23 WlrLayershell.namespace: "background"
24 exclusionMode: ExclusionMode.Ignore
25
26 anchors.top: true
27 anchors.bottom: true
28 anchors.left: true
29 anchors.right: true
30
31 color: "black"
32
33 WallpaperBackground {
34 screen: bgWindow.screen.name
35 }
36 }
37
38 Bar {
39 modelData: screenScope.modelData
40 }
41 }
42 }
43
44 Lockscreen {}
45 NiriIdle {}
46
47 VolumeOSD {}
48 BrightnessOSD {}
49
50 NotificationDisplay {}
51
52 UnixIPC {}
53}
diff --git a/accounts/gkleen@sif/synadm/default.nix b/accounts/gkleen@sif/synadm/default.nix
new file mode 100644
index 00000000..0a8e0d4c
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/default.nix
@@ -0,0 +1,9 @@
1{ config, pkgs, ... }:
2{
3 home.packages = with pkgs; [ synadm ];
4 sops.secrets."synadm.yaml" = {
5 format = "binary";
6 sopsFile = ./synadm_yaml;
7 path = config.xdg.configHome + "/synadm.yaml";
8 };
9}
diff --git a/accounts/gkleen@sif/synadm/synadm_yaml b/accounts/gkleen@sif/synadm/synadm_yaml
new file mode 100644
index 00000000..8d951ccc
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/synadm_yaml
@@ -0,0 +1,15 @@
1{
2 "data": "ENC[AES256_GCM,data:qJy4Pmbbxja4jmW7OaHsD0mQZ7anZwLhiVmAgkavb+CqwWGDnUBXdz22/MHCbxng5NshcFSpBoCBhgY6B9V2bUiES6bH9AtMlDcs9ebKGMArBTUTnQ2MjWQGfQTqraWdNgy+n327uj9swwCH8EZXdYH/Hlv0t/re470W+VOHeXhGghQ3Y9IGz2sgfvMGr8QxaJNydZz85rgs5QUP/PglCwWIOw2mY1EX2vYwnmiAo49LmIEaxWvRi++KHaeBveDt0nlkJwzUlipL2VOKWxkgpK3yGucQn2mz+FRe1btp+4KGm8H17eUI9FO9sBwq,iv:kgM921ovwCgDYHQj3c5Rupy/8JxHehxUD2jb1k9Ik2Y=,tag:3TLQkJbv679VWy8V2TMugw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6bzVHUGNxZTF2WC9MYmZr\neGdVVzJXN3lGdEk3cTBER3J6UTFtcUJna2d3CjdNQmRXd2haZW1MYlJzNkk1dWVD\nVTFQc2gvS0JrejJ6SFh2MXpPWDZpRE0KLS0tIE0wTC85bEpvSnlGdGFkZVFhNjFZ\nbzRiZkxMWUg2ODNVUlBmNFlPNGRrZlkK1VXLJWcssv3ETyZSSM/Hhn5VIaI9iov9\nzShZA9Zx/FX6PYTuUMC29pJ57gKourcIxa/7HwSv/xYn1A6WcYfgSg==\n-----END AGE ENCRYPTED FILE-----\n"
8 }
9 ],
10 "lastmodified": "2025-05-18T11:03:42Z",
11 "mac": "ENC[AES256_GCM,data:yonJC68PhilAgEHNNJQ8nO53Qo3rx/LnfiOWfuMm24bOUIH9QM3WZZxpigd7bHI4eC4TqRb4LvcSi0nEURTRAhwiTqGNrWbpw2Iv3n5dhLEN9aTcetG5ZuhaXqfVUoML45/ovdBZG/0l8+XIHqxN2M/g/h4JwKoR/6lqzcrVhgo=,iv:xvxBJwy+E5zUdjhGPdZPdy7tnBIEj50hfiDJFsS3wNg=,tag:L4Fas36ZOg4h0QQwC4gjNA==,type:str]",
12 "unencrypted_suffix": "_unencrypted",
13 "version": "3.10.2"
14 }
15}
diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix
index 2237b708..e601b49c 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -205,11 +205,6 @@ in {
205 StartLimitBurst = 7; 205 StartLimitBurst = 7;
206 }; 206 };
207 }; 207 };
208 swayidle = {
209 Service = {
210 RuntimeDirectory = "swayidle";
211 };
212 };
213 psi-notify = { 208 psi-notify = {
214 Install = { 209 Install = {
215 WantedBy = ["graphical-session.target"]; 210 WantedBy = ["graphical-session.target"];
@@ -242,7 +237,7 @@ in {
242 "-${lib.getExe pkgs.playerctl} -a pause" 237 "-${lib.getExe pkgs.playerctl} -a pause"
243 "-${lib.getExe (pkgs.writeShellApplication { 238 "-${lib.getExe (pkgs.writeShellApplication {
244 name = "generate-css"; 239 name = "generate-css";
245 runtimeInputs = with pkgs; [cfg.programs.wpaperd.package jq coreutils imagemagick findutils]; 240 runtimeInputs = with pkgs; [cfg.services.wpaperd.package jq coreutils imagemagick findutils];
246 text = '' 241 text = ''
247 declare -A monitors 242 declare -A monitors
248 monitors=() 243 monitors=()
@@ -333,26 +328,24 @@ in {
333 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\""; 328 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\"";
334 }; 329 };
335 }; 330 };
336 wpaperd = { 331 # wpaperd = {
337 Install = { 332 # Install = {
338 WantedBy = ["graphical-session.target"]; 333 # WantedBy = ["graphical-session.target"];
339 }; 334 # };
340 Unit = { 335 # Unit = {
341 After = [ "graphical-session.target" ]; 336 # After = [ "graphical-session.target" ];
342 PartOf = [ "graphical-session.target" ]; 337 # PartOf = [ "graphical-session.target" ];
343 }; 338 # };
344 Service = { 339 # Service = {
345 ExecStart = lib.getExe cfg.programs.wpaperd.package; 340 # ExecStart = lib.getExe cfg.services.wpaperd.package;
346 Type = "simple"; 341 # Type = "simple";
347 Restart = "always"; 342 # Restart = "always";
348 RestartSec = "2s"; 343 # RestartSec = "2s";
349 }; 344 # };
350 }; 345 # };
351 xembed-sni-proxy = { 346 xembed-sni-proxy = {
352 Unit = { 347 Unit = {
353 PartOf = lib.mkForce ["tray.target"]; 348 PartOf = lib.mkForce ["tray.target"];
354 BindsTo = ["xwayland-satellite.service"];
355 After = ["xwayland-satellite.service"];
356 }; 349 };
357 }; 350 };
358 poweralertd = { 351 poweralertd = {
@@ -385,6 +378,8 @@ in {
385 }; 378 };
386 Service = { 379 Service = {
387 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}"; 380 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}";
381 Restart = "always";
382 RestartSec = "23s";
388 }; 383 };
389 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]); 384 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]);
390 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 385 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
@@ -439,8 +434,8 @@ in {
439 tray = { 434 tray = {
440 Unit = { 435 Unit = {
441 PartOf = [ "graphical-session.target" ]; 436 PartOf = [ "graphical-session.target" ];
442 Requires = [ "waybar.service" ]; 437 # Requires = [ "waybar.service" ];
443 After = [ "graphical-session.target" "waybar.service" ]; 438 After = [ "graphical-session.target" ]; # "waybar.service" ];
444 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"]; 439 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"];
445 }; 440 };
446 }; 441 };
diff --git a/accounts/gkleen@sif/utils/async-yt-dlp.nix b/accounts/gkleen@sif/utils/async-yt-dlp.nix
new file mode 100644
index 00000000..c3b82ec5
--- /dev/null
+++ b/accounts/gkleen@sif/utils/async-yt-dlp.nix
@@ -0,0 +1,57 @@
1{ writers, python3Packages, ... }:
2
3writers.writePython3Bin "async-yt-dlp" {
4 libraries = with python3Packages; [ yt-dlp ];
5 flakeIgnore = ["E501"];
6} ''
7import sys
8import os
9
10import yt_dlp
11import yt_dlp.options
12from collections import namedtuple
13import socket
14from pathlib import Path
15import json
16
17create_parser = yt_dlp.options.create_parser
18
19
20def parse_patched_options(opts):
21 patched_parser = create_parser()
22 patched_parser.defaults.update({
23 'ignoreerrors': False,
24 'retries': 0,
25 'fragment_retries': 0,
26 'extract_flat': False,
27 'concat_playlist': 'never',
28 })
29 yt_dlp.options.create_parser = lambda: patched_parser
30 try:
31 return yt_dlp.parse_options(opts)
32 finally:
33 yt_dlp.options.create_parser = create_parser
34
35
36default_opts = parse_patched_options([]).ydl_opts
37
38
39def cli_to_api(opts):
40 opts = parse_patched_options(opts)
41 urls = opts.urls
42 opts = opts.ydl_opts
43
44 diff = {k: v for k, v in opts.items() if default_opts[k] != v}
45 if 'postprocessors' in diff:
46 diff['postprocessors'] = [pp for pp in diff['postprocessors']
47 if pp not in default_opts['postprocessors']]
48 return namedtuple('Options', ('params', 'urls'))(diff, urls)
49
50
51if __name__ == '__main__':
52 opts = cli_to_api(sys.argv[1:])
53 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
54 sock.connect(str(Path(os.environ["XDG_RUNTIME_DIR"]) / "yt-dlp.sock").encode('utf-8'))
55 with sock.makefile(mode='w', buffering=1, encoding='utf-8') as fh:
56 json.dump(opts._asdict(), fh)
57''
diff --git a/accounts/gkleen@sif/utils/pdf2pdf.nix b/accounts/gkleen@sif/utils/pdf2pdf.nix
new file mode 100644
index 00000000..9f4cbc3e
--- /dev/null
+++ b/accounts/gkleen@sif/utils/pdf2pdf.nix
@@ -0,0 +1,8 @@
1pkgs@{ lib, resholve, zsh, ghostscript_headless, ... }:
2
3resholve.writeScriptBin "pdf2pdf" {
4 inputs = with pkgs; [ghostscript_headless];
5 interpreter = lib.getExe zsh;
6} ''
7 exec gs -dPDFSETTINGS=/prepress -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER -dPreserveAnnots=false "-sOutputFile=''${2}" "''${1}"
8''
diff --git a/accounts/gkleen@sif/utils/sieve-edit.nix b/accounts/gkleen@sif/utils/sieve-edit.nix
new file mode 100644
index 00000000..f985a3f6
--- /dev/null
+++ b/accounts/gkleen@sif/utils/sieve-edit.nix
@@ -0,0 +1,24 @@
1pkgs@{ lib, resholve, zsh, sieve-connect, sops, ... }:
2
3resholve.writeScriptBin "sieve-edit" {
4 inputs = with pkgs; [sieve-connect sops];
5 interpreter = lib.getExe zsh;
6 execer = with pkgs; [
7 "cannot:${lib.getExe sieve-connect}"
8 "cannot:${lib.getExe sops}"
9 ];
10} ''
11 host=$1; shift
12 case "$host" in
13 surtr)
14 sieve-connect -s surtr.yggdrasil.li -m EXTERNAL --clientkey <(sops decrypt $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.key) --clientcert $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.crt --edit --remotesieve sieve
15 ;;
16 ymir)
17 sieve-connect -s ymir.yggdrasil.li -u gkleen --edit --remotesieve sieve
18 ;;
19 *)
20 echo "Unknown host: ‘$host’" >&2
21 return 2
22 ;;
23 esac
24''
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
index c628e2e9..702990c3 100644
--- a/accounts/gkleen@sif/zshrc
+++ b/accounts/gkleen@sif/zshrc
@@ -2,17 +2,14 @@ dir() {
2 curlArchive=false 2 curlArchive=false
3 templateArchive="" 3 templateArchive=""
4 repoUrl="" 4 repoUrl=""
5 nixShell=""
6 findNix=false
7 dir="" 5 dir=""
8 forceShell=false 6 forceShell=false
9 wormhole=false 7 wormhole=false
10 gitWorktree="" 8 gitWorktree=""
11 # notmuchMsg=""
12 quickserve=false
13 modifyPDF="" 9 modifyPDF=""
10 miniserve=false
14 11
15 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 12 while getopts ':t:a:d:ir:wg:p:m' arg; do
16 case $arg in 13 case $arg in
17 "t") ;; 14 "t") ;;
18 "a") 15 "a")
@@ -23,16 +20,13 @@ dir() {
23 templateArchive=${OPTARG:a} 20 templateArchive=${OPTARG:a}
24 fi 21 fi
25 ;; 22 ;;
26 "s") nixShell=${OPTARG:a} ;;
27 "S") findNix=true ;;
28 "d") dir=${OPTARG} ;; 23 "d") dir=${OPTARG} ;;
29 "i") forceShell=true ;; 24 "i") forceShell=true ;;
30 "r") repoUrl=${OPTARG} ;; 25 "r") repoUrl=${OPTARG} ;;
31 "w") wormhole=true ;; 26 "w") wormhole=true ;;
32 "g") gitWorktree=${OPTARG} ;; 27 "g") gitWorktree=${OPTARG} ;;
33 # "n") notmuchMsg=${OPTARG} ;;
34 "q") quickserve=true ;;
35 "p") modifyPDF=${OPTARG:a} ;; 28 "p") modifyPDF=${OPTARG:a} ;;
29 "m") miniserve=true ;;
36 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 30 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
37 esac 31 esac
38 done 32 done
@@ -56,20 +50,34 @@ dir() {
56 gitWorktree="" 50 gitWorktree=""
57 fi 51 fi
58 52
53 miniservePIDFile=""
54 if [[ ${miniserve} = "true" ]]; then
55 miniservePIDFile=$(mktemp --tmpdir --suffix=.pid)
56 fi
57
59 cleanup() 58 cleanup()
60 { 59 {
61 cd ${modifyPDF:h} 60 if [[ -n ${modifyPDF} ]]; then
62 [[ -n ${modifyPDF} ]] && nix shell 'nixos#imagemagick' -c convert -verbose ${dir}/${modifyPDF:t:r}_*.png(on) ${modifyPDF} 61 cd ${modifyPDF:h}
62 typeset -a pages
63 eval 'pages=(${dir}/${modifyPDF:t:r}_*.png(on))'
64 magick -verbose "$pages" ${modifyPDF}
65 modifyPDF=""
66 fi
67 if [[ -n ${miniservePIDFile} ]]; then
68 command kill --verbose -- $(cat ${miniservePIDFile}) && wait $(cat ${miniservePIDFile})
69 miniservePIDFile=""
70 fi
63 } 71 }
64 72
65 ( 73 (
74 set -o localoptions -o localtraps
75 trap 'return 1' INT TERM
66 trap cleanup EXIT 76 trap cleanup EXIT
67 77
68 cd ${dir} 78 cd ${dir}
69 export dir; 79 export dir;
70 80
71 ${findNix} && { nixShell=$(findNix) || return $? }
72
73 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} . 81 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} .
74 82
75 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF} 83 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF}
@@ -82,23 +90,23 @@ dir() {
82 } 90 }
83 trap cleanup EXIT 91 trap cleanup EXIT
84 92
85 if ${curlArchive}; then 93 if [[ $curlArchive = "true" ]]; then
86 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}") 94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
87 95
88 curl -L -o ${archiveFile} ${templateArchive} 96 curl -SfL -o ${archiveFile} ${templateArchive}
89 97
90 templateArchive=${archiveFile} 98 templateArchive=${archiveFile}
91 fi 99 fi
92 100
93 unpack=true 101 unpack=true
94 while ${unpack}; do 102 while [[ $unpack = "true" ]]; do
95 case $(file --brief --mime-type --dereference ${templateArchive}) in 103 case $(file --brief --mime-type --dereference ${templateArchive}) in
96 application/zip) 104 application/zip)
97 unzip ${templateArchive} 105 unzip ${templateArchive}
98 unpack=false 106 unpack=false
99 ;; 107 ;;
100 application/vnd.debian.binary-package) 108 application/vnd.debian.binary-package)
101 nix shell 'nixos#binutils' --command ar x ${templateArchive} 109 ar x ${templateArchive}
102 mkdir control data 110 mkdir control data
103 tar -C control -xvaf control.* 111 tar -C control -xvaf control.*
104 tar -C data -xvaf data.* 112 tar -C data -xvaf data.*
@@ -106,7 +114,7 @@ dir() {
106 ;; 114 ;;
107 application/x-rpm) 115 application/x-rpm)
108 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio") 116 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio")
109 nix shell 'nixos#busybox' --command rpm2cpio ${templateArchive} > ${cpioArchive} 117 rpm2cpio ${templateArchive} > ${cpioArchive}
110 templateArchive=${cpioArchive} 118 templateArchive=${cpioArchive}
111 unpack=true 119 unpack=true
112 ;; 120 ;;
@@ -115,15 +123,19 @@ dir() {
115 unpack=false 123 unpack=false
116 ;; 124 ;;
117 application/pdf) 125 application/pdf)
118 nix shell 'nixos#ghostscript' 'nixos#imagemagick' -c convert -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png 126 magick -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png
119 unpack=false 127 unpack=false
120 ;; 128 ;;
121 application/octet-stream) 129 application/octet-stream)
122 if [[ $(file --brief --dereferenc ${templateArchive}) =~ Squashfs ]]; then 130 if [[ $(file --brief --dereference ${templateArchive}) =~ Squashfs ]]; then
123 nix shell 'nixos#squashfsTools' -c unsquashfs -d . ${templateArchive} 131 unsquashfs -d . ${templateArchive}
124 unpack=false 132 unpack=false
125 fi 133 fi
126 ;; 134 ;;
135 application/x-iso9660-image)
136 7z x ${templateArchive}
137 unpack=false
138 ;;
127 *) 139 *)
128 tar -xvaf ${templateArchive} 140 tar -xvaf ${templateArchive}
129 unpack=false 141 unpack=false
@@ -134,25 +146,21 @@ dir() {
134 fi 146 fi
135 147
136 148
137 ${wormhole} && wormhole receive --accept-file 149 [[ $wormhole = "true" ]] && wormhole receive --accept-file
138 150
139 151
140 if ${quickserve}; then 152 if [[ ${#@} -gt 0 ]]; then
141 quickserve --root . --upload . --show-hidden --tar gz 153 ${@}
142 fi 154 fi
143 155
156 cd $(pwd) # Needed for mounting to work
144 157
145 if [[ ${#@} -eq 0 ]] || ${forceShell}; then 158 if [[ ${miniserve} = "true" ]]; then
146 if [[ ${#@} -gt 0 ]]; then 159 miniserve --random-route --hidden --enable-tar-gz --enable-zip . &
147 if [[ -z ${nixShell} ]]; then 160 echo $! > "${miniservePIDFile}"
148 ${@} 161 fi
149 else
150 nix-shell ${nixShell} --run "${@}"
151 fi
152 fi
153
154 cd $(pwd) # Needed for mounting to work
155 162
163 if [[ ${#@} -eq 0 ]] && [[ ${miniserve} != "true" ]] || [[ $forceShell = "true" ]]; then
156 isSingleDir() { 164 isSingleDir() {
157 typeset -a contents 165 typeset -a contents
158 contents=(*(N) .*(N)) 166 contents=(*(N) .*(N))
@@ -166,18 +174,9 @@ dir() {
166 } 174 }
167 while d=$(isSingleDir); do cd ${d}; done 175 while d=$(isSingleDir); do cd ${d}; done
168 176
169 177 zsh
170 if [[ -z ${nixShell} ]]; then 178 elif [[ ${miniserve} == "true" ]]; then
171 zsh 179 wait $(cat "${miniservePIDFile}")
172 else
173 nix-shell ${nixShell} --run zsh
174 fi
175 else
176 if [[ -z ${nixShell} ]]; then
177 ${@}
178 else
179 nix-shell ${nixShell} --run "${@}"
180 fi
181 fi 180 fi
182 ) 181 )
183} 182}
@@ -185,27 +184,30 @@ dir() {
185tmpdir() { 184tmpdir() {
186 cleanup() 185 cleanup()
187 { 186 {
188 cd / 187 cd /
189 unmount() { 188 unmount() {
190 printf "Unmounting %s\n" ${1} >&2 189 printf "Unmounting %s\n" ${1} >&2
191 fusermount -u ${1} || umount ${1} || sudo umount ${1} 190 fusermount -u ${1} || umount ${1} || sudo umount ${1}
192 } 191 }
193 192
194 if mountpoint -q -- ${dir}; then 193 if [[ -n ${dir} ]]; then
195 unmount ${dir} || return $? 194 if mountpoint -q -- ${dir}; then
196 else 195 unmount ${dir} || return $?
197 while read -d $'\0' subDir; do 196 else
198 mountpoint -q -- ${subDir} || continue 197 while read -d $'\0' subDir; do
199 unmount ${subDir} || return $? 198 mountpoint -q -- ${subDir} || continue
200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr) 199 unmount ${subDir} || return $?
201 fi 200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr)
202 201 fi
203 rm -rfv --one-file-system -- ${dir} 202
203 rm -rfv --one-file-system -- ${dir}
204 dir=""
205 fi
204 } 206 }
205 207
206 local tmpdir="" 208 local tmpdir=""
207 209
208 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 210 while getopts ':t:a:d:ir:wg:p:m' arg; do
209 case $arg in 211 case $arg in
210 "t") tmpdir="=${OPTARG}" ;; 212 "t") tmpdir="=${OPTARG}" ;;
211 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 213 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
@@ -213,6 +215,8 @@ tmpdir() {
213 done 215 done
214 216
215 ( 217 (
218 set -o localoptions -o localtraps
219 trap 'return 1' INT TERM
216 trap cleanup EXIT 220 trap cleanup EXIT
217 221
218 222
@@ -231,17 +235,7 @@ clock() {
231} 235}
232 236
233public-ip() { 237public-ip() {
234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip' 238 curl -sSf -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
235}
236
237nix-ghci() {
238 pkgExpr=""
239 if [[ ${#@} -gt 0 ]]; then
240 pkgExpr="${1}"
241 shift
242 fi
243
244 nix-shell -p "with (import <nixpkgs> {}); pkgs.haskellPackages.ghcWithPackages (p: with p; [${pkgExpr}])" --run "ghci ${@}"
245} 239}
246 240
247swap() { 241swap() {
@@ -271,14 +265,6 @@ l() {
271 ls --long --binary --git --time-style=iso --header $@ 265 ls --long --binary --git --time-style=iso --header $@
272} 266}
273 267
274re() {
275 systemctl restart $@
276}
277
278ure() {
279 systemctl --user restart $@
280}
281
282ssh-installer() { 268ssh-installer() {
283 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@ 269 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@
284} 270}
diff --git a/accounts/gkleen@surtr.nix b/accounts/gkleen@surtr.nix
index 58c4f21d..8f678ac9 100644
--- a/accounts/gkleen@surtr.nix
+++ b/accounts/gkleen@surtr.nix
@@ -1,3 +1,7 @@
1{ userName, ... }: { 1{ flake, userName, ... }: {
2 home-manager.users.${userName}.home.stateVersion = "20.09"; 2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
6 config.home-manager.users.${userName}.home.stateVersion = "20.09";
3} 7}
diff --git a/accounts/gkleen@vidhar.nix b/accounts/gkleen@vidhar.nix
index 8509c2f4..3a37c4bd 100644
--- a/accounts/gkleen@vidhar.nix
+++ b/accounts/gkleen@vidhar.nix
@@ -1,4 +1,8 @@
1{ flake, pkgs, userName, config, ... }: { 1{ flake, pkgs, userName, config, ... }: {
2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
2 config = { 6 config = {
3 users.users.${userName} = { 7 users.users.${userName} = {
4 uid = 1000; 8 uid = 1000;
diff --git a/accounts/mherold@eostre.nix b/accounts/mherold@eostre.nix
index 51e4529a..0e2f37aa 100644
--- a/accounts/mherold@eostre.nix
+++ b/accounts/mherold@eostre.nix
@@ -7,9 +7,9 @@
7 home-manager.users.${userName} = { 7 home-manager.users.${userName} = {
8 home.stateVersion = "20.09"; 8 home.stateVersion = "20.09";
9 9
10 nixpkgs.config = { 10 # nixpkgs.config = {
11 allowUnfree = true; 11 # allowUnfree = true;
12 }; 12 # };
13 13
14 home.packages = with pkgs; [ 14 home.packages = with pkgs; [
15 thunderbird libreoffice element-desktop keepassxc vlc 15 thunderbird libreoffice element-desktop keepassxc vlc
diff --git a/accounts/root@installer.nix b/accounts/root@installer.nix
index c7a418f8..5fe1db38 100644
--- a/accounts/root@installer.nix
+++ b/accounts/root@installer.nix
@@ -1,7 +1,11 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2 2
3{ 3{
4 home-manager.users.${userName} = { config, ... } : { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = { config, ... } : {
5 home.stateVersion = config.home.version.release; 9 home.stateVersion = config.home.version.release;
6 }; 10 };
7} 11}
diff --git a/accounts/root@sif.nix b/accounts/root@sif.nix
index c9e129a0..bb816230 100644
--- a/accounts/root@sif.nix
+++ b/accounts/root@sif.nix
@@ -1,6 +1,10 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2{ 2{
3 home-manager.users.${userName} = { 3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh tmux
5 ];
6
7 config.home-manager.users.${userName} = {
4 home.stateVersion = "20.09"; 8 home.stateVersion = "20.09";
5 9
6 programs.ssh.matchBlocks = { 10 programs.ssh.matchBlocks = {
diff --git a/accounts/root@surtr.nix b/accounts/root@surtr.nix
index 58c4f21d..8f678ac9 100644
--- a/accounts/root@surtr.nix
+++ b/accounts/root@surtr.nix
@@ -1,3 +1,7 @@
1{ userName, ... }: { 1{ flake, userName, ... }: {
2 home-manager.users.${userName}.home.stateVersion = "20.09"; 2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
6 config.home-manager.users.${userName}.home.stateVersion = "20.09";
3} 7}
diff --git a/accounts/root@vidhar.nix b/accounts/root@vidhar.nix
index e82414a8..0fc56633 100644
--- a/accounts/root@vidhar.nix
+++ b/accounts/root@vidhar.nix
@@ -1,6 +1,11 @@
1{ config, userName, ... }: 1{ flake, config, userName, ... }:
2
2{ 3{
3 home-manager.users.${userName} = { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = {
4 home.stateVersion = "20.09"; 9 home.stateVersion = "20.09";
5 10
6 programs.ssh.matchBlocks = { 11 programs.ssh.matchBlocks = {