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.nix550
-rw-r--r--accounts/gkleen@sif/emacs.el8
-rw-r--r--accounts/gkleen@sif/firefox-chrome.css30
-rw-r--r--accounts/gkleen@sif/libvirt/default.nix119
-rw-r--r--accounts/gkleen@sif/niri.nix1005
-rw-r--r--accounts/gkleen@sif/niri/default.nix536
-rw-r--r--accounts/gkleen@sif/niri/mako.nix50
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix338
-rw-r--r--accounts/gkleen@sif/shell/default.nix123
-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.qml130
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml139
-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.qml128
-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.qml107
-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.qml216
-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/ScreenRecord.qml63
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml84
-rw-r--r--accounts/gkleen@sif/shell/quickshell/SystemTray.qml201
-rw-r--r--accounts/gkleen@sif/shell/quickshell/UnixIPC.qml106
-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.qml212
-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/ssh-hosts.nix96
-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.nix292
-rw-r--r--accounts/gkleen@sif/utils/async-yt-dlp.nix57
-rw-r--r--accounts/gkleen@sif/utils/ldif2json/conf.patch12
-rw-r--r--accounts/gkleen@sif/utils/ldif2json/default.nix20
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/.envrc4
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/.gitignore2
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/default.nix22
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__init__.py0
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__main__.py73
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/pyproject.toml17
-rw-r--r--accounts/gkleen@sif/utils/nixpkgs-pr-watch/uv.lock309
-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/zshrc153
-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
85 files changed, 9525 insertions, 1335 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 7d5a9c25..101330c3 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -4,33 +4,6 @@ with lib;
4 4
5let 5let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 emacsScratch = pkgs.stdenv.mkDerivation (sources.emacs-scratch_el // rec {
8 phases = [ "installPhase" ];
9
10 installPhase = ''
11 mkdir -p $out/share/emacs/site-lisp
12 cp $src/scratch.el $out/share/emacs/site-lisp/default.el
13 '';
14 });
15 muteScript = pkgs.stdenv.mkDerivation {
16 name = "mute";
17 src = ./scripts/mute.zsh;
18
19 buildInputs = with pkgs; [ makeWrapper ];
20
21 phases = [ "installPhase" ];
22
23 installPhase = ''
24 mkdir -p $out/bin
25 install -m 0755 $src $out/bin/mute
26 wrapProgram $out/bin/mute \
27 --prefix PATH : ${pkgs.zsh}/bin \
28 --prefix PATH : ${pkgs.findutils}/bin \
29 --prefix PATH : ${pkgs.util-linux}/bin \
30 --prefix PATH : ${pkgs.coreutils}/bin \
31 --prefix PATH : ${pkgs.pulseaudio}/bin
32 '';
33 };
34 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin { 7 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin {
35 name = "${package.name}-wrapped"; 8 name = "${package.name}-wrapped";
36 buildInputs = with pkgs; [ makeWrapper ]; 9 buildInputs = with pkgs; [ makeWrapper ];
@@ -47,10 +20,6 @@ let
47 ''; 20 '';
48 }; 21 };
49 22
50 wrappedChrome = wrapElectron { package = pkgs.google-chrome; outBin = "google-chrome"; };
51 wrappedZulip = wrapElectron { package = pkgs.zulip; bin = "zulip"; outBin = "zulip"; };
52 wrappedElementDesktop = wrapElectron { package = pkgs.element-desktop; bin = "element-desktop"; };
53 wrappedRocketChatDesktop = wrapElectron { package = pkgs.rocketchat-desktop; bin = "rocketchat-desktop"; outBin = "rocketchat"; };
54 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; }; 23 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; };
55 24
56 wrappedKeepassxc = pkgs.symlinkJoin { 25 wrappedKeepassxc = pkgs.symlinkJoin {
@@ -63,7 +32,7 @@ let
63 text = '' 32 text = ''
64 [D-BUS Service] 33 [D-BUS Service]
65 Name=org.keepassxc.KeePassXC.MainWindow 34 Name=org.keepassxc.KeePassXC.MainWindow
66 Exec=${pkgs.coreutils}/bin/false 35 Exec=${lib.getExe' pkgs.coreutils "false"}
67 SystemdService=keepassxc.service 36 SystemdService=keepassxc.service
68 ''; 37 '';
69 }) 38 })
@@ -73,36 +42,48 @@ let
73 text = '' 42 text = ''
74 [D-BUS Service] 43 [D-BUS Service]
75 Name=org.freedesktop.secrets 44 Name=org.freedesktop.secrets
76 Exec=${pkgs.coreutils}/bin/false 45 Exec=${lib.getExe' pkgs.coreutils "false"}
77 SystemdService=keepassxc.service 46 SystemdService=keepassxc.service
78 ''; 47 '';
79 }) 48 })
80 ]; 49 ];
81 }; 50 };
82 51
83 lockCommand = "${config.systemd.package}/bin/systemctl --user start gtklock.service"; 52 editor = pkgs.symlinkJoin {
53 name = "${cfg.services.emacs.package.name}-editor";
54 buildInputs = with pkgs; [ makeWrapper ];
55 paths = [ cfg.services.emacs.package ];
56 postBuild = ''
57 wrapProgram $out/bin/emacsclient \
58 --inherit-argv0 \
59 --add-flags ${lib.escapeShellArg (lib.escapeShellArgs cfg.services.emacs.client.arguments)}
60 '';
61 };
84in { 62in {
85 imports = with flake.nixosModules.userProfiles.${userName}; [ 63 imports = with flake.nixosModules.userProfiles.${userName}; [
86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 64 zsh tmux mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
87 ]; 65 ];
88 66
89 config = { 67 config = {
90 services.displayManager.defaultSession = "Hyprland"; # "none+xmonad"; 68 nixpkgs.externalConfig.allowUnfreePackages = [
69 "graphite"
70 ];
91 71
92 home-manager.users.${userName} = { 72 home-manager.users.${userName} = {
93 imports = [ 73 imports = [
94 ./libvirt 74 ./libvirt
95 ./niri 75 ./niri.nix
96 flakeInputs.nix-index-database.hmModules.nix-index 76 ./shell
97 flakeInputs.impermanence.nixosModules.home-manager.impermanence 77 ./synadm
78 flakeInputs.nix-index-database.homeModules.nix-index
98 ]; 79 ];
99 80
100 home.stateVersion = "20.09"; 81 home.stateVersion = "20.09";
101 82
102 nixpkgs.config = { 83 # nixpkgs.config = {
103 allowUnfree = true; 84 # allowUnfree = true;
104 zathura.useMupdf = false; 85 # zathura.useMupdf = false;
105 }; 86 # };
106 87
107 nix.registry = { 88 nix.registry = {
108 "flk" = { 89 "flk" = {
@@ -112,14 +93,14 @@ in {
112 }; 93 };
113 to = { 94 to = {
114 type = "git"; 95 type = "git";
115 url = "file:///home/gkleen/config/nixos-flakes"; 96 url = "file:///home/gkleen/projects/machines";
116 }; 97 };
117 }; 98 };
118 }; 99 };
119 100
120 programs = { 101 programs = {
121 ssh = { 102 ssh = {
122 matchBlocks = import ./ssh-hosts.nix { inherit pkgs; }; # customUtils.nixImport { dir = ./ssh-hosts; }; 103 matchBlocks = import ./ssh-hosts.nix inputs; # customUtils.nixImport { dir = ./ssh-hosts; };
123 extraConfig = '' 104 extraConfig = ''
124 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 105 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
125 ProxyJump remote.cip.ifi.lmu.de 106 ProxyJump remote.cip.ifi.lmu.de
@@ -137,11 +118,12 @@ in {
137 ''} 118 ''}
138 119
139 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 120 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
140 # ProxyCommand ${pkgs.socat}/bin/socat - SOCKS4A:127.0.0.1:%h:%p,socksport=8118 121 ProxyCommand ${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=8118
141 ProxyJump ssh.math.lmu.de 122 # ProxyJump ssh.math.lmu.de
142 123
143 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null" 124 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null"
144 ProxyJump cip04 125 ProxyCommand ${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=8126
126 # ProxyJump cip04
145 127
146 Match host *.ifi.lmu.de,*.math.lmu.de 128 Match host *.ifi.lmu.de,*.math.lmu.de
147 AddressFamily inet 129 AddressFamily inet
@@ -155,22 +137,32 @@ in {
155 137
156 emacs = { 138 emacs = {
157 enable = true; 139 enable = true;
158 package = pkgs.emacs29-pgtk; 140 package = pkgs.emacs-pgtk;
159 extraPackages = epkgs: with epkgs; [ 141 extraPackages = epkgs: with epkgs; [
160 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode 142 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode
161 yaml-mode json-mode shakespeare-mode smart-mode-line 143 yaml-mode json-mode shakespeare-mode smart-mode-line
162 highlight-parentheses highlight-symbol ag sass-mode lua-mode 144 highlight-parentheses highlight-symbol ag sass-mode
163 fira-code-mode use-package wanderlust # notmuch 145 lua-mode fira-code-mode use-package wanderlust # notmuch
164 git-gutter emacsScratch 146 git-gutter scratch edit-server mediawiki editorconfig
165 edit-server mediawiki editorconfig typescript-mode 147 typescript-mode markdown-mode nftables-mode rustic
166 markdown-mode nftables-mode rustic lsp-mode lsp-ui 148 lsp-mode lsp-ui direnv company projectile
167 direnv company projectile tomorrow-night-paradise-theme 149 tomorrow-night-paradise-theme
168 treesit-grammars.with-all-grammars magit-delta scad-mode 150 treesit-grammars.with-all-grammars magit-delta scad-mode
151 typst-ts-mode
169 ]; 152 ];
170 overrides = self: super: { 153 overrides = self: super: {
171 tomorrow-night-paradise-theme = super.trivialBuild { 154 tomorrow-night-paradise-theme = super.trivialBuild {
172 inherit (sources.tomorrow-night-paradise-theme) pname version src; 155 inherit (sources.tomorrow-night-paradise-theme) pname version src;
173 }; 156 };
157 scratch = pkgs.stdenv.mkDerivation {
158 inherit (sources.emacs-scratch_el) pname version src;
159
160 phases = [ "unpackPhase" "installPhase" ];
161
162 installPhase = ''
163 install -Dt $out/share/emacs/site-lisp scratch.el
164 '';
165 };
174 }; 166 };
175 }; 167 };
176 firefox = { 168 firefox = {
@@ -184,23 +176,110 @@ in {
184 }; 176 };
185 }; 177 };
186 }; 178 };
179 chromium.enable = true;
187 180
188 zathura.enable = true; 181 zathura = {
182 enable = true;
183 options = {
184 scroll-page-aware = true;
185 };
186 };
189 imv.enable = true; 187 imv.enable = true;
190 188
191 mpv.config = { 189 mpv.config = {
192 demuxer-max-bytes = 1073741824; 190 demuxer-max-bytes = 2147483648;
193 demuxer-max-back-bytes = 268435456; 191 demuxer-max-back-bytes = 536870912;
194 gpu-api = "vulkan"; 192 vo = "dmabuf-wayland";
195 }; 193 };
196 194
197 zsh.initExtra = '' 195 zsh.initContent = let
198 source ${./zshrc} 196 zshrc = pkgs.resholve.mkDerivation {
197 pname = "zshrc";
198 version = "0.0.0";
199
200 src = ./zshrc;
201
202 dontUnpack = true;
203 dontConfigure = true;
204 dontBuild = true;
205
206 installPhase = ''
207 mkdir -p $out/share
208 install "$src" $out/share/zshrc
209 '';
210
211 solutions = {
212 default = {
213 scripts = [ "share/zshrc" ];
214 interpreter = "none";
215 inputs = with pkgs; [
216 coreutils
217 rpm
218 binutils
219 squashfsTools
220 unzip
221 cfg.programs.git.package
222 magickWrapped
223 curl
224 file
225 gnutar
226 cpio
227 magic-wormhole
228 cfg.programs.zsh.package
229 fuse
230 util-linux
231 findutils
232 qrencode
233 tty-clock
234 cfg.programs.jq.package
235 eza
236 less
237 config.systemd.package
238 config.programs.ssh.package
239 gnused
240 miniserve
241 p7zip
242 ];
243 execer = with pkgs; [
244 "cannot:${lib.getExe' rpm "rpm2cpio"}"
245 "cannot:${lib.getExe' squashfsTools "unsquashfs"}"
246 "cannot:${lib.getExe' unzip "unzip"}"
247 "cannot:${lib.getExe cfg.programs.git.package}"
248 "cannot:${lib.getExe cpio}"
249 "cannot:${lib.getExe' magic-wormhole "wormhole"}"
250 "cannot:${lib.getExe' fuse "fusermount"}"
251 "cannot:${lib.getExe less}"
252 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
253 "cannot:${lib.getExe config.programs.ssh.package}"
254 "cannot:${lib.getExe' p7zip "7z"}"
255 ];
256 wrapper = with pkgs; [
257 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
258 ];
259 fake = {
260 builtin = ["print"];
261 external = ["sudo" "umount"];
262 };
263 };
264 };
265 };
266 magickWrapped = pkgs.symlinkJoin {
267 inherit (pkgs.imagemagick) name;
268 paths = [ pkgs.imagemagick ];
269
270 buildInputs = with pkgs; [ makeWrapper ];
271 postBuild = ''
272 wrapProgram $out/bin/magick \
273 --prefix PATH : ${lib.makeBinPath (with pkgs; [ ghostscript ])}
274 '';
275 };
276 in ''
277 source ${zshrc}/share/zshrc
199 ''; 278 '';
200 zsh.dirHashes = let 279 zsh.dirHashes = let
201 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs; 280 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs;
202 inputNames = { 281 inputNames = {
203 "nixpkgs" = "nixos"; 282 self = "nixos";
204 }; 283 };
205 in flakeHashes // { 284 in flakeHashes // {
206 u2w = "$HOME/projects/uni2work"; 285 u2w = "$HOME/projects/uni2work";
@@ -212,6 +291,24 @@ in {
212 pro = "$HOME/projects/pro"; 291 pro = "$HOME/projects/pro";
213 media = "$HOME/media"; 292 media = "$HOME/media";
214 }; 293 };
294 starship.settings = {
295 directory.substitutions = mapAttrs' (name: value: let
296 value' = builtins.unsafeDiscardStringContext value;
297 value'' = if hasPrefix "$HOME/" value' then "~" + removePrefix "$HOME" value' else value';
298 in nameValuePair value'' "~${name}"
299 ) cfg.programs.zsh.dirHashes;
300 };
301 nix-index.enable = true;
302 jq.colors = {
303 arrays = "1;37";
304 "false" = "0;37";
305 "null" = "2;37";
306 numbers = "0;37";
307 objectKeys = "1;34";
308 objects = "1;37";
309 strings = "0;32";
310 "true" = "0;37";
311 };
215 312
216 obs-studio = { 313 obs-studio = {
217 enable = true; 314 enable = true;
@@ -221,55 +318,59 @@ in {
221 gh = { 318 gh = {
222 enable = true; 319 enable = true;
223 settings = { 320 settings = {
224 editor = "${config.home-manager.users.${userName}.programs.emacs.package}/bin/emacsclient"; 321 editor = lib.getExe' editor "emacsclient";
225 gitProtocol = "ssh"; 322 gitProtocol = "ssh";
226 }; 323 };
227 }; 324 };
228 325
229 kitty = { 326 alacritty = {
230 enable = true; 327 enable = true;
231 font = { 328 theme = "kitty";
232 package = pkgs.nerd-fonts.fira-mono;
233 name = "Fira Mono";
234 size = 10;
235 };
236 settings = { 329 settings = {
237 scrollback_pager_history_size = 50; 330 font = {
238 # background_opacity = "0.9"; 331 normal = {
239 enable_audio_bell = false; 332 family = "Fira Mono";
240 update_check_interval = 0; 333 style = "Medium";
241 strip_trailing_spaces = "smart"; 334 };
242 focus_follows_mouse = true; 335 size = 10;
243 visual_bell_duration = "0.1"; 336 };
244 visual_bell_color = "#26240d"; 337 hints = {
245 tab_bar_style = "powerline"; 338 alphabet = "aoeuhtns";
246 tab_powerline_style = "slanted"; 339 enabled = [
247 # notify_on_cmd_finish = "invisible 120"; 340 {
248 }; 341 command = lib.getExe' pkgs.xdg-utils "xdg-open";
249 keybindings = { 342 hyperlinks = true;
250 "kitty_mod+n" = "detach_window"; 343 post_processing = true;
251 "kitty_mod+m" = "detach_window ask"; 344 persist = false;
252 }; 345 mouse.enabled = true;
253 }; 346 binding = { key = "O"; mods = "Control|Shift"; };
254 wpaperd = { 347 regex = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\\\s{-}\\\\^⟨⟩`\\\\\\\\]+";
255 enable = true; 348 }
256 settings.default = { 349 {
257 path = "~/.wallpapers"; 350 command = lib.getExe' pkgs.wl-clipboard-rs "wl-copy";
258 duration = "8h"; 351 hyperlinks = true;
259 mode = "center"; 352 post_processing = true;
353 persist = false;
354 mouse.enabled = true;
355 binding = { key = "Y"; mods = "Control|Shift"; };
356 regex = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\\\s{-}\\\\^⟨⟩`\\\\\\\\]+";
357 }
358 ];
359 };
360 terminal.shell.program = lib.getExe cfg.programs.zsh.package;
260 }; 361 };
261 }; 362 };
262 fuzzel = { 363 fuzzel = {
263 enable = true; 364 enable = true;
264 settings = { 365 settings = {
265 main = { 366 main = {
266 terminal = lib.getExe cfg.programs.kitty.package; 367 terminal = "${lib.getExe cfg.programs.alacritty.package} msg --socket /run/user/1000/alacritty.sock create-window --command ${lib.getExe' config.systemd.package "systemd-run"} --user --pty --same-dir --wait --collect --quiet --service-type=exec --expand-environment=no {cmd}";
267 layer = "overlay"; 368 layer = "overlay";
268 icon-theme = "Paper"; 369 icon-theme = "Paper";
269 font = "Fira Sans"; 370 font = "Fira Sans";
270 }; 371 };
271 colors = { 372 colors = {
272 background = "000000aa"; 373 background = "000000cc";
273 text = "cdd6f4ff"; 374 text = "cdd6f4ff";
274 match = "94e2d5ff"; 375 match = "94e2d5ff";
275 selection = "585b70ff"; 376 selection = "585b70ff";
@@ -282,26 +383,46 @@ in {
282 }; 383 };
283 }; 384 };
284 }; 385 };
386 pandoc = {
387 enable = true;
388 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."];
389 };
390 nushell = {
391 enable = true;
392 settings.show_banner = false;
393 };
394 fd.enable = true;
285 }; 395 };
286 396
287 services = { 397 services = {
398 wpaperd = {
399 enable = false;
400 settings.default = {
401 path = "~/.wallpapers";
402 duration = "15m";
403 mode = "center";
404 };
405 };
288 emacs = { 406 emacs = {
289 enable = true; 407 enable = true;
290 socketActivation.enable = true; 408 socketActivation.enable = true;
291 client = { 409 client = {
292 enable = true; 410 enable = true;
293 arguments = mkForce ["--reuse-frame" "--alternate-editor" "\"\""]; 411 arguments = mkForce ["--create-frame" "--alternate-editor" (lib.getExe cfg.services.emacs.package)];
294 }; 412 };
295 }; 413 };
296 gpg-agent = { 414 gpg-agent = {
297 enable = true; 415 enable = true;
298 enableSshSupport = true; 416 enableSshSupport = true;
299 extraConfig = '' 417 extraConfig = ''
300 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry 418 pinentry-program ${lib.getExe' pkgs.pinentry-gtk2 "pinentry"}
301 grab 419 grab
302 ''; 420 '';
303 }; 421 };
304 xembed-sni-proxy.enable = true; 422 xembed-sni-proxy = {
423 enable = true;
424 package = pkgs.kdePackages.plasma-workspace;
425 };
305 udiskie = { 426 udiskie = {
306 enable = true; 427 enable = true;
307 automount = false; 428 automount = false;
@@ -313,8 +434,11 @@ in {
313 device_mounted = []; 434 device_mounted = [];
314 }; 435 };
315 device_config = [ 436 device_config = [
316 { mount_path = "/run/etc-metadata"; ignore = true; } 437 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; }
438 { mount_path = "/run/nixos-etc-metadata"; ignore = true; }
439 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; }
317 ]; 440 ];
441 icon_names.media = ["drive-removable-media-symbolic"];
318 }; 442 };
319 }; 443 };
320 network-manager-applet.enable = true; 444 network-manager-applet.enable = true;
@@ -331,7 +455,7 @@ in {
331 batch = "true"; 455 batch = "true";
332 log = "false"; 456 log = "false";
333 repeat = "watch"; 457 repeat = "watch";
334 sshcmd = "${pkgs.openssh}/bin/ssh"; 458 sshcmd = lib.getExe' pkgs.openssh "ssh";
335 ui = "text"; 459 ui = "text";
336 }; 460 };
337 }; 461 };
@@ -347,35 +471,9 @@ in {
347 serverUrl = "https://etesync.yggdrasil.li"; 471 serverUrl = "https://etesync.yggdrasil.li";
348 }; 472 };
349 473
350 swayidle = {
351 enable = true;
352 events = [
353 { event = "before-sleep"; command = lockCommand; }
354 # { event = "after-resume"; command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms on"; }
355 { event = "lock"; command = lockCommand; }
356 ];
357 timeouts = [
358 # { timeout = 300;
359 # command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off";
360 # }
361 { timeout = 330; command = lockCommand; }
362 ];
363 extraArgs = [
364 "idlehint" "30"
365 ];
366 };
367 poweralertd.enable = true; 474 poweralertd.enable = true;
368 avizo = { 475
369 enable = true; 476 gnome-keyring.enable = lib.mkForce false;
370 settings.default = {
371 time = "1.0";
372 background = "rgba(0, 0, 0, 0.8)";
373 border-color = "rgba(0, 0, 0, 1)";
374 bar-fg-color = "rgba(160, 160, 160, 1)";
375 bar-bg-color = "rgba(32, 32, 32, 0.96)";
376 # y-offset = "0.25";
377 };
378 };
379 }; 477 };
380 478
381 home.pointerCursor = { 479 home.pointerCursor = {
@@ -406,6 +504,15 @@ in {
406 name = "Paper-Mono-Dark"; 504 name = "Paper-Mono-Dark";
407 }; 505 };
408 }; 506 };
507 qt.enable = true;
508 qt.platformTheme.name = "gtk";
509
510 qt.kde.settings = {
511 kwalletrc = {
512 KSecretD.Enabled = false;
513 Wallet."Default Wallet" = "store";
514 };
515 };
409 516
410 xsession.preferStatusNotifierItems = true; 517 xsession.preferStatusNotifierItems = true;
411 518
@@ -415,18 +522,19 @@ in {
415 packages = with pkgs; [ 522 packages = with pkgs; [
416 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 523 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
417 mumble pulseaudio-ctl pamixer libnotify screen-message 524 mumble pulseaudio-ctl pamixer libnotify screen-message
418 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 525 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
419 thunderbird zoom-us steam steam-run wireshark virt-manager 526 thunderbird zoom-us xdg-desktop-portal steam steam-run
420 rclone cached-nix-shell worktime fira-code-symbols 527 wireshark virt-manager rclone cached-nix-shell worktime
421 libreoffice xournalpp google-chrome nixos-shell virt-viewer 528 fira-code-symbols libreoffice xournalpp
422 freerdp gnome-icon-theme paper-icon-theme sshpassSecret 529 nixos-shell virt-viewer freerdp gnome-icon-theme
423 weechat element-desktop matrix-synapse-tools.synadm 530 paper-icon-theme sshpassSecret weechat element-desktop
424 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs 531 sieve-connect gimp3 inkscape udiskie glab
425 sieve-connect gimp inkscape udiskie glab nitrokey-app 532 gtklock wlrctl remmina openscad spice-record
426 pynitrokey gtklock wlrctl remmina openscad spice-record 533 nerd-fonts.fira-mono
427 libguestfs-with-appliance nerd-fonts.fira-mono
428 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 534 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
429 ]; 535 swtpm (hunspell.withDicts (dicts: with dicts; [en_GB-large de_DE]))
536 libation libqalculate graphite foliate gnucash
537 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg { inherit sources flake flakeInputs; }) (customUtils.nixImport { dir = ./utils; });
430 538
431 file = { 539 file = {
432 ".backup-munin".source = ./backup-patterns; 540 ".backup-munin".source = ./backup-patterns;
@@ -443,15 +551,13 @@ in {
443 sessionVariables = { 551 sessionVariables = {
444 # GDK_SCALE = 96.0 / 282.0; 552 # GDK_SCALE = 96.0 / 282.0;
445 # QT_AUTO_SCREEN_SCALE_FACTOR = 1; 553 # QT_AUTO_SCREEN_SCALE_FACTOR = 1;
446 QT_QPA_PLATFORMTHEME = "qt5ct"; 554 QT_QPA_PLATFORMTHEME = lib.mkForce "gtk3";
447 LIBVIRT_DEFAULT_URI = "qemu:///system"; 555 LIBVIRT_DEFAULT_URI = "qemu:///system";
448 STACK_XDG = 1; 556 STACK_XDG = 1;
449 EDITOR = pkgs.writeShellScript "editor" '' 557 EDITOR = lib.getExe' editor "emacsclient";
450 args=("--reuse-frame" "--alternate-editor" "") 558 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
451 args+=("$@") 559 SYSTEMD_ADJUST_TERMINAL_TITLE = "false";
452 exec -a emacsclient ${cfg.services.emacs.package}/bin/emacsclient "''${args[@]}" 560 SYSTEMD_TINT_BACKGROUND = "false";
453 '';
454 RCLONE_PASSWORD_COMMAND = "${pkgs.libsecret}/bin/secret-tool lookup service rclone";
455 }; 561 };
456 562
457 extraProfileCommands = '' 563 extraProfileCommands = ''
@@ -464,7 +570,7 @@ in {
464 source = ./wireplumber; 570 source = ./wireplumber;
465 recursive = true; 571 recursive = true;
466 onChange = '' 572 onChange = ''
467 ${pkgs.systemd}/bin/systemctl --user try-restart wireplumber 573 ${lib.getExe' config.systemd.package "systemctl"} --user try-restart wireplumber
468 ''; 574 '';
469 }; 575 };
470 "stack/config.yaml" = { 576 "stack/config.yaml" = {
@@ -488,37 +594,32 @@ in {
488 General = { 594 General = {
489 dot_as_separator = 0; 595 dot_as_separator = 0;
490 }; 596 };
597 Mode = {
598 calculate_as_you_type = 1;
599 };
491 }; 600 };
492 }; 601 };
493 "emacs/init.el".source = ./emacs.el; 602 "emacs/init.el".source = pkgs.substitute {
603 src = ./emacs.el;
604 substitutions = [
605 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
606 ];
607 };
608 # "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
609 # [Unit]
610 # After=graphical-session.target
611 # '';
612 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = ''
613 [Unit]
614 Before=graphical-session-pre.target
615 '';
494 }; 616 };
495 617
496 xdg.dataFile = { 618 xdg.dataFile = {
497 "pandoc/abbreviations" = {
498 source = pkgs.runCommand "pandoc-abbreviations" {
499 buildInputs = [ pkgs.pandoc pkgs.coreutils ];
500 } (let
501 germanAbbrevs = pkgs.fetchFromGitHub {
502 owner = "jfilter";
503 repo = "german-abbreviations";
504 rev = "8eb9dae93b6f05d7c53374cd217ab2dc89558e0c";
505 sha256 = "SaD3tSqzen6Y3SPICe6/9vhe4iMHlArZ3kFQaEk7Hps=";
506 };
507 in ''
508 cat \
509 <(pandoc --print-default-data-file=abbreviations) \
510 <(grep -E '^[^ ]+\.$' ${germanAbbrevs}/german_abbreviations.txt) \
511 ${pkgs.writeText "abbrevs.txt" ''
512 i.A.
513 d.h.
514 D.h.
515 gdw.
516 ''} \
517 | sort | uniq >$out
518 '');
519 };
520 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 619 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
521 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 620 "dbus-1/services/org.freedesktop.secrets.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service";
621 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
622 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
522 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 623 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
523 inherit (sources.emoji-data) pname src; 624 inherit (sources.emoji-data) pname src;
524 version = lib.removePrefix "v" sources.emoji-data.version; 625 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -604,19 +705,68 @@ in {
604 name = "Rainbow"; 705 name = "Rainbow";
605 exec = toString (pkgs.writeShellScript "rainbow" '' 706 exec = toString (pkgs.writeShellScript "rainbow" ''
606 exec -- \ 707 exec -- \
607 ${config.systemd.package}/bin/systemd-run --wait --user --slice-inherit \ 708 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
608 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 709 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
609 --property 'Environment=DSCP=46' \ 710 -E DSCP=46 -E NIXOS_OZONE_WL \
610 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \ 711 -- ${lib.getExe pkgs.dscp} ${lib.getExe cfg.programs.chromium.package} \
611 --force-device-scale-factor=1.5 \
612 --class=Rainbow \ 712 --class=Rainbow \
613 --kiosk "https://web.openrainbow.com" \ 713 --app="https://web.openrainbow.com" \
614 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 714 --user-data-dir=''${HOME}/.config/chromium-rainbow
615 ''); 715 '');
616 icon = pkgs.fetchurl { 716 icon = pkgs.fetchurl {
617 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 717 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
618 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU="; 718 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU=";
619 }; 719 };
720 settings = {
721 StartupWMClass = "Rainbow";
722 };
723 };
724 kimai = {
725 name = "Kimai";
726 exec = toString (pkgs.writeShellScript "kimai" ''
727 exec -- \
728 ${lib.getExe cfg.programs.chromium.package} \
729 --class=Kimai \
730 --app="https://kimai.yggdrasil.li" \
731 --user-data-dir=''${HOME}/.config/chromium-kimai
732 '');
733 icon = pkgs.fetchurl {
734 url = "https://www.kimai.org/images/kimai_logo.png";
735 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
736 };
737 settings = {
738 StartupWMClass = "Kimai";
739 StartupNotify = "true";
740 };
741 };
742 audiobookshelf = {
743 name = "Audiobookshelf";
744 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
745 exec -- \
746 ${lib.getExe cfg.programs.chromium.package} \
747 --class=Audiobookshelf \
748 --app="https://audiobookshelf.yggdrasil.li" \
749 --user-data-dir=''${HOME}/.config/chromium-audiobookshelf
750 '');
751 icon = pkgs.fetchurl {
752 url = "https://www.audiobookshelf.org/Logo.png";
753 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
754 };
755 settings = {
756 StartupWMClass = "Audiobookshelf";
757 StartupNotify = "true";
758 };
759 };
760 thunderbird-lmu = {
761 name = "Thunderbird (LMU)";
762 exec = "thunderbird --name thunderbird -P lmu %U";
763 icon = "thunderbird";
764 genericName = "Email Client";
765 categories = [ "Network" "Chat" "Email" "Feed" "GTK" "News" ];
766 settings = {
767 StartupWMClass = "thunderbird";
768 StartupNotify = "true";
769 };
620 }; 770 };
621 }; 771 };
622 772
@@ -632,6 +782,48 @@ in {
632 color-scheme = "prefer-dark"; 782 color-scheme = "prefer-dark";
633 }; 783 };
634 }; 784 };
785
786 i18n.inputMethod = {
787 enable = true;
788 type = "fcitx5";
789 fcitx5 = {
790 waylandFrontend = true;
791 settings = {
792 globalOptions."Behavior/DisabledAddons" = {
793 "0" = "clipboard";
794 };
795 inputMethod = {
796 "Groups/0" = {
797 Name = "Default";
798 "Default Layout" = "us";
799 DefaultIM = "keyboard-us";
800 };
801 "Groups/0/Items/0" = {
802 Name = "keyboard-us";
803 Layout = "";
804 };
805 "GroupOrder"."0" = "Default";
806 };
807 addons = {
808 classicui.globalSection = {
809 "Vertical Candidate List" = "True";
810 Font = "Fira Sans 10";
811 MenuFont = "Fira Sans 10";
812 TrayFont = "Fira Sans Demi-Bold 10";
813 UseDarkTheme = "True";
814 };
815 quickphrase = {
816 globalSection = {
817 "Choose Modifier" = "None";
818 Spell = "True";
819 FallbackSpellLanguage = "en";
820 };
821 sections.TriggerKey."0" = "Super+E";
822 };
823 };
824 };
825 };
826 };
635 }; 827 };
636 }; 828 };
637} 829}
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index 183cb322..8ed3368a 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)
@@ -228,6 +228,7 @@ necessarily running."
228 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling)) 228 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling))
229 229
230(add-hook 'server-switch-hook 'install-emacsclient-wrapped-kill-buffer) 230(add-hook 'server-switch-hook 'install-emacsclient-wrapped-kill-buffer)
231(add-hook 'server-switch-hook #'raise-frame)
231 232
232(defun move-file (new-location) 233(defun move-file (new-location)
233 "Write this file to NEW-LOCATION, and delete the old one." 234 "Write this file to NEW-LOCATION, and delete the old one."
@@ -253,3 +254,8 @@ necessarily running."
253(bind-key "C-x C-m" #'move-file) 254(bind-key "C-x C-m" #'move-file)
254 255
255(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@")
259
260(define-key typst-ts-mode-map (kbd "C-<left>") nil)
261(define-key typst-ts-mode-map (kbd "C-<right>") nil)
diff --git a/accounts/gkleen@sif/firefox-chrome.css b/accounts/gkleen@sif/firefox-chrome.css
index 8900e2b9..726f1e4b 100644
--- a/accounts/gkleen@sif/firefox-chrome.css
+++ b/accounts/gkleen@sif/firefox-chrome.css
@@ -4,6 +4,21 @@
4 font-size:12px; 4 font-size:12px;
5} 5}
6 6
7#sidebar-main:has([expanded]) {
8 min-width:20em !important;
9 max-width:20em !important;
10}
11
12#sidebar, #sidebar-box {
13 min-width:35em !important;
14 max-width:35em !important;
15}
16
17#sidebar-box {
18 margin-right: var(--space-small);
19}
20
21/*
7#sidebar { 22#sidebar {
8 min-width:20em !important; 23 min-width:20em !important;
9 max-width:20em !important; 24 max-width:20em !important;
@@ -19,8 +34,21 @@
19} 34}
20 35
21#toolbar-menubar[inactive="true"] + #TabsToolbar { 36#toolbar-menubar[inactive="true"] + #TabsToolbar {
22 visibility: collapse !important; 37 visibility: collapse !important;
23} 38}
24 39
25#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; } 40#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; }
26#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; } 41#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; }
42*/
43
44.titlebar-buttonbox-container{ display: none; }
45#vertical-spacer { display: none; }
46#tabbrowser-tabs[orient="vertical"] .tab-background {
47 border-radius: var(--border-radius-small) !important;
48}
49hbox:has(> #tabs-newtab-button) {
50 display: none;
51}
52#sidebar-main .tools-and-extensions {
53 justify-content: space-around !important;
54}
diff --git a/accounts/gkleen@sif/libvirt/default.nix b/accounts/gkleen@sif/libvirt/default.nix
index 70ac22b9..86df9678 100644
--- a/accounts/gkleen@sif/libvirt/default.nix
+++ b/accounts/gkleen@sif/libvirt/default.nix
@@ -3,23 +3,31 @@
3with lib; 3with lib;
4with flakeInputs.nixVirt.lib; 4with flakeInputs.nixVirt.lib;
5 5
6{ 6let
7 libosinfo = id: with xml;
8 elem "libosinfo:libosinfo" [ (attr "xmlns:libosinfo" "http://libosinfo.org/xmlns/libvirt/domain/1.0") ]
9 [
10 (elem "libosinfo:os" [ (attr "id" id) ] [])
11 ];
12in {
7 config = { 13 config = {
8 virtualisation.libvirt = { 14 virtualisation.libvirt = {
9 enable = true; 15 enable = true;
16 swtpm.enable = true;
10 connections."qemu:///session" = { 17 connections."qemu:///session" = {
11 domains = [ 18 domains = [
12 { definition = domain.writeXML (updateManyAttrsByPath [ 19 { definition = domain.writeXML (recursiveUpdate (domain.templates.windows {
13 ] (recursiveUpdate (domain.templates.windows {
14 name = "lmmirzm-vmrz01"; 20 name = "lmmirzm-vmrz01";
15 uuid = "9e1dab2e-7986-4cb3-88af-6fad8969e15f"; 21 uuid = "9e1dab2e-7986-4cb3-88af-6fad8969e15f";
16 memory = { count = 16; unit = "GiB"; }; 22 memory = { count = 16; unit = "GiB"; };
17 storage_vol = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2"; 23 storage_vol = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2";
18 nvram_path = "/home/gkleen/.local/share/libvirt/lmmirzm-vmrz01.nvram"; 24 nvram_path = "/home/gkleen/.local/share/libvirt/lmmirzm-vmrz01.nvram";
19 virtio_drive = true;
20 virtio_video = true; 25 virtio_video = true;
21 install_virtio = false; 26 install_virtio = false;
22 }) { 27 }) {
28 metadata = [
29 (libosinfo "http://microsoft.com/win/11")
30 ];
23 qemu-commandline.env = [ 31 qemu-commandline.env = [
24 { name = "SPICE_DEBUG_ALLOW_MC"; value = "1"; } 32 { name = "SPICE_DEBUG_ALLOW_MC"; value = "1"; }
25 ]; 33 ];
@@ -38,7 +46,6 @@ with flakeInputs.nixVirt.lib;
38 devices.video.model.acceleration.accel3d = false; 46 devices.video.model.acceleration.accel3d = false;
39 devices.interface = { 47 devices.interface = {
40 model.type = "virtio"; 48 model.type = "virtio";
41 # model.type = "e1000e";
42 type = "bridge"; 49 type = "bridge";
43 mac.address = "52:54:00:b9:f3:ed"; 50 mac.address = "52:54:00:b9:f3:ed";
44 source.bridge = "rz-0971"; 51 source.bridge = "rz-0971";
@@ -59,7 +66,7 @@ with flakeInputs.nixVirt.lib;
59 } 66 }
60 ]; 67 ];
61 devices.tpm.model = "tpm-tis"; 68 devices.tpm.model = "tpm-tis";
62 })); 69 });
63 } 70 }
64 { definition = domain.writeXML (recursiveUpdate (domain.templates.linux { 71 { definition = domain.writeXML (recursiveUpdate (domain.templates.linux {
65 name = "vmrz02"; 72 name = "vmrz02";
@@ -73,6 +80,7 @@ with flakeInputs.nixVirt.lib;
73 { 80 {
74 readonly = true; 81 readonly = true;
75 type = "pflash"; 82 type = "pflash";
83 secure = false;
76 path = "${pkgs.OVMFFull.fd}/FV/OVMF_CODE.ms.fd"; 84 path = "${pkgs.OVMFFull.fd}/FV/OVMF_CODE.ms.fd";
77 }; 85 };
78 nvram = 86 nvram =
@@ -82,6 +90,9 @@ with flakeInputs.nixVirt.lib;
82 }; 90 };
83 bootmenu.enable = true; 91 bootmenu.enable = true;
84 }; 92 };
93 metadata = [
94 (libosinfo "http://ubuntu.com/ubuntu/20.04")
95 ];
85 qemu-commandline.env = [ 96 qemu-commandline.env = [
86 { name = "SPICE_DEBUG_ALLOW_MC"; value = "1"; } 97 { name = "SPICE_DEBUG_ALLOW_MC"; value = "1"; }
87 ]; 98 ];
@@ -136,6 +147,86 @@ with flakeInputs.nixVirt.lib;
136 }; 147 };
137 }); 148 });
138 } 149 }
150 { definition = domain.writeXML (recursiveUpdate (domain.templates.linux {
151 name = "vmrz03";
152 uuid = "1250a47c-3199-478d-8cf3-ea4a64c0567a";
153 memory = { count = 8; unit = "GiB"; };
154 storage_vol = "/home/gkleen/.local/share/libvirt/images/vmrz03.qcow2";
155 virtio_video = true;
156 }) {
157 os = {
158 loader =
159 {
160 readonly = true;
161 type = "pflash";
162 secure = false;
163 path = "${pkgs.OVMFFull.fd}/FV/OVMF_CODE.ms.fd";
164 };
165 nvram =
166 {
167 template = "${pkgs.OVMFFull.fd}/FV/OVMF_VARS.ms.fd";
168 path = "/home/gkleen/.local/share/libvirt/vmrz03.nvram";
169 };
170 bootmenu.enable = true;
171 };
172 metadata = [
173 (libosinfo "http://ubuntu.com/ubuntu/24.04")
174 ];
175 qemu-commandline.env = [
176 { name = "SPICE_DEBUG_ALLOW_MC"; value = "1"; }
177 ];
178 vcpu.count = 4;
179 cpu = {
180 mode = "host-model";
181 feature = [
182 { name = "vmx"; policy = "require"; }
183 ];
184 };
185 devices.graphics = {
186 listen.type = "address";
187 gl.enable = false;
188 };
189 devices.video.model.acceleration.accel3d = false;
190 devices.interface = [
191 {
192 model.type = "virtio";
193 type = "bridge";
194 mac.address = "52:54:00:a1:f7:8a";
195 source.bridge = "rz-0971";
196 }
197 {
198 model.type = "virtio";
199 type = "bridge";
200 mac.address = "52:54:00:8d:d3:0b";
201 source.bridge = "rz-2403";
202 link.state = "down";
203 }
204 ];
205 devices.channel = [
206 {
207 type = "unix";
208 target = { type = "virtio"; name = "org.qemu.guest_agent.0"; };
209 }
210 {
211 type = "spicevmc";
212 target = { type = "virtio"; name = "com.redhat.spice.0"; };
213 }
214 {
215 type = "spiceport";
216 target = { type = "virtio"; name = "org.spice-space.webdav.0"; };
217 source.channel = "org.spice-space.webdav.0";
218 }
219 ];
220 devices.tpm = {
221 model = "tpm-tis";
222 backend =
223 {
224 type = "emulator";
225 version = "2.0";
226 };
227 };
228 });
229 }
139 ]; 230 ];
140 pools = [ 231 pools = [
141 { definition = pool.writeXML { 232 { definition = pool.writeXML {
@@ -151,7 +242,8 @@ with flakeInputs.nixVirt.lib;
151 { definition = volume.writeXML { 242 { definition = volume.writeXML {
152 type = "file"; 243 type = "file";
153 name = "lmmirzm-vmrz01.qcow2"; 244 name = "lmmirzm-vmrz01.qcow2";
154 capacity = { count = 40; unit = "GB"; }; 245 allocation = { count = 0; };
246 capacity = { count = 256; unit = "GB"; };
155 target = { 247 target = {
156 path = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2"; 248 path = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2";
157 format.type = "qcow2"; 249 format.type = "qcow2";
@@ -162,6 +254,7 @@ with flakeInputs.nixVirt.lib;
162 { definition = volume.writeXML { 254 { definition = volume.writeXML {
163 type = "file"; 255 type = "file";
164 name = "vmrz02.qcow2"; 256 name = "vmrz02.qcow2";
257 allocation = { count = 0; };
165 capacity = { count = 256; unit = "GB"; }; 258 capacity = { count = 256; unit = "GB"; };
166 target = { 259 target = {
167 path = "/home/gkleen/.local/share/libvirt/images/vmrz02.qcow2"; 260 path = "/home/gkleen/.local/share/libvirt/images/vmrz02.qcow2";
@@ -170,6 +263,18 @@ with flakeInputs.nixVirt.lib;
170 }; 263 };
171 }; 264 };
172 } 265 }
266 { definition = volume.writeXML {
267 type = "file";
268 name = "vmrz03.qcow2";
269 allocation = { count = 0; };
270 capacity = { count = 256; unit = "GB"; };
271 target = {
272 path = "/home/gkleen/.local/share/libvirt/images/vmrz03.qcow2";
273 format.type = "qcow2";
274 features.lazy_refcounts = {};
275 };
276 };
277 }
173 ]; 278 ];
174 } 279 }
175 ]; 280 ];
diff --git a/accounts/gkleen@sif/niri.nix b/accounts/gkleen@sif/niri.nix
new file mode 100644
index 00000000..b30c5701
--- /dev/null
+++ b/accounts/gkleen@sif/niri.nix
@@ -0,0 +1,1005 @@
1{ config, hostConfig, pkgs, lib, utils, flakeInputs, ... }:
2let
3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6 sleaf = name: arg: kdl.node name [arg] [];
7
8 niri = cfg.package;
9 spawnTerminal = { command ? lib.getExe config.programs.zsh.package, extraArgs ? [] }:
10 [(lib.getExe config.programs.alacritty.package) "msg" "--socket" "/run/user/1000/alacritty.sock" "create-window"] ++ lib.toList extraArgs ++ ["--command" (lib.getExe' hostConfig.systemd.package "systemd-run") "--user" "--pty" "--same-dir" "--wait" "--collect" "--quiet" "--service-type=exec" "--expand-environment=no"] ++ lib.mapAttrsToList (k: v: "--property=Environment=${utils.escapeSystemdExecArg "${k}=${if v == null then "" else v}"}") execEnvironment ++ lib.toList command;
11
12 execEnvironment = {
13 NIXOS_OZONE_WL = "1";
14 QT_QPA_PLATFORM = "wayland";
15 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
16 GDK_BACKEND = "wayland";
17 SDL_VIDEODRIVER = "wayland";
18 DISPLAY = ":0";
19 ELECTRON_OZONE_PLATFORM_HINT = "auto";
20 SSH_ASKPASS_REQUIRE = "prefer";
21 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
22 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
23 GTK_IM_MODULE = null;
24 QT_IM_MODULE = null;
25 QT_IM_MODULES = null;
26 };
27
28 focus_or_spawn = pkgs.writeShellApplication {
29 name = "focus-or-spawn";
30 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
31 text = ''
32 window_select="$1"
33 shift
34 workspace_name="$1"
35 shift
36
37 workspaces_json="$(niri msg -j workspaces)"
38 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")"
39 # active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
40 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
41 if [[ $workspace_output != "$active_output" ]]; then
42 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
43 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
44 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
45 fi
46
47 while IFS=$'\n' read -r window_json; do
48 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
49 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
50 niri msg action focus-workspace-previous
51 else
52 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
53 niri msg action focus-workspace "$workspace_name"
54 else
55 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
56 fi
57 fi
58 exit 0
59 fi
60 done < <(niri msg -j windows | jq -c '.[]')
61
62 exec "$@"
63 '';
64 };
65 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
66
67 with_adjacent_workspace = pkgs.writeShellApplication {
68 name = "with-adjacent-workspace";
69 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
70 text = ''
71 blacklist="$1"
72 shift
73 direction="$1"
74 shift
75 action="$1"
76 shift
77
78 workspaces_json="$(niri msg -j workspaces)"
79 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
80 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
81 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
82
83 jq_script='map(select('
84 case "$direction" in
85 down)
86 # shellcheck disable=SC2016
87 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
88 up)
89 # shellcheck disable=SC2016
90 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
91 esac
92 # shellcheck disable=SC2016
93 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
94 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
95 jq_script=''${jq_script}' | .[0]'
96
97 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
98 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
99 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
100 '';
101 };
102 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
103 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
104 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
105
106 with_unnamed_workspace = pkgs.writeShellApplication {
107 name = "with-unnamed-workspace";
108 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
109 text = ''
110 action="$1"
111 shift
112
113 workspaces_json="$(niri msg -j workspaces)"
114 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
115 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
116
117 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
118 workspace_json="$(jq -c --arg active_output "$active_output" --argjson history "$history_json" 'map(select(.output == $active_output and .name == null)) | map({"value": ., "history_idx": ((. as $workspace | ($history[$active_output] | index($workspace | .id))) as $active_idx | if $active_idx then $active_idx else ($history[$active_output] | length) + 1 end)}) | sort_by(.history_idx, .value.idx) | map(.value) | .[0]' <<<"$workspaces_json")"
119 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
120 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
121 '';
122 };
123 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
124
125 with_empty_unnamed_workspace = pkgs.writeShellApplication {
126 name = "with-empty-unnamed-workspace";
127 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
128 text = ''
129 action="$1"
130 shift
131
132 workspaces_json="$(niri msg -j workspaces)"
133 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
134 target_workspace_id="$(jq --argjson active_output "$active_output" 'map(select(.active_window_id == null and .name == null and .output == $active_output)) | sort_by(.idx) | .[0].id' <<<"$workspaces_json")"
135 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
136 '';
137 };
138 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
139
140 with_select_window = pkgs.writeShellApplication {
141 name = "with-select-window";
142 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
143 text = ''
144 window_select="$1"
145 shift
146 action="$1"
147 shift
148
149 windows_json="$(niri msg -j windows)"
150 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
151 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)"
152 # shellcheck disable=SC2016
153 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
154
155 [[ -z "$window_json" ]] && exit 1
156
157 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
158 '';
159 };
160 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
161
162 with_predicate_window = pred: pkgs.writeShellApplication {
163 name = "with-predicate-window";
164 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
165 text = ''
166 action="$1"
167 shift
168
169 windows_json="$(niri msg -j windows)"
170 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
171
172 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
173
174 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
175 '';
176 };
177
178 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
179 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
180in {
181 options = {
182 programs.niri.scratchspaces = lib.mkOption {
183 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
184 options = {
185 name = lib.mkOption {
186 type = lib.types.str;
187 };
188 match = lib.mkOption {
189 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
190 default = [];
191 };
192 exclude = lib.mkOption {
193 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
194 default = [];
195 };
196 windowRuleExtra = lib.mkOption {
197 type = kdl.types.kdl-nodes;
198 default = [];
199 };
200 key = lib.mkOption {
201 type = lib.types.nullOr lib.types.str;
202 default = null;
203 };
204 moveKey = lib.mkOption {
205 type = lib.types.nullOr lib.types.str;
206 default = let
207 keys = lib.splitString "+" config.key;
208 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
209 (lib.take (lib.length keys - 1) keys)
210 ["Shift"]
211 (lib.takeEnd 1 keys)
212 ]);
213 in if config.key == null then null else defMoveKey;
214 };
215 spawn = lib.mkOption {
216 type = lib.types.nullOr (lib.types.listOf lib.types.str);
217 default = null;
218 };
219 app-id = lib.mkOption {
220 type = lib.types.nullOr lib.types.str;
221 default = null;
222 };
223 selector = lib.mkOption {
224 type = lib.types.nullOr lib.types.str;
225 default = null;
226 };
227 };
228
229 config = lib.mkMerge [
230 (lib.mkIf (config.app-id != null) {
231 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
232 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
233 })
234 ];
235 }));
236 default = [];
237 };
238 };
239
240 config = {
241 home.packages = [ pkgs.xwayland-satellite-unstable ];
242
243 systemd.user.sockets.niri-workspace-history = {
244 Socket = {
245 ListenStream = "%t/niri-workspace-history.sock";
246 SocketMode = "0600";
247 };
248 };
249 systemd.user.services.niri-workspace-history = {
250 Unit = {
251 BindsTo = [ "niri.service" ];
252 After = [ "niri.service" ];
253 };
254 Install = {
255 WantedBy = [ "niri.service" ];
256 };
257 Service = {
258 Type = "simple";
259 Sockets = [ "niri-workspace-history.socket" ];
260 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
261 import os
262 import socket
263 import json
264 # import sys
265 from collections import defaultdict
266 from threading import Thread, Lock
267 from socketserver import StreamRequestHandler, ThreadingTCPServer
268 from contextlib import contextmanager
269 from io import TextIOWrapper
270
271
272 @contextmanager
273 def detaching(thing):
274 try:
275 yield thing
276 finally:
277 thing.detach()
278
279
280 workspace_history = defaultdict(list)
281 history_lock = Lock()
282
283
284 def monitor_niri():
285 workspaces = list()
286
287 def focus_workspace(output, workspace):
288 with history_lock:
289 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
290 # print(json.dumps(workspace_history), file=sys.stderr)
291
292 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
293 sock.connect(os.environ["NIRI_SOCKET"])
294 sock.send(b"\"EventStream\"\n")
295 for line in sock.makefile(buffering=1, encoding='utf-8'):
296 if line_json := json.loads(line):
297 if "WorkspacesChanged" in line_json:
298 workspaces = line_json["WorkspacesChanged"]["workspaces"]
299 for ws in workspaces:
300 if ws["is_focused"]:
301 focus_workspace(ws["output"], ws["id"])
302 if "WorkspaceActivated" in line_json:
303 for ws in workspaces:
304 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
305 continue
306 focus_workspace(ws["output"], ws["id"])
307 break
308
309
310 class RequestHandler(StreamRequestHandler):
311 def handle(self):
312 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
313 with history_lock:
314 json.dump(workspace_history, out)
315
316
317 class Server(ThreadingTCPServer):
318 def __init__(self):
319 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
320 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
321
322
323 def run_server():
324 with Server() as server:
325 server.serve_forever()
326
327
328 niri = Thread(target=monitor_niri)
329 niri.daemon = True
330 niri.start()
331
332 server_thread = Thread(target=run_server)
333 server_thread.daemon = True
334 server_thread.start()
335
336 while True:
337 server_thread.join(timeout=0.5)
338 niri.join(timeout=0.5)
339
340 if not (niri.is_alive() and server_thread.is_alive()):
341 break
342 '';
343 };
344 };
345 systemd.user.services.niri-workspace-sort = {
346 Unit = {
347 BindsTo = [ "niri.service" ];
348 After = [ "niri.service" ];
349 };
350 Install = {
351 WantedBy = [ "niri.service" ];
352 };
353 Service = {
354 Type = "simple";
355 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
356 import os
357 import sys
358 import socket
359 import json
360
361 outputs = None
362 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
363
364
365 class Niri(socket.socket):
366 def __init__(self):
367 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
368 super().connect(os.environ["NIRI_SOCKET"])
369 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
370
371 def cmd(self, obj):
372 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
373
374 def event_stream(self):
375 self.cmd("EventStream")
376 return self.fh
377
378
379 with Niri() as niri, Niri().event_stream() as niri_stream:
380 for line in niri_stream:
381 workspaces = None
382 if line_json := json.loads(line):
383 if "WorkspacesChanged" in line_json:
384 workspaces = line_json["WorkspacesChanged"]["workspaces"]
385
386 if workspaces is None:
387 continue
388
389 old_outputs = outputs
390 outputs = {ws["output"] for ws in workspaces}
391 if old_outputs is None:
392 print("Initial outputs: {}".format(outputs), file=sys.stderr)
393 continue
394
395 new_outputs = outputs - old_outputs
396 if not new_outputs:
397 continue
398 print("New outputs: {}".format(new_outputs), file=sys.stderr)
399
400 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
401 target_output = next(iter(outputs - set(only.keys())))
402 if not target_output:
403 continue
404 for ws in relevant_workspaces:
405 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
406 if ws["output"] not in set(only.keys()):
407 continue
408 if ws_ident in only[ws["output"]]:
409 continue
410
411 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
412 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
413 '';
414 Restart = "on-failure";
415 RestartSec = 10;
416 };
417 };
418
419 programs.niri.scratchspaces = [
420 { name = "pwctl";
421 key = "Mod+Control+A";
422 spawn = ["pwvucontrol"];
423 app-id = "com.saivert.pwvucontrol";
424 }
425 { name = "kpxc";
426 exclude = [
427 { title = "^Unlock Database.*"; }
428 { title = "^Access Request.*"; }
429 { title = ".*Passkey credentials$"; }
430 ];
431 windowRuleExtra = with kdl; [
432 (sleaf "open-focused" false)
433 ];
434 key = "Mod+Control+P";
435 app-id = "org.keepassxc.KeePassXC";
436 spawn = [ "keepassxc" ];
437 }
438 { name = "bmgr";
439 key = "Mod+Control+B";
440 app-id = ".blueman-manager-wrapped";
441 spawn = [ "blueman-manager" ];
442 }
443 { name = "term";
444 key = "Mod+Control+Return";
445 app-id = "Alacritty-scratch";
446 spawn = spawnTerminal { extraArgs = ["--class" "Alacritty-scratch"]; };
447 }
448 { name = "edit";
449 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
450 key = "Mod+Control+E";
451 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
452 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
453 }
454 { name = "eff";
455 key = "Mod+Control+O";
456 app-id = "com.github.wwmm.easyeffects";
457 spawn = [ "easyeffects" ];
458 }
459 { name = "time";
460 key = "Mod+Control+K";
461 app-id = "chrome-kimai.yggdrasil.li__-Default";
462 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
463 interpreter = pkgs.runtimeShell;
464 inputs = [ pkgs.dex ];
465 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
466 } ''
467 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
468 '')) ];
469 windowRuleExtra = with kdl; [
470 (sleaf "block-out-from" "screencast")
471 ];
472 }
473 ];
474 programs.niri.config =
475 let
476 inherit (kdl) node plain leaf flag;
477 optional-node = cond: v:
478 if cond
479 then v
480 else null;
481 opt-props = lib.filterAttrs (lib.const (value: value != null));
482 normalize-nodes = nodes: lib.remove null (lib.flatten nodes);
483 in
484 normalize-nodes [
485 (flag "prefer-no-csd")
486
487 (sleaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
488
489 (plain "hotkey-overlay" [
490 (flag "skip-at-startup")
491 ])
492
493 (plain "input" [
494 (plain "keyboard" [
495 (sleaf "repeat-delay" 300)
496 (sleaf "repeat-rate" 50)
497
498 (plain "xkb" [
499 (sleaf "layout" "us,us")
500 (sleaf "variant" "dvp,")
501 (sleaf "options" "compose:caps,grp:win_space_toggle")
502 ])
503 ])
504
505 (flag "workspace-auto-back-and-forth")
506 # (sleaf "focus-follows-mouse" {})
507 # (flag "warp-mouse-to-focus")
508
509 # (plain "touchpad" [ (flag "off") ])
510 (plain "trackball" [
511 (sleaf "scroll-method" "on-button-down")
512 (sleaf "scroll-button" 278)
513 ])
514 (plain "touch" [
515 (sleaf "map-to-output" "eDP-1")
516 ])
517 (plain "tablet" [
518 (sleaf "map-to-output" "eDP-1")
519 ])
520 ])
521
522 (plain "gestures" [
523 (plain "hot-corners" [(flag "off")])
524 ])
525
526 (plain "environment" (lib.mapAttrsToList sleaf execEnvironment))
527
528 (node "output" ["eDP-1"] [
529 (sleaf "scale" 1.5)
530 (sleaf "position" { x = 0; y = 0; })
531 ])
532 (node "output" ["Ancor Communications Inc ASUS PB287Q 0x0000DD9B"] [
533 (sleaf "scale" 1.5)
534 (sleaf "position" { x = 2560; y = 0; })
535 ])
536 (node "output" ["HP Inc. HP 727pu CN4417143K"] [
537 (sleaf "mode" "2560x1440@119.998")
538 (sleaf "scale" 1)
539 (sleaf "position" { x = 2560; y = 0; })
540 (flag "variable-refresh-rate")
541 ])
542
543 (plain "debug" [
544 (sleaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
545 ])
546
547 (plain "animations" [
548 (sleaf "slowdown" 0.5)
549 (plain "workspace-switch" [(flag "off")])
550 ])
551
552 (plain "layout" [
553 (sleaf "gaps" 8)
554 (plain "struts" [
555 (sleaf "left" 26)
556 (sleaf "right" 26)
557 (sleaf "top" 0)
558 (sleaf "bottom" 0)
559 ])
560 (plain "border" [
561 (sleaf "width" 2)
562 (sleaf "active-gradient" {
563 from = "hsla(195 100% 45% 1)";
564 to = "hsla(155 100% 37.5% 1)";
565 angle = 29;
566 relative-to = "workspace-view";
567 })
568 (sleaf "inactive-gradient" {
569 from = "hsla(0 0% 27.7% 1)";
570 to = "hsla(0 0% 23% 1)";
571 angle = 29;
572 relative-to = "workspace-view";
573 })
574 ])
575 (plain "focus-ring" [
576 (flag "off")
577 ])
578
579 (plain "preset-column-widths" (map (prop: sleaf "proportion" prop) [
580 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
581 ]))
582 (plain "default-column-width" [ (sleaf "proportion" (1. / 2.)) ])
583 (plain "preset-window-heights" (map (prop: sleaf "proportion" prop) [
584 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
585 ]))
586
587 (flag "always-center-single-column")
588
589 (plain "tab-indicator" [
590 (sleaf "gap" 4)
591 (sleaf "width" 8)
592 (sleaf "gaps-between-tabs" 4)
593 (flag "place-within-column")
594 (sleaf "length" { total-proportion = 1.; })
595 (sleaf "active-gradient" {
596 from = "hsla(195 100% 60% 0.75)";
597 to = "hsla(155 100% 50% 0.75)";
598 angle = 29;
599 relative-to = "workspace-view";
600 })
601 (sleaf "inactive-gradient" {
602 from = "hsla(0 0% 42% 0.66)";
603 to = "hsla(0 0% 35% 0.66)";
604 angle = 29;
605 relative-to = "workspace-view";
606 })
607 ])
608 ])
609
610 (plain "cursor" [
611 (flag "hide-when-typing")
612 ])
613
614 (map (name:
615 (node "workspace" [name] [
616 (sleaf "open-on-output" "eDP-1")
617 ])
618 ) (map ({name, ...}: name) cfg.scratchspaces))
619 (map (name:
620 (sleaf "workspace" name)
621 ) ["comm" "web" "vid" "bmr"])
622
623 (plain "window-rule" [
624 (sleaf "clip-to-geometry" true)
625 ])
626
627 (plain "window-rule" [
628 (sleaf "match" { is-floating = true; })
629 (sleaf "geometry-corner-radius" 8)
630 (plain "shadow" [ (flag "on") ])
631 ])
632
633 (plain "window-rule" [
634 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
635 (sleaf "block-out-from" "screencast")
636 ])
637 (plain "window-rule" (normalize-nodes [
638 (map (title:
639 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
640 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
641 (sleaf "open-focused" true)
642 (sleaf "open-floating" true)
643 ]))
644
645 (map ({ name, match, exclude, windowRuleExtra, ... }:
646 (optional-node (match != []) (plain "window-rule" (normalize-nodes [
647 (map (sleaf "match") match)
648 (map (sleaf "exclude") exclude)
649 (sleaf "open-on-workspace" name)
650 (sleaf "open-maximized" true)
651 windowRuleExtra
652 ])))
653 ) cfg.scratchspaces)
654
655 (plain "window-rule" [
656 (sleaf "match" { app-id = "^emacs$"; })
657 (sleaf "match" { app-id = "^firefox$"; })
658 (sleaf "match" { app-id = "^gnucash$"; })
659 (sleaf "exclude" { app-id = "^gnucash$"; title = "^(Securities|Price Database)$"; })
660 (plain "default-column-width" [(sleaf "proportion" (2. / 3.))])
661 ])
662 (plain "window-rule" [
663 (sleaf "match" { app-id = "^Alacritty$"; })
664 (sleaf "match" { app-id = "^Alacritty-play$"; })
665 (plain "default-column-width" [(sleaf "proportion" (1. / 3.))])
666 ])
667
668 (plain "window-rule" [
669 (sleaf "match" { app-id = "^thunderbird$"; })
670 (sleaf "match" { app-id = "^Element$"; })
671 (sleaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
672 (sleaf "open-on-workspace" "comm")
673 ])
674 (plain "window-rule" [
675 (sleaf "match" { app-id = "^firefox$"; })
676 (sleaf "open-on-workspace" "web")
677 (sleaf "open-maximized" true)
678 ])
679 (plain "window-rule" [
680 (sleaf "match" { app-id = "^mpv$"; })
681 (sleaf "open-on-workspace" "vid")
682 (plain "default-column-width" [(sleaf "proportion" 1.)])
683 ])
684 (plain "window-rule" [
685 (sleaf "match" { app-id = "^Alacritty-play$"; })
686 (sleaf "open-on-workspace" "vid")
687 (sleaf "open-focused" false)
688 ])
689 (plain "window-rule" [
690 (sleaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
691 (sleaf "match" { app-id = "^YouTube Music Desktop App$"; })
692 (sleaf "open-on-workspace" "vid")
693 ])
694 (plain "window-rule" [
695 (sleaf "match" { app-id = "^pdfpc$"; })
696 (plain "default-column-width" [(sleaf "proportion" 1.)])
697 ])
698 (plain "window-rule" [
699 (sleaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
700 (plain "default-column-width" [(sleaf "proportion" 1.)])
701 (sleaf "open-fullscreen" true)
702 (sleaf "open-on-workspace" "bmr")
703 (sleaf "open-focused" false)
704 ])
705 (plain "window-rule" (normalize-nodes [
706 (map (sleaf "match") [
707 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
708 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
709 { app-id = "^xdg-desktop-portal-gtk$"; }
710 ])
711 (sleaf "open-floating" true)
712 ]))
713 (plain "window-rule" [
714 (sleaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
715 (sleaf "match" { app-id = "^evince$"; })
716 (sleaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
717 (sleaf "default-column-display" "tabbed")
718 ])
719 (plain "window-rule" [
720 (sleaf "match" { is-window-cast-target = true; })
721 (plain "border" [
722 (sleaf "width" 2)
723 (sleaf "active-gradient" {
724 from = "hsla(20 100% 45% 1)";
725 to = "hsla(340 100% 37.5% 1)";
726 angle = 29;
727 relative-to = "workspace-view";
728 })
729 (sleaf "inactive-gradient" {
730 from = "hsla(20 50% 27.7% 1)";
731 to = "hsla(340 50% 23% 1)";
732 angle = 29;
733 relative-to = "workspace-view";
734 })
735 ])
736 ])
737
738 (plain "layer-rule" [
739 (sleaf "match" { namespace = "^notifications$"; })
740 (sleaf "match" { namespace = "^bar$"; })
741 (sleaf "match" { namespace = "^launcher$"; })
742 (sleaf "block-out-from" "screencast")
743 ])
744
745 (plain "recent-windows" [
746 (flag "off")
747 ])
748
749 (plain "binds"
750 (let
751 bind = name: cfg: node name [(lib.removeAttrs cfg ["action"])] (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
752
753 move-column-to-workspace = kdl.magic-leaf "move-column-to-workspace";
754 screenshot = kdl.magic-leaf "screenshot";
755 screenshot-window = kdl.magic-leaf "screenshot-window";
756 screenshot-screen = kdl.magic-leaf "screenshot-screen";
757 in
758 normalize-nodes [
759 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
760 "Mod+Slash".action = show-hotkey-overlay;
761
762 "Mod+Return".action = spawn (spawnTerminal {});
763 "Mod+Shift+Return".action = spawn (spawnTerminal {
764 command = lib.getExe config.programs.nushell.package;
765 });
766 "Mod+Q".action = close-window;
767 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
768 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
769
770 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
771 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
772 name = "queue-yt-dlp";
773 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
774 text = ''
775 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
776 '';
777 }));
778 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
779 name = "queue-yt-dlp";
780 runtimeInputs = with pkgs; [ wl-clipboard-rs ];
781 text = ''
782 exec -- ${lib.concatStringsSep " " (spawnTerminal {
783 extraArgs = [ "--class" "Alacritty-play" "--working-directory" "\"$HOME\"/media" ];
784 command = ["mpv" "\"$(wl-paste)\""];
785 })}
786 '';
787 }));
788 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
789
790 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
791 name = "qalc-fuzzel";
792 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
793 text = ''
794 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
795 prev() {
796 FOUND=false
797 while IFS= read -r line; do
798 [[ -n "$line" ]] || continue
799 FOUND=true
800 echo "$line"
801 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)
802 $FOUND || echo
803 }
804 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
805 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
806 QALC_RES="$FUZZEL_RES"
807 QALC_RET=0
808 else
809 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
810 QALC_RET=$?
811 fi
812 [[ -n "$QALC_RES" ]] || exit 1
813 EXISTING=false
814 set +o pipefail
815 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
816 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
817 set -o pipefail
818 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
819 set +o pipefail
820 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
821 set -o pipefail
822 cat >"$RES_FILE" <<<"$QALC_RES"
823 fi
824 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
825 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
826 notify-send "$QALC_RES"
827 '';
828 }));
829 "Mod+Shift+U".action = spawn (spawnTerminal {
830 command = lib.getExe pkgs.libqalculate;
831 });
832 # "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
833 # name = "emoji-fuzzel";
834 # runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
835 # text = ''
836 # FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
837 # [[ -n "$FUZZEL_RES" ]] || exit 1
838 # wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
839 # '';
840 # }));
841 "Print".action = screenshot;
842 "Control+Print".action = screenshot-window;
843 "Shift+Print".action = screenshot-screen;
844 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
845 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
846
847 "Mod+Escape" = {
848 allow-inhibiting = false;
849 action = toggle-keyboard-shortcuts-inhibit;
850 };
851
852 "Mod+H".action = focus-column-left;
853 "Mod+T".action = focus-window-down;
854 "Mod+N".action = focus-window-up;
855 "Mod+S".action = focus-column-right;
856
857 "Mod+Shift+H".action = move-column-left;
858 "Mod+Shift+T".action = move-window-down;
859 "Mod+Shift+N".action = move-window-up;
860 "Mod+Shift+S".action = move-column-right;
861
862 "Mod+Control+H".action = focus-monitor-left;
863 "Mod+Control+T".action = focus-monitor-down;
864 "Mod+Control+N".action = focus-monitor-up;
865 "Mod+Control+S".action = focus-monitor-right;
866
867 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
868 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
869 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
870 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
871
872 "Mod+G".action = focus-adjacent-workspace "down";
873 "Mod+C".action = focus-adjacent-workspace "up";
874
875 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
876 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
877
878 "Mod+Shift+Control+G".action = move-workspace-down;
879 "Mod+Shift+Control+C".action = move-workspace-up;
880
881 "Mod+ParenLeft".action = focus-workspace "comm";
882 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
883
884 "Mod+ParenRight".action = focus-workspace "web";
885 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
886
887 "Mod+BraceRight".action = focus-workspace "read";
888 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
889
890 "Mod+BraceLeft".action = focus-workspace "mon";
891 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
892
893 "Mod+Asterisk".action = focus-workspace "vid";
894 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
895
896 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
897 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
898
899 "Mod+M".action = consume-or-expel-window-left;
900 "Mod+W".action = consume-or-expel-window-right;
901
902 "Mod+Shift+M".action = toggle-column-tabbed-display;
903
904 "Mod+L".action = maximize-window-to-edges;
905 "Mod+R".action = switch-preset-column-width;
906 "Mod+Shift+R".action = maximize-column;
907 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
908 "Mod+F".action = center-column;
909 "Mod+Shift+F".action = toggle-windowed-fullscreen;
910 "Mod+Ctrl+Shift+F".action = fullscreen-window;
911
912 "Mod+V".action = switch-focus-between-floating-and-tiling;
913 "Mod+Shift+V".action = toggle-window-floating;
914
915 "Mod+Left".action = set-column-width "-10%";
916 "Mod+Down".action = set-window-height "-10%";
917 "Mod+Up".action = set-window-height "+10%";
918 "Mod+Right".action = set-column-width "+10%";
919
920 "Mod+Shift+Z" = {
921 action = power-off-monitors;
922 allow-when-locked = true;
923 };
924 "Mod+Shift+E".action = quit;
925
926 # "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
927 # "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
928 # "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
929 # "Mod+Comma".action = spawn makoctl "restore";
930
931 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
932 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
933
934 "Mod+X".action = set-dynamic-cast-window;
935 "Mod+Shift+X".action = set-dynamic-cast-monitor;
936 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
937
938 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
939 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
940
941 "Mod+K".action = spawn (toString (pkgs.resholve.writeScript "worktime-ui" {
942 interpreter = pkgs.runtimeShell;
943 inputs = [ pkgs.worktime config.programs.quickshell.package ];
944 execer = [
945 "cannot:${lib.getExe' config.programs.quickshell.package "qs"}"
946 ];
947 } ''
948 worktime-ui && qs ipc call Worktime refresh
949 ''));
950 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
951 }))
952 (lib.mapAttrsToList (name: cfg: node name [(lib.removeAttrs cfg ["action"])] [cfg.action]) (let
953 shell = obj: leaf "send-unix" [
954 { path = ''''${XDG_RUNTIME_DIR}/shell.sock''; }
955 (builtins.toJSON obj + "\n")
956 ];
957 in {
958 "XF86AudioRaiseVolume" = {
959 allow-when-locked = true;
960 action = shell { Volume.volume = "up"; };
961 };
962 "XF86AudioLowerVolume" = {
963 allow-when-locked = true;
964 action = shell { Volume.volume = "down"; };
965 };
966 "XF86AudioMute" = {
967 allow-when-locked = true;
968 action = shell { Volume.muted = "toggle"; };
969 };
970 "XF86AudioMicMute" = {
971 allow-when-locked = true;
972 action = shell { Volume."mic-muted" = "toggle"; };
973 };
974 "XF86MonBrightnessUp" = {
975 action = shell { Brightness = "up"; };
976 allow-when-locked = true;
977 };
978 "XF86MonBrightnessDown" = {
979 action = shell { Brightness = "down"; };
980 allow-when-locked = true;
981 };
982 "Mod+Shift+L".action = shell { LockSession = {}; };
983 "Mod+Shift+Minus" = {
984 action = shell { Suspend = {}; };
985 allow-when-locked = true;
986 };
987 "Mod+Shift+Control+Minus" = {
988 action = shell { Hibernate = {}; };
989 allow-when-locked = true;
990 };
991 "Mod+Shift+P" = {
992 action = shell { Mpris = { PauseAll = {}; }; };
993 allow-when-locked = true;
994 };
995 "Mod+Semicolon".action = shell { Notifications = { DismissGroup = {}; }; };
996 "Mod+Shift+Semicolon".action = shell { Notifications = { DismissAll = {}; }; };
997 "Mod+Print".action = shell { ScreenRecord = { Toggle = {}; }; };
998 }))
999 (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)
1000 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = move-column-to-workspace name; } else null) cfg.scratchspaces)
1001 ]
1002 ))
1003 ];
1004 };
1005}
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
deleted file mode 100644
index 0c4a58e5..00000000
--- a/accounts/gkleen@sif/niri/default.nix
+++ /dev/null
@@ -1,536 +0,0 @@
1{ config, hostConfig, pkgs, lib, ... }:
2let
3 niri = config.programs.niri.package;
4 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl";
6 volumectl = lib.getExe' config.services.avizo.package "volumectl";
7 makoctl = lib.getExe' config.services.mako.package "makoctl";
8 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
10
11 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn";
13 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
14 text = ''
15 window_select="$1"
16 shift
17 workspace_name="$1"
18 shift
19
20 workspaces_json="$(niri msg -j workspaces)"
21 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")"
22 active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
23 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
24 if [[ $workspace_output != "$active_output" ]]; then
25 niri msg action move-workspace-to-monitor --output "$active_output" "$workspace_name"
26 socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
27 niri msg action move-workspace-to-index --index 1 "$workspace_name"
28 fi
29
30 while IFS=$'\n' read -r window_json; do
31 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
32 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
33 exit 0
34 fi
35 done < <(niri msg -j windows | jq -c '.[]')
36
37 exec "$@"
38 '';
39 };
40 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
41 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
42
43 with_adjacent_workspace = pkgs.writeShellApplication {
44 name = "with-adjacent-workspace";
45 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
46 text = ''
47 blacklist="$1"
48 shift
49 direction="$1"
50 shift
51 action="$1"
52 shift
53
54 workspaces_json="$(niri msg -j workspaces)"
55 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
56 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
57 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
58
59 jq_script='map(select('
60 case "$direction" in
61 down)
62 # shellcheck disable=SC2016
63 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
64 up)
65 # shellcheck disable=SC2016
66 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
67 esac
68 # shellcheck disable=SC2016
69 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
70 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
71 jq_script=''${jq_script}' | .[0]'
72
73 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
74 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
75 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
76 '';
77 };
78 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|kpxc|bmgr|edit|term$";
79 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
80 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
81
82 with_unnamed_workspace = pkgs.writeShellApplication {
83 name = "with-unnamed-workspace";
84 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
85 text = ''
86 action="$1"
87 shift
88
89 workspaces_json="$(niri msg -j workspaces)"
90 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
91 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
92
93 workspace_json="$(jq -c --arg active_output "$active_output" 'map(select(.output == $active_output and .name == null)) | sort_by(.idx) | .[0]' <<<"$workspaces_json")"
94 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
95 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
96 '';
97 };
98 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
99
100 with_select_window = pkgs.writeShellApplication {
101 name = "with-unnamed-workspace";
102 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
103 text = ''
104 window_select="$1"
105 shift
106 action="$1"
107 shift
108
109 windows_json="$(niri msg -j windows)"
110 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
111 window="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\t\(.id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu)"
112 window_id="$(awk -F $'\t' '{print $2}' <<<"$window")"
113 window_json="$(jq -r --arg window_id "$window_id" '.[] | select(.id == ($window_id | tonumber))' <<<"$windows_json")"
114
115 [[ -z "$window_json" ]] && exit 1
116
117 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
118 '';
119 };
120 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
121in {
122 imports = [
123 ./waybar.nix
124 ./mako.nix
125 ];
126
127 config = {
128 systemd.user.services.xwayland-satellite = {
129 Unit = {
130 BindsTo = [ "graphical-session.target" ];
131 PartOf = [ "graphical-session.target" ];
132 After = [ "graphical-session.target" ];
133 Requisite = [ "graphical-session.target" ];
134 };
135 Service = {
136 Type = "notify";
137 NotifyAccess = "all";
138 Environment = [ "DISPLAY=:0" ];
139 ExecStart = ''${lib.getExe pkgs.xwayland-satellite-unstable} ''${DISPLAY}'';
140 ExecStartPre = "${systemctl} --user import-environment DISPLAY";
141 StandardOutput = "journal";
142 };
143 Install = {
144 WantedBy = [ "graphical-session.target" ];
145 };
146 };
147
148 services.swayidle = {
149 events = [
150 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
151 ];
152 timeouts = [
153 { timeout = 300;
154 command = "${lib.getExe niri} msg action power-off-monitors";
155 }
156 ];
157 };
158
159 programs.niri.settings = {
160 prefer-no-csd = true;
161 screenshot-path = "${config.home.homeDirectory}/screenshots";
162
163 hotkey-overlay.skip-at-startup = true;
164
165 input = {
166 keyboard.xkb = {
167 layout = "us,us";
168 variant = "dvp,";
169 options = "compose:caps,grp:win_space_toggle";
170 };
171
172 workspace-auto-back-and-forth = true;
173 # focus-follows-mouse.enable = true;
174 warp-mouse-to-focus = true;
175 };
176
177 outputs = {
178 "eDP-1" = {
179 scale = 1.5;
180 position = { x = 0; y = 0; };
181 };
182 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
183 scale = 1.5;
184 position = { x = 2560; y = 0; };
185 };
186 };
187
188 environment = {
189 NIXOS_OZONE_WL = "1";
190 QT_QPA_PLATFORM = "wayland";
191 GDK_BACKEND = "wayland";
192 SDL_VIDEODRIVER = "wayland";
193 };
194
195 layout = {
196 gaps = 8;
197 struts = { left = 0; right = 0; top = 0; bottom = 0; };
198 focus-ring = {
199 width = 2;
200 active.gradient = {
201 from = "hsla(195 100% 60% 0.75)";
202 to = "hsla(155 100% 50% 0.75)";
203 angle = 29;
204 relative-to = "workspace-view";
205 };
206 inactive.gradient = {
207 from = "hsla(0 0% 42% 0.66)";
208 to = "hsla(0 0% 35% 0.66)";
209 angle = 29;
210 relative-to = "workspace-view";
211 };
212 };
213
214 preset-column-widths = [
215 { proportion = 1. / 4.; }
216 { proportion = 1. / 3.; }
217 { proportion = 1. / 2.; }
218 { proportion = 2. / 3.; }
219 { proportion = 3. / 4.; }
220 ];
221 default-column-width.proportion = 1. / 2.;
222 preset-window-heights = [
223 { proportion = 1. / 3.; }
224 { proportion = 1. / 2.; }
225 { proportion = 2. / 3.; }
226 { proportion = 1.; }
227 ];
228
229 always-center-single-column = true;
230 };
231
232 cursor.hide-when-typing = true;
233
234 workspaces = {
235 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
236 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
237 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
238 "004" = { name = "term"; open-on-output = "eDP-1"; };
239 "005" = { name = "edit"; open-on-output = "eDP-1"; };
240 "101".name = "comm";
241 "102".name = "web";
242 # "104".name = "read";
243 # "105".name = "mon";
244 "110".name = "vid";
245 };
246
247 window-rules = [
248 # {
249 # geometry-corner-radius =
250 # let
251 # allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
252 # in allCorners 4.;
253 # clip-to-geometry = true;
254 # }
255 {
256 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
257 open-on-workspace = "pwctl";
258 }
259 {
260 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
261 open-on-workspace = "bmgr";
262 }
263 {
264 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
265 excludes = [
266 { title = "^Unlock Database.*"; }
267 { title = "^Access Request.*"; }
268 { title = ".*Passkey credentials$"; }
269 ];
270 open-on-workspace = "kpxc";
271 open-focused = false;
272 }
273 {
274 matches = [
275 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
276 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
277 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
278 ];
279 open-focused = true;
280 }
281 {
282 matches = [ { app-id = "^kitty-scratch$"; } ];
283 open-on-workspace = "term";
284 }
285 {
286 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
287 open-on-workspace = "edit";
288 }
289 {
290 matches = [
291 { app-id = "^thunderbird$"; }
292 { app-id = "^Element$"; }
293 ];
294 open-on-workspace = "comm";
295 }
296 {
297 matches = [ { app-id = "^firefox$"; } ];
298 open-on-workspace = "web";
299 }
300 # {
301 # matches = [
302 # { app-id = "^evince$"; }
303 # { app-id = "^imv$"; }
304 # { app-id = "^org\.pwmt\.zathura$"; }
305 # ];
306 # open-on-workspace = "read";
307 # }
308 {
309 matches = [ { app-id = "^mpv$"; } ];
310 open-on-workspace = "vid";
311 default-column-width.proportion = 1.;
312 }
313 {
314 matches = [ { app-id = "^kitty-play$"; } ];
315 open-on-workspace = "vid";
316 default-column-width.proportion = 1. / 3.;
317 open-focused = false;
318 }
319 # {
320 # matches = [
321 # { app-id = "^qemu$"; }
322 # { app-id = "^virt-manager$"; }
323 # ];
324 # open-on-workspace = "mon";
325 # }
326 ];
327
328 binds = with config.lib.niri.actions; {
329 "Mod+Slash".action = show-hotkey-overlay;
330
331 "Mod+Return".action = spawn terminal;
332 "Mod+Q".action = close-window;
333 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
334 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
335
336 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
337 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
338 name = "queue-yt-dlp";
339 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
340 text = ''
341 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
342 '';
343 }));
344 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
345 name = "queue-yt-dlp";
346 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
347 text = ''
348 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
349 '';
350 }));
351
352 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
353 name = "qalc-fuzzel";
354 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
355 text = ''
356 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
357 prev() {
358 FOUND=false
359 while IFS= read -r line; do
360 [[ -n "$line" ]] || continue
361 FOUND=true
362 echo "$line"
363 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)
364 $FOUND || echo
365 }
366 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
367 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
368 QALC_RES="$FUZZEL_RES"
369 QALC_RET=0
370 else
371 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
372 QALC_RET=$?
373 fi
374 [[ -n "$QALC_RES" ]] || exit 1
375 EXISTING=false
376 set +e
377 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
378 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
379 set -e
380 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
381 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
382 cat >"$RES_FILE" <<<"$QALC_RES"
383 fi
384 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
385 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
386 notify-send "$QALC_RES"
387 '';
388 }));
389 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
390 name = "emoji-fuzzel";
391 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
392 text = ''
393 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
394 [[ -n "$FUZZEL_RES" ]] || exit 1
395 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
396 '';
397 }));
398 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
399 name = "screenshot";
400 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
401 text = ''
402 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
403 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
404 | wl-copy --type image/png
405 '';
406 }));
407 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
408 name = "screenshot";
409 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
410 text = ''
411 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
412 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
413 | wl-copy --type image/png
414 '';
415 }));
416 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
417 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
418
419 "Mod+H".action = focus-column-left;
420 "Mod+T".action = focus-window-down;
421 "Mod+N".action = focus-window-up;
422 "Mod+S".action = focus-column-right;
423
424 "Mod+Shift+H".action = move-column-left;
425 "Mod+Shift+T".action = move-window-down;
426 "Mod+Shift+N".action = move-window-up;
427 "Mod+Shift+S".action = move-column-right;
428
429 "Mod+Control+H".action = focus-monitor-left;
430 "Mod+Control+T".action = focus-monitor-down;
431 "Mod+Control+N".action = focus-monitor-up;
432 "Mod+Control+S".action = focus-monitor-right;
433
434 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
435 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
436 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
437 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
438
439 "Mod+G".action = focus-adjacent-workspace "down";
440 "Mod+C".action = focus-adjacent-workspace "up";
441
442 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
443 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
444
445 "Mod+Shift+Control+G".action = move-workspace-down;
446 "Mod+Shift+Control+C".action = move-workspace-up;
447
448 "Mod+ParenLeft".action = focus-workspace "comm";
449 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
450
451 "Mod+ParenRight".action = focus-workspace "web";
452 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
453
454 "Mod+BraceRight".action = focus-workspace "read";
455 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
456
457 "Mod+BraceLeft".action = focus-workspace "mon";
458 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
459
460 "Mod+Asterisk".action = focus-workspace "vid";
461 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
462
463 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
464 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
465
466 "Mod+M".action = consume-or-expel-window-left;
467 "Mod+W".action = consume-or-expel-window-right;
468
469 "Mod+R".action = switch-preset-column-width;
470 "Mod+Shift+R".action = switch-preset-window-height;
471 "Mod+F".action = center-column;
472 "Mod+Shift+F".action = maximize-column;
473 "Mod+Shift+Ctrl+F".action = fullscreen-window;
474
475 "Mod+V".action = switch-focus-between-floating-and-tiling;
476 "Mod+Shift+V".action = toggle-window-floating;
477
478 "Mod+Left".action = set-column-width "-10%";
479 "Mod+Down".action = set-window-height "-10%";
480 "Mod+Up".action = set-window-height "+10%";
481 "Mod+Right".action = set-column-width "+10%";
482
483 "Mod+Shift+Z" = {
484 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
485 allow-when-locked = true;
486 };
487 "Mod+Shift+L".action = spawn loginctl "lock-session";
488 "Mod+Shift+E".action = quit;
489 "Mod+Shift+Minus" = {
490 action = spawn systemctl "suspend";
491 allow-when-locked = true;
492 };
493 "Mod+Shift+Control+Minus" = {
494 action = spawn systemctl "hibernate";
495 allow-when-locked = true;
496 };
497
498 "XF86MonBrightnessUp" = {
499 action = spawn lightctl "-d" "-e4" "-n1" "up";
500 allow-when-locked = true;
501 };
502 "XF86MonBrightnessDown" = {
503 action = spawn lightctl "-d" "-e4" "-n1" "down";
504 allow-when-locked = true;
505 };
506 "XF86AudioRaiseVolume" = {
507 action = spawn volumectl "-d" "-u" "up";
508 allow-when-locked = true;
509 };
510 "XF86AudioLowerVolume" = {
511 action = spawn volumectl "-d" "-u" "down";
512 allow-when-locked = true;
513 };
514 "XF86AudioMute" = {
515 action = spawn volumectl "-d" "toggle-mute";
516 allow-when-locked = true;
517 };
518 "XF86AudioMicMute" = {
519 action = spawn volumectl "-d" "-m" "toggle-mute";
520 allow-when-locked = true;
521 };
522
523 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
524 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
525 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
526 "Mod+Comma".action = spawn makoctl "restore";
527
528 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
529 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
530 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
531 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
532 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
533 };
534 };
535 };
536}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
deleted file mode 100644
index 8abf14d8..00000000
--- a/accounts/gkleen@sif/niri/mako.nix
+++ /dev/null
@@ -1,50 +0,0 @@
1{ config, lib, ... }:
2{
3 config = {
4 services.mako = {
5 enable = true;
6 font = "Fira Sans 10";
7 format = "<i>%s</i>\\n%b";
8 margin = "2";
9 maxVisible = -1;
10 backgroundColor = "#000000dd";
11 progressColor = "source #223544ff";
12 width = 384;
13 extraConfig = ''
14 outer-margin=1
15 max-history=100
16
17 [grouped]
18 format=<b>(%g)</b> <i>%s</i>\n%b
19
20 [urgency=low]
21 text-color=#999999ff
22
23 [urgency=critical]
24 background-color=#900000dd
25
26 [app-name=Element]
27 group-by=summary
28
29 [mode=silent]
30 invisible=1
31 '';
32 };
33 systemd.user.services.mako = {
34 Unit = {
35 Description = "Mako notification daemon";
36 PartOf = [ "graphical-session.target" ];
37 };
38 Install = {
39 WantedBy = [ "graphical-session.target" ];
40 };
41 Service = {
42 Type = "dbus";
43 BusName = "org.freedesktop.Notifications";
44 ExecStart = lib.getExe config.services.mako.package;
45 RestartSec = 5;
46 Restart = "always";
47 };
48 };
49 };
50}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
deleted file mode 100644
index 79c429f8..00000000
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ /dev/null
@@ -1,338 +0,0 @@
1{ lib, pkgs, ... }:
2{
3 config = {
4 programs.waybar = {
5 enable = true;
6 systemd = {
7 enable = true;
8 target = "graphical-session.target";
9 };
10 settings = let
11 windowRewrites = {
12 "(.*) — Mozilla Firefox" = "$1";
13 "(.*) - Mozilla Thunderbird" = "$1";
14 "(.*) - mpv" = "$1";
15 };
16 iconSize = 11;
17 in [
18 {
19 layer = "top";
20 position = "top";
21 height = 14;
22 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today"
26 "custom/weather"
27 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
29
30 "custom/mako" = {
31 format = "{}";
32 return-type = "json";
33 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
34 from dbus_next.aio import MessageBus
35
36 import asyncio
37
38 import json
39
40
41 loop = asyncio.new_event_loop()
42 asyncio.set_event_loop(loop)
43
44
45 async def main():
46 bus = await MessageBus().connect()
47 # the introspection xml would normally be included in your project, but
48 # this is convenient for development
49 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
50
51 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
52 mako = obj.get_interface('fr.emersion.Mako')
53 properties = obj.get_interface('org.freedesktop.DBus.Properties')
54
55 async def print_mode():
56 modes = await mako.get_modes()
57 is_silent = "silent" in modes
58 icon = "&#xf009b;" if is_silent else "&#xf009a;"
59 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
60 if is_silent:
61 text = f"<span color=\"#ffffff\">{text}</span>"
62 print(json.dumps({'text': text}, separators=(',', ':')), flush=True) # noqa: E501
63
64 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
65 if "Modes" not in invalidated_properties:
66 return
67
68 await print_mode()
69
70 properties.on_properties_changed(on_properties_changed)
71 await print_mode()
72
73 await loop.create_future()
74
75
76 loop.run_until_complete(main())
77 '';
78 on-click = "makoctl mode -t silent";
79 };
80 "custom/weather" = {
81 format = "{}";
82 tooltip = true;
83 interval = 3600;
84 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
85 return-type = "json";
86 };
87 "custom/keymap" = {
88 format = "{}";
89 tooltip = true;
90 return-type = "json";
91 exec = pkgs.writers.writePython3 "keymap" {} ''
92 import os
93 import socket
94 import json
95
96
97 def output(keymap):
98 short = keymap
99 if keymap == "English (programmer Dvorak)":
100 short = "dvp"
101 elif keymap == "English (US)":
102 short = "<span color=\"#ffffff\">us</span>"
103 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
104
105
106 keyboard_layouts = []
107
108 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
109 sock.connect(os.environ["NIRI_SOCKET"])
110 sock.send(b"\"EventStream\"\n")
111 for line in sock.makefile(buffering=1, encoding='utf-8'):
112 if line_json := json.loads(line):
113 if "KeyboardLayoutsChanged" in line_json:
114 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
115 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
116 if "KeyboardLayoutSwitched" in line_json:
117 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
118 '';
119 on-click = "niri msg action switch-layout next";
120 };
121 "custom/worktime" = {
122 interval = 60;
123 exec = lib.getExe pkgs.worktime;
124 tooltip = false;
125 };
126 "custom/worktime-today" = {
127 interval = 60;
128 exec = "${lib.getExe pkgs.worktime} today";
129 tooltip = false;
130 };
131 "niri/workspaces" = {
132 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
133 };
134 "niri/window" = {
135 separate-outputs = true;
136 icon = true;
137 icon-size = 14;
138 rewrite = windowRewrites;
139 };
140 clock = {
141 interval = 1;
142 # timezone = "Europe/Berlin";
143 format = "W{:%V-%u %F %H:%M:%S%Ez}";
144 tooltip-format = "<tt><small>{calendar}</small></tt>";
145 calendar = {
146 mode = "year";
147 mode-mon-col = 3;
148 weeks-pos = "left";
149 on-scroll = 1;
150 format = {
151 months = "<span color='#ffead3'><b>{}</b></span>";
152 days = "{}";
153 weeks = "<span color='#99ffdd'><b>{}</b></span>";
154 weekdays = "<span color='#ffcc66'><b>{}</b></span>";
155 today = "<span color='#ff6699'><b>{}</b></span>";
156 };
157 };
158 };
159 battery = {
160 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
161 icon-size = iconSize - 2;
162 states = { warning = 30; critical = 15; };
163 format-icons = ["&#xf008e;" "&#xf007a;" "&#xf007b;" "&#xf007c;" "&#xf007d;" "&#xf007e;" "&#xf007f;" "&#xf0080;" "&#xf0081;" "&#xf0082;" "&#xf0079;" ];
164 format-charging = "&#xf0084;";
165 format-plugged = "&#xf06a5;";
166 tooltip-format = "{capacity}% {timeTo}";
167 interval = 20;
168 };
169 tray = {
170 icon-size = 16;
171 # show-passive-items = true;
172 spacing = 1;
173 };
174 privacy = {
175 icon-spacing = 7;
176 icon-size = iconSize;
177 modules = [
178 { type = "screenshare"; }
179 { type = "audio-in"; }
180 ];
181 };
182 idle_inhibitor = {
183 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
184 icon-size = iconSize;
185 format-icons = { activated = "&#xf0208;"; deactivated = "&#xf0209;"; };
186 timeout = 120;
187 };
188 backlight = {
189 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
190 icon-size = iconSize;
191 tooltip-format = "{percent}%";
192 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
193 on-scroll-up = "lightctl -d -e4 -n1 up";
194 on-scroll-down = "lightctl -d -e4 -n1 down";
195 };
196 wireplumber = {
197 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
198 icon-size = iconSize;
199 tooltip-format = "{volume}% {node_name}";
200 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
201 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
202 # ignored-sinks = ["Easy Effects Sink"];
203 on-scroll-up = "volumectl -d -u up";
204 on-scroll-down = "volumectl -d -u down";
205 on-click = "volumectl -d toggle-mute";
206 };
207 }
208 {
209 layer = "top";
210 position = "top";
211 height = 14;
212 output = [ "!eDP-1" "!DP-2" "!DP-3" ];
213 modules-left = [ "niri/workspaces" ];
214 modules-center = [ "niri/window" ];
215 modules-right = [ "clock" ];
216
217 "niri/workspaces" = {
218 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
219 };
220 "niri/window" = {
221 separate-outputs = true;
222 icon = true;
223 icon-size = 14;
224 rewrite = windowRewrites;
225 };
226 clock = {
227 interval = 1;
228 # timezone = "Europe/Berlin";
229 format = "{:%H:%M}";
230 tooltip-format = "W{:%V-%u %F %H:%M:%S%Ez}";
231 };
232 }
233 ];
234 style = ''
235 @define-color white #ffffff;
236 @define-color grey #555555;
237 @define-color blue #1a8fff;
238 @define-color green #23fd00;
239 @define-color orange #f28a21;
240 @define-color red #f2201f;
241
242 * {
243 border: none;
244 font-family: "Fira Sans Nerd Font";
245 font-size: 10pt;
246 min-height: 0;
247 }
248
249 window#waybar {
250 background-color: rgba(0, 0, 0, 0.66);
251 color: @white;
252 }
253
254 .modules-left {
255 margin-left: 8px;
256 }
257 .modules-right {
258 margin-right: 8px;
259 }
260
261 .module {
262 margin: 0 5px;
263 }
264
265 #workspaces button {
266 color: @white;
267 padding: 2px 5px;
268 }
269 #workspaces button.empty {
270 color: @grey;
271 }
272 #workspaces button.active {
273 color: @green;
274 }
275 #workspaces button.urgent {
276 color: @red;
277 }
278
279 #custom-weather, #custom-keymap, #custom-worktime, #custom-worktime-today {
280 color: @grey;
281 margin: 0 5px;
282 }
283 #custom-weather, #custom-worktime-today {
284 margin-right: 3px;
285 }
286 #custom-keymap, #custom-weather {
287 margin-left: 3px;
288 }
289
290 #tray {
291 margin: 0;
292 }
293 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
294 color: @grey;
295 margin: 0 5px 0 2px;
296 }
297 #idle_inhibitor {
298 margin-right: 4px;
299 margin-left: 6px;
300 }
301 #custom-mako {
302 margin-right: 2px;
303 margin-left: 3px;
304 }
305 #battery {
306 margin-right: 3px;
307 }
308 #battery.discharging {
309 color: @white;
310 }
311 #battery.warning {
312 color: @orange;
313 }
314 #battery.critical {
315 color: @red;
316 }
317 #battery.charging {
318 color: @white;
319 }
320 #idle_inhibitor.activated {
321 color: @white;
322 }
323
324 #idle_inhibitor {
325 padding-top: 1px;
326 }
327
328 #privacy {
329 color: @red;
330 margin: -1px 2px 0px 5px;
331 }
332 #clock {
333 /* margin-right: 5px; */
334 }
335 '';
336 };
337 };
338}
diff --git a/accounts/gkleen@sif/shell/default.nix b/accounts/gkleen@sif/shell/default.nix
new file mode 100644
index 00000000..09eb6863
--- /dev/null
+++ b/accounts/gkleen@sif/shell/default.nix
@@ -0,0 +1,123 @@
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 QT_PLUGIN_PATH : ${pkgs.qt6.qtimageformats}/${pkgs.qt6.qtbase.qtPluginPrefix} \
16 --prefix QML_IMPORT_PATH : ${pkgs.qt6Packages.callPackage ./quickshell-plugins {}}/${pkgs.qt6.qtbase.qtQmlPrefix}
17 done
18 '';
19 };
20 config = {
21 src = ./quickshell;
22 replacements = {
23 ignore_workspaces = builtins.toJSON (map ({ name, ... }: name) config.programs.niri.scratchspaces);
24 wallpapers = builtins.toJSON (pkgs.stdenvNoCC.mkDerivation {
25 name = "wallpapers";
26 srcs = [
27 (pkgs.fetchurl {
28 url = "https://esawebb.org/media/archives/images/publicationtiff10k/carinanebula3.tif";
29 hash = "sha256-YxZEweDKJfvfrdxb/QFmgJhcZDEJYxotoHrG+RRn1tw=";
30 })
31 (pkgs.fetchurl {
32 url = "https://esawebb.org/media/archives/images/original/pillarsofcreation_composite.tif";
33 hash = "sha256-qRiODxR0lZWdxgYXna0fNRFFDErpBJDwOJuQl6sNjRc=";
34 })
35 (pkgs.fetchurl {
36 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2212a.tif";
37 hash = "sha256-l2fqE/z//C1a0xkvZwsnwPbTSb+WuA11h+SUl3E1dhw=";
38 })
39 (pkgs.fetchurl {
40 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2415a.tif";
41 hash = "sha256-onBy7cPoUpDuzQStbY2E+qmlGgSLXPwFCLX53ukAb4c=";
42 })
43 (pkgs.fetchurl {
44 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2330a.tif";
45 hash = "sha256-nn0ZtjZIrPcpj3YcLTsrL7XiXvyh3QYgCSmdDMD+3OM=";
46 })
47 (pkgs.fetchurl {
48 url = "https://esawebb.org/media/archives/images/original/weic2426a.tif";
49 hash = "sha256-EDnfPn3GE9jt6XPqiGInP7E2F3Az7d25NqATSWltDv0=";
50 })
51 (pkgs.fetchurl {
52 url = "https://esawebb.org/media/archives/images/original/weic2503a.tif";
53 hash = "sha256-3/RX6RQp8naELcgReHPd5/zhJkoCjnA10w5BEnNo+qI=";
54 })
55 (pkgs.fetchurl {
56 url = "https://esawebb.org/media/archives/images/original/weic2506a.tif";
57 hash = "sha256-aDld0aoY1owRxDVf7Jcyw71TH42M1foYotxn2thyFYw=";
58 })
59 (pkgs.fetchurl {
60 url = "https://esawebb.org/media/archives/images/original/weic2514a.tif";
61 hash = "sha256-jTi1G1Ofo5xsF6ggrbtYJHxqLaCQ7edM5B3uORiVQtg=";
62 })
63 (pkgs.fetchurl {
64 url = "https://esawebb.org/media/archives/images/original/weic2425c.tif";
65 hash = "sha256-oaEOexSJHEGj090dJF3ct5HAoR+Y5gRiPrUlxdvnTRo=";
66 })
67 (pkgs.fetchurl {
68 url = "https://esawebb.org/media/archives/images/original/weic2605c.tif";
69 hash = "sha256-IwWfgkz84LFXkfk77anb5e0Mf0wWiIszum7UC3yZn0U=";
70 })
71 ];
72
73 dontUnpack = true;
74
75 buildInputs = [ pkgs.imagemagick ];
76 buildPhase = ''
77 runHook preBuild
78
79 typeset sources=($srcs)
80
81 mkdir -p $out
82 magick ''${sources[0]} -crop 10000x5625+0+79 +repage -define webp:target-size=10000000 $out/carinanebula3.webp
83 magick ''${sources[1]} -crop 6716x3778+329+80 +repage -define webp:target-size=10000000 $out/pillarsofcreation_composite.webp
84 magick ''${sources[2]} -crop 10000x5625+0+79 +repage -define webp:target-size=10000000 $out/weic2212a.webp
85 magick ''${sources[3]} -crop 7650x4302+1166+389 +repage -define webp:target-size=10000000 $out/weic2415a.webp
86 magick ''${sources[4]} -crop 8732x4912+0+434 +repage -define webp:target-size=10000000 $out/weic2330a.webp
87 magick ''${sources[5]} -crop 5302x2982+636+0 +repage -define webp:target-size=10000000 $out/weic2426a.webp
88 magick ''${sources[6]} -crop 4328x2434+0+906 +repage -define webp:target-size=10000000 $out/weic2503a.webp
89 magick ''${sources[7]} -crop 4152x2335+0+666 +repage -define webp:target-size=10000000 $out/weic2506a.webp
90 magick ''${sources[8]} -crop 4320x2430+0+0 +repage -define webp:target-size=10000000 $out/weic2514a.webp
91 magick ''${sources[9]} -crop 5863x3298+0+477 +repage -define webp:target-size=10000000 $out/weic2425c.webp
92 magick ''${sources[10]} -crop 3990x2244+0+1379 +repage -define webp:target-size=10000000 $out/weic2605c.webp
93
94 runHook postBuild
95 '';
96 });
97 niri_session = builtins.toJSON [
98 (pkgs.writeShellScript "niri-session" ''
99 exec ${lib.getExe pkgs.dex} -w ${config.programs.niri.package}/share/wayland-sessions/niri.desktop &>/tmp/niri-session-$$.log
100 '')
101 # (lib.getExe pkgs.dex)
102 # "${config.programs.niri.package}/share/wayland-sessions/niri.desktop"
103 ];
104 username = builtins.toJSON config.home.username;
105 mdi = builtins.toJSON (pkgs.fetchFromGitHub {
106 owner = "Templarian";
107 repo = "MaterialDesign";
108 rev = "2424e748e0cc63ab7b9c095a099b9fe239b737c0";
109 hash = "sha256-QMGl7soAhErrrnY3aKOZpt49yebkSNzy10p/v5OaqQ0=";
110 });
111 worktime = builtins.toJSON (lib.getExe pkgs.worktime);
112 slurp = builtins.toJSON (lib.getExe pkgs.slurp);
113 wf-recorder = builtins.toJSON (lib.getExe pkgs.wf-recorder);
114 };
115 };
116 };
117 systemd.user.services.quickshell = {
118 Service = {
119 RuntimeDirectory = "quickshell";
120 };
121 };
122 };
123}
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..ac1cbf3a
--- /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..e8bd6245
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -0,0 +1,130 @@
1import Quickshell
2import Quickshell.Wayland
3import QtQuick
4import qs.Services
5
6PanelWindow {
7 id: bar
8
9 WlrLayershell.namespace: "bar"
10
11 property bool haveMaximizedWindow: {
12 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
13 return ws.output === bar.screen.name && ws.is_active;
14 })?.active_window_id;
15 let activeWindowTileSize = Array.from(NiriService.windows).find(win => win.id == currWindowId)?.layout?.tile_size;
16
17 if (!activeWindowTileSize)
18 return false;
19 return activeWindowTileSize[0] >= bar.screen.width && activeWindowTileSize[1] >= bar.screen.height - bar.height;
20 }
21
22 anchors {
23 top: true
24 left: true
25 right: true
26 }
27 margins {
28 left: bar.haveMaximizedWindow ? 0 : 26 + 8
29 right: bar.haveMaximizedWindow ? 0 : 26 + 8
30 }
31
32 implicitHeight: 21
33 color: "transparent"
34
35 property bool haveScreenshare: Array.from(NiriService.casts).some(cast => cast.target.Output?.name == bar.screen.name)
36
37 Rectangle {
38 color: {
39 if (bar.haveScreenshare)
40 return bar.haveMaximizedWindow ? Qt.rgba(0.2, 0, 0, 1) : Qt.rgba(0.2, 0, 0, 0.75);
41 return bar.haveMaximizedWindow ? "black" : Qt.rgba(0, 0, 0, 0.75);
42 }
43 anchors.fill: parent
44 // bottomLeftRadius: 8
45 // bottomRightRadius: 8
46 }
47
48 Row {
49 id: left
50
51 height: parent.height
52 width: childrenRect.width
53 anchors.left: parent.left
54 anchors.leftMargin: 8
55 anchors.verticalCenter: parent.verticalCenter
56 spacing: 8
57
58 WorkspaceSwitcher {
59 screen: bar.screen
60 }
61 }
62
63 Row {
64 id: center
65
66 height: parent.height
67 width: childrenRect.width
68 anchors.centerIn: parent
69 spacing: 5
70
71 ActiveWindowDisplay {
72 screen: bar.screen
73 maxWidth: bar.width - 2*Math.max(left.width, right.width) - 2*8
74 }
75 }
76
77 Row {
78 id: right
79
80 height: parent.height
81 width: childrenRect.width
82 anchors.right: parent.right
83 anchors.rightMargin: 8
84 anchors.verticalCenter: parent.verticalCenter
85 spacing: 0
86
87 WorktimeWidget {}
88
89 KeyboardLayout {}
90
91 Item {
92 visible: privacy.visible
93 height: parent.height
94 width: 8 - 4
95 }
96
97 PrivacyWidget {
98 id: privacy
99 }
100
101 Item {
102 visible: privacy.visible
103 height: parent.height
104 width: 8 - 4
105 }
106
107 SystemTray {}
108
109 PipewireWidget {}
110
111 BrightnessWidget {}
112
113 BatteryWidget {}
114
115 WaylandInhibitorWidget {
116 window: bar
117 }
118
119 NotificationInhibitorWidget {}
120
121 LidSwitchInhibitorWidget {}
122
123 Item {
124 height: parent.height
125 width: 8 - 4
126 }
127
128 Clock {}
129 }
130} \ 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..f465fa20
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml
@@ -0,0 +1,139 @@
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 if (!root.batteryDevice?.ready)
106 return "Not ready";
107
108 const stateStr = UPowerDeviceState.toString(root.batteryDevice.state);
109 var outStr = stateStr;
110 if (root.batteryDevice.state !== UPowerDeviceState.FullyCharged)
111 outStr += ` ${Math.round(root.batteryDevice.percentage * 100)}%`;
112
113 function formatTime(t) {
114 var res = "";
115 for (const unit of [{ "s": "h", "v": 3600 }, { "s": "m", "v": 60 }, { "s": "s", "v": 1 }]) {
116 if (t < unit.v)
117 continue;
118 res += Math.floor(t / unit.v) + unit.s;
119 t %= unit.v;
120 }
121 return res;
122 }
123 if (root.batteryDevice.timeToEmpty != 0) {
124 const tStr = formatTime(Math.floor(root.batteryDevice.timeToEmpty / 60) * 60);
125 if (tStr)
126 outStr += " " + tStr;
127 } else if (root.batteryDevice.timeToFull != 0) {
128 const tStr = formatTime(Math.ceil(root.batteryDevice.timeToFull / 60) * 60);
129 if (tStr)
130 outStr += " " + tStr;
131 }
132
133 return outStr;
134 }
135 }
136 }
137 }
138 }
139}
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..30fa68b5
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
@@ -0,0 +1,128 @@
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 Connections {
35 target: Custom.Systemd
36 function onSleep(before: bool) {
37 console.log(`received prepare for sleep ${before}`);
38 if (before) {
39 lock.locked = true;
40 if (pam.active)
41 pam.abort();
42 }
43 }
44 function onLock() { lock.locked = true; }
45 function onUnlock() { lock.locked = false; }
46 }
47
48 IdleMonitor {
49 id: idleMonitor
50 enabled: !lock.secure
51 timeout: 600
52 respectInhibitors: true
53
54 onIsIdleChanged: {
55 if (idleMonitor.isIdle)
56 lock.locked = true;
57 }
58 }
59
60 Custom.SystemdInhibitor {
61 enabled: !lock.secure
62
63 what: Custom.SystemdInhibitorParams.Sleep
64 who: "quickshell"
65 why: "Lock session"
66 mode: Custom.SystemdInhibitorParams.Delay
67 }
68
69 Binding {
70 target: NotificationManager
71 property: "lockscreenActive"
72 value: lock.locked
73 }
74
75 WlSessionLock {
76 id: lock
77
78 onLockStateChanged: {
79 if (!locked && pam.active)
80 pam.abort();
81
82 if (locked) {
83 NiriService.sendCommand({ "Action": { "PowerOffMonitors": {} } }, _ => {});
84 Custom.KeePassXC.lockAllDatabases();
85 Array.from(MprisProxy.players).forEach(player => {
86 if (player.canPause && player.isPlaying)
87 player.pause();
88 });
89 // Custom.Systemd.stopUserUnit("gpg-agent.service", "replace");
90 GpgAgent.reloadAgent();
91 }
92 }
93 Component.onCompleted: { (_ => {})(MprisProxy.players); }
94
95 onSecureStateChanged: Custom.Systemd.lockedHint = lock.secure
96
97 WlSessionLockSurface {
98 id: lockSurface
99
100 color: "black"
101
102 LockSurface {
103 id: surfaceContent
104
105 onResponse: responseText => pam.respond(responseText)
106 onAuthRunningChanged: {
107 if (authRunning)
108 pam.start();
109 }
110 Connections {
111 target: pam
112 function onMessagesChanged() { surfaceContent.messages = pam.messages; }
113 function onResponseRequiredChanged() { surfaceContent.responseRequired = pam.responseRequired; }
114 function onActiveChanged() { surfaceContent.authRunning = pam.active; }
115 }
116 onCurrentTextChanged: lockscreen.currentText = currentText
117 Connections {
118 target: lockscreen
119 function onCurrentTextChanged() { surfaceContent.currentText = lockscreen.currentText; }
120 }
121 Connections {
122 target: lockSurface
123 function onScreenChanged() { surfaceContent.screen = lockSurface.screen; }
124 }
125 }
126 }
127 }
128}
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..80cbba19
--- /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 && NotificationManager.history.length > 0 && (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..f3ae6804
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml
@@ -0,0 +1,107 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Io
6import Custom as Custom
7import qs.Services
8
9Singleton {
10 id: root
11
12 property string subsystem: "backlight"
13 property string device: "intel_backlight"
14
15 property real currBrightness
16 property real exponent: 4
17
18 function calcCurrBrightness() {
19 if (!currFile.loaded || !maxFile.loaded)
20 return undefined;
21 const curr = Number(currFile.text());
22 const max = Number(maxFile.text());
23 const val = Math.pow(curr / max, 1 / root.exponent);
24 return val;
25 }
26
27 Connections {
28 target: currFile
29 function onLoaded() {
30 const b = root.calcCurrBrightness();
31 if (typeof b !== 'undefined')
32 root.currBrightness = b;
33 }
34 }
35 Connections {
36 target: maxFile
37 function onLoaded() {
38 const b = root.calcCurrBrightness();
39 if (typeof b !== 'undefined')
40 root.currBrightness = b;
41 }
42 }
43
44 onCurrBrightnessChanged: {
45 root.currBrightness = Math.max(0, Math.min(1, root.currBrightness));
46
47 const prev = root.calcCurrBrightness();
48 if (typeof prev === 'undefined' || Math.abs(root.currBrightness - prev) < 0.01)
49 return;
50
51 const max = Number(maxFile.text());
52 const actual = Number(currFile.text());
53 let curr = Math.max(0, Math.min(max, Math.pow(root.currBrightness, root.exponent) * max));
54 if (Math.round(curr) == actual && curr < actual)
55 curr = Math.max(0, actual - 1);
56 else if (Math.round(curr) == actual && curr > actual)
57 curr = Math.min(max, actual + 1);
58 // root.currBrightness = Math.pow(curr / max, 1 / root.exponent);
59 Custom.Systemd.setBrightness(root.subsystem, root.device, Math.round(curr));
60 }
61
62 FileView {
63 id: currFile
64 path: `/sys/class/${root.subsystem}/${root.device}/brightness`
65 blockAllReads: true
66 watchChanges: true
67 onFileChanged: reload()
68 }
69 FileView {
70 id: maxFile
71 path: `/sys/class/${root.subsystem}/${root.device}/max_brightness`
72 blockAllReads: true
73 watchChanges: true
74 onFileChanged: reload()
75 }
76
77
78 Timer {
79 id: startupDelay
80 interval: 500
81 running: true
82 repeat: false
83 }
84 property bool onlyInternal: true
85 Connections {
86 target: NiriService
87 function onOutputsChanged() {
88 root.onlyInternal = Object.keys(NiriService.outputs).every(oname => oname == "eDP-1");
89 }
90 }
91 onOnlyInternalChanged: {
92 if (startupDelay.running)
93 return;
94
95 if (!root.onlyInternal)
96 root.currBrightness = 1
97 }
98
99
100 IpcHandler {
101 target: "Brightness"
102
103 function set(brightness: real): void {
104 root.currBrightness = brightness;
105 }
106 }
107}
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..cd4ed125
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
@@ -0,0 +1,216 @@
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 property var casts: []
15 readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
16
17 function refreshOutputs() {
18 commandSocket.sendCommand("Outputs", data => {
19 outputs = data.Ok.Outputs;
20 });
21 }
22
23 function sendCommand(command, callback) {
24 commandSocket.sendCommand(command, callback);
25 }
26
27 Socket {
28 id: eventStreamSocket
29 path: root.socketPath
30 connected: true
31
32 property bool acked: false
33
34 onConnectionStateChanged: {
35 if (connected) {
36 acked = false;
37 write('"EventStream"\n');
38 }
39 }
40
41 parser: SplitParser {
42 onRead: line => {
43 try {
44 const event = JSON.parse(line)
45
46 // console.log(JSON.stringify(event))
47
48 if (event.WorkspacesChanged) {
49 root.workspaces = event.WorkspacesChanged.workspaces
50 root.refreshOutputs();
51 } else if (event.WorkspaceActivated)
52 eventWorkspaceActivated(event.WorkspaceActivated);
53 else if (event.WorkspaceUrgencyChanged)
54 eventWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
55 else if (event.WorkspaceActiveWindowChanged)
56 eventWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged);
57 else if (event.KeyboardLayoutsChanged)
58 root.keyboardLayouts = event.KeyboardLayoutsChanged.keyboard_layouts;
59 else if (event.KeyboardLayoutSwitched)
60 root.keyboardLayouts = Object.assign({}, root.keyboardLayouts, {"current_idx": event.KeyboardLayoutSwitched.idx });
61 else if (event.WindowsChanged)
62 root.windows = event.WindowsChanged.windows
63 else if (event.WindowOpenedOrChanged)
64 eventWindowOpenedOrChanged(event.WindowOpenedOrChanged);
65 else if (event.WindowClosed)
66 eventWindowClosed(event.WindowClosed);
67 else if (event.WindowFocusChanged)
68 eventWindowFocusChanged(event.WindowFocusChanged);
69 else if (event.WindowUrgencyChanged)
70 eventWindowUrgencyChanged(event.WindowUrgencyChanged);
71 else if (event.WindowLayoutsChanged)
72 eventWindowLayoutsChanged(event.WindowLayoutsChanged);
73 else if (event.WindowFocusTimestampChanged)
74 eventWindowFocusTimestampChanged(event.WindowFocusTimestampChanged);
75 else if (event.CastsChanged)
76 root.casts = event.CastsChanged.casts
77 else if (event.CastStartedOrChanged)
78 eventCastStartedOrChanged(event.CastStartedOrChanged);
79 else if (event.CastStopped)
80 eventCastStopped(event.CastStopped);
81 else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; }
82 else if (event.OverviewOpenedOrClosed) {}
83 else if (event.ConfigLoaded) {}
84 else
85 console.log(JSON.stringify(event));
86 } catch (e) {
87 console.warn("NiriService: Failed to parse event:", line, e)
88 }
89 }
90 }
91 }
92
93 Socket {
94 id: commandSocket
95 path: root.socketPath
96 connected: true
97
98 property var awaitingAnswer: null
99 property var cmdQueue: []
100
101 parser: SplitParser {
102 onRead: line => {
103 if (commandSocket.awaitingAnswer === null)
104 return;
105
106 try {
107 const response = JSON.parse(line);
108 commandSocket.awaitingAnswer.callback(response);
109 commandSocket.awaitingAnswer = null;
110 } catch (e) {
111 console.warn("NiriService: Failed to parse response:", line, e)
112 }
113 commandSocket._handleQueue();
114 }
115 }
116
117 onCmdQueueChanged: {
118 _handleQueue();
119 }
120 onAwaitingAnswerChanged: {
121 _handleQueue();
122 }
123
124 function _handleQueue() {
125 if (cmdQueue.length <= 0 || awaitingAnswer !== null)
126 return;
127
128 let localQueue = Array.from(cmdQueue);
129 awaitingAnswer = localQueue.shift();
130 cmdQueue = localQueue;
131 write(JSON.stringify(awaitingAnswer.command) + '\n');
132 }
133
134 function sendCommand(command, callback) {
135 cmdQueue = Array.from(cmdQueue).concat([{ "command": command, "callback": callback }])
136 }
137 }
138
139 function eventWorkspaceActivated(data) {
140 let relevant_output = null;
141 Array.from(root.workspaces).forEach(ws => {
142 if (data.id === ws.id)
143 relevant_output = ws.output;
144 });
145 root.workspaces = Array.from(root.workspaces).map(ws => {
146 if (data.focused)
147 ws.is_focused = false;
148 if (ws.output === relevant_output)
149 ws.is_active = false;
150 if (data.id === ws.id) {
151 ws.is_active = true;
152 ws.is_focused = data.focused;
153 }
154 return ws;
155 });
156 }
157 function eventWorkspaceUrgencyChanged(data) {
158 root.workspaces = Array.from(root.workspaces).map(ws => {
159 if (data.id == ws.id)
160 ws.is_urgent = data.urgent;
161 return ws;
162 });
163 }
164 function eventWorkspaceActiveWindowChanged(data) {
165 root.workspaces = Array.from(root.workspaces).map(ws => {
166 if (data.workspace_id === ws.id)
167 ws.active_window_id = data.active_window_id;
168 return ws;
169 });
170 }
171 function eventWindowOpenedOrChanged(data) {
172 root.windows = Array.from(root.windows).map(win => {
173 if (data.window.is_focused)
174 win.is_focused = false;
175 return win;
176 }).filter(win => win.id !== data.window.id).concat([data.window]);
177 }
178 function eventWindowClosed(data) {
179 root.windows = Array.from(root.windows).filter(win => win.id !== data.id);
180 }
181 function eventWindowFocusChanged(data) {
182 root.windows = Array.from(root.windows).map(win => {
183 win.is_focused = win.id === data.id;
184 return win;
185 });
186 }
187 function eventWindowUrgencyChanged(data) {
188 root.windows = Array.from(root.windows).map(win => {
189 if (win.id === data.id)
190 win.is_urgent = data.urgent;
191 return win;
192 });
193 }
194 function eventWindowLayoutsChanged(data) {
195 root.windows = Array.from(root.windows).map(win => {
196 Array.from(data.changes).forEach(change => {
197 if (win.id === change[0])
198 win.layout = change[1];
199 });
200 return win;
201 });
202 }
203 function eventWindowFocusTimestampChanged(data) {
204 root.windows = Array.from(root.windows).map(win => {
205 if (win.id === data.id)
206 win.focus_timestamp = data.focus_timestamp;
207 return win;
208 });
209 }
210 function eventCastStartedOrChanged(data) {
211 root.casts = [...Array.from(root.casts).filter(cast => cast.stream_id !== data.cast.stream_id), data.cast];
212 }
213 function eventCastStopped(data) {
214 root.casts = Array.from(root.casts).filter(cast => cast.stream_id !== data.stream_id);
215 }
216}
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..5f8ff419
--- /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.toLocaleDateString(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/ScreenRecord.qml b/accounts/gkleen@sif/shell/quickshell/Services/ScreenRecord.qml
new file mode 100644
index 00000000..eb415452
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/ScreenRecord.qml
@@ -0,0 +1,63 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5
6Singleton {
7 id: root
8 property bool active: false
9 property bool slurpSuccess: false
10
11 onActiveChanged: {
12 if (!active) {
13 slurp.running = false;
14 screenRecorder.running = false;
15 }
16 if (active)
17 slurp.running = true;
18 }
19
20 Process {
21 id: screenRecorder
22 running: false
23 onRunningChanged: {
24 console.log("wf-recorder running: ", screenRecorder.running);
25
26 if (!screenRecorder.running && !slurp.running)
27 root.active = false;
28 }
29 stderr: SplitParser {
30 onRead: line => console.log("wf-recorder stderr: ", line)
31 }
32 stdout: SplitParser {
33 onRead: line => console.log("wf-recorder stdout: ", line)
34 }
35 }
36
37 Process {
38 id: slurp
39 running: false
40 command: [ @slurp@, "-o", "-d" ]
41 stdout: StdioCollector {}
42 stderr: SplitParser {
43 onRead: line => console.log("slurp stderr: ", line)
44 }
45 onExited: exitCode => {
46 if (exitCode !== 0) {
47 console.log("slurp failed: ", exitCode);
48 root.active = false;
49 return;
50 }
51 console.log("slurp succeeded: ", slurp.stdout.text);
52
53 const nowDate = new Date();
54
55 screenRecorder.command = [
56 @wf-recorder@,
57 "-g", slurp.stdout.text,
58 "-f", `${Quickshell.env("HOME")}/screenshots/${nowDate.toLocaleString(Qt.locale(), "yyyy-MM-ddThh:mm:ss")}.mkv`,
59 ];
60 screenRecorder.running = true;
61 }
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..c71a9cca
--- /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: 79200000
8}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml b/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml
new file mode 100644
index 00000000..d98378f1
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml
@@ -0,0 +1,84 @@
1pragma Singleton
2
3import QtQuick
4import Quickshell
5import Quickshell.Io
6import QtQml
7
8Singleton {
9 id: root
10
11 property alias time: timeState
12 property alias today: todayState
13
14 CommandState {
15 id: timeState
16 command: "time"
17 }
18 CommandState {
19 id: todayState
20 command: "today"
21 }
22
23 IpcHandler {
24 target: "Worktime"
25
26 function refresh(): void {
27 time.running = true;
28 today.running = true;
29 }
30 }
31
32 component CommandState : Scope {
33 id: commandState
34
35 required property string command
36 property var state: null
37
38 property bool strikeout: !strikeoutTimer.running
39 property alias running: process.running
40 property alias updating: updateTimer.running
41
42 Process {
43 id: process
44 running: true
45 command: [ @worktime@, commandState.command, "--waybar" ]
46 stdout: StdioCollector {
47 id: processCollector
48 onStreamFinished: {
49 try {
50 commandState.state = JSON.parse(processCollector.text);
51 strikeoutTimer.restart();
52 } catch (e) {
53 console.warn("Worktime: Failed to parse output:", processCollector.text, e);
54 }
55 }
56 }
57 }
58
59 Timer {
60 id: updateTimer
61 running: commandState.state?.class == "running" || commandState.state?.class == "over"
62 interval: 60000
63 repeat: true
64 onTriggered: process.running = true
65 }
66
67 Timer {
68 id: strikeoutTimer
69 running: false
70 interval: 5 * updateTimer.interval
71 repeat: false
72 }
73 }
74
75 Timer {
76 running: Boolean(timeState.state) && Boolean(todayState.state) && timeState.strikeout && todayState.strikeout
77 interval: 1000
78 repeat: false
79 onTriggered: {
80 timeState.state = null;
81 todayState.state = null;
82 }
83 }
84}
diff --git a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
new file mode 100644
index 00000000..f0c50632
--- /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 && !(["Fcitx"].includes(item.id)));
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..3d950031
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml
@@ -0,0 +1,106 @@
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 if (command.ScreenRecord)
43 root.onCommandScreenRecord(command.ScreenRecord);
44 else
45 console.warn("UnixIPC: Command not handled:", JSON.stringify(command));
46 }
47 }
48
49 onError: e => {
50 if (e == 1)
51 return;
52 console.warn("QLocalSocket::LocalSocketError", e);
53 }
54 }
55 }
56
57 PwObjectTracker {
58 objects: [ Pipewire.defaultAudioSink, Pipewire.defaultAudioSource ]
59 }
60 function onCommandVolume(command) {
61 if (command.muted === "toggle")
62 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
63 if (command.volume === "up")
64 Pipewire.defaultAudioSink.audio.volume += 0.02;
65 if (command.volume === "down")
66 Pipewire.defaultAudioSink.audio.volume -= 0.02;
67
68 if (command["mic-muted"] === "toggle")
69 Pipewire.defaultAudioSource.audio.muted = !Pipewire.defaultAudioSource.audio.muted;
70 }
71
72 function onCommandBrightness(command) {
73 if (command === "up")
74 Brightness.currBrightness += 0.02
75 if (command === "down")
76 Brightness.currBrightness -= 0.02
77 }
78
79 function onCommandMpris(command) {
80 if (command.PauseAll)
81 Array.from(MprisProxy.players).forEach(player => {
82 if (player.canPause && player.isPlaying)
83 player.pause();
84 });
85 }
86 Component.onCompleted: { (_ => {})(MprisProxy.players); }
87
88 function onCommandNotifications(command) {
89 if (command.DismissGroup && NotificationManager.active) {
90 if (NotificationManager.groups.length > 0)
91 for (const notif of [...NotificationManager.groups[0]])
92 notif.dismiss();
93 }
94 if (command.DismissAll && NotificationManager.active) {
95 for (const notif of [...NotificationManager.trackedNotifications.values])
96 notif.dismiss();
97 }
98 }
99
100 function onCommandScreenRecord(command) {
101 if (command.Toggle)
102 ScreenRecord.active = !ScreenRecord.active;
103 else
104 console.warn("UnixIPC.ScreenRecord: Command not handled:", JSON.stringify(command));
105 }
106}
diff --git a/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
new file mode 100644
index 00000000..91dc9591
--- /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 || null
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 || null
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..d0595b3d
--- /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..b7baee34
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml
@@ -0,0 +1,212 @@
1import QtQml
2import Quickshell
3import QtQuick
4import Quickshell.Widgets
5import qs.Services
6
7Item {
8 id: root
9
10 height: parent.height
11 width: (timeWidget.visible ? timeWidget.label.contentWidth + 8 : 0) + (todayWidget.visible ? todayWidget.label.contentWidth + 8 : 0) + (icon.visible ? icon.implicitWidth + 8 : 0)
12 anchors.verticalCenter: parent.verticalCenter
13
14 component TextWidget : Item {
15 id: textWidget
16
17 visible: textWidget.state.state?.text ?? false
18
19 required property var state
20 property alias label: label
21 property alias mouseArea: mouseArea
22
23 anchors.verticalCenter: parent.verticalCenter
24 implicitWidth: label.contentWidth + 8
25 height: parent.height
26
27 WrapperMouseArea {
28 id: mouseArea
29
30 anchors.fill: parent
31
32 enabled: true
33 hoverEnabled: true
34 acceptedButtons: Qt.NoButton
35 cursorShape: Qt.PointingHandCursor
36
37 Item {
38 anchors.fill: parent
39
40 Text {
41 id: label
42
43 anchors.centerIn: parent
44
45 text: textWidget.state.state?.text ?? ""
46
47 font.pointSize: 10
48 font.family: "Fira Sans"
49 font.strikeout: textWidget.state.strikeout
50 color: {
51 if (textWidget.state.state?.class == "running")
52 return "white";
53 if (textWidget.state.state?.class == "over")
54 return "#f28a21";
55 return "#555";
56 }
57 }
58 }
59 }
60 }
61
62 WrapperMouseArea {
63 id: mouseArea
64
65 anchors.fill: parent
66 hoverEnabled: true
67
68 cursorShape: Qt.PointingHandCursor
69 onClicked: {
70 Worktime.time.running = true;
71 Worktime.today.running = true;
72 }
73
74 Rectangle {
75 anchors.fill: parent
76 color: {
77 if (mouseArea.containsMouse)
78 return "#33808080";
79 return "transparent";
80 }
81
82 Row {
83 height: parent.height
84 anchors.centerIn: parent
85 spacing: 0
86
87 TextWidget {
88 id: timeWidget
89 state: Worktime.time
90 }
91
92 TextWidget {
93 id: todayWidget
94 state: Worktime.today
95 }
96
97 MaterialDesignIcon {
98 id: icon
99
100 anchors.verticalCenter: parent.verticalCenter
101
102 implicitSize: 14
103
104 visible: !timeWidget.visible && !todayWidget.visible
105
106 icon: (Worktime.time.running || Worktime.today.running) ? "update" : "timer-off"
107 color: "#555"
108 }
109 }
110 }
111 }
112
113 component WorktimePopup : PopupWindow {
114 id: tooltip
115
116 required property var state
117 required property var mouseArea
118
119 property bool iconVisible: tooltip.state.running || !tooltip.state.updating
120 property bool tooltipVisible: tooltip.state.state?.tooltip ?? false
121
122 property bool nextVisible: (tooltip.tooltipVisible || tooltip.iconVisible) && (tooltip.mouseArea.containsMouse || tooltipMouseArea.containsMouse)
123
124 anchor {
125 item: tooltip.mouseArea
126 edges: Edges.Bottom | Edges.Left
127 }
128 visible: false
129
130 onNextVisibleChanged: hangTimer.restart()
131
132 Timer {
133 id: hangTimer
134 interval: 100
135 onTriggered: tooltip.visible = tooltip.nextVisible
136 }
137
138 implicitWidth: (tooltipIcon.visible ? tooltipIcon.implicitWidth : 0) + (tooltipIcon.visible && tooltipText.visible ? 8 : 0) + (tooltipText.visible ? tooltipText.implicitWidth : 0) + 16
139 implicitHeight: tooltipText.implicitHeight + 16
140 color: "black"
141
142 WrapperMouseArea {
143 id: tooltipMouseArea
144
145 enabled: true
146 hoverEnabled: true
147
148 anchors.fill: parent
149
150 Item {
151 anchors.fill: parent
152
153 Row {
154 id: tooltipLayout
155
156 anchors {
157 left: parent.left
158 top: parent.top
159 leftMargin: 8
160 topMargin: 8
161 verticalCenter: parent.verticalCenter
162 }
163
164 height: parent.height
165 width: childrenRect.width
166
167 spacing: 0
168
169 MaterialDesignIcon {
170 id: tooltipIcon
171
172 implicitSize: 14
173 anchors.verticalCenter: parent.verticalCenter
174
175 visible: tooltip.iconVisible
176
177 icon: tooltip.state.running ? "update" : "timer-off"
178 }
179
180 Item {
181 visible: tooltipIcon.visible && tooltipText.visible
182 height: parent.height
183 width: 8
184 }
185
186 Text {
187 id: tooltipText
188
189 visible: tooltip.tooltipVisible
190
191 anchors.verticalCenter: parent.verticalCenter
192
193 font.pointSize: 10
194 font.family: "Fira Sans"
195 color: "white"
196
197 text: tooltip.state.state?.tooltip ?? ""
198 }
199 }
200 }
201 }
202 }
203
204 WorktimePopup {
205 state: Worktime.time
206 mouseArea: timeWidget.mouseArea
207 }
208 WorktimePopup {
209 state: Worktime.today
210 mouseArea: todayWidget.mouseArea
211 }
212}
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..1a5875f0
--- /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 screen: 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/ssh-hosts.nix b/accounts/gkleen@sif/ssh-hosts.nix
index 107f1e76..0b49f385 100644
--- a/accounts/gkleen@sif/ssh-hosts.nix
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -1,5 +1,13 @@
1{ pkgs, ... }: 1{ lib, pkgs, ... }:
2{ 2let
3 autosshProxyPorts = {
4 "ssh.math.lmu.de" = 8118;
5 "mathw0h" = 8122;
6 "mathw0e" = 8124;
7 "cip04" = 8126;
8 };
9 autosshProxy = host: "${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=${toString autosshProxyPorts.${host}}";
10in {
3 "git.ymir" = 11 "git.ymir" =
4 { hostname = "ymir.yggdrasil.li"; 12 { hostname = "ymir.yggdrasil.li";
5 user = "gitolite"; 13 user = "gitolite";
@@ -290,15 +298,15 @@
290 }; 298 };
291 "mathw0d" = 299 "mathw0d" =
292 { hostname = "mathw0d.mathinst.loc"; 300 { hostname = "mathw0d.mathinst.loc";
293 proxyJump = "mathw0h"; 301 proxyCommand = autosshProxy "mathw0h";
294 }; 302 };
295 "mathw0e" = 303 "mathw0e" =
296 { hostname = "mathw0e.mathinst.loc"; 304 { hostname = "mathw0e.mathinst.loc";
297 proxyJump = "mathw0h"; 305 proxyCommand = autosshProxy "mathw0h";
298 }; 306 };
299 "mathw0f" = 307 "mathw0f" =
300 { hostname = "mathw0f.mathinst.loc"; 308 { hostname = "mathw0f.mathinst.loc";
301 proxyJump = "mathw0h"; 309 proxyCommand = autosshProxy "mathw0h";
302 }; 310 };
303 "mathw0g" = 311 "mathw0g" =
304 { hostname = "mathw0g.mathinst.loc"; 312 { hostname = "mathw0g.mathinst.loc";
@@ -306,8 +314,8 @@
306 "mathw0h" = 314 "mathw0h" =
307 { hostname = "mathw0h.mathinst.loc"; 315 { hostname = "mathw0h.mathinst.loc";
308 }; 316 };
309 "proxy.mathw0g" = 317 "proxy.ssh.math.lmu.de" =
310 { hostname = "mathw0g.mathinst.loc"; 318 { hostname = "ssh.math.lmu.de";
311 extraOptions = { 319 extraOptions = {
312 ControlPath = "none"; 320 ControlPath = "none";
313 ExitOnForwardFailure = "yes"; 321 ExitOnForwardFailure = "yes";
@@ -317,7 +325,27 @@
317 }; 325 };
318 "proxy.mathw0h" = 326 "proxy.mathw0h" =
319 { hostname = "mathw0h.mathinst.loc"; 327 { hostname = "mathw0h.mathinst.loc";
320 proxyJump = "proxy.mathw0g"; 328 proxyCommand = autosshProxy "ssh.math.lmu.de";
329 extraOptions = {
330 ControlPath = "none";
331 ExitOnForwardFailure = "yes";
332 ServerAliveCountMax = "15";
333 ServerAliveInterval = "2";
334 };
335 };
336 "proxy.mathw0e" =
337 { hostname = "mathw0e.mathinst.loc";
338 proxyCommand = autosshProxy "mathw0h";
339 extraOptions = {
340 ControlPath = "none";
341 ExitOnForwardFailure = "yes";
342 ServerAliveCountMax = "15";
343 ServerAliveInterval = "2";
344 };
345 };
346 "proxy.cip04" =
347 { hostname = "cip04.cipmath.loc";
348 proxyCommand = autosshProxy "mathw0h";
321 extraOptions = { 349 extraOptions = {
322 ControlPath = "none"; 350 ControlPath = "none";
323 ExitOnForwardFailure = "yes"; 351 ExitOnForwardFailure = "yes";
@@ -327,7 +355,7 @@
327 }; 355 };
328 "vrt-kvm06" = 356 "vrt-kvm06" =
329 { hostname = "vrt-kvm06"; 357 { hostname = "vrt-kvm06";
330 proxyJump = "mathw0e"; 358 proxyCommand = autosshProxy "mathw0e";
331 user = "root"; 359 user = "root";
332 extraOptions = { 360 extraOptions = {
333 PasswordAuthentication = "yes"; 361 PasswordAuthentication = "yes";
@@ -336,7 +364,7 @@
336 }; 364 };
337 "vrt-kvm05" = 365 "vrt-kvm05" =
338 { hostname = "vrt-kvm05"; 366 { hostname = "vrt-kvm05";
339 proxyJump = "mathw0e"; 367 proxyCommand = autosshProxy "mathw0e";
340 user = "root"; 368 user = "root";
341 extraOptions = { 369 extraOptions = {
342 PasswordAuthentication = "yes"; 370 PasswordAuthentication = "yes";
@@ -345,7 +373,7 @@
345 }; 373 };
346 "vrt-kvm04" = 374 "vrt-kvm04" =
347 { hostname = "vrt-kvm04"; 375 { hostname = "vrt-kvm04";
348 proxyJump = "mathw0e"; 376 proxyCommand = autosshProxy "mathw0e";
349 user = "root"; 377 user = "root";
350 extraOptions = { 378 extraOptions = {
351 PasswordAuthentication = "yes"; 379 PasswordAuthentication = "yes";
@@ -354,7 +382,7 @@
354 }; 382 };
355 "vrt-kvm02" = 383 "vrt-kvm02" =
356 { hostname = "vrt-kvm02"; 384 { hostname = "vrt-kvm02";
357 proxyJump = "mathw0e"; 385 proxyCommand = autosshProxy "mathw0e";
358 user = "root"; 386 user = "root";
359 extraOptions = { 387 extraOptions = {
360 PasswordAuthentication = "yes"; 388 PasswordAuthentication = "yes";
@@ -363,7 +391,7 @@
363 }; 391 };
364 "vrt-kvm03" = 392 "vrt-kvm03" =
365 { hostname = "vrt-kvm03"; 393 { hostname = "vrt-kvm03";
366 proxyJump = "mathw0e"; 394 proxyCommand = autosshProxy "mathw0e";
367 user = "root"; 395 user = "root";
368 extraOptions = { 396 extraOptions = {
369 PasswordAuthentication = "yes"; 397 PasswordAuthentication = "yes";
@@ -372,7 +400,7 @@
372 }; 400 };
373 "vrt-kvm01" = 401 "vrt-kvm01" =
374 { hostname = "vrt-kvm01"; 402 { hostname = "vrt-kvm01";
375 proxyJump = "mathw0e"; 403 proxyCommand = autosshProxy "mathw0e";
376 user = "root"; 404 user = "root";
377 extraOptions = { 405 extraOptions = {
378 PasswordAuthentication = "yes"; 406 PasswordAuthentication = "yes";
@@ -381,39 +409,44 @@
381 }; 409 };
382 "tts-www01" = 410 "tts-www01" =
383 { hostname = "tts-www01.mathinst.loc"; 411 { hostname = "tts-www01.mathinst.loc";
384 proxyJump = "mathw0h"; 412 proxyCommand = autosshProxy "mathw0h";
385 user = "root"; 413 user = "root";
386 }; 414 };
387 "vpn-wg01" = 415 "vpn-wg01" =
388 { hostname = "vpn-wg01.mathinst.loc"; 416 { hostname = "vpn-wg01.mathinst.loc";
389 proxyJump = "mathw0h"; 417 proxyCommand = autosshProxy "mathw0h";
390 user = "root"; 418 user = "root";
391 }; 419 };
392 "repo-apt01" = 420 "repo-apt01" =
393 { hostname = "repo-apt01.mathinst.loc"; 421 { hostname = "repo-apt01.mathinst.loc";
394 proxyJump = "mathw0h"; 422 proxyCommand = autosshProxy "mathw0h";
395 user = "root"; 423 user = "root";
396 }; 424 };
397 "ldap-lmumr01" = 425 "ldap-lmumr01" =
398 { hostname = "ldap-lmumr01.mathinst.loc"; 426 { hostname = "ldap-lmumr01.mathinst.loc";
399 proxyJump = "mathw0h"; 427 proxyCommand = autosshProxy "mathw0h";
400 user = "root"; 428 user = "root";
401 }; 429 };
402 "mail-mi01" = 430 "mail-mi01" =
403 { hostname = "mail-mi01.mathinst.loc"; 431 { hostname = "mail-mi01.mathinst.loc";
404 proxyJump = "mathw0h"; 432 proxyCommand = autosshProxy "mathw0h";
405 }; 433 };
406 "mail-www02" = 434 "mail-www02" =
407 { hostname = "mail-www02.mathinst.loc"; 435 { hostname = "mail-www02.mathinst.loc";
408 proxyJump = "mathw0h"; 436 proxyCommand = autosshProxy "mathw0h";
409 }; 437 };
410 "dpl-fai01" = 438 "dpl-fai01" =
411 { hostname = "dpl-fai01.mathinst.loc"; 439 { hostname = "dpl-fai01.mathinst.loc";
412 user = "root"; 440 user = "root";
413 }; 441 };
442 "dpl-fai02" =
443 { hostname = "dpl-fai02.mathinst.loc";
444 user = "root";
445 proxyJump = "mgmt01";
446 };
414 "math05" = 447 "math05" =
415 { hostname = "math05.mathinst.loc"; 448 { hostname = "math05.mathinst.loc";
416 proxyJump = "mathw0h"; 449 proxyCommand = autosshProxy "mathw0h";
417 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1"; 450 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1";
418 }; 451 };
419 "switch01" = 452 "switch01" =
@@ -439,20 +472,20 @@
439 }; 472 };
440 "www-mi01" = 473 "www-mi01" =
441 { hostname = "www-mi01.mathinst.loc"; 474 { hostname = "www-mi01.mathinst.loc";
442 proxyJump = "mathw0h"; 475 proxyCommand = autosshProxy "mathw0h";
443 }; 476 };
444 "cip04" = 477 "cip04" =
445 { hostname = "cip04.cipmath.loc"; 478 { hostname = "cip04.cipmath.loc";
446 proxyJump = "mathw0h"; 479 proxyCommand = autosshProxy "mathw0h";
447 }; 480 };
448 "mgmt-cls01" = 481 "mgmt-cls01" =
449 { user = "root"; 482 { user = "root";
450 hostname = "mgmt-cls01.cipmath.loc"; 483 hostname = "mgmt-cls01.cipmath.loc";
451 proxyJump = "ssh.math.lmu.de"; 484 proxyCommand = autosshProxy "ssh.math.lmu.de";
452 }; 485 };
453 "mgmt01" = 486 "mgmt01" =
454 { hostname = "mgmt01.mathinst.loc"; 487 { hostname = "mgmt01.mathinst.loc";
455 proxyJump = "mathw0h"; 488 proxyCommand = autosshProxy "mathw0h";
456 user = "root"; 489 user = "root";
457 }; 490 };
458 "ssh-lb01" = 491 "ssh-lb01" =
@@ -471,17 +504,17 @@
471 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; }; 504 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; };
472 "math0d" = 505 "math0d" =
473 { hostname = "math0d.mathinst.loc"; 506 { hostname = "math0d.mathinst.loc";
474 proxyJump = "mathw0h"; 507 proxyCommand = autosshProxy "mathw0h";
475 }; 508 };
476 "dhcp01" = 509 "dhcp01" =
477 { hostname = "dhcp01.mathinst.loc"; 510 { hostname = "dhcp01.mathinst.loc";
478 user = "root"; 511 user = "root";
479 proxyJump = "mathw0h"; 512 proxyCommand = autosshProxy "mathw0h";
480 }; 513 };
481 "dhcp02" = 514 "dhcp02" =
482 { hostname = "dhcp02.mathinst.loc"; 515 { hostname = "dhcp02.mathinst.loc";
483 user = "root"; 516 user = "root";
484 proxyJump = "mathw0h"; 517 proxyCommand = autosshProxy "mathw0h";
485 }; 518 };
486 "cc-gpu-l01" = 519 "cc-gpu-l01" =
487 { hostname = "cc-gpu-l01.mathinst.loc"; 520 { hostname = "cc-gpu-l01.mathinst.loc";
@@ -546,7 +579,7 @@
546 user = "root"; 579 user = "root";
547 }; 580 };
548 "nas*" = 581 "nas*" =
549 { proxyJump = "mathw0e"; 582 { proxyCommand = autosshProxy "mathw0e";
550 user = "admin"; 583 user = "admin";
551 extraOptions = { 584 extraOptions = {
552 PasswordAuthentication = "yes"; 585 PasswordAuthentication = "yes";
@@ -554,9 +587,4 @@
554 HostKeyAlgorithms = "+ecdsa-sha2-nistp256"; 587 HostKeyAlgorithms = "+ecdsa-sha2-nistp256";
555 }; 588 };
556 }; 589 };
557 "game01" =
558 { hostname = "game01.yggdrasil.li";
559 user = "factorio";
560 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
561 };
562} 590}
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 cefcf4ea..2ccbaea0 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -6,7 +6,7 @@ let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 7
8 autossh-socks-script = pkgs.writeScript "autossh" '' 8 autossh-socks-script = pkgs.writeScript "autossh" ''
9 #!${pkgs.zsh}/bin/zsh -xe 9 #!${lib.getExe pkgs.zsh} -xe
10 10
11 host="''${1%:*}" 11 host="''${1%:*}"
12 port="''${1#*:}" 12 port="''${1#*:}"
@@ -15,31 +15,29 @@ let
15 cmd=() 15 cmd=()
16 16
17 if [[ -n "''${SSHPASS_SECRET}" ]]; then 17 if [[ -n "''${SSHPASS_SECRET}" ]]; then
18 cmd+=(${pkgs.sshpassSecret}/bin/sshpass-secret) 18 cmd+=(${lib.getExe' pkgs.sshpassSecret "sshpass-secret"})
19 cmd+=("''${(@s/:/)SSHPASS_SECRET}") 19 cmd+=("''${(@s/:/)SSHPASS_SECRET}")
20 cmd+=(--) 20 cmd+=(--)
21 fi 21 fi
22 22
23 cmd+=(${pkgs.openssh}/bin/ssh -vN -D localhost:''${port} "''${host}") 23 cmd+=(${lib.getExe' pkgs.openssh "ssh"} -vN -D 127.0.0.1:''${port} "''${host}")
24 24
25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) & 25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) &
26 pid=$! 26 pid=$!
27 27
28 newpid="" 28 newpid=""
29 i=200 29 i=200
30 while ! newpid=$(${pkgs.lsof}/bin/lsof -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do 30 while ! newpid=$(${lib.getExe pkgs.lsof} -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do
31 if ! kill -0 "''${pid}"; then 31 if ! kill -0 "''${pid}"; then
32 wait "''${pid}" 32 wait "''${pid}"
33 exit $? 33 exit $?
34 fi 34 fi
35 [[ "''${i}" -gt 0 ]] || exit 1 35 [[ "''${i}" -gt 0 ]] || exit 1
36 i=$((''${i} - 1)) 36 i=$((''${i} - 1))
37 ${pkgs.coreutils}/bin/sleep 0.1 37 ${lib.getExe' pkgs.coreutils "sleep"} 0.1
38 done 38 done
39 39
40 ${config.systemd.package}/bin/systemd-notify --ready 40 ${lib.getExe' config.systemd.package "systemd-notify"} --pid=''${newpid} --ready
41
42 wait "''${pid}" "''${newpid}"
43 ''; 41 '';
44in { 42in {
45 tmpfiles.rules = [ 43 tmpfiles.rules = [
@@ -48,11 +46,11 @@ in {
48 ]; 46 ];
49 47
50 services = { 48 services = {
51 sync-keepass = { 49 "sync-keepass@" = {
52 Service = { 50 Service = {
53 Type = "oneshot"; 51 Type = "oneshot";
54 WorkingDirectory = "~"; 52 WorkingDirectory = "~";
55 ExecStart = toString (pkgs.writers.writePython3 "sync-keepass" { 53 ExecStart = "${pkgs.writers.writePython3 "sync-keepass" {
56 libraries = with pkgs.python3Packages; [ python-dateutil ]; 54 libraries = with pkgs.python3Packages; [ python-dateutil ];
57 } '' 55 } ''
58 import json 56 import json
@@ -61,13 +59,13 @@ in {
61 from datetime import datetime 59 from datetime import datetime
62 from dateutil.tz import tzlocal 60 from dateutil.tz import tzlocal
63 from dateutil.parser import isoparse 61 from dateutil.parser import isoparse
64 from sys import stderr 62 from sys import stderr, argv
65 63
66 64
67 remote_fs = 'surtr' 65 remote_fs = 'surtr' if argv[1] == 'store.kdbx' else 'mathcloud'
68 remote_file = 'store.kdbx' 66 remote_file = argv[1]
69 target_file = expanduser('~/store.kdbx') 67 target_file = expanduser(f'~/{argv[1]}')
70 meta_file = expanduser('~/.store.kdbx.json') 68 meta_file = expanduser(f'~/.{argv[1]}.json')
71 69
72 upload_time = None 70 upload_time = None
73 our_last_upload_time = None 71 our_last_upload_time = None
@@ -117,13 +115,14 @@ in {
117 do_upload() 115 do_upload()
118 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501 116 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501
119 do_download() 117 do_download()
120 ''); 118 ''} \"%I\"";
121 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ]; 119 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ];
122 }; 120 };
123 }; 121 };
124 emacs = { 122 emacs = {
125 Unit = { 123 Unit = {
126 After = ["graphical-session-pre.target"]; 124 After = [ "graphical-session.target" ];
125 BindsTo = [ "graphical-session.target" ];
127 }; 126 };
128 }; 127 };
129 keepassxc = { 128 keepassxc = {
@@ -135,8 +134,8 @@ in {
135 Environment = [ "QT_QPA_PLATFORM=wayland" ]; 134 Environment = [ "QT_QPA_PLATFORM=wayland" ];
136 }; 135 };
137 Unit = { 136 Unit = {
138 Requires = ["graphical-session-pre.target"]; 137 After = [ "graphical-session.target" ];
139 After = ["graphical-session-pre.target"]; 138 BindsTo = [ "graphical-session.target" ];
140 }; 139 };
141 }; 140 };
142 mpris-proxy = { 141 mpris-proxy = {
@@ -145,7 +144,7 @@ in {
145 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy"; 144 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy";
146 Install.WantedBy = [ "default.target" ]; 145 Install.WantedBy = [ "default.target" ];
147 }; 146 };
148 "autossh-socks@proxy.mathw0h:8119" = { 147 "autossh-socks@proxy.ssh.math.lmu.de:8119" = {
149 Service = { 148 Service = {
150 Type = "notify"; 149 Type = "notify";
151 NotifyAccess = "all"; 150 NotifyAccess = "all";
@@ -153,7 +152,7 @@ in {
153 Restart = "always"; 152 Restart = "always";
154 RestartSec = "23s"; 153 RestartSec = "23s";
155 ExecStart = "${autossh-socks-script} \"%I\""; 154 ExecStart = "${autossh-socks-script} \"%I\"";
156 Environment = [ "SSHPASS_SECRET=gkleen@mathw0g.math.lmu.de" ]; 155 Environment = [ "SSHPASS_SECRET=gkleen@ssh.math.lmu.de" ];
157 }; 156 };
158 Unit = { 157 Unit = {
159 StopWhenUnneeded = true; 158 StopWhenUnneeded = true;
@@ -172,11 +171,56 @@ in {
172 }; 171 };
173 Unit = { 172 Unit = {
174 StopWhenUnneeded = true; 173 StopWhenUnneeded = true;
174 StartLimitInterval = "2s";
175 StartLimitBurst = 25;
175 }; 176 };
176 }; 177 };
177 swayidle = { 178 "autossh-socks@proxy.mathw0h:8123" = {
178 Service = { 179 Service = {
179 RuntimeDirectory = "swayidle"; 180 Type = "notify";
181 NotifyAccess = "all";
182 WorkingDirectory = "~";
183 Restart = "always";
184 RestartSec = "23s";
185 ExecStart = "${autossh-socks-script} \"%I\"";
186 Environment = [ "SSHPASS_SECRET=gkleen@mathw0h.mathinst.loc" ];
187 };
188 Unit = {
189 StopWhenUnneeded = true;
190 StartLimitInterval = "180s";
191 StartLimitBurst = 7;
192 };
193 };
194 "autossh-socks@proxy.mathw0e:8125" = {
195 Service = {
196 Type = "notify";
197 NotifyAccess = "all";
198 WorkingDirectory = "~";
199 Restart = "always";
200 RestartSec = "23s";
201 ExecStart = "${autossh-socks-script} \"%I\"";
202 Environment = [ "SSHPASS_SECRET=gkleen@mathw0e.mathinst.loc" ];
203 };
204 Unit = {
205 StopWhenUnneeded = true;
206 StartLimitInterval = "180s";
207 StartLimitBurst = 7;
208 };
209 };
210 "autossh-socks@proxy.cip04:8127" = {
211 Service = {
212 Type = "notify";
213 NotifyAccess = "all";
214 WorkingDirectory = "~";
215 Restart = "always";
216 RestartSec = "23s";
217 ExecStart = "${autossh-socks-script} \"%I\"";
218 Environment = [ "SSHPASS_SECRET=gkleen@cip04.cipmath.loc" ];
219 };
220 Unit = {
221 StopWhenUnneeded = true;
222 StartLimitInterval = "180s";
223 StartLimitBurst = 7;
180 }; 224 };
181 }; 225 };
182 psi-notify = { 226 psi-notify = {
@@ -184,8 +228,8 @@ in {
184 WantedBy = ["graphical-session.target"]; 228 WantedBy = ["graphical-session.target"];
185 }; 229 };
186 Unit = { 230 Unit = {
187 Requires = ["graphical-session-pre.target"]; 231 After = [ "graphical-session.target" ];
188 After = ["graphical-session-pre.target"]; 232 PartOf = [ "graphical-session.target" ];
189 }; 233 };
190 Service = { 234 Service = {
191 ExecStart = lib.getExe pkgs.psi-notify; 235 ExecStart = lib.getExe pkgs.psi-notify;
@@ -198,6 +242,7 @@ in {
198 gtklock = { 242 gtklock = {
199 Unit = { 243 Unit = {
200 Requisite = ["graphical-session.target"]; 244 Requisite = ["graphical-session.target"];
245 After = [ "graphical-session.target" ];
201 PartOf = ["graphical-session.target"]; 246 PartOf = ["graphical-session.target"];
202 }; 247 };
203 Service = { 248 Service = {
@@ -205,53 +250,55 @@ in {
205 RuntimeDirectory = "gtklock"; 250 RuntimeDirectory = "gtklock";
206 CacheDirectory = "gtklock"; 251 CacheDirectory = "gtklock";
207 ExecStartPre = [ 252 ExecStartPre = [
208 "${pkgs.libsForQt5.qt5.qttools.bin}/bin/qdbus org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases" 253 "-${lib.getExe' pkgs.libsForQt5.qt5.qttools.bin "qdbus"} org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases"
209 "${config.systemd.package}/bin/systemctl --user stop gpg-agent.service" 254 "-${lib.getExe' config.systemd.package "systemctl"} --user stop gpg-agent.service"
210 (pkgs.writeShellScript "generate-css" '' 255 "-${lib.getExe pkgs.playerctl} -a pause"
211 set -x 256 "-${lib.getExe (pkgs.writeShellApplication {
212 export PATH="${lib.makeBinPath [cfg.programs.wpaperd.package pkgs.jq pkgs.coreutils pkgs.imagemagick pkgs.findutils]}:$PATH" 257 name = "generate-css";
258 runtimeInputs = with pkgs; [cfg.services.wpaperd.package jq coreutils imagemagick findutils];
259 text = ''
260 declare -A monitors
261 monitors=()
262 while IFS= read -r entry; do
263 path=$(jq -r ".path" <<<"$entry")
264 [[ -z "$path" || ! -f "$path" ]] && continue
265 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}"
266 monitor=$(jq -r ".display" <<<"$entry")
267 if [[ ! -f "$blurred_path" ]]; then
268 mkdir -p "$(dirname "$blurred_path")"
269 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" &
270 fi
271 monitors+=([$monitor]="$blurred_path")
272 done < <(wpaperctl all-wallpapers -j | jq -c ".[]")
273 # wait
213 274
214 declare -A monitors 275 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" ''
215 monitors=() 276 #window-box {
216 while IFS= read -r entry; do 277 padding: 64px;
217 path=$(jq -r ".path" <<<"$entry") 278 /* border: 1px solid black; */
218 [[ -z "$path" || ! -f "$path" ]] && continue 279 border-radius: 4px;
219 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}" 280 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
220 monitor=$(jq -r ".display" <<<"$entry") 281 /* background-color: white; */
221 if [[ ! -f "$blurred_path" ]]; then 282 background-color: rgba(0, 0, 0, 0.5);
222 mkdir -p "$(dirname "$blurred_path")" 283 }
223 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" & 284 ''} "$RUNTIME_DIRECTORY"/style.css
224 fi 285 for monitor in "''${!monitors[@]}"; do
225 monitors+=([$monitor]="$blurred_path") 286 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF
226 done < <(wpaperctl all-wallpapers -j | jq -c ".[]") 287 window#''${monitor} {
227 wait 288 background-image: url("''${monitors[$monitor]}");
228 289 background-repeat: no-repeat;
229 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" '' 290 background-size: 100% 100%;
230 #window-box { 291 background-origin: content-box;
231 padding: 64px;
232 /* border: 1px solid black; */
233 border-radius: 4px;
234 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
235 /* background-color: white; */
236 background-color: rgba(0, 0, 0, 0.5);
237 } 292 }
238 ''} "$RUNTIME_DIRECTORY"/style.css 293 EOF
239 for monitor in "''${!monitors[@]}"; do 294 done
240 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF 295 '';
241 window#''${monitor} { 296 })}"
242 background-image: url("''${monitors[$monitor]}");
243 background-repeat: no-repeat;
244 background-size: 100% 100%;
245 background-origin: content-box;
246 }
247 EOF
248 done
249 '')
250 ]; 297 ];
251 NotifyAccess = "all"; 298 NotifyAccess = "all";
252 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" '' 299 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" ''
253 ${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off 300 ${lib.getExe cfg.programs.niri.package} msg action power-off-monitors
254 ${config.systemd.package}/bin/systemd-notify --ready 301 ${lib.getExe' config.systemd.package "systemd-notify"} --ready
255 ''}''; 302 ''}'';
256 }; 303 };
257 }; 304 };
@@ -299,38 +346,101 @@ in {
299 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\""; 346 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\"";
300 }; 347 };
301 }; 348 };
302 wpaperd = { 349 # wpaperd = {
350 # Install = {
351 # WantedBy = ["graphical-session.target"];
352 # };
353 # Unit = {
354 # After = [ "graphical-session.target" ];
355 # PartOf = [ "graphical-session.target" ];
356 # };
357 # Service = {
358 # ExecStart = lib.getExe cfg.services.wpaperd.package;
359 # Type = "simple";
360 # Restart = "always";
361 # RestartSec = "2s";
362 # };
363 # };
364 xembed-sni-proxy = {
365 Unit = {
366 PartOf = lib.mkForce ["tray.target"];
367 };
368 };
369 poweralertd = {
370 Unit = {
371 After = ["graphical-session.target"];
372 };
373 };
374 network-manager-applet = {
375 Unit = {
376 PartOf = lib.mkForce ["tray.target"];
377 };
378 };
379 udiskie = {
380 Unit = {
381 PartOf = lib.mkForce ["tray.target"];
382 };
383 };
384 blueman-applet = {
385 Unit = {
386 PartOf = lib.mkForce ["tray.target"];
387 };
303 Install = { 388 Install = {
304 WantedBy = ["graphical-session.target"]; 389 WantedBy = lib.mkForce ["tray.target"];
305 }; 390 };
391 };
392 alacritty = {
306 Unit = { 393 Unit = {
307 BindsTo = ["graphical-session-pre.target"]; 394 Requisite = ["graphical-session.target"];
308 After = ["graphical-session-pre.target"]; 395 After = [ "graphical-session.target" ];
309 }; 396 };
310 Service = { 397 Service = {
311 ExecStart = lib.getExe cfg.programs.wpaperd.package; 398 ExecStart = "${lib.getExe pkgs.alacritty} --daemon --socket %t/alacritty-activated.sock";
312 Type = "simple"; 399 };
313 Restart = "always"; 400 };
314 RestartSec = "2s"; 401 alacritty-proxy = {
402 Unit = {
403 Requires = ["alacritty.service" "alacritty-proxy.socket"];
404 After = ["alacritty.service" "alacritty-proxy.socket"];
405 };
406 Service = {
407 Type = "notify";
408 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd %t/alacritty-activated.sock";
315 }; 409 };
316 }; 410 };
317 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" { 411 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" {
318 Unit = { 412 Unit = {
319 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 413 BindsTo = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
320 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 414 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
321 }; 415 };
322 Service = { 416 Service = {
323 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=10s localhost:${toString (port + 1)}"; 417 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}";
418 Restart = "always";
419 RestartSec = "23s";
324 }; 420 };
325 }) [{ host = "proxy.mathw0h"; port = 8118; } { host = "proxy.vidhar"; port = 8120; }]); 421 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; } { host = "proxy.cip04"; port = 8126; }]);
326 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 422 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
327 Socket = { 423 Socket = {
328 ListenStream = "%I"; 424 ListenStream = "%I";
425 TriggerLimitIntervalSec = 0;
426 PollLimitIntervalSec = "180s";
427 PollLimitBurst = 6;
329 }; 428 };
330 Install = { 429 Install = {
331 WantedBy = ["default.target"]; 430 WantedBy = ["default.target"];
332 }; 431 };
333 }) [8118 8120]) // { 432 }) [8118 8122 8124 8126]) // {
433 "proxy-to-autossh-socks@8120" = {
434 Socket = {
435 ListenStream = "%I";
436 TriggerLimitIntervalSec = 0;
437 PollLimitIntervalSec = "2s";
438 PollLimitBurst = 20;
439 };
440 Install = {
441 WantedBy = ["default.target"];
442 };
443 };
334 "yt-dlp" = { 444 "yt-dlp" = {
335 Socket = { 445 Socket = {
336 SocketMode = "0600"; 446 SocketMode = "0600";
@@ -342,9 +452,18 @@ in {
342 WantedBy = ["sockets.target"]; 452 WantedBy = ["sockets.target"];
343 }; 453 };
344 }; 454 };
455 "alacritty-proxy" = {
456 Socket = {
457 SocketMode = "0600";
458 ListenStream = "%t/alacritty.sock";
459 };
460 Install = {
461 WantedBy = ["sockets.target"];
462 };
463 };
345 }; 464 };
346 timers = { 465 timers = {
347 sync-keepass = { 466 "sync-keepass@store.kdbx" = {
348 Timer = { 467 Timer = {
349 OnActiveSec = "1m"; 468 OnActiveSec = "1m";
350 OnUnitActiveSec = "1m"; 469 OnUnitActiveSec = "1m";
@@ -354,6 +473,16 @@ in {
354 WantedBy = ["default.target"]; 473 WantedBy = ["default.target"];
355 }; 474 };
356 }; 475 };
476 "sync-keepass@rz.kdbx" = {
477 Timer = {
478 OnActiveSec = "1d";
479 OnUnitActiveSec = "1d";
480 };
481
482 Install = {
483 WantedBy = ["default.target"];
484 };
485 };
357 }; 486 };
358 targets = { 487 targets = {
359 graphical-session = { 488 graphical-session = {
@@ -364,6 +493,9 @@ in {
364 }; 493 };
365 tray = { 494 tray = {
366 Unit = { 495 Unit = {
496 PartOf = [ "graphical-session.target" ];
497 # Requires = [ "waybar.service" ];
498 After = [ "graphical-session.target" ]; # "waybar.service" ];
367 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"]; 499 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"];
368 }; 500 };
369 }; 501 };
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/ldif2json/conf.patch b/accounts/gkleen@sif/utils/ldif2json/conf.patch
new file mode 100644
index 00000000..e253e5f7
--- /dev/null
+++ b/accounts/gkleen@sif/utils/ldif2json/conf.patch
@@ -0,0 +1,12 @@
1diff --git i/src/conf.cr w/src/conf.cr
2index b29ca84..eb7e0b4 100644
3--- i/src/conf.cr
4+++ w/src/conf.cr
5@@ -89,7 +89,6 @@ module Ldif2json
6
7 end.parse
8
9- puts "@coercions #{@coercions.inspect} @can_be_coerced #{@can_be_coerced.inspect} @can_be_coerced[\"foobar\"] #{@can_be_coerced["foobar"].inspect} @can_be_flattened #{@can_be_flattened.inspect} @can_be_flattened[\"foobar\"] #{@can_be_flattened["foobar"].inspect}"
10 raise NormalError.new("cannot set types in join mode") if @mode == Mode::Join && @coercions.size > 0
11
12 end
diff --git a/accounts/gkleen@sif/utils/ldif2json/default.nix b/accounts/gkleen@sif/utils/ldif2json/default.nix
new file mode 100644
index 00000000..548ae9b1
--- /dev/null
+++ b/accounts/gkleen@sif/utils/ldif2json/default.nix
@@ -0,0 +1,20 @@
1{ crystal, sources, ... }:
2crystal.buildCrystalPackage {
3 inherit (sources.ldif2json) pname version src;
4
5 patches = [
6 ./conf.patch
7 ];
8
9 buildPhase = ''
10 make bin/ldif2json
11 '';
12
13 installPhase = ''
14 mkdir -p $out/bin $out/share/man/man1
15 install -m 0555 -t $out/bin bin/ldif2json
16 install -m 0444 -t $out/share/man/man1 doc/ldif2json.1.gz
17 '';
18
19 doCheck = false;
20}
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.envrc b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.gitignore b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/default.nix b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/default.nix
new file mode 100644
index 00000000..64ee4623
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/default.nix
@@ -0,0 +1,22 @@
1{ pkgs, flake, flakeInputs, ... }:
2
3let
4 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
5 pythonSet = flake.lib.pythonSet {
6 inherit pkgs;
7 python = pkgs.python3;
8 overlay = workspace.mkPyprojectOverlay {
9 sourcePreference = "wheel";
10 };
11 };
12 package = "nixpkgs-pr-watch";
13 venv = pythonSet.mkVirtualEnv package workspace.deps.default;
14 inherit (pkgs.callPackages flakeInputs.pyproject-nix.build.util {}) mkApplication;
15in (mkApplication {
16 inherit venv;
17 package = pythonSet.${package};
18}).overrideAttrs (oldAttrs: {
19 meta = (oldAttrs.meta or {}) // {
20 mainProgram = "nixpkgs-pr-watch";
21 };
22})
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__init__.py b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__init__.py
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__main__.py b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__main__.py
new file mode 100644
index 00000000..09bb8651
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/nixpkgs_pr_watch/__main__.py
@@ -0,0 +1,73 @@
1import argparse
2import re
3import requests
4from urllib.parse import urljoin
5from xdg_base_dirs import xdg_config_home, xdg_config_dirs
6import toml
7import sys
8
9from github import Github
10
11class ApiKeyAuth(requests.auth.AuthBase):
12 def __init__(self, token):
13 self.token = token
14 def __call__(self, r):
15 r.headers["x-api-key"] = self.token
16 return r
17
18class ChangeDetectionSession(requests.Session):
19 def __init__(self, base_url: str, api_token: str):
20 super().__init__()
21 self.base_url = base_url
22 self.auth = ApiKeyAuth(api_token)
23
24 def request(self, method, url, *args, **kwargs):
25 joined_url = urljoin(urljoin(self.base_url, '/api/v1/'), url)
26 return super().request(method, joined_url, *args, **kwargs)
27
28def main():
29 def pr_number(s):
30 if m := re.fullmatch(r'(?:https?://github\.com/NixOS/nixpkgs/pull/)?(?P<pr>[0-9]+)(?:[^0-9].*)?', s, flags=re.I):
31 return int(m.group('pr'))
32 else:
33 raise ValueError
34
35 parser = argparse.ArgumentParser(prog = "nixpkgs-pr-watch")
36 parser.add_argument('prs', metavar = 'PR', type = pr_number, nargs = '+')
37
38 args = parser.parse_args()
39
40 config = None
41 for d in [xdg_config_home(), *xdg_config_dirs()]:
42 try:
43 config = toml.load(d / 'nixpkgs-pr-watch.toml')
44 break
45 except FileNotFoundError:
46 pass
47
48 for pr in args.prs:
49 pr_title = None
50 with Github() as g:
51 pr_title = g.get_repo('NixOS/nixpkgs').get_pull(pr).title
52
53 api = ChangeDetectionSession(
54 base_url = config.get("BaseUrl"),
55 api_token = config.get("ApiToken"),
56 )
57 try:
58 api.post(
59 "watch",
60 headers = {
61 'Content-Type': 'application/json',
62 },
63 json = {
64 "url": urljoin("https://nixpkgs.molybdenum.software/api/v2/landings/", str(pr)),
65 **({ "title": pr_title } if pr_title else {}),
66 "time_between_check_use_default": False,
67 "time_between_check": { "hours": 1 },
68 "fetch_backend": "html_requests",
69 },
70 ).raise_for_status()
71 except requests.HTTPError as e:
72 print(e.response.text, file=sys.stderr)
73 raise e
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/pyproject.toml b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/pyproject.toml
new file mode 100644
index 00000000..593c1dc7
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/pyproject.toml
@@ -0,0 +1,17 @@
1[project]
2name = "nixpkgs-pr-watch"
3version = "0.1.0"
4requires-python = ">=3.13"
5dependencies = [
6 "pygithub>=2.8.1",
7 "requests>=2.32.5",
8 "toml>=0.10.2",
9 "xdg-base-dirs>=6.0.2",
10]
11
12[project.scripts]
13nixpkgs-pr-watch = "nixpkgs_pr_watch.__main__:main"
14
15[build-system]
16requires = ["hatchling"]
17build-backend = "hatchling.build"
diff --git a/accounts/gkleen@sif/utils/nixpkgs-pr-watch/uv.lock b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/uv.lock
new file mode 100644
index 00000000..65537049
--- /dev/null
+++ b/accounts/gkleen@sif/utils/nixpkgs-pr-watch/uv.lock
@@ -0,0 +1,309 @@
1version = 1
2revision = 3
3requires-python = ">=3.13"
4
5[[package]]
6name = "certifi"
7version = "2025.11.12"
8source = { registry = "https://pypi.org/simple" }
9sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
10wheels = [
11 { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
12]
13
14[[package]]
15name = "cffi"
16version = "2.0.0"
17source = { registry = "https://pypi.org/simple" }
18dependencies = [
19 { name = "pycparser", marker = "implementation_name != 'PyPy'" },
20]
21sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
22wheels = [
23 { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
24 { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
25 { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
26 { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
27 { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
28 { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
29 { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
30 { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
31 { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
32 { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
33 { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
34 { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
35 { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
36 { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
37 { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
38 { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
39 { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
40 { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
41 { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
42 { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
43 { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
44 { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
45 { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
46 { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
47 { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
48 { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
49 { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
50 { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
51 { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
52 { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
53 { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
54 { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
55 { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
56 { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
57]
58
59[[package]]
60name = "charset-normalizer"
61version = "3.4.4"
62source = { registry = "https://pypi.org/simple" }
63sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
64wheels = [
65 { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
66 { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
67 { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
68 { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
69 { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
70 { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
71 { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
72 { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
73 { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
74 { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
75 { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
76 { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
77 { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
78 { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
79 { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
80 { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
81 { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
82 { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
83 { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
84 { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
85 { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
86 { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
87 { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
88 { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
89 { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
90 { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
91 { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
92 { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
93 { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
94 { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
95 { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
96 { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
97 { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
98]
99
100[[package]]
101name = "cryptography"
102version = "46.0.3"
103source = { registry = "https://pypi.org/simple" }
104dependencies = [
105 { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
106]
107sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
108wheels = [
109 { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
110 { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
111 { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
112 { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
113 { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
114 { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
115 { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
116 { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
117 { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
118 { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
119 { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
120 { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
121 { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
122 { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
123 { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
124 { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
125 { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
126 { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
127 { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
128 { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
129 { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
130 { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
131 { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
132 { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
133 { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
134 { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
135 { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
136 { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
137 { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
138 { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
139 { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
140 { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
141 { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
142 { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
143 { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
144 { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
145 { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
146 { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
147 { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
148 { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
149 { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
150 { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
151 { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
152 { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
153 { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
154]
155
156[[package]]
157name = "idna"
158version = "3.11"
159source = { registry = "https://pypi.org/simple" }
160sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
161wheels = [
162 { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
163]
164
165[[package]]
166name = "nixpkgs-pr-watch"
167version = "0.1.0"
168source = { editable = "." }
169dependencies = [
170 { name = "pygithub" },
171 { name = "requests" },
172 { name = "toml" },
173 { name = "xdg-base-dirs" },
174]
175
176[package.metadata]
177requires-dist = [
178 { name = "pygithub", specifier = ">=2.8.1" },
179 { name = "requests", specifier = ">=2.32.5" },
180 { name = "toml", specifier = ">=0.10.2" },
181 { name = "xdg-base-dirs", specifier = ">=6.0.2" },
182]
183
184[[package]]
185name = "pycparser"
186version = "2.23"
187source = { registry = "https://pypi.org/simple" }
188sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
189wheels = [
190 { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
191]
192
193[[package]]
194name = "pygithub"
195version = "2.8.1"
196source = { registry = "https://pypi.org/simple" }
197dependencies = [
198 { name = "pyjwt", extra = ["crypto"] },
199 { name = "pynacl" },
200 { name = "requests" },
201 { name = "typing-extensions" },
202 { name = "urllib3" },
203]
204sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" }
205wheels = [
206 { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" },
207]
208
209[[package]]
210name = "pyjwt"
211version = "2.10.1"
212source = { registry = "https://pypi.org/simple" }
213sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
214wheels = [
215 { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
216]
217
218[package.optional-dependencies]
219crypto = [
220 { name = "cryptography" },
221]
222
223[[package]]
224name = "pynacl"
225version = "1.6.1"
226source = { registry = "https://pypi.org/simple" }
227dependencies = [
228 { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
229]
230sdist = { url = "https://files.pythonhosted.org/packages/b2/46/aeca065d227e2265125aea590c9c47fbf5786128c9400ee0eb7c88931f06/pynacl-1.6.1.tar.gz", hash = "sha256:8d361dac0309f2b6ad33b349a56cd163c98430d409fa503b10b70b3ad66eaa1d", size = 3506616, upload-time = "2025-11-10T16:02:13.195Z" }
231wheels = [
232 { url = "https://files.pythonhosted.org/packages/75/d6/4b2dca33ed512de8f54e5c6074aa06eaeb225bfbcd9b16f33a414389d6bd/pynacl-1.6.1-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:7d7c09749450c385301a3c20dca967a525152ae4608c0a096fe8464bfc3df93d", size = 389109, upload-time = "2025-11-10T16:01:28.79Z" },
233 { url = "https://files.pythonhosted.org/packages/3c/30/e8dbb8ff4fa2559bbbb2187ba0d0d7faf728d17cb8396ecf4a898b22d3da/pynacl-1.6.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc734c1696ffd49b40f7c1779c89ba908157c57345cf626be2e0719488a076d3", size = 808254, upload-time = "2025-11-10T16:01:37.839Z" },
234 { url = "https://files.pythonhosted.org/packages/44/f9/f5449c652f31da00249638dbab065ad4969c635119094b79b17c3a4da2ab/pynacl-1.6.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3cd787ec1f5c155dc8ecf39b1333cfef41415dc96d392f1ce288b4fe970df489", size = 1407365, upload-time = "2025-11-10T16:01:40.454Z" },
235 { url = "https://files.pythonhosted.org/packages/eb/2f/9aa5605f473b712065c0a193ebf4ad4725d7a245533f0cd7e5dcdbc78f35/pynacl-1.6.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b35d93ab2df03ecb3aa506be0d3c73609a51449ae0855c2e89c7ed44abde40b", size = 843842, upload-time = "2025-11-10T16:01:30.524Z" },
236 { url = "https://files.pythonhosted.org/packages/32/8d/748f0f6956e207453da8f5f21a70885fbbb2e060d5c9d78e0a4a06781451/pynacl-1.6.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dece79aecbb8f4640a1adbb81e4aa3bfb0e98e99834884a80eb3f33c7c30e708", size = 1445559, upload-time = "2025-11-10T16:01:33.663Z" },
237 { url = "https://files.pythonhosted.org/packages/78/d0/2387f0dcb0e9816f38373999e48db4728ed724d31accdd4e737473319d35/pynacl-1.6.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c2228054f04bf32d558fb89bb99f163a8197d5a9bf4efa13069a7fa8d4b93fc3", size = 825791, upload-time = "2025-11-10T16:01:34.823Z" },
238 { url = "https://files.pythonhosted.org/packages/18/3d/ef6fb7eb072aaf15f280bc66f26ab97e7fc9efa50fb1927683013ef47473/pynacl-1.6.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:2b12f1b97346f177affcdfdc78875ff42637cb40dcf79484a97dae3448083a78", size = 1410843, upload-time = "2025-11-10T16:01:36.401Z" },
239 { url = "https://files.pythonhosted.org/packages/e3/fb/23824a017526850ee7d8a1cc4cd1e3e5082800522c10832edbbca8619537/pynacl-1.6.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e735c3a1bdfde3834503baf1a6d74d4a143920281cb724ba29fb84c9f49b9c48", size = 801140, upload-time = "2025-11-10T16:01:42.013Z" },
240 { url = "https://files.pythonhosted.org/packages/5d/d1/ebc6b182cb98603a35635b727d62f094bc201bf610f97a3bb6357fe688d2/pynacl-1.6.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3384a454adf5d716a9fadcb5eb2e3e72cd49302d1374a60edc531c9957a9b014", size = 1371966, upload-time = "2025-11-10T16:01:43.297Z" },
241 { url = "https://files.pythonhosted.org/packages/64/f4/c9d7b6f02924b1f31db546c7bd2a83a2421c6b4a8e6a2e53425c9f2802e0/pynacl-1.6.1-cp314-cp314t-win32.whl", hash = "sha256:d8615ee34d01c8e0ab3f302dcdd7b32e2bcf698ba5f4809e7cc407c8cdea7717", size = 230482, upload-time = "2025-11-10T16:01:47.688Z" },
242 { url = "https://files.pythonhosted.org/packages/c4/2c/942477957fba22da7bf99131850e5ebdff66623418ab48964e78a7a8293e/pynacl-1.6.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5f5b35c1a266f8a9ad22525049280a600b19edd1f785bccd01ae838437dcf935", size = 243232, upload-time = "2025-11-10T16:01:45.208Z" },
243 { url = "https://files.pythonhosted.org/packages/7a/0c/bdbc0d04a53b96a765ab03aa2cf9a76ad8653d70bf1665459b9a0dedaa1c/pynacl-1.6.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d984c91fe3494793b2a1fb1e91429539c6c28e9ec8209d26d25041ec599ccf63", size = 187907, upload-time = "2025-11-10T16:01:46.328Z" },
244 { url = "https://files.pythonhosted.org/packages/49/41/3cfb3b4f3519f6ff62bf71bf1722547644bcfb1b05b8fdbdc300249ba113/pynacl-1.6.1-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:a6f9fd6d6639b1e81115c7f8ff16b8dedba1e8098d2756275d63d208b0e32021", size = 387591, upload-time = "2025-11-10T16:01:49.1Z" },
245 { url = "https://files.pythonhosted.org/packages/18/21/b8a6563637799f617a3960f659513eccb3fcc655d5fc2be6e9dc6416826f/pynacl-1.6.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e49a3f3d0da9f79c1bec2aa013261ab9fa651c7da045d376bd306cf7c1792993", size = 798866, upload-time = "2025-11-10T16:01:55.688Z" },
246 { url = "https://files.pythonhosted.org/packages/e8/6c/dc38033bc3ea461e05ae8f15a81e0e67ab9a01861d352ae971c99de23e7c/pynacl-1.6.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7713f8977b5d25f54a811ec9efa2738ac592e846dd6e8a4d3f7578346a841078", size = 1398001, upload-time = "2025-11-10T16:01:57.101Z" },
247 { url = "https://files.pythonhosted.org/packages/9f/05/3ec0796a9917100a62c5073b20c4bce7bf0fea49e99b7906d1699cc7b61b/pynacl-1.6.1-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3becafc1ee2e5ea7f9abc642f56b82dcf5be69b961e782a96ea52b55d8a9fc", size = 834024, upload-time = "2025-11-10T16:01:50.228Z" },
248 { url = "https://files.pythonhosted.org/packages/f0/b7/ae9982be0f344f58d9c64a1c25d1f0125c79201634efe3c87305ac7cb3e3/pynacl-1.6.1-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ce50d19f1566c391fedc8dc2f2f5be265ae214112ebe55315e41d1f36a7f0a9", size = 1436766, upload-time = "2025-11-10T16:01:51.886Z" },
249 { url = "https://files.pythonhosted.org/packages/b4/51/b2ccbf89cf3025a02e044dd68a365cad593ebf70f532299f2c047d2b7714/pynacl-1.6.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:543f869140f67d42b9b8d47f922552d7a967e6c116aad028c9bfc5f3f3b3a7b7", size = 817275, upload-time = "2025-11-10T16:01:53.351Z" },
250 { url = "https://files.pythonhosted.org/packages/a8/6c/dd9ee8214edf63ac563b08a9b30f98d116942b621d39a751ac3256694536/pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a2bb472458c7ca959aeeff8401b8efef329b0fc44a89d3775cffe8fad3398ad8", size = 1401891, upload-time = "2025-11-10T16:01:54.587Z" },
251 { url = "https://files.pythonhosted.org/packages/0f/c1/97d3e1c83772d78ee1db3053fd674bc6c524afbace2bfe8d419fd55d7ed1/pynacl-1.6.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3206fa98737fdc66d59b8782cecc3d37d30aeec4593d1c8c145825a345bba0f0", size = 772291, upload-time = "2025-11-10T16:01:58.111Z" },
252 { url = "https://files.pythonhosted.org/packages/4d/ca/691ff2fe12f3bb3e43e8e8df4b806f6384593d427f635104d337b8e00291/pynacl-1.6.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:53543b4f3d8acb344f75fd4d49f75e6572fce139f4bfb4815a9282296ff9f4c0", size = 1370839, upload-time = "2025-11-10T16:01:59.252Z" },
253 { url = "https://files.pythonhosted.org/packages/30/27/06fe5389d30391fce006442246062cc35773c84fbcad0209fbbf5e173734/pynacl-1.6.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:319de653ef84c4f04e045eb250e6101d23132372b0a61a7acf91bac0fda8e58c", size = 791371, upload-time = "2025-11-10T16:02:01.075Z" },
254 { url = "https://files.pythonhosted.org/packages/2c/7a/e2bde8c9d39074a5aa046c7d7953401608d1f16f71e237f4bef3fb9d7e49/pynacl-1.6.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:262a8de6bba4aee8a66f5edf62c214b06647461c9b6b641f8cd0cb1e3b3196fe", size = 1363031, upload-time = "2025-11-10T16:02:02.656Z" },
255 { url = "https://files.pythonhosted.org/packages/dd/b6/63fd77264dae1087770a1bb414bc604470f58fbc21d83822fc9c76248076/pynacl-1.6.1-cp38-abi3-win32.whl", hash = "sha256:9fd1a4eb03caf8a2fe27b515a998d26923adb9ddb68db78e35ca2875a3830dde", size = 226585, upload-time = "2025-11-10T16:02:07.116Z" },
256 { url = "https://files.pythonhosted.org/packages/12/c8/b419180f3fdb72ab4d45e1d88580761c267c7ca6eda9a20dcbcba254efe6/pynacl-1.6.1-cp38-abi3-win_amd64.whl", hash = "sha256:a569a4069a7855f963940040f35e87d8bc084cb2d6347428d5ad20550a0a1a21", size = 238923, upload-time = "2025-11-10T16:02:04.401Z" },
257 { url = "https://files.pythonhosted.org/packages/35/76/c34426d532e4dce7ff36e4d92cb20f4cbbd94b619964b93d24e8f5b5510f/pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf", size = 183970, upload-time = "2025-11-10T16:02:05.786Z" },
258]
259
260[[package]]
261name = "requests"
262version = "2.32.5"
263source = { registry = "https://pypi.org/simple" }
264dependencies = [
265 { name = "certifi" },
266 { name = "charset-normalizer" },
267 { name = "idna" },
268 { name = "urllib3" },
269]
270sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
271wheels = [
272 { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
273]
274
275[[package]]
276name = "toml"
277version = "0.10.2"
278source = { registry = "https://pypi.org/simple" }
279sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
280wheels = [
281 { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
282]
283
284[[package]]
285name = "typing-extensions"
286version = "4.15.0"
287source = { registry = "https://pypi.org/simple" }
288sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
289wheels = [
290 { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
291]
292
293[[package]]
294name = "urllib3"
295version = "2.6.2"
296source = { registry = "https://pypi.org/simple" }
297sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
298wheels = [
299 { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
300]
301
302[[package]]
303name = "xdg-base-dirs"
304version = "6.0.2"
305source = { registry = "https://pypi.org/simple" }
306sdist = { url = "https://files.pythonhosted.org/packages/bf/d0/bbe05a15347538aaf9fa5b51ac3b97075dfb834931fcb77d81fbdb69e8f6/xdg_base_dirs-6.0.2.tar.gz", hash = "sha256:950504e14d27cf3c9cb37744680a43bf0ac42efefc4ef4acf98dc736cab2bced", size = 4085, upload-time = "2024-10-19T14:35:08.114Z" }
307wheels = [
308 { url = "https://files.pythonhosted.org/packages/fc/03/030b47fd46b60fc87af548e57ff59c2ca84b2a1dadbe721bb0ce33896b2e/xdg_base_dirs-6.0.2-py3-none-any.whl", hash = "sha256:3c01d1b758ed4ace150ac960ac0bd13ce4542b9e2cdf01312dcda5012cfebabe", size = 4747, upload-time = "2024-10-19T14:35:05.931Z" },
309]
diff --git a/accounts/gkleen@sif/utils/pdf2pdf.nix b/accounts/gkleen@sif/utils/pdf2pdf.nix
new file mode 100644
index 00000000..7305a201
--- /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:r}_pdf2pdf.pdf}" "''${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 e3f675a1..df0fe1d6 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[@]}" $(mktemp --tmpdir=${modifyPDF:h} ${modifyPDF:t:r}_XXXXXXXXXX.${modifyPDF:e})
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}
@@ -306,20 +292,7 @@ done < <(find ~/projects ~/uni -regextype posix-extended -maxdepth 2 -type d -re
306 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \ 292 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \
307 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2) 293 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2)
308 294
309alias '..'='cd ..'
310alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\'' 295alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\''
311alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .' 296alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .'
312alias -g L='| less'
313alias -g S='&> /dev/null'
314alias -g G='| grep'
315alias -g B='&> /dev/null &'
316alias -g BB='&> /dev/null &!'
317 297
318export DEFAULT_USER=gkleen 298export DEFAULT_USER=gkleen
319
320bindkey -e
321bindkey ';5C' emacs-forward-word
322bindkey ';5D' emacs-backward-word
323bindkey '^[[1;5C' emacs-forward-word
324bindkey '^[[1;5D' emacs-backward-word
325bindkey '^H' backward-kill-word
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 = {