summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif')
-rw-r--r--accounts/gkleen@sif/default.nix740
-rw-r--r--accounts/gkleen@sif/dunst-settings.nix45
-rw-r--r--accounts/gkleen@sif/dunstrc.d/00-urgency_low.conf4
-rw-r--r--accounts/gkleen@sif/dunstrc.d/01-urgency_normal.conf4
-rw-r--r--accounts/gkleen@sif/dunstrc.d/02-urgency_critical.conf4
-rw-r--r--accounts/gkleen@sif/dunstrc.d/10-brightness.conf5
-rw-r--r--accounts/gkleen@sif/dunstrc.d/10-pulseaudio-ctl.conf5
-rw-r--r--accounts/gkleen@sif/dunstrc.d/20-element.conf3
-rw-r--r--accounts/gkleen@sif/dunstrc.d/20-kitty.conf3
-rw-r--r--accounts/gkleen@sif/dunstrc.d/20-mail.conf3
-rw-r--r--accounts/gkleen@sif/dunstrc.d/20-zulip.conf3
-rw-r--r--accounts/gkleen@sif/emacs.el7
-rw-r--r--accounts/gkleen@sif/firefox-chrome.css30
-rw-r--r--accounts/gkleen@sif/hyprland.nix424
-rw-r--r--accounts/gkleen@sif/libvirt/default.nix21
-rw-r--r--accounts/gkleen@sif/niri/default.nix978
-rw-r--r--accounts/gkleen@sif/niri/mako.nix112
-rw-r--r--accounts/gkleen@sif/shell/default.nix115
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt168
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp83
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp55
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp102
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp52
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp18
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp21
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp198
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp147
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h7
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/default.nix30
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml28
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml445
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml146
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml817
-rw-r--r--accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml172
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Bar.qml117
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml136
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml117
-rw-r--r--accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml84
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Clock.qml295
-rw-r--r--accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml112
-rw-r--r--accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml47
-rw-r--r--accounts/gkleen@sif/shell/quickshell/LockSurface.qml227
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Lockscreen.qml132
-rw-r--r--accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml35
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NiriIdle.qml30
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml340
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml266
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml483
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml49
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml75
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml18
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml22
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml194
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml162
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml63
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/SystemTray.qml201
-rw-r--r--accounts/gkleen@sif/shell/quickshell/UnixIPC.qml97
-rw-r--r--accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml163
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml89
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml56
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml204
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml120
-rw-r--r--accounts/gkleen@sif/shell/quickshell/displaymanager.qml115
-rw-r--r--accounts/gkleen@sif/shell/quickshell/shell.qml53
-rw-r--r--accounts/gkleen@sif/ssh-hosts.nix85
-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.nix254
-rw-r--r--accounts/gkleen@sif/taffybar/default.nix2
-rw-r--r--accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal32
-rw-r--r--accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs111
-rw-r--r--accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs101
-rw-r--r--accounts/gkleen@sif/taffybar/src/taffybar.hs89
-rw-r--r--accounts/gkleen@sif/taffybar/taffybar.css146
-rw-r--r--accounts/gkleen@sif/utils/async-yt-dlp.nix57
-rw-r--r--accounts/gkleen@sif/utils/pdf2pdf.nix8
-rw-r--r--accounts/gkleen@sif/utils/sieve-edit.nix24
-rw-r--r--accounts/gkleen@sif/xmonad/.gitignore4
-rw-r--r--accounts/gkleen@sif/xmonad/default.nix7
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs127
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs94
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs105
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs246
-rw-r--r--accounts/gkleen@sif/xmonad/package.yaml31
-rw-r--r--accounts/gkleen@sif/xmonad/stack.nix17
-rw-r--r--accounts/gkleen@sif/xmonad/stack.yaml10
-rw-r--r--accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix21
-rw-r--r--accounts/gkleen@sif/xmonad/xmonad.hs939
-rw-r--r--accounts/gkleen@sif/zshrc153
91 files changed, 8529 insertions, 3271 deletions
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index 00707e87..36b722e4 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,35 +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 # lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service";
53 lockCommand = "${lib.getExe' cfg.programs.quickshell.package "qs"} ipc call Lockscreen setLocked true";
54
55 editor = pkgs.symlinkJoin {
56 inherit (cfg.services.emacs.package) name;
57 buildInputs = with pkgs; [ makeWrapper ];
58 paths = [ cfg.services.emacs.package ];
59 postBuild = ''
60 wrapProgram $out/bin/emacsclient \
61 --inherit-argv0 \
62 --add-flags ${lib.escapeShellArg (lib.escapeShellArgs cfg.services.emacs.client.arguments)}
63 '';
64 };
84in { 65in {
85 imports = with flake.nixosModules.userProfiles.${userName}; [ 66 imports = with flake.nixosModules.userProfiles.${userName}; [
86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 67 zsh tmux mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
87 ]; 68 ];
88 69
89 config = { 70 config = {
90 services.displayManager.defaultSession = "Hyprland"; # "none+xmonad";
91
92 home-manager.users.${userName} = { 71 home-manager.users.${userName} = {
93 imports = [ 72 imports = [
94 ./libvirt 73 ./libvirt
95 flakeInputs.nix-index-database.hmModules.nix-index 74 ./niri
75 ./shell
76 ./synadm
77 flakeInputs.nix-index-database.homeModules.nix-index
96 flakeInputs.impermanence.nixosModules.home-manager.impermanence 78 flakeInputs.impermanence.nixosModules.home-manager.impermanence
97 ]; 79 ];
98 80
99 home.stateVersion = "20.09"; 81 home.stateVersion = "20.09";
100 82
101 nixpkgs.config = { 83 # nixpkgs.config = {
102 allowUnfree = true; 84 # allowUnfree = true;
103 zathura.useMupdf = false; 85 # zathura.useMupdf = false;
104 }; 86 # };
105 87
106 nix.registry = { 88 nix.registry = {
107 "flk" = { 89 "flk" = {
@@ -111,14 +93,14 @@ in {
111 }; 93 };
112 to = { 94 to = {
113 type = "git"; 95 type = "git";
114 url = "file:///home/gkleen/config/nixos-flakes"; 96 url = "file:///home/gkleen/projects/machines";
115 }; 97 };
116 }; 98 };
117 }; 99 };
118 100
119 programs = { 101 programs = {
120 ssh = { 102 ssh = {
121 matchBlocks = import ./ssh-hosts.nix { inherit pkgs; }; # customUtils.nixImport { dir = ./ssh-hosts; }; 103 matchBlocks = import ./ssh-hosts.nix inputs; # customUtils.nixImport { dir = ./ssh-hosts; };
122 extraConfig = '' 104 extraConfig = ''
123 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"
124 ProxyJump remote.cip.ifi.lmu.de 106 ProxyJump remote.cip.ifi.lmu.de
@@ -136,8 +118,8 @@ in {
136 ''} 118 ''}
137 119
138 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"
139 # 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
140 ProxyJump ssh.math.lmu.de 122 # ProxyJump ssh.math.lmu.de
141 123
142 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"
143 ProxyJump cip04 125 ProxyJump cip04
@@ -154,22 +136,31 @@ in {
154 136
155 emacs = { 137 emacs = {
156 enable = true; 138 enable = true;
157 package = pkgs.emacs29-pgtk; 139 package = pkgs.emacs-pgtk;
158 extraPackages = epkgs: with epkgs; [ 140 extraPackages = epkgs: with epkgs; [
159 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode 141 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode
160 yaml-mode json-mode shakespeare-mode smart-mode-line 142 yaml-mode json-mode shakespeare-mode smart-mode-line
161 highlight-parentheses highlight-symbol ag sass-mode lua-mode 143 highlight-parentheses highlight-symbol ag sass-mode
162 fira-code-mode use-package wanderlust # notmuch 144 lua-mode fira-code-mode use-package wanderlust # notmuch
163 use-package-ensure-system-package git-gutter emacsScratch 145 git-gutter scratch edit-server mediawiki editorconfig
164 edit-server mediawiki editorconfig typescript-mode 146 typescript-mode markdown-mode nftables-mode rustic
165 markdown-mode nftables-mode rustic lsp-mode lsp-ui 147 lsp-mode lsp-ui direnv company projectile
166 direnv company projectile tomorrow-night-paradise-theme 148 tomorrow-night-paradise-theme
167 treesit-grammars.with-all-grammars magit-delta scad-mode 149 treesit-grammars.with-all-grammars magit-delta scad-mode
168 ]; 150 ];
169 overrides = self: super: { 151 overrides = self: super: {
170 tomorrow-night-paradise-theme = super.trivialBuild { 152 tomorrow-night-paradise-theme = super.trivialBuild {
171 inherit (sources.tomorrow-night-paradise-theme) pname version src; 153 inherit (sources.tomorrow-night-paradise-theme) pname version src;
172 }; 154 };
155 scratch = pkgs.stdenv.mkDerivation {
156 inherit (sources.emacs-scratch_el) pname version src;
157
158 phases = [ "unpackPhase" "installPhase" ];
159
160 installPhase = ''
161 install -Dt $out/share/emacs/site-lisp scratch.el
162 '';
163 };
173 }; 164 };
174 }; 165 };
175 firefox = { 166 firefox = {
@@ -183,8 +174,14 @@ in {
183 }; 174 };
184 }; 175 };
185 }; 176 };
177 chromium.enable = true;
186 178
187 zathura.enable = true; 179 zathura = {
180 enable = true;
181 options = {
182 scroll-page-aware = true;
183 };
184 };
188 imv.enable = true; 185 imv.enable = true;
189 186
190 mpv.config = { 187 mpv.config = {
@@ -193,13 +190,93 @@ in {
193 gpu-api = "vulkan"; 190 gpu-api = "vulkan";
194 }; 191 };
195 192
196 zsh.initExtra = '' 193 zsh.initContent = let
197 source ${./zshrc} 194 zshrc = pkgs.resholve.mkDerivation {
195 pname = "zshrc";
196 version = "0.0.0";
197
198 src = ./zshrc;
199
200 dontUnpack = true;
201 dontConfigure = true;
202 dontBuild = true;
203
204 installPhase = ''
205 mkdir -p $out/share
206 install "$src" $out/share/zshrc
207 '';
208
209 solutions = {
210 default = {
211 scripts = [ "share/zshrc" ];
212 interpreter = "none";
213 inputs = with pkgs; [
214 coreutils
215 rpm
216 binutils
217 squashfsTools
218 unzip
219 cfg.programs.git.package
220 magickWrapped
221 curl
222 file
223 gnutar
224 cpio
225 magic-wormhole
226 cfg.programs.zsh.package
227 fuse
228 util-linux
229 findutils
230 qrencode
231 tty-clock
232 cfg.programs.jq.package
233 eza
234 less
235 config.systemd.package
236 config.programs.ssh.package
237 gnused
238 miniserve
239 p7zip
240 ];
241 execer = with pkgs; [
242 "cannot:${lib.getExe' rpm "rpm2cpio"}"
243 "cannot:${lib.getExe' squashfsTools "unsquashfs"}"
244 "cannot:${lib.getExe' unzip "unzip"}"
245 "cannot:${lib.getExe cfg.programs.git.package}"
246 "cannot:${lib.getExe cpio}"
247 "cannot:${lib.getExe' magic-wormhole "wormhole"}"
248 "cannot:${lib.getExe' fuse "fusermount"}"
249 "cannot:${lib.getExe less}"
250 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
251 "cannot:${lib.getExe config.programs.ssh.package}"
252 "cannot:${lib.getExe' p7zip "7z"}"
253 ];
254 wrapper = with pkgs; [
255 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
256 ];
257 fake = {
258 builtin = ["print"];
259 external = ["sudo" "umount"];
260 };
261 };
262 };
263 };
264 magickWrapped = pkgs.symlinkJoin {
265 inherit (pkgs.imagemagick) name;
266 paths = [ pkgs.imagemagick ];
267
268 buildInputs = with pkgs; [ makeWrapper ];
269 postBuild = ''
270 wrapProgram $out/bin/magick \
271 --prefix PATH : ${lib.makeBinPath (with pkgs; [ ghostscript ])}
272 '';
273 };
274 in ''
275 source ${zshrc}/share/zshrc
198 ''; 276 '';
199 zsh.dirHashes = let 277 zsh.dirHashes = let
200 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs; 278 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs;
201 inputNames = { 279 inputNames = {
202 "nixpkgs" = "nixos";
203 }; 280 };
204 in flakeHashes // { 281 in flakeHashes // {
205 u2w = "$HOME/projects/uni2work"; 282 u2w = "$HOME/projects/uni2work";
@@ -211,6 +288,16 @@ in {
211 pro = "$HOME/projects/pro"; 288 pro = "$HOME/projects/pro";
212 media = "$HOME/media"; 289 media = "$HOME/media";
213 }; 290 };
291 jq.colors = {
292 arrays = "1;37";
293 "false" = "0;37";
294 "null" = "2;37";
295 numbers = "0;37";
296 objectKeys = "1;34";
297 objects = "1;37";
298 strings = "0;32";
299 "true" = "0;37";
300 };
214 301
215 obs-studio = { 302 obs-studio = {
216 enable = true; 303 enable = true;
@@ -220,7 +307,7 @@ in {
220 gh = { 307 gh = {
221 enable = true; 308 enable = true;
222 settings = { 309 settings = {
223 editor = "${config.home-manager.users.${userName}.programs.emacs.package}/bin/emacsclient"; 310 editor = lib.getExe' editor "emacsclient";
224 gitProtocol = "ssh"; 311 gitProtocol = "ssh";
225 }; 312 };
226 }; 313 };
@@ -246,309 +333,23 @@ in {
246 # notify_on_cmd_finish = "invisible 120"; 333 # notify_on_cmd_finish = "invisible 120";
247 }; 334 };
248 keybindings = { 335 keybindings = {
249 "kitty_mod+n" = "detach_window"; 336 "kitty_mod+n" = "new_os_window_with_cwd";
250 "kitty_mod+m" = "detach_window ask"; 337 "kitty_mod+m" = "detach_window ask";
251 }; 338 "kitty_mod+enter" = "new_window_with_cwd";
252 }; 339 "kitty_mod+t" = "new_tab_with_cwd";
253 waybar = {
254 enable = true;
255 systemd = {
256 enable = true;
257 target = "hyprland-session.target";
258 };
259 settings = let
260 windowRewrites = {
261 "(.*) — Mozilla Firefox" = "$1";
262 "(.*) - Mozilla Thunderbird" = "$1";
263 "(.*) - mpv" = "$1";
264 };
265 iconSize = 11;
266 in [
267 {
268 layer = "top";
269 position = "top";
270 height = 14;
271 output = [ "eDP-1" "DP-2" "DP-3" ];
272 modules-left = [ "hyprland/workspaces" ];
273 modules-center = [ "hyprland/window" ];
274 modules-right = [ "custom/worktime" "custom/worktime-today" "custom/weather" "custom/keymap" "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "clock" ];
275
276 "custom/weather" = {
277 format = "{}";
278 tooltip = true;
279 interval = 3600;
280 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"120%\\\">{ICON}</span> {FeelsLikeC}°\"";
281 return-type = "json";
282 };
283 "custom/keymap" = {
284 format = "{}";
285 tooltip = true;
286 return-type = "json";
287 exec = pkgs.writers.writePython3 "keymap" {} ''
288 import os
289 import socket
290 import re
291 import subprocess
292 import json
293
294
295 def output(keymap):
296 short = keymap
297 if keymap == "English (programmer Dvorak)":
298 short = "dvp"
299 elif keymap == "English (US)":
300 short = "<span color=\"#ffffff\">us</span>"
301 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
302
303
304 r = subprocess.run(["hyprctl", "devices", "-j"], check=True, stdout=subprocess.PIPE, text=True) # noqa: E501
305 for keyboard in json.loads(r.stdout)['keyboards']:
306 if keyboard['name'] != "at-translated-set-2-keyboard":
307 continue
308 output(keyboard['active_keymap'])
309
310 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
311 sock.connect(os.environ["XDG_RUNTIME_DIR"] + "/hypr/" + os.environ["HYPRLAND_INSTANCE_SIGNATURE"] + "/.socket2.sock") # noqa: E501
312 expected = re.compile(r'^activelayout>>at-translated-set-2-keyboard,(?P<keymap>.+)$') # noqa: E501
313 for line in sock.makefile(buffering=1, encoding='utf-8'):
314 if match := expected.match(line):
315 output(match.group("keymap"))
316 '';
317 on-click = "hyprctl switchxkblayout at-translated-set-2-keyboard next";
318 };
319 "custom/worktime" = {
320 interval = 60;
321 exec = getExe pkgs.worktime;
322 tooltip = false;
323 };
324 "custom/worktime-today" = {
325 interval = 60;
326 exec = "${getExe pkgs.worktime} today";
327 tooltip = false;
328 };
329 "hyprland/workspaces" = {
330 all-outputs = true;
331 };
332 "hyprland/window" = {
333 separate-outputs = true;
334 icon = true;
335 icon-size = 14;
336 rewrite = windowRewrites;
337 };
338 clock = {
339 interval = 1;
340 # timezone = "Europe/Berlin";
341 format = "W{:%V-%u %F %H:%M:%S%Ez}";
342 tooltip-format = "<tt><small>{calendar}</small></tt>";
343 calendar = {
344 mode = "year";
345 mode-mon-col = 3;
346 weeks-pos = "left";
347 on-scroll = 1;
348 format = {
349 months = "<span color='#ffead3'><b>{}</b></span>";
350 days = "{}";
351 weeks = "<span color='#99ffdd'><b>{}</b></span>";
352 weekdays = "<span color='#ffcc66'><b>{}</b></span>";
353 today = "<span color='#ff6699'><b>{}</b></span>";
354 };
355 };
356 };
357 battery = {
358 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
359 icon-size = iconSize - 2;
360 states = { warning = 30; critical = 15; };
361 format-icons = ["&#xf008e;" "&#xf007a;" "&#xf007b;" "&#xf007c;" "&#xf007d;" "&#xf007e;" "&#xf007f;" "&#xf0080;" "&#xf0081;" "&#xf0082;" "&#xf0079;" ];
362 format-charging = "&#xf0084;";
363 format-plugged = "&#xf06a5;";
364 tooltip-format = "{capacity}% {timeTo}";
365 interval = 20;
366 };
367 tray = {
368 icon-size = 16;
369 # show-passive-items = true;
370 spacing = 1;
371 };
372 privacy = {
373 icon-spacing = 7;
374 icon-size = iconSize;
375 modules = [
376 { type = "screenshare"; }
377 { type = "audio-in"; }
378 ];
379 };
380 idle_inhibitor = {
381 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
382 icon-size = iconSize;
383 format-icons = { activated = "&#xf0208;"; deactivated = "&#xf0209;"; };
384 timeout = 120;
385 };
386 backlight = {
387 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
388 icon-size = iconSize;
389 tooltip-format = "{percent}%";
390 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
391 on-scroll-up = "lightctl -d -e4 -n1 up";
392 on-scroll-down = "lightctl -d -e4 -n1 down";
393 };
394 wireplumber = {
395 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
396 icon-size = iconSize;
397 tooltip-format = "{volume}% {node_name}";
398 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
399 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
400 # ignored-sinks = ["Easy Effects Sink"];
401 on-scroll-up = "volumectl -d -u up";
402 on-scroll-down = "volumectl -d -u down";
403 on-click = "volumectl -d toggle-mute";
404 };
405 }
406 {
407 layer = "top";
408 position = "top";
409 height = 14;
410 output = [ "!eDP-1" "!DP-2" "!DP-3" ];
411 modules-left = [ "hyprland/workspaces" ];
412 modules-center = [ "hyprland/window" ];
413 modules-right = [ "clock" ];
414
415 "hyprland/workspaces" = {
416 all-outputs = false;
417 };
418 "hyprland/window" = {
419 separate-outputs = true;
420 icon = true;
421 icon-size = 14;
422 rewrite = windowRewrites;
423 };
424 clock = {
425 interval = 1;
426 # timezone = "Europe/Berlin";
427 format = "{:%H:%M}";
428 tooltip-format = "W{:%V-%u %F %H:%M:%S%Ez}";
429 };
430 }
431 ];
432 style = ''
433 @define-color white #ffffff;
434 @define-color grey #555555;
435 @define-color blue #1a8fff;
436 @define-color green #23fd00;
437 @define-color orange #f28a21;
438 @define-color red #f2201f;
439
440 * {
441 border: none;
442 font-family: "Fira Sans Nerd Font";
443 font-size: 10pt;
444 min-height: 0;
445 }
446
447 window#waybar {
448 background-color: rgba(0, 0, 0, 0.66);
449 color: @white;
450 }
451
452 .modules-left {
453 margin-left: 9px;
454 }
455 .modules-right {
456 margin-right: 9px;
457 }
458
459 .module {
460 margin: 0 5px;
461 }
462
463 #workspaces button {
464 color: @grey;
465 }
466 #workspaces button.hosting-monitor {
467 color: @white;
468 }
469 #workspaces button.visible {
470 color: @blue;
471 }
472 #workspaces button.active {
473 color: @green;
474 }
475 #workspaces button.urgent {
476 color: @red;
477 }
478
479 #custom-weather, #custom-keymap, #custom-worktime, #custom-worktime-today {
480 color: @grey;
481 margin: 0 5px;
482 }
483 #custom-weather, #custom-worktime-today {
484 margin-right: 3px;
485 }
486 #custom-keymap, #custom-weather {
487 margin-left: 3px;
488 }
489
490 #tray {
491 margin: 0;
492 }
493 #battery, #idle_inhibitor, #backlight, #wireplumber {
494 color: @grey;
495 margin: 0 5px 0 2px;
496 }
497 #idle_inhibitor {
498 margin-right: 2px;
499 margin-left: 3px;
500 }
501 #battery {
502 margin-right: 3px;
503 }
504 #battery.discharging {
505 color: @white;
506 }
507 #battery.warning {
508 color: @orange;
509 }
510 #battery.critical {
511 color: @red;
512 }
513 #battery.charging {
514 color: @white;
515 }
516 #idle_inhibitor.activated {
517 color: @white;
518 }
519
520 #idle_inhibitor {
521 padding-top: 1px;
522 }
523
524 #privacy {
525 color: @red;
526 margin: -1px 2px 0px 5px;
527 }
528 #clock {
529 /* margin-right: 5px; */
530 }
531 '';
532 };
533 wpaperd = {
534 enable = true;
535 settings.default = {
536 path = "~/.wallpapers";
537 duration = "8h";
538 mode = "center";
539 }; 340 };
540 }; 341 };
541 fuzzel = { 342 fuzzel = {
542 enable = true; 343 enable = true;
543 settings = { 344 settings = {
544 main = { 345 main = {
545 terminal = lib.getExe pkgs.kitty; 346 terminal = lib.getExe cfg.programs.kitty.package;
546 layer = "overlay"; 347 layer = "overlay";
547 icon-theme = "Paper"; 348 icon-theme = "Paper";
548 font = "Fira Sans"; 349 font = "Fira Sans";
549 }; 350 };
550 colors = { 351 colors = {
551 background = "000000aa"; 352 background = "000000cc";
552 text = "cdd6f4ff"; 353 text = "cdd6f4ff";
553 match = "94e2d5ff"; 354 match = "94e2d5ff";
554 selection = "585b70ff"; 355 selection = "585b70ff";
@@ -561,34 +362,46 @@ in {
561 }; 362 };
562 }; 363 };
563 }; 364 };
365 pandoc = {
366 enable = true;
367 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."];
368 };
369 nushell = {
370 enable = true;
371 settings.show_banner = false;
372 };
373 fd.enable = true;
564 }; 374 };
565 375
566 services = { 376 services = {
567 dunst = { 377 wpaperd = {
568 settings = import ./dunst-settings.nix inputs; 378 enable = false;
569 iconTheme = { 379 settings.default = {
570 package = pkgs.paper-icon-theme; 380 path = "~/.wallpapers";
571 name = "Paper"; 381 duration = "15m";
382 mode = "center";
572 }; 383 };
573 enable = true;
574 }; 384 };
575 emacs = { 385 emacs = {
576 enable = true; 386 enable = true;
577 socketActivation.enable = true; 387 socketActivation.enable = true;
578 client = { 388 client = {
579 enable = true; 389 enable = true;
580 arguments = mkForce ["--reuse-frame" "--alternate-editor" "\"\""]; 390 arguments = mkForce ["--create-frame" "--alternate-editor" (lib.getExe cfg.services.emacs.package)];
581 }; 391 };
582 }; 392 };
583 gpg-agent = { 393 gpg-agent = {
584 enable = true; 394 enable = true;
585 enableSshSupport = true; 395 enableSshSupport = true;
586 extraConfig = '' 396 extraConfig = ''
587 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry 397 pinentry-program ${lib.getExe' pkgs.pinentry-gtk2 "pinentry"}
588 grab 398 grab
589 ''; 399 '';
590 }; 400 };
591 xembed-sni-proxy.enable = true; 401 xembed-sni-proxy = {
402 enable = true;
403 package = pkgs.kdePackages.plasma-workspace;
404 };
592 udiskie = { 405 udiskie = {
593 enable = true; 406 enable = true;
594 automount = false; 407 automount = false;
@@ -599,6 +412,12 @@ in {
599 notification_actions = { 412 notification_actions = {
600 device_mounted = []; 413 device_mounted = [];
601 }; 414 };
415 device_config = [
416 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; }
417 { mount_path = "/run/nixos-etc-metadata"; ignore = true; }
418 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; }
419 ];
420 icon_names.media = ["drive-removable-media-symbolic"];
602 }; 421 };
603 }; 422 };
604 network-manager-applet.enable = true; 423 network-manager-applet.enable = true;
@@ -615,7 +434,7 @@ in {
615 batch = "true"; 434 batch = "true";
616 log = "false"; 435 log = "false";
617 repeat = "watch"; 436 repeat = "watch";
618 sshcmd = "${pkgs.openssh}/bin/ssh"; 437 sshcmd = lib.getExe' pkgs.openssh "ssh";
619 ui = "text"; 438 ui = "text";
620 }; 439 };
621 }; 440 };
@@ -631,35 +450,7 @@ in {
631 serverUrl = "https://etesync.yggdrasil.li"; 450 serverUrl = "https://etesync.yggdrasil.li";
632 }; 451 };
633 452
634 swayidle = {
635 enable = true;
636 events = [
637 { event = "before-sleep"; command = lockCommand; }
638 { event = "after-resume"; command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms on"; }
639 { event = "lock"; command = lockCommand; }
640 ];
641 timeouts = [
642 { timeout = 300;
643 command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off";
644 }
645 { timeout = 330; command = lockCommand; }
646 ];
647 extraArgs = [
648 "idlehint" "30"
649 ];
650 };
651 poweralertd.enable = true; 453 poweralertd.enable = true;
652 avizo = {
653 enable = true;
654 settings.default = {
655 time = "1.0";
656 background = "rgba(0, 0, 0, 0.8)";
657 border-color = "rgba(0, 0, 0, 1)";
658 bar-fg-color = "rgba(160, 160, 160, 1)";
659 bar-bg-color = "rgba(32, 32, 32, 0.96)";
660 # y-offset = "0.25";
661 };
662 };
663 }; 454 };
664 455
665 home.pointerCursor = { 456 home.pointerCursor = {
@@ -690,6 +481,15 @@ in {
690 name = "Paper-Mono-Dark"; 481 name = "Paper-Mono-Dark";
691 }; 482 };
692 }; 483 };
484 qt.enable = true;
485 qt.platformTheme.name = "gtk";
486
487 qt.kde.settings = {
488 kwalletrc = {
489 KSecretD.Enabled = false;
490 Wallet."Default Wallet" = "store";
491 };
492 };
693 493
694 xsession.preferStatusNotifierItems = true; 494 xsession.preferStatusNotifierItems = true;
695 495
@@ -699,18 +499,19 @@ in {
699 packages = with pkgs; [ 499 packages = with pkgs; [
700 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 500 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
701 mumble pulseaudio-ctl pamixer libnotify screen-message 501 mumble pulseaudio-ctl pamixer libnotify screen-message
702 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 502 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
703 thunderbird zoom-us steam steam-run wireshark virt-manager 503 thunderbird zoom-us xdg-desktop-portal steam steam-run
704 rclone cached-nix-shell worktime fira-code-symbols 504 wireshark virt-manager rclone cached-nix-shell worktime
705 libreoffice xournalpp google-chrome nixos-shell virt-viewer 505 fira-code-symbols libreoffice xournalpp
706 freerdp gnome-icon-theme paper-icon-theme sshpassSecret 506 nixos-shell virt-viewer freerdp gnome-icon-theme
707 weechat element-desktop matrix-synapse-tools.synadm 507 paper-icon-theme sshpassSecret weechat element-desktop
708 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs 508 sieve-connect gimp3 inkscape udiskie glab nitrokey-app
709 sieve-connect gimp inkscape udiskie glab nitrokey-app
710 pynitrokey gtklock wlrctl remmina openscad spice-record 509 pynitrokey gtklock wlrctl remmina openscad spice-record
711 libguestfs-with-appliance nerd-fonts.fira-mono 510 nerd-fonts.fira-mono
712 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 511 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
713 ]; 512 swtpm (hunspell.withDicts (dicts: with dicts; [en_GB-large de_DE]))
513 libation libqalculate
514 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; });
714 515
715 file = { 516 file = {
716 ".backup-munin".source = ./backup-patterns; 517 ".backup-munin".source = ./backup-patterns;
@@ -727,15 +528,12 @@ in {
727 sessionVariables = { 528 sessionVariables = {
728 # GDK_SCALE = 96.0 / 282.0; 529 # GDK_SCALE = 96.0 / 282.0;
729 # QT_AUTO_SCREEN_SCALE_FACTOR = 1; 530 # QT_AUTO_SCREEN_SCALE_FACTOR = 1;
730 QT_QPA_PLATFORMTHEME = "qt5ct"; 531 QT_QPA_PLATFORMTHEME = lib.mkForce "gtk3";
731 LIBVIRT_DEFAULT_URI = "qemu:///system"; 532 LIBVIRT_DEFAULT_URI = "qemu:///system";
732 STACK_XDG = 1; 533 STACK_XDG = 1;
733 EDITOR = pkgs.writeShellScript "editor" '' 534 EDITOR = lib.getExe' editor "emacsclient";
734 args=("--reuse-frame" "--alternate-editor" "") 535 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
735 args+=("$@") 536 SYSTEMD_TINT_BACKGROUND = "false";
736 exec -a emacsclient ${cfg.services.emacs.package}/bin/emacsclient "''${args[@]}"
737 '';
738 RCLONE_PASSWORD_COMMAND = "${pkgs.libsecret}/bin/secret-tool lookup service rclone";
739 }; 537 };
740 538
741 extraProfileCommands = '' 539 extraProfileCommands = ''
@@ -744,18 +542,11 @@ in {
744 }; 542 };
745 543
746 xdg.configFile = { 544 xdg.configFile = {
747 "dunst/dunstrc.d" = {
748 source = ./dunstrc.d;
749 recursive = true;
750 onChange = ''
751 ${pkgs.systemd}/bin/systemctl --user try-restart dunst
752 '';
753 };
754 "wireplumber" = { 545 "wireplumber" = {
755 source = ./wireplumber; 546 source = ./wireplumber;
756 recursive = true; 547 recursive = true;
757 onChange = '' 548 onChange = ''
758 ${pkgs.systemd}/bin/systemctl --user try-restart wireplumber 549 ${lib.getExe' config.systemd.package "systemctl"} --user try-restart wireplumber
759 ''; 550 '';
760 }; 551 };
761 "stack/config.yaml" = { 552 "stack/config.yaml" = {
@@ -779,37 +570,36 @@ in {
779 General = { 570 General = {
780 dot_as_separator = 0; 571 dot_as_separator = 0;
781 }; 572 };
573 Mode = {
574 calculate_as_you_type = 1;
575 };
782 }; 576 };
783 }; 577 };
784 "emacs/init.el".source = ./emacs.el; 578 "emacs/init.el".source = pkgs.substitute {
579 src = ./emacs.el;
580 substitutions = [
581 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
582 ];
583 };
584 # "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
585 # [Unit]
586 # After=graphical-session.target
587 # '';
588 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = ''
589 [Unit]
590 Before=graphical-session-pre.target
591 '';
592 "pdfpc/pdfpcrc".text = ''
593 mouse 8 prev
594 mouse 9 next
595 '';
785 }; 596 };
786 597
787 xdg.dataFile = { 598 xdg.dataFile = {
788 "pandoc/abbreviations" = {
789 source = pkgs.runCommand "pandoc-abbreviations" {
790 buildInputs = [ pkgs.pandoc pkgs.coreutils ];
791 } (let
792 germanAbbrevs = pkgs.fetchFromGitHub {
793 owner = "jfilter";
794 repo = "german-abbreviations";
795 rev = "8eb9dae93b6f05d7c53374cd217ab2dc89558e0c";
796 sha256 = "SaD3tSqzen6Y3SPICe6/9vhe4iMHlArZ3kFQaEk7Hps=";
797 };
798 in ''
799 cat \
800 <(pandoc --print-default-data-file=abbreviations) \
801 <(grep -E '^[^ ]+\.$' ${germanAbbrevs}/german_abbreviations.txt) \
802 ${pkgs.writeText "abbrevs.txt" ''
803 i.A.
804 d.h.
805 D.h.
806 gdw.
807 ''} \
808 | sort | uniq >$out
809 '');
810 };
811 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 599 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
812 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 600 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service";
601 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
602 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
813 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 603 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
814 inherit (sources.emoji-data) pname src; 604 inherit (sources.emoji-data) pname src;
815 version = lib.removePrefix "v" sources.emoji-data.version; 605 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -895,19 +685,68 @@ in {
895 name = "Rainbow"; 685 name = "Rainbow";
896 exec = toString (pkgs.writeShellScript "rainbow" '' 686 exec = toString (pkgs.writeShellScript "rainbow" ''
897 exec -- \ 687 exec -- \
898 ${config.systemd.package}/bin/systemd-run --wait --user --slice-inherit \ 688 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
899 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 689 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
900 --property 'Environment=DSCP=46' \ 690 -E DSCP=46 -E NIXOS_OZONE_WL \
901 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \ 691 -- ${lib.getExe pkgs.dscp} ${lib.getExe cfg.programs.chromium.package} \
902 --force-device-scale-factor=1.5 \
903 --class=Rainbow \ 692 --class=Rainbow \
904 --kiosk "https://web.openrainbow.com" \ 693 --app="https://web.openrainbow.com" \
905 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 694 --user-data-dir=''${HOME}/.config/chromium-rainbow
906 ''); 695 '');
907 icon = pkgs.fetchurl { 696 icon = pkgs.fetchurl {
908 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 697 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
909 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU="; 698 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU=";
910 }; 699 };
700 settings = {
701 StartupWMClass = "Rainbow";
702 };
703 };
704 kimai = {
705 name = "Kimai";
706 exec = toString (pkgs.writeShellScript "kimai" ''
707 exec -- \
708 ${lib.getExe cfg.programs.chromium.package} \
709 --class=Kimai \
710 --app="https://kimai.yggdrasil.li" \
711 --user-data-dir=''${HOME}/.config/chromium-kimai
712 '');
713 icon = pkgs.fetchurl {
714 url = "https://www.kimai.org/images/kimai_logo.png";
715 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
716 };
717 settings = {
718 StartupWMClass = "Kimai";
719 StartupNotify = "true";
720 };
721 };
722 audiobookshelf = {
723 name = "Audiobookshelf";
724 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
725 exec -- \
726 ${lib.getExe cfg.programs.chromium.package} \
727 --class=Audiobookshelf \
728 --app="https://audiobookshelf.yggdrasil.li" \
729 --user-data-dir=''${HOME}/.config/chromium-audiobookshelf
730 '');
731 icon = pkgs.fetchurl {
732 url = "https://www.audiobookshelf.org/Logo.png";
733 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
734 };
735 settings = {
736 StartupWMClass = "Audiobookshelf";
737 StartupNotify = "true";
738 };
739 };
740 thunderbird-lmu = {
741 name = "Thunderbird (LMU)";
742 exec = "thunderbird --name thunderbird -P lmu %U";
743 icon = "thunderbird";
744 genericName = "Email Client";
745 categories = [ "Network" "Chat" "Email" "Feed" "GTK" "News" ];
746 settings = {
747 StartupWMClass = "thunderbird";
748 StartupNotify = "true";
749 };
911 }; 750 };
912 }; 751 };
913 752
@@ -923,23 +762,6 @@ in {
923 color-scheme = "prefer-dark"; 762 color-scheme = "prefer-dark";
924 }; 763 };
925 }; 764 };
926
927 wayland.windowManager.hyprland = {
928 enable = true;
929 settings = import ./hyprland.nix inputs;
930 };
931
932 xdg.portal = {
933 enable = true;
934 xdgOpenUsePortal = true;
935 config = {
936 common.default = [ "gtk" ];
937 hyprland.default = [ "gtk" "kde" "hyprland" ];
938 };
939 extraPortals = with pkgs; [
940 xdg-desktop-portal-kde xdg-desktop-portal-gtk xdg-desktop-portal-wlr xdg-desktop-portal-hyprland
941 ];
942 };
943 }; 765 };
944 }; 766 };
945} 767}
diff --git a/accounts/gkleen@sif/dunst-settings.nix b/accounts/gkleen@sif/dunst-settings.nix
deleted file mode 100644
index 72687aea..00000000
--- a/accounts/gkleen@sif/dunst-settings.nix
+++ /dev/null
@@ -1,45 +0,0 @@
1{ pkgs, ... }:
2{
3 global = {
4 font = "Fira Sans 12";
5 markup = "full";
6 format = "<i>%s</i> %p\\n%b";
7 alignment = "left";
8 # geometry = "1216x10-32+64";
9 width = 500;
10 height = 100;
11 offset = "4x4";
12 origin = "top-right";
13 shrink = true;
14 monitor = 0;
15 follow = "none";
16 padding = 6;
17 horizontal_padding = 6;
18 separator_height = 1;
19 separator_color = "frame";
20 idle_threshold = 0;
21
22 transparency = 10;
23
24 frame_width = 1;
25 frame_color = "#999999";
26
27 word_wrap = true;
28 show_age_threshold = 15;
29 show_indicators = false;
30 icon_position = "right";
31 min_icon_size = 25;
32 max_icon_size = 25;
33 sort = false;
34 sticky_history = false;
35
36 dmenu = "fuzzel --dmenu";
37 browser = "${pkgs.xdg-utils}/bin/xdg-open";
38 };
39 # shortcuts = {
40 # close = "ctrl+space";
41 # close_all = "ctrl+shift+space";
42 # history = "ctrl+comma";
43 # context = "ctrl+period";
44 # };
45}
diff --git a/accounts/gkleen@sif/dunstrc.d/00-urgency_low.conf b/accounts/gkleen@sif/dunstrc.d/00-urgency_low.conf
deleted file mode 100644
index 98c94b64..00000000
--- a/accounts/gkleen@sif/dunstrc.d/00-urgency_low.conf
+++ /dev/null
@@ -1,4 +0,0 @@
1[urgency_low]
2background="#000000aa"
3foreground="#999999"
4timeout=5 \ No newline at end of file
diff --git a/accounts/gkleen@sif/dunstrc.d/01-urgency_normal.conf b/accounts/gkleen@sif/dunstrc.d/01-urgency_normal.conf
deleted file mode 100644
index f8fa8e2d..00000000
--- a/accounts/gkleen@sif/dunstrc.d/01-urgency_normal.conf
+++ /dev/null
@@ -1,4 +0,0 @@
1[urgency_normal]
2background="#000000aa"
3foreground="#ffffff"
4timeout=15 \ No newline at end of file
diff --git a/accounts/gkleen@sif/dunstrc.d/02-urgency_critical.conf b/accounts/gkleen@sif/dunstrc.d/02-urgency_critical.conf
deleted file mode 100644
index a08bf4b1..00000000
--- a/accounts/gkleen@sif/dunstrc.d/02-urgency_critical.conf
+++ /dev/null
@@ -1,4 +0,0 @@
1[urgency_critical]
2background="#900000aa"
3foreground="#ffffff"
4timeout=0
diff --git a/accounts/gkleen@sif/dunstrc.d/10-brightness.conf b/accounts/gkleen@sif/dunstrc.d/10-brightness.conf
deleted file mode 100644
index c54595ab..00000000
--- a/accounts/gkleen@sif/dunstrc.d/10-brightness.conf
+++ /dev/null
@@ -1,5 +0,0 @@
1[brightness]
2appname="brightness"
3set_stack_tag="brightness"
4set_transient=yes
5history_ignore=yes
diff --git a/accounts/gkleen@sif/dunstrc.d/10-pulseaudio-ctl.conf b/accounts/gkleen@sif/dunstrc.d/10-pulseaudio-ctl.conf
deleted file mode 100644
index 074f4535..00000000
--- a/accounts/gkleen@sif/dunstrc.d/10-pulseaudio-ctl.conf
+++ /dev/null
@@ -1,5 +0,0 @@
1[pulseaudio-ctl]
2body="Current is *"
3history_ignore=yes
4set_stack_tag="volume"
5summary="Volume *"
diff --git a/accounts/gkleen@sif/dunstrc.d/20-element.conf b/accounts/gkleen@sif/dunstrc.d/20-element.conf
deleted file mode 100644
index 5ff6031e..00000000
--- a/accounts/gkleen@sif/dunstrc.d/20-element.conf
+++ /dev/null
@@ -1,3 +0,0 @@
1[element-im]
2appname=Element
3timeout=0 \ No newline at end of file
diff --git a/accounts/gkleen@sif/dunstrc.d/20-kitty.conf b/accounts/gkleen@sif/dunstrc.d/20-kitty.conf
deleted file mode 100644
index b27ee27e..00000000
--- a/accounts/gkleen@sif/dunstrc.d/20-kitty.conf
+++ /dev/null
@@ -1,3 +0,0 @@
1[kitty]
2appname=kitty
3urgency=low
diff --git a/accounts/gkleen@sif/dunstrc.d/20-mail.conf b/accounts/gkleen@sif/dunstrc.d/20-mail.conf
deleted file mode 100644
index cb568e01..00000000
--- a/accounts/gkleen@sif/dunstrc.d/20-mail.conf
+++ /dev/null
@@ -1,3 +0,0 @@
1[element]
2appname="notmuch"
3timeout=0 \ No newline at end of file
diff --git a/accounts/gkleen@sif/dunstrc.d/20-zulip.conf b/accounts/gkleen@sif/dunstrc.d/20-zulip.conf
deleted file mode 100644
index d7fbd32c..00000000
--- a/accounts/gkleen@sif/dunstrc.d/20-zulip.conf
+++ /dev/null
@@ -1,3 +0,0 @@
1[zulip]
2appname="Zulip"
3timeout=0 \ No newline at end of file
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index b1b1b198..3beefba6 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -14,7 +14,7 @@
14(setq package-archives nil) 14(setq package-archives nil)
15(package-initialize) 15(package-initialize)
16(require 'use-package) 16(require 'use-package)
17(use-package use-package-ensure-system-package :ensure t) 17;; (use-package use-package-ensure-system-package :ensure t)
18 18
19(require 'evil) 19(require 'evil)
20(evil-mode 1) 20(evil-mode 1)
@@ -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,5 @@ 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@")
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/hyprland.nix b/accounts/gkleen@sif/hyprland.nix
deleted file mode 100644
index d3061c61..00000000
--- a/accounts/gkleen@sif/hyprland.nix
+++ /dev/null
@@ -1,424 +0,0 @@
1{ pkgs, lib, config, userName, ... }:
2let
3 cfg = config.home-manager.users.${userName};
4in {
5 monitor = [
6 ",preferred,auto,auto,bitdepth,8"
7 "eDP-1,3840x2160@60,auto,1.5,bitdepth,8"
8 ];
9
10 "$terminal" = "kitty";
11 "$menu" = "fuzzel";
12
13 exec-once = [
14 "wpaperd"
15 ];
16
17 env = [
18 "NIXOS_OZONE_WL,1"
19 "QT_QPA_PLATFORM,wayland"
20 "QT_WAYLAND_DISABLE_WINDOWDECORATION,1"
21 "GDK_BACKEND,wayland"
22 "GDK_SCALE,0.66"
23 "QT_AUTO_SCREEN_SCALE_FACTOR,1"
24 "SDL_VIDEODRIVER,wayland"
25 # "AQ_DRM_DEVICES,/dev/dri/by-path/pci-0000:01:00.0-card"
26 "__NV_PRIME_RENDER_OFFLOAD,1"
27 "__NV_PRIME_RENDER_OFFLOAD_PROVIDER,NVIDIA-G0"
28 "__GLX_VENDOR_LIBRARY_NAME,nvidia"
29 "__VK_LAYER_NV_optimus,NVIDIA_only"
30 ];
31
32 xwayland.force_zero_scaling = true;
33
34 general = {
35 gaps_in = 3;
36 gaps_out = 9;
37 "col.active_border" = "rgba(33ccffee) rgba(00ff95ee) 45deg";
38 "col.inactive_border" = "rgba(595959aa)";
39
40 resize_on_border = false;
41
42 allow_tearing = false;
43
44 layout = "dwindle";
45 };
46
47 decoration = {
48 rounding = 5;
49 dim_special = 0.0;
50 };
51
52 animations = {
53 enabled = true;
54 bezier = "myBezier, 0.05, 0.9, 0.1, 1.05";
55 animation = [
56 "windows, 1, 1, default, popin 80%"
57 "windowsMove, 0"
58 # "windows, 1, 7, myBezier"
59 # "windowsOut, 1, 7, myBezier, popin 80%"
60 "border, 1, 10, default"
61 "borderangle, 1, 8, default"
62 "fade, 1, 1, default"
63 "workspaces, 1, 1, default, fade"
64 # "workspaces, 1, 6, default"
65 ];
66 };
67
68 dwindle = {
69 pseudotile = false;
70 preserve_split = true;
71 };
72
73 master = {
74 new_status = "master";
75 };
76
77 misc = {
78 disable_hyprland_logo = true;
79 disable_splash_rendering = true;
80 # focus_on_activate = true;
81 mouse_move_enables_dpms = true;
82 key_press_enables_dpms = true;
83 new_window_takes_over_fullscreen = 1;
84 exit_window_retains_fullscreen = true;
85 };
86
87 cursor = {
88 hide_on_key_press = true;
89 };
90
91 input = {
92 kb_layout = "us,us";
93 kb_variant = "dvp,";
94 kb_model = "";
95 kb_options = "compose:caps,grp:win_space_toggle";
96 kb_rules = "";
97
98 follow_mouse = 1;
99
100 sensitivity = 0;
101
102 touchpad = {
103 natural_scroll = false;
104 };
105 };
106
107 device = [
108 { name = "synaptics-tm3512-010";
109 sensitivity = 0.4;
110 }
111 { name = "tpps/2-elan-trackpoint";
112 sensitivity = 0.2;
113 }
114 { name = "logitech-ergo-m575";
115 sensitivity = 1.333;
116 }
117 ];
118
119 gestures = {
120 workspace_swipe = false;
121 };
122
123 dwindle = {
124 # no_gaps_when_only = 1;
125 };
126
127 "$mainMod" = "SUPER";
128
129 bind = [
130 "$mainMod, return, exec, $terminal"
131 "$mainMod, Q, killactive"
132 "$mainMod SHIFT, Q, exec, hyprctl kill"
133 "$mainMod, V, togglefloating"
134 "$mainMod, D, exec, $menu"
135 "$mainMod SHIFT, D, exec, $menu --list-executables-in-path"
136 # "$mainMod, J, togglesplit,"
137
138 "$mainMod SHIFT, L, exec, loginctl lock-session"
139 "$mainMod SHIFT, E, exit"
140
141 "$mainMod, left, movefocus, l"
142 "$mainMod, right, movefocus, r"
143 "$mainMod, up, movefocus, u"
144 "$mainMod, down, movefocus, d"
145 "$mainMod SHIFT, left, swapwindow, l"
146 "$mainMod SHIFT, right, swapwindow, r"
147 "$mainMod SHIFT, up, swapwindow, u"
148 "$mainMod SHIFT, down, swapwindow, d"
149
150 "$mainMod, T, cyclenext"
151
152 "$mainMod, G, focusmonitor, 0"
153 "$mainMod, C, focusmonitor, 1"
154 "$mainMod, R, focusmonitor, 2"
155 "$mainMod, L, focusmonitor, 3"
156
157 "$mainMod CTRL, G, movecurrentworkspacetomonitor, 0"
158 "$mainMod CTRL, C, movecurrentworkspacetomonitor, 1"
159 "$mainMod CTRL, R, movecurrentworkspacetomonitor, 2"
160 "$mainMod CTRL, L, movecurrentworkspacetomonitor, 3"
161
162 "$mainMod, F, fullscreen, 1"
163 "$mainMod SHIFT, F, fullscreen, 0"
164 "$mainMod CTRL SHIFT, F, fullscreenstate, 1, 2"
165
166 "$mainMod, code:14, workspace, 1"
167 "$mainMod, code:17, workspace, 2"
168 "$mainMod, code:13, workspace, 3"
169 "$mainMod, code:18, workspace, 4"
170 "$mainMod, code:12, workspace, 5"
171 "$mainMod, code:19, workspace, 6"
172 "$mainMod, code:11, workspace, 7"
173 "$mainMod, code:20, workspace, 8"
174 "$mainMod, code:15, workspace, 9"
175 "$mainMod, code:16, workspace, 10"
176
177 "$mainMod SHIFT, code:14, movetoworkspacesilent, 1"
178 "$mainMod SHIFT, code:17, movetoworkspacesilent, 2"
179 "$mainMod SHIFT, code:13, movetoworkspacesilent, 3"
180 "$mainMod SHIFT, code:18, movetoworkspacesilent, 4"
181 "$mainMod SHIFT, code:12, movetoworkspacesilent, 5"
182 "$mainMod SHIFT, code:19, movetoworkspacesilent, 6"
183 "$mainMod SHIFT, code:11, movetoworkspacesilent, 7"
184 "$mainMod SHIFT, code:20, movetoworkspacesilent, 8"
185 "$mainMod SHIFT, code:15, movetoworkspacesilent, 9"
186 "$mainMod SHIFT, code:16, movetoworkspacesilent, 10"
187
188 "$mainMod, semicolon, exec, dunstctl close"
189 "$mainMod SHIFT, semicolon, exec, dunstctl close-all"
190 "$mainMod, period, exec, dunstctl context"
191 "$mainMod, comma, exec, dunstctl history-pop"
192
193 "$mainMod ALT, E, exec, emacsclient -c"
194 "$mainMod ALT, Y, exec, ${pkgs.writeShellScript "yt-dlp" ''
195 export PATH="${lib.makeBinPath (with pkgs; [ wl-clipboard-rs socat ])}:$PATH"
196 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
197 ''}"
198 "$mainMod ALT, L, exec, ${pkgs.writeShellScript "mpv" ''
199 export PATH="${lib.makeBinPath (with pkgs; [ wl-clipboard-rs ])}:$PATH"
200 exec mpv "$(wl-paste)"
201 ''}"
202
203 ", Print, exec, ${pkgs.writeShellScript "screenshot" ''
204 export PATH="${lib.makeBinPath (with pkgs; [ grim slurp wl-clipboard-rs coreutils ])}:$PATH"
205
206 outFile="$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png"
207 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" "$outFile"
208 wl-copy --type image/png <"$outFile"
209 ''}"
210 "SHIFT, Print, exec, ${pkgs.writeShellScript "screenshot" ''
211 export PATH="${lib.makeBinPath (with pkgs; [ grim jq wl-clipboard-rs coreutils ])}:$PATH"
212
213 outFile="$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png"
214 grim -o "$(hyprctl monitors -j | jq -r '.[] | select(.focused) | .name')" "$outFile"
215 wl-copy --type image/png <"$outFile"
216 ''}"
217 "CTRL SHIFT, Print, exec, ${pkgs.runCommand "picker" {
218 buildInputs = [ pkgs.makeWrapper ];
219 } ''
220 makeWrapper ${lib.getExe pkgs.hyprpicker} $out \
221 --prefix PATH : ${lib.makeBinPath [pkgs.wl-clipboard-rs]}
222 ''} -a"
223 "$mainMod, M, exec, ${pkgs.writeShellScript "qalc-fuzzel" ''
224 export PATH="${lib.makeBinPath (with pkgs; [ wl-clipboard-rs libqalculate cfg.programs.fuzzel.package coreutils findutils libnotify gnugrep ])}:$PATH"
225
226 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
227 prev() {
228 FOUND=false
229 while IFS= read -r line; do
230 [[ -n "$line" ]] || continue
231 FOUND=true
232 echo $line
233 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)
234 $FOUND || echo
235 }
236 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
237 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
238 QALC_RES="$FUZZEL_RES"
239 QALC_RET=0
240 else
241 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
242 QALC_RET=$?
243 fi
244 [[ -n "$QALC_RES" ]] || exit 1
245 EXISTING=false
246 fgrep -xrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
247 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
248 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
249 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
250 cat >"$RES_FILE" <<<"$QALC_RES"
251 fi
252 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
253 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
254 notify-send "$QALC_RES"
255 ''}"
256 "$mainMod, E, exec, ${pkgs.writeShellScript "emoji-fuzzel" ''
257 export PATH="${lib.makeBinPath (with pkgs; [ cfg.programs.fuzzel.package wtype wl-clipboard-rs ])}:$PATH"
258
259 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <$HOME/.local/share/emoji-data/list.txt) || exit $?
260 [[ -n "$FUZZEL_RES" ]] || exit 1
261 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
262 ''}"
263 "$mainMod, B, exec, ${pkgs.writeShellScript "bring" ''
264 export PATH="${lib.makeBinPath (with pkgs; [ cfg.programs.fuzzel.package gawk gojq cfg.wayland.windowManager.hyprland.package ])}:$PATH"
265
266 state="$(hyprctl -j clients)"
267 active_window="$(hyprctl -j activewindow)"
268 active_workspace="$(hyprctl -j activeworkspace | gojq -r '.id')"
269
270 current_addr="$(echo "$active_window" | gojq -r '.address')"
271
272 window="$(echo "$state" |
273 gojq -r '.[] | select(.workspace.id == '"$active_workspace"') | select(.monitor != -1 ) | "\(.title)\t\(.address)"' |
274 fuzzel --log-level=warning --dmenu)"
275
276 addr="$(echo "$window" | awk -F $'\t' '{print $2}')"
277
278 if [[ "$addr" = "$current_addr" ]]; then
279 exit 0
280 fi
281
282 fullscreen_on_same_ws="$(echo "$state" | gojq -r '.[] | select(.fullscreen == true) | select(.workspace.id == '"$active_workspace"') | .address')"
283
284 if [[ "$window" != "" ]]; then
285 if [[ "$fullscreen_on_same_ws" == "" ]]; then
286 hyprctl dispatch focuswindow address:"''${addr}"
287 else
288 # If we want to focus app_A and app_B is fullscreen on the same workspace,
289 # app_A will get focus, but app_B will remain on top.
290 # This monstrosity is to make sure app_A will end up on top instead.
291 # XXX: doesn't handle fullscreen 0, but I don't care.
292 hyprctl --batch "dispatch focuswindow address:''${fullscreen_on_same_ws}; dispatch fullscreen 1; dispatch focuswindow address:''${addr}; dispatch fullscreen 1"
293 fi
294 fi
295 ''}"
296 "$mainMod SHIFT, B, exec, ${pkgs.writeShellScript "bring" ''
297 export PATH="${lib.makeBinPath (with pkgs; [ cfg.programs.fuzzel.package gawk gojq cfg.wayland.windowManager.hyprland.package ])}:$PATH"
298
299 state="$(hyprctl -j clients)"
300 active_window="$(hyprctl -j activewindow)"
301
302 current_addr="$(echo "$active_window" | gojq -r '.address')"
303
304 window="$(echo "$state" |
305 gojq -r '.[] | select(.monitor != -1 ) | "\(.title)\t\(.workspace.name)\t\(.address)"' |
306 fuzzel --log-level=warning --dmenu)"
307
308 addr="$(echo "$window" | awk -F $'\t' '{print $3}')"
309 ws="$(echo "$window" | awk -F $'\t' '{print $2}')"
310
311 if [[ "$addr" = "$current_addr" ]]; then
312 exit 0
313 fi
314
315 fullscreen_on_same_ws="$(echo "$state" | gojq -r ".[] | select(.fullscreen == true) | select(.workspace.name == \"$ws\") | .address")"
316
317 if [[ "$window" != "" ]]; then
318 if [[ "$fullscreen_on_same_ws" == "" ]]; then
319 hyprctl dispatch focuswindow address:"''${addr}"
320 else
321 # If we want to focus app_A and app_B is fullscreen on the same workspace,
322 # app_A will get focus, but app_B will remain on top.
323 # This monstrosity is to make sure app_A will end up on top instead.
324 # XXX: doesn't handle fullscreen 0, but I don't care.
325 hyprctl --batch "dispatch focuswindow address:''${fullscreen_on_same_ws}; dispatch fullscreen 1; dispatch focuswindow address:''${addr}; dispatch fullscreen 1"
326 fi
327 fi
328 ''}"
329
330 "$mainMod CTRL, return, togglespecialworkspace, term"
331 "$mainMod CTRL, e, togglespecialworkspace, edit"
332 "$mainMod CTRL, a, togglespecialworkspace, pwvucontrol"
333 "$mainMod CTRL, o, togglespecialworkspace, easyeffects"
334 "$mainMod CTRL, b, togglespecialworkspace, blueman"
335 "$mainMod CTRL, p, togglespecialworkspace, keepass"
336 ];
337 bindm = [
338 "$mainMod, mouse:272, movewindow"
339 "$mainMod, mouse:273, resizewindow"
340 ];
341 bindel = [
342 ", XF86MonBrightnessUp, exec, lightctl -d -e4 -n1 up"
343 ", XF86MonBrightnessDown, exec, lightctl -d -e4 -n1 down"
344 ", XF86AudioRaiseVolume, exec, volumectl -d -u up"
345 ", XF86AudioLowerVolume, exec, volumectl -d -u down"
346 ];
347 bindl = [
348 ", XF86AudioMute, exec, volumectl -d toggle-mute"
349 ", XF86AudioMicMute, exec, volumectl -d -m toggle-mute"
350 "$mainMod SHIFT, S, exec, systemctl suspend"
351
352 ", switch:off:Lid Switch,exec,hyprctl dispatch dpms on eDP-1"
353 ", switch:on:Lid Switch,exec,hyprctl dispatch dpms off eDP-1"
354
355 ", switch:off:Lid Switch,exec,${pkgs.writeShellScript "clamshell-off" ''
356 export PATH="${lib.makeBinPath (with pkgs; [ jq ])}:$PATH"
357 [[ $(hyprctl monitors -j | jq '.[] | select(.name == "eDP-1") | .disabled') = "true" ]] || exit 0
358
359 hyprctl keyword monitor "eDP-1,3840x2160@60,auto,1.5"
360 ''}"
361 ", switch:on:Lid Switch,exec,${pkgs.writeShellScript "clamshell-on" ''
362 export PATH="${lib.makeBinPath (with pkgs; [ jq ])}:$PATH"
363
364 [[ $(hyprctl monitors -j | jq 'reduce (.[] | select(.disabled == false)) as $_ (0; .+1)') -gt 1 ]] || exit 0
365
366 hyprctl keyword monitor "eDP-1,disable"
367 ''}"
368 ];
369
370 windowrulev2 = [
371 "suppressevent maximize fullscreen, class:.*"
372
373 # "maximize, class:^(Element|thunderbird)$"
374 "workspace special:pwvucontrol, class:^com\.saivert\.pwvucontrol$"
375 "workspace special:easyeffects, class:^com\.github\.wwmm\.easyeffects$"
376 "workspace special:blueman, class:^\.blueman-manager-wrapped$"
377 "workspace special:keepass silent, class:^org\.keepassxc\.KeePassXC$, title:^(?!Unlock Database.*)(?!.*(Access Request|Passkey credentials))"
378 # "group set always lock invade, class:^Element$"
379 "workspace 2, class:^firefox$"
380 "workspace 4, class:^evince$"
381 "workspace 4, class:^imv$"
382 "workspace 4, class:^org\.pwmt\.zathura$"
383 "workspace 10, class:^mpv$"
384 "workspace 1, class:^Element$"
385 "workspace 1, class:^thunderbird$"
386 "workspace 5, class:^virt-manager$"
387 "workspace 5, class:^qemu$"
388 "float, class:^org\.keepassxc\.KeePassXC$, title:Access Request$"
389 "center, class:^org\.keepassxc\.KeePassXC$, title:Access Request$"
390 "float, class:^org\.keepassxc\.KeePassXC$, title:Passkey credentials$"
391 "center, class:^org\.keepassxc\.KeePassXC$, title:Passkey credentials$"
392 "float, class:^org\.keepassxc\.KeePassXC$, title:^Unlock Database"
393 "center, class:^org\.keepassxc\.KeePassXC$, title:^Unlock Database"
394 "float, class:^xdg-desktop-portal-gtk$"
395 "center, class:^xdg-desktop-portal-gtk$"
396
397 "bordercolor rgba(ffaa33ee) rgba(bfff00ee) 45deg, fullscreen:1"
398 "bordercolor rgba(3366ffee) rgba(6a00ffee) 45deg, xwayland:1"
399 "bordercolor rgba(6633ffee) rgba(ea00ffee) 45deg, xwayland:1, fullscreen:1"
400 ];
401
402 workspace = [
403 "s[true], gapsout:100"
404
405 "special:term, on-created-empty:kitty"
406 "special:edit, on-created-empty:emacsclient -c"
407 "special:pwvucontrol, on-created-empty:pwvucontrol"
408 "special:easyeffects, on-created-empty:easyeffects"
409 "special:blueman, on-created-empty:blueman-manager"
410 "special:keepass, on-created-empty:keepassxc"
411
412 "1, defaultName:comm"
413 "2, defaultName:web"
414 "3, defaultName:work"
415 "4, defaultName:read"
416 ];
417
418 layerrule = [
419 "blur, waybar"
420 "blur, launcher"
421 "noanim, notifications"
422 "blur, notifications"
423 ];
424}
diff --git a/accounts/gkleen@sif/libvirt/default.nix b/accounts/gkleen@sif/libvirt/default.nix
index f86a68a2..4e5a9b90 100644
--- a/accounts/gkleen@sif/libvirt/default.nix
+++ b/accounts/gkleen@sif/libvirt/default.nix
@@ -7,6 +7,7 @@ with flakeInputs.nixVirt.lib;
7 config = { 7 config = {
8 virtualisation.libvirt = { 8 virtualisation.libvirt = {
9 enable = true; 9 enable = true;
10 swtpm.enable = true;
10 connections."qemu:///session" = { 11 connections."qemu:///session" = {
11 domains = [ 12 domains = [
12 { definition = domain.writeXML (updateManyAttrsByPath [ 13 { definition = domain.writeXML (updateManyAttrsByPath [
@@ -16,8 +17,8 @@ with flakeInputs.nixVirt.lib;
16 memory = { count = 16; unit = "GiB"; }; 17 memory = { count = 16; unit = "GiB"; };
17 storage_vol = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2"; 18 storage_vol = "/home/gkleen/.local/share/libvirt/images/lmmirzm-vmrz01.qcow2";
18 nvram_path = "/home/gkleen/.local/share/libvirt/lmmirzm-vmrz01.nvram"; 19 nvram_path = "/home/gkleen/.local/share/libvirt/lmmirzm-vmrz01.nvram";
19 virtio_drive = false; 20 virtio_drive = true;
20 virtio_video = false; 21 virtio_video = true;
21 install_virtio = false; 22 install_virtio = false;
22 }) { 23 }) {
23 qemu-commandline.env = [ 24 qemu-commandline.env = [
@@ -33,11 +34,12 @@ with flakeInputs.nixVirt.lib;
33 os.bootmenu.enable = true; 34 os.bootmenu.enable = true;
34 devices.graphics = { 35 devices.graphics = {
35 listen.type = "address"; 36 listen.type = "address";
36 # gl.enable = true; 37 gl.enable = false;
37 }; 38 };
39 devices.video.model.acceleration.accel3d = false;
38 devices.interface = { 40 devices.interface = {
39 # model.type = "virtio"; 41 model.type = "virtio";
40 model.type = "e1000e"; 42 # model.type = "e1000e";
41 type = "bridge"; 43 type = "bridge";
42 mac.address = "52:54:00:b9:f3:ed"; 44 mac.address = "52:54:00:b9:f3:ed";
43 source.bridge = "rz-0971"; 45 source.bridge = "rz-0971";
@@ -47,6 +49,15 @@ with flakeInputs.nixVirt.lib;
47 type = "unix"; 49 type = "unix";
48 target = { type = "virtio"; name = "org.qemu.guest_agent.0"; }; 50 target = { type = "virtio"; name = "org.qemu.guest_agent.0"; };
49 } 51 }
52 {
53 type = "spicevmc";
54 target = { type = "virtio"; name = "com.redhat.spice.0"; };
55 }
56 {
57 type = "spiceport";
58 target = { type = "virtio"; name = "org.spice-space.webdav.0"; };
59 source.channel = "org.spice-space.webdav.0";
60 }
50 ]; 61 ];
51 devices.tpm.model = "tpm-tis"; 62 devices.tpm.model = "tpm-tis";
52 })); 63 }));
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
new file mode 100644
index 00000000..5ae372c1
--- /dev/null
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -0,0 +1,978 @@
1{ config, hostConfig, pkgs, lib, 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 terminal = lib.getExe config.programs.kitty.package;
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 --reference "$workspace_name" "$active_output"
26 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
27 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
28 fi
29
30 while IFS=$'\n' read -r window_json; do
31 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
32 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
33 niri msg action focus-workspace-previous
34 else
35 if [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].is_focused' <<<"$workspaces_json") != "true" ]] && [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].id' <<<"$workspaces_json") = $(jq -r '.workspace_id' <<<"$window_json") ]]; then
36 niri msg action focus-workspace "$workspace_name"
37 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
39 fi
40 fi
41 exit 0
42 fi
43 done < <(niri msg -j windows | jq -c '.[]')
44
45 exec "$@"
46 '';
47 };
48 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
49
50 with_adjacent_workspace = pkgs.writeShellApplication {
51 name = "with-adjacent-workspace";
52 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
53 text = ''
54 blacklist="$1"
55 shift
56 direction="$1"
57 shift
58 action="$1"
59 shift
60
61 workspaces_json="$(niri msg -j workspaces)"
62 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
63 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
64 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
65
66 jq_script='map(select('
67 case "$direction" in
68 down)
69 # shellcheck disable=SC2016
70 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
71 up)
72 # shellcheck disable=SC2016
73 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
74 esac
75 # shellcheck disable=SC2016
76 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
77 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
78 jq_script=''${jq_script}' | .[0]'
79
80 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
81 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
82 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
83 '';
84 };
85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
88
89 with_unnamed_workspace = pkgs.writeShellApplication {
90 name = "with-unnamed-workspace";
91 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
92 text = ''
93 action="$1"
94 shift
95
96 workspaces_json="$(niri msg -j workspaces)"
97 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
98 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
99
100 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
101 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")"
102 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
103 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
104 '';
105 };
106 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
107
108 with_empty_unnamed_workspace = pkgs.writeShellApplication {
109 name = "with-empty-unnamed-workspace";
110 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
111 text = ''
112 action="$1"
113 shift
114
115 workspaces_json="$(niri msg -j workspaces)"
116 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
117 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")"
118 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
119 '';
120 };
121 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
122
123 with_select_window = pkgs.writeShellApplication {
124 name = "with-select-window";
125 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
126 text = ''
127 window_select="$1"
128 shift
129 action="$1"
130 shift
131
132 windows_json="$(niri msg -j windows)"
133 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
134 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --width=60 --log-level=warning --dmenu --index)"
135 # shellcheck disable=SC2016
136 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
137
138 [[ -z "$window_json" ]] && exit 1
139
140 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
141 '';
142 };
143 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
144
145 with_predicate_window = pred: pkgs.writeShellApplication {
146 name = "with-predicate-window";
147 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
148 text = ''
149 action="$1"
150 shift
151
152 windows_json="$(niri msg -j windows)"
153 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
154
155 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
156
157 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
158 '';
159 };
160
161 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
162 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
163in {
164 options = {
165 programs.niri.scratchspaces = lib.mkOption {
166 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
167 options = {
168 name = lib.mkOption {
169 type = lib.types.str;
170 };
171 match = lib.mkOption {
172 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
173 default = [];
174 };
175 exclude = lib.mkOption {
176 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
177 default = [];
178 };
179 windowRuleExtra = lib.mkOption {
180 type = kdl.types.kdl-nodes;
181 default = [];
182 };
183 key = lib.mkOption {
184 type = lib.types.nullOr lib.types.str;
185 default = null;
186 };
187 moveKey = lib.mkOption {
188 type = lib.types.nullOr lib.types.str;
189 default = let
190 keys = lib.splitString "+" config.key;
191 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
192 (lib.take (lib.length keys - 1) keys)
193 ["Shift"]
194 (lib.takeEnd 1 keys)
195 ]);
196 in if config.key == null then null else defMoveKey;
197 };
198 spawn = lib.mkOption {
199 type = lib.types.nullOr (lib.types.listOf lib.types.str);
200 default = null;
201 };
202 app-id = lib.mkOption {
203 type = lib.types.nullOr lib.types.str;
204 default = null;
205 };
206 selector = lib.mkOption {
207 type = lib.types.nullOr lib.types.str;
208 default = null;
209 };
210 };
211
212 config = lib.mkMerge [
213 (lib.mkIf (config.app-id != null) {
214 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
215 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
216 })
217 ];
218 }));
219 default = [];
220 };
221 };
222
223 config = {
224 home.packages = [ pkgs.xwayland-satellite-unstable ];
225
226 systemd.user.sockets.niri-workspace-history = {
227 Socket = {
228 ListenStream = "%t/niri-workspace-history.sock";
229 SocketMode = "0600";
230 };
231 };
232 systemd.user.services.niri-workspace-history = {
233 Unit = {
234 BindsTo = [ "niri.service" ];
235 After = [ "niri.service" ];
236 };
237 Install = {
238 WantedBy = [ "niri.service" ];
239 };
240 Service = {
241 Type = "simple";
242 Sockets = [ "niri-workspace-history.socket" ];
243 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
244 import os
245 import socket
246 import json
247 # import sys
248 from collections import defaultdict
249 from threading import Thread, Lock
250 from socketserver import StreamRequestHandler, ThreadingTCPServer
251 from contextlib import contextmanager
252 from io import TextIOWrapper
253
254
255 @contextmanager
256 def detaching(thing):
257 try:
258 yield thing
259 finally:
260 thing.detach()
261
262
263 workspace_history = defaultdict(list)
264 history_lock = Lock()
265
266
267 def monitor_niri():
268 workspaces = list()
269
270 def focus_workspace(output, workspace):
271 with history_lock:
272 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
273 # print(json.dumps(workspace_history), file=sys.stderr)
274
275 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
276 sock.connect(os.environ["NIRI_SOCKET"])
277 sock.send(b"\"EventStream\"\n")
278 for line in sock.makefile(buffering=1, encoding='utf-8'):
279 if line_json := json.loads(line):
280 if "WorkspacesChanged" in line_json:
281 workspaces = line_json["WorkspacesChanged"]["workspaces"]
282 for ws in workspaces:
283 if ws["is_focused"]:
284 focus_workspace(ws["output"], ws["id"])
285 if "WorkspaceActivated" in line_json:
286 for ws in workspaces:
287 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
288 continue
289 focus_workspace(ws["output"], ws["id"])
290 break
291
292
293 class RequestHandler(StreamRequestHandler):
294 def handle(self):
295 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
296 with history_lock:
297 json.dump(workspace_history, out)
298
299
300 class Server(ThreadingTCPServer):
301 def __init__(self):
302 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
303 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
304
305
306 def run_server():
307 with Server() as server:
308 server.serve_forever()
309
310
311 niri = Thread(target=monitor_niri)
312 niri.daemon = True
313 niri.start()
314
315 server_thread = Thread(target=run_server)
316 server_thread.daemon = True
317 server_thread.start()
318
319 while True:
320 server_thread.join(timeout=0.5)
321 niri.join(timeout=0.5)
322
323 if not (niri.is_alive() and server_thread.is_alive()):
324 break
325 '';
326 };
327 };
328 systemd.user.services.niri-workspace-sort = {
329 Unit = {
330 BindsTo = [ "niri.service" ];
331 After = [ "niri.service" ];
332 };
333 Install = {
334 WantedBy = [ "niri.service" ];
335 };
336 Service = {
337 Type = "simple";
338 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
339 import os
340 import sys
341 import socket
342 import json
343
344 outputs = None
345 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
346
347
348 class Niri(socket.socket):
349 def __init__(self):
350 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
351 super().connect(os.environ["NIRI_SOCKET"])
352 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
353
354 def cmd(self, obj):
355 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
356
357 def event_stream(self):
358 self.cmd("EventStream")
359 return self.fh
360
361
362 with Niri() as niri, Niri().event_stream() as niri_stream:
363 for line in niri_stream:
364 workspaces = None
365 if line_json := json.loads(line):
366 if "WorkspacesChanged" in line_json:
367 workspaces = line_json["WorkspacesChanged"]["workspaces"]
368
369 if workspaces is None:
370 continue
371
372 old_outputs = outputs
373 outputs = {ws["output"] for ws in workspaces}
374 if old_outputs is None:
375 print("Initial outputs: {}".format(outputs), file=sys.stderr)
376 continue
377
378 new_outputs = outputs - old_outputs
379 if not new_outputs:
380 continue
381 print("New outputs: {}".format(new_outputs), file=sys.stderr)
382
383 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
384 target_output = next(iter(outputs - set(only.keys())))
385 if not target_output:
386 continue
387 for ws in relevant_workspaces:
388 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
389 if ws["output"] not in set(only.keys()):
390 continue
391 if ws_ident in only[ws["output"]]:
392 continue
393
394 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
395 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
396 '';
397 Restart = "on-failure";
398 RestartSec = 10;
399 };
400 };
401
402 programs.niri.scratchspaces = [
403 { name = "pwctl";
404 key = "Mod+Control+A";
405 spawn = ["pwvucontrol"];
406 app-id = "com.saivert.pwvucontrol";
407 }
408 { name = "kpxc";
409 exclude = [
410 { title = "^Unlock Database.*"; }
411 { title = "^Access Request.*"; }
412 { title = ".*Passkey credentials$"; }
413 ];
414 windowRuleExtra = with kdl; [
415 (sleaf "open-focused" false)
416 ];
417 key = "Mod+Control+P";
418 app-id = "org.keepassxc.KeePassXC";
419 spawn = [ "keepassxc" ];
420 }
421 { name = "bmgr";
422 key = "Mod+Control+B";
423 app-id = ".blueman-manager-wrapped";
424 spawn = [ "blueman-manager" ];
425 }
426 { name = "term";
427 key = "Mod+Control+Return";
428 app-id = "kitty-scratch";
429 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
430 }
431 { name = "edit";
432 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
433 key = "Mod+Control+E";
434 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
435 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
436 }
437 { name = "eff";
438 key = "Mod+Control+O";
439 app-id = "com.github.wwmm.easyeffects";
440 spawn = [ "easyeffects" ];
441 }
442 { name = "time";
443 key = "Mod+Control+K";
444 app-id = "chrome-kimai.yggdrasil.li__-Default";
445 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
446 interpreter = pkgs.runtimeShell;
447 inputs = [ pkgs.dex ];
448 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
449 } ''
450 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
451 '')) ];
452 windowRuleExtra = with kdl; [
453 (sleaf "block-out-from" "screencast")
454 ];
455 }
456 ];
457 programs.niri.config =
458 let
459 inherit (kdl) node plain leaf flag;
460 optional-node = cond: v:
461 if cond
462 then v
463 else null;
464 opt-props = lib.filterAttrs (lib.const (value: value != null));
465 normalize-nodes = nodes: lib.remove null (lib.flatten nodes);
466 in
467 normalize-nodes [
468 (flag "prefer-no-csd")
469
470 (sleaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
471
472 (plain "hotkey-overlay" [
473 (flag "skip-at-startup")
474 ])
475
476 (plain "input" [
477 (plain "keyboard" [
478 (sleaf "repeat-delay" 300)
479 (sleaf "repeat-rate" 50)
480
481 (plain "xkb" [
482 (sleaf "layout" "us,us")
483 (sleaf "variant" "dvp,")
484 (sleaf "options" "compose:caps,grp:win_space_toggle")
485 ])
486 ])
487
488 (flag "workspace-auto-back-and-forth")
489 # (sleaf "focus-follows-mouse" {})
490 # (flag "warp-mouse-to-focus")
491
492 # (plain "touchpad" [ (flag "off") ])
493 (plain "trackball" [
494 (sleaf "scroll-method" "on-button-down")
495 (sleaf "scroll-button" 278)
496 ])
497 (plain "touch" [
498 (sleaf "map-to-output" "eDP-1")
499 ])
500 ])
501
502 (plain "gestures" [
503 (plain "hot-corners" [(flag "off")])
504 ])
505
506 (plain "environment" (lib.mapAttrsToList sleaf {
507 NIXOS_OZONE_WL = "1";
508 QT_QPA_PLATFORM = "wayland";
509 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
510 GDK_BACKEND = "wayland";
511 SDL_VIDEODRIVER = "wayland";
512 DISPLAY = ":0";
513 ELECTRON_OZONE_PLATFORM_HINT = "auto";
514 SSH_ASKPASS_REQUIRE = "prefer";
515 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
516 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
517 }))
518
519 (node "output" ["eDP-1"] [
520 (sleaf "scale" 1.5)
521 (sleaf "position" { x = 0; y = 0; })
522 ])
523 (node "output" ["Ancor Communications Inc ASUS PB287Q 0x0000DD9B"] [
524 (sleaf "scale" 1.5)
525 (sleaf "position" { x = 2560; y = 0; })
526 ])
527 (node "output" ["HP Inc. HP 727pu CN4417143K"] [
528 (sleaf "mode" "2560x1440@119.998")
529 (sleaf "scale" 1)
530 (sleaf "position" { x = 2560; y = 0; })
531 (flag "variable-refresh-rate")
532 ])
533
534 (plain "debug" [
535 (sleaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
536 ])
537
538 (plain "animations" [
539 (sleaf "slowdown" 0.5)
540 (plain "workspace-switch" [(flag "off")])
541 ])
542
543 (plain "layout" [
544 (sleaf "gaps" 8)
545 (plain "struts" [
546 (sleaf "left" 26)
547 (sleaf "right" 26)
548 (sleaf "top" 0)
549 (sleaf "bottom" 0)
550 ])
551 (plain "border" [
552 (sleaf "width" 2)
553 (sleaf "active-gradient" {
554 from = "hsla(195 100% 45% 1)";
555 to = "hsla(155 100% 37.5% 1)";
556 angle = 29;
557 relative-to = "workspace-view";
558 })
559 (sleaf "inactive-gradient" {
560 from = "hsla(0 0% 27.7% 1)";
561 to = "hsla(0 0% 23% 1)";
562 angle = 29;
563 relative-to = "workspace-view";
564 })
565 ])
566 (plain "focus-ring" [
567 (flag "off")
568 ])
569
570 (plain "preset-column-widths" (map (prop: sleaf "proportion" prop) [
571 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
572 ]))
573 (plain "default-column-width" [ (sleaf "proportion" (1. / 2.)) ])
574 (plain "preset-window-heights" (map (prop: sleaf "proportion" prop) [
575 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
576 ]))
577
578 (flag "always-center-single-column")
579
580 (plain "tab-indicator" [
581 (sleaf "gap" 4)
582 (sleaf "width" 8)
583 (sleaf "gaps-between-tabs" 4)
584 (flag "place-within-column")
585 (sleaf "length" { total-proportion = 1.; })
586 (sleaf "active-gradient" {
587 from = "hsla(195 100% 60% 0.75)";
588 to = "hsla(155 100% 50% 0.75)";
589 angle = 29;
590 relative-to = "workspace-view";
591 })
592 (sleaf "inactive-gradient" {
593 from = "hsla(0 0% 42% 0.66)";
594 to = "hsla(0 0% 35% 0.66)";
595 angle = 29;
596 relative-to = "workspace-view";
597 })
598 ])
599 ])
600
601 (plain "cursor" [
602 (flag "hide-when-typing")
603 ])
604
605 (map (name:
606 (node "workspace" [name] [
607 (sleaf "open-on-output" "eDP-1")
608 ])
609 ) (map ({name, ...}: name) cfg.scratchspaces))
610 (map (name:
611 (sleaf "workspace" name)
612 ) ["comm" "web" "vid" "bmr"])
613
614 (plain "window-rule" [
615 (sleaf "clip-to-geometry" true)
616 ])
617
618 (plain "window-rule" [
619 (sleaf "match" { is-floating = true; })
620 (sleaf "geometry-corner-radius" 8)
621 (plain "shadow" [ (flag "on") ])
622 ])
623
624 (plain "window-rule" [
625 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
626 (sleaf "block-out-from" "screencast")
627 ])
628 (plain "window-rule" (normalize-nodes [
629 (map (title:
630 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
631 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
632 (sleaf "open-focused" true)
633 (sleaf "open-floating" true)
634 ]))
635
636 (map ({ name, match, exclude, windowRuleExtra, ... }:
637 (optional-node (match != []) (plain "window-rule" (normalize-nodes [
638 (map (sleaf "match") match)
639 (map (sleaf "exclude") exclude)
640 (sleaf "open-on-workspace" name)
641 (sleaf "open-maximized" true)
642 windowRuleExtra
643 ])))
644 ) cfg.scratchspaces)
645
646 (plain "window-rule" [
647 (sleaf "match" { app-id = "^emacs$"; })
648 (sleaf "match" { app-id = "^firefox$"; })
649 (plain "default-column-width" [(sleaf "proportion" (2. / 3.))])
650 ])
651 (plain "window-rule" [
652 (sleaf "match" { app-id = "^kitty$"; })
653 (sleaf "match" { app-id = "^kitty-play$"; })
654 (plain "default-column-width" [(sleaf "proportion" (1. / 3.))])
655 ])
656
657 (plain "window-rule" [
658 (sleaf "match" { app-id = "^thunderbird$"; })
659 (sleaf "match" { app-id = "^Element$"; })
660 (sleaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
661 (sleaf "open-on-workspace" "comm")
662 ])
663 (plain "window-rule" [
664 (sleaf "match" { app-id = "^firefox$"; })
665 (sleaf "open-on-workspace" "web")
666 (sleaf "open-maximized" true)
667 ])
668 (plain "window-rule" [
669 (sleaf "match" { app-id = "^mpv$"; })
670 (sleaf "open-on-workspace" "vid")
671 (plain "default-column-width" [(sleaf "proportion" 1.)])
672 ])
673 (plain "window-rule" [
674 (sleaf "match" { app-id = "^kitty-play$"; })
675 (sleaf "open-on-workspace" "vid")
676 (sleaf "open-focused" false)
677 ])
678 (plain "window-rule" [
679 (sleaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
680 (sleaf "match" { app-id = "^YouTube Music Desktop App$"; })
681 (sleaf "open-on-workspace" "vid")
682 ])
683 (plain "window-rule" [
684 (sleaf "match" { app-id = "^pdfpc$"; })
685 (plain "default-column-width" [(sleaf "proportion" 1.)])
686 ])
687 (plain "window-rule" [
688 (sleaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
689 (plain "default-column-width" [(sleaf "proportion" 1.)])
690 (sleaf "open-fullscreen" true)
691 (sleaf "open-on-workspace" "bmr")
692 (sleaf "open-focused" false)
693 ])
694 (plain "window-rule" (normalize-nodes [
695 (map (sleaf "match") [
696 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
697 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
698 { app-id = "^xdg-desktop-portal-gtk$"; }
699 ])
700 (sleaf "open-floating" true)
701 ]))
702 (plain "window-rule" [
703 (sleaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
704 (sleaf "match" { app-id = "^evince$"; })
705 (sleaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
706 (sleaf "default-column-display" "tabbed")
707 ])
708
709 (plain "layer-rule" [
710 (sleaf "match" { namespace = "^notifications$"; })
711 (sleaf "match" { namespace = "^bar$"; })
712 (sleaf "match" { namespace = "^launcher$"; })
713 (sleaf "block-out-from" "screencast")
714 ])
715
716 (plain "binds"
717 (let
718 bind = name: cfg: node name [(lib.removeAttrs cfg ["action"])] (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
719 in
720 normalize-nodes [
721 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
722 "Mod+Slash".action = show-hotkey-overlay;
723
724 "Mod+Return".action = spawn terminal;
725 "Mod+Shift+Return".action =
726 let
727 nushellKitty = pkgs.symlinkJoin {
728 name = "nushell-kitty";
729 paths = [ config.programs.kitty.package ];
730 buildInputs = [ pkgs.makeWrapper ];
731 postBuild = ''
732 wrapProgram $out/bin/kitty \
733 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
734 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
735 shell ${lib.getExe config.programs.nushell.package}
736 ''}"
737 '';
738 };
739 in spawn (lib.getExe' nushellKitty "kitty");
740 "Mod+Q".action = close-window;
741 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
742 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
743
744 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
745 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
746 name = "queue-yt-dlp";
747 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
748 text = ''
749 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
750 '';
751 }));
752 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
753 name = "queue-yt-dlp";
754 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
755 text = ''
756 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
757 '';
758 }));
759 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
760
761 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
762 name = "qalc-fuzzel";
763 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
764 text = ''
765 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
766 prev() {
767 FOUND=false
768 while IFS= read -r line; do
769 [[ -n "$line" ]] || continue
770 FOUND=true
771 echo "$line"
772 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
773 $FOUND || echo
774 }
775 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
776 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
777 QALC_RES="$FUZZEL_RES"
778 QALC_RET=0
779 else
780 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
781 QALC_RET=$?
782 fi
783 [[ -n "$QALC_RES" ]] || exit 1
784 EXISTING=false
785 set +o pipefail
786 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
787 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
788 set -o pipefail
789 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
790 set +o pipefail
791 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
792 set -o pipefail
793 cat >"$RES_FILE" <<<"$QALC_RES"
794 fi
795 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
796 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
797 notify-send "$QALC_RES"
798 '';
799 }));
800 "Mod+Shift+U".action =
801 let
802 qalcKitty = pkgs.symlinkJoin {
803 name = "qalc-kitty";
804 paths = [ config.programs.kitty.package ];
805 buildInputs = [ pkgs.makeWrapper ];
806 postBuild = ''
807 wrapProgram $out/bin/kitty \
808 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
809 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
810 shell ${lib.getExe pkgs.libqalculate}
811 ''}"
812 '';
813 };
814 in spawn (lib.getExe' qalcKitty "kitty");
815 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
816 name = "emoji-fuzzel";
817 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
818 text = ''
819 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
820 [[ -n "$FUZZEL_RES" ]] || exit 1
821 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
822 '';
823 }));
824 "Print".action = screenshot;
825 "Control+Print".action = screenshot-window;
826 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
827 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
828 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
829
830 "Mod+Escape" = {
831 allow-inhibiting = false;
832 action = toggle-keyboard-shortcuts-inhibit;
833 };
834
835 "Mod+H".action = focus-column-left;
836 "Mod+T".action = focus-window-down;
837 "Mod+N".action = focus-window-up;
838 "Mod+S".action = focus-column-right;
839
840 "Mod+Shift+H".action = move-column-left;
841 "Mod+Shift+T".action = move-window-down;
842 "Mod+Shift+N".action = move-window-up;
843 "Mod+Shift+S".action = move-column-right;
844
845 "Mod+Control+H".action = focus-monitor-left;
846 "Mod+Control+T".action = focus-monitor-down;
847 "Mod+Control+N".action = focus-monitor-up;
848 "Mod+Control+S".action = focus-monitor-right;
849
850 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
851 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
852 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
853 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
854
855 "Mod+G".action = focus-adjacent-workspace "down";
856 "Mod+C".action = focus-adjacent-workspace "up";
857
858 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
859 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
860
861 "Mod+Shift+Control+G".action = move-workspace-down;
862 "Mod+Shift+Control+C".action = move-workspace-up;
863
864 "Mod+ParenLeft".action = focus-workspace "comm";
865 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
866
867 "Mod+ParenRight".action = focus-workspace "web";
868 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
869
870 "Mod+BraceRight".action = focus-workspace "read";
871 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
872
873 "Mod+BraceLeft".action = focus-workspace "mon";
874 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
875
876 "Mod+Asterisk".action = focus-workspace "vid";
877 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
878
879 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
880 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
881
882 "Mod+M".action = consume-or-expel-window-left;
883 "Mod+W".action = consume-or-expel-window-right;
884
885 "Mod+Shift+M".action = toggle-column-tabbed-display;
886
887 "Mod+R".action = switch-preset-column-width;
888 "Mod+Shift+R".action = maximize-column;
889 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
890 "Mod+F".action = center-column;
891 "Mod+Shift+F".action = toggle-windowed-fullscreen;
892 "Mod+Ctrl+Shift+F".action = fullscreen-window;
893
894 "Mod+V".action = switch-focus-between-floating-and-tiling;
895 "Mod+Shift+V".action = toggle-window-floating;
896
897 "Mod+Left".action = set-column-width "-10%";
898 "Mod+Down".action = set-window-height "-10%";
899 "Mod+Up".action = set-window-height "+10%";
900 "Mod+Right".action = set-column-width "+10%";
901
902 "Mod+Shift+Z" = {
903 action = power-off-monitors;
904 allow-when-locked = true;
905 };
906 "Mod+Shift+E".action = quit;
907
908 # "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
909 # "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
910 # "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
911 # "Mod+Comma".action = spawn makoctl "restore";
912
913 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
914 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
915
916 "Mod+X".action = set-dynamic-cast-window;
917 "Mod+Shift+X".action = set-dynamic-cast-monitor;
918 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
919
920 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
921 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
922
923 "Mod+K".action = spawn (lib.getExe' pkgs.worktime "worktime-ui");
924 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
925 }))
926 (lib.mapAttrsToList (name: cfg: node name [(lib.removeAttrs cfg ["action"])] [cfg.action]) (let
927 shell = obj: leaf "send-unix" [
928 { path = ''''${XDG_RUNTIME_DIR}/shell.sock''; }
929 (builtins.toJSON obj + "\n")
930 ];
931 in {
932 "XF86AudioRaiseVolume" = {
933 allow-when-locked = true;
934 action = shell { Volume.volume = "up"; };
935 };
936 "XF86AudioLowerVolume" = {
937 allow-when-locked = true;
938 action = shell { Volume.volume = "down"; };
939 };
940 "XF86AudioMute" = {
941 allow-when-locked = true;
942 action = shell { Volume.muted = "toggle"; };
943 };
944 "XF86AudioMicMute" = {
945 allow-when-locked = true;
946 action = shell { Volume."mic-muted" = "toggle"; };
947 };
948 "XF86MonBrightnessUp" = {
949 action = shell { Brightness = "up"; };
950 allow-when-locked = true;
951 };
952 "XF86MonBrightnessDown" = {
953 action = shell { Brightness = "down"; };
954 allow-when-locked = true;
955 };
956 "Mod+Shift+L".action = shell { LockSession = {}; };
957 "Mod+Shift+Minus" = {
958 action = shell { Suspend = {}; };
959 allow-when-locked = true;
960 };
961 "Mod+Shift+Control+Minus" = {
962 action = shell { Hibernate = {}; };
963 allow-when-locked = true;
964 };
965 "Mod+Shift+P" = {
966 action = shell { Mpris = { PauseAll = {}; }; };
967 allow-when-locked = true;
968 };
969 "Mod+Semicolon".action = shell { Notifications = { DismissGroup = {}; }; };
970 "Mod+Shift+Semicolon".action = shell { Notifications = { DismissAll = {}; }; };
971 }))
972 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces)
973 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
974 ]
975 ))
976 ];
977 };
978}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
new file mode 100644
index 00000000..3d246d96
--- /dev/null
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -0,0 +1,112 @@
1{ config, lib, pkgs, ... }:
2{
3 config = lib.mkIf false {
4 services.mako = {
5 enable = true;
6 settings = {
7 font = "Fira Sans 10";
8 format = "<i>%s</i>\\n%b";
9 margin = "2";
10 max-visible = -1;
11 background-color = "#000000dd";
12 progress-color = "source #223544ff";
13 width = 384;
14 outer-margin = 1;
15 max-history = 100;
16 max-icon-size = 48;
17
18 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
19 "urgency=low".text-color = "#999999ff";
20 "urgency=critical".background-color = "#900000dd";
21 "app-name=Element".group-by = "summary";
22 "app-name=poweralertd" = {
23 history = false;
24 ignore-timeout = true;
25 default-timeout = 2000;
26 };
27 "app-name=worktime".history = false;
28 "mode=silent".invisible = true;
29 };
30 package = pkgs.symlinkJoin {
31 name = "${pkgs.mako.name}-wrapped";
32 paths = with pkgs; [ mako ];
33 inherit (pkgs.mako) meta;
34 postBuild = ''
35 rm -r $out/share/dbus-1
36 '';
37 };
38 };
39 systemd.user.services.mako = {
40 Unit = {
41 Description = "Mako notification daemon";
42 PartOf = [ "graphical-session.target" ];
43 };
44 Install = {
45 WantedBy = [ "graphical-session.target" ];
46 };
47 Service = {
48 Type = "dbus";
49 BusName = "org.freedesktop.Notifications";
50 ExecStart = lib.getExe config.services.mako.package;
51 RestartSec = 5;
52 Restart = "always";
53 };
54 };
55
56 systemd.user.services.mako-follows-focus = {
57 Unit = {
58 BindsTo = [ "niri.service" "mako.service" ];
59 After = [ "niri.service" "mako.service" ];
60 };
61 Service = {
62 Type = "simple";
63 Restart = "always";
64 ExecStart = pkgs.writers.writePython3 "mako-follows-focus" {
65 libraries = with pkgs.python3Packages; [];
66 } ''
67 import os
68 import socket
69 import json
70 import subprocess
71
72
73 current_output = None
74 workspaces = []
75
76
77 def output_changed(new_output):
78 global current_output
79
80 if current_output == new_output:
81 return
82
83 current_output = new_output
84 subprocess.run(["makoctl", "reload"])
85
86
87 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
88 sock.connect(os.environ["NIRI_SOCKET"])
89 sock.send(b"\"EventStream\"\n")
90 for line in sock.makefile(buffering=1, encoding='utf-8'):
91 if line_json := json.loads(line):
92 if "WorkspacesChanged" in line_json:
93 workspaces = line_json["WorkspacesChanged"]["workspaces"]
94 for workspace in workspaces:
95 if not workspace["is_focused"]:
96 continue
97 output_changed(workspace["output"])
98 break
99 if "WorkspaceActivated" in line_json and line_json["WorkspaceActivated"]["focused"]: # noqa: E501
100 for workspace in workspaces:
101 if not workspace["id"] == line_json["WorkspaceActivated"]["id"]: # noqa: E501
102 continue
103 output_changed(workspace["output"])
104 break
105 '';
106 };
107 Install = {
108 WantedBy = [ "mako.service" ];
109 };
110 };
111 };
112}
diff --git a/accounts/gkleen@sif/shell/default.nix b/accounts/gkleen@sif/shell/default.nix
new file mode 100644
index 00000000..44462865
--- /dev/null
+++ b/accounts/gkleen@sif/shell/default.nix
@@ -0,0 +1,115 @@
1{ config, pkgs, lib, ... }:
2
3{
4 config = {
5 programs.quickshell = {
6 enable = true;
7 package = pkgs.symlinkJoin {
8 pname = pkgs.quickshell.pname + "-wrapped";
9 inherit (pkgs.quickshell) version meta;
10 paths = [ pkgs.quickshell ];
11 buildInputs = [ pkgs.makeWrapper ];
12 postBuild = ''
13 for binary in quickshell qs; do
14 wrapProgram $out/bin/$binary \
15 --prefix QML_IMPORT_PATH : ${pkgs.qt6Packages.callPackage ./quickshell-plugins {}}/${pkgs.qt6.qtbase.qtQmlPrefix}
16 done
17 '';
18 };
19 config = {
20 src = ./quickshell;
21 replacements = {
22 ignore_workspaces = builtins.toJSON (map ({ name, ... }: name) config.programs.niri.scratchspaces);
23 wallpapers = builtins.toJSON (pkgs.stdenvNoCC.mkDerivation {
24 name = "wallpapers";
25 srcs = [
26 (pkgs.fetchurl {
27 url = "https://esawebb.org/media/archives/images/publicationtiff10k/carinanebula3.tif";
28 hash = "sha256-YxZEweDKJfvfrdxb/QFmgJhcZDEJYxotoHrG+RRn1tw=";
29 })
30 (pkgs.fetchurl {
31 url = "https://esawebb.org/media/archives/images/original/pillarsofcreation_composite.tif";
32 hash = "sha256-qRiODxR0lZWdxgYXna0fNRFFDErpBJDwOJuQl6sNjRc=";
33 })
34 (pkgs.fetchurl {
35 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2212a.tif";
36 hash = "sha256-l2fqE/z//C1a0xkvZwsnwPbTSb+WuA11h+SUl3E1dhw=";
37 })
38 (pkgs.fetchurl {
39 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2415a.tif";
40 hash = "sha256-onBy7cPoUpDuzQStbY2E+qmlGgSLXPwFCLX53ukAb4c=";
41 })
42 (pkgs.fetchurl {
43 url = "https://esawebb.org/media/archives/images/publicationtiff10k/weic2330a.tif";
44 hash = "sha256-nn0ZtjZIrPcpj3YcLTsrL7XiXvyh3QYgCSmdDMD+3OM=";
45 })
46 (pkgs.fetchurl {
47 url = "https://esawebb.org/media/archives/images/original/weic2426a.tif";
48 hash = "sha256-EDnfPn3GE9jt6XPqiGInP7E2F3Az7d25NqATSWltDv0=";
49 })
50 (pkgs.fetchurl {
51 url = "https://esawebb.org/media/archives/images/original/weic2503a.tif";
52 hash = "sha256-3/RX6RQp8naELcgReHPd5/zhJkoCjnA10w5BEnNo+qI=";
53 })
54 (pkgs.fetchurl {
55 url = "https://esawebb.org/media/archives/images/original/weic2506a.tif";
56 hash = "sha256-aDld0aoY1owRxDVf7Jcyw71TH42M1foYotxn2thyFYw=";
57 })
58 (pkgs.fetchurl {
59 url = "https://esawebb.org/media/archives/images/original/weic2514a.tif";
60 hash = "sha256-jTi1G1Ofo5xsF6ggrbtYJHxqLaCQ7edM5B3uORiVQtg=";
61 })
62 (pkgs.fetchurl {
63 url = "https://esawebb.org/media/archives/images/original/weic2425c.tif";
64 hash = "sha256-oaEOexSJHEGj090dJF3ct5HAoR+Y5gRiPrUlxdvnTRo=";
65 })
66 ];
67
68 dontUnpack = true;
69
70 buildInputs = [ pkgs.imagemagick ];
71 buildPhase = ''
72 runHook preBuild
73
74 typeset sources=($srcs)
75
76 mkdir -p $out
77 magick ''${sources[0]} -crop 10000x5625+0+79 +repage -define jpeg:extent=10MB $out/carinanebula3.jpeg
78 magick ''${sources[1]} -crop 6716x3778+329+80 +repage -define jpeg:extent=10MB $out/pillarsofcreation_composite.jpeg
79 magick ''${sources[2]} -crop 10000x5625+0+79 +repage -define jpeg:extent=10MB $out/weic2212a.jpeg
80 magick ''${sources[3]} -crop 7650x4302+1166+389 +repage -define jpeg:extent=10MB $out/weic2415a.jpeg
81 magick ''${sources[4]} -crop 8732x4912+0+434 +repage -define jpeg:extent=10MB $out/weic2330a.jpeg
82 magick ''${sources[5]} -crop 5302x2982+636+0 +repage -define jpeg:extent=10MB $out/weic2426a.jpeg
83 magick ''${sources[6]} -crop 4328x2434+0+906 +repage -define jpeg:extent=10MB $out/weic2503a.jpeg
84 magick ''${sources[7]} -crop 4152x2335+0+666 +repage -define jpeg:extent=10MB $out/weic2506a.jpeg
85 magick ''${sources[8]} -crop 4320x2430+0+0 +repage -define jpeg:extent=10MB $out/weic2514a.jpeg
86 magick ''${sources[9]} -crop 5863x3298+0+477 +repage -define jpeg:extent=10MB $out/weic2425c.jpeg
87
88 runHook postBuild
89 '';
90 });
91 niri_session = builtins.toJSON [
92 (pkgs.writeShellScript "niri-session" ''
93 exec ${lib.getExe pkgs.dex} -w ${config.programs.niri.package}/share/wayland-sessions/niri.desktop &>/tmp/niri-session-$$.log
94 '')
95 # (lib.getExe pkgs.dex)
96 # "${config.programs.niri.package}/share/wayland-sessions/niri.desktop"
97 ];
98 username = builtins.toJSON config.home.username;
99 mdi = builtins.toJSON (pkgs.fetchFromGitHub {
100 owner = "Templarian";
101 repo = "MaterialDesign";
102 rev = "2424e748e0cc63ab7b9c095a099b9fe239b737c0";
103 hash = "sha256-QMGl7soAhErrrnY3aKOZpt49yebkSNzy10p/v5OaqQ0=";
104 });
105 worktime = builtins.toJSON (lib.getExe pkgs.worktime);
106 };
107 };
108 };
109 systemd.user.services.quickshell = {
110 Service = {
111 RuntimeDirectory = "quickshell";
112 };
113 };
114 };
115}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
new file mode 100644
index 00000000..020c0515
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
@@ -0,0 +1,168 @@
1set(INSTALL_QMLDIR "" CACHE STRING "QML install dir")
2set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix")
3
4# There doesn't seem to be a standard cross-distro qml install path.
5if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "")
6 message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.")
7else()
8 if ("${INSTALL_QMLDIR}" STREQUAL "")
9 set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}")
10 else()
11 set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}")
12 endif()
13
14 message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}")
15endif()
16
17# Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem
18# to be an official way to do it.
19# see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160
20function(install_qml_module arg_TARGET)
21 if (NOT DEFINED QML_FULL_INSTALLDIR)
22 return()
23 endif()
24
25 qt_query_qml_module(${arg_TARGET}
26 URI module_uri
27 VERSION module_version
28 PLUGIN_TARGET module_plugin_target
29 TARGET_PATH module_target_path
30 QMLDIR module_qmldir
31 TYPEINFO module_typeinfo
32 QML_FILES module_qml_files
33 RESOURCES module_resources
34 )
35
36 set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}")
37
38 if (NOT TARGET "${module_plugin_target}")
39 message(FATAL_ERROR "install_qml_modules called for a target without a plugin")
40 endif()
41
42 get_target_property(target_type "${arg_TARGET}" TYPE)
43 if (NOT "${target_type}" STREQUAL "STATIC_LIBRARY")
44 install(
45 TARGETS "${arg_TARGET}"
46 LIBRARY DESTINATION "${module_dir}"
47 RUNTIME DESTINATION "${module_dir}"
48 )
49
50 install(
51 TARGETS "${module_plugin_target}"
52 LIBRARY DESTINATION "${module_dir}"
53 RUNTIME DESTINATION "${module_dir}"
54 )
55 endif()
56
57 install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
58 install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
59
60 # Install QML files
61 list(LENGTH module_qml_files num_files)
62 if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
63 qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths)
64
65 math(EXPR last_index "${num_files} - 1")
66 foreach(i RANGE 0 ${last_index})
67 list(GET module_qml_files ${i} src_file)
68 list(GET qml_files_deploy_paths ${i} deploy_path)
69 get_filename_component(dst_name "${deploy_path}" NAME)
70 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
71 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
72 endforeach()
73 endif()
74
75 # Install resources
76 list(LENGTH module_resources num_files)
77 if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
78 qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths)
79
80 math(EXPR last_index "${num_files} - 1")
81 foreach(i RANGE 0 ${last_index})
82 list(GET module_resources ${i} src_file)
83 list(GET resources_deploy_paths ${i} deploy_path)
84 get_filename_component(dst_name "${deploy_path}" NAME)
85 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
86 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
87 endforeach()
88 endif()
89endfunction()
90
91
92cmake_minimum_required(VERSION 3.20)
93project(custom LANGUAGES CXX)
94
95find_package(Qt6 REQUIRED COMPONENTS Core Qml DBus)
96
97qt_standard_project_setup(REQUIRES 6.6)
98
99qt6_policy(SET QTP0001 NEW)
100qt6_add_qml_module(customplugin
101 URI "Custom"
102 PLUGIN_TARGET customplugin
103)
104
105set_source_files_properties(org.keepassxc.KeePassXC.MainWindow.xml PROPERTIES
106 CLASSNAME DBusKeePassXC
107 NO_NAMESPACE TRUE
108)
109qt_add_dbus_interface(DBUS_INTERFACES
110 org.keepassxc.KeePassXC.MainWindow.xml
111 dbus_keepassxc
112)
113
114set_source_files_properties(org.freedesktop.systemd1.Manager.xml PROPERTIES
115 CLASSNAME DBusSystemdManager
116 NO_NAMESPACE TRUE
117)
118qt_add_dbus_interface(DBUS_INTERFACES
119 org.freedesktop.systemd1.Manager.xml
120 dbus_systemd_manager
121)
122
123set_source_files_properties(org.freedesktop.login1.Manager.xml PROPERTIES
124 CLASSNAME DBusLogindManager
125 NO_NAMESPACE TRUE
126)
127qt_add_dbus_interface(DBUS_INTERFACES
128 org.freedesktop.login1.Manager.xml
129 dbus_logind_manager
130)
131
132set_source_files_properties(org.freedesktop.login1.Session.xml PROPERTIES
133 CLASSNAME DBusLogindSession
134 NO_NAMESPACE TRUE
135)
136qt_add_dbus_interface(DBUS_INTERFACES
137 org.freedesktop.login1.Session.xml
138 dbus_logind_session
139)
140
141set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES
142 CLASSNAME DBusProperties
143 NO_NAMESPACE TRUE
144)
145qt_add_dbus_interface(DBUS_INTERFACES
146 org.freedesktop.DBus.Properties.xml
147 dbus_properties
148)
149
150include_directories(${CMAKE_SOURCE_DIR}/build)
151
152target_compile_features(customplugin PUBLIC cxx_std_26)
153
154target_link_libraries(customplugin PRIVATE
155 Qt6::Core
156 Qt6::Qml
157 Qt6::DBus
158)
159
160target_sources(customplugin PRIVATE
161 Chrono.cpp Chrono.hpp
162 FileSelector.cpp FileSelector.hpp
163 KeePassXC.cpp KeePassXC.hpp
164 Systemd.cpp Systemd.hpp
165 ${DBUS_INTERFACES}
166)
167
168install_qml_module(customplugin)
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
new file mode 100644
index 00000000..22b3469b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
@@ -0,0 +1,83 @@
1#include "Chrono.hpp"
2
3#include <format>
4#include <iostream>
5#include <cmath>
6
7Chrono::Chrono(QObject* parent): QObject(parent) {
8 QObject::connect(&this->timer, &QTimer::timeout, this, &Chrono::onTimeout);
9 this->update();
10}
11
12bool Chrono::enabled() const { return this->mEnabled; }
13
14void Chrono::setEnabled(bool enabled) {
15 if (enabled == this->mEnabled) return;
16 this->mEnabled = enabled;
17 emit this->enabledChanged();
18 this->update();
19}
20
21Chrono::Precision Chrono::precision() const { return this->mPrecision; }
22
23void Chrono::setPrecision(Chrono::Precision precision) {
24 if (precision == this->mPrecision) return;
25 this->mPrecision = precision;
26 emit this->precisionChanged();
27 this->update();
28}
29
30void Chrono::onTimeout() {
31 this->setTime(this->targetTime);
32 this->schedule(this->targetTime);
33}
34
35void Chrono::update() {
36 if (this->mEnabled) {
37 this->setTime(std::chrono::time_point<std::chrono::system_clock>());
38 this->schedule(std::chrono::time_point<std::chrono::system_clock>());
39 } else {
40 this->timer.stop();
41 }
42}
43
44void Chrono::setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
45 using namespace std::chrono_literals;
46
47 auto currentTime = std::chrono::system_clock::now();
48 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
49 this->currentTime = abs(offset) < 500ms ? targetTime : currentTime;
50
51 switch (this->mPrecision) {
52 case Chrono::Hours: this->currentTime = std::chrono::time_point_cast<std::chrono::hours>(this->currentTime);
53 case Chrono::Minutes: this->currentTime = std::chrono::time_point_cast<std::chrono::minutes>(this->currentTime);
54 case Chrono::Seconds: this->currentTime = std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime);
55 }
56
57 emit this->dateChanged();
58}
59
60void Chrono::schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
61 using namespace std::chrono_literals;
62
63 auto currentTime = std::chrono::system_clock::now();
64 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
65 auto nextTime = abs(offset) < 500ms ? targetTime : currentTime;
66
67 switch (this->mPrecision) {
68 case Chrono::Hours: nextTime = std::chrono::time_point_cast<std::chrono::hours>(nextTime) + 1h;
69 case Chrono::Minutes: nextTime = std::chrono::time_point_cast<std::chrono::minutes>(nextTime) + 1min;
70 case Chrono::Seconds: nextTime = std::chrono::time_point_cast<std::chrono::seconds>(nextTime) + 1s;
71 }
72
73 this->targetTime = nextTime;
74 this->timer.start(std::chrono::duration_cast<std::chrono::milliseconds>(nextTime - currentTime));
75}
76
77QString Chrono::format(const QString& fmt) const {
78 return QString::fromStdString(std::format(std::runtime_format(fmt.toStdString()), std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime))));
79}
80
81QDateTime Chrono::date() const {
82 return QDateTime::fromStdTimePoint(std::chrono::time_point_cast<std::chrono::milliseconds>(this->currentTime));
83}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
new file mode 100644
index 00000000..04080187
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
@@ -0,0 +1,55 @@
1#pragma once
2
3#include <chrono>
4
5#include <QDateTime>
6#include <QObject>
7#include <QTimer>
8
9#include <qqmlintegration.h>
10
11class Chrono : public QObject {
12 Q_OBJECT;
13 Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
14 Q_PROPERTY(Chrono::Precision precision READ precision WRITE setPrecision NOTIFY precisionChanged);
15 Q_PROPERTY(QDateTime date READ date NOTIFY dateChanged)
16 QML_ELEMENT;
17
18public:
19 enum Precision : quint8 {
20 Hours = 1,
21 Minutes = 2,
22 Seconds = 3,
23 };
24 Q_ENUM(Precision);
25
26 explicit Chrono(QObject* parent = nullptr);
27
28 bool enabled() const;
29 void setEnabled(bool enabled);
30
31 Chrono::Precision precision() const;
32 void setPrecision(Chrono::Precision precision);
33
34 Q_INVOKABLE QString format(const QString& fmt) const;
35
36 QDateTime date() const;
37
38signals:
39 void enabledChanged();
40 void precisionChanged();
41 void dateChanged();
42
43private slots:
44 void onTimeout();
45
46private:
47 bool mEnabled = true;
48 Chrono::Precision mPrecision = Chrono::Seconds;
49 QTimer timer;
50 std::chrono::time_point<std::chrono::system_clock> currentTime, targetTime;
51
52 void update();
53 void setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
54 void schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
55};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp
new file mode 100644
index 00000000..d7051d2a
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.cpp
@@ -0,0 +1,102 @@
1#include "FileSelector.hpp"
2
3#include <sstream>
4#include <vector>
5#include <random>
6#include <algorithm>
7
8#include <iostream>
9#include <format>
10
11namespace fs = std::filesystem;
12
13FileSelector::FileSelector(QObject* parent): QObject(parent) {
14 QObject::connect(&this->timer, &QTimer::timeout, this, &FileSelector::onTimeout);
15 this->timer.setTimerType(Qt::PreciseTimer);
16}
17
18QString FileSelector::directory() const {
19 return QString::fromStdString(this->mDirectory->string());
20}
21void FileSelector::setDirectory(QString directory) {
22 this->mDirectory = directory.toStdString();
23 if (this->mDirectory && this->mEpoch)
24 this->update();
25 emit this->directoryChanged();
26}
27
28unsigned int FileSelector::epoch() const {
29 return std::chrono::duration_cast<std::chrono::milliseconds>(*this->mEpoch).count();
30}
31void FileSelector::setEpoch(unsigned int epoch) {
32 this->mEpoch = std::chrono::milliseconds{epoch};
33 if (this->mDirectory && this->mEpoch)
34 this->update();
35 emit this->epochChanged();
36}
37
38QString FileSelector::seed() const {
39 return this->mSeed;
40}
41void FileSelector::setSeed(QString seed) {
42 this->mSeed = seed;
43 emit this->seedChanged();
44 if (this->mDirectory && this->mEpoch)
45 emit this->selectedChanged();
46}
47
48QString FileSelector::selected() const {
49 if (!this->mDirectory || !this->mEpoch)
50 return QString();
51
52 std::vector<fs::path> shuffled(this->mFiles.begin(), this->mFiles.end());
53 std::sort(shuffled.begin(), shuffled.end());
54
55 auto currentTime = std::chrono::system_clock::now();
56 uint64_t currentEpoch = currentTime.time_since_epoch() / *this->mEpoch;
57 std::chrono::milliseconds timeInEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime.time_since_epoch()) % *this->mEpoch;
58
59 std::ostringstream seed;
60 seed << this->mSeed.size() << ";" << this->mSeed.toStdString() << ";";
61 seed << *this->mEpoch << ";";
62 seed << currentEpoch << ";";
63 seed << this->mDirectory->string().size() << ";" << *this->mDirectory << ";";
64 seed << this->mFiles.size() << ";";
65 for (const fs::path& p: this->mFiles)
66 seed << p.string().size() << ";" << p << ";";
67
68 std::vector<std::seed_seq::result_type> v;
69 v.reserve(seed.str().size());
70 for (const char& c: seed.str())
71 v.push_back(c);
72
73 std::seed_seq engine_seed(v.begin(), v.end());
74 std::mt19937 g(engine_seed);
75 std::shuffle(shuffled.begin(), shuffled.end(), g);
76
77 std::vector<fs::path>::size_type ix = shuffled.size() * timeInEpoch / *this->mEpoch;
78 return QString::fromStdString((*this->mDirectory / shuffled[ix]).string());
79}
80
81void FileSelector::onTimeout() {
82 if (!this->mFiles.size())
83 return;
84
85 auto currentTime = std::chrono::system_clock::now();
86 uint64_t currentMinorEpoch = currentTime.time_since_epoch() / (*this->mEpoch / this->mFiles.size());
87 auto nextTime = std::chrono::time_point<std::chrono::system_clock>((currentMinorEpoch + 1) * (*this->mEpoch / this->mFiles.size()));
88 this->timer.start(std::chrono::duration_cast<std::chrono::milliseconds>(nextTime - currentTime));
89
90 emit this->selectedChanged();
91}
92
93void FileSelector::update() {
94 this->mFiles = std::set<fs::path>{};
95 for (const fs::directory_entry& entry:
96 fs::recursive_directory_iterator(*this->mDirectory, fs::directory_options::follow_directory_symlink))
97 {
98 this->mFiles.insert(fs::relative(entry, *this->mDirectory));
99 }
100
101 this->onTimeout();
102}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp
new file mode 100644
index 00000000..72c4f2a7
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/FileSelector.hpp
@@ -0,0 +1,52 @@
1#pragma once
2
3#include <filesystem>
4#include <chrono>
5#include <set>
6#include <optional>
7
8#include <QObject>
9#include <QTimer>
10
11#include <qqmlintegration.h>
12
13class FileSelector : public QObject {
14 Q_OBJECT;
15 Q_PROPERTY(QString directory READ directory WRITE setDirectory NOTIFY directoryChanged REQUIRED);
16 Q_PROPERTY(unsigned int epoch READ epoch WRITE setEpoch NOTIFY epochChanged REQUIRED);
17 Q_PROPERTY(QString seed READ seed WRITE setSeed NOTIFY seedChanged);
18 Q_PROPERTY(QString selected READ selected NOTIFY selectedChanged);
19 QML_ELEMENT;
20
21public:
22 explicit FileSelector(QObject* parent = nullptr);
23
24 QString directory() const;
25 void setDirectory(QString directory);
26
27 unsigned int epoch() const;
28 void setEpoch(unsigned int epoch);
29
30 QString seed() const;
31 void setSeed(QString seed);
32
33 QString selected() const;
34
35signals:
36 void directoryChanged();
37 void epochChanged();
38 void seedChanged();
39 void selectedChanged();
40
41private slots:
42 void onTimeout();
43
44private:
45 std::optional<std::filesystem::path> mDirectory;
46 std::optional<std::chrono::milliseconds> mEpoch;
47 std::set<std::filesystem::path> mFiles;
48 QString mSeed;
49 QTimer timer;
50
51 void update();
52};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
new file mode 100644
index 00000000..f6e4dd6e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
@@ -0,0 +1,18 @@
1#include "KeePassXC.hpp"
2
3#include <QDBusConnection>
4
5KeePassXC::KeePassXC() {
6 this->service = new DBusKeePassXC(DBusKeePassXC::staticInterfaceName(), "/keepassxc", QDBusConnection::sessionBus(), this);
7}
8KeePassXC::~KeePassXC() {
9 if (this->service)
10 delete this->service;
11}
12
13void KeePassXC::lockAllDatabases() {
14 if (!this->service)
15 return;
16
17 this->service->lockAllDatabases();
18}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
new file mode 100644
index 00000000..c4cd71e0
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
@@ -0,0 +1,21 @@
1#pragma once
2
3#include "dbus_keepassxc.h"
4
5#include <QObject>
6#include <QtQmlIntegration/qqmlintegration.h>
7
8class KeePassXC : public QObject {
9 Q_OBJECT;
10 QML_SINGLETON;
11 QML_ELEMENT;
12
13public:
14 explicit KeePassXC();
15 ~KeePassXC();
16
17 Q_INVOKABLE void lockAllDatabases();
18
19private:
20 DBusKeePassXC* service = nullptr;
21};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
new file mode 100644
index 00000000..308659e9
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
@@ -0,0 +1,198 @@
1#include "Systemd.hpp"
2
3#include <sstream>
4
5#include <QDBusConnection>
6#include <QDBusReply>
7#include <QDebug>
8#include <QDBusUnixFileDescriptor>
9#include <QDBusObjectPath>
10
11Systemd::Systemd(QObject* parent) : QObject(parent) {
12 this->systemdManager = new DBusSystemdManager(
13 "org.freedesktop.systemd1",
14 "/org/freedesktop/systemd1",
15 QDBusConnection::sessionBus(),
16 this
17 );
18 this->logindManager = new DBusLogindManager(
19 "org.freedesktop.login1",
20 "/org/freedesktop/login1",
21 QDBusConnection::systemBus(),
22 this
23 );
24
25 QDBusReply<QDBusObjectPath> sessionPath = this->logindManager->GetSession("auto");
26 qDebug() << sessionPath;
27 this->logindSession = new DBusLogindSession(
28 "org.freedesktop.login1",
29 sessionPath.value().path(),
30 QDBusConnection::systemBus(),
31 this
32 );
33 this->logindSessionProperties = new DBusProperties(
34 "org.freedesktop.login1",
35 sessionPath.value().path(),
36 QDBusConnection::systemBus(),
37 this
38 );
39
40 QObject::connect(this->logindManager, &DBusLogindManager::PrepareForShutdown, this, &Systemd::shutdown);
41 QObject::connect(this->logindManager, &DBusLogindManager::PrepareForSleep, this, &Systemd::sleep);
42 QObject::connect(this->logindSession, &DBusLogindSession::Lock, this, &Systemd::lock);
43 QObject::connect(this->logindSession, &DBusLogindSession::Unlock, this, &Systemd::unlock);
44 QObject::connect(this->logindSessionProperties, &DBusProperties::PropertiesChanged, this, &Systemd::onLogindSessionPropertiesChanged);
45}
46
47void Systemd::onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties) {
48 if (changed_properties.contains("IdleHint") || invalidated_properties.contains("IdleHint"))
49 emit this->idleHintChanged();
50 if (changed_properties.contains("LockedHint") || invalidated_properties.contains("LockedHint"))
51 emit this->lockedHintChanged();
52}
53
54void Systemd::stopUserUnit(const QString& unit, const QString& mode) { this->systemdManager->StopUnit(unit, mode); }
55
56void Systemd::setBrightness(const QString& subsystem, const QString& name, quint32 brightness) { this->logindSession->SetBrightness(subsystem, name, brightness); }
57
58bool Systemd::idleHint() { return this->logindSession->idleHint(); }
59void Systemd::setIdleHint(bool idle) { this->logindSession->SetIdleHint(idle); }
60bool Systemd::lockedHint() { return this->logindSession->lockedHint(); }
61void Systemd::setLockedHint(bool locked) { this->logindSession->SetLockedHint(locked); }
62void Systemd::lockSession() { this->logindSession->call("Lock"); }
63void Systemd::suspend() { this->logindManager->Suspend(true); }
64void Systemd::hibernate() { this->logindManager->Hibernate(true); }
65
66std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::WhatItem what) {
67 if (what == SystemdInhibitorParams::Shutdown)
68 return "shutdown";
69 else if (what == SystemdInhibitorParams::Sleep)
70 return "sleep";
71 else if (what == SystemdInhibitorParams::Idle)
72 return "idle";
73 else if (what == SystemdInhibitorParams::HandlePowerKey)
74 return "handle-power-key";
75 else if (what == SystemdInhibitorParams::HandleSuspendKey)
76 return "handle-suspend-key";
77 else if (what == SystemdInhibitorParams::HandleHibernateKey)
78 return "handle-hibernate-key";
79 else if (what == SystemdInhibitorParams::HandleLidSwitch)
80 return "handle-lid-switch";
81 return "";
82}
83
84std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::What what) {
85 std::ostringstream res;
86 bool first = true;
87 for (const WhatItem& item: SystemdInhibitorParams::allWhatItems) {
88 if (!(what & item))
89 continue;
90
91 if (!first)
92 res << ":";
93 else
94 first = false;
95 res << SystemdInhibitorParams::toString(item);
96 }
97 return res.str();
98}
99
100std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::Mode mode) {
101 if (mode == SystemdInhibitorParams::Block)
102 return "block";
103 else if (mode == SystemdInhibitorParams::BlockWeak)
104 return "block-weak";
105 else if (mode == SystemdInhibitorParams::Delay)
106 return "delay";
107 return "";
108}
109
110bool SystemdInhibitor::enabled() const { return static_cast<bool>(this->activeInhibitor); }
111void SystemdInhibitor::setEnabled(bool enabled) {
112 this->mEnabled = enabled;
113
114 if (enabled)
115 this->update();
116 else
117 this->release();
118}
119
120SystemdInhibitorParams::What SystemdInhibitor::what() const { return this->mWhat; }
121void SystemdInhibitor::setWhat(SystemdInhibitorParams::What what) {
122 this->mWhat = what;
123 this->update();
124}
125
126QString SystemdInhibitor::who() const { return this->mWho; }
127void SystemdInhibitor::setWho(QString who) {
128 this->mWho = who;
129 this->update();
130}
131
132QString SystemdInhibitor::why() const { return this->mWhy; }
133void SystemdInhibitor::setWhy(QString why) {
134 this->mWhy = why;
135 this->update();
136}
137
138SystemdInhibitorParams::Mode SystemdInhibitor::mode() const { return this->mMode; }
139void SystemdInhibitor::setMode(SystemdInhibitorParams::Mode mode) {
140 this->mMode = mode;
141 this->update();
142}
143
144SystemdInhibitor::ActiveSystemdInhibitor::ActiveSystemdInhibitor(SystemdInhibitorParams::What what_, QString who_, QString why_, SystemdInhibitorParams::Mode mode_): what(what_), who(who_), why(why_), mode(mode_) {
145 DBusLogindManager logindManager(
146 "org.freedesktop.login1",
147 "/org/freedesktop/login1",
148 QDBusConnection::systemBus()
149 );
150 QDBusReply<QDBusUnixFileDescriptor> fd_ = logindManager.Inhibit(QString::fromStdString(SystemdInhibitorParams::toString(what)), who, why, QString::fromStdString(SystemdInhibitorParams::toString(mode)));
151 if (fd_.error().isValid())
152 throw fd_.error();
153 this->fd = ::dup(fd_.value().fileDescriptor());
154}
155SystemdInhibitor::ActiveSystemdInhibitor::~ActiveSystemdInhibitor() {
156 if (this->fd != -1)
157 ::close(this->fd);
158}
159
160void SystemdInhibitor::release() {
161 if (!this->activeInhibitor)
162 return;
163
164 this->activeInhibitor.reset();
165 emit this->enabledChanged();
166}
167
168void SystemdInhibitor::update() {
169 if (!this->mWhat || this->mWho.isEmpty() || this->mWhy.isEmpty() || !this->mMode || !this->mEnabled)
170 if (this->activeInhibitor)
171 this->release();
172 else
173 return;
174
175 if (this->activeInhibitor && this->mWhat == this->activeInhibitor->what && this->mWho == this->activeInhibitor->who && this->mWhy == this->activeInhibitor->why && this->mMode == this->activeInhibitor->mode)
176 return;
177
178 std::unique_ptr<ActiveSystemdInhibitor> otherInhibitor;
179 try {
180 otherInhibitor.reset(new SystemdInhibitor::ActiveSystemdInhibitor(this->mWhat, this->mWho, this->mWhy, this->mMode));
181 } catch (const QDBusError& err) {
182 qCritical().noquote()
183 << err.name().toStdString() << " " << err.message().toStdString();
184 return;
185 }
186 this->activeInhibitor.swap(otherInhibitor);
187
188 if (!otherInhibitor && this->activeInhibitor)
189 emit this->enabledChanged();
190 if (otherInhibitor && otherInhibitor->what != this->activeInhibitor->what)
191 emit this->whatChanged();
192 if (otherInhibitor && otherInhibitor->who != this->activeInhibitor->who)
193 emit this->whoChanged();
194 if (otherInhibitor && otherInhibitor->why != this->activeInhibitor->why)
195 emit this->whyChanged();
196 if (otherInhibitor && otherInhibitor->mode != this->activeInhibitor->mode)
197 emit this->modeChanged();
198}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
new file mode 100644
index 00000000..615024d2
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
@@ -0,0 +1,147 @@
1#pragma once
2
3#include <cstdint>
4#include <memory>
5#include <string>
6
7#include <QObject>
8#include <QDBusInterface>
9#include <QtQmlIntegration/qqmlintegration.h>
10
11#include "dbus_systemd_manager.h"
12#include "dbus_logind_manager.h"
13#include "dbus_logind_session.h"
14#include "dbus_properties.h"
15
16class Systemd : public QObject {
17 Q_OBJECT;
18 QML_SINGLETON;
19 QML_ELEMENT;
20
21public:
22 explicit Systemd(QObject* parent = nullptr);
23
24 Q_PROPERTY(bool idleHint READ idleHint WRITE setIdleHint NOTIFY idleHintChanged)
25 Q_PROPERTY(bool lockedHint READ lockedHint WRITE setLockedHint NOTIFY lockedHintChanged)
26
27 Q_INVOKABLE void stopUserUnit(const QString& unit, const QString& mode);
28 Q_INVOKABLE void setBrightness(const QString& subsystem, const QString& name, quint32 brightness);
29 Q_INVOKABLE void setIdleHint(bool idle);
30 Q_INVOKABLE void setLockedHint(bool locked);
31 Q_INVOKABLE void lockSession();
32 Q_INVOKABLE void suspend();
33 Q_INVOKABLE void hibernate();
34
35 bool idleHint();
36 bool lockedHint();
37
38signals:
39 void shutdown(bool before);
40 void sleep(bool before);
41 void lock();
42 void unlock();
43 void idleHintChanged();
44 void lockedHintChanged();
45
46private slots:
47 void onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties);
48
49private:
50 DBusSystemdManager* systemdManager;
51 DBusLogindManager* logindManager;
52 DBusLogindSession* logindSession;
53 DBusProperties* logindSessionProperties;
54};
55
56class SystemdInhibitorParams : public QObject {
57 Q_OBJECT;
58 QML_ELEMENT;
59 QML_SINGLETON;
60
61public:
62 enum WhatItem : uint8_t {
63 Shutdown = 0b1,
64 Sleep = 0b10,
65 Idle = 0b100,
66 HandlePowerKey = 0b1000,
67 HandleSuspendKey = 0b10000,
68 HandleHibernateKey = 0b100000,
69 HandleLidSwitch = 0b1000000,
70 };
71 Q_ENUM(WhatItem);
72 Q_DECLARE_FLAGS(What, WhatItem);
73
74 enum Mode : uint8_t {
75 Block = 1,
76 BlockWeak = 2,
77 Delay = 3,
78 };
79 Q_ENUM(Mode);
80
81 Q_INVOKABLE static std::string toString(WhatItem what);
82 Q_INVOKABLE static std::string toString(What what);
83 Q_INVOKABLE static std::string toString(Mode mode);
84
85 static constexpr WhatItem allWhatItems[] = { Shutdown, Sleep, Idle, HandlePowerKey, HandleSuspendKey, HandleHibernateKey, HandleLidSwitch };
86};
87Q_DECLARE_OPERATORS_FOR_FLAGS(SystemdInhibitorParams::What)
88
89class SystemdInhibitor : public QObject {
90 Q_OBJECT;
91 Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
92 Q_PROPERTY(SystemdInhibitorParams::What what READ what WRITE setWhat NOTIFY whatChanged);
93 Q_PROPERTY(QString who READ who WRITE setWho NOTIFY whoChanged);
94 Q_PROPERTY(QString why READ why WRITE setWhy NOTIFY whyChanged);
95 Q_PROPERTY(SystemdInhibitorParams::Mode mode READ mode WRITE setMode NOTIFY modeChanged);
96 QML_ELEMENT;
97
98public:
99 explicit SystemdInhibitor(QObject* parent = nullptr): QObject(parent) {}
100
101 bool enabled() const;
102 void setEnabled(bool enabled);
103
104 SystemdInhibitorParams::What what() const;
105 void setWhat(SystemdInhibitorParams::What what);
106
107 QString who() const;
108 void setWho(QString who);
109
110 QString why() const;
111 void setWhy(QString why);
112
113 SystemdInhibitorParams::Mode mode() const;
114 void setMode(SystemdInhibitorParams::Mode mode);
115
116 Q_INVOKABLE void release();
117
118signals:
119 void enabledChanged();
120 void whatChanged();
121 void whoChanged();
122 void whyChanged();
123 void modeChanged();
124
125private:
126 class ActiveSystemdInhibitor {
127 public:
128 uint32_t fd = -1;
129 SystemdInhibitorParams::What what;
130 QString who;
131 QString why;
132 SystemdInhibitorParams::Mode mode;
133
134 ActiveSystemdInhibitor(SystemdInhibitorParams::What what_, QString who_, QString why_, SystemdInhibitorParams::Mode mode_);
135 ~ActiveSystemdInhibitor();
136 };
137
138 void update();
139
140 bool mEnabled = true;
141 std::unique_ptr<ActiveSystemdInhibitor> activeInhibitor;
142 SystemdInhibitorParams::What mWhat = static_cast<SystemdInhibitorParams::What>(0);
143 QString mWho;
144 QString mWhy;
145 SystemdInhibitorParams::Mode mMode = static_cast<SystemdInhibitorParams::Mode>(0);
146};
147
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
new file mode 100644
index 00000000..e66ba9e3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
@@ -0,0 +1,7 @@
1#include <QQmlEngineExtensionPlugin>
2
3class CustomPlugin : public QQmlEngineExtensionPlugin
4{
5 Q_OBJECT
6 Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
7};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
new file mode 100644
index 00000000..20a195eb
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
@@ -0,0 +1,30 @@
1{ lib
2, stdenv
3, cmake
4, qt6
5, fmt
6, keepassxc
7, systemd
8}:
9
10stdenv.mkDerivation rec {
11 name = "quickshell-custom";
12
13 src = ./.;
14
15 prePatch = ''
16 cp ${keepassxc.src}/src/gui/org.keepassxc.KeePassXC.MainWindow.xml .
17 '';
18
19 nativeBuildInputs = [ cmake qt6.wrapQtAppsHook ];
20 buildInputs = [
21 qt6.qtbase
22 qt6.qtdeclarative
23 ];
24
25 cmakeFlags = [
26 (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
27 ];
28
29 LC_ALL = "C.UTF-8";
30}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml
new file mode 100644
index 00000000..7588e7a5
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.DBus.Properties.xml
@@ -0,0 +1,28 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.DBus.Properties">
5 <method name="Get">
6 <arg name="interface_name" direction="in" type="s"/>
7 <arg name="property_name" direction="in" type="s"/>
8 <arg name="value" direction="out" type="v"/>
9 </method>
10 <method name="GetAll">
11 <arg name="interface_name" direction="in" type="s"/>
12 <arg name="props" direction="out" type="a{sv}"/>
13 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
14 </method>
15 <method name="Set">
16 <arg name="interface_name" direction="in" type="s"/>
17 <arg name="property_name" direction="in" type="s"/>
18 <arg name="value" direction="in" type="v"/>
19 </method>
20 <signal name="PropertiesChanged">
21 <arg type="s" name="interface_name"/>
22 <arg type="a{sv}" name="changed_properties"/>
23 <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
24 <arg type="as" name="invalidated_properties"/>
25 </signal>
26 </interface>
27</node>
28
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml
new file mode 100644
index 00000000..120a06d9
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Manager.xml
@@ -0,0 +1,445 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.login1.Manager">
5 <property name="EnableWallMessages" type="b" access="readwrite">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
7 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
8 </property>
9 <property name="WallMessage" type="s" access="readwrite">
10 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
11 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
12 </property>
13 <property name="NAutoVTs" type="u" access="read">
14 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
15 </property>
16 <property name="KillOnlyUsers" type="as" access="read">
17 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
18 </property>
19 <property name="KillExcludeUsers" type="as" access="read">
20 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
21 </property>
22 <property name="KillUserProcesses" type="b" access="read">
23 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
24 </property>
25 <property name="RebootParameter" type="s" access="read">
26 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
27 </property>
28 <property name="RebootToFirmwareSetup" type="b" access="read">
29 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
30 </property>
31 <property name="RebootToBootLoaderMenu" type="t" access="read">
32 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
33 </property>
34 <property name="RebootToBootLoaderEntry" type="s" access="read">
35 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
36 </property>
37 <property name="BootLoaderEntries" type="as" access="read">
38 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
39 </property>
40 <property name="IdleHint" type="b" access="read">
41 </property>
42 <property name="IdleSinceHint" type="t" access="read">
43 </property>
44 <property name="IdleSinceHintMonotonic" type="t" access="read">
45 </property>
46 <property name="BlockInhibited" type="s" access="read">
47 </property>
48 <property name="BlockWeakInhibited" type="s" access="read">
49 </property>
50 <property name="DelayInhibited" type="s" access="read">
51 </property>
52 <property name="InhibitDelayMaxUSec" type="t" access="read">
53 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
54 </property>
55 <property name="UserStopDelayUSec" type="t" access="read">
56 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
57 </property>
58 <property name="SleepOperation" type="as" access="read">
59 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
60 </property>
61 <property name="HandlePowerKey" type="s" access="read">
62 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
63 </property>
64 <property name="HandlePowerKeyLongPress" type="s" access="read">
65 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
66 </property>
67 <property name="HandleRebootKey" type="s" access="read">
68 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
69 </property>
70 <property name="HandleRebootKeyLongPress" type="s" access="read">
71 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
72 </property>
73 <property name="HandleSuspendKey" type="s" access="read">
74 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
75 </property>
76 <property name="HandleSuspendKeyLongPress" type="s" access="read">
77 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
78 </property>
79 <property name="HandleHibernateKey" type="s" access="read">
80 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
81 </property>
82 <property name="HandleHibernateKeyLongPress" type="s" access="read">
83 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
84 </property>
85 <property name="HandleLidSwitch" type="s" access="read">
86 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
87 </property>
88 <property name="HandleLidSwitchExternalPower" type="s" access="read">
89 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
90 </property>
91 <property name="HandleLidSwitchDocked" type="s" access="read">
92 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
93 </property>
94 <property name="HandleSecureAttentionKey" type="s" access="read">
95 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
96 </property>
97 <property name="HoldoffTimeoutUSec" type="t" access="read">
98 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
99 </property>
100 <property name="IdleAction" type="s" access="read">
101 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
102 </property>
103 <property name="IdleActionUSec" type="t" access="read">
104 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
105 </property>
106 <property name="PreparingForShutdown" type="b" access="read">
107 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
108 </property>
109 <property name="PreparingForShutdownWithMetadata" type="a{sv}" access="read">
110 <annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
111 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
112 </property>
113 <property name="PreparingForSleep" type="b" access="read">
114 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
115 </property>
116 <!-- <property name="ScheduledShutdown" type="(st)" access="read"> -->
117 <!-- </property> -->
118 <property name="DesignatedMaintenanceTime" type="s" access="read">
119 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
120 </property>
121 <property name="Docked" type="b" access="read">
122 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
123 </property>
124 <property name="LidClosed" type="b" access="read">
125 </property>
126 <property name="OnExternalPower" type="b" access="read">
127 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
128 </property>
129 <property name="RemoveIPC" type="b" access="read">
130 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
131 </property>
132 <property name="RuntimeDirectorySize" type="t" access="read">
133 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
134 </property>
135 <property name="RuntimeDirectoryInodesMax" type="t" access="read">
136 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
137 </property>
138 <property name="InhibitorsMax" type="t" access="read">
139 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
140 </property>
141 <property name="NCurrentInhibitors" type="t" access="read">
142 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
143 </property>
144 <property name="SessionsMax" type="t" access="read">
145 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
146 </property>
147 <property name="NCurrentSessions" type="t" access="read">
148 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
149 </property>
150 <property name="StopIdleSessionUSec" type="t" access="read">
151 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
152 </property>
153 <method name="GetSession">
154 <arg type="s" name="session_id" direction="in"/>
155 <arg type="o" name="object_path" direction="out"/>
156 </method>
157 <method name="GetSessionByPID">
158 <arg type="u" name="pid" direction="in"/>
159 <arg type="o" name="object_path" direction="out"/>
160 </method>
161 <method name="GetUser">
162 <arg type="u" name="uid" direction="in"/>
163 <arg type="o" name="object_path" direction="out"/>
164 </method>
165 <method name="GetUserByPID">
166 <arg type="u" name="pid" direction="in"/>
167 <arg type="o" name="object_path" direction="out"/>
168 </method>
169 <method name="GetSeat">
170 <arg type="s" name="seat_id" direction="in"/>
171 <arg type="o" name="object_path" direction="out"/>
172 </method>
173 <!-- <method name="ListSessions"> -->
174 <!-- <arg type="a(susso)" name="sessions" direction="out"/> -->
175 <!-- </method> -->
176 <!-- <method name="ListSessionsEx"> -->
177 <!-- <arg type="a(sussussbto)" name="sessions" direction="out"/> -->
178 <!-- </method> -->
179 <!-- <method name="ListUsers"> -->
180 <!-- <arg type="a(uso)" name="users" direction="out"/> -->
181 <!-- </method> -->
182 <!-- <method name="ListSeats"> -->
183 <!-- <arg type="a(so)" name="seats" direction="out"/> -->
184 <!-- </method> -->
185 <!-- <method name="ListInhibitors"> -->
186 <!-- <arg type="a(ssssuu)" name="inhibitors" direction="out"/> -->
187 <!-- </method> -->
188 <!-- <method name="CreateSession"> -->
189 <!-- <arg type="u" name="uid" direction="in"/> -->
190 <!-- <arg type="u" name="pid" direction="in"/> -->
191 <!-- <arg type="s" name="service" direction="in"/> -->
192 <!-- <arg type="s" name="type" direction="in"/> -->
193 <!-- <arg type="s" name="class" direction="in"/> -->
194 <!-- <arg type="s" name="desktop" direction="in"/> -->
195 <!-- <arg type="s" name="seat_id" direction="in"/> -->
196 <!-- <arg type="u" name="vtnr" direction="in"/> -->
197 <!-- <arg type="s" name="tty" direction="in"/> -->
198 <!-- <arg type="s" name="display" direction="in"/> -->
199 <!-- <arg type="b" name="remote" direction="in"/> -->
200 <!-- <arg type="s" name="remote_user" direction="in"/> -->
201 <!-- <arg type="s" name="remote_host" direction="in"/> -->
202 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
203 <!-- <arg type="s" name="session_id" direction="out"/> -->
204 <!-- <arg type="o" name="object_path" direction="out"/> -->
205 <!-- <arg type="s" name="runtime_path" direction="out"/> -->
206 <!-- <arg type="h" name="fifo_fd" direction="out"/> -->
207 <!-- <arg type="u" name="uid" direction="out"/> -->
208 <!-- <arg type="s" name="seat_id" direction="out"/> -->
209 <!-- <arg type="u" name="vtnr" direction="out"/> -->
210 <!-- <arg type="b" name="existing" direction="out"/> -->
211 <!-- <annotation name="org.freedesktop.systemd1.Privileged" value="true"/> -->
212 <!-- </method> -->
213 <!-- <method name="CreateSessionWithPIDFD"> -->
214 <!-- <arg type="u" name="uid" direction="in"/> -->
215 <!-- <arg type="h" name="pidfd" direction="in"/> -->
216 <!-- <arg type="s" name="service" direction="in"/> -->
217 <!-- <arg type="s" name="type" direction="in"/> -->
218 <!-- <arg type="s" name="class" direction="in"/> -->
219 <!-- <arg type="s" name="desktop" direction="in"/> -->
220 <!-- <arg type="s" name="seat_id" direction="in"/> -->
221 <!-- <arg type="u" name="vtnr" direction="in"/> -->
222 <!-- <arg type="s" name="tty" direction="in"/> -->
223 <!-- <arg type="s" name="display" direction="in"/> -->
224 <!-- <arg type="b" name="remote" direction="in"/> -->
225 <!-- <arg type="s" name="remote_user" direction="in"/> -->
226 <!-- <arg type="s" name="remote_host" direction="in"/> -->
227 <!-- <arg type="t" name="flags" direction="in"/> -->
228 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
229 <!-- <arg type="s" name="session_id" direction="out"/> -->
230 <!-- <arg type="o" name="object_path" direction="out"/> -->
231 <!-- <arg type="s" name="runtime_path" direction="out"/> -->
232 <!-- <arg type="h" name="fifo_fd" direction="out"/> -->
233 <!-- <arg type="u" name="uid" direction="out"/> -->
234 <!-- <arg type="s" name="seat_id" direction="out"/> -->
235 <!-- <arg type="u" name="vtnr" direction="out"/> -->
236 <!-- <arg type="b" name="existing" direction="out"/> -->
237 <!-- <annotation name="org.freedesktop.systemd1.Privileged" value="true"/> -->
238 <!-- </method> -->
239 <method name="ReleaseSession">
240 <arg type="s" name="session_id" direction="in"/>
241 </method>
242 <method name="ActivateSession">
243 <arg type="s" name="session_id" direction="in"/>
244 </method>
245 <method name="ActivateSessionOnSeat">
246 <arg type="s" name="session_id" direction="in"/>
247 <arg type="s" name="seat_id" direction="in"/>
248 </method>
249 <method name="LockSession">
250 <arg type="s" name="session_id" direction="in"/>
251 </method>
252 <method name="UnlockSession">
253 <arg type="s" name="session_id" direction="in"/>
254 </method>
255 <method name="LockSessions">
256 </method>
257 <method name="UnlockSessions">
258 </method>
259 <method name="KillSession">
260 <arg type="s" name="session_id" direction="in"/>
261 <arg type="s" name="whom" direction="in"/>
262 <arg type="i" name="signal_number" direction="in"/>
263 </method>
264 <method name="KillUser">
265 <arg type="u" name="uid" direction="in"/>
266 <arg type="i" name="signal_number" direction="in"/>
267 </method>
268 <method name="TerminateSession">
269 <arg type="s" name="session_id" direction="in"/>
270 </method>
271 <method name="TerminateUser">
272 <arg type="u" name="uid" direction="in"/>
273 </method>
274 <method name="TerminateSeat">
275 <arg type="s" name="seat_id" direction="in"/>
276 </method>
277 <method name="SetUserLinger">
278 <arg type="u" name="uid" direction="in"/>
279 <arg type="b" name="enable" direction="in"/>
280 <arg type="b" name="interactive" direction="in"/>
281 </method>
282 <method name="AttachDevice">
283 <arg type="s" name="seat_id" direction="in"/>
284 <arg type="s" name="sysfs_path" direction="in"/>
285 <arg type="b" name="interactive" direction="in"/>
286 </method>
287 <method name="FlushDevices">
288 <arg type="b" name="interactive" direction="in"/>
289 </method>
290 <method name="PowerOff">
291 <arg type="b" name="interactive" direction="in"/>
292 </method>
293 <method name="PowerOffWithFlags">
294 <arg type="t" name="flags" direction="in"/>
295 </method>
296 <method name="Reboot">
297 <arg type="b" name="interactive" direction="in"/>
298 </method>
299 <method name="RebootWithFlags">
300 <arg type="t" name="flags" direction="in"/>
301 </method>
302 <method name="Halt">
303 <arg type="b" name="interactive" direction="in"/>
304 </method>
305 <method name="HaltWithFlags">
306 <arg type="t" name="flags" direction="in"/>
307 </method>
308 <method name="Suspend">
309 <arg type="b" name="interactive" direction="in"/>
310 </method>
311 <method name="SuspendWithFlags">
312 <arg type="t" name="flags" direction="in"/>
313 </method>
314 <method name="Hibernate">
315 <arg type="b" name="interactive" direction="in"/>
316 </method>
317 <method name="HibernateWithFlags">
318 <arg type="t" name="flags" direction="in"/>
319 </method>
320 <method name="HybridSleep">
321 <arg type="b" name="interactive" direction="in"/>
322 </method>
323 <method name="HybridSleepWithFlags">
324 <arg type="t" name="flags" direction="in"/>
325 </method>
326 <method name="SuspendThenHibernate">
327 <arg type="b" name="interactive" direction="in"/>
328 </method>
329 <method name="SuspendThenHibernateWithFlags">
330 <arg type="t" name="flags" direction="in"/>
331 </method>
332 <method name="Sleep">
333 <arg type="t" name="flags" direction="in"/>
334 </method>
335 <method name="CanPowerOff">
336 <arg type="s" name="result" direction="out"/>
337 </method>
338 <method name="CanReboot">
339 <arg type="s" name="result" direction="out"/>
340 </method>
341 <method name="CanHalt">
342 <arg type="s" name="result" direction="out"/>
343 </method>
344 <method name="CanSuspend">
345 <arg type="s" name="result" direction="out"/>
346 </method>
347 <method name="CanHibernate">
348 <arg type="s" name="result" direction="out"/>
349 </method>
350 <method name="CanHybridSleep">
351 <arg type="s" name="result" direction="out"/>
352 </method>
353 <method name="CanSuspendThenHibernate">
354 <arg type="s" name="result" direction="out"/>
355 </method>
356 <method name="CanSleep">
357 <arg type="s" name="result" direction="out"/>
358 </method>
359 <method name="ScheduleShutdown">
360 <arg type="s" name="type" direction="in"/>
361 <arg type="t" name="usec" direction="in"/>
362 </method>
363 <method name="CancelScheduledShutdown">
364 <arg type="b" name="cancelled" direction="out"/>
365 </method>
366 <method name="Inhibit">
367 <arg type="s" name="what" direction="in"/>
368 <arg type="s" name="who" direction="in"/>
369 <arg type="s" name="why" direction="in"/>
370 <arg type="s" name="mode" direction="in"/>
371 <arg type="h" name="pipe_fd" direction="out"/>
372 </method>
373 <method name="CanRebootParameter">
374 <arg type="s" name="result" direction="out"/>
375 </method>
376 <method name="SetRebootParameter">
377 <arg type="s" name="parameter" direction="in"/>
378 </method>
379 <method name="CanRebootToFirmwareSetup">
380 <arg type="s" name="result" direction="out"/>
381 </method>
382 <method name="SetRebootToFirmwareSetup">
383 <arg type="b" name="enable" direction="in"/>
384 </method>
385 <method name="CanRebootToBootLoaderMenu">
386 <arg type="s" name="result" direction="out"/>
387 </method>
388 <method name="SetRebootToBootLoaderMenu">
389 <arg type="t" name="timeout" direction="in"/>
390 </method>
391 <method name="CanRebootToBootLoaderEntry">
392 <arg type="s" name="result" direction="out"/>
393 </method>
394 <method name="SetRebootToBootLoaderEntry">
395 <arg type="s" name="boot_loader_entry" direction="in"/>
396 </method>
397 <method name="SetWallMessage">
398 <arg type="s" name="wall_message" direction="in"/>
399 <arg type="b" name="enable" direction="in"/>
400 </method>
401 <signal name="SecureAttentionKey">
402 <arg type="s" name="seat_id"/>
403 <arg type="o" name="object_path"/>
404 </signal>
405 <signal name="SessionNew">
406 <arg type="s" name="session_id"/>
407 <arg type="o" name="object_path"/>
408 </signal>
409 <signal name="SessionRemoved">
410 <arg type="s" name="session_id"/>
411 <arg type="o" name="object_path"/>
412 </signal>
413 <signal name="UserNew">
414 <arg type="u" name="uid"/>
415 <arg type="o" name="object_path"/>
416 </signal>
417 <signal name="UserRemoved">
418 <arg type="u" name="uid"/>
419 <arg type="o" name="object_path"/>
420 </signal>
421 <signal name="SeatNew">
422 <arg type="s" name="seat_id"/>
423 <arg type="o" name="object_path"/>
424 </signal>
425 <signal name="SeatRemoved">
426 <arg type="s" name="seat_id"/>
427 <arg type="o" name="object_path"/>
428 </signal>
429 <signal name="PrepareForShutdown">
430 <arg type="b" name="start"/>
431 </signal>
432 <signal name="PrepareForShutdownWithMetadata">
433 <arg type="b" name="start"/>
434 <arg type="a{sv}" name="metadata"/>
435 <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
436 </signal>
437 <signal name="PrepareForSleep">
438 <arg type="b" name="start"/>
439 </signal>
440 </interface>
441 <node name="user"/>
442 <node name="session"/>
443 <node name="seat"/>
444</node>
445
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml
new file mode 100644
index 00000000..7d6fc8ee
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.login1.Session.xml
@@ -0,0 +1,146 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.login1.Session">
5 <property name="Id" type="s" access="read">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
7 </property>
8 <property name="User" type="o" access="read">
9 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
10 </property>
11 <property name="Name" type="s" access="read">
12 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
13 </property>
14 <property name="Timestamp" type="t" access="read">
15 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
16 </property>
17 <property name="TimestampMonotonic" type="t" access="read">
18 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
19 </property>
20 <property name="VTNr" type="u" access="read">
21 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
22 </property>
23 <property name="Seat" type="o" access="read">
24 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
25 </property>
26 <property name="TTY" type="s" access="read">
27 </property>
28 <property name="Display" type="s" access="read">
29 </property>
30 <property name="Remote" type="b" access="read">
31 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
32 </property>
33 <property name="RemoteHost" type="s" access="read">
34 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
35 </property>
36 <property name="RemoteUser" type="s" access="read">
37 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
38 </property>
39 <property name="Service" type="s" access="read">
40 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
41 </property>
42 <property name="Desktop" type="s" access="read">
43 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
44 </property>
45 <property name="Scope" type="s" access="read">
46 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
47 </property>
48 <property name="Leader" type="u" access="read">
49 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
50 </property>
51 <property name="Audit" type="u" access="read">
52 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
53 </property>
54 <property name="Type" type="s" access="read">
55 </property>
56 <!-- <property name="Class" type="s" access="read"> -->
57 <!-- </property> -->
58 <property name="Active" type="b" access="read">
59 </property>
60 <property name="State" type="s" access="read">
61 </property>
62 <property name="IdleHint" type="b" access="read">
63 </property>
64 <property name="IdleSinceHint" type="t" access="read">
65 </property>
66 <property name="IdleSinceHintMonotonic" type="t" access="read">
67 </property>
68 <property name="CanIdle" type="b" access="read">
69 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
70 </property>
71 <property name="CanLock" type="b" access="read">
72 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
73 </property>
74 <property name="LockedHint" type="b" access="read">
75 </property>
76 <method name="Terminate">
77 </method>
78 <method name="Activate">
79 </method>
80 <!-- <method name="Lock"> -->
81 <!-- </method> -->
82 <!-- <method name="Unlock"> -->
83 <!-- </method> -->
84 <method name="SetIdleHint">
85 <arg type="b" name="idle" direction="in"/>
86 </method>
87 <method name="SetLockedHint">
88 <arg type="b" name="locked" direction="in"/>
89 </method>
90 <method name="Kill">
91 <arg type="s" name="whom" direction="in"/>
92 <arg type="i" name="signal_number" direction="in"/>
93 </method>
94 <method name="TakeControl">
95 <arg type="b" name="force" direction="in"/>
96 </method>
97 <method name="ReleaseControl">
98 </method>
99 <method name="SetType">
100 <arg type="s" name="type" direction="in"/>
101 </method>
102 <!-- <method name="SetClass"> -->
103 <!-- <arg type="s" name="class" direction="in"/> -->
104 <!-- </method> -->
105 <method name="SetDisplay">
106 <arg type="s" name="display" direction="in"/>
107 </method>
108 <method name="SetTTY">
109 <arg type="h" name="tty_fd" direction="in"/>
110 </method>
111 <method name="TakeDevice">
112 <arg type="u" name="major" direction="in"/>
113 <arg type="u" name="minor" direction="in"/>
114 <arg type="h" name="fd" direction="out"/>
115 <arg type="b" name="inactive" direction="out"/>
116 </method>
117 <method name="ReleaseDevice">
118 <arg type="u" name="major" direction="in"/>
119 <arg type="u" name="minor" direction="in"/>
120 </method>
121 <method name="PauseDeviceComplete">
122 <arg type="u" name="major" direction="in"/>
123 <arg type="u" name="minor" direction="in"/>
124 </method>
125 <method name="SetBrightness">
126 <arg type="s" name="subsystem" direction="in"/>
127 <arg type="s" name="name" direction="in"/>
128 <arg type="u" name="brightness" direction="in"/>
129 </method>
130 <signal name="PauseDevice">
131 <arg type="u" name="major"/>
132 <arg type="u" name="minor"/>
133 <arg type="s" name="type"/>
134 </signal>
135 <signal name="ResumeDevice">
136 <arg type="u" name="major"/>
137 <arg type="u" name="minor"/>
138 <arg type="h" name="fd"/>
139 </signal>
140 <signal name="Lock">
141 </signal>
142 <signal name="Unlock">
143 </signal>
144 </interface>
145</node>
146
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml
new file mode 100644
index 00000000..b4f84a13
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml
@@ -0,0 +1,817 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.systemd1.Manager">
5 <property name="Version" type="s" access="read">
6 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
7 </property>
8 <property name="Features" type="s" access="read">
9 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
10 </property>
11 <property name="Virtualization" type="s" access="read">
12 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
13 </property>
14 <property name="ConfidentialVirtualization" type="s" access="read">
15 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
16 </property>
17 <property name="Architecture" type="s" access="read">
18 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
19 </property>
20 <property name="Tainted" type="s" access="read">
21 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
22 </property>
23 <property name="FirmwareTimestamp" type="t" access="read">
24 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
25 </property>
26 <property name="FirmwareTimestampMonotonic" type="t" access="read">
27 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
28 </property>
29 <property name="LoaderTimestamp" type="t" access="read">
30 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
31 </property>
32 <property name="LoaderTimestampMonotonic" type="t" access="read">
33 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
34 </property>
35 <property name="KernelTimestamp" type="t" access="read">
36 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
37 </property>
38 <property name="KernelTimestampMonotonic" type="t" access="read">
39 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
40 </property>
41 <property name="InitRDTimestamp" type="t" access="read">
42 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
43 </property>
44 <property name="InitRDTimestampMonotonic" type="t" access="read">
45 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
46 </property>
47 <property name="UserspaceTimestamp" type="t" access="read">
48 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
49 </property>
50 <property name="UserspaceTimestampMonotonic" type="t" access="read">
51 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
52 </property>
53 <property name="FinishTimestamp" type="t" access="read">
54 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
55 </property>
56 <property name="FinishTimestampMonotonic" type="t" access="read">
57 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
58 </property>
59 <property name="ShutdownStartTimestamp" type="t" access="read">
60 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
61 </property>
62 <property name="ShutdownStartTimestampMonotonic" type="t" access="read">
63 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
64 </property>
65 <property name="SecurityStartTimestamp" type="t" access="read">
66 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
67 </property>
68 <property name="SecurityStartTimestampMonotonic" type="t" access="read">
69 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
70 </property>
71 <property name="SecurityFinishTimestamp" type="t" access="read">
72 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
73 </property>
74 <property name="SecurityFinishTimestampMonotonic" type="t" access="read">
75 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
76 </property>
77 <property name="GeneratorsStartTimestamp" type="t" access="read">
78 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
79 </property>
80 <property name="GeneratorsStartTimestampMonotonic" type="t" access="read">
81 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
82 </property>
83 <property name="GeneratorsFinishTimestamp" type="t" access="read">
84 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
85 </property>
86 <property name="GeneratorsFinishTimestampMonotonic" type="t" access="read">
87 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
88 </property>
89 <property name="UnitsLoadStartTimestamp" type="t" access="read">
90 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
91 </property>
92 <property name="UnitsLoadStartTimestampMonotonic" type="t" access="read">
93 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
94 </property>
95 <property name="UnitsLoadFinishTimestamp" type="t" access="read">
96 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
97 </property>
98 <property name="UnitsLoadFinishTimestampMonotonic" type="t" access="read">
99 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
100 </property>
101 <property name="UnitsLoadTimestamp" type="t" access="read">
102 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
103 </property>
104 <property name="UnitsLoadTimestampMonotonic" type="t" access="read">
105 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
106 </property>
107 <property name="InitRDSecurityStartTimestamp" type="t" access="read">
108 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
109 </property>
110 <property name="InitRDSecurityStartTimestampMonotonic" type="t" access="read">
111 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
112 </property>
113 <property name="InitRDSecurityFinishTimestamp" type="t" access="read">
114 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
115 </property>
116 <property name="InitRDSecurityFinishTimestampMonotonic" type="t" access="read">
117 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
118 </property>
119 <property name="InitRDGeneratorsStartTimestamp" type="t" access="read">
120 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
121 </property>
122 <property name="InitRDGeneratorsStartTimestampMonotonic" type="t" access="read">
123 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
124 </property>
125 <property name="InitRDGeneratorsFinishTimestamp" type="t" access="read">
126 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
127 </property>
128 <property name="InitRDGeneratorsFinishTimestampMonotonic" type="t" access="read">
129 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
130 </property>
131 <property name="InitRDUnitsLoadStartTimestamp" type="t" access="read">
132 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
133 </property>
134 <property name="InitRDUnitsLoadStartTimestampMonotonic" type="t" access="read">
135 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
136 </property>
137 <property name="InitRDUnitsLoadFinishTimestamp" type="t" access="read">
138 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
139 </property>
140 <property name="InitRDUnitsLoadFinishTimestampMonotonic" type="t" access="read">
141 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
142 </property>
143 <property name="LogLevel" type="s" access="readwrite">
144 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
145 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
146 </property>
147 <property name="LogTarget" type="s" access="readwrite">
148 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
149 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
150 </property>
151 <property name="NNames" type="u" access="read">
152 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
153 </property>
154 <property name="NFailedUnits" type="u" access="read">
155 </property>
156 <property name="NJobs" type="u" access="read">
157 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
158 </property>
159 <property name="NInstalledJobs" type="u" access="read">
160 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
161 </property>
162 <property name="NFailedJobs" type="u" access="read">
163 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
164 </property>
165 <property name="Progress" type="d" access="read">
166 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
167 </property>
168 <property name="Environment" type="as" access="read">
169 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
170 </property>
171 <property name="ConfirmSpawn" type="b" access="read">
172 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
173 </property>
174 <property name="ShowStatus" type="b" access="read">
175 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
176 </property>
177 <property name="UnitPath" type="as" access="read">
178 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
179 </property>
180 <property name="DefaultStandardOutput" type="s" access="read">
181 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
182 </property>
183 <property name="DefaultStandardError" type="s" access="read">
184 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
185 </property>
186 <property name="WatchdogDevice" type="s" access="read">
187 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
188 </property>
189 <property name="WatchdogLastPingTimestamp" type="t" access="read">
190 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
191 </property>
192 <property name="WatchdogLastPingTimestampMonotonic" type="t" access="read">
193 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
194 </property>
195 <property name="RuntimeWatchdogUSec" type="t" access="readwrite">
196 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
197 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
198 </property>
199 <property name="RuntimeWatchdogPreUSec" type="t" access="readwrite">
200 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
201 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
202 </property>
203 <property name="RuntimeWatchdogPreGovernor" type="s" access="readwrite">
204 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
205 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
206 </property>
207 <property name="RebootWatchdogUSec" type="t" access="readwrite">
208 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
209 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
210 </property>
211 <property name="KExecWatchdogUSec" type="t" access="readwrite">
212 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
213 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
214 </property>
215 <property name="ServiceWatchdogs" type="b" access="readwrite">
216 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
217 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
218 </property>
219 <property name="ControlGroup" type="s" access="read">
220 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
221 </property>
222 <property name="SystemState" type="s" access="read">
223 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
224 </property>
225 <property name="ExitCode" type="y" access="read">
226 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
227 </property>
228 <property name="DefaultTimerAccuracyUSec" type="t" access="read">
229 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
230 </property>
231 <property name="DefaultTimeoutStartUSec" type="t" access="read">
232 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
233 </property>
234 <property name="DefaultTimeoutStopUSec" type="t" access="read">
235 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
236 </property>
237 <property name="DefaultTimeoutAbortUSec" type="t" access="read">
238 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
239 </property>
240 <property name="DefaultDeviceTimeoutUSec" type="t" access="read">
241 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
242 </property>
243 <property name="DefaultRestartUSec" type="t" access="read">
244 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
245 </property>
246 <property name="DefaultStartLimitIntervalUSec" type="t" access="read">
247 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
248 </property>
249 <property name="DefaultStartLimitBurst" type="u" access="read">
250 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
251 </property>
252 <property name="DefaultCPUAccounting" type="b" access="read">
253 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
254 </property>
255 <property name="DefaultBlockIOAccounting" type="b" access="read">
256 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
257 </property>
258 <property name="DefaultIOAccounting" type="b" access="read">
259 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
260 </property>
261 <property name="DefaultIPAccounting" type="b" access="read">
262 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
263 </property>
264 <property name="DefaultMemoryAccounting" type="b" access="read">
265 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
266 </property>
267 <property name="DefaultTasksAccounting" type="b" access="read">
268 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
269 </property>
270 <property name="DefaultLimitCPU" type="t" access="read">
271 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
272 </property>
273 <property name="DefaultLimitCPUSoft" type="t" access="read">
274 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
275 </property>
276 <property name="DefaultLimitFSIZE" type="t" access="read">
277 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
278 </property>
279 <property name="DefaultLimitFSIZESoft" type="t" access="read">
280 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
281 </property>
282 <property name="DefaultLimitDATA" type="t" access="read">
283 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
284 </property>
285 <property name="DefaultLimitDATASoft" type="t" access="read">
286 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
287 </property>
288 <property name="DefaultLimitSTACK" type="t" access="read">
289 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
290 </property>
291 <property name="DefaultLimitSTACKSoft" type="t" access="read">
292 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
293 </property>
294 <property name="DefaultLimitCORE" type="t" access="read">
295 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
296 </property>
297 <property name="DefaultLimitCORESoft" type="t" access="read">
298 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
299 </property>
300 <property name="DefaultLimitRSS" type="t" access="read">
301 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
302 </property>
303 <property name="DefaultLimitRSSSoft" type="t" access="read">
304 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
305 </property>
306 <property name="DefaultLimitNOFILE" type="t" access="read">
307 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
308 </property>
309 <property name="DefaultLimitNOFILESoft" type="t" access="read">
310 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
311 </property>
312 <property name="DefaultLimitAS" type="t" access="read">
313 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
314 </property>
315 <property name="DefaultLimitASSoft" type="t" access="read">
316 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
317 </property>
318 <property name="DefaultLimitNPROC" type="t" access="read">
319 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
320 </property>
321 <property name="DefaultLimitNPROCSoft" type="t" access="read">
322 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
323 </property>
324 <property name="DefaultLimitMEMLOCK" type="t" access="read">
325 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
326 </property>
327 <property name="DefaultLimitMEMLOCKSoft" type="t" access="read">
328 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
329 </property>
330 <property name="DefaultLimitLOCKS" type="t" access="read">
331 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
332 </property>
333 <property name="DefaultLimitLOCKSSoft" type="t" access="read">
334 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
335 </property>
336 <property name="DefaultLimitSIGPENDING" type="t" access="read">
337 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
338 </property>
339 <property name="DefaultLimitSIGPENDINGSoft" type="t" access="read">
340 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
341 </property>
342 <property name="DefaultLimitMSGQUEUE" type="t" access="read">
343 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
344 </property>
345 <property name="DefaultLimitMSGQUEUESoft" type="t" access="read">
346 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
347 </property>
348 <property name="DefaultLimitNICE" type="t" access="read">
349 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
350 </property>
351 <property name="DefaultLimitNICESoft" type="t" access="read">
352 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
353 </property>
354 <property name="DefaultLimitRTPRIO" type="t" access="read">
355 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
356 </property>
357 <property name="DefaultLimitRTPRIOSoft" type="t" access="read">
358 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
359 </property>
360 <property name="DefaultLimitRTTIME" type="t" access="read">
361 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
362 </property>
363 <property name="DefaultLimitRTTIMESoft" type="t" access="read">
364 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
365 </property>
366 <property name="DefaultTasksMax" type="t" access="read">
367 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
368 </property>
369 <property name="DefaultMemoryPressureThresholdUSec" type="t" access="read">
370 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
371 </property>
372 <property name="DefaultMemoryPressureWatch" type="s" access="read">
373 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
374 </property>
375 <property name="TimerSlackNSec" type="t" access="read">
376 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
377 </property>
378 <property name="DefaultOOMPolicy" type="s" access="read">
379 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
380 </property>
381 <property name="DefaultOOMScoreAdjust" type="i" access="read">
382 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
383 </property>
384 <property name="CtrlAltDelBurstAction" type="s" access="read">
385 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
386 </property>
387 <property name="SoftRebootsCount" type="u" access="read">
388 <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
389 </property>
390 <method name="GetUnit">
391 <arg type="s" name="name" direction="in"/>
392 <arg type="o" name="unit" direction="out"/>
393 </method>
394 <method name="GetUnitByPID">
395 <arg type="u" name="pid" direction="in"/>
396 <arg type="o" name="unit" direction="out"/>
397 </method>
398 <method name="GetUnitByInvocationID">
399 <arg type="ay" name="invocation_id" direction="in"/>
400 <arg type="o" name="unit" direction="out"/>
401 </method>
402 <method name="GetUnitByControlGroup">
403 <arg type="s" name="cgroup" direction="in"/>
404 <arg type="o" name="unit" direction="out"/>
405 </method>
406 <method name="GetUnitByPIDFD">
407 <arg type="h" name="pidfd" direction="in"/>
408 <arg type="o" name="unit" direction="out"/>
409 <arg type="s" name="unit_id" direction="out"/>
410 <arg type="ay" name="invocation_id" direction="out"/>
411 </method>
412 <method name="LoadUnit">
413 <arg type="s" name="name" direction="in"/>
414 <arg type="o" name="unit" direction="out"/>
415 </method>
416 <method name="StartUnit">
417 <arg type="s" name="name" direction="in"/>
418 <arg type="s" name="mode" direction="in"/>
419 <arg type="o" name="job" direction="out"/>
420 </method>
421 <method name="StartUnitWithFlags">
422 <arg type="s" name="name" direction="in"/>
423 <arg type="s" name="mode" direction="in"/>
424 <arg type="t" name="flags" direction="in"/>
425 <arg type="o" name="job" direction="out"/>
426 </method>
427 <method name="StartUnitReplace">
428 <arg type="s" name="old_unit" direction="in"/>
429 <arg type="s" name="new_unit" direction="in"/>
430 <arg type="s" name="mode" direction="in"/>
431 <arg type="o" name="job" direction="out"/>
432 </method>
433 <method name="StopUnit">
434 <arg type="s" name="name" direction="in"/>
435 <arg type="s" name="mode" direction="in"/>
436 <arg type="o" name="job" direction="out"/>
437 </method>
438 <method name="ReloadUnit">
439 <arg type="s" name="name" direction="in"/>
440 <arg type="s" name="mode" direction="in"/>
441 <arg type="o" name="job" direction="out"/>
442 </method>
443 <method name="RestartUnit">
444 <arg type="s" name="name" direction="in"/>
445 <arg type="s" name="mode" direction="in"/>
446 <arg type="o" name="job" direction="out"/>
447 </method>
448 <method name="TryRestartUnit">
449 <arg type="s" name="name" direction="in"/>
450 <arg type="s" name="mode" direction="in"/>
451 <arg type="o" name="job" direction="out"/>
452 </method>
453 <method name="ReloadOrRestartUnit">
454 <arg type="s" name="name" direction="in"/>
455 <arg type="s" name="mode" direction="in"/>
456 <arg type="o" name="job" direction="out"/>
457 </method>
458 <method name="ReloadOrTryRestartUnit">
459 <arg type="s" name="name" direction="in"/>
460 <arg type="s" name="mode" direction="in"/>
461 <arg type="o" name="job" direction="out"/>
462 </method>
463 <!-- <method name="EnqueueUnitJob"> -->
464 <!-- <arg type="s" name="name" direction="in"/> -->
465 <!-- <arg type="s" name="job_type" direction="in"/> -->
466 <!-- <arg type="s" name="job_mode" direction="in"/> -->
467 <!-- <arg type="u" name="job_id" direction="out"/> -->
468 <!-- <arg type="o" name="job_path" direction="out"/> -->
469 <!-- <arg type="s" name="unit_id" direction="out"/> -->
470 <!-- <arg type="o" name="unit_path" direction="out"/> -->
471 <!-- <arg type="s" name="job_type" direction="out"/> -->
472 <!-- <arg type="a(uosos)" name="affected_jobs" direction="out"/> -->
473 <!-- </method> -->
474 <method name="KillUnit">
475 <arg type="s" name="name" direction="in"/>
476 <arg type="s" name="whom" direction="in"/>
477 <arg type="i" name="signal" direction="in"/>
478 </method>
479 <method name="QueueSignalUnit">
480 <arg type="s" name="name" direction="in"/>
481 <arg type="s" name="whom" direction="in"/>
482 <arg type="i" name="signal" direction="in"/>
483 <arg type="i" name="value" direction="in"/>
484 </method>
485 <method name="CleanUnit">
486 <arg type="s" name="name" direction="in"/>
487 <arg type="as" name="mask" direction="in"/>
488 </method>
489 <method name="FreezeUnit">
490 <arg type="s" name="name" direction="in"/>
491 </method>
492 <method name="ThawUnit">
493 <arg type="s" name="name" direction="in"/>
494 </method>
495 <method name="ResetFailedUnit">
496 <arg type="s" name="name" direction="in"/>
497 </method>
498 <!-- <method name="SetUnitProperties"> -->
499 <!-- <arg type="s" name="name" direction="in"/> -->
500 <!-- <arg type="b" name="runtime" direction="in"/> -->
501 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
502 <!-- </method> -->
503 <method name="BindMountUnit">
504 <arg type="s" name="name" direction="in"/>
505 <arg type="s" name="source" direction="in"/>
506 <arg type="s" name="destination" direction="in"/>
507 <arg type="b" name="read_only" direction="in"/>
508 <arg type="b" name="mkdir" direction="in"/>
509 </method>
510 <!-- <method name="MountImageUnit"> -->
511 <!-- <arg type="s" name="name" direction="in"/> -->
512 <!-- <arg type="s" name="source" direction="in"/> -->
513 <!-- <arg type="s" name="destination" direction="in"/> -->
514 <!-- <arg type="b" name="read_only" direction="in"/> -->
515 <!-- <arg type="b" name="mkdir" direction="in"/> -->
516 <!-- <arg type="a(ss)" name="options" direction="in"/> -->
517 <!-- </method> -->
518 <method name="RefUnit">
519 <arg type="s" name="name" direction="in"/>
520 </method>
521 <method name="UnrefUnit">
522 <arg type="s" name="name" direction="in"/>
523 </method>
524 <!-- <method name="StartTransientUnit"> -->
525 <!-- <arg type="s" name="name" direction="in"/> -->
526 <!-- <arg type="s" name="mode" direction="in"/> -->
527 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
528 <!-- <arg type="a(sa(sv))" name="aux" direction="in"/> -->
529 <!-- <arg type="o" name="job" direction="out"/> -->
530 <!-- </method> -->
531 <!-- <method name="GetUnitProcesses"> -->
532 <!-- <arg type="s" name="name" direction="in"/> -->
533 <!-- <arg type="a(sus)" name="processes" direction="out"/> -->
534 <!-- </method> -->
535 <!-- <method name="AttachProcessesToUnit"> -->
536 <!-- <arg type="s" name="unit_name" direction="in"/> -->
537 <!-- <arg type="s" name="subcgroup" direction="in"/> -->
538 <!-- <arg type="au" name="pids" direction="in"/> -->
539 <!-- </method> -->
540 <method name="AbandonScope">
541 <arg type="s" name="name" direction="in"/>
542 </method>
543 <method name="GetJob">
544 <arg type="u" name="id" direction="in"/>
545 <arg type="o" name="job" direction="out"/>
546 </method>
547 <!-- <method name="GetJobAfter"> -->
548 <!-- <arg type="u" name="id" direction="in"/> -->
549 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
550 <!-- </method> -->
551 <!-- <method name="GetJobBefore"> -->
552 <!-- <arg type="u" name="id" direction="in"/> -->
553 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
554 <!-- </method> -->
555 <method name="CancelJob">
556 <arg type="u" name="id" direction="in"/>
557 </method>
558 <method name="ClearJobs">
559 </method>
560 <method name="ResetFailed">
561 </method>
562 <method name="SetShowStatus">
563 <arg type="s" name="mode" direction="in"/>
564 </method>
565 <!-- <method name="ListUnits"> -->
566 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
567 <!-- </method> -->
568 <!-- <method name="ListUnitsFiltered"> -->
569 <!-- <arg type="as" name="states" direction="in"/> -->
570 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
571 <!-- </method> -->
572 <!-- <method name="ListUnitsByPatterns"> -->
573 <!-- <arg type="as" name="states" direction="in"/> -->
574 <!-- <arg type="as" name="patterns" direction="in"/> -->
575 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
576 <!-- </method> -->
577 <!-- <method name="ListUnitsByNames"> -->
578 <!-- <arg type="as" name="names" direction="in"/> -->
579 <!-- <arg type="a(ssssssouso)" name="units" direction="out"/> -->
580 <!-- </method> -->
581 <!-- <method name="ListJobs"> -->
582 <!-- <arg type="a(usssoo)" name="jobs" direction="out"/> -->
583 <!-- </method> -->
584 <method name="Subscribe">
585 </method>
586 <method name="Unsubscribe">
587 </method>
588 <method name="Dump">
589 <arg type="s" name="output" direction="out"/>
590 </method>
591 <method name="DumpUnitsMatchingPatterns">
592 <arg type="as" name="patterns" direction="in"/>
593 <arg type="s" name="output" direction="out"/>
594 </method>
595 <method name="DumpByFileDescriptor">
596 <arg type="h" name="fd" direction="out"/>
597 </method>
598 <method name="DumpUnitsMatchingPatternsByFileDescriptor">
599 <arg type="as" name="patterns" direction="in"/>
600 <arg type="h" name="fd" direction="out"/>
601 </method>
602 <method name="Reload">
603 </method>
604 <method name="Reexecute">
605 <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
606 </method>
607 <method name="Exit">
608 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
609 </method>
610 <method name="Reboot">
611 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
612 </method>
613 <method name="SoftReboot">
614 <arg type="s" name="new_root" direction="in"/>
615 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
616 </method>
617 <method name="PowerOff">
618 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
619 </method>
620 <method name="Halt">
621 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
622 </method>
623 <method name="KExec">
624 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
625 </method>
626 <method name="SwitchRoot">
627 <arg type="s" name="new_root" direction="in"/>
628 <arg type="s" name="init" direction="in"/>
629 <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
630 </method>
631 <method name="SetEnvironment">
632 <arg type="as" name="assignments" direction="in"/>
633 </method>
634 <method name="UnsetEnvironment">
635 <arg type="as" name="names" direction="in"/>
636 </method>
637 <method name="UnsetAndSetEnvironment">
638 <arg type="as" name="names" direction="in"/>
639 <arg type="as" name="assignments" direction="in"/>
640 </method>
641 <method name="EnqueueMarkedJobs">
642 <arg type="ao" name="jobs" direction="out"/>
643 </method>
644 <!-- <method name="ListUnitFiles"> -->
645 <!-- <arg type="a(ss)" name="unit_files" direction="out"/> -->
646 <!-- </method> -->
647 <!-- <method name="ListUnitFilesByPatterns"> -->
648 <!-- <arg type="as" name="states" direction="in"/> -->
649 <!-- <arg type="as" name="patterns" direction="in"/> -->
650 <!-- <arg type="a(ss)" name="unit_files" direction="out"/> -->
651 <!-- </method> -->
652 <method name="GetUnitFileState">
653 <arg type="s" name="file" direction="in"/>
654 <arg type="s" name="state" direction="out"/>
655 </method>
656 <!-- <method name="EnableUnitFiles"> -->
657 <!-- <arg type="as" name="files" direction="in"/> -->
658 <!-- <arg type="b" name="runtime" direction="in"/> -->
659 <!-- <arg type="b" name="force" direction="in"/> -->
660 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
661 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
662 <!-- </method> -->
663 <!-- <method name="DisableUnitFiles"> -->
664 <!-- <arg type="as" name="files" direction="in"/> -->
665 <!-- <arg type="b" name="runtime" direction="in"/> -->
666 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
667 <!-- </method> -->
668 <!-- <method name="EnableUnitFilesWithFlags"> -->
669 <!-- <arg type="as" name="files" direction="in"/> -->
670 <!-- <arg type="t" name="flags" direction="in"/> -->
671 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
672 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
673 <!-- </method> -->
674 <!-- <method name="DisableUnitFilesWithFlags"> -->
675 <!-- <arg type="as" name="files" direction="in"/> -->
676 <!-- <arg type="t" name="flags" direction="in"/> -->
677 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
678 <!-- </method> -->
679 <!-- <method name="DisableUnitFilesWithFlagsAndInstallInfo"> -->
680 <!-- <arg type="as" name="files" direction="in"/> -->
681 <!-- <arg type="t" name="flags" direction="in"/> -->
682 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
683 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
684 <!-- </method> -->
685 <!-- <method name="ReenableUnitFiles"> -->
686 <!-- <arg type="as" name="files" direction="in"/> -->
687 <!-- <arg type="b" name="runtime" direction="in"/> -->
688 <!-- <arg type="b" name="force" direction="in"/> -->
689 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
690 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
691 <!-- </method> -->
692 <!-- <method name="LinkUnitFiles"> -->
693 <!-- <arg type="as" name="files" direction="in"/> -->
694 <!-- <arg type="b" name="runtime" direction="in"/> -->
695 <!-- <arg type="b" name="force" direction="in"/> -->
696 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
697 <!-- </method> -->
698 <!-- <method name="PresetUnitFiles"> -->
699 <!-- <arg type="as" name="files" direction="in"/> -->
700 <!-- <arg type="b" name="runtime" direction="in"/> -->
701 <!-- <arg type="b" name="force" direction="in"/> -->
702 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
703 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
704 <!-- </method> -->
705 <!-- <method name="PresetUnitFilesWithMode"> -->
706 <!-- <arg type="as" name="files" direction="in"/> -->
707 <!-- <arg type="s" name="mode" direction="in"/> -->
708 <!-- <arg type="b" name="runtime" direction="in"/> -->
709 <!-- <arg type="b" name="force" direction="in"/> -->
710 <!-- <arg type="b" name="carries_install_info" direction="out"/> -->
711 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
712 <!-- </method> -->
713 <!-- <method name="MaskUnitFiles"> -->
714 <!-- <arg type="as" name="files" direction="in"/> -->
715 <!-- <arg type="b" name="runtime" direction="in"/> -->
716 <!-- <arg type="b" name="force" direction="in"/> -->
717 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
718 <!-- </method> -->
719 <!-- <method name="UnmaskUnitFiles"> -->
720 <!-- <arg type="as" name="files" direction="in"/> -->
721 <!-- <arg type="b" name="runtime" direction="in"/> -->
722 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
723 <!-- </method> -->
724 <!-- <method name="RevertUnitFiles"> -->
725 <!-- <arg type="as" name="files" direction="in"/> -->
726 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
727 <!-- </method> -->
728 <!-- <method name="SetDefaultTarget"> -->
729 <!-- <arg type="s" name="name" direction="in"/> -->
730 <!-- <arg type="b" name="force" direction="in"/> -->
731 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
732 <!-- </method> -->
733 <method name="GetDefaultTarget">
734 <arg type="s" name="name" direction="out"/>
735 </method>
736 <!-- <method name="PresetAllUnitFiles"> -->
737 <!-- <arg type="s" name="mode" direction="in"/> -->
738 <!-- <arg type="b" name="runtime" direction="in"/> -->
739 <!-- <arg type="b" name="force" direction="in"/> -->
740 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
741 <!-- </method> -->
742 <!-- <method name="AddDependencyUnitFiles"> -->
743 <!-- <arg type="as" name="files" direction="in"/> -->
744 <!-- <arg type="s" name="target" direction="in"/> -->
745 <!-- <arg type="s" name="type" direction="in"/> -->
746 <!-- <arg type="b" name="runtime" direction="in"/> -->
747 <!-- <arg type="b" name="force" direction="in"/> -->
748 <!-- <arg type="a(sss)" name="changes" direction="out"/> -->
749 <!-- </method> -->
750 <method name="GetUnitFileLinks">
751 <arg type="s" name="name" direction="in"/>
752 <arg type="b" name="runtime" direction="in"/>
753 <arg type="as" name="links" direction="out"/>
754 </method>
755 <method name="SetExitCode">
756 <arg type="y" name="number" direction="in"/>
757 </method>
758 <method name="LookupDynamicUserByName">
759 <arg type="s" name="name" direction="in"/>
760 <arg type="u" name="uid" direction="out"/>
761 </method>
762 <method name="LookupDynamicUserByUID">
763 <arg type="u" name="uid" direction="in"/>
764 <arg type="s" name="name" direction="out"/>
765 </method>
766 <!-- <method name="GetDynamicUsers"> -->
767 <!-- <arg type="a(us)" name="users" direction="out"/> -->
768 <!-- </method> -->
769 <!-- <method name="DumpUnitFileDescriptorStore"> -->
770 <!-- <arg type="s" name="name" direction="in"/> -->
771 <!-- <arg type="a(suuutuusu)" name="entries" direction="out"/> -->
772 <!-- </method> -->
773 <!-- <method name="StartAuxiliaryScope"> -->
774 <!-- <arg type="s" name="name" direction="in"/> -->
775 <!-- <arg type="ah" name="pidfds" direction="in"/> -->
776 <!-- <arg type="t" name="flags" direction="in"/> -->
777 <!-- <arg type="a(sv)" name="properties" direction="in"/> -->
778 <!-- <arg type="o" name="job" direction="out"/> -->
779 <!-- <annotation name="org.freedesktop.DBus.Deprecated" value="true"/> -->
780 <!-- </method> -->
781 <signal name="UnitNew">
782 <arg type="s" name="id"/>
783 <arg type="o" name="unit"/>
784 </signal>
785 <signal name="UnitRemoved">
786 <arg type="s" name="id"/>
787 <arg type="o" name="unit"/>
788 </signal>
789 <signal name="JobNew">
790 <arg type="u" name="id"/>
791 <arg type="o" name="job"/>
792 <arg type="s" name="unit"/>
793 </signal>
794 <signal name="JobRemoved">
795 <arg type="u" name="id"/>
796 <arg type="o" name="job"/>
797 <arg type="s" name="unit"/>
798 <arg type="s" name="result"/>
799 </signal>
800 <signal name="StartupFinished">
801 <arg type="t" name="firmware"/>
802 <arg type="t" name="loader"/>
803 <arg type="t" name="kernel"/>
804 <arg type="t" name="initrd"/>
805 <arg type="t" name="userspace"/>
806 <arg type="t" name="total"/>
807 </signal>
808 <signal name="UnitFilesChanged">
809 </signal>
810 <signal name="Reloading">
811 <arg type="b" name="active"/>
812 </signal>
813 </interface>
814 <node name="unit"/>
815 <node name="job"/>
816</node>
817
diff --git a/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
new file mode 100644
index 00000000..dcc23279
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
@@ -0,0 +1,172 @@
1import QtQuick
2import qs.Services
3import Quickshell
4import Quickshell.Widgets
5
6Item {
7 id: activeWindowDisplay
8
9 required property int maxWidth
10 required property var screen
11
12 property var activeWindow: {
13 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
14 return ws.output === screen.name && ws.is_active;
15 })?.active_window_id;
16
17 return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null;
18 }
19 property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null
20
21 anchors.verticalCenter: parent.verticalCenter
22 width: activeWindowDisplayContent.width
23 height: parent.height
24
25 WrapperMouseArea {
26 id: widgetMouseArea
27
28 anchors.fill: parent
29
30 hoverEnabled: true
31
32 Item {
33 anchors.fill: parent
34
35 Row {
36 id: activeWindowDisplayContent
37
38 width: childrenRect.width
39 height: parent.height
40 anchors.verticalCenter: parent.verticalCenter
41 spacing: 8
42
43 IconImage {
44 id: activeWindowIcon
45
46 implicitSize: 14
47
48 anchors.verticalCenter: parent.verticalCenter
49
50 source: {
51 let icon = activeWindowDisplay.windowEntry?.icon
52 if (typeof icon === 'string' || icon instanceof String) {
53 if (icon.includes("?path=")) {
54 const split = icon.split("?path=")
55 if (split.length !== 2)
56 return icon
57 const name = split[0]
58 const path = split[1]
59 const fileName = name.substring(
60 name.lastIndexOf("/") + 1)
61 return `file://${path}/${fileName}`
62 } else
63 icon = Quickshell.iconPath(icon);
64 return icon
65 }
66 return ""
67 }
68 asynchronous: true
69 smooth: true
70 mipmap: true
71 }
72
73 Text {
74 id: windowTitle
75
76 width: Math.min(implicitWidth, activeWindowDisplay.maxWidth - activeWindowIcon.width - activeWindowDisplayContent.spacing)
77
78 property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" }
79
80 elide: Text.ElideRight
81 maximumLineCount: 1
82 color: "white"
83 anchors.verticalCenter: parent.verticalCenter
84 text: {
85 if (!activeWindowDisplay.activeWindow)
86 return "";
87
88 var title = activeWindowDisplay.activeWindow.title;
89 var appName = activeWindowDisplay.windowEntry?.name;
90 if (appAliases[appName])
91 appName = appAliases[appName];
92 if (appName && title.endsWith(appName)) {
93 const oldTitle = title;
94 title = title.substring(0, title.length - appName.length);
95 title = title.replace(/\s*(—|-)\s*$/, "");
96 if (!title)
97 title = oldTitle;
98 }
99 return title;
100 }
101 }
102 }
103 }
104 }
105
106 Loader {
107 id: tooltipLoader
108
109 active: false
110
111 Connections {
112 target: widgetMouseArea
113 function onContainsMouseChanged() {
114 if (widgetMouseArea.containsMouse)
115 tooltipLoader.active = true;
116 }
117 }
118
119 PopupWindow {
120 id: tooltip
121
122 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
123
124 anchor {
125 item: widgetMouseArea
126 edges: Edges.Bottom | Edges.Left
127 }
128 visible: false
129
130 onNextVisibleChanged: hangTimer.restart()
131
132 Timer {
133 id: hangTimer
134 interval: tooltip.visible ? 100 : 500
135 onTriggered: {
136 tooltip.visible = tooltip.nextVisible;
137 if (!tooltip.visible)
138 tooltipLoader.active = false;
139 }
140 }
141
142 implicitWidth: widgetTooltipText.contentWidth + 16
143 implicitHeight: widgetTooltipText.contentHeight + 16
144 color: "black"
145
146 WrapperMouseArea {
147 id: tooltipMouseArea
148
149 hoverEnabled: true
150 enabled: true
151
152 anchors.fill: parent
153
154 Item {
155 anchors.fill: parent
156
157 Text {
158 id: widgetTooltipText
159
160 anchors.centerIn: parent
161
162 font.pointSize: 10
163 font.family: "Fira Mono"
164 color: "white"
165
166 text: JSON.stringify(Object.assign({}, activeWindowDisplay.activeWindow), null, 2)
167 }
168 }
169 }
170 }
171 }
172}
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml
new file mode 100644
index 00000000..9210066c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -0,0 +1,117 @@
1import Quickshell
2import Quickshell.Wayland
3import QtQuick
4
5PanelWindow {
6 id: bar
7
8 required property var modelData
9 screen: modelData
10
11 WlrLayershell.namespace: "bar"
12
13 anchors {
14 top: true
15 left: true
16 right: true
17 }
18 margins {
19 left: 26 + 8
20 right: 26 + 8
21 }
22
23 implicitHeight: 21
24 color: "transparent"
25
26 Rectangle {
27 color: Qt.rgba(0, 0, 0, 0.75)
28 anchors.fill: parent
29 // bottomLeftRadius: 8
30 // bottomRightRadius: 8
31 }
32
33 Row {
34 id: left
35
36 height: parent.height
37 width: childrenRect.width
38 anchors.left: parent.left
39 anchors.leftMargin: 8
40 anchors.verticalCenter: parent.verticalCenter
41 spacing: 8
42
43 WorkspaceSwitcher {
44 screen: bar.screen
45 }
46 }
47
48 Row {
49 id: center
50
51 height: parent.height
52 width: childrenRect.width
53 anchors.centerIn: parent
54 spacing: 5
55
56 ActiveWindowDisplay {
57 screen: bar.screen
58 maxWidth: bar.width - 2*Math.max(left.width, right.width) - 2*8
59 }
60 }
61
62 Row {
63 id: right
64
65 height: parent.height
66 width: childrenRect.width
67 anchors.right: parent.right
68 anchors.rightMargin: 8
69 anchors.verticalCenter: parent.verticalCenter
70 spacing: 0
71
72 // WorktimeWidget { command: "time"; }
73
74 // WorktimeWidget { command: "today"; }
75
76 KeyboardLayout {}
77
78 Item {
79 visible: privacy.visible
80 height: parent.height
81 width: 8 - 4
82 }
83
84 PrivacyWidget {
85 id: privacy
86 }
87
88 Item {
89 visible: privacy.visible
90 height: parent.height
91 width: 8 - 4
92 }
93
94 SystemTray {}
95
96 PipewireWidget {}
97
98 BrightnessWidget {}
99
100 BatteryWidget {}
101
102 WaylandInhibitorWidget {
103 window: bar
104 }
105
106 NotificationInhibitorWidget {}
107
108 LidSwitchInhibitorWidget {}
109
110 Item {
111 height: parent.height
112 width: 8 - 4
113 }
114
115 Clock {}
116 }
117} \ No newline at end of file
diff --git a/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml
new file mode 100644
index 00000000..da17df2a
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml
@@ -0,0 +1,136 @@
1import QtQuick
2import Quickshell
3import Quickshell.Widgets
4import Quickshell.Services.UPower
5
6Item {
7 id: root
8
9 height: parent.height
10 width: batteryIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 property var batteryDevice: Array.from(UPower.devices.values).find(dev => dev.isLaptopBattery)
14
15 WrapperMouseArea {
16 id: widgetMouseArea
17
18 anchors.fill: parent
19
20 hoverEnabled: true
21
22 Item {
23 anchors.fill: parent
24
25 MaterialDesignIcon {
26 id: batteryIcon
27
28 implicitSize: 14
29 anchors.centerIn: parent
30
31 icon: {
32 if (!root.batteryDevice?.ready)
33 return "battery-unknown";
34
35 if (root.batteryDevice.state == UPowerDeviceState.FullyCharged)
36 return "power-plug-battery";
37
38 const perdec = 10 * Math.max(1, Math.ceil(root.batteryDevice.percentage * 10));
39 if (root.batteryDevice.state == UPowerDeviceState.Charging)
40 return `battery-charging-${perdec}`;
41 if (perdec == 100)
42 return "battery";
43 return `battery-${perdec}`;
44 }
45 color: {
46 if (!root.batteryDevice?.ready)
47 return "#555";
48
49 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 20 * 60)
50 return "#f2201f";
51 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 40 * 60)
52 return "#f28a21";
53 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged)
54 return "#fff";
55 return "#555";
56 }
57 }
58 }
59 }
60
61 PopupWindow {
62 id: tooltip
63
64 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
65
66 anchor {
67 item: widgetMouseArea
68 edges: Edges.Bottom | Edges.Left
69 }
70 visible: false
71
72 onNextVisibleChanged: hangTimer.restart()
73
74 Timer {
75 id: hangTimer
76 interval: 100
77 onTriggered: tooltip.visible = tooltip.nextVisible
78 }
79
80 implicitWidth: widgetTooltipText.contentWidth + 16
81 implicitHeight: widgetTooltipText.contentHeight + 16
82 color: "black"
83
84 WrapperMouseArea {
85 id: tooltipMouseArea
86
87 hoverEnabled: true
88 enabled: true
89
90 anchors.fill: parent
91
92 Item {
93 anchors.fill: parent
94
95 Text {
96 id: widgetTooltipText
97
98 anchors.centerIn: parent
99
100 font.pointSize: 10
101 font.family: "Fira Sans"
102 color: "white"
103
104 text: {
105 const stateStr = UPowerDeviceState.toString(root.batteryDevice.state);
106 var outStr = stateStr;
107 if (root.batteryDevice.state != UPowerDeviceState.FullyCharged)
108 outStr += ` ${Math.round(root.batteryDevice.percentage * 100)}%`;
109
110 function formatTime(t) {
111 var res = "";
112 for (const unit of [{ "s": "h", "v": 3600 }, { "s": "m", "v": 60 }, { "s": "s", "v": 1 }]) {
113 if (t < unit.v)
114 continue;
115 res += Math.floor(t / unit.v) + unit.s;
116 t %= unit.v;
117 }
118 return res;
119 }
120 if (root.batteryDevice.timeToEmpty != 0) {
121 const tStr = formatTime(Math.floor(root.batteryDevice.timeToEmpty / 60) * 60);
122 if (tStr)
123 outStr += " " + tStr;
124 } else if (root.batteryDevice.timeToFull != 0) {
125 const tStr = formatTime(Math.ceil(root.batteryDevice.timeToFull / 60) * 60);
126 if (tStr)
127 outStr += " " + tStr;
128 }
129
130 return outStr;
131 }
132 }
133 }
134 }
135 }
136}
diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml
new file mode 100644
index 00000000..a432179e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml
@@ -0,0 +1,117 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Widgets
5import qs.Services
6
7Scope {
8 id: root
9
10 property bool show: false
11 property bool inhibited: true
12
13 Connections {
14 target: Brightness
15
16 function onCurrBrightnessChanged() {
17 root.show = true;
18 hideTimer.restart();
19 }
20 }
21
22 onShowChanged: {
23 if (show)
24 hideTimer.restart();
25 }
26
27 Timer {
28 id: hideTimer
29 interval: 1000
30 onTriggered: root.show = false
31 }
32
33 Timer {
34 id: startInhibit
35 interval: 100
36 running: true
37 onTriggered: {
38 root.show = false;
39 root.inhibited = false;
40 }
41 }
42
43 LazyLoader {
44 active: root.show && !root.inhibited
45
46 Variants {
47 model: Quickshell.screens
48
49 delegate: Scope {
50 id: screenScope
51
52 required property var modelData
53
54 PanelWindow {
55 id: window
56
57 screen: screenScope.modelData
58
59 anchors.top: true
60 margins.top: screen.height / 2 - 50 + 3.5
61 exclusiveZone: 0
62 exclusionMode: ExclusionMode.Ignore
63
64 implicitWidth: 400
65 implicitHeight: 50
66
67 mask: Region {}
68
69 color: "transparent"
70
71 Rectangle {
72 anchors.fill: parent
73 color: Qt.rgba(0, 0, 0, 0.75)
74 }
75
76 RowLayout {
77 id: layout
78
79 anchors.centerIn: parent
80
81 height: 50 - 8*2
82 width: 400 - 8*2
83
84 MaterialDesignIcon {
85 id: volumeIcon
86
87 implicitWidth: parent.height
88 implicitHeight: parent.height
89
90 icon: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}`
91 }
92
93 Rectangle {
94 Layout.fillWidth: true
95
96 implicitHeight: 10
97
98 color: "#50ffffff"
99
100 Rectangle {
101 anchors {
102 left: parent.left
103 top: parent.top
104 bottom: parent.bottom
105 }
106
107 color: "white"
108
109 implicitWidth: parent.width * Brightness.currBrightness
110 }
111 }
112 }
113 }
114 }
115 }
116 }
117}
diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml
new file mode 100644
index 00000000..3bb5a80e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml
@@ -0,0 +1,84 @@
1import QtQuick
2import Quickshell
3import Quickshell.Widgets
4import qs.Services
5
6Item {
7 height: parent.height
8 width: brightnessIcon.width + 8
9 anchors.verticalCenter: parent.verticalCenter
10
11 WrapperMouseArea {
12 id: widgetMouseArea
13
14 anchors.fill: parent
15
16 hoverEnabled: true
17
18 property real sensitivity: (1 / 50) / 120
19 onWheel: event => Brightness.currBrightness += event.angleDelta.y * sensitivity
20
21 Item {
22 anchors.fill: parent
23
24 MaterialDesignIcon {
25 id: brightnessIcon
26
27 implicitSize: 14
28 anchors.centerIn: parent
29
30 icon: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}`
31 color: "#555"
32 }
33 }
34 }
35
36 PopupWindow {
37 id: tooltip
38
39 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse
40
41 anchor {
42 item: widgetMouseArea
43 edges: Edges.Bottom | Edges.Left
44 }
45 visible: false
46
47 onNextVisibleChanged: hangTimer.restart()
48
49 Timer {
50 id: hangTimer
51 interval: 100
52 onTriggered: tooltip.visible = tooltip.nextVisible
53 }
54
55 implicitWidth: widgetTooltipText.contentWidth + 16
56 implicitHeight: widgetTooltipText.contentHeight + 16
57 color: "black"
58
59 WrapperMouseArea {
60 id: tooltipMouseArea
61
62 hoverEnabled: true
63 enabled: true
64
65 anchors.fill: parent
66
67 Item {
68 anchors.fill: parent
69
70 Text {
71 id: widgetTooltipText
72
73 anchors.centerIn: parent
74
75 font.pointSize: 10
76 font.family: "Fira Sans"
77 color: "white"
78
79 text: `${Math.round(Brightness.currBrightness * 100)}%`
80 }
81 }
82 }
83 }
84}
diff --git a/accounts/gkleen@sif/shell/quickshell/Clock.qml b/accounts/gkleen@sif/shell/quickshell/Clock.qml
new file mode 100644
index 00000000..b7004528
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Clock.qml
@@ -0,0 +1,295 @@
1import QtQml
2import QtQuick
3import Quickshell
4import Custom as Custom
5import QtQuick.Controls
6import QtQuick.Layouts
7import Quickshell.Widgets
8
9Item {
10 id: clockItem
11
12 property bool calendarPopup: true
13
14 width: clock.contentWidth
15 height: parent.height
16 anchors.verticalCenter: parent.verticalCenter
17
18 WrapperMouseArea {
19 id: clockMouseArea
20
21 anchors.fill: parent
22 hoverEnabled: true
23 enabled: clockItem.calendarPopup
24
25 Item {
26 anchors.fill: parent
27
28 Text {
29 id: clock
30 color: "white"
31
32 anchors.verticalCenter: parent.verticalCenter
33
34 Custom.Chrono {
35 id: chrono
36
37 onDateChanged: clock.text = format("W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}")
38 }
39
40 font.pointSize: 10
41 font.family: "Fira Sans"
42 font.features: { "tnum": 1 }
43 }
44 }
45 }
46
47 Loader {
48 id: tooltipLoader
49
50 active: false
51
52 Connections {
53 target: clockMouseArea
54 function onContainsMouseChanged() {
55 if (clockMouseArea.containsMouse)
56 tooltipLoader.active = true;
57 }
58 }
59
60 sourceComponent: PopupWindow {
61 id: tooltip
62
63 property bool nextVisible: clockMouseArea.containsMouse || tooltipMouseArea.containsMouse
64
65 anchor {
66 item: clockMouseArea
67 edges: Edges.Bottom | Edges.Left
68 }
69 visible: false
70
71 onNextVisibleChanged: hangTimer.restart()
72
73 Timer {
74 id: hangTimer
75 interval: 100
76 onTriggered: {
77 tooltip.visible = tooltip.nextVisible;
78 if (!tooltip.visible)
79 tooltipLoader.active = false;
80 }
81 }
82
83 implicitWidth: tooltipLayout.childrenRect.width + 16
84 implicitHeight: tooltipLayout.childrenRect.height + 16
85 color: "black"
86
87 onVisibleChanged: {
88 yearCalendar.year = chrono.date.getFullYear();
89 yearCalendar.angleRem = 0;
90 }
91
92 WrapperMouseArea {
93 id: tooltipMouseArea
94
95 hoverEnabled: true
96 enabled: true
97
98 onWheel: event => yearCalendar.scrollYear(event)
99
100 anchors.fill: parent
101
102 Item {
103 id: clockTooltipContent
104
105 anchors.fill: parent
106
107 ColumnLayout {
108 id: tooltipLayout
109
110 anchors {
111 left: parent.left
112 top: parent.top
113 leftMargin: 8
114 topMargin: 8
115 }
116
117 Text {
118 id: yearLabel
119
120 horizontalAlignment: Text.AlignHCenter
121
122 font.pointSize: 14
123 font.family: "Fira Sans"
124 font.features: { "tnum": 1 }
125 color: "white"
126
127 text: yearCalendar.year
128
129 Layout.fillWidth: true
130 Layout.bottomMargin: 8
131 }
132
133 GridLayout {
134 property int year: chrono.date.getFullYear()
135
136 id: yearCalendar
137
138 columns: 3
139 columnSpacing: 16
140 rowSpacing: 16
141
142 Layout.alignment: Qt.AlignHCenter
143 Layout.fillWidth: false
144
145 property real angleRem: 0
146 property real sensitivity: 1 / 120
147
148 function scrollYear(event) {
149 angleRem += event.angleDelta.y;
150 const d = Math.round(angleRem * sensitivity);
151 yearCalendar.year += d;
152 angleRem -= d / sensitivity;
153 }
154
155 Connections {
156 target: clockMouseArea
157 function onWheel(event) { yearCalendar.scrollYear(event); }
158 }
159
160 Repeater {
161 model: 12
162
163 GridLayout {
164 columns: 2
165
166 required property int index
167 property int month: index
168
169 id: monthCalendar
170
171 Layout.alignment: Qt.AlignTop | Qt.AlignRight
172 Layout.fillWidth: false
173
174 Text {
175 Layout.column: 1
176 Layout.fillWidth: true
177
178 horizontalAlignment: Text.AlignHCenter
179
180 font.pointSize: 10
181 font.family: "Fira Sans"
182
183 text: new Date(yearCalendar.year, monthCalendar.month, 1).toLocaleString(Qt.locale("en_DK"), "MMMM")
184
185 color: "#ffead3"
186 }
187
188 DayOfWeekRow {
189 locale: grid.locale
190
191 Layout.row: 1
192 Layout.column: 1
193 Layout.fillWidth: true
194
195 delegate: WrapperItem {
196 required property string shortName
197
198 width: dowLabel.contentWidth + 6
199
200 Text {
201 id: dowLabel
202
203 anchors.fill: parent
204
205 font.pointSize: 10
206 font.family: "Fira Sans"
207
208 text: parent.shortName
209 color: "#ffcc66"
210
211 horizontalAlignment: Text.AlignHCenter
212 verticalAlignment: Text.AlignVCenter
213 }
214 }
215 }
216
217 WeekNumberColumn {
218 month: grid.month
219 year: grid.year
220 locale: grid.locale
221
222 Layout.fillHeight: true
223
224 delegate: Text {
225 required property int weekNumber
226
227 opacity: {
228 const simple = new Date(weekNumber == 1 && monthCalendar.month == 12 ? yearCalendar.year + 1 : yearCalendar.year, 0, 1 + (weekNumber - 1) * 7);
229 const dayOfWeek = simple.getDay();
230 const isoWeekStart = simple;
231
232 isoWeekStart.setDate(simple.getDate() - dayOfWeek + 1);
233 if (dayOfWeek > 4) {
234 isoWeekStart.setDate(isoWeekStart.getDate() + 7);
235 }
236
237 for (let i = 0; i < 7; i++) {
238 const dayInWeek = new Date(isoWeekStart);
239 dayInWeek.setDate(dayInWeek.getDate() + i);
240 if (dayInWeek.getMonth() == monthCalendar.month)
241 return 1;
242 }
243
244 return 0;
245 }
246
247 font.pointSize: 10
248 font.family: "Fira Sans"
249 font.features: { "tnum": 1 }
250
251 text: weekNumber
252 color: "#99ffdd"
253
254 horizontalAlignment: Text.AlignRight
255 verticalAlignment: Text.AlignVCenter
256 }
257 }
258
259 MonthGrid {
260 id: grid
261
262 year: yearCalendar.year
263 month: monthCalendar.month
264 locale: Qt.locale("en_DK")
265
266 Layout.fillWidth: true
267 Layout.fillHeight: true
268
269 delegate: Text {
270 required property var model
271
272 opacity: model.month === monthCalendar.month ? 1 : 0
273
274 font.pointSize: 10
275 font.family: "Fira Sans"
276 font.features: { "tnum": 1 }
277
278 property bool today: chrono.date.getFullYear() == model.year && chrono.date.getMonth() == model.month && chrono.date.getDate() == model.day
279
280 text: model.day
281 color: today ? "#ff6699" : "white"
282
283 horizontalAlignment: Text.AlignRight
284 verticalAlignment: Text.AlignVCenter
285 }
286 }
287 }
288 }
289 }
290 }
291 }
292 }
293 }
294 }
295}
diff --git a/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
new file mode 100644
index 00000000..46302e54
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
@@ -0,0 +1,112 @@
1import Quickshell
2import QtQuick
3import qs.Services
4import Quickshell.Widgets
5
6Item {
7 width: kbdLabel.contentWidth + 8
8 height: parent.height
9 anchors.verticalCenter: parent.verticalCenter
10
11 WrapperMouseArea {
12 id: kbdMouseArea
13
14 anchors.fill: parent
15
16 hoverEnabled: true
17 cursorShape: Qt.PointingHandCursor
18 enabled: true
19 onClicked: {
20 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {})
21 }
22 onWheel: event => {
23 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": event.angleDelta > 0 ? "Next" : "Prev" } } }, _ => {})
24 }
25
26 Rectangle {
27 id: kbdWidget
28
29 property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" }
30
31 anchors.fill: parent
32 color: {
33 if (kbdMouseArea.containsMouse) {
34 return "#33808080";
35 }
36 return "transparent";
37 }
38
39 Text {
40 id: kbdLabel
41
42 font.pointSize: 10
43 font.family: "Fira Sans"
44 color: {
45 if (NiriService.keyboardLayouts?.current_idx === 0)
46 return "#555";
47 return "white";
48 }
49 anchors.centerIn: parent
50
51 text: {
52 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
53 if (!currentLayout)
54 return "";
55 return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout;
56 }
57 }
58 }
59 }
60
61 PopupWindow {
62 id: tooltip
63
64 property bool nextVisible: kbdMouseArea.containsMouse || tooltipMouseArea.containsMouse
65
66 anchor {
67 item: kbdMouseArea
68 edges: Edges.Bottom | Edges.Left
69 }
70 visible: false
71
72 onNextVisibleChanged: hangTimer.restart()
73
74 Timer {
75 id: hangTimer
76 interval: 100
77 onTriggered: tooltip.visible = tooltip.nextVisible
78 }
79
80 implicitWidth: kbdTooltipText.contentWidth + 16
81 implicitHeight: kbdTooltipText.contentHeight + 16
82 color: "black"
83
84 WrapperMouseArea {
85 id: tooltipMouseArea
86
87 hoverEnabled: true
88 enabled: true
89
90 anchors.fill: parent
91
92 Item {
93 anchors.fill: parent
94
95 Text {
96 id: kbdTooltipText
97
98 anchors.centerIn: parent
99
100 font.pointSize: 10
101 font.family: "Fira Sans"
102 color: "white"
103
104 text: {
105 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
106 return currentLayout || "";
107 }
108 }
109 }
110 }
111 }
112}
diff --git a/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml
new file mode 100644
index 00000000..8410dcda
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/LidSwitchInhibitorWidget.qml
@@ -0,0 +1,47 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import qs.Services
5
6Item {
7 id: root
8
9 width: icon.width + 8
10 height: parent.height
11 anchors.verticalCenter: parent.verticalCenter
12
13 WrapperMouseArea {
14 id: widgetMouseArea
15
16 anchors.fill: parent
17
18 hoverEnabled: true
19 cursorShape: Qt.PointingHandCursor
20
21 onClicked: InhibitorState.lidSwitchInhibited = !InhibitorState.lidSwitchInhibited
22
23 Rectangle {
24 anchors.fill: parent
25 color: {
26 if (widgetMouseArea.containsMouse) {
27 return "#33808080";
28 }
29 return "transparent";
30 }
31
32 Item {
33 anchors.fill: parent
34
35 MaterialDesignIcon {
36 id: icon
37
38 implicitSize: 14
39 anchors.centerIn: parent
40
41 icon: InhibitorState.lidSwitchInhibited ? "laptop-off" : "laptop"
42 color: InhibitorState.lidSwitchInhibited ? "#f28a21" : "#555"
43 }
44 }
45 }
46 }
47}
diff --git a/accounts/gkleen@sif/shell/quickshell/LockSurface.qml b/accounts/gkleen@sif/shell/quickshell/LockSurface.qml
new file mode 100644
index 00000000..f4f8f0cd
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/LockSurface.qml
@@ -0,0 +1,227 @@
1import Quickshell.Widgets
2import QtQuick.Effects
3import QtQuick.Layouts
4import QtQuick
5import QtQuick.Controls
6import QtQuick.Controls.Fusion
7import qs.Services
8import QtQml
9
10Item {
11 id: lockSurface
12
13 property var screen
14 property list<var> messages: []
15 property bool responseRequired: false
16 property bool responseVisible: false
17 property string currentText: ""
18 property bool authRunning: false
19
20 signal response(string responseText)
21
22 anchors.fill: parent
23
24 Item {
25 id: background
26
27 anchors.fill: parent
28
29 property Img current: one
30 property string source: selector.selected
31
32 WallpaperSelector {
33 id: selector
34 seed: lockSurface.screen?.name || ""
35 }
36
37 onSourceChanged: {
38 if (!source)
39 current = null;
40 else if (current === one)
41 two.update()
42 else
43 one.update()
44 }
45
46 Img { id: one }
47 Img { id: two }
48
49 component Img: Item {
50 id: img
51
52 property string source
53
54 function update() {
55 source = background.source || ""
56 }
57
58 anchors.fill: parent
59
60 Image {
61 id: imageSource
62
63 source: img.source
64 sourceSize: Qt.size(parent.width, parent.height)
65 fillMode: Image.PreserveAspectCrop
66 smooth: true
67 visible: false
68 asynchronous: true
69 cache: false
70
71 onStatusChanged: {
72 if (status === Image.Ready) {
73 background.current = img
74 }
75 }
76 }
77
78 MultiEffect {
79 id: imageEffect
80
81 source: imageSource
82 anchors.fill: parent
83 blurEnabled: true
84 blur: 1
85 blurMax: 64
86 blurMultiplier: 2
87
88 opacity: 0
89
90 states: State {
91 name: "visible"
92 when: background.current === img
93
94 PropertyChanges {
95 imageEffect.opacity: 1
96 }
97 StateChangeScript {
98 name: "unloadOther"
99 script: {
100 if (img === one)
101 two.source = ""
102 if (img === two)
103 one.source = ""
104 }
105 }
106 }
107
108 transitions: Transition {
109 SequentialAnimation {
110 NumberAnimation {
111 target: imageEffect
112 properties: "opacity"
113 duration: {
114 if (img === one && two.source == "" || img === two && one.source == "")
115 return 0;
116 return 5000;
117 }
118 easing.type: Easing.OutCubic
119 }
120 ScriptAction {
121 scriptName: "unloadOther"
122 }
123 }
124 }
125 }
126 }
127 }
128
129 Item {
130 anchors {
131 top: lockSurface.top
132 left: lockSurface.left
133 right: lockSurface.right
134 }
135
136 implicitWidth: lockSurface.width
137 implicitHeight: 21
138
139 Rectangle {
140 anchors.fill: parent
141 color: Qt.rgba(0, 0, 0, 0.75)
142 }
143
144 Clock {
145 anchors.centerIn: parent
146 calendarPopup: false
147 }
148 }
149
150 WrapperRectangle {
151 id: unlockUi
152
153 Keys.onPressed: event => {
154 if (!lockSurface.authRunning) {
155 event.accepted = true;
156 lockSurface.authRunning = true;
157 }
158 }
159 focus: !passwordBox.visible
160
161 visible: lockSurface.authRunning
162
163 color: Qt.rgba(0, 0, 0, 0.75)
164 margin: 8
165
166 anchors.centerIn: parent
167
168 ColumnLayout {
169 spacing: 4
170
171 BusyIndicator {
172 visible: running
173 running: !Array.from(lockSurface.messages).length && !lockSurface.responseRequired
174 }
175
176 Repeater {
177 model: lockSurface.messages
178
179 Text {
180 required property var modelData
181
182 font.pointSize: 10
183 font.family: "Fira Sans"
184 color: modelData.error ? "#f28a21" : "#ffffff"
185
186 text: String(modelData.text).trim()
187
188 Layout.fillWidth: true
189 horizontalAlignment: Text.AlignHCenter
190 }
191 }
192
193 TextField {
194 id: passwordBox
195
196 visible: lockSurface.responseRequired
197 echoMode: lockSurface.responseVisible ? TextInput.Normal : TextInput.Password
198 inputMethodHints: Qt.ImhSensitiveData
199
200 onTextChanged: lockSurface.currentText = passwordBox.text
201 onAccepted: {
202 passwordBox.readOnly = true;
203 lockSurface.response(lockSurface.currentText);
204 }
205
206 Connections {
207 target: lockSurface
208 function onCurrentTextChanged() {
209 passwordBox.text = lockSurface.currentText
210 }
211 }
212 Connections {
213 target: lockSurface
214 function onResponseRequiredChanged() {
215 if (lockSurface.responseRequired)
216 passwordBox.readOnly = false;
217 passwordBox.focus = true;
218 passwordBox.selectAll();
219 }
220 }
221
222 Layout.topMargin: 4
223 Layout.fillWidth: true
224 }
225 }
226 }
227}
diff --git a/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
new file mode 100644
index 00000000..996fd41b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
@@ -0,0 +1,132 @@
1import Quickshell
2import Quickshell.Wayland
3import Quickshell.Io
4import Quickshell.Services.Pam
5import Quickshell.Services.Mpris
6import Custom as Custom
7import qs.Services
8import QtQml
9
10Scope {
11 id: lockscreen
12
13 property string currentText: ""
14
15 PamContext {
16 id: pam
17
18 property list<var> messages: []
19
20 config: "quickshell"
21 onCompleted: result => {
22 if (result === PamResult.Success) {
23 lock.locked = false;
24 }
25 }
26 onPamMessage: {
27 messages = Array.from(messages).concat([{ "text": pam.message, "error": pam.messageIsError }])
28 }
29 onActiveChanged: {
30 messages = [];
31 }
32 }
33
34 IpcHandler {
35 target: "Lockscreen"
36
37 function setLocked(locked: bool): void { lock.locked = locked; }
38 function getLocked(): bool { return lock.locked; }
39 }
40
41 Connections {
42 target: Custom.Systemd
43 function onSleep(before: bool) {
44 console.log(`received prepare for sleep ${before}`);
45 if (before)
46 lock.locked = true;
47 }
48 function onLock() { lock.locked = true; }
49 function onUnlock() { lock.locked = false; }
50 }
51
52 IdleMonitor {
53 id: idleMonitor
54 enabled: !lock.secure
55 timeout: 600
56 respectInhibitors: true
57
58 onIsIdleChanged: {
59 if (idleMonitor.isIdle)
60 lock.locked = true;
61 }
62 }
63
64 Custom.SystemdInhibitor {
65 enabled: !lock.secure
66
67 what: Custom.SystemdInhibitorParams.Sleep
68 who: "quickshell"
69 why: "Lock session"
70 mode: Custom.SystemdInhibitorParams.Delay
71 }
72
73 Binding {
74 target: NotificationManager
75 property: "lockscreenActive"
76 value: lock.locked
77 }
78
79 WlSessionLock {
80 id: lock
81
82 onLockStateChanged: {
83 if (!locked && pam.active)
84 pam.abort();
85
86 if (locked) {
87 NiriService.sendCommand({ "Action": { "PowerOffMonitors": {} } }, _ => {});
88 Custom.KeePassXC.lockAllDatabases();
89 Array.from(MprisProxy.players).forEach(player => {
90 if (player.canPause && player.isPlaying)
91 player.pause();
92 });
93 // Custom.Systemd.stopUserUnit("gpg-agent.service", "replace");
94 GpgAgent.reloadAgent();
95 }
96 }
97 Component.onCompleted: { (_ => {})(MprisProxy.players); }
98
99 onSecureStateChanged: Custom.Systemd.lockedHint = lock.secure
100
101 WlSessionLockSurface {
102 id: lockSurface
103
104 color: "black"
105
106 LockSurface {
107 id: surfaceContent
108
109 onResponse: responseText => pam.respond(responseText)
110 onAuthRunningChanged: {
111 if (authRunning)
112 pam.start();
113 }
114 Connections {
115 target: pam
116 function onMessagesChanged() { surfaceContent.messages = pam.messages; }
117 function onResponseRequiredChanged() { surfaceContent.responseRequired = pam.responseRequired; }
118 function onActiveChanged() { surfaceContent.authRunning = pam.active; }
119 }
120 onCurrentTextChanged: lockscreen.currentText = currentText
121 Connections {
122 target: lockscreen
123 function onCurrentTextChanged() { surfaceContent.currentText = lockscreen.currentText; }
124 }
125 Connections {
126 target: lockSurface
127 function onScreenChanged() { surfaceContent.screen = lockSurface.screen; }
128 }
129 }
130 }
131 }
132}
diff --git a/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
new file mode 100644
index 00000000..155a009e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
@@ -0,0 +1,35 @@
1import QtQuick
2import QtQuick.Effects
3
4Item {
5 id: root
6
7 required property string icon
8 property color color: "white"
9
10 property real implicitSize: 0
11
12 readonly property real actualSize: Math.min(root.width, root.height)
13
14 implicitWidth: root.implicitSize
15 implicitHeight: root.implicitSize
16
17 Image {
18 id: sourceImage
19 source: "file://" + @mdi@ + "/svg/" + root.icon + ".svg"
20 anchors.fill: parent
21 fillMode: Image.PreserveAspectFit
22
23 sourceSize.width: root.actualSize
24 sourceSize.height: root.actualSize
25
26 layer.enabled: true
27 layer.effect: MultiEffect {
28 id: effect
29
30 brightness: 1
31 colorization: 1
32 colorizationColor: root.color
33 }
34 }
35}
diff --git a/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml b/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml
new file mode 100644
index 00000000..beff205c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NiriIdle.qml
@@ -0,0 +1,30 @@
1import QtQml
2import Quickshell
3import Quickshell.Wayland
4import qs.Services
5import Custom as Custom
6
7Scope {
8 IdleMonitor {
9 id: idleMonitor30
10 timeout: 30
11
12 onIsIdleChanged: Custom.Systemd.setIdleHint(idleMonitor30.isIdle)
13 }
14 IdleMonitor {
15 id: idleMonitor540
16 timeout: 540
17
18 onIsIdleChanged: {
19 if (idleMonitor540.isIdle)
20 NiriService.sendCommand({ "Action": { "PowerOffMonitors": {} } }, _ => {});
21 }
22 }
23 Connections {
24 target: Custom.Systemd
25 function onSleep(before: bool) {
26 if (!before)
27 NiriService.sendCommand({ "Action": { "PowerOnMonitors": {} } }, _ => {});
28 }
29 }
30}
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
new file mode 100644
index 00000000..cc0e49b1
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
@@ -0,0 +1,340 @@
1import QtQml
2import QtQml.Models
3import QtQuick
4import Quickshell
5import Quickshell.Widgets
6import Quickshell.Wayland
7import qs.Services
8import QtQuick.Layouts
9import Quickshell.Services.Notifications
10
11Scope {
12 id: root
13
14 property var activeScreen: Array.from(Quickshell.screens).find(screen => screen.name === Array.from(NiriService.workspaces).find(ws => ws.is_focused)?.output) ?? null
15
16 Instantiator {
17 id: notifsRepeater
18
19 model: ScriptModel {
20 values: NotificationManager.groups
21 }
22
23 delegate: PanelWindow {
24 id: notifWindow
25
26 visible: NotificationManager.active
27
28 screen: root.activeScreen
29
30 WlrLayershell.namespace: "notifications"
31
32 required property var modelData
33 required property var index
34
35 property int activeIx: modelData.length - 1
36 onModelDataChanged: {
37 notifWindow.activeIx = modelData.length - 1;
38 }
39
40 property color textColor: {
41 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Low)
42 return "#ff999999";
43 return "white";
44 }
45 property color backgroundColor: {
46 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Critical)
47 return "#dd900000";
48 return "black";
49 }
50
51 anchors {
52 right: true
53 top: true
54 }
55
56 readonly property real spaceAbove: {
57 var res = 0;
58 for (let i = 0; i < notifWindow.index; i++) {
59 (_ => {})(notifsRepeater.objectAt(i).modelData);
60 res += notifsRepeater.objectAt(i).height + 8;
61 }
62 return res;
63 }
64
65 margins {
66 right: 26 + 8
67 top: 8 + spaceAbove
68 }
69
70 color: "transparent"
71
72 implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + (notifTime.visible ? 8 + notifTime.contentHeight : 0) + 16
73 implicitWidth: 400
74
75 WrapperMouseArea {
76 enabled: true
77
78 anchors.fill: parent
79
80 cursorShape: Qt.PointingHandCursor
81
82 onClicked: {
83 for (const notif of notifWindow.modelData)
84 notif.dismiss();
85 }
86
87 property real angleRem: 0
88 property real sensitivity: 1 / 120
89 onWheel: event => {
90 angleRem += event.angleDelta.y;
91 const d = Math.round(angleRem * sensitivity);
92 angleRem -= d / sensitivity;
93 notifWindow.activeIx = ((notifWindow.modelData?.length ?? 1) + notifWindow.activeIx - d) % (notifWindow.modelData?.length ?? 1);
94 }
95
96 Rectangle {
97 color: notifWindow.backgroundColor
98 anchors.fill: parent
99 border {
100 color: Qt.hsla(195/360, 1, 0.45, 1)
101 width: 2
102 }
103
104 GridLayout {
105 id: notifLayout
106
107 width: 400 - 16
108 anchors.fill: parent
109 anchors.margins: 8
110 columnSpacing: 8
111 rowSpacing: 8
112
113 columns: notifImage.visible ? 3 : 2
114 rows: {
115 var res = 1;
116 if (notifBody.visible)
117 res += 1;
118 if (notifActions.visible)
119 res += 1;
120 if (notifTime.visible)
121 res += 1;
122 return res;
123 }
124
125 Text {
126 id: notifCount
127
128 visible: notifWindow.modelData?.length > 1 ?? false
129 text: `${notifWindow.activeIx + 1}/${notifWindow.modelData?.length ?? ""}`
130
131 font.pointSize: 10
132 font.family: "Fira Sans"
133 font.bold: true
134 font.features: { "tnum": 1 }
135 color: notifWindow.textColor
136 maximumLineCount: 1
137
138 Layout.fillWidth: false
139 Layout.row: 0
140 Layout.column: notifImage.visible ? 1 : 0
141 }
142
143 Text {
144 id: notifSummary
145
146 text: notifWindow.modelData?.[notifWindow.activeIx]?.summary ?? ""
147
148 font.pointSize: 10
149 font.family: "Fira Sans"
150 font.italic: true
151 color: notifWindow.textColor
152 maximumLineCount: 1
153 elide: Text.ElideRight
154
155 Layout.fillWidth: true
156 Layout.row: 0
157 Layout.column: (notifCount.visible ? 1 : 0) + (notifImage.visible ? 1 : 0)
158 Layout.columnSpan: notifCount.visible ? 1 : 2
159 }
160
161 Image {
162 id: notifImage
163
164 visible: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? false
165
166 onStatusChanged: {
167 if (notifImage.status == Image.Error)
168 notifImage.visible = false;
169 }
170
171 source: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? ""
172 fillMode: Image.PreserveAspectFit
173 asynchronous: true
174 smooth: true
175 mipmap: true
176
177 Layout.maximumWidth: 50
178 Layout.column: 0
179 Layout.row: 0
180 Layout.fillHeight: true
181 Layout.rowSpan: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0)
182 }
183
184 Text {
185 id: notifBody
186
187 visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false
188 text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? ""
189 textFormat: Text.RichText
190 wrapMode: Text.Wrap
191
192 font.pointSize: 10
193 font.family: "Fira Sans"
194 color: notifWindow.textColor
195
196 Layout.fillWidth: true
197 Layout.row: 1
198 Layout.column: notifImage.visible ? 1 : 0
199 Layout.columnSpan: notifCount.visible ? 2 : 1
200 }
201
202 Text {
203 id: notifTime
204
205 Connections {
206 target: NotificationManager.clock
207 function onDateChanged() {
208 notifTime.text = NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime);
209 }
210 }
211
212 visible: notifTime.text && notifTime.text !== "now"
213 text: NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime)
214
215 font.pointSize: 8
216 font.family: "Fira Sans"
217 font.italic: true
218 color: "#555"
219 maximumLineCount: 1
220 horizontalAlignment: Text.AlignRight
221
222 Layout.fillWidth: true
223 Layout.row: notifBody.visible ? 2 : 1
224 Layout.column: notifImage.visible ? 1 : 0
225 Layout.columnSpan: 2
226 }
227
228 RowLayout {
229 id: notifActions
230
231 visible: notifWindow.modelData?.[notifWindow.activeIx]?.actions.length > 0 ?? false
232
233 spacing: 8
234 uniformCellSizes: true
235
236 width: 400 - 16
237 Layout.row: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0)
238 Layout.column: 0
239 Layout.columnSpan: 2 + (notifImage.visible ? 1 : 0)
240
241 Repeater {
242 model: ScriptModel {
243 values: notifWindow.modelData?.[notifWindow.activeIx]?.actions
244 }
245
246 delegate: WrapperMouseArea {
247 id: actionMouseArea
248
249 required property var modelData
250
251 height: actionLabelWrapper.implicitHeight
252 Layout.fillWidth: true
253 Layout.horizontalStretchFactor: 1
254
255 hoverEnabled: true
256 cursorShape: Qt.PointingHandCursor
257
258 onClicked: actionMouseArea.modelData?.invoke()
259
260 Rectangle {
261 anchors.fill: parent
262
263 color: actionMouseArea.containsMouse ? "#20ffffff" : "transparent"
264
265 border {
266 width: 2
267 color: "#20ffffff"
268 }
269
270 WrapperItem {
271 id: actionLabelWrapper
272
273 margin: 8
274 anchors.centerIn: parent
275
276 RowLayout {
277 id: actionLabelLayout
278
279 spacing: 8
280
281 IconImage {
282 id: actionIcon
283
284 visible: notifWindow.modelData?.[notifWindow.activeIx]?.hasActionIcons
285
286 onStatusChanged: {
287 if (actionIcon.status == Image.Error)
288 actionIcon.visible = false;
289 }
290
291 implicitSize: 16
292 source: {
293 if (!actionIcon.visible)
294 return "";
295
296 let icon = actionMouseArea.modelData?.identifier ?? ""
297 if (icon.includes("?path=")) {
298 const split = icon.split("?path=")
299 if (split.length !== 2)
300 return icon
301 const name = split[0]
302 const path = split[1]
303 const fileName = name.substring(
304 name.lastIndexOf("/") + 1)
305 return `file://${path}/${fileName}`
306 }
307 return icon
308 }
309 asynchronous: true
310 smooth: true
311 mipmap: true
312
313 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
314 }
315
316 Text {
317 id: actionLabel
318
319 visible: actionMouseArea.modelData?.text ?? false
320
321 text: actionMouseArea.modelData?.text ?? ""
322
323 font.pointSize: 10
324 font.family: "Fira Sans"
325 color: notifWindow.textColor
326 maximumLineCount: 1
327 elide: Text.ElideRight
328 }
329 }
330 }
331 }
332 }
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340}
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml
new file mode 100644
index 00000000..b58467b3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml
@@ -0,0 +1,266 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import qs.Services
5import QtQuick.Controls
6import QtQuick.Layouts
7import QtQuick.Shapes
8
9Item {
10 id: root
11
12 width: icon.width + 8
13 height: parent.height
14 anchors.verticalCenter: parent.verticalCenter
15
16 WrapperMouseArea {
17 id: widgetMouseArea
18
19 anchors.fill: parent
20
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: NotificationManager.displayInhibited = !NotificationManager.displayInhibited
25
26 Rectangle {
27 anchors.fill: parent
28 color: {
29 if (widgetMouseArea.containsMouse) {
30 return "#33808080";
31 }
32 return "transparent";
33 }
34
35 Item {
36 anchors.fill: parent
37
38 MaterialDesignIcon {
39 id: icon
40
41 implicitSize: 14
42 anchors.centerIn: parent
43
44 icon: NotificationManager.active ? "message" : "message-off"
45 color: {
46 if (!NotificationManager.active && !NotificationManager.displayInhibited)
47 return "#f28a21";
48 if (NotificationManager.displayInhibited)
49 return "white";
50 return "#555";
51 }
52 }
53 }
54 }
55 }
56
57 Loader {
58 id: tooltipLoader
59
60 active: false
61
62 Connections {
63 target: widgetMouseArea
64 function onContainsMouseChanged() {
65 if (widgetMouseArea.containsMouse)
66 tooltipLoader.active = true;
67 }
68 }
69
70 sourceComponent: PopupWindow {
71 id: tooltip
72
73 property bool nextVisible: NotificationManager.active && (widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse)
74
75 anchor {
76 item: widgetMouseArea
77 edges: Edges.Bottom | Edges.Left
78 }
79 visible: false
80
81 onNextVisibleChanged: hangTimer.restart()
82
83 Timer {
84 id: hangTimer
85 interval: tooltip.visible ? 100 : 500
86 onTriggered: {
87 tooltip.visible = tooltip.nextVisible;
88 if (!tooltip.visible)
89 tooltipLoader.active = false;
90 }
91 }
92
93 implicitWidth: 400
94 implicitHeight: Math.min(tooltip.screen.height * 0.66, Math.max(100, scroll.contentHeight + 16))
95 color: "black"
96
97 WrapperMouseArea {
98 id: tooltipMouseArea
99
100 hoverEnabled: true
101 enabled: true
102
103 anchors.fill: parent
104
105 WrapperItem {
106 margin: 8
107
108 ScrollView {
109 id: scroll
110
111 contentWidth: availableWidth
112 // ScrollBar.vertical.policy: ScrollBar.AlwaysOn
113
114 ColumnLayout {
115 id: historyLayout
116 anchors {
117 left: parent.left
118 right: parent.right
119 }
120
121 spacing: 8
122
123 Repeater {
124 model: ScriptModel {
125 values: [...NotificationManager.history].reverse().map(o => o.notification)
126 }
127
128 delegate: GridLayout {
129 id: notif
130
131 Layout.fillWidth: true
132 Layout.preferredHeight: notifSummary.contentHeight + (notifBody.visible ? notifBody.contentHeight + 8 : 0) + (notifSep.visible ? notifSep.height + 8 : 0) + notifTime.contentHeight + 8
133
134 required property var modelData
135 required property int index
136
137 columnSpacing: 8
138 rowSpacing: 8
139
140 columns: notifImage.visible ? 2 : 1
141 rows: {
142 var res = 2;
143 if (notifBody.visible)
144 res += 1;
145 if (notifSep.visible)
146 res += 1;
147 return res;
148 }
149
150 Shape {
151 id: notifSep
152
153 visible: notif.index != 0
154
155 height: 2
156 width: 400 - 32
157
158 ShapePath {
159 strokeWidth: 2
160 strokeColor: "#20ffffff"
161 startX: 0; startY: 0;
162 PathLine { x: 400 - 32; y: 0; }
163 }
164
165 Layout.row: 0
166 Layout.column: 0
167 Layout.columnSpan: notifImage.visible ? 2 : 1
168 Layout.alignment: Qt.AlignHCenter
169 }
170
171 Text {
172 id: notifSummary
173
174 text: notif.modelData?.summary ?? ""
175
176 font.pointSize: 10
177 font.family: "Fira Sans"
178 font.italic: true
179 color: "white"
180 maximumLineCount: 1
181 elide: Text.ElideRight
182
183 Layout.fillWidth: true
184 Layout.row: notifSep.visible ? 1 : 0
185 Layout.column: notifImage.visible ? 1 : 0
186 }
187
188 Image {
189 id: notifImage
190
191 visible: (notif.modelData?.image || notif.modelData?.appIcon) ?? false
192
193 onStatusChanged: {
194 if (notifImage.status == Image.Error)
195 notifImage.visible = false;
196 }
197
198 source: (notif.modelData?.image || notif.modelData?.appIcon) ?? ""
199 fillMode: Image.PreserveAspectFit
200 asynchronous: true
201 smooth: true
202 mipmap: true
203
204 Layout.maximumWidth: 50
205 Layout.column: 0
206 Layout.row: notifSep.visible ? 1 : 0
207 Layout.fillHeight: true
208 Layout.rowSpan: notifBody.visible ? 3 : 2
209 }
210
211 Text {
212 id: notifBody
213
214 visible: notif.modelData?.body ?? false
215 text: notif.modelData?.body ?? ""
216 textFormat: Text.RichText
217 wrapMode: Text.Wrap
218
219 font.pointSize: 10
220 font.family: "Fira Sans"
221 color: "white"
222
223 Layout.fillWidth: true
224 Layout.row: notifSep.visible ? 2 : 1
225 Layout.column: notifImage.visible ? 1 : 0
226 }
227
228 Text {
229 id: notifTime
230
231 Connections {
232 target: NotificationManager.clock
233 function onDateChanged() {
234 notifTime.text = NotificationManager.formatTime(notif.modelData?.receivedTime);
235 }
236 }
237
238 text: NotificationManager.formatTime(notif.modelData?.receivedTime)
239
240 font.pointSize: 8
241 font.family: "Fira Sans"
242 font.italic: true
243 color: "#555"
244 maximumLineCount: 1
245 horizontalAlignment: Text.AlignRight
246
247 Layout.fillWidth: true
248 Layout.row: {
249 var res = 1;
250 if (notifSep.visible)
251 res += 1;
252 if (notifBody.visible)
253 res += 1;
254 return res;
255 }
256 Layout.column: notifImage.visible ? 1 : 0
257 }
258 }
259 }
260 }
261 }
262 }
263 }
264 }
265 }
266}
diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
new file mode 100644
index 00000000..9c6b65a4
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
@@ -0,0 +1,483 @@
1import QtQuick
2import QtQuick.Layouts
3import QtQuick.Controls.Fusion
4import Quickshell
5import Quickshell.Services.Pipewire
6import Quickshell.Widgets
7
8Item {
9 height: parent.height
10 width: volumeIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 PwObjectTracker {
14 objects: [Pipewire.defaultAudioSink]
15 }
16
17 WrapperMouseArea {
18 id: widgetMouseArea
19
20 anchors.fill: parent
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: {
25 if (!Pipewire.defaultAudioSink)
26 return;
27 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
28 }
29
30 property real sensitivity: (1 / 40) / 120
31 onWheel: event => {
32 if (!Pipewire.defaultAudioSink)
33 return;
34 Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity;
35 }
36
37 Rectangle {
38 id: volumeWidget
39
40 anchors.fill: parent
41 color: {
42 if (widgetMouseArea.containsMouse)
43 return "#33808080";
44 return "transparent";
45 }
46
47 Item {
48 anchors.fill: parent
49
50 MaterialDesignIcon {
51 id: volumeIcon
52
53 implicitSize: 14
54 anchors.centerIn: parent
55
56 icon: {
57 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
58 return "volume-off";
59 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
60 return "volume-low";
61 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
62 return "volume-medium";
63 return "volume-high";
64 }
65 color: "#555"
66 }
67 }
68 }
69 }
70
71 Loader {
72 id: tooltipLoader
73
74 active: false
75
76 Connections {
77 target: widgetMouseArea
78 function onContainsMouseChanged() {
79 if (widgetMouseArea.containsMouse)
80 tooltipLoader.active = true;
81 }
82 }
83
84 PwObjectTracker {
85 objects: Pipewire.devices
86 }
87 PwObjectTracker {
88 objects: Pipewire.nodes
89 }
90
91 sourceComponent: PopupWindow {
92 id: tooltip
93
94 property bool openPopup: false
95 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup
96
97 anchor {
98 item: widgetMouseArea
99 edges: Edges.Bottom | Edges.Left
100 }
101 visible: false
102
103 onNextVisibleChanged: hangTimer.restart()
104
105 Timer {
106 id: hangTimer
107 interval: 100
108 onTriggered: {
109 tooltip.visible = tooltip.nextVisible;
110 if (!tooltip.visible)
111 tooltipLoader.active = false;
112 }
113 }
114
115 implicitWidth: tooltipContent.width
116 implicitHeight: tooltipContent.height
117 color: "transparent"
118
119 Rectangle {
120 width: tooltip.width
121 height: tooltipLayout.childrenRect.height + 16
122 color: "black"
123 }
124
125 WrapperItem {
126 id: tooltipContent
127
128 bottomMargin: Math.max(0, 200 - tooltipLayout.implicitHeight)
129
130 WrapperMouseArea {
131 id: tooltipMouseArea
132
133 hoverEnabled: true
134 enabled: true
135
136 WrapperItem {
137 margin: 8
138 bottomMargin: 8
139
140 GridLayout {
141 id: tooltipLayout
142
143 columns: 4
144
145 Repeater {
146 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
147
148 Item {
149 id: descItem
150
151 required property var modelData
152 required property int index
153
154 Layout.column: 0
155 Layout.row: index
156
157 implicitWidth: descText.contentWidth
158 implicitHeight: descText.contentHeight
159
160 Text {
161 id: descText
162
163 color: "white"
164 font.pointSize: 10
165 font.family: "Fira Sans"
166
167 text: descItem.modelData.description
168 }
169 }
170 }
171
172 Repeater {
173 id: defaultSinkRepeater
174
175 model: {
176 Array.from(Pipewire.devices.values)
177 .filter(dev => dev.type == "Audio/Device")
178 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id ));
179 }
180
181 Item {
182 id: defaultSinkItem
183
184 required property var modelData
185 required property int index
186
187 visible: Boolean(modelData)
188
189 PwObjectTracker {
190 objects: [defaultSinkItem.modelData]
191 }
192
193 Layout.column: 1
194 Layout.row: index
195
196 Layout.fillHeight: true
197
198 implicitWidth: 16 + 8
199
200 WrapperMouseArea {
201 id: defaultSinkMouseArea
202
203 anchors.fill: parent
204 hoverEnabled: true
205 cursorShape: Qt.PointingHandCursor
206
207 onClicked: {
208 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData
209 }
210
211 onWheel: event => scrollVolume(event);
212 property real sensitivity: (1 / 40) / 120
213 function scrollVolume(event) {
214 defaultSinkItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
215 }
216
217 Rectangle {
218 id: defaultSinkWidget
219
220 anchors.fill: parent
221 color: {
222 if (defaultSinkMouseArea.containsMouse)
223 return "#33808080";
224 return "transparent";
225 }
226
227 MaterialDesignIcon {
228 width: 16
229 height: 16
230 anchors.centerIn: parent
231
232 icon: {
233 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id)
234 return "speaker";
235 return "speaker-off";
236 }
237 color: icon == "speaker" ? "white" : "#555"
238 }
239 }
240 }
241
242 PopupWindow {
243 id: volumeTooltip
244
245 property bool nextVisible: defaultSinkMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
246
247 anchor {
248 item: defaultSinkMouseArea
249 edges: Edges.Bottom | Edges.Left
250 }
251 visible: false
252
253 onNextVisibleChanged: volumeHangTimer.restart()
254
255 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
256
257 Timer {
258 id: volumeHangTimer
259 interval: 100
260 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
261 }
262
263 implicitWidth: volumeTooltipText.contentWidth + 16
264 implicitHeight: volumeTooltipText.contentHeight + 16
265 color: "black"
266
267 WrapperMouseArea {
268 id: volumeTooltipMouseArea
269
270 hoverEnabled: true
271 enabled: true
272
273 onWheel: event => defaultSinkMouseArea.scrollVolume(event);
274
275 anchors.fill: parent
276
277 Item {
278 anchors.fill: parent
279
280 Text {
281 id: volumeTooltipText
282
283 anchors.centerIn: parent
284
285 font.pointSize: 10
286 font.family: "Fira Sans"
287 color: "white"
288
289 text: `${Math.round(defaultSinkItem.modelData?.audio?.volume * 100)}%`
290 }
291 }
292 }
293 }
294 }
295 }
296
297 Repeater {
298 id: defaultSourceRepeater
299
300 model: {
301 Array.from(Pipewire.devices.values)
302 .filter(dev => dev.type == "Audio/Device")
303 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id ));
304 }
305
306 Item {
307 id: defaultSourceItem
308
309 required property var modelData
310 required property int index
311
312 visible: Boolean(modelData)
313
314 PwObjectTracker {
315 objects: [defaultSourceItem.modelData]
316 }
317
318 Layout.column: 2
319 Layout.row: index
320
321 Layout.fillHeight: true
322
323 implicitWidth: 16 + 8
324
325 WrapperMouseArea {
326 id: defaultSourceMouseArea
327
328 anchors.fill: parent
329 hoverEnabled: true
330 cursorShape: Qt.PointingHandCursor
331
332 onClicked: {
333 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData
334 }
335
336 onWheel: event => scrollVolume(event);
337 property real sensitivity: (1 / 40) / 120
338 function scrollVolume(event) {
339 defaultSourceItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
340 }
341
342 Rectangle {
343 id: defaultSourceWidget
344
345 anchors.fill: parent
346 color: {
347 if (defaultSourceMouseArea.containsMouse)
348 return "#33808080";
349 return "transparent";
350 }
351
352 MaterialDesignIcon {
353 width: 16
354 height: 16
355 anchors.centerIn: parent
356
357 icon: {
358 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id)
359 return "microphone";
360 return "microphone-off";
361 }
362 color: icon == "microphone" ? "white" : "#555"
363 }
364 }
365 }
366
367 PopupWindow {
368 id: volumeTooltip
369
370 property bool nextVisible: defaultSourceMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
371
372 anchor {
373 item: defaultSourceMouseArea
374 edges: Edges.Bottom | Edges.Left
375 }
376 visible: false
377
378 onNextVisibleChanged: volumeHangTimer.restart()
379
380 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
381
382 Timer {
383 id: volumeHangTimer
384 interval: 100
385 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
386 }
387
388 implicitWidth: volumeTooltipText.contentWidth + 16
389 implicitHeight: volumeTooltipText.contentHeight + 16
390 color: "black"
391
392 WrapperMouseArea {
393 id: volumeTooltipMouseArea
394
395 hoverEnabled: true
396 enabled: true
397
398 onWheel: event => defaultSourceMouseArea.scrollVolume(event);
399
400 anchors.fill: parent
401
402 Item {
403 anchors.fill: parent
404
405 Text {
406 id: volumeTooltipText
407
408 anchors.centerIn: parent
409
410 font.pointSize: 10
411 font.family: "Fira Sans"
412 color: "white"
413
414 text: `${Math.round(defaultSourceItem.modelData?.audio?.volume * 100)}%`
415 }
416 }
417 }
418 }
419 }
420 }
421
422 Repeater {
423 id: profileRepeater
424
425 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
426
427 Item {
428 id: profileItem
429
430 required property var modelData
431 required property int index
432
433 PwObjectTracker {
434 objects: [profileItem.modelData]
435 }
436
437 Layout.column: 3
438 Layout.row: index
439
440 Layout.fillWidth: true
441
442 implicitWidth: Math.max(profileBox.implicitWidth, 300)
443 implicitHeight: profileBox.height
444
445 ComboBox {
446 id: profileBox
447
448 model: profileItem.modelData.profiles
449
450 textRole: "description"
451 valueRole: "index"
452 onActivated: profileItem.modelData.setProfile(currentValue)
453
454 anchors.fill: parent
455
456 implicitContentWidthPolicy: ComboBox.WidestText
457
458 Connections {
459 target: profileItem.modelData
460 function onCurrentProfileChanged() {
461 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
462 }
463 }
464 Component.onCompleted: {
465 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
466 }
467
468 Connections {
469 target: profileBox.popup
470 function onVisibleChanged() {
471 tooltip.openPopup = profileBox.popup.visible
472 }
473 }
474 }
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482 }
483}
diff --git a/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml
new file mode 100644
index 00000000..d7ffadfe
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml
@@ -0,0 +1,49 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Widgets
5import qs.Services
6
7Item {
8 height: parent.height
9 width: layout.childrenRect.width
10 anchors.verticalCenter: parent.verticalCenter
11
12 visible: Array.from(Privacy.activeItems).length > 0
13
14 RowLayout {
15 id: layout
16
17 anchors.fill: parent
18
19 spacing: 8
20
21 Repeater {
22 model: Privacy.activeItems
23
24 Item {
25 id: privacyItem
26
27 required property var modelData;
28
29 height: parent.height
30 width: icon.width
31
32 MaterialDesignIcon {
33 id: icon
34
35 implicitSize: 14
36 anchors.centerIn: parent
37
38 icon: {
39 if (privacyItem.modelData == Privacy.Item.Microphone)
40 return "microphone";
41 if (privacyItem.modelData == Privacy.Item.Screensharing)
42 return "monitor-share";
43 }
44 color: "#f2201f"
45 }
46 }
47 }
48 }
49}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml
new file mode 100644
index 00000000..8318df50
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml
@@ -0,0 +1,75 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Io
6import Custom as Custom
7
8Singleton {
9 id: root
10
11 property string subsystem: "backlight"
12 property string device: "intel_backlight"
13
14 property real currBrightness
15 property real exponent: 4
16
17 function calcCurrBrightness() {
18 if (!currFile.loaded || !maxFile.loaded)
19 return undefined;
20 const curr = Number(currFile.text());
21 const max = Number(maxFile.text());
22 const val = Math.pow(curr / max, 1 / root.exponent);
23 return val;
24 }
25
26 Connections {
27 target: currFile
28 function onLoaded() {
29 const b = root.calcCurrBrightness();
30 if (typeof b !== 'undefined')
31 root.currBrightness = b;
32 }
33 }
34 Connections {
35 target: maxFile
36 function onLoaded() {
37 const b = root.calcCurrBrightness();
38 if (typeof b !== 'undefined')
39 root.currBrightness = b;
40 }
41 }
42
43 onCurrBrightnessChanged: {
44 root.currBrightness = Math.max(0, Math.min(1, root.currBrightness));
45
46 const prev = root.calcCurrBrightness();
47 if (typeof prev === 'undefined' || Math.abs(root.currBrightness - prev) < 0.01)
48 return;
49
50 const max = Number(maxFile.text());
51 const actual = Number(currFile.text());
52 let curr = Math.max(0, Math.min(max, Math.pow(root.currBrightness, root.exponent) * max));
53 if (Math.round(curr) == actual && curr < actual)
54 curr = Math.max(0, actual - 1);
55 else if (Math.round(curr) == actual && curr > actual)
56 curr = Math.min(max, actual + 1);
57 // root.currBrightness = Math.pow(curr / max, 1 / root.exponent);
58 Custom.Systemd.setBrightness(root.subsystem, root.device, Math.round(curr));
59 }
60
61 FileView {
62 id: currFile
63 path: `/sys/class/${root.subsystem}/${root.device}/brightness`
64 blockAllReads: true
65 watchChanges: true
66 onFileChanged: reload()
67 }
68 FileView {
69 id: maxFile
70 path: `/sys/class/${root.subsystem}/${root.device}/max_brightness`
71 blockAllReads: true
72 watchChanges: true
73 onFileChanged: reload()
74 }
75}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
new file mode 100644
index 00000000..3de69535
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
@@ -0,0 +1,18 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5
6Singleton {
7 id: root
8
9 Socket {
10 id: agentSocket
11 connected: true
12 path: `${Quickshell.env("XDG_RUNTIME_DIR")}/gnupg/S.gpg-agent`
13 }
14
15 function reloadAgent() {
16 agentSocket.write("RELOADAGENT\n")
17 }
18}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml b/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml
new file mode 100644
index 00000000..fe48fd7f
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/InhibitorState.qml
@@ -0,0 +1,22 @@
1pragma Singleton
2
3import Quickshell
4import Custom as Custom
5
6Singleton {
7 id: inhibitorState
8
9 property bool waylandIdleInhibited: false
10 property alias lidSwitchInhibited: lidSwitchInhibitor.enabled
11
12 Custom.SystemdInhibitor {
13 id: lidSwitchInhibitor
14
15 enabled: false
16
17 what: Custom.SystemdInhibitorParams.HandleLidSwitch
18 who: "quickshell"
19 why: "User request"
20 mode: Custom.SystemdInhibitorParams.BlockWeak
21 }
22}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml b/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml
new file mode 100644
index 00000000..e3ab9755
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/MprisProxy.qml
@@ -0,0 +1,8 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Services.Mpris
5
6Scope {
7 property list<var> players: Mpris.players.values
8}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
new file mode 100644
index 00000000..cce614eb
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
@@ -0,0 +1,194 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5import QtQuick
6
7Singleton {
8 id: root
9
10 property var workspaces: []
11 property var outputs: {}
12 property var keyboardLayouts: {}
13 property var windows: []
14 readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
15
16 function refreshOutputs() {
17 commandSocket.sendCommand("Outputs", data => {
18 outputs = data.Ok.Outputs;
19 });
20 }
21
22 function sendCommand(command, callback) {
23 commandSocket.sendCommand(command, callback);
24 }
25
26 Socket {
27 id: eventStreamSocket
28 path: root.socketPath
29 connected: true
30
31 property bool acked: false
32
33 onConnectionStateChanged: {
34 if (connected) {
35 acked = false;
36 write('"EventStream"\n');
37 }
38 }
39
40 parser: SplitParser {
41 onRead: line => {
42 try {
43 const event = JSON.parse(line)
44
45 // console.log(JSON.stringify(event))
46
47 if (event.WorkspacesChanged) {
48 root.workspaces = event.WorkspacesChanged.workspaces
49 root.refreshOutputs();
50 } else if (event.WorkspaceActivated)
51 eventWorkspaceActivated(event.WorkspaceActivated);
52 else if (event.WorkspaceUrgencyChanged)
53 eventWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
54 else if (event.WorkspaceActiveWindowChanged)
55 eventWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged);
56 else if (event.KeyboardLayoutsChanged)
57 root.keyboardLayouts = event.KeyboardLayoutsChanged.keyboard_layouts;
58 else if (event.KeyboardLayoutSwitched)
59 root.keyboardLayouts = Object.assign({}, root.keyboardLayouts, {"current_idx": event.KeyboardLayoutSwitched.idx });
60 else if (event.WindowsChanged)
61 root.windows = event.WindowsChanged.windows
62 else if (event.WindowOpenedOrChanged)
63 eventWindowOpenedOrChanged(event.WindowOpenedOrChanged);
64 else if (event.WindowClosed)
65 eventWindowClosed(event.WindowClosed);
66 else if (event.WindowFocusChanged)
67 eventWindowFocusChanged(event.WindowFocusChanged);
68 else if (event.WindowUrgencyChanged)
69 eventWindowUrgencyChanged(event.WindowUrgencyChanged);
70 else if (event.WindowLayoutsChanged)
71 eventWindowLayoutsChanged(event.WindowLayoutsChanged);
72 else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; }
73 else if (event.OverviewOpenedOrClosed) {}
74 else if (event.ConfigLoaded) {}
75 else
76 console.log(JSON.stringify(event));
77 } catch (e) {
78 console.warn("NiriService: Failed to parse event:", line, e)
79 }
80 }
81 }
82 }
83
84 Socket {
85 id: commandSocket
86 path: root.socketPath
87 connected: true
88
89 property var awaitingAnswer: null
90 property var cmdQueue: []
91
92 parser: SplitParser {
93 onRead: line => {
94 if (commandSocket.awaitingAnswer === null)
95 return;
96
97 try {
98 const response = JSON.parse(line);
99 commandSocket.awaitingAnswer.callback(response);
100 commandSocket.awaitingAnswer = null;
101 } catch (e) {
102 console.warn("NiriService: Failed to parse response:", line, e)
103 }
104 commandSocket._handleQueue();
105 }
106 }
107
108 onCmdQueueChanged: {
109 _handleQueue();
110 }
111 onAwaitingAnswerChanged: {
112 _handleQueue();
113 }
114
115 function _handleQueue() {
116 if (cmdQueue.length <= 0 || awaitingAnswer !== null)
117 return;
118
119 let localQueue = Array.from(cmdQueue);
120 awaitingAnswer = localQueue.shift();
121 cmdQueue = localQueue;
122 write(JSON.stringify(awaitingAnswer.command) + '\n');
123 }
124
125 function sendCommand(command, callback) {
126 cmdQueue = Array.from(cmdQueue).concat([{ "command": command, "callback": callback }])
127 }
128 }
129
130 function eventWorkspaceActivated(data) {
131 let relevant_output = null;
132 Array.from(root.workspaces).forEach(ws => {
133 if (data.id === ws.id)
134 relevant_output = ws.output;
135 });
136 root.workspaces = Array.from(root.workspaces).map(ws => {
137 if (data.focused)
138 ws.is_focused = false;
139 if (ws.output === relevant_output)
140 ws.is_active = false;
141 if (data.id === ws.id) {
142 ws.is_active = true;
143 ws.is_focused = data.focused;
144 }
145 return ws;
146 });
147 }
148 function eventWorkspaceUrgencyChanged(data) {
149 root.workspaces = Array.from(root.workspaces).map(ws => {
150 if (data.id == ws.id)
151 ws.is_urgent = data.urgent;
152 return ws;
153 });
154 }
155 function eventWorkspaceActiveWindowChanged(data) {
156 root.workspaces = Array.from(root.workspaces).map(ws => {
157 if (data.workspace_id === ws.id)
158 ws.active_window_id = data.active_window_id;
159 return ws;
160 });
161 }
162 function eventWindowOpenedOrChanged(data) {
163 root.windows = Array.from(root.windows).map(win => {
164 if (data.window.is_focused)
165 win.is_focused = false;
166 return win;
167 }).filter(win => win.id !== data.window.id).concat([data.window]);
168 }
169 function eventWindowClosed(data) {
170 root.windows = Array.from(root.windows).filter(win => win.id !== data.id);
171 }
172 function eventWindowFocusChanged(data) {
173 root.windows = Array.from(root.windows).map(win => {
174 win.is_focused = win.id === data.id;
175 return win;
176 });
177 }
178 function eventWindowUrgencyChanged(data) {
179 root.windows = Array.from(root.windows).map(win => {
180 if (win.id === data.id)
181 win.is_urgent = data.urgent;
182 return win;
183 });
184 }
185 function eventWindowLayoutsChanged(data) {
186 root.windows = Array.from(root.windows).map(win => {
187 Array.from(data.changes).forEach(change => {
188 if (win.id === change[0])
189 win.layout = change[1];
190 });
191 return win;
192 });
193 }
194}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml
new file mode 100644
index 00000000..f02d1695
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml
@@ -0,0 +1,162 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Services.Notifications
6
7Singleton {
8 id: root
9
10 readonly property bool active: !root.lockscreenActive && !root.displayInhibited
11 property bool lockscreenActive: false
12 property bool displayInhibited: false
13 property alias trackedNotifications: server.trackedNotifications
14 readonly property var groups: {
15 function matchesGroupKey(notif, groupKey) {
16 var matches = true;
17 for (const prop in groupKey.test) {
18 if (notif[prop] !== groupKey.test[prop]) {
19 matches = false;
20 break;
21 }
22 }
23 return matches;
24 }
25
26 var groups = new Map();
27 var notifs = new Array();
28 for (const [ix, notif] of server.trackedNotifications.values.entries()) {
29 var didGroup = false;
30 for (const groupKey of root.groupKeys) {
31 if (!matchesGroupKey(notif, groupKey))
32 continue;
33
34 const key = JSON.stringify({
35 "key": groupKey,
36 "values": Object.assign({}, ...(Array.from(groupKey["group-by"]).map(prop => {
37 var res = {};
38 res[prop] = notif[prop];
39 return res;
40 })))
41 });
42 if (!groups.has(key))
43 groups.set(key, new Array());
44 groups.get(key).push({ "ix": ix, "notif": notif });
45 didGroup = true;
46 break;
47 }
48
49 if (!didGroup)
50 notifs.push([{ "ix": ix, "notif": notif }]);
51 }
52 notifs.push(...groups.values());
53 notifs.sort((as, bs) => Math.min(...(as.map(o => o.ix))) - Math.min(...(bs.map(o => o.ix))));
54 return notifs.map(ns => ns.map(n => n.notif));
55 }
56
57 property var groupKeys: [
58 { "test": { "appName": "Element" }, "group-by": [ "summary" ] }
59 ];
60
61 property int historyLimit: 100
62 property var history: []
63
64 Component {
65 id: expirationTimer
66
67 QtObject {
68 id: timer
69
70 required property QtObject parent
71 required property int expirationTime
72
73 property list<QtObject> data: [
74 Timer {
75 running: root.active && !timer.expired
76 interval: timer.expirationTime
77 onTriggered: {
78 timer.parent.expirationTimer.destroy();
79 timer.parent.expirationTimer = null;
80 timer.parent.expire();
81 }
82 }
83 ]
84 }
85 }
86
87 Component {
88 id: notificationLock
89
90 RetainableLock {}
91 }
92
93 readonly property SystemClock clock: SystemClock {
94 precision: SystemClock.Minutes
95 }
96
97 function formatTime(time) {
98 const now = root.clock.date;
99 const diff = now - time;
100 const minutes = Math.ceil(diff / 60000);
101 const hours = Math.floor(minutes / 60);
102
103 if (hours < 1) {
104 if (minutes < 1)
105 return "now";
106 if (minutes == 1)
107 return "1 minute";
108 return `${minutes} minutes`;
109 }
110
111 const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
112 const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate())
113 const days = Math.floor((nowDate - timeDate) / (1000 * 86400))
114
115 const timeStr = time.toLocaleTimeString(Qt.locale(), "HH:mm");
116
117 if (days === 0)
118 return timeStr;
119 if (days === 1)
120 return `yesterday ${timeStr}`;
121
122 const dateStr = time.toLocaleTimeString(Qt.locale(), "YYYY-MM-DD");
123 return `${dateStr} ${timeStr}`;
124 }
125
126 NotificationServer {
127 id: server
128
129 bodySupported: true
130 actionsSupported: true
131 actionIconsSupported: true
132 imageSupported: true
133 bodyMarkupSupported: true
134 bodyImagesSupported: true
135
136 onNotification: notification => {
137 var timeout = notification.expireTimeout * 1000;
138 if (notification.appName == "poweralertd")
139 timeout = 2000;
140 if (timeout > 0) {
141 Object.defineProperty(notification, "expirationTimer", { configurable: true, enumerable: true, writable: true });
142 notification.expirationTimer = expirationTimer.createObject(notification, { parent: notification, expirationTime: timeout });
143 }
144 Object.defineProperty(notification, "receivedTime", { configurable: true, enumerable: true, writable: true });
145 notification.receivedTime = root.clock.date;
146 notification.closed.connect((reason) => server.onNotificationClosed(notification, reason));
147 notification.tracked = true;
148 }
149
150 function onNotificationClosed(notification, reason) {
151 while (root.history.length >= root.historyLimit) {
152 root.history[0].lock.locked = false;
153 root.history.shift();
154 }
155
156 root.history.push({
157 lock: notificationLock.createObject(root, { locked: true, object: notification }),
158 notification: notification
159 });
160 }
161 }
162}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml
new file mode 100644
index 00000000..9c813e49
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml
@@ -0,0 +1,63 @@
1pragma Singleton
2
3import QtQml
4import Quickshell
5import Quickshell.Services.Pipewire
6
7Singleton {
8 id: root
9
10 PwObjectTracker {
11 objects: Pipewire.nodes.values
12 }
13
14 enum Item {
15 Microphone,
16 Screensharing
17 }
18
19 readonly property list<var> activeItems: {
20 var items = [];
21 if (microphoneActive)
22 items.push(Privacy.Item.Microphone);
23 if (screensharingActive)
24 items.push(Privacy.Item.Screensharing);
25 return items;
26 }
27
28 readonly property bool microphoneActive: {
29 if (!Pipewire.ready || !Pipewire.nodes?.values) {
30 return false
31 }
32
33 for (const node of Pipewire.nodes.values) {
34 if (!node || (node.type & PwNodeType.AudioInStream) != PwNodeType.AudioInStream)
35 continue;
36
37 if (node.properties?.["stream.monitor"] === "true")
38 continue;
39
40 if (node.audio?.muted)
41 continue;
42
43 return true;
44 }
45
46 return false;
47 }
48
49 readonly property bool screensharingActive: {
50 if (!Pipewire.ready || !Pipewire.nodes?.values) {
51 return false
52 }
53
54 for (const node of Pipewire.nodes.values) {
55 if (!node || (node.type & PwNodeType.VideoInStream) != PwNodeType.VideoInStream)
56 continue;
57
58 return true;
59 }
60
61 return false;
62 }
63}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml b/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml
new file mode 100644
index 00000000..3c524955
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/WallpaperSelector.qml
@@ -0,0 +1,8 @@
1import Custom as Custom
2
3Custom.FileSelector {
4 id: root
5
6 directory: @wallpapers@
7 epoch: 72000000
8}
diff --git a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
new file mode 100644
index 00000000..f7b4ed96
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
@@ -0,0 +1,201 @@
1import QtQuick
2import QtQuick.Effects
3import Quickshell
4import Quickshell.Widgets
5import Quickshell.Services.SystemTray
6
7Item {
8 anchors.verticalCenter: parent.verticalCenter
9 width: systemTrayRow.childrenRect.width
10 height: parent.height
11 clip: true
12
13 Row {
14 id: systemTrayRow
15 anchors.centerIn: parent
16 width: childrenRect.width
17 height: parent.height
18 spacing: 0
19
20 Repeater {
21 model: ScriptModel {
22 values: {
23 var trayItems = Array.from(SystemTray.items.values).filter(item => item.status !== Status.Passive);
24 trayItems.sort((a, b) => a.category !== b.category ? b.category - a.category : a.id.localeCompare(b.id))
25 return trayItems;
26 }
27 }
28
29 delegate: Item {
30 id: trayItemWrapper
31
32 required property var modelData
33 required property int index
34
35 property var trayItem: modelData
36 property string iconSource: {
37 let icon = trayItem && trayItem.icon
38 if (typeof icon === 'string' || icon instanceof String) {
39 if (icon.includes("?path=")) {
40 const split = icon.split("?path=")
41 if (split.length !== 2)
42 return icon
43 const name = split[0]
44 const path = split[1]
45 const fileName = name.substring(
46 name.lastIndexOf("/") + 1)
47 return `file://${path}/${fileName}`
48 }
49 return icon
50 }
51 return ""
52 }
53
54 width: icon.width + 6
55 height: parent.height
56 anchors.verticalCenter: parent.verticalCenter
57
58 WrapperMouseArea {
59 id: trayItemArea
60
61 anchors.fill: parent
62 acceptedButtons: Qt.LeftButton | Qt.RightButton
63 hoverEnabled: true
64 cursorShape: trayItem.onlyMenu ? Qt.ArrowCursor : Qt.PointingHandCursor
65 onClicked: mouse => {
66 if (!trayItem)
67 return
68
69 if (mouse.button === Qt.LeftButton
70 && !trayItem.onlyMenu) {
71 trayItem.activate()
72 return
73 }
74
75 if (trayItem.hasMenu) {
76 var globalPos = mapToGlobal(0, 0)
77 var currentScreen = screen || Screen
78 var screenX = currentScreen.x || 0
79 var relativeX = globalPos.x - screenX
80 menuAnchor.menu = trayItem.menu
81 menuAnchor.anchor.window = bar
82 menuAnchor.anchor.rect = Qt.rect(
83 relativeX,
84 21,
85 parent.width, 1)
86 menuAnchor.open()
87 }
88 }
89
90 Rectangle {
91 anchors.fill: parent
92 color: {
93 if (trayItemArea.containsMouse)
94 return "#33808080";
95 return "transparent";
96 }
97
98 Item {
99 anchors.fill: parent
100
101 layer.enabled: true
102 layer.effect: MultiEffect {
103 colorization: 1
104 colorizationColor: "#555"
105 }
106
107 IconImage {
108 id: icon
109
110 anchors.centerIn: parent
111 implicitSize: 16
112 source: trayItemWrapper.iconSource
113 asynchronous: true
114 smooth: true
115 mipmap: true
116
117 layer.enabled: true
118 layer.effect: MultiEffect {
119 id: effect
120
121 brightness: 1
122 }
123 }
124 }
125 }
126 }
127
128 PopupWindow {
129 id: tooltip
130
131 property bool nextVisible: (trayItem.tooltipTitle || trayItem.tooltipDescription) && (trayItemArea.containsMouse || tooltipMouseArea.containsMouse) && !menuAnchor.visible
132
133 anchor {
134 item: trayItemArea
135 edges: Edges.Bottom
136 }
137
138 visible: false
139 onNextVisibleChanged: hangTimer.restart()
140
141 Timer {
142 id: hangTimer
143 interval: 100
144 onTriggered: tooltip.visible = tooltip.nextVisible
145 }
146
147 color: "black"
148
149 implicitWidth: Math.max(tooltipTitle.contentWidth, tooltipDescription.contentWidth) + 16
150 implicitHeight: (trayItem.tooltipTitle ? tooltipTitle.contentHeight : 0) + (trayItem.tooltipDescription ? tooltipDescription.contentHeight : 0) + 16
151
152 WrapperMouseArea {
153 id: tooltipMouseArea
154
155 hoverEnabled: true
156 enabled: true
157
158 margin: 4
159
160 anchors.fill: parent
161
162 Item {
163 anchors.fill: parent
164
165 Column {
166 anchors.centerIn: parent
167 Text {
168 id: tooltipTitle
169
170 enabled: trayItem.tooltipTitle
171
172 font.pointSize: 10
173 font.family: "Fira Sans"
174 font.bold: true
175 color: "white"
176
177 text: trayItem.tooltipTitle
178 }
179
180 Text {
181 id: tooltipDescription
182
183 enabled: trayItem.tooltipDescription
184
185 font.pointSize: 10
186 font.family: "Fira Sans"
187 color: "white"
188
189 text: trayItem.tooltipDescription
190 }
191 }
192 }
193 }
194 }
195 }
196 }
197 }
198 QsMenuAnchor {
199 id: menuAnchor
200 }
201}
diff --git a/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml
new file mode 100644
index 00000000..05a40dbc
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml
@@ -0,0 +1,97 @@
1import Quickshell
2import Quickshell.Io
3import Quickshell.Services.Pipewire
4import Quickshell.Services.Mpris
5import qs.Services
6import Custom as Custom
7import QtQml
8
9Scope {
10 id: root
11
12 SocketServer {
13 active: true
14 path: `${Quickshell.env("XDG_RUNTIME_DIR")}/shell.sock`
15 handler: Socket {
16 parser: SplitParser {
17 onRead: line => {
18 const command = (() => {
19 try {
20 return JSON.parse(line);
21 } catch (e) {
22 console.warn("UnixIPC: Failed to parse command:", line, e);
23 }
24 })();
25 if (!command)
26 return;
27
28 if (command.Volume)
29 root.onCommandVolume(command.Volume);
30 else if (command.Brightness)
31 root.onCommandBrightness(command.Brightness);
32 else if (command.LockSession)
33 Custom.Systemd.lockSession();
34 else if (command.Suspend)
35 Custom.Systemd.suspend();
36 else if (command.Hibernate)
37 Custom.Systemd.hibernate();
38 else if (command.Mpris)
39 root.onCommandMpris(command.Mpris);
40 else if (command.Notifications)
41 root.onCommandNotifications(command.Notifications);
42 else
43 console.warn("UnixIPC: Command not handled:", JSON.stringify(command));
44 }
45 }
46
47 onError: e => {
48 if (e == 1)
49 return;
50 console.warn("QLocalSocket::LocalSocketError", e);
51 }
52 }
53 }
54
55 PwObjectTracker {
56 objects: [ Pipewire.defaultAudioSink, Pipewire.defaultAudioSource ]
57 }
58 function onCommandVolume(command) {
59 if (command.muted === "toggle")
60 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
61 if (command.volume === "up")
62 Pipewire.defaultAudioSink.audio.volume += 0.02;
63 if (command.volume === "down")
64 Pipewire.defaultAudioSink.audio.volume -= 0.02;
65
66 if (command["mic-muted"] === "toggle")
67 Pipewire.defaultAudioSource.audio.muted = !Pipewire.defaultAudioSource.audio.muted;
68 }
69
70 function onCommandBrightness(command) {
71 if (command === "up")
72 Brightness.currBrightness += 0.02
73 if (command === "down")
74 Brightness.currBrightness -= 0.02
75 }
76
77 function onCommandMpris(command) {
78 if (command.PauseAll)
79 Array.from(MprisProxy.players).forEach(player => {
80 if (player.canPause && player.isPlaying)
81 player.pause();
82 });
83 }
84 Component.onCompleted: { (_ => {})(MprisProxy.players); }
85
86 function onCommandNotifications(command) {
87 if (command.DismissGroup && NotificationManager.active) {
88 if (NotificationManager.groups.length > 0)
89 for (const notif of [...NotificationManager.groups[0]])
90 notif.dismiss();
91 }
92 if (command.DismissAll && NotificationManager.active) {
93 for (const notif of [...NotificationManager.trackedNotifications.values])
94 notif.dismiss();
95 }
96 }
97}
diff --git a/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
new file mode 100644
index 00000000..653f4763
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
@@ -0,0 +1,163 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Services.Pipewire
5import Quickshell.Widgets
6
7Scope {
8 id: root
9
10 property string show: ""
11 property bool inhibited: true
12
13 PwObjectTracker {
14 objects: [ Pipewire.defaultAudioSink, Pipewire.defaultAudioSource ]
15 }
16
17 Connections {
18 enabled: Pipewire.defaultAudioSink
19 target: Pipewire.defaultAudioSink?.audio
20
21 function onVolumeChanged() {
22 root.show = "sink";
23 hideTimer.restart();
24 }
25 function onMutedChanged() {
26 root.show = "sink";
27 hideTimer.restart();
28 }
29 }
30
31 Connections {
32 enabled: Pipewire.defaultAudioSource
33 target: Pipewire.defaultAudioSource?.audio
34
35 function onVolumeChanged() {
36 root.show = "source";
37 hideTimer.restart();
38 }
39 function onMutedChanged() {
40 root.show = "source";
41 hideTimer.restart();
42 }
43 }
44
45 onShowChanged: {
46 if (show)
47 hideTimer.restart();
48 }
49
50 Timer {
51 id: hideTimer
52 interval: 1000
53 onTriggered: root.show = ""
54 }
55
56 Timer {
57 id: startInhibit
58 interval: 100
59 running: true
60 onTriggered: {
61 root.show = "";
62 root.inhibited = false;
63 }
64 }
65
66 LazyLoader {
67 active: root.show && !root.inhibited
68
69 Variants {
70 model: Quickshell.screens
71
72 delegate: Scope {
73 id: screenScope
74
75 required property var modelData
76
77 PanelWindow {
78 id: window
79
80 screen: screenScope.modelData
81
82 anchors.top: true
83 margins.top: screen.height / 2 - 50 + 3.5
84 exclusiveZone: 0
85 exclusionMode: ExclusionMode.Ignore
86
87 implicitWidth: 400
88 implicitHeight: 50
89
90 mask: Region {}
91
92 color: "transparent"
93
94 Rectangle {
95 anchors.fill: parent
96 color: Qt.rgba(0, 0, 0, 0.75)
97 }
98
99 RowLayout {
100 id: layout
101
102 anchors.centerIn: parent
103
104 height: 50 - 8*2
105 width: 400 - 8*2
106
107 MaterialDesignIcon {
108 id: volumeIcon
109
110 implicitWidth: parent.height
111 implicitHeight: parent.height
112
113 icon: {
114 if (root.show == "sink") {
115 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
116 return "volume-off";
117 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
118 return "volume-low";
119 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
120 return "volume-medium";
121 return "volume-high";
122 } else if (root.show == "source") {
123 if (!Pipewire.defaultAudioSource || Pipewire.defaultAudioSource.audio.muted)
124 return "microphone-off";
125 if (Pipewire.defaultAudioSource.audio.volume > 1)
126 return "microphone-plus";
127 return "microphone";
128 }
129 return "volume-high";
130 }
131 }
132
133 Rectangle {
134 Layout.fillWidth: true
135
136 implicitHeight: 10
137
138 color: "#50ffffff"
139
140 Rectangle {
141 anchors {
142 left: parent.left
143 top: parent.top
144 bottom: parent.bottom
145 }
146
147 color: Pipewire.defaultAudioSink?.audio.muted ? "#70ffffff" : "white"
148
149 implicitWidth: {
150 if (root.show == "sink")
151 return parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0);
152 else if (root.show == "source")
153 return parent.width * Math.min(1, (Pipewire.defaultAudioSource?.audio.volume ?? 0));
154 return 0;
155 }
156 }
157 }
158 }
159 }
160 }
161 }
162 }
163}
diff --git a/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml b/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml
new file mode 100644
index 00000000..4f85a900
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WallpaperBackground.qml
@@ -0,0 +1,89 @@
1import QtQuick
2import Quickshell
3import qs.Services
4
5Item {
6 id: root
7
8 anchors.fill: parent
9
10 required property string screen
11
12 property Img current: one
13 property string source: selector.selected
14
15 WallpaperSelector {
16 id: selector
17 seed: screen
18 }
19
20 onSourceChanged: {
21 if (!source)
22 current = null;
23 else if (current === one)
24 two.update()
25 else
26 one.update()
27 }
28
29 Img { id: one }
30 Img { id: two }
31
32 component Img: Image {
33 id: img
34
35 function update() {
36 source = root.source || ""
37 }
38
39 anchors.fill: parent
40 fillMode: Image.PreserveAspectCrop
41 smooth: true
42 asynchronous: true
43 cache: false
44
45 opacity: 0
46
47 onStatusChanged: {
48 if (status === Image.Ready) {
49 root.current = this
50 }
51 }
52
53 states: State {
54 name: "visible"
55 when: root.current === img
56
57 PropertyChanges {
58 img.opacity: 1
59 }
60 StateChangeScript {
61 name: "unloadOther"
62 script: {
63 if (img === one)
64 two.source = ""
65 if (img === two)
66 one.source = ""
67 }
68 }
69 }
70
71 transitions: Transition {
72 SequentialAnimation {
73 NumberAnimation {
74 target: img
75 properties: "opacity"
76 duration: {
77 if (img === one && two.source == "" || img === two && one.source == "")
78 return 0;
79 return 5000;
80 }
81 easing.type: Easing.OutCubic
82 }
83 ScriptAction {
84 scriptName: "unloadOther"
85 }
86 }
87 }
88 }
89}
diff --git a/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml
new file mode 100644
index 00000000..0512ff51
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WaylandInhibitorWidget.qml
@@ -0,0 +1,56 @@
1import Quickshell
2import QtQuick
3import Quickshell.Widgets
4import Quickshell.Wayland
5import qs.Services
6
7Item {
8 id: root
9
10 required property var window
11
12 width: icon.width + 8
13 height: parent.height
14 anchors.verticalCenter: parent.verticalCenter
15
16 IdleInhibitor {
17 id: inhibitor
18 enabled: InhibitorState.waylandIdleInhibited
19 window: root.window
20 }
21
22 WrapperMouseArea {
23 id: widgetMouseArea
24
25 anchors.fill: parent
26
27 hoverEnabled: true
28 cursorShape: Qt.PointingHandCursor
29
30 onClicked: InhibitorState.waylandIdleInhibited = !InhibitorState.waylandIdleInhibited
31
32 Rectangle {
33 anchors.fill: parent
34 color: {
35 if (widgetMouseArea.containsMouse) {
36 return "#33808080";
37 }
38 return "transparent";
39 }
40
41 Item {
42 anchors.fill: parent
43
44 MaterialDesignIcon {
45 id: icon
46
47 implicitSize: 14
48 anchors.centerIn: parent
49
50 icon: inhibitor.enabled ? "eye" : "eye-off"
51 color: inhibitor.enabled ? "white" : "#555"
52 }
53 }
54 }
55 }
56}
diff --git a/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
new file mode 100644
index 00000000..3ae94346
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
@@ -0,0 +1,204 @@
1import Quickshell
2import QtQuick
3import qs.Services
4import Quickshell.Widgets
5import QtQuick.Layouts
6
7Row {
8 id: workspaces
9
10 required property var screen
11
12 property var ignoreWorkspaces: @ignore_workspaces@
13
14 height: parent.height
15 anchors.verticalCenter: parent.verticalCenter
16 spacing: 0
17
18 Repeater {
19 model: ScriptModel {
20 values: {
21 let currWorkspaces = NiriService.workspaces;
22 const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces);
23 currWorkspaces = currWorkspaces.filter(ws => ws.output == workspaces.screen.name).filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name));
24 currWorkspaces.sort((a, b) => {
25 if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x)
26 return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x
27 if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y)
28 return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y
29 return a.idx - b.idx;
30 });
31 return currWorkspaces;
32 }
33 }
34
35 Item {
36 id: wsItem
37
38 property var workspaceData: modelData
39
40 width: wsLabel.contentWidth + 8
41 height: parent.height
42 anchors.verticalCenter: parent.verticalCenter
43
44 WrapperMouseArea {
45 id: mouseArea
46
47 anchors.fill: parent
48
49 hoverEnabled: true
50 cursorShape: Qt.PointingHandCursor
51 enabled: true
52 onClicked: {
53 NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {});
54 }
55
56 Rectangle {
57 anchors.fill: parent
58
59 color: {
60 if (mouseArea.containsMouse) {
61 return "#33808080";
62 }
63 return "transparent";
64 }
65
66 Text {
67 id: wsLabel
68
69 anchors.centerIn: parent
70
71 font.pointSize: 10
72 font.family: "Fira Sans"
73 color: {
74 if (workspaceData.is_active)
75 return "#23fd00";
76 if (workspaceData.active_window_id === null)
77 return "#555";
78 return "white";
79 }
80
81 text: workspaceData.name ? workspaceData.name : workspaceData.idx
82 }
83 }
84 }
85
86 PopupWindow {
87 id: tooltip
88
89 property bool nextVisible: (mouseArea.containsMouse || tooltipMouseArea.containsMouse) && [...windowsModel.values].length > 0
90
91 anchor {
92 item: mouseArea
93 edges: Edges.Bottom | Edges.Left
94 }
95 visible: false
96
97 onNextVisibleChanged: hangTimer.restart()
98
99 Timer {
100 id: hangTimer
101 interval: 100
102 onTriggered: tooltip.visible = tooltip.nextVisible
103 }
104
105 implicitWidth: tooltipContent.implicitWidth
106 implicitHeight: tooltipContent.implicitHeight
107 color: "black"
108
109 WrapperMouseArea {
110 id: tooltipMouseArea
111
112 hoverEnabled: true
113 enabled: true
114
115 anchors.fill: parent
116
117 WrapperItem {
118 id: tooltipContent
119
120 margin: 0
121
122 ColumnLayout {
123 spacing: 0
124
125 Repeater {
126 model: ScriptModel {
127 id: windowsModel
128
129 values: {
130 let currWindows = Array.from(NiriService.windows).filter(win => win.workspace_id == wsItem.workspaceData.id);
131 currWindows.sort((a, b) => {
132 if (a.is_floating !== b.is_floating)
133 return b.is_floating - a.is_floating;
134 if (a.layout.tile_pos_in_workspace_view?.[0] !== b.layout.tile_pos_in_workspace_view?.[0])
135 return a.layout.tile_pos_in_workspace_view?.[0] - b.layout.tile_pos_in_workspace_view?.[0]
136 if (a.layout.tile_pos_in_workspace_view?.[1] !== b.layout.tile_pos_in_workspace_view?.[1])
137 return a.layout.tile_pos_in_workspace_view?.[1] - b.layout.tile_pos_in_workspace_view?.[1]
138 if (a.layout.pos_in_scrolling_layout?.[0] !== b.layout.pos_in_scrolling_layout?.[0])
139 return a.layout.pos_in_scrolling_layout?.[0] - b.layout.pos_in_scrolling_layout?.[0]
140 if (a.layout.pos_in_scrolling_layout?.[1] !== b.layout.pos_in_scrolling_layout?.[1])
141 return a.layout.pos_in_scrolling_layout?.[1] - b.layout.pos_in_scrolling_layout?.[1]
142 if (a.app_id !== b.app_id)
143 return a.app_id.localeCompare(b.app_id);
144
145 return a.title.localeCompare(b.title);
146 });
147 return currWindows;
148 }
149 }
150
151 WrapperMouseArea {
152 id: windowMouseArea
153
154 required property int index
155 required property var modelData
156 property var windowData: modelData
157
158 hoverEnabled: true
159 cursorShape: Qt.PointingHandCursor
160 enabled: true
161
162 Layout.fillWidth: true
163
164 onClicked: {
165 NiriService.sendCommand({ "Action": { "FocusWindow": { "id": windowData.id } } }, _ => {})
166 }
167
168 WrapperRectangle {
169 color: windowMouseArea.containsMouse ? "#33808080" : "transparent";
170
171 WrapperItem {
172 rightMargin: 8
173 leftMargin: 8
174 topMargin: windowMouseArea.index == 0 ? 8 : 4
175 bottomMargin: windowMouseArea.index == windowsModel.values.length - 1 ? 8 : 4
176
177 Text {
178 id: windowLabel
179
180 font.pointSize: 10
181 font.family: "Fira Sans"
182 color: {
183 if (windowData.is_focused)
184 return "#23fd00";
185 if (NiriService.workspaces.find(ws => ws.id == windowData.workspace_id)?.active_window_id == windowData.id)
186 return "white";
187 return "#555";
188 }
189
190 text: windowData.title
191
192 horizontalAlignment: Text.AlignLeft
193 }
194 }
195 }
196 }
197 }
198 }
199 }
200 }
201 }
202 }
203 }
204}
diff --git a/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml
new file mode 100644
index 00000000..04bcc581
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml
@@ -0,0 +1,120 @@
1import QtQml
2import Quickshell
3import Quickshell.Io
4import QtQuick
5import Quickshell.Widgets
6
7Item {
8 id: root
9
10 required property string command
11 property var state: null
12
13 height: parent.height
14 width: label.contentWidth + 8
15 anchors.verticalCenter: parent.verticalCenter
16
17 Process {
18 id: process
19 running: true
20 command: [ @worktime@, root.command, "--waybar" ]
21 stdout: StdioCollector {
22 id: processCollector
23 onStreamFinished: {
24 try {
25 root.state = JSON.parse(processCollector.text);
26 } catch (e) {
27 console.warn("Worktime: Failed to parse output:", processCollector.text, e);
28 }
29 }
30 }
31 }
32
33 Timer {
34 running: true
35 interval: 60
36 repeat: true
37 onTriggered: process.running = true
38 }
39
40 WrapperMouseArea {
41 id: mouseArea
42
43 anchors.fill: parent
44
45 enabled: true
46 hoverEnabled: true
47
48 Item {
49 anchors.fill: parent
50
51 Text {
52 id: label
53
54 anchors.centerIn: parent
55
56 visible: root.state?.text ?? false
57 text: root.state?.text ?? ""
58
59 font.pointSize: 10
60 font.family: "Fira Sans"
61 color: {
62 if (root.state?.class == "running")
63 return "white";
64 if (root.state?.class == "over")
65 return "#f28a21";
66 return "#555";
67 }
68 }
69 }
70 }
71
72 PopupWindow {
73 id: tooltip
74
75 property bool nextVisible: Boolean(root.state?.tooltip ?? false) && (mouseArea.containsMouse || tooltipMouseArea.containsMouse)
76
77 anchor {
78 item: mouseArea
79 edges: Edges.Bottom | Edges.Left
80 }
81 visible: false
82
83 onNextVisibleChanged: hangTimer.restart()
84
85 Timer {
86 id: hangTimer
87 interval: 100
88 onTriggered: tooltip.visible = tooltip.nextVisible
89 }
90
91 implicitWidth: tooltipText.contentWidth + 16
92 implicitHeight: tooltipText.contentHeight + 16
93 color: "black"
94
95 WrapperMouseArea {
96 id: tooltipMouseArea
97
98 enabled: true
99 hoverEnabled: true
100
101 anchors.fill: parent
102
103 Item {
104 anchors.fill: parent
105
106 Text {
107 id: tooltipText
108
109 anchors.centerIn: parent
110
111 font.pointSize: 10
112 font.family: "Fira Sans"
113 color: "white"
114
115 text: root.state?.tooltip ?? ""
116 }
117 }
118 }
119 }
120}
diff --git a/accounts/gkleen@sif/shell/quickshell/displaymanager.qml b/accounts/gkleen@sif/shell/quickshell/displaymanager.qml
new file mode 100644
index 00000000..b452c03d
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/displaymanager.qml
@@ -0,0 +1,115 @@
1//@ pragma UseQApplication
2
3import Quickshell
4import Quickshell.Wayland
5import Quickshell.Io
6import Quickshell.Services.Greetd
7import QtQml
8
9
10ShellRoot {
11 id: displaymanager
12
13 settings.watchFiles: false
14
15 property string currentText: ""
16 property string username: @username@
17 property list<string> command: @niri_session@
18 property list<var> messages: []
19 property bool responseRequired: false
20 property bool responseVisible: false
21
22 signal startAuth()
23
24 onStartAuth: {
25 if (Greetd.state !== GreetdState.Inactive)
26 Greetd.cancelSession();
27 displaymanager.messages = [];
28 Greetd.createSession(displaymanager.username);
29 }
30
31 Connections {
32 target: Greetd
33 function onStateChanged() {
34 console.log("greetd state: ", GreetdState.toString(Greetd.state));
35 if (Greetd.state === GreetdState.ReadyToLaunch)
36 Greetd.launch(displaymanager.command);
37 }
38 function onAuthMessage(message: string, error: bool, responseRequired: bool, echoResponse: bool) {
39 displaymanager.responseVisible = echoResponse;
40 displaymanager.responseRequired = responseRequired;
41 displaymanager.messages = Array.from(displaymanager.messages).concat([{ "text": message, "error": error }]);
42 }
43 function onAuthFailure(message: string) {
44 displaymanager.responseRequired = false;
45 displaymanager.messages = Array.from(displaymanager.messages).concat([{ "text": message, "error": true }]);
46 }
47 }
48
49 Component.onCompleted: {
50 if (Greetd.state !== GreetdState.Inactive)
51 Greetd.cancelSession();
52 }
53
54 Variants {
55 model: Quickshell.screens
56
57 delegate: Scope {
58 id: screenScope
59
60 required property var modelData
61
62 PanelWindow {
63 color: "black"
64
65 screen: screenScope.modelData
66
67 WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
68
69 anchors.top: true
70 anchors.bottom: true
71 anchors.left: true
72 anchors.right: true
73
74 LockSurface {
75 id: surfaceContent
76
77 screen: screenScope.modelData
78
79 onCurrentTextChanged: displaymanager.currentText = currentText
80 Connections {
81 target: displaymanager
82 function onCurrentTextChanged() { surfaceContent.currentText = displaymanager.currentText; }
83 function onMessagesChanged() { surfaceContent.messages = Array.from(displaymanager.messages); }
84 function onResponseRequiredChanged() { surfaceContent.responseRequired = displaymanager.responseRequired; }
85 function onResponseVisibleChanged() { surfaceContent.responseVisible = displaymanager.responseVisible; }
86 }
87
88 onResponse: responseText => Greetd.respond(responseText);
89 Connections {
90 target: Greetd
91 function onStateChanged() {
92 if (Greetd.state === GreetdState.Authenticating) {
93 surfaceContent.authRunning = true;
94 } else {
95 surfaceContent.authRunning = false;
96 }
97 }
98 }
99
100 onAuthRunningChanged: {
101 if (surfaceContent.authRunning && Greetd.state !== GreetdState.Authenticating)
102 displaymanager.startAuth();
103 }
104 Component.onCompleted: {
105 surfaceContent.authRunning = Greetd.state === GreetdState.Authenticating
106 surfaceContent.messages = Array.from(displaymanager.messages);
107 surfaceContent.responseVisible = displaymanager.responseVisible;
108 surfaceContent.responseRequired = displaymanager.responseRequired;
109 surfaceContent.currentText = displaymanager.currentText;
110 }
111 }
112 }
113 }
114 }
115}
diff --git a/accounts/gkleen@sif/shell/quickshell/shell.qml b/accounts/gkleen@sif/shell/quickshell/shell.qml
new file mode 100644
index 00000000..fb8b16dc
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/shell.qml
@@ -0,0 +1,53 @@
1//@ pragma UseQApplication
2
3import Quickshell
4import Quickshell.Wayland
5
6ShellRoot {
7 settings.watchFiles: false
8
9 Variants {
10 model: Quickshell.screens
11
12 delegate: Scope {
13 id: screenScope
14
15 required property var modelData
16
17 PanelWindow {
18 id: bgWindow
19
20 screen: screenScope.modelData
21
22 WlrLayershell.layer: WlrLayer.Background
23 WlrLayershell.namespace: "background"
24 exclusionMode: ExclusionMode.Ignore
25
26 anchors.top: true
27 anchors.bottom: true
28 anchors.left: true
29 anchors.right: true
30
31 color: "black"
32
33 WallpaperBackground {
34 screen: bgWindow.screen.name
35 }
36 }
37
38 Bar {
39 modelData: screenScope.modelData
40 }
41 }
42 }
43
44 Lockscreen {}
45 NiriIdle {}
46
47 VolumeOSD {}
48 BrightnessOSD {}
49
50 NotificationDisplay {}
51
52 UnixIPC {}
53}
diff --git a/accounts/gkleen@sif/ssh-hosts.nix b/accounts/gkleen@sif/ssh-hosts.nix
index 107f1e76..a250509b 100644
--- a/accounts/gkleen@sif/ssh-hosts.nix
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -1,5 +1,12 @@
1{ pkgs, ... }: 1{ lib, pkgs, ... }:
2{ 2let
3 autosshProxyPorts = {
4 "ssh.math.lmu.de" = 8118;
5 "mathw0h" = 8122;
6 "mathw0e" = 8124;
7 };
8 autosshProxy = host: "${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=${toString autosshProxyPorts.${host}}";
9in {
3 "git.ymir" = 10 "git.ymir" =
4 { hostname = "ymir.yggdrasil.li"; 11 { hostname = "ymir.yggdrasil.li";
5 user = "gitolite"; 12 user = "gitolite";
@@ -290,15 +297,15 @@
290 }; 297 };
291 "mathw0d" = 298 "mathw0d" =
292 { hostname = "mathw0d.mathinst.loc"; 299 { hostname = "mathw0d.mathinst.loc";
293 proxyJump = "mathw0h"; 300 proxyCommand = autosshProxy "mathw0h";
294 }; 301 };
295 "mathw0e" = 302 "mathw0e" =
296 { hostname = "mathw0e.mathinst.loc"; 303 { hostname = "mathw0e.mathinst.loc";
297 proxyJump = "mathw0h"; 304 proxyCommand = autosshProxy "mathw0h";
298 }; 305 };
299 "mathw0f" = 306 "mathw0f" =
300 { hostname = "mathw0f.mathinst.loc"; 307 { hostname = "mathw0f.mathinst.loc";
301 proxyJump = "mathw0h"; 308 proxyCommand = autosshProxy "mathw0h";
302 }; 309 };
303 "mathw0g" = 310 "mathw0g" =
304 { hostname = "mathw0g.mathinst.loc"; 311 { hostname = "mathw0g.mathinst.loc";
@@ -306,8 +313,8 @@
306 "mathw0h" = 313 "mathw0h" =
307 { hostname = "mathw0h.mathinst.loc"; 314 { hostname = "mathw0h.mathinst.loc";
308 }; 315 };
309 "proxy.mathw0g" = 316 "proxy.ssh.math.lmu.de" =
310 { hostname = "mathw0g.mathinst.loc"; 317 { hostname = "ssh.math.lmu.de";
311 extraOptions = { 318 extraOptions = {
312 ControlPath = "none"; 319 ControlPath = "none";
313 ExitOnForwardFailure = "yes"; 320 ExitOnForwardFailure = "yes";
@@ -317,7 +324,17 @@
317 }; 324 };
318 "proxy.mathw0h" = 325 "proxy.mathw0h" =
319 { hostname = "mathw0h.mathinst.loc"; 326 { hostname = "mathw0h.mathinst.loc";
320 proxyJump = "proxy.mathw0g"; 327 proxyCommand = autosshProxy "ssh.math.lmu.de";
328 extraOptions = {
329 ControlPath = "none";
330 ExitOnForwardFailure = "yes";
331 ServerAliveCountMax = "15";
332 ServerAliveInterval = "2";
333 };
334 };
335 "proxy.mathw0e" =
336 { hostname = "mathw0e.mathinst.loc";
337 proxyCommand = autosshProxy "mathw0h";
321 extraOptions = { 338 extraOptions = {
322 ControlPath = "none"; 339 ControlPath = "none";
323 ExitOnForwardFailure = "yes"; 340 ExitOnForwardFailure = "yes";
@@ -327,7 +344,7 @@
327 }; 344 };
328 "vrt-kvm06" = 345 "vrt-kvm06" =
329 { hostname = "vrt-kvm06"; 346 { hostname = "vrt-kvm06";
330 proxyJump = "mathw0e"; 347 proxyCommand = autosshProxy "mathw0e";
331 user = "root"; 348 user = "root";
332 extraOptions = { 349 extraOptions = {
333 PasswordAuthentication = "yes"; 350 PasswordAuthentication = "yes";
@@ -336,7 +353,7 @@
336 }; 353 };
337 "vrt-kvm05" = 354 "vrt-kvm05" =
338 { hostname = "vrt-kvm05"; 355 { hostname = "vrt-kvm05";
339 proxyJump = "mathw0e"; 356 proxyCommand = autosshProxy "mathw0e";
340 user = "root"; 357 user = "root";
341 extraOptions = { 358 extraOptions = {
342 PasswordAuthentication = "yes"; 359 PasswordAuthentication = "yes";
@@ -345,7 +362,7 @@
345 }; 362 };
346 "vrt-kvm04" = 363 "vrt-kvm04" =
347 { hostname = "vrt-kvm04"; 364 { hostname = "vrt-kvm04";
348 proxyJump = "mathw0e"; 365 proxyCommand = autosshProxy "mathw0e";
349 user = "root"; 366 user = "root";
350 extraOptions = { 367 extraOptions = {
351 PasswordAuthentication = "yes"; 368 PasswordAuthentication = "yes";
@@ -354,7 +371,7 @@
354 }; 371 };
355 "vrt-kvm02" = 372 "vrt-kvm02" =
356 { hostname = "vrt-kvm02"; 373 { hostname = "vrt-kvm02";
357 proxyJump = "mathw0e"; 374 proxyCommand = autosshProxy "mathw0e";
358 user = "root"; 375 user = "root";
359 extraOptions = { 376 extraOptions = {
360 PasswordAuthentication = "yes"; 377 PasswordAuthentication = "yes";
@@ -363,7 +380,7 @@
363 }; 380 };
364 "vrt-kvm03" = 381 "vrt-kvm03" =
365 { hostname = "vrt-kvm03"; 382 { hostname = "vrt-kvm03";
366 proxyJump = "mathw0e"; 383 proxyCommand = autosshProxy "mathw0e";
367 user = "root"; 384 user = "root";
368 extraOptions = { 385 extraOptions = {
369 PasswordAuthentication = "yes"; 386 PasswordAuthentication = "yes";
@@ -372,7 +389,7 @@
372 }; 389 };
373 "vrt-kvm01" = 390 "vrt-kvm01" =
374 { hostname = "vrt-kvm01"; 391 { hostname = "vrt-kvm01";
375 proxyJump = "mathw0e"; 392 proxyCommand = autosshProxy "mathw0e";
376 user = "root"; 393 user = "root";
377 extraOptions = { 394 extraOptions = {
378 PasswordAuthentication = "yes"; 395 PasswordAuthentication = "yes";
@@ -381,39 +398,44 @@
381 }; 398 };
382 "tts-www01" = 399 "tts-www01" =
383 { hostname = "tts-www01.mathinst.loc"; 400 { hostname = "tts-www01.mathinst.loc";
384 proxyJump = "mathw0h"; 401 proxyCommand = autosshProxy "mathw0h";
385 user = "root"; 402 user = "root";
386 }; 403 };
387 "vpn-wg01" = 404 "vpn-wg01" =
388 { hostname = "vpn-wg01.mathinst.loc"; 405 { hostname = "vpn-wg01.mathinst.loc";
389 proxyJump = "mathw0h"; 406 proxyCommand = autosshProxy "mathw0h";
390 user = "root"; 407 user = "root";
391 }; 408 };
392 "repo-apt01" = 409 "repo-apt01" =
393 { hostname = "repo-apt01.mathinst.loc"; 410 { hostname = "repo-apt01.mathinst.loc";
394 proxyJump = "mathw0h"; 411 proxyCommand = autosshProxy "mathw0h";
395 user = "root"; 412 user = "root";
396 }; 413 };
397 "ldap-lmumr01" = 414 "ldap-lmumr01" =
398 { hostname = "ldap-lmumr01.mathinst.loc"; 415 { hostname = "ldap-lmumr01.mathinst.loc";
399 proxyJump = "mathw0h"; 416 proxyCommand = autosshProxy "mathw0h";
400 user = "root"; 417 user = "root";
401 }; 418 };
402 "mail-mi01" = 419 "mail-mi01" =
403 { hostname = "mail-mi01.mathinst.loc"; 420 { hostname = "mail-mi01.mathinst.loc";
404 proxyJump = "mathw0h"; 421 proxyCommand = autosshProxy "mathw0h";
405 }; 422 };
406 "mail-www02" = 423 "mail-www02" =
407 { hostname = "mail-www02.mathinst.loc"; 424 { hostname = "mail-www02.mathinst.loc";
408 proxyJump = "mathw0h"; 425 proxyCommand = autosshProxy "mathw0h";
409 }; 426 };
410 "dpl-fai01" = 427 "dpl-fai01" =
411 { hostname = "dpl-fai01.mathinst.loc"; 428 { hostname = "dpl-fai01.mathinst.loc";
412 user = "root"; 429 user = "root";
413 }; 430 };
431 "dpl-fai02" =
432 { hostname = "dpl-fai02.mathinst.loc";
433 user = "root";
434 proxyJump = "mgmt01";
435 };
414 "math05" = 436 "math05" =
415 { hostname = "math05.mathinst.loc"; 437 { hostname = "math05.mathinst.loc";
416 proxyJump = "mathw0h"; 438 proxyCommand = autosshProxy "mathw0h";
417 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1"; 439 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1";
418 }; 440 };
419 "switch01" = 441 "switch01" =
@@ -439,20 +461,20 @@
439 }; 461 };
440 "www-mi01" = 462 "www-mi01" =
441 { hostname = "www-mi01.mathinst.loc"; 463 { hostname = "www-mi01.mathinst.loc";
442 proxyJump = "mathw0h"; 464 proxyCommand = autosshProxy "mathw0h";
443 }; 465 };
444 "cip04" = 466 "cip04" =
445 { hostname = "cip04.cipmath.loc"; 467 { hostname = "cip04.cipmath.loc";
446 proxyJump = "mathw0h"; 468 proxyCommand = autosshProxy "mathw0h";
447 }; 469 };
448 "mgmt-cls01" = 470 "mgmt-cls01" =
449 { user = "root"; 471 { user = "root";
450 hostname = "mgmt-cls01.cipmath.loc"; 472 hostname = "mgmt-cls01.cipmath.loc";
451 proxyJump = "ssh.math.lmu.de"; 473 proxyCommand = autosshProxy "ssh.math.lmu.de";
452 }; 474 };
453 "mgmt01" = 475 "mgmt01" =
454 { hostname = "mgmt01.mathinst.loc"; 476 { hostname = "mgmt01.mathinst.loc";
455 proxyJump = "mathw0h"; 477 proxyCommand = autosshProxy "mathw0h";
456 user = "root"; 478 user = "root";
457 }; 479 };
458 "ssh-lb01" = 480 "ssh-lb01" =
@@ -471,17 +493,17 @@
471 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; }; 493 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; };
472 "math0d" = 494 "math0d" =
473 { hostname = "math0d.mathinst.loc"; 495 { hostname = "math0d.mathinst.loc";
474 proxyJump = "mathw0h"; 496 proxyCommand = autosshProxy "mathw0h";
475 }; 497 };
476 "dhcp01" = 498 "dhcp01" =
477 { hostname = "dhcp01.mathinst.loc"; 499 { hostname = "dhcp01.mathinst.loc";
478 user = "root"; 500 user = "root";
479 proxyJump = "mathw0h"; 501 proxyCommand = autosshProxy "mathw0h";
480 }; 502 };
481 "dhcp02" = 503 "dhcp02" =
482 { hostname = "dhcp02.mathinst.loc"; 504 { hostname = "dhcp02.mathinst.loc";
483 user = "root"; 505 user = "root";
484 proxyJump = "mathw0h"; 506 proxyCommand = autosshProxy "mathw0h";
485 }; 507 };
486 "cc-gpu-l01" = 508 "cc-gpu-l01" =
487 { hostname = "cc-gpu-l01.mathinst.loc"; 509 { hostname = "cc-gpu-l01.mathinst.loc";
@@ -546,7 +568,7 @@
546 user = "root"; 568 user = "root";
547 }; 569 };
548 "nas*" = 570 "nas*" =
549 { proxyJump = "mathw0e"; 571 { proxyCommand = autosshProxy "mathw0e";
550 user = "admin"; 572 user = "admin";
551 extraOptions = { 573 extraOptions = {
552 PasswordAuthentication = "yes"; 574 PasswordAuthentication = "yes";
@@ -554,9 +576,4 @@
554 HostKeyAlgorithms = "+ecdsa-sha2-nistp256"; 576 HostKeyAlgorithms = "+ecdsa-sha2-nistp256";
555 }; 577 };
556 }; 578 };
557 "game01" =
558 { hostname = "game01.yggdrasil.li";
559 user = "factorio";
560 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
561 };
562} 579}
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 33bf7ef2..e601b49c 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,22 +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" ];
127 }; 125 BindsTo = [ "graphical-session.target" ];
128 };
129 dunst = {
130 Service = {
131 ExecStart = lib.mkForce "${cfg.services.dunst.package}/bin/dunst";
132 Restart = "always";
133 };
134 Install = {
135 WantedBy = ["graphical-session.target"];
136 }; 126 };
137 }; 127 };
138 keepassxc = { 128 keepassxc = {
@@ -144,8 +134,8 @@ in {
144 Environment = [ "QT_QPA_PLATFORM=wayland" ]; 134 Environment = [ "QT_QPA_PLATFORM=wayland" ];
145 }; 135 };
146 Unit = { 136 Unit = {
147 Requires = ["graphical-session-pre.target"]; 137 After = [ "graphical-session.target" ];
148 After = ["graphical-session-pre.target"]; 138 BindsTo = [ "graphical-session.target" ];
149 }; 139 };
150 }; 140 };
151 mpris-proxy = { 141 mpris-proxy = {
@@ -154,7 +144,7 @@ in {
154 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy"; 144 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy";
155 Install.WantedBy = [ "default.target" ]; 145 Install.WantedBy = [ "default.target" ];
156 }; 146 };
157 "autossh-socks@proxy.mathw0h:8119" = { 147 "autossh-socks@proxy.ssh.math.lmu.de:8119" = {
158 Service = { 148 Service = {
159 Type = "notify"; 149 Type = "notify";
160 NotifyAccess = "all"; 150 NotifyAccess = "all";
@@ -162,7 +152,7 @@ in {
162 Restart = "always"; 152 Restart = "always";
163 RestartSec = "23s"; 153 RestartSec = "23s";
164 ExecStart = "${autossh-socks-script} \"%I\""; 154 ExecStart = "${autossh-socks-script} \"%I\"";
165 Environment = [ "SSHPASS_SECRET=gkleen@mathw0g.math.lmu.de" ]; 155 Environment = [ "SSHPASS_SECRET=gkleen@ssh.math.lmu.de" ];
166 }; 156 };
167 Unit = { 157 Unit = {
168 StopWhenUnneeded = true; 158 StopWhenUnneeded = true;
@@ -183,44 +173,58 @@ in {
183 StopWhenUnneeded = true; 173 StopWhenUnneeded = true;
184 }; 174 };
185 }; 175 };
186 swayidle = { 176 "autossh-socks@proxy.mathw0h:8123" = {
187 Service = { 177 Service = {
188 RuntimeDirectory = "swayidle"; 178 Type = "notify";
189 }; 179 NotifyAccess = "all";
190 }; 180 WorkingDirectory = "~";
191 psi-notify = { 181 Restart = "always";
192 Install = { 182 RestartSec = "23s";
193 WantedBy = ["graphical-session.target"]; 183 ExecStart = "${autossh-socks-script} \"%I\"";
184 Environment = [ "SSHPASS_SECRET=gkleen@mathw0h.mathinst.loc" ];
194 }; 185 };
195 Unit = { 186 Unit = {
196 Requires = ["graphical-session-pre.target"]; 187 StopWhenUnneeded = true;
197 After = ["graphical-session-pre.target"]; 188 StartLimitInterval = "180s";
189 StartLimitBurst = 7;
198 }; 190 };
191 };
192 "autossh-socks@proxy.mathw0e:8125" = {
199 Service = { 193 Service = {
200 ExecStart = lib.getExe pkgs.psi-notify;
201 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
202 Type = "notify"; 194 Type = "notify";
195 NotifyAccess = "all";
196 WorkingDirectory = "~";
203 Restart = "always"; 197 Restart = "always";
204 WatchdogSec = "2s"; 198 RestartSec = "23s";
199 ExecStart = "${autossh-socks-script} \"%I\"";
200 Environment = [ "SSHPASS_SECRET=gkleen@mathw0e.mathinst.loc" ];
201 };
202 Unit = {
203 StopWhenUnneeded = true;
204 StartLimitInterval = "180s";
205 StartLimitBurst = 7;
205 }; 206 };
206 }; 207 };
207 polkit-gnome-authentication-agent-1 = { 208 psi-notify = {
208 Install = { 209 Install = {
209 WantedBy = ["graphical-session.target"]; 210 WantedBy = ["graphical-session.target"];
210 }; 211 };
211 Unit = { 212 Unit = {
212 PartOf = ["graphical-session.target"]; 213 After = [ "graphical-session.target" ];
213 Requires = ["graphical-session-pre.target"]; 214 PartOf = [ "graphical-session.target" ];
214 After = ["graphical-session-pre.target"];
215 }; 215 };
216 Service = { 216 Service = {
217 ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; 217 ExecStart = lib.getExe pkgs.psi-notify;
218 Restart = "on-failure"; 218 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
219 Type = "notify";
220 Restart = "always";
221 WatchdogSec = "2s";
219 }; 222 };
220 }; 223 };
221 gtklock = { 224 gtklock = {
222 Unit = { 225 Unit = {
223 Requisite = ["graphical-session.target"]; 226 Requisite = ["graphical-session.target"];
227 After = [ "graphical-session.target" ];
224 PartOf = ["graphical-session.target"]; 228 PartOf = ["graphical-session.target"];
225 }; 229 };
226 Service = { 230 Service = {
@@ -228,53 +232,55 @@ in {
228 RuntimeDirectory = "gtklock"; 232 RuntimeDirectory = "gtklock";
229 CacheDirectory = "gtklock"; 233 CacheDirectory = "gtklock";
230 ExecStartPre = [ 234 ExecStartPre = [
231 "${pkgs.libsForQt5.qt5.qttools.bin}/bin/qdbus org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases" 235 "-${lib.getExe' pkgs.libsForQt5.qt5.qttools.bin "qdbus"} org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases"
232 "${config.systemd.package}/bin/systemctl --user stop gpg-agent.service" 236 "-${lib.getExe' config.systemd.package "systemctl"} --user stop gpg-agent.service"
233 (pkgs.writeShellScript "generate-css" '' 237 "-${lib.getExe pkgs.playerctl} -a pause"
234 set -x 238 "-${lib.getExe (pkgs.writeShellApplication {
235 export PATH="${lib.makeBinPath [cfg.programs.wpaperd.package pkgs.jq pkgs.coreutils pkgs.imagemagick pkgs.findutils]}:$PATH" 239 name = "generate-css";
240 runtimeInputs = with pkgs; [cfg.services.wpaperd.package jq coreutils imagemagick findutils];
241 text = ''
242 declare -A monitors
243 monitors=()
244 while IFS= read -r entry; do
245 path=$(jq -r ".path" <<<"$entry")
246 [[ -z "$path" || ! -f "$path" ]] && continue
247 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}"
248 monitor=$(jq -r ".display" <<<"$entry")
249 if [[ ! -f "$blurred_path" ]]; then
250 mkdir -p "$(dirname "$blurred_path")"
251 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" &
252 fi
253 monitors+=([$monitor]="$blurred_path")
254 done < <(wpaperctl all-wallpapers -j | jq -c ".[]")
255 # wait
236 256
237 declare -A monitors 257 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" ''
238 monitors=() 258 #window-box {
239 while IFS= read -r entry; do 259 padding: 64px;
240 path=$(jq -r ".path" <<<"$entry") 260 /* border: 1px solid black; */
241 [[ -z "$path" || ! -f "$path" ]] && continue 261 border-radius: 4px;
242 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}" 262 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
243 monitor=$(jq -r ".display" <<<"$entry") 263 /* background-color: white; */
244 if [[ ! -f "$blurred_path" ]]; then 264 background-color: rgba(0, 0, 0, 0.5);
245 mkdir -p "$(dirname "$blurred_path")" 265 }
246 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" & 266 ''} "$RUNTIME_DIRECTORY"/style.css
247 fi 267 for monitor in "''${!monitors[@]}"; do
248 monitors+=([$monitor]="$blurred_path") 268 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF
249 done < <(wpaperctl all-wallpapers -j | jq -c ".[]") 269 window#''${monitor} {
250 wait 270 background-image: url("''${monitors[$monitor]}");
251 271 background-repeat: no-repeat;
252 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" '' 272 background-size: 100% 100%;
253 #window-box { 273 background-origin: content-box;
254 padding: 64px;
255 /* border: 1px solid black; */
256 border-radius: 4px;
257 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
258 /* background-color: white; */
259 background-color: rgba(0, 0, 0, 0.5);
260 } 274 }
261 ''} "$RUNTIME_DIRECTORY"/style.css 275 EOF
262 for monitor in "''${!monitors[@]}"; do 276 done
263 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF 277 '';
264 window#''${monitor} { 278 })}"
265 background-image: url("''${monitors[$monitor]}");
266 background-repeat: no-repeat;
267 background-size: 100% 100%;
268 background-origin: content-box;
269 }
270 EOF
271 done
272 '')
273 ]; 279 ];
274 NotifyAccess = "all"; 280 NotifyAccess = "all";
275 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" '' 281 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" ''
276 ${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off 282 ${lib.getExe cfg.programs.niri.package} msg action power-off-monitors
277 ${config.systemd.package}/bin/systemd-notify --ready 283 ${lib.getExe' config.systemd.package "systemd-notify"} --ready
278 ''}''; 284 ''}'';
279 }; 285 };
280 }; 286 };
@@ -322,15 +328,60 @@ in {
322 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\""; 328 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\"";
323 }; 329 };
324 }; 330 };
331 # wpaperd = {
332 # Install = {
333 # WantedBy = ["graphical-session.target"];
334 # };
335 # Unit = {
336 # After = [ "graphical-session.target" ];
337 # PartOf = [ "graphical-session.target" ];
338 # };
339 # Service = {
340 # ExecStart = lib.getExe cfg.services.wpaperd.package;
341 # Type = "simple";
342 # Restart = "always";
343 # RestartSec = "2s";
344 # };
345 # };
346 xembed-sni-proxy = {
347 Unit = {
348 PartOf = lib.mkForce ["tray.target"];
349 };
350 };
351 poweralertd = {
352 Unit = {
353 After = ["graphical-session.target"];
354 };
355 };
356 network-manager-applet = {
357 Unit = {
358 PartOf = lib.mkForce ["tray.target"];
359 };
360 };
361 udiskie = {
362 Unit = {
363 PartOf = lib.mkForce ["tray.target"];
364 };
365 };
366 blueman-applet = {
367 Unit = {
368 PartOf = lib.mkForce ["tray.target"];
369 };
370 Install = {
371 WantedBy = lib.mkForce ["tray.target"];
372 };
373 };
325 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" { 374 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" {
326 Unit = { 375 Unit = {
327 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 376 BindsTo = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
328 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 377 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
329 }; 378 };
330 Service = { 379 Service = {
331 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=10s localhost:${toString (port + 1)}"; 380 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}";
381 Restart = "always";
382 RestartSec = "23s";
332 }; 383 };
333 }) [{ host = "proxy.mathw0h"; port = 8118; } { host = "proxy.vidhar"; port = 8120; }]); 384 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]);
334 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 385 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
335 Socket = { 386 Socket = {
336 ListenStream = "%I"; 387 ListenStream = "%I";
@@ -338,7 +389,7 @@ in {
338 Install = { 389 Install = {
339 WantedBy = ["default.target"]; 390 WantedBy = ["default.target"];
340 }; 391 };
341 }) [8118 8120]) // { 392 }) [8118 8120 8122 8124]) // {
342 "yt-dlp" = { 393 "yt-dlp" = {
343 Socket = { 394 Socket = {
344 SocketMode = "0600"; 395 SocketMode = "0600";
@@ -352,7 +403,7 @@ in {
352 }; 403 };
353 }; 404 };
354 timers = { 405 timers = {
355 sync-keepass = { 406 "sync-keepass@store.kdbx" = {
356 Timer = { 407 Timer = {
357 OnActiveSec = "1m"; 408 OnActiveSec = "1m";
358 OnUnitActiveSec = "1m"; 409 OnUnitActiveSec = "1m";
@@ -362,6 +413,16 @@ in {
362 WantedBy = ["default.target"]; 413 WantedBy = ["default.target"];
363 }; 414 };
364 }; 415 };
416 "sync-keepass@rz.kdbx" = {
417 Timer = {
418 OnActiveSec = "1d";
419 OnUnitActiveSec = "1d";
420 };
421
422 Install = {
423 WantedBy = ["default.target"];
424 };
425 };
365 }; 426 };
366 targets = { 427 targets = {
367 graphical-session = { 428 graphical-session = {
@@ -372,6 +433,9 @@ in {
372 }; 433 };
373 tray = { 434 tray = {
374 Unit = { 435 Unit = {
436 PartOf = [ "graphical-session.target" ];
437 # Requires = [ "waybar.service" ];
438 After = [ "graphical-session.target" ]; # "waybar.service" ];
375 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"]; 439 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"];
376 }; 440 };
377 }; 441 };
diff --git a/accounts/gkleen@sif/taffybar/default.nix b/accounts/gkleen@sif/taffybar/default.nix
deleted file mode 100644
index 98366d8f..00000000
--- a/accounts/gkleen@sif/taffybar/default.nix
+++ /dev/null
@@ -1,2 +0,0 @@
1{ haskellPackages ? (import <nixpkgs> {}).haskellPackages }:
2haskellPackages.callCabal2nix "gkleen-sif-taffybar" ./. {}
diff --git a/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal b/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal
deleted file mode 100644
index e32cb473..00000000
--- a/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal
+++ /dev/null
@@ -1,32 +0,0 @@
1name: gkleen-sif-taffybar
2version: 0.0.0
3build-type: Simple
4cabal-version: >=1.10
5
6data-files: taffybar.css
7
8executable taffybar
9 hs-source-dirs: src
10 main-is: taffybar.hs
11 ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -Wall
12 build-depends: base
13 , containers
14 , directory
15 , filepath
16 , gtk3
17 , taffybar
18 , X11>=1.8
19 , transformers
20 , gi-gtk
21 , time, time-locale-compat
22 , text
23 , HStringTemplate
24 , gtk-sni-tray
25 , hslogger
26 other-modules: Paths_gkleen_sif_taffybar
27 , System.Taffybar.Widget.Clock
28 , System.Taffybar.Widget.TooltipBattery
29 default-language: Haskell2010
30 default-extensions: ScopedTypeVariables
31 , LambdaCase
32 , NamedFieldPuns \ No newline at end of file
diff --git a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs
deleted file mode 100644
index e8dc480f..00000000
--- a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs
+++ /dev/null
@@ -1,111 +0,0 @@
1{-# LANGUAGE OverloadedStrings #-}
2module System.Taffybar.Widget.Clock
3 ( textClockNew
4 , textClockNewWith
5 , defaultClockConfig
6 , ClockConfig(..)
7 , ClockUpdateStrategy(..)
8 ) where
9
10import Control.Monad.IO.Class
11import Data.Maybe
12import qualified Data.Text as T
13import qualified Data.Time.Clock as Clock
14import Data.Time.Format
15import Data.Time.LocalTime
16import qualified Data.Time.Locale.Compat as L
17import GI.Gtk
18import System.Taffybar.Widget.Generic.PollingLabel
19
20type ClockFormat = L.TimeLocale -> ZonedTime -> T.Text
21
22-- | Create the widget. I recommend passing @Nothing@ for the TimeLocale
23-- parameter. The format string can include Pango markup
24-- (<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>).
25textClockNew ::
26 MonadIO m => Maybe L.TimeLocale -> ClockFormat -> Double -> m GI.Gtk.Widget
27textClockNew userLocale format interval =
28 textClockNewWith cfg
29 where
30 cfg = defaultClockConfig { clockTimeLocale = userLocale
31 , clockFormat = format
32 , clockUpdateStrategy = ConstantInterval interval
33 }
34
35data ClockUpdateStrategy
36 = ConstantInterval Double
37 | RoundedTargetInterval Int Double
38 deriving (Eq, Ord, Show)
39
40data ClockConfig = ClockConfig
41 { clockTimeZone :: Maybe TimeZone
42 , clockTimeLocale :: Maybe L.TimeLocale
43 , clockFormat :: ClockFormat
44 , clockUpdateStrategy :: ClockUpdateStrategy
45 }
46
47-- | A clock configuration that defaults to the current locale
48defaultClockConfig :: ClockConfig
49defaultClockConfig = ClockConfig
50 { clockTimeZone = Nothing
51 , clockTimeLocale = Nothing
52 , clockFormat = \locale zonedTime -> T.pack $ formatTime locale "%a %b %_d %r" zonedTime
53 , clockUpdateStrategy = RoundedTargetInterval 5 0.0
54 }
55
56systemGetTZ :: IO TimeZone
57systemGetTZ = getCurrentTimeZone
58
59-- | A configurable text-based clock widget. It currently allows for
60-- a configurable time zone through the 'ClockConfig'.
61--
62-- See also 'textClockNew'.
63textClockNewWith :: MonadIO m => ClockConfig -> m Widget
64textClockNewWith ClockConfig
65 { clockTimeZone = userZone
66 , clockTimeLocale = userLocale
67 , clockFormat = format
68 , clockUpdateStrategy = updateStrategy
69 } = liftIO $ do
70 let getTZ = maybe systemGetTZ return userZone
71 locale = fromMaybe L.defaultTimeLocale userLocale
72
73 let getUserZonedTime =
74 utcToZonedTime <$> getTZ <*> Clock.getCurrentTime
75
76 doTimeFormat = format locale
77
78 getRoundedTimeAndNextTarget = do
79 zonedTime <- getUserZonedTime
80 return $ case updateStrategy of
81 ConstantInterval interval ->
82 (doTimeFormat zonedTime, Nothing, interval)
83 RoundedTargetInterval roundSeconds offset ->
84 let roundSecondsDiffTime = fromIntegral roundSeconds
85 addTheRound = addLocalTime roundSecondsDiffTime
86 localTime = zonedTimeToLocalTime zonedTime
87 ourLocalTimeOfDay = localTimeOfDay localTime
88 seconds = round $ todSec ourLocalTimeOfDay
89 secondsFactor = seconds `div` roundSeconds
90 displaySeconds = secondsFactor * roundSeconds
91 baseLocalTimeOfDay =
92 ourLocalTimeOfDay { todSec = fromIntegral displaySeconds }
93 ourLocalTime =
94 localTime { localTimeOfDay = baseLocalTimeOfDay }
95 roundedLocalTime =
96 if seconds `mod` roundSeconds > roundSeconds `div` 2
97 then addTheRound ourLocalTime
98 else ourLocalTime
99 roundedZonedTime =
100 zonedTime { zonedTimeToLocalTime = roundedLocalTime }
101 nextTarget = addTheRound ourLocalTime
102 amountToWait = realToFrac $ diffLocalTime nextTarget localTime
103 in (doTimeFormat roundedZonedTime, Nothing, amountToWait - offset)
104
105 label <- pollingLabelWithVariableDelay getRoundedTimeAndNextTarget
106 ebox <- eventBoxNew
107 containerAdd ebox label
108 eventBoxSetVisibleWindow ebox False
109 widgetShowAll ebox
110 toWidget ebox
111
diff --git a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs
deleted file mode 100644
index 9dc52774..00000000
--- a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs
+++ /dev/null
@@ -1,101 +0,0 @@
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3module System.Taffybar.Widget.TooltipBattery ( batteryIconTooltipNew ) where
4
5import Control.Applicative
6import Control.Monad
7import Control.Monad.IO.Class
8import Control.Monad.Trans.Reader
9import Data.Int (Int64)
10import qualified Data.Text as T
11import GI.Gtk
12import Prelude
13import StatusNotifier.Tray (scalePixbufToSize)
14import System.Taffybar.Context
15import System.Taffybar.Information.Battery
16import System.Taffybar.Util
17import System.Taffybar.Widget.Generic.AutoSizeImage
18import System.Taffybar.Widget.Generic.ChannelWidget
19import Text.Printf
20import Text.StringTemplate
21import Data.Function ((&))
22
23-- | Just the battery info that will be used for display (this makes combining
24-- several easier).
25data BatteryWidgetInfo = BWI
26 { seconds :: Maybe Int64
27 , percent :: Double
28 , status :: String
29 , rate :: Maybe Double
30 } deriving (Eq, Show)
31
32-- | Format a duration expressed as seconds to hours and minutes
33formatDuration :: Int64 -> String
34formatDuration secs = let minutes, hours, minutes' :: Int64
35 minutes = secs `div` 60
36 (hours, minutes') = minutes `divMod` 60
37 in printf "%02d:%02d" hours minutes'
38
39getBatteryWidgetInfo :: BatteryInfo -> BatteryWidgetInfo
40getBatteryWidgetInfo info =
41 let battPctNum :: Double
42 battPctNum = batteryPercentage info
43 battTime :: Maybe Int64
44 battTime =
45 case batteryState info of
46 BatteryStateCharging -> Just $ batteryTimeToFull info
47 BatteryStateDischarging -> Just $ batteryTimeToEmpty info
48 _ -> Nothing
49 battStatus :: String
50 battStatus =
51 case batteryState info of
52 BatteryStateCharging -> "↑"
53 BatteryStateDischarging -> "↓"
54 BatteryStateEmpty -> "⤓"
55 BatteryStateFullyCharged -> "⤒"
56 _ -> "?"
57 battRate :: Maybe Double
58 battRate | rawRate < 0.1 = Nothing
59 | otherwise = Just rawRate
60 where rawRate = batteryEnergyRate info
61 in BWI{ seconds = battTime, percent = battPctNum, status = battStatus, rate = battRate }
62
63-- | Given (maybe summarized) battery info and format: provides the string to display
64formatBattInfo :: BatteryWidgetInfo -> String -> T.Text
65formatBattInfo info fmt =
66 let tpl = newSTMP fmt
67 tpl' = tpl
68 & setManyAttrib [ ("percentage", printf "%.0f" $ percent info)
69 , ("status", status info)
70 ]
71 & setManyAttrib [ ("time", formatDuration <$> seconds info)
72 , ("rate", printf "%.0f" <$> rate info)
73 ]
74 in render tpl'
75
76themeLoadFlags :: [IconLookupFlags]
77themeLoadFlags = [IconLookupFlagsGenericFallback, IconLookupFlagsUseBuiltin]
78
79batteryIconTooltipNew :: String -> TaffyIO Widget
80batteryIconTooltipNew format = do
81 DisplayBatteryChanVar (chan, _) <- setupDisplayBatteryChanVar ["IconName", "State", "Percentage", "TimeToFull", "TimeToEmpty", "EnergyRate"]
82 ctx <- ask
83 liftIO $ do
84 image <- imageNew
85 styleCtx <- widgetGetStyleContext =<< toWidget image
86 defaultTheme <- iconThemeGetDefault
87 let getCurrentBatteryIconNameStringTooltip = do
88 info <- runReaderT getDisplayBatteryInfo ctx
89 let iconNameString = T.pack $ batteryIconName info
90 tooltip = formatBattInfo (getBatteryWidgetInfo info) format
91 return (iconNameString, tooltip)
92 extractPixbuf info =
93 fst <$> iconInfoLoadSymbolicForContext info styleCtx
94 setIconForSize size = do
95 (name, tooltip) <- getCurrentBatteryIconNameStringTooltip
96 widgetSetTooltipMarkup image $ Just tooltip
97 iconThemeLookupIcon defaultTheme name size themeLoadFlags >>=
98 traverse extractPixbuf >>=
99 traverse (scalePixbufToSize size OrientationHorizontal)
100 updateImage <- autoSizeImage image setIconForSize OrientationHorizontal
101 toWidget =<< channelWidgetNew image chan (const $ postGUIASync updateImage)
diff --git a/accounts/gkleen@sif/taffybar/src/taffybar.hs b/accounts/gkleen@sif/taffybar/src/taffybar.hs
deleted file mode 100644
index 67ee942d..00000000
--- a/accounts/gkleen@sif/taffybar/src/taffybar.hs
+++ /dev/null
@@ -1,89 +0,0 @@
1{-# LANGUAGE OverloadedStrings #-}
2
3module Main where
4
5import System.Taffybar (startTaffybar)
6import System.Taffybar.Context (TaffybarConfig(..))
7import System.Taffybar.Hooks
8import System.Taffybar.SimpleConfig hiding (SimpleTaffyConfig(cssPaths))
9import System.Taffybar.Widget
10import qualified System.Taffybar.Widget.Clock as MyClock
11import System.Taffybar.Widget.TooltipBattery
12
13import Data.Time.Format
14import Data.Time.LocalTime
15import Data.Time.Calendar.WeekDate
16
17import qualified Data.Text as T
18
19import Control.Exception (SomeException, try)
20import Control.Monad.Trans.Reader (mapReaderT)
21
22import Paths_gkleen_sif_taffybar
23
24import System.Log.Logger
25
26
27main :: IO ()
28main = do
29 logger <- getLogger "System.Taffybar"
30 saveGlobalLogger $ setLevel INFO logger
31
32 myCssPath <- getDataFileName "taffybar.css"
33 startTaffybar taffybarConfig{ cssPaths = pure myCssPath }
34
35
36taffybarConfig :: TaffybarConfig
37taffybarConfig =
38 let myWorkspacesConfig =
39 defaultWorkspacesConfig
40 { maxIcons = Just 0
41 , widgetGap = 7
42 , showWorkspaceFn = \case
43 -- Workspace{ workspaceState = Empty } -> False
44 Workspace{ workspaceName } | workspaceName == "NSP" -> False
45 _other -> True
46 , getWindowIconPixbuf = \i d -> either (\(_ :: SomeException) -> Nothing) id <$> mapReaderT try (defaultGetWindowIconPixbuf i d)
47 , urgentWorkspaceState = True
48 }
49 workspaces = workspacesNew myWorkspacesConfig
50 clock = MyClock.textClockNewWith MyClock.defaultClockConfig
51 { MyClock.clockUpdateStrategy = MyClock.RoundedTargetInterval 1 0.0
52 , MyClock.clockFormat = \tl zt@ZonedTime{ zonedTimeToLocalTime = LocalTime{ localDay } }
53 -> let date = formatTime tl "%Y-%m-%d" localDay
54 weekdate = "W" <> show2 woy <> "-" <> show dow
55 where (_, woy, dow) = toWeekDate localDay
56 show2 :: Int -> String
57 show2 x = replicate (2 - length s) '0' ++ s
58 where s = show x
59 time = formatTime tl "%H:%M:%S%Ez" zt
60 in T.intercalate " " $ map T.pack [weekdate, date, time]
61 }
62 layout = layoutNew defaultLayoutConfig
63 windowsW = windowsNew defaultWindowsConfig
64 { getMenuLabel = truncatedGetMenuLabel 80
65 , getActiveLabel = truncatedGetActiveLabel 80
66 }
67 worktime = commandRunnerNew 60 "worktime" [] "worktime"
68 worktimeToday = commandRunnerNew 60 "worktime" ["today"] "worktime today"
69 -- See https://github.com/taffybar/gtk-sni-tray#statusnotifierwatcher
70 -- for a better way to set up the sni tray
71 -- tray = sniTrayThatStartsWatcherEvenThoughThisIsABadWayToDoIt
72 tray = sniTrayNew
73 myConfig = defaultSimpleTaffyConfig
74 { startWidgets =
75 workspaces : map (>>= buildContentsBox) [ layout, windowsW ]
76 , endWidgets = map (>>= buildContentsBox) $ reverse
77 -- , mpris2New
78 [ worktime, worktimeToday
79 , clock
80 , tray
81 , batteryIconTooltipNew "$status$ $percentage$%$if(time)$$if(rate)$ ($rate$W $time$)$else$ ($time$)$endif$$elseif(rate)$ ($rate$W)$endif$"
82 ]
83 , barPosition = Top
84 , barPadding = 2
85 , barHeight = ExactSize 28
86 , widgetSpacing = 10
87 }
88 in withBatteryRefresh $ withLogServer $
89 withToggleServer $ toTaffyConfig myConfig
diff --git a/accounts/gkleen@sif/taffybar/taffybar.css b/accounts/gkleen@sif/taffybar/taffybar.css
deleted file mode 100644
index 7a297465..00000000
--- a/accounts/gkleen@sif/taffybar/taffybar.css
+++ /dev/null
@@ -1,146 +0,0 @@
1@define-color transparent rgba(0.0, 0.0, 0.0, 0.0);
2@define-color white #808080;
3@define-color gray #202020;
4@define-color green #008000;
5@define-color yellow #808000;
6@define-color blue #000080;
7@define-color red #800000;
8@define-color black #000000;
9/* @define-color taffy-blue #0c7cd5; */
10@define-color taffy-blue @blue;
11
12@define-color active-window-color @white;
13@define-color urgent-window-color @taffy-blue;
14@define-color font-color @white;
15@define-color menu-background-color @black;
16@define-color menu-font-color @white;
17
18/* Top level styling */
19
20.taffy-window * {
21 /*
22 This removes any existing styling from UI elements. Taffybar will not
23 cohere with your gtk theme.
24 */
25 all: unset;
26
27 font-family: "Fira Sans", sans-serif;
28 font-size: 21px;
29 color: @font-color;
30}
31
32.taffy-box {
33 /* border-radius: 10px; */
34 background-color: @black;
35}
36
37.inner-pad {
38 /* padding-bottom: 5px; */
39 /* padding-top: 5px; */
40 padding-left: 2px;
41 padding-right: 2px;
42}
43
44.contents {
45 /* padding-bottom: 4px; */
46 /* padding-top: 4px; */
47 padding-right: 2px;
48 padding-left: 2px;
49 transition: background-color .5s;
50 border-radius: 5px;
51}
52
53/* Workspaces styling */
54
55.workspace-label {
56 padding-right: 3px;
57 padding-left: 2px;
58 font-size: 21px;
59}
60
61.workspace-label.active {
62 color: @green;
63}
64.workspace-label.visible {
65 color: @yellow;
66}
67.workspace-label.empty {
68 color: @gray;
69}
70.workspace-label.urgent {
71 color: @red;
72}
73
74.active .contents {
75 background-color: rgba(0.0, 0.0, 0.0, 0.5);
76}
77
78.visible .contents {
79 background-color: rgba(0.0, 0.0, 0.0, 0.2);
80}
81
82.window-icon-container {
83 transition: opacity .5s, box-shadow .5s;
84 opacity: 1;
85}
86
87/* This gives space for the box-shadow (they look like underlines) that follow.
88 This will actually affect all widgets, (not just the workspace icons), but
89 that is what we want since we want the icons to look the same. */
90.auto-size-image, .sni-tray {
91 padding-top: 3px;
92 padding-bottom: 3px;
93}
94
95.window-icon-container.active {
96 box-shadow: inset 0 -3px @white;
97}
98
99.window-icon-container.urgent {
100 box-shadow: inset 0 -3px @urgent-window-color;
101}
102
103.window-icon-container.inactive .window-icon {
104 padding: 0px;
105}
106
107.window-icon-container.minimized .window-icon {
108 opacity: .3;
109}
110
111.window-icon {
112 opacity: 1;
113 transition: opacity .5s;
114}
115
116/* Button styling */
117
118button {
119 background-color: @transparent;
120 border-width: 0px;
121 border-radius: 0px;
122}
123
124button:checked, button:hover .Contents:hover {
125 box-shadow: inset 0 -3px @taffy-blue;
126}
127
128/* Menu styling */
129
130/* The ".taffy-window" prefixed selectors are needed because if they aren't present,
131 the top level .Taffybar selector takes precedence */
132.taffy-window menuitem *, menuitem * {
133 color: @menu-font-color;
134}
135
136.taffy-window menuitem, menuitem {
137 background-color: @menu-background-color;
138}
139
140.taffy-window menuitem:hover, menuitem:hover {
141 background-color: @taffy-blue;
142}
143
144.taffy-window menuitem:hover > label, menuitem:hover > label {
145 color: @white;
146}
diff --git a/accounts/gkleen@sif/utils/async-yt-dlp.nix b/accounts/gkleen@sif/utils/async-yt-dlp.nix
new file mode 100644
index 00000000..c3b82ec5
--- /dev/null
+++ b/accounts/gkleen@sif/utils/async-yt-dlp.nix
@@ -0,0 +1,57 @@
1{ writers, python3Packages, ... }:
2
3writers.writePython3Bin "async-yt-dlp" {
4 libraries = with python3Packages; [ yt-dlp ];
5 flakeIgnore = ["E501"];
6} ''
7import sys
8import os
9
10import yt_dlp
11import yt_dlp.options
12from collections import namedtuple
13import socket
14from pathlib import Path
15import json
16
17create_parser = yt_dlp.options.create_parser
18
19
20def parse_patched_options(opts):
21 patched_parser = create_parser()
22 patched_parser.defaults.update({
23 'ignoreerrors': False,
24 'retries': 0,
25 'fragment_retries': 0,
26 'extract_flat': False,
27 'concat_playlist': 'never',
28 })
29 yt_dlp.options.create_parser = lambda: patched_parser
30 try:
31 return yt_dlp.parse_options(opts)
32 finally:
33 yt_dlp.options.create_parser = create_parser
34
35
36default_opts = parse_patched_options([]).ydl_opts
37
38
39def cli_to_api(opts):
40 opts = parse_patched_options(opts)
41 urls = opts.urls
42 opts = opts.ydl_opts
43
44 diff = {k: v for k, v in opts.items() if default_opts[k] != v}
45 if 'postprocessors' in diff:
46 diff['postprocessors'] = [pp for pp in diff['postprocessors']
47 if pp not in default_opts['postprocessors']]
48 return namedtuple('Options', ('params', 'urls'))(diff, urls)
49
50
51if __name__ == '__main__':
52 opts = cli_to_api(sys.argv[1:])
53 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
54 sock.connect(str(Path(os.environ["XDG_RUNTIME_DIR"]) / "yt-dlp.sock").encode('utf-8'))
55 with sock.makefile(mode='w', buffering=1, encoding='utf-8') as fh:
56 json.dump(opts._asdict(), fh)
57''
diff --git a/accounts/gkleen@sif/utils/pdf2pdf.nix b/accounts/gkleen@sif/utils/pdf2pdf.nix
new file mode 100644
index 00000000..9f4cbc3e
--- /dev/null
+++ b/accounts/gkleen@sif/utils/pdf2pdf.nix
@@ -0,0 +1,8 @@
1pkgs@{ lib, resholve, zsh, ghostscript_headless, ... }:
2
3resholve.writeScriptBin "pdf2pdf" {
4 inputs = with pkgs; [ghostscript_headless];
5 interpreter = lib.getExe zsh;
6} ''
7 exec gs -dPDFSETTINGS=/prepress -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER -dPreserveAnnots=false "-sOutputFile=''${2}" "''${1}"
8''
diff --git a/accounts/gkleen@sif/utils/sieve-edit.nix b/accounts/gkleen@sif/utils/sieve-edit.nix
new file mode 100644
index 00000000..f985a3f6
--- /dev/null
+++ b/accounts/gkleen@sif/utils/sieve-edit.nix
@@ -0,0 +1,24 @@
1pkgs@{ lib, resholve, zsh, sieve-connect, sops, ... }:
2
3resholve.writeScriptBin "sieve-edit" {
4 inputs = with pkgs; [sieve-connect sops];
5 interpreter = lib.getExe zsh;
6 execer = with pkgs; [
7 "cannot:${lib.getExe sieve-connect}"
8 "cannot:${lib.getExe sops}"
9 ];
10} ''
11 host=$1; shift
12 case "$host" in
13 surtr)
14 sieve-connect -s surtr.yggdrasil.li -m EXTERNAL --clientkey <(sops decrypt $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.key) --clientcert $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.crt --edit --remotesieve sieve
15 ;;
16 ymir)
17 sieve-connect -s ymir.yggdrasil.li -u gkleen --edit --remotesieve sieve
18 ;;
19 *)
20 echo "Unknown host: ‘$host’" >&2
21 return 2
22 ;;
23 esac
24''
diff --git a/accounts/gkleen@sif/xmonad/.gitignore b/accounts/gkleen@sif/xmonad/.gitignore
deleted file mode 100644
index c11891cd..00000000
--- a/accounts/gkleen@sif/xmonad/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
1**/#*#
2**/.stack-work/
3/stack.yaml.lock
4/*.cabal
diff --git a/accounts/gkleen@sif/xmonad/default.nix b/accounts/gkleen@sif/xmonad/default.nix
deleted file mode 100644
index 8790c12f..00000000
--- a/accounts/gkleen@sif/xmonad/default.nix
+++ /dev/null
@@ -1,7 +0,0 @@
1argumentPackages@{ ... }:
2
3let
4 # defaultPackages = (import ./stackage.nix {});
5 # haskellPackages = defaultPackages // argumentPackages;
6 haskellPackages = argumentPackages;
7in haskellPackages.callPackage ./xmonad-yggdrasil.nix {}
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs
deleted file mode 100644
index e6accdcc..00000000
--- a/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs
+++ /dev/null
@@ -1,127 +0,0 @@
1{-# LANGUAGE DeriveGeneric, OverloadedLists, OverloadedStrings, ViewPatterns, ExistentialQuantification, MultiWayIf #-}
2
3module XMonad.Mpv
4 ( MpvCommand(..), MpvResponse(..), MpvException(..)
5 , mpv
6 , mpvDir
7 , mpvAll, mpvOne
8 , mpvResponse
9 ) where
10
11import Data.Aeson
12
13import Data.Monoid
14
15import Network.Socket hiding (recv)
16import Network.Socket.ByteString
17
18import qualified Data.ByteString as BS
19import qualified Data.ByteString.Char8 as CBS
20import qualified Data.ByteString.Lazy as LBS
21
22import GHC.Generics (Generic)
23import Data.Typeable (Typeable)
24import Data.String (IsString(..))
25
26import Control.Exception
27
28import System.IO.Temp (getCanonicalTemporaryDirectory)
29
30import Control.Monad
31import Control.Exception (bracket)
32import Control.Monad.IO.Class (MonadIO(..))
33
34import System.FilePath
35import System.Directory (getDirectoryContents)
36
37import Data.List
38import Data.Either
39import Data.Maybe
40
41import Debug.Trace
42
43
44data MpvCommand
45 = forall a. ToJSON a => MpvSetProperty String a
46 | MpvGetProperty String
47data MpvResponse
48 = MpvError String
49 | MpvSuccess (Maybe Value)
50 deriving (Read, Show, Generic, Eq)
51data MpvException = MpvException String
52 | MpvNoValue
53 | MpvNoParse String
54 deriving (Generic, Typeable, Read, Show)
55instance Exception MpvException
56
57
58instance ToJSON MpvCommand where
59 toJSON (MpvSetProperty name val) = Array ["set_property", fromString name, toJSON val]
60 toJSON (MpvGetProperty name) = Array ["get_property", fromString name]
61
62instance FromJSON MpvResponse where
63 parseJSON = withObject "response object" $ \obj -> do
64 mval <- obj .:? "data"
65 err <- obj .: "error"
66
67 let ret
68 | err == "success" = MpvSuccess mval
69 | otherwise = MpvError err
70
71 return ret
72
73mpvSocket :: FilePath -> (Socket -> IO a) -> IO a
74mpvSocket sockPath = withSocketsDo . bracket mkSock close
75 where
76 mkSock = do
77 sock <- socket AF_UNIX Stream defaultProtocol
78 connect sock $ SockAddrUnix (traceId sockPath)
79 return sock
80
81mpvResponse :: FromJSON v => MpvResponse -> IO v
82mpvResponse (MpvError str) = throwIO $ MpvException str
83mpvResponse (MpvSuccess Nothing) = throwIO MpvNoValue
84mpvResponse (MpvSuccess (Just v)) = case fromJSON v of
85 Success v' -> return v'
86 Error str -> throwIO $ MpvNoParse str
87
88mpv :: FilePath -> MpvCommand -> IO MpvResponse
89mpv sockPath cmd = mpvSocket sockPath $ \sock -> do
90 let message = (`BS.append` "\n") . LBS.toStrict . encode $ Object [("command", toJSON cmd)]
91 traceIO $ show message
92 sendAll sock message
93 let recvAll = do
94 prefix <- recv sock 4096
95 if
96 | (prefix', rest) <- CBS.break (== '\n') prefix
97 , not (BS.null rest) -> return prefix'
98 | BS.null prefix -> return prefix
99 | otherwise -> BS.append prefix <$> recvAll
100 response <- recvAll
101 traceIO $ show response
102 either (ioError . userError) return . traceShowId $ eitherDecodeStrict' response
103
104mpvDir :: Exception e => FilePath -> (FilePath -> [(FilePath, Either e MpvResponse)] -> Maybe MpvCommand) -> IO [(FilePath, Either e MpvResponse)]
105mpvDir dir step = do
106 socks <- filter (".sock" `isSuffixOf`) <$> getDirectoryContents dir
107 go [] socks
108 where
109 go acc [] = return acc
110 go acc (sock:socks)
111 | Just cmd <- step sock acc = do
112 res <- try $ mpv (dir </> sock) cmd
113 go ((sock, res) : acc) socks
114 | otherwise =
115 go acc socks
116
117mpvAll :: FilePath -> MpvCommand -> IO [MpvResponse]
118mpvAll dir cmd = do
119 results <- map snd <$> (mpvDir dir (\_ _ -> Just cmd) :: IO [(FilePath, Either SomeException MpvResponse)])
120 mapM (either throwIO return) results
121
122mpvOne :: FilePath -> MpvCommand -> IO (Maybe MpvResponse)
123mpvOne dir cmd = listToMaybe . snd . partitionEithers . map snd <$> (mpvDir dir step :: IO [(FilePath, Either SomeException MpvResponse)])
124 where
125 step _ results
126 | any (isRight . snd) results = Nothing
127 | otherwise = Just cmd
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs
deleted file mode 100644
index 1caefae5..00000000
--- a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs
+++ /dev/null
@@ -1,94 +0,0 @@
1module XMonad.Prompt.MyPass
2 (
3 -- * Usages
4 -- $usages
5 mkPassPrompt
6 ) where
7
8import Control.Monad (liftM)
9import XMonad.Core
10import XMonad.Prompt ( XPrompt
11 , showXPrompt
12 , commandToComplete
13 , nextCompletion
14 , getNextCompletion
15 , XPConfig
16 , mkXPrompt
17 , searchPredicate)
18import System.Directory (getHomeDirectory)
19import System.FilePath (takeExtension, dropExtension, combine)
20import System.Posix.Env (getEnv)
21import XMonad.Util.Run (runProcessWithInput)
22
23-- $usages
24-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
25--
26-- > import XMonad.Prompt.Pass
27--
28-- Then add a keybinding for 'passPrompt', 'passGeneratePrompt' or 'passRemovePrompt':
29--
30-- > , ((modMask x , xK_p) , passPrompt xpconfig)
31-- > , ((modMask x .|. controlMask, xK_p) , passGeneratePrompt xpconfig)
32-- > , ((modMask x .|. controlMask .|. shiftMask, xK_p), passRemovePrompt xpconfig)
33--
34-- For detailed instructions on:
35--
36-- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings".
37--
38-- - how to setup the password storage, see <http://git.zx2c4.com/password-store/about/>
39--
40
41type Predicate = String -> String -> Bool
42
43getPassCompl :: [String] -> Predicate -> String -> IO [String]
44getPassCompl compls p s
45 | length s <= minL
46 , all ((> minL) . length) compls = return []
47 | otherwise = do return $ filter (p s) compls
48 where
49 minL = 3
50
51type PromptLabel = String
52
53data Pass = Pass PromptLabel
54
55instance XPrompt Pass where
56 showXPrompt (Pass prompt) = prompt ++ ": "
57 commandToComplete _ c = c
58 nextCompletion _ = getNextCompletion
59
60-- | Default password store folder in $HOME/.password-store
61--
62passwordStoreFolderDefault :: String -> String
63passwordStoreFolderDefault home = combine home ".password-store"
64
65-- | Compute the password store's location.
66-- Use the PASSWORD_STORE_DIR environment variable to set the password store.
67-- If empty, return the password store located in user's home.
68--
69passwordStoreFolder :: IO String
70passwordStoreFolder =
71 getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir
72 where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory
73 computePasswordStoreDir (Just storeDir) = return storeDir
74
75-- | A pass prompt factory
76--
77mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X ()
78mkPassPrompt promptLabel passwordFunction xpconfig = do
79 passwords <- io (passwordStoreFolder >>= getPasswords)
80 mkXPrompt (Pass promptLabel) xpconfig (getPassCompl passwords $ searchPredicate xpconfig) passwordFunction
81
82-- | Retrieve the list of passwords from the password storage 'passwordStoreDir
83getPasswords :: FilePath -> IO [String]
84getPasswords passwordStoreDir = do
85 files <- runProcessWithInput "find" [
86 passwordStoreDir,
87 "-type", "f",
88 "-name", "*.gpg",
89 "-printf", "%P\n"] []
90 return $ map removeGpgExtension $ lines files
91
92removeGpgExtension :: String -> String
93removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file
94 | otherwise = file
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs
deleted file mode 100644
index c268f87d..00000000
--- a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs
+++ /dev/null
@@ -1,105 +0,0 @@
1module XMonad.Prompt.MyShell
2 ( Shell (..)
3 , shellPrompt
4 , prompt
5 , safePrompt
6 , unsafePrompt
7 , getCommands
8 , getShellCompl
9 , split
10 ) where
11
12import Codec.Binary.UTF8.String (encodeString)
13import Control.Exception as E
14import Control.Monad (forM)
15import Data.List (isPrefixOf)
16import System.Directory (doesDirectoryExist, getDirectoryContents)
17import System.Environment (getEnv)
18import System.Posix.Files (getFileStatus, isDirectory)
19
20import XMonad hiding (config)
21import XMonad.Prompt
22import XMonad.Util.Run
23
24econst :: Monad m => a -> IOException -> m a
25econst = const . return
26
27data Shell = Shell String
28
29instance XPrompt Shell where
30 showXPrompt (Shell q) = q
31 completionToCommand _ = escape
32
33shellPrompt :: String -> XPConfig -> X ()
34shellPrompt q c = do
35 cmds <- io getCommands
36 mkXPrompt (Shell q) c (getShellCompl cmds) spawn
37
38{- $spawns
39 See safe and unsafeSpawn in "XMonad.Util.Run".
40 prompt is an alias for safePrompt;
41 safePrompt and unsafePrompt work on the same principles, but will use
42 XPrompt to interactively query the user for input; the appearance is
43 set by passing an XPConfig as the second argument. The first argument
44 is the program to be run with the interactive input.
45 You would use these like this:
46
47 > , ((modm, xK_b), safePrompt "firefox" greenXPConfig)
48 > , ((modm .|. shiftMask, xK_c), prompt ("xterm" ++ " -e") greenXPConfig)
49
50 Note that you want to use safePrompt for Firefox input, as Firefox
51 wants URLs, and unsafePrompt for the XTerm example because this allows
52 you to easily start a terminal executing an arbitrary command, like
53 'top'. -}
54
55prompt, unsafePrompt, safePrompt :: String -> FilePath -> XPConfig -> X ()
56prompt = unsafePrompt
57safePrompt q c config = mkXPrompt (Shell q) config (getShellCompl [c]) run
58 where run = safeSpawn c . return
59unsafePrompt q c config = mkXPrompt (Shell q) config (getShellCompl [c]) run
60 where run a = unsafeSpawn $ c ++ " " ++ a
61
62getShellCompl :: [String] -> String -> IO [String]
63getShellCompl cmds s | s == "" || last s == ' ' = return []
64 | otherwise = do
65 f <- fmap lines $ runProcessWithInput "bash" [] ("compgen -A file -- "
66 ++ s ++ "\n")
67 files <- case f of
68 [x] -> do fs <- getFileStatus (encodeString x)
69 if isDirectory fs then return [x ++ "/"]
70 else return [x]
71 _ -> return f
72 return . uniqSort $ files ++ commandCompletionFunction cmds s
73
74commandCompletionFunction :: [String] -> String -> [String]
75commandCompletionFunction cmds str | '/' `elem` str = []
76 | otherwise = filter (isPrefixOf str) cmds
77
78getCommands :: IO [String]
79getCommands = do
80 p <- getEnv "PATH" `E.catch` econst []
81 let ds = filter (/= "") $ split ':' p
82 es <- forM ds $ \d -> do
83 exists <- doesDirectoryExist d
84 if exists
85 then getDirectoryContents d
86 else return []
87 return . uniqSort . filter ((/= '.') . head) . concat $ es
88
89split :: Eq a => a -> [a] -> [[a]]
90split _ [] = []
91split e l =
92 f : split e (rest ls)
93 where
94 (f,ls) = span (/=e) l
95 rest s | s == [] = []
96 | otherwise = tail s
97
98escape :: String -> String
99escape [] = ""
100escape (x:xs)
101 | isSpecialChar x = '\\' : x : escape xs
102 | otherwise = x : escape xs
103
104isSpecialChar :: Char -> Bool
105isSpecialChar = flip elem " &\\@\"'#?$*()[]{};"
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs
deleted file mode 100644
index 998c533e..00000000
--- a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs
+++ /dev/null
@@ -1,246 +0,0 @@
1module XMonad.Prompt.MySsh
2 ( -- * Usage
3 -- $usage
4 sshPrompt,
5 Ssh,
6 Override (..),
7 mkOverride,
8 Conn (..),
9 moshCmd,
10 moshCmd',
11 sshCmd,
12 inTmux,
13 withEnv
14 ) where
15
16import XMonad
17import XMonad.Util.Run
18import XMonad.Prompt
19
20import System.Directory
21import System.Environment
22import qualified Control.Exception as E
23
24import Control.Monad
25import Data.Maybe
26
27import Text.Parsec.String
28import Text.Parsec
29import Data.Char (isSpace)
30
31econst :: Monad m => a -> E.IOException -> m a
32econst = const . return
33
34-- $usage
35-- 1. In your @~\/.xmonad\/xmonad.hs@:
36--
37-- > import XMonad.Prompt
38-- > import XMonad.Prompt.Ssh
39--
40-- 2. In your keybindings add something like:
41--
42-- > , ((modm .|. controlMask, xK_s), sshPrompt defaultXPConfig)
43--
44-- Keep in mind, that if you want to use the completion you have to
45-- disable the "HashKnownHosts" option in your ssh_config
46--
47-- For detailed instruction on editing the key binding see
48-- "XMonad.Doc.Extending#Editing_key_bindings".
49
50data Override = Override
51 { oUser :: Maybe String
52 , oHost :: String
53 , oPort :: Maybe Int
54 , oCommand :: Conn -> String
55 }
56
57mkOverride = Override { oUser = Nothing, oHost = "", oPort = Nothing, oCommand = sshCmd }
58sshCmd c = concat
59 [ "ssh -t "
60 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
61 , cHost c
62 , if isJust $ cPort c then " -p " ++ (show $ fromJust $ cPort c) else ""
63 , " -- "
64 , cCommand c
65 ]
66moshCmd c = concat
67 [ "mosh "
68 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
69 , cHost c
70 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
71 , " -- "
72 , cCommand c
73 ]
74moshCmd' p c = concat
75 [ "mosh "
76 , "--server=" ++ p ++ " "
77 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
78 , cHost c
79 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
80 , " -- "
81 , cCommand c
82 ]
83inTmux Nothing c
84 | null $ cCommand c = c { cCommand = "tmux new-session" }
85 | otherwise = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
86inTmux (Just h) c
87 | null $ cCommand c = c { cCommand = "tmux new-session -As " <> h }
88 | otherwise = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
89withEnv :: [(String, String)] -> Conn -> Conn
90withEnv envs c = c { cCommand = "env" ++ (concat $ map (\(n, v) -> ' ' : (n ++ "=" ++ v)) envs) ++ " " ++ (cCommand c) }
91
92data Conn = Conn
93 { cUser :: Maybe String
94 , cHost :: String
95 , cPort :: Maybe Int
96 , cCommand :: String
97 } deriving (Eq, Show, Read)
98
99data Ssh = Ssh
100
101instance XPrompt Ssh where
102 showXPrompt Ssh = "SSH to: "
103 commandToComplete _ c = c
104 nextCompletion _ = getNextCompletion
105
106toConn :: String -> Maybe Conn
107toConn = toConn' . parse connParser "(unknown)"
108toConn' :: Either ParseError Conn -> Maybe Conn
109toConn' (Left _) = Nothing
110toConn' (Right a) = Just a
111
112connParser :: Parser Conn
113connParser = do
114 spaces
115 user' <- optionMaybe $ try $ do
116 str <- many1 $ satisfy (\c -> (not $ isSpace c) && (c /= '@'))
117 char '@'
118 return str
119 host' <- many1 $ satisfy (not . isSpace)
120 port' <- optionMaybe $ try $ do
121 space
122 string "-p"
123 spaces
124 int <- many1 digit
125 (space >> return ()) <|> eof
126 return $ (read int :: Int)
127 spaces
128 command' <- many anyChar
129 eof
130 return $ Conn
131 { cHost = host'
132 , cUser = user'
133 , cPort = port'
134 , cCommand = command'
135 }
136
137sshPrompt :: [Override] -> XPConfig -> X ()
138sshPrompt o c = do
139 sc <- io sshComplList
140 mkXPrompt Ssh c (mkComplFunFromList c sc) $ ssh o
141
142ssh :: [Override] -> String -> X ()
143ssh overrides str = do
144 let cmd = applyOverrides overrides str
145 liftIO $ putStr "SSH Command: "
146 liftIO $ putStrLn cmd
147 runInTerm "" cmd
148
149applyOverrides :: [Override] -> String -> String
150applyOverrides [] str = "ssh " ++ str
151applyOverrides (o:os) str = case (applyOverride o str) of
152 Just str -> str
153 Nothing -> applyOverrides os str
154
155applyOverride :: Override -> String -> Maybe String
156applyOverride o str = let
157 conn = toConn str
158 in
159 if isNothing conn then Nothing else
160 case (fromJust conn) `matches` o of
161 True -> Just $ (oCommand o) (fromJust conn)
162 False -> Nothing
163
164matches :: Conn -> Override -> Bool
165a `matches` b = and
166 [ justBool (cUser a) (oUser b) (==)
167 , (cHost a) == (oHost b)
168 , justBool (cPort a) (oPort b) (==)
169 ]
170
171justBool :: Eq a => Maybe a -> Maybe a -> (a -> a -> Bool) -> Bool
172justBool Nothing _ _ = True
173justBool _ Nothing _ = True
174justBool (Just a) (Just b) match = a `match` b
175
176sshComplList :: IO [String]
177sshComplList = uniqSort `fmap` liftM2 (++) sshComplListLocal sshComplListGlobal
178
179sshComplListLocal :: IO [String]
180sshComplListLocal = do
181 h <- getEnv "HOME"
182 s1 <- sshComplListFile $ h ++ "/.ssh/known_hosts"
183 s2 <- sshComplListConf $ h ++ "/.ssh/config"
184 return $ s1 ++ s2
185
186sshComplListGlobal :: IO [String]
187sshComplListGlobal = do
188 env <- getEnv "SSH_KNOWN_HOSTS" `E.catch` econst "/nonexistent"
189 fs <- mapM fileExists [ env
190 , "/usr/local/etc/ssh/ssh_known_hosts"
191 , "/usr/local/etc/ssh_known_hosts"
192 , "/etc/ssh/ssh_known_hosts"
193 , "/etc/ssh_known_hosts"
194 ]
195 case catMaybes fs of
196 [] -> return []
197 (f:_) -> sshComplListFile' f
198
199sshComplListFile :: String -> IO [String]
200sshComplListFile kh = do
201 f <- doesFileExist kh
202 if f then sshComplListFile' kh
203 else return []
204
205sshComplListFile' :: String -> IO [String]
206sshComplListFile' kh = do
207 l <- readFile kh
208 return $ map (getWithPort . takeWhile (/= ',') . concat . take 1 . words)
209 $ filter nonComment
210 $ lines l
211
212sshComplListConf :: String -> IO [String]
213sshComplListConf kh = do
214 f <- doesFileExist kh
215 if f then sshComplListConf' kh
216 else return []
217
218sshComplListConf' :: String -> IO [String]
219sshComplListConf' kh = do
220 l <- readFile kh
221 return $ map (!!1)
222 $ filter isHost
223 $ map words
224 $ lines l
225 where
226 isHost ws = take 1 ws == ["Host"] && length ws > 1
227
228fileExists :: String -> IO (Maybe String)
229fileExists kh = do
230 f <- doesFileExist kh
231 if f then return $ Just kh
232 else return Nothing
233
234nonComment :: String -> Bool
235nonComment [] = False
236nonComment ('#':_) = False
237nonComment ('|':_) = False -- hashed, undecodeable
238nonComment _ = True
239
240getWithPort :: String -> String
241getWithPort ('[':str) = host ++ " -p " ++ port
242 where (host,p) = break (==']') str
243 port = case p of
244 ']':':':x -> x
245 _ -> "22"
246getWithPort str = str
diff --git a/accounts/gkleen@sif/xmonad/package.yaml b/accounts/gkleen@sif/xmonad/package.yaml
deleted file mode 100644
index f65137af..00000000
--- a/accounts/gkleen@sif/xmonad/package.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
1name: xmonad-yggdrasil
2
3executables:
4 xmonad:
5 dependencies:
6 - base
7 - xmonad
8 - xmonad-contrib
9 - aeson
10 - bytestring
11 - text
12 - temporary
13 - filepath
14 - directory
15 - network
16 - unix
17 - utf8-string
18 - parsec
19 - process
20 - mtl
21 - X11
22 - transformers
23 - containers
24 - hostname
25 - libnotify
26 - taffybar
27
28 main: xmonad.hs
29 source-dirs:
30 - .
31 - lib
diff --git a/accounts/gkleen@sif/xmonad/stack.nix b/accounts/gkleen@sif/xmonad/stack.nix
deleted file mode 100644
index 17a49e04..00000000
--- a/accounts/gkleen@sif/xmonad/stack.nix
+++ /dev/null
@@ -1,17 +0,0 @@
1{ ghc, nixpkgs ? import ./nixpkgs.nix {} }:
2
3let
4 haskellPackages = import ./stackage.nix { inherit nixpkgs; };
5 inherit (nixpkgs {}) pkgs;
6in pkgs.haskell.lib.buildStackProject {
7 inherit ghc;
8 inherit (haskellPackages) stack;
9 name = "stackenv";
10 buildInputs = (with pkgs;
11 [ xorg.libX11 xorg.libXrandr xorg.libXinerama xorg.libXScrnSaver xorg.libXext xorg.libXft
12 cairo
13 glib
14 ]) ++ (with haskellPackages;
15 [
16 ]);
17}
diff --git a/accounts/gkleen@sif/xmonad/stack.yaml b/accounts/gkleen@sif/xmonad/stack.yaml
deleted file mode 100644
index b8ed1147..00000000
--- a/accounts/gkleen@sif/xmonad/stack.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
1nix:
2 enable: true
3 shell-file: stack.nix
4
5resolver: lts-13.21
6
7packages:
8 - .
9
10extra-deps: []
diff --git a/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix b/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix
deleted file mode 100644
index 7c853619..00000000
--- a/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix
+++ /dev/null
@@ -1,21 +0,0 @@
1{ mkDerivation, aeson, base, bytestring, containers, directory
2, filepath, hostname, hpack, mtl, network, parsec, process, lib
3, temporary, transformers, unix, utf8-string, X11, xmonad
4, xmonad-contrib, libnotify, taffybar
5}:
6mkDerivation {
7 pname = "xmonad-yggdrasil";
8 version = "0.0.0";
9 src = ./.;
10 isLibrary = false;
11 isExecutable = true;
12 libraryToolDepends = [ hpack ];
13 executableHaskellDepends = [
14 aeson base bytestring containers directory filepath hostname mtl
15 network parsec process temporary transformers unix utf8-string X11
16 xmonad xmonad-contrib libnotify taffybar
17 ];
18 preConfigure = "hpack";
19 license = "unknown";
20 hydraPlatforms = lib.platforms.none;
21}
diff --git a/accounts/gkleen@sif/xmonad/xmonad.hs b/accounts/gkleen@sif/xmonad/xmonad.hs
deleted file mode 100644
index a44d3bb7..00000000
--- a/accounts/gkleen@sif/xmonad/xmonad.hs
+++ /dev/null
@@ -1,939 +0,0 @@
1{-# LANGUAGE TupleSections, ViewPatterns, OverloadedStrings, FlexibleInstances, UndecidableInstances, MultiWayIf, NumDecimals #-}
2
3import XMonad
4import XMonad.Hooks.DynamicLog
5import XMonad.Hooks.ManageDocks
6import XMonad.Util.Run hiding (proc)
7import XMonad.Util.Loggers
8import XMonad.Util.EZConfig(additionalKeys)
9import System.IO
10import System.IO.Error
11import System.Environment
12import Data.Map (Map)
13import qualified Data.Map as Map
14import qualified XMonad.StackSet as W
15import System.Exit
16import Control.Monad.State (get)
17-- import XMonad.Layout.Spiral
18import Data.Ratio
19import Data.List
20import Data.Char
21import Data.Maybe (fromMaybe, listToMaybe, maybeToList, catMaybes, isJust)
22import XMonad.Layout.Tabbed
23import XMonad.Prompt
24import XMonad.Prompt.Input
25import XMonad.Util.Scratchpad
26import XMonad.Util.NamedScratchpad
27import XMonad.Util.Ungrab
28import Control.Monad (sequence, liftM, liftM2, join, void)
29import XMonad.Util.WorkspaceCompare
30import XMonad.Layout.NoBorders
31import XMonad.Layout.PerWorkspace
32import XMonad.Layout.SimplestFloat
33import XMonad.Layout.Renamed
34import XMonad.Layout.Reflect
35import XMonad.Layout.OnHost
36import XMonad.Layout.Combo
37import XMonad.Layout.ComboP
38import XMonad.Layout.Column
39import XMonad.Layout.TwoPane
40import XMonad.Layout.IfMax
41import XMonad.Layout.LayoutBuilder
42import XMonad.Layout.WindowNavigation
43import XMonad.Layout.Dwindle
44import XMonad.Layout.TrackFloating
45import System.Process
46import System.Directory (removeFile)
47import System.Posix.Files
48import System.FilePath ((</>))
49import Control.Concurrent
50import System.Posix.Process (getProcessID)
51import System.IO.Error
52import System.IO
53import XMonad.Hooks.ManageHelpers hiding (CW)
54import XMonad.Hooks.UrgencyHook as U
55import XMonad.Hooks.EwmhDesktops
56import XMonad.StackSet (RationalRect (..))
57import Control.Monad (when, filterM, (<=<))
58import Graphics.X11.ExtraTypes.XF86
59import XMonad.Util.Cursor
60import XMonad.Actions.Warp
61import XMonad.Actions.FloatKeys
62import XMonad.Util.SpawnOnce
63import System.Directory
64import System.FilePath
65import XMonad.Actions.CopyWindow
66import XMonad.Hooks.ServerMode
67import XMonad.Actions.Commands
68import XMonad.Actions.CycleWS
69import XMonad.Actions.RotSlaves
70import XMonad.Actions.UpdatePointer
71import XMonad.Prompt.Window
72import Data.IORef
73import Data.Monoid
74import Data.String
75import qualified XMonad.Actions.PhysicalScreens as P
76
77import XMonad.Layout.IM
78
79import System.Taffybar.Support.PagerHints (pagerHints)
80
81import XMonad.Prompt.MyShell
82import XMonad.Prompt.MyPass
83import XMonad.Prompt.MySsh
84
85import XMonad.Mpv
86
87import Network.HostName
88
89import Control.Applicative ((<$>))
90
91import Libnotify as Notify hiding (appName)
92import qualified Libnotify as Notify (appName)
93import Libnotify (Notification)
94-- import System.Information.Battery
95
96import Data.Int (Int32)
97
98import System.Posix.Process
99import System.Posix.Signals
100import System.Posix.IO as Posix
101import Control.Exception
102
103import System.IO.Unsafe
104
105import Control.Monad.Trans.Class
106import Control.Monad.Trans.Maybe
107
108import Data.Fixed (Micro)
109
110import qualified Data.Text as Text
111import Data.Ord (comparing)
112import Debug.Trace
113
114instance MonadIO m => IsString (m ()) where
115 fromString = spawn
116
117type KeyMap = Map (ButtonMask, KeySym) (X ())
118
119data Host = Host
120 { hName :: HostName
121 , hManageHook :: ManageHook
122 , hWsp :: Integer -> WorkspaceId
123 , hCoWsp :: String -> Maybe WorkspaceId
124 , hKeysMod :: XConfig Layout -> (KeyMap -> KeyMap)
125 , hScreens :: [P.PhysicalScreen]
126 , hKbLayouts :: [(String, Maybe String)]
127 , hCmds :: X [(String, X ())]
128 , hKeyUpKeys :: XConfig Layout -> KeyMap
129 }
130
131defaultHost = Host { hName = "unkown"
132 , hManageHook = composeOne [manageScratchTerm]
133 , hWsp = show
134 , hCoWsp = const Nothing
135 , hKeysMod = const id
136 , hScreens = [0,1..]
137 , hKbLayouts = [ ("us", Just "dvp")
138 , ("us", Nothing)
139 , ("de", Nothing)
140 ]
141 , hCmds = return []
142 , hKeyUpKeys = const Map.empty
143 }
144
145browser :: String
146browser = "env MOZ_USE_XINPUT2=1 firefox"
147
148gray, darkGray, red, green :: String
149gray = "#808080"
150darkGray = "#202020"
151red = "#800000"
152green = "#008000"
153
154hostFromName :: HostName -> Host
155hostFromName h@("vali") = defaultHost { hName = h
156 , hManageHook = composeOne $ catMaybes [ Just manageScratchTerm
157 , assign "web" $ className =? ".dwb-wrapped"
158 , assign "web" $ className =? "Chromium"
159 , assign "work" $ className =? "Emacs"
160 , assign "media" $ className =? "mpv"
161 ]
162 , hWsp = hWsp
163 , hCoWsp = hCoWsp
164 , hKeysMod = \conf -> Map.union $ (Map.fromList $ join $ map (spawnBindings conf) [ (xK_d, ["chromium", "chromium $(xclip -o)"])
165 , (xK_e, ["emacsclient -c"])
166 ])
167 `Map.union`
168 ( Map.fromList [ ((XMonad.modMask conf .|. controlMask, xK_Return), scratchpadSpawnActionCustom $ (XMonad.terminal conf) ++ " -name scratchpad -title scratchpad -e tmux new-session -D -s scratch")
169 ] )
170 , hScreens = hScreens defaultHost
171 }
172 where
173 workspaceNames = Map.fromList [ (2, "web")
174 , (3, "work")
175 , (10, "media")
176 ]
177 hWsp = wspFromMap workspaceNames
178 hCoWsp = coWspFromMap workspaceNames
179 assign wsp test = (\wsp -> test -?> doShift wsp) <$> hCoWsp wsp
180hostFromName h
181 | h `elem` ["hel", "sif"] = defaultHost { hName = h
182 , hManageHook = namedScratchpadManageHook scratchpads <+> composeOne (catMaybes
183 [ assign "mpv" $ className =? "mpv"
184 , assign "mpv" $ stringProperty "WM_WINDOW_ROLE" =? "presentation"
185 , assign "read" $ stringProperty "WM_WINDOW_ROLE" =? "presenter"
186 , assign "mpv" $ className =? "factorio"
187 , assign "mpv" $ resource =? "twitch"
188 , assign "web" $ className =? "chromium-browser"
189 , assign "web" $ className =? "Google-chrome"
190 , assign "work" $ (appName =? "Devtools" <&&> className =? "firefox")
191 , assign "work" $ className =? "Postman"
192 , assign "web" $ (appName =? "Navigator" <&&> className =? "firefox")
193 , assign "comm" $ (className =? "Emacs" <&&> title =? "Mail")
194 , assign "comm" $ className =? "Zulip"
195 , assign "comm" $ className =? "Element"
196 , assign "comm" $ className =? "Rocket.Chat"
197 , assign "comm" $ className =? "Discord"
198 , assign "comm" $ className =? "Rainbow"
199 , assign "media" $ resource =? "media"
200 , assign "monitor" $ className =? "Grafana"
201 , assign "monitor" $ className =? "Virt-viewer"
202 , assign "monitor" $ resource =? "htop"
203 , assign "monitor" $ resource =? "monitor"
204 , assign "monitor" $ className =? "xfreerdp"
205 , assign "monitor" $ className =? "org.remmina.Remmina"
206 , Just $ resource =? "htop" -?> centerFloat
207 , Just $ (className =? "Scp-dbus-service.py") -?> centerFloat
208 , Just $ resource =? "log" -?> centerFloat
209 , assign "work" $ className =? "Alacritty"
210 , Just $ (appName =? "Edit with Emacs FRAME") -?> centerFloat
211 , assign' ["work", "uni"] $ (className =? "Emacs" <&&> appName /=? "Edit with Emacs FRAME")
212 , assign' ["work", "uni"] $ className =? "jetbrains-idea-ce"
213 , assign "read" $ className =? "llpp"
214 , assign "read" $ className =? "Evince"
215 , assign "read" $ className =? "Zathura"
216 , assign "read" $ className =? "MuPDF"
217 , assign "read" $ className =? "Xournal"
218 , assign "read" $ appName =? "libreoffice"
219 , assign "read" $ appName =? "com-trollworks-gcs-app-GCS"
220 , assign "read" $ appName =? "Tux.py"
221 , assign "read" $ className =? "Gnucash"
222 , assign "comm" $ className =? "Skype"
223 , assign "comm" $ className =? "Daily"
224 , assign "comm" $ className =? "Pidgin"
225 , assign "comm" $ className =? "Thunderbird"
226 , assign "comm" $ className =? "Slack"
227 , Just $ (resource =? "xvkbd") -?> doRectFloat $ RationalRect (1 % 8) (3 % 8) (6 % 8) (4 % 8)
228 , Just $ (stringProperty "_NET_WM_WINDOW_TYPE" =? "_NET_WM_WINDOW_TYPE_DIALOG") -?> doFloat
229 , Just $ (className =? "Dunst") -?> doFloat
230 , Just $ (className =? "Xmessage") -?> doCenterFloat
231 , Just $ (className =? "Nm-openconnect-auth-dialog") -?> centerFloat
232 , Just $ (className =? "Pinentry") -?> doCenterFloat
233 , Just $ (className =? "pinentry") -?> doCenterFloat
234 , Just $ (stringProperty "WM_WINDOW_ROLE" =? "GtkFileChooseDialog") -?> centerFloatSmall
235 , Just $ (className =? "Nvidia-settings") -?> doCenterFloat
236 , Just $ fmap ("Minetest" `isInfixOf`) title -?> doIgnore
237 , Just $ fmap ("Automachef" `isInfixOf`) title -?> doIgnore
238 , assign "call" $ className =? "zoom"
239 ])
240 , hWsp = hWsp
241 , hCoWsp = hCoWsp
242 , hKeysMod = \conf -> Map.union $ (Map.fromList $ join $ map (spawnBindings conf) [ (xK_e, ["emacsclient -c"])
243 , (xK_d, [fromString browser, "google-chrome" {- , "notmuch-links" -}])
244 , (xK_c, [ inputPrompt xPConfigMonospace "dc" ?+ dc ])
245 , (xK_g, ["pidgin"])
246 , (xK_s, ["skype"])
247 -- , (xK_p, [mkPassPrompt "Type password" pwType xPConfig, mkPassPrompt "Show password" pwShow xPConfig, mkPassPrompt "Copy password" pwClip xPConfig])
248 , (xK_w, ["sudo rewacom"])
249 , (xK_y, [ "tmux new-window -dt media /var/media/link.hs $(xclip -o)"
250 , "tmux new-window -dt media /var/media/download.hs $(xclip -o)"
251 , "tmux new-window -dt media /var/media/download.hs $(xclip -o -selection clipboard)"
252 ])
253 , (xK_l, [ "tmux new-window -dt media mpv $(xclip -o)"
254 , "tmux new-window -dt media mpv $(xclip -o -selection clipboard)"
255 , "alacritty --class media -e tmuxp load /var/media"
256 ])
257 {- , (xK_m, [ "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch)'"
258 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch-mua-new-mail)'"
259 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e \"(browse-url-mail \"$(xclip -o)\")\""
260 ]) -}
261 , (xK_Return, ["keynav start,windowzoom", "keynav start"])
262 , (xK_t, [inputPrompt xPConfigMonospace "fuzzytime timer" ?+ fuzzytime, fuzzytime "unset", work_fuzzytime])
263 , (xK_a, [inputPrompt xPConfigMonospace "adjmix" ?+ adjmix])
264 , (xK_s, [ inputPromptWithCompl xPConfigMonospace "start synergy" synergyCompl ?+ synergyStart
265 , inputPromptWithCompl xPConfigMonospace "stop synergy" synergyCompl ?+ synergyStop
266 ])
267 , (xK_h, [ "alacritty --class htop -e htop"
268 , "alacritty --class log -e journalctl -xef"
269 ])
270 , (xK_x, [ "autorandr -c"
271 , "autorandr -fl def"
272 ])
273 , (xK_z, [ "zulip -- --force-device-scale-factor=2"
274 ])
275 ])
276 `Map.union`
277 ( Map.fromList [ ((XMonad.modMask conf .|. controlMask, xK_Return), namedScratchpadAction scratchpads "term")
278 , ((XMonad.modMask conf .|. controlMask, xK_a), namedScratchpadAction scratchpads "pavucontrol")
279 , ((XMonad.modMask conf .|. controlMask, xK_o), namedScratchpadAction scratchpads "easyeffects")
280 , ((XMonad.modMask conf .|. controlMask .|. shiftMask, xK_o), namedScratchpadAction scratchpads "helvum")
281 , ((XMonad.modMask conf .|. controlMask, xK_w), namedScratchpadAction scratchpads "alarms")
282 , ((XMonad.modMask conf .|. controlMask, xK_b), namedScratchpadAction scratchpads "blueman")
283 , ((XMonad.modMask conf .|. controlMask, xK_p), namedScratchpadAction scratchpads "keepassxc")
284 , ((XMonad.modMask conf .|. controlMask, xK_t), namedScratchpadAction scratchpads "toggl")
285 , ((XMonad.modMask conf .|. controlMask, xK_e), namedScratchpadAction scratchpads "emacs")
286 , ((XMonad.modMask conf .|. controlMask, xK_m), namedScratchpadAction scratchpads "calendar")
287 , ((XMonad.modMask conf .|. controlMask, xK_f), namedScratchpadAction scratchpads "music")
288 , ((XMonad.modMask conf .|. mod1Mask, xK_Up), rotate U)
289 , ((XMonad.modMask conf .|. mod1Mask, xK_Down), rotate D)
290 , ((XMonad.modMask conf .|. mod1Mask, xK_Left), rotate L)
291 , ((XMonad.modMask conf .|. mod1Mask, xK_Right), rotate R)
292 , ((controlMask, xK_space ), "dunstctl close" )
293 , ((controlMask .|. shiftMask, xK_space ), "dunstctl close-all" )
294 , ((controlMask, xK_period), "dunstctl context" )
295 , ((controlMask, xK_comma ), "dunstctl history-pop")
296 -- , ((XMonad.modMask conf .|. shiftMask, xK_a), startMute "hel")
297 ] )
298 , hKeyUpKeys = \conf -> Map.fromList [ -- ((XMonad.modMask conf .|. shiftMask, xK_a), stopMute "hel")
299 ]
300 , hScreens = hScreens defaultHost
301 , hCmds = return [ ("prev-workspace", prevWS)
302 , ("next-workspace", nextWS)
303 , ("prev-window", rotAllDown)
304 , ("next-window", rotAllUp)
305 , ("banish", banishScreen LowerRight)
306 , ("update-gpg-tty", safeSpawn "gpg-connect-agent" ["UPDATESTARTUPTTY", "/bye"])
307 , ("rescreen", rescreen)
308 , ("repanel", do
309 spawn "nm-applet"
310 spawn "blueman-applet"
311 spawn "pasystray"
312 spawn "kdeconnect-indicator"
313 spawn "dunst -print"
314 spawn "udiskie"
315 spawn "autocutsel -s PRIMARY"
316 spawn "autocutsel -s CLIPBOARD"
317 )
318 , ("pause", mediaMpv $ MpvSetProperty "pause" True)
319 , ("unpause", mediaMpv $ MpvSetProperty "pause" False)
320 , ("exit", io $ exitWith ExitSuccess)
321 ]
322 }
323 where
324 withGdkScale act = void . xfork $ setEnv "GDK_SCALE" "2" >> act
325 workspaceNames = Map.fromList [ (1, "comm")
326 , (2, "web")
327 , (3, "work")
328 , (4, "read")
329 , (5, "monitor")
330 , (6, "uni")
331 , (8, "call")
332 , (9, "media")
333 , (10, "mpv")
334 ]
335 scratchpads = [ NS "term" "alacritty --class scratchpad --title scratchpad -e tmux new-session -AD -s scratch" (resource =? "scratchpad") centerFloat
336 , NS "pavucontrol" "pavucontrol" (resource =? "pavucontrol") centerFloat
337 , NS "helvum" "helvum" (resource =? "helvum") centerFloat
338 , NS "easyeffects" "easyeffects" (resource =? "easyeffects") centerFloat
339 , NS "alarms" "alarm-clock-applet" (className =? "Alarm-clock-applet" <&&> title =? "Alarms") centerFloat
340 , NS "blueman" "blueman-manager" (className =? ".blueman-manager-wrapped") centerFloat
341 , NS "keepassxc" "keepassxc" (className =? "KeePassXC") centerFloat
342 , NS "toggl" "toggldesktop" (className =? "Toggl Desktop") centerFloat
343 , NS "calendar" "minetime -- --force-device-scale-factor=1.6" (className =? "MineTime") centerFloat
344 , NS "emacs" "emacsclient -c -F \"'(title . \\\"Scratchpad\\\")\"" (className =? "Emacs" <&&> title =? "Scratchpad") centerFloat
345 , NS "music" "ytmdesktop" (className =? "youtube-music-desktop-app") centerFloat
346 ]
347 centerFloat = customFloating $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
348 centerFloatSmall = customFloating $ RationalRect (1 % 4) (1 % 4) (1 % 2) (1 % 2)
349 hWsp = wspFromMap workspaceNames
350 hCoWsp = coWspFromMap workspaceNames
351 assign wsp test = (\wsp -> test -?> doShift wsp) <$> hCoWsp wsp
352 assign' :: [String] -> Query Bool -> Maybe MaybeManageHook
353 assign' wsps test = do
354 wsIds <- mapM hCoWsp wsps
355 return $ test -?> go wsIds
356 where
357 go :: [WorkspaceId] -> ManageHook
358 go wsps = do
359 visWsps <- liftX $ (\wset -> W.tag . W.workspace <$> W.current wset : W.visible wset) <$> gets windowset
360 case (filter (`elem` visWsps) wsps, wsps) of
361 (wsp : _, _) -> doShift wsp
362 (_, wsp : _) -> doShift wsp
363 ([], []) -> return mempty
364 rotate rot = do
365 safeSpawn "xrandr" ["--output", "eDP-1", "--rotate", xrandrDir]
366 mapM_ rotTouch touchscreens
367 where
368 xrandrDir = case rot of
369 U -> "normal"
370 L -> "left"
371 R -> "right"
372 D -> "inverted"
373 matrix = case rot of
374 U -> [ [ 1, 0, 0]
375 , [ 0, 1, 0]
376 , [ 0, 0, 1]
377 ]
378 L -> [ [ 0, -1, 1]
379 , [ 1, 0, 0]
380 , [ 0, 0, 1]
381 ]
382 R -> [ [ 0, 1, 0]
383 , [-1, 0, 1]
384 , [ 0, 0, 1]
385 ]
386 D -> [ [-1, 0, 1]
387 , [ 0, -1, 1]
388 , [ 0, 0, 1]
389 ]
390 touchscreens = [ "Wacom Co.,Ltd. Pen and multitouch sensor Finger touch"
391 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen stylus"
392 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen eraser"
393 ]
394 rotTouch screen = do
395 safeSpawn "xinput" $ ["set-prop", screen, "Coordinate Transformation Matrix"] ++ map (\n -> show n ++ ",") (concat matrix)
396 safeSpawn "xinput" ["map-to-output", screen, "eDP-1"]
397 withPw f label = io . void . forkProcess $ do
398 uninstallSignalHandlers
399 void $ createSession
400 (dropWhileEnd isSpace -> pw) <- readCreateProcess (proc "pass" ["show", label]) ""
401 void $ f pw
402 pwType :: String -> X ()
403 pwType = withPw $ readCreateProcess (proc "xdotool" ["type", "--clearmodifiers", "--file", "-"])
404 pwClip label = safeSpawn "pass" ["show", "--clip", label]
405 pwShow :: String -> X ()
406 pwShow = withPw $ \pw -> do
407 xmessage <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE")
408 readCreateProcess (proc xmessage ["-file", "-"]) pw
409 fuzzytime str = safeSpawn "fuzzytime" $ "timer" : words str
410 work_fuzzytime = io . void . forkProcess $ do
411 readCreateProcess (proc "worktime" []) "" >>= safeSpawn "fuzzytime" . ("timer" : ) . pure
412 adjmix str = safeSpawn "adjmix" $ words str
413 dc expr = void . xfork $ do
414 result <- readProcess "dc" [] $ expr ++ "f"
415 let
416 (first : rest) = filter (not . null) $ lines result
417 notification = Notify.summary first <> Notify.body (unlines rest) <> Notify.timeout Infinite <> Notify.urgency Normal <> Notify.appName "dc"
418 void $ Notify.display notification
419 synergyCompl = mkComplFunFromList' xPConfigMonospace ["mathw86"]
420 synergyStart host = safeSpawn "systemctl" ["--user", "start", "synergy-rtunnel@" ++ host ++ ".service"]
421 synergyStop host = safeSpawn "systemctl" ["--user", "stop", "synergy-rtunnel@" ++ host ++ ".service"]
422
423hostFromName _ = defaultHost
424
425-- muteRef :: IORef (Maybe (String, Notification))
426-- {-# NOINLINE muteRef #-}
427-- muteRef = unsafePerformIO $ newIORef Nothing
428
429-- startMute, stopMute :: String -> X ()
430-- startMute sink = liftIO $ do
431-- muted <- isJust <$> readIORef muteRef
432-- when (not muted) $ do
433-- let
434-- notification = Notify.summary "Muted" <> Notify.timeout Infinite <> Notify.urgency Normal
435-- level = "0.0dB"
436-- -- level <- runProcessWithInput "ssh" ["bragi", "cat", "/dev/shm/mix/" ++ sink ++ "/level"] ""
437-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", "0"]
438-- hPutStrLn stderr "Mute"
439-- writeIORef muteRef . Just . (level, ) =<< Notify.display notification
440-- stopMute sink = liftIO $ do
441-- let
442-- unmute (Just (level, notification)) = do
443-- hPutStrLn stderr "Unmute"
444-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", level]
445-- Notify.close notification
446-- unmute Nothing = return ()
447-- muted <- isJust <$> readIORef muteRef
448-- when muted . join . atomicModifyIORef muteRef $ (Nothing, ) . unmute
449
450wspFromMap workspaceNames = \i -> case Map.lookup i workspaceNames of
451 Just str -> show i ++ " " ++ str
452 Nothing -> show i
453
454coWspFromMap workspaceNames = \str -> case filter ((== str) . snd) $ Map.toList workspaceNames of
455 [] -> Nothing
456 [(i, _)] -> Just $ wspFromMap workspaceNames i
457 _ -> Nothing
458
459spawnModifiers = [0, controlMask, shiftMask .|. controlMask]
460spawnBindings :: XConfig layout -> (KeySym, [X ()]) -> [((KeyMask, KeySym), X ())]
461spawnBindings conf (k, cmds) = zipWith (\m cmd -> ((modm .|. mod1Mask .|. m, k), cmd)) spawnModifiers cmds
462 where
463 modm = XMonad.modMask conf
464
465manageScratchTerm = (resource =? "scratchpad" <||> resource =? "keysetup") -?> doRectFloat $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
466
467tabbedLayout t = renamed [Replace "Tabbed"] $ reflectHoriz $ t CustomShrink $ tabbedTheme
468tabbedLayoutHoriz t = renamed [Replace "Tabbed Horiz"] $ reflectVert $ t CustomShrink $ tabbedTheme
469tabbedTheme = def
470 { activeColor = "black"
471 , inactiveColor = "black"
472 , urgentColor = "black"
473 , activeBorderColor = gray
474 , inactiveBorderColor = darkGray
475 , urgentBorderColor = red
476 , activeTextColor = gray
477 , inactiveTextColor = gray
478 , urgentTextColor = gray
479 , decoHeight = 32
480 , fontName = "xft:Fira Sans:pixelsize=21"
481 }
482
483main :: IO ()
484main = do
485 arguments <- either (const []) id <$> tryIOError getArgs
486 case arguments of
487 ["--command", s] -> do
488 d <- openDisplay ""
489 rw <- rootWindow d $ defaultScreen d
490 a <- internAtom d "XMONAD_COMMAND" False
491 m <- internAtom d s False
492 allocaXEvent $ \e -> do
493 setEventType e clientMessage
494 setClientMessageEvent e rw a 32 m currentTime
495 sendEvent d rw False structureNotifyMask e
496 sync d False
497 _ -> do
498 -- batteryMon <- xfork $ monitorBattery Nothing Nothing
499 hostname <- getHostName
500 let
501 host = hostFromName hostname
502 setEnv "HOST" hostname
503 let myConfig = withHostUrgency . ewmhFullscreen . ewmh . pagerHints $ docks def
504 { manageHook = hManageHook host
505 , terminal = "alacritty"
506 , layoutHook = smartBorders . avoidStruts $ windowNavigation layout'
507 , logHook = do
508 dynamicLogString xmobarPP' >>= writeProps
509 updatePointer (99 % 100, 98 % 100) (0, 0)
510 , modMask = mod4Mask
511 , keys = \conf -> hKeysMod host conf $ myKeys' conf host
512 , workspaces = take (length numKeys) $ map wsp [1..]
513 , startupHook = setDefaultCursor xC_left_ptr
514 , normalBorderColor = darkGray
515 , focusedBorderColor = gray
516 , handleEventHook = serverModeEventHookCmd' (hCmds host) <+> keyUpEventHook
517 }
518 writeProps str = do
519 let encodeCChar = map $ fromIntegral . fromEnum
520 atoms = [ "_XMONAD_WORKSPACES"
521 , "_XMONAD_LAYOUT"
522 , "_XMONAD_TITLE"
523 ]
524 (flip mapM_) (zip atoms (lines str)) $ \(atom', content) -> do
525 ustring <- getAtom "UTF8_STRING"
526 atom <- getAtom atom'
527 withDisplay $ \dpy -> io $ do
528 root <- rootWindow dpy $ defaultScreen dpy
529 changeProperty8 dpy root atom ustring propModeReplace $ encodeCChar content
530 sync dpy True
531 wsp = hWsp host
532 -- We can´t define per-host layout modifiers because we lack dependent types
533 layout' = onHost "skadhi" ( onWorkspace (wsp 1) (Full ||| withIM (1%5) (Title "Buddy List") tabbedLayout') $
534 onWorkspace (wsp 10) Full $
535 onWorkspace (wsp 2) (Full ||| tabbedLayout') $
536 onWorkspace (wsp 5) tabbedLayout' $
537 onWorkspace (wsp 8) (withIM (1%5) (Title "Friends") tabbedLayout') $
538 defaultLayouts
539 ) $
540 onHost "vali" ( onWorkspace (wsp 2) (Full ||| tabbedLayout' ||| combineTwo (TwoPane 0.01 0.57) Full tabbedLayout') $
541 onWorkspace (wsp 3) workLayouts $
542 defaultLayouts
543 ) $
544 onHost "hel" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
545 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
546 onWorkspace (wsp 3) workLayouts $
547 onWorkspace (wsp 6) workLayouts $
548 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
549 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
550 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
551 defaultLayouts
552 ) $
553 onHost "sif" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
554 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
555 onWorkspace (wsp 3) workLayouts $
556 onWorkspace (wsp 6) workLayouts $
557 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
558 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
559 onWorkspace (wsp 8) tabbedLayout''' $
560 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
561 defaultLayouts
562 ) $
563 defaultLayouts
564 -- tabbedLayout''' = renamed [Replace "Tabbed'"] $ IfMax 1 (noBorders Full) (tabbedLayout tabbedBottomAlways)
565 tabbedLayout''' = tabbedLayout tabbedBottom
566 tabbedLayout' = tabbedLayout tabbedBottomAlways
567 tabbedLayoutHoriz' = tabbedLayoutHoriz tabbedLeftAlways
568 defaultLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW 1 (5 % 100) ||| tabbedLayout' ||| Full
569 -- workLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW (2 % 1) (5 % 100) ||| tabbedLayout' ||| Full
570 workLayouts = tabbedLayout' ||| (renamed [Replace "Combined"] $ combineTwoP (TwoPane (1 % 100) (1891 % 2560)) tabbedLayout''' (Column 1.6) (ClassName "Postman" `Or` ClassName "Emacs" `Or` ClassName "jetbrains-idea-ce" `Or` (Resource "Devtools" `And` ClassName "Firefox"))) ||| Full ||| Dwindle R CW 1 (5 % 100)
571 sqrtTwo = approxRational (sqrt 2) (1 / 2560)
572 xmobarPP' = xmobarPP { ppTitle = shorten 80
573 , ppSort = (liftM2 (.)) getSortByIndex $ return scratchpadFilterOutWorkspace
574 , ppUrgent = wrap "(" ")" . xmobarColor "#800000" ""
575 , ppHiddenNoWindows = xmobarColor "#202020" "" . wrap "(" ")"
576 , ppVisible = wrap "(" ")" . xmobarColor "#808000" ""
577 , ppCurrent = wrap "(" ")" . xmobarColor "#008000" ""
578 , ppHidden = wrap "(" ")"
579 , ppWsSep = " "
580 , ppSep = "\n"
581 }
582 withHostUrgency = case hostname of
583 "sif" -> withUrgencyHookC urgencyHook' $ def { suppressWhen = U.Never, remindWhen = Every 2 }
584 _ -> id
585 urgencyHook' window = do
586 let blinkLight = (lightHigh >> threadDelay 0.5e6) `finally` lightLow
587 where
588 lightHigh =
589 writeFile "/sys/class/leds/input0::capslock/brightness" =<< readFile "/sys/class/leds/input0::capslock/max_brightness"
590 lightLow = writeFile "/sys/class/leds/input0::capslock/brightness" "0"
591 runQuery ((resource =? "comm" <||> resource =? "Pidgin" <||> className =? "Gajim" <||> className =? "Skype" <||> className =? "Thunderbird") --> void (xfork blinkLight)) window
592 urgencyHook (BorderUrgencyHook { urgencyBorderColor = red }) window
593 shutdown :: SomeException -> IO a
594 shutdown e = do
595 let pids = [ -- batteryMon
596 ]
597 mapM_ (signalProcess sigTERM) pids
598 mapM_ (getProcessStatus False False) pids
599 throw e
600 keyUpEventHook :: Event -> X All
601 keyUpEventHook event = handle event >> return (All True)
602 where
603 handle (KeyEvent { ev_event_type = t, ev_state = m, ev_keycode = code })
604 | t == keyRelease = withDisplay $ \dpy -> do
605 s <- io $ keycodeToKeysym dpy code 0
606 mClean <- cleanMask m
607 ks <- asks $ hKeyUpKeys host . config
608 userCodeDef () $ whenJust (Map.lookup (mClean, s) ks) id
609 | otherwise = return ()
610 handle _ = return ()
611 handle shutdown $ launch myConfig =<< getDirectories
612
613secs :: Int -> Int
614secs = (* 1000000)
615
616-- monitorBattery :: Maybe BatteryContext -> Maybe Notification -> IO ()
617-- monitorBattery Nothing n = do
618-- ctx <- batteryContextNew
619-- case ctx of
620-- Nothing -> threadDelay (secs 10) >> monitorBattery Nothing n
621-- Just _ -> monitorBattery ctx n
622-- monitorBattery ctx@(Just ctx') n = do
623-- batInfo <- getBatteryInfo ctx'
624-- case batInfo of
625-- Nothing -> threadDelay (secs 1) >> monitorBattery ctx n
626-- Just batInfo -> do
627-- let n'
628-- | batteryState batInfo == BatteryStateDischarging
629-- , timeLeft <= 1200
630-- , timeLeft > 0 = Just $ summary "Discharging" <> hint "value" percentage <> urgency u <> body (duz timeLeft ++ "left")
631-- | otherwise = Nothing
632-- u
633-- | timeLeft <= 600 = Critical
634-- | timeLeft <= 1800 = Normal
635-- | otherwise = Low
636-- timeLeft = batteryTimeToEmpty batInfo
637-- percentage :: Int32
638-- percentage = round $ batteryPercentage batInfo
639-- ts = [("s", 60), ("m", 60), ("h", 24), ("d", 365), ("y", 1)]
640-- duz ms = ss
641-- where (ss, _) = foldl (\(ss, x) (s, y) -> ((if rem x y > 0 then show (rem x y) ++ s ++ " " else "") ++ ss , quot x y)) ("", ms) ts
642-- case n' of
643-- Just n' -> Notify.display (maybe mempty reuse n <> Notify.appName "monitorBattery" <> n') >>= (\n -> threadDelay (secs 2) >> monitorBattery ctx (Just n))
644-- Nothing -> threadDelay (secs 30) >> monitorBattery ctx n
645
646disableTouchpad, disableTrackpoint, enableTrackpoint, enableTouchpad :: X ()
647enableTouchpad = safeSpawn "xinput" ["enable", "SynPS/2 Synaptics TouchPad"]
648disableTouchpad = safeSpawn "xinput" ["disable", "SynPS/2 Synaptics TouchPad"]
649enableTrackpoint = safeSpawn "xinput" ["enable", "TPPS/2 IBM TrackPoint"]
650disableTrackpoint = safeSpawn "xinput" ["disable", "TPPS/2 IBM TrackPoint"]
651
652isDisabled :: String -> X Bool
653isDisabled str = do
654 out <- runProcessWithInput "xinput" ["list", str] ""
655 return $ "disabled" `isInfixOf` out
656
657
658spawnKeychain :: X ()
659spawnKeychain = do
660 home <- liftIO getHomeDirectory
661 let keys = (map ((home </>) . (".ssh/" ++)) ["id", "id-rsa"]) ++ ["6B13AA67"]
662 liftIO (maybe (return ()) (setEnv "SSH_ASKPASS") =<< findAskpass)
663 safeSpawn "keychain" . (["--agents", "gpg,ssh"] ++)=<< liftIO (filterM doesFileExist keys)
664 where
665 findAskpass = filter `liftM` readFile "/etc/zshrc"
666 filter = listToMaybe . catMaybes . map (stripPrefix "export SSH_ASKPASS=") . lines
667
668assimilateKeychain :: X ()
669assimilateKeychain = liftIO $ assimilateKeychain' >> return ()
670assimilateKeychain' = tryIOError $ do
671 -- pid <- getProcessID
672 -- tmpDir <- lookupEnv "TMPDIR"
673 -- let tmpDir' = fromMaybe "/tmp" tmpDir
674 -- tmpFile = tmpDir' </> "xmonad-keychain" ++ (show pid) ++ ".env"
675 env <- runProcessWithInput "sh" ["-c", "eval $(keychain --eval --noask --agents gpg,ssh); env"] "" -- > " ++ tmpFile] ""
676 -- env <- readFile tmpFile
677 let envVars = Map.fromList $ map (\(k, v) -> (k, tail' v)) $ map (span (/= '=')) $ envLines
678 envVars' = Map.filterWithKey (\k _ -> k `elem` transfer) envVars
679 transfer = ["SSH_AUTH_SOCK", "SSH_AGENT_PID", "GPG_AGENT_INFO"]
680 envLines = filter (elem '=') $ lines env :: [String]
681 sequence $ map (\(k, c) -> setEnv k c) $ Map.toList envVars'
682 -- removeFile tmpFile
683 where
684 tail' [] = []
685 tail' (x:xs) = xs
686
687
688numKeys = [xK_parenleft, xK_parenright, xK_braceright, xK_plus, xK_braceleft, xK_bracketright, xK_bracketleft, xK_exclam, xK_equal, xK_asterisk]
689
690instance Shrinker CustomShrink where
691 shrinkIt _ "" = [""]
692 shrinkIt s cs
693 | length cs >= 4 = cs : shrinkIt s ((reverse . drop 4 . reverse $ cs) ++ "...")
694 | otherwise = cs : shrinkIt s (init cs)
695
696xPConfig, xPConfigMonospace :: XPConfig
697xPConfig = def
698 { font = "xft:Fira Sans:pixelsize=21"
699 , height = 32
700 , bgColor = "black"
701 , fgColor = gray
702 , fgHLight = green
703 , bgHLight = "black"
704 , borderColor = gray
705 , searchPredicate = (\needle haystack -> all (`isInfixOf` map toLower haystack) . map (map toLower) $ words needle)
706 , position = Top
707 }
708xPConfigMonospace = xPConfig { font = "xft:Fira Code:pixelsize=21" }
709
710sshOverrides host = map (\h -> mkOverride { oHost = h, oCommand = moshCmd . inTmux host} )
711 [ "odin"
712 , "ymir"
713 , "surtr"
714 , "vidhar"
715 , "srv02.uniworx.de"
716 ]
717 ++
718 map (\h -> mkOverride { oHost = h, oCommand = moshCmd' "/run/current-system/sw/bin/mosh-server" . withEnv [("TERM", "xterm")] . inTmux host} )
719 [ "bragi", "bragi.asgard.yggdrasil"
720 ]
721 ++
722 map (\h -> mkOverride { oHost = h, oCommand = sshCmd . inTmux host } )
723 [ "uni2work-dev1", "srv01.uniworx.de"
724 ]
725 ++
726 map (\h -> mkOverride { oHost = h, oCommand = sshCmd . withEnv [("TERM", "xterm")] . inTmux host } )
727 [ "remote.cip.ifi.lmu.de"
728 , "uniworx3", "uniworx4", "uniworx5", "uniworxdb2"
729 , "testworx"
730 ]
731
732backlight :: (Rational -> Rational) -> X ()
733backlight f = void . xfork . liftIO $ do
734 [ _device
735 , _class
736 , read . Text.unpack -> currentBright
737 , _currentPercentage
738 , read . Text.unpack -> maximumBright
739 ] <- Text.splitOn "," . Text.pack <$> readProcess "brightnessctl" ["-m"] ""
740 let current = currentBright % maximumBright
741 new' = f current * fromIntegral maximumBright
742 new :: Integer
743 new | floor new' < 0 = 0
744 | ceiling new' > maximumBright = maximumBright
745 | new' >= maximumBright % 2 = ceiling new'
746 | otherwise = floor new'
747 callProcess "brightnessctl" ["-m", "s", show new]
748
749cycleThrough :: [Rational] -> (Rational -> Rational)
750cycleThrough opts current = fromMaybe currentOpt $ listToMaybe next'
751 where currentOpt = minimumBy (comparing $ abs . subtract current) opts
752 (_, _ : next') = break (== currentOpt) opts
753
754cycleKbLayout :: [(String, Maybe String)] -> X ()
755cycleKbLayout [] = return ()
756cycleKbLayout layouts = liftIO $ do
757 next <- (getNext . extract) `liftM` runProcessWithInput "setxkbmap" ["-query"] ""
758 let
759 args = case next of
760 (l, Just v) -> [l, v]
761 (l, Nothing) -> [l]
762 safeSpawn "setxkbmap" args
763 where
764 extract :: String -> Maybe (String, Maybe String)
765 extract str = listToMaybe $ do
766 ["layout:", l] <- str'
767 [(l, Just v) | ["variant:", v] <- str'] ++ pure (l, Nothing)
768 where
769 str' = map words $ lines str
770 getNext :: Maybe (String, Maybe String) -> (String, Maybe String)
771 getNext = maybe (head layouts) getNext'
772 getNext' x = case elemIndex x layouts of
773 Nothing -> getNext Nothing
774 Just i -> layouts !! ((i + 1) `mod` length layouts)
775
776mpvAll' :: MpvCommand -> IO [MpvResponse]
777mpvAll' = mpvAll "/var/media/.mpv-ipc"
778
779mpvOne' :: MpvCommand -> IO (Maybe MpvResponse)
780mpvOne' = mpvOne "/var/media/.mpv-ipc"
781
782mediaMpv :: MpvCommand -> X ()
783mediaMpv cmd = void . xfork $ print =<< mpvAll' cmd
784
785mediaMpvTogglePause :: X ()
786mediaMpvTogglePause = void . xfork $ do
787 paused <- mapM mpvResponse <=< mpvAll' $ MpvGetProperty "pause"
788 if
789 | and paused -> print <=< mpvAll' $ MpvSetProperty "pause" False
790 | otherwise -> print <=< mpvOne' $ MpvSetProperty "pause" True
791
792myKeys' conf host = Map.fromList $
793 -- launch a terminal
794 [ ((modm, xK_Return), spawn $ (XMonad.terminal conf) ++ " -e tmux")
795 , ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
796
797 -- launch dmenu
798 --, ((modm, xK_d ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"")
799 , ((modm, xK_d ), shellPrompt "Run: " xPConfigMonospace)
800 , ((modm .|. shiftMask, xK_d ), prompt "Run in Terminal: " ("alacritty" ++ " -e") xPConfigMonospace)
801 , ((modm, xK_at ), sshPrompt (sshOverrides . Just $ hName host) xPConfigMonospace)
802
803 -- close focused window
804 , ((modm .|. shiftMask, xK_q ), kill)
805 , ((modm .|. controlMask .|. shiftMask, xK_q ), spawn "xkill")
806
807 -- Rotate through the available layout algorithms
808 , ((modm, xK_space ), sendMessage NextLayout)
809
810 -- Reset the layouts on the current workspace to default
811 , ((modm .|. controlMask, xK_r ), (setLayout $ XMonad.layoutHook conf) >> refresh)
812
813 -- Resize viewed windows to the correct size
814 , ((modm, xK_r ), refresh)
815
816 -- Move focus to the next window
817 , ((modm, xK_t ), windows W.focusDown)
818
819 -- Move focus to the previous window
820 , ((modm, xK_n ), windows W.focusUp )
821
822 -- Move focus to the master window
823 , ((modm, xK_m ), windows W.focusMaster )
824
825 -- Swap the focused window and the master window
826 , ((modm .|. shiftMask, xK_m ), windows W.swapMaster)
827
828 -- Swap the focused window with the next window
829 , ((modm .|. shiftMask, xK_t ), windows W.swapDown )
830
831 -- Swap the focused window with the previous window
832 , ((modm .|. shiftMask, xK_n ), windows W.swapUp )
833
834 -- Swap the focused window with the previous window
835 , ((modm .|. shiftMask .|. controlMask, xK_m), sendMessage SwapWindow)
836
837 , ((modm, xK_Right), sendMessage $ Go R)
838 , ((modm, xK_Left ), sendMessage $ Go L)
839 , ((modm, xK_Up ), sendMessage $ Go U)
840 , ((modm, xK_Down ), sendMessage $ Go D)
841 , ((modm .|. shiftMask , xK_Right), sendMessage $ Move R)
842 , ((modm .|. shiftMask , xK_Left ), sendMessage $ Move L)
843 , ((modm .|. shiftMask , xK_Up ), sendMessage $ Move U)
844 , ((modm .|. shiftMask , xK_Down ), sendMessage $ Move D)
845 -- , ((modm .|. controlMask, xK_Right), withFocused $ keysMoveWindow (10, 0))
846 -- , ((modm .|. controlMask, xK_Left ), withFocused $ keysMoveWindow (-10, 0))
847 -- , ((modm .|. controlMask, xK_Up ), withFocused $ keysMoveWindow (0, -10))
848 -- , ((modm .|. controlMask, xK_Down ), withFocused $ keysMoveWindow (0, 10))
849 -- Shrink the master area
850 , ((modm, xK_h ), sendMessage Shrink)
851
852 -- Expand the master area
853 , ((modm, xK_s ), sendMessage Expand)
854
855 -- Push window back into tiling
856 , ((modm .|. shiftMask, xK_space ), withFocused $ windows . W.sink)
857 , ((modm, xK_BackSpace), focusUrgent)
858 , ((modm .|. shiftMask, xK_BackSpace), clearUrgents)
859
860 -- Increment the number of windows in the master area
861 , ((modm , xK_comma ), sendMessage (IncMasterN 1))
862
863 -- Deincrement the number of windows in the master area
864 , ((modm , xK_period), sendMessage (IncMasterN (-1)))
865
866 , ((0, xF86XK_AudioRaiseVolume), safeSpawn "pamixer" ["-i", "2"])
867 , ((0, xF86XK_AudioLowerVolume), safeSpawn "pamixer" ["-d", "2"])
868 , ((0, xF86XK_AudioMute), safeSpawn "pamixer" ["-t"])
869 , ((0, xF86XK_AudioPause), mediaMpv $ MpvSetProperty "pause" False)
870 , ((0, {-xF86XK_AudioMicMute-} 269025202), safeSpawn "pulseaudio-ctl" ["mute-input"])
871 , ((0, xF86XK_AudioPlay), mediaMpvTogglePause)
872 , ((0, xK_Print), do
873 home <- liftIO getHomeDirectory
874 unGrab
875 safeSpawn "scrot" ["-s", "-F", home </> "screenshots" </> "%Y-%m-%dT%H:%M:%S.png", "-e", "xclip -selection clipboard -t image/png -i $f"]
876 )
877 , ((modm .|. mod1Mask, xK_space), mediaMpvTogglePause)
878
879 -- , ((0, xF86XK_MonBrightnessDown), backlight . cycleThrough $ reverse brCycle)
880 -- , ((0, xF86XK_MonBrightnessUp ), backlight $ cycleThrough brCycle)
881 , ((modm .|. shiftMask , xK_b), backlight . cycleThrough $ reverse brCycle)
882 , ((modm .|. shiftMask .|. controlMask, xK_b), backlight $ cycleThrough brCycle)
883
884 , ((modm , xK_Escape), cycleKbLayout (hKbLayouts host))
885 , ((modm .|. controlMask, xK_Escape), safeSpawn "setxkbmap" $ fst (head $ hKbLayouts host) : maybeToList (snd . head $ hKbLayouts host))
886
887 -- Toggle the status bar gap
888 -- Use this binding with avoidStruts from Hooks.ManageDocks.
889 -- See also the statusBar function from Hooks.DynamicLog.
890 --
891 , ((modm , xK_b ), sendMessage ToggleStruts)
892
893 , ((modm .|. shiftMask, xK_p ), safeSpawn "playerctl" ["-a", "pause"])
894
895 -- Quit xmonad
896 , ((modm .|. shiftMask, xK_e ), io (exitWith ExitSuccess))
897
898 -- Restart xmonad
899 -- , ((modm .|. shiftMask .|. controlMask, xK_r ), void . xfork $ recompile False >>= flip when (safeSpawn "xmonad" ["--restart"]))
900 , ((modm .|. shiftMask, xK_r ), void . liftIO $ executeFile "xmonad" True [] Nothing)
901 , ((modm .|. shiftMask, xK_l ), void . xfork $ do
902 sessId <- getEnv "XDG_SESSION_ID"
903 safeSpawn "loginctl" ["lock-session", sessId]
904 )
905 , ((modm .|. shiftMask, xK_s ), safeSpawn "systemctl" ["suspend"])
906 , ((modm .|. shiftMask, xK_h ), inputPromptWithCompl xPConfigMonospace "systemctl" powerActCompl ?+ powerAct)
907 , ((modm, xK_v ), windows copyToAll) -- @@ Make focused window always visible
908 , ((modm .|. shiftMask, xK_v ), killAllOtherCopies) -- @@ Toggle window state back
909 , ((modm .|. shiftMask, xK_g ), windowPrompt xPConfig Goto wsWindows)
910 , ((modm , xK_g ), windowPrompt xPConfig Bring allWindows)
911 ]
912 ++
913
914 --
915 -- mod-[1..9], Switch to workspace N
916 --
917 -- mod-[1..9], Switch to workspace N
918 -- mod-shift-[1..9], Move client to workspace N
919 --
920 [((m .|. modm, k), windows $ f i)
921 | (i, k) <- zip (XMonad.workspaces conf) $ numKeys
922 , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]
923 ]
924 ++
925 [((m .|. modm .|. controlMask, k), void . runMaybeT $
926 MaybeT (P.getScreen def i) >>= MaybeT . screenWorkspace >>= lift . windows . f
927 )
928 | (i, k) <- zip (hScreens host) [xK_g, xK_c, xK_r, xK_l]
929 , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]
930 ]
931 where
932 modm = XMonad.modMask conf
933
934 brCycle = [0, 1 % 500, 1 % 250, 1 % 100, 1 % 10, 1 % 4, 1 % 2, 3 % 4, 1]
935
936 powerActWords = ["poweroff", "reboot", "hibernate", "suspend"]
937 powerActCompl = mkComplFunFromList' xPConfigMonospace powerActWords
938 powerAct act | act `elem` powerActWords = safeSpawn "systemctl" $ pure act
939 | otherwise = return ()
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
index e3f675a1..702990c3 100644
--- a/accounts/gkleen@sif/zshrc
+++ b/accounts/gkleen@sif/zshrc
@@ -2,17 +2,14 @@ dir() {
2 curlArchive=false 2 curlArchive=false
3 templateArchive="" 3 templateArchive=""
4 repoUrl="" 4 repoUrl=""
5 nixShell=""
6 findNix=false
7 dir="" 5 dir=""
8 forceShell=false 6 forceShell=false
9 wormhole=false 7 wormhole=false
10 gitWorktree="" 8 gitWorktree=""
11 # notmuchMsg=""
12 quickserve=false
13 modifyPDF="" 9 modifyPDF=""
10 miniserve=false
14 11
15 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 12 while getopts ':t:a:d:ir:wg:p:m' arg; do
16 case $arg in 13 case $arg in
17 "t") ;; 14 "t") ;;
18 "a") 15 "a")
@@ -23,16 +20,13 @@ dir() {
23 templateArchive=${OPTARG:a} 20 templateArchive=${OPTARG:a}
24 fi 21 fi
25 ;; 22 ;;
26 "s") nixShell=${OPTARG:a} ;;
27 "S") findNix=true ;;
28 "d") dir=${OPTARG} ;; 23 "d") dir=${OPTARG} ;;
29 "i") forceShell=true ;; 24 "i") forceShell=true ;;
30 "r") repoUrl=${OPTARG} ;; 25 "r") repoUrl=${OPTARG} ;;
31 "w") wormhole=true ;; 26 "w") wormhole=true ;;
32 "g") gitWorktree=${OPTARG} ;; 27 "g") gitWorktree=${OPTARG} ;;
33 # "n") notmuchMsg=${OPTARG} ;;
34 "q") quickserve=true ;;
35 "p") modifyPDF=${OPTARG:a} ;; 28 "p") modifyPDF=${OPTARG:a} ;;
29 "m") miniserve=true ;;
36 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 30 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
37 esac 31 esac
38 done 32 done
@@ -56,20 +50,34 @@ dir() {
56 gitWorktree="" 50 gitWorktree=""
57 fi 51 fi
58 52
53 miniservePIDFile=""
54 if [[ ${miniserve} = "true" ]]; then
55 miniservePIDFile=$(mktemp --tmpdir --suffix=.pid)
56 fi
57
59 cleanup() 58 cleanup()
60 { 59 {
61 cd ${modifyPDF:h} 60 if [[ -n ${modifyPDF} ]]; then
62 [[ -n ${modifyPDF} ]] && nix shell nixos#imagemagick -c convert -verbose ${dir}/${modifyPDF:t:r}_*.png(on) ${modifyPDF} 61 cd ${modifyPDF:h}
62 typeset -a pages
63 eval 'pages=(${dir}/${modifyPDF:t:r}_*.png(on))'
64 magick -verbose "$pages" ${modifyPDF}
65 modifyPDF=""
66 fi
67 if [[ -n ${miniservePIDFile} ]]; then
68 command kill --verbose -- $(cat ${miniservePIDFile}) && wait $(cat ${miniservePIDFile})
69 miniservePIDFile=""
70 fi
63 } 71 }
64 72
65 ( 73 (
74 set -o localoptions -o localtraps
75 trap 'return 1' INT TERM
66 trap cleanup EXIT 76 trap cleanup EXIT
67 77
68 cd ${dir} 78 cd ${dir}
69 export dir; 79 export dir;
70 80
71 ${findNix} && { nixShell=$(findNix) || return $? }
72
73 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} . 81 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} .
74 82
75 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF} 83 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF}
@@ -82,23 +90,23 @@ dir() {
82 } 90 }
83 trap cleanup EXIT 91 trap cleanup EXIT
84 92
85 if ${curlArchive}; then 93 if [[ $curlArchive = "true" ]]; then
86 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}") 94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
87 95
88 curl -L -o ${archiveFile} ${templateArchive} 96 curl -SfL -o ${archiveFile} ${templateArchive}
89 97
90 templateArchive=${archiveFile} 98 templateArchive=${archiveFile}
91 fi 99 fi
92 100
93 unpack=true 101 unpack=true
94 while ${unpack}; do 102 while [[ $unpack = "true" ]]; do
95 case $(file --brief --mime-type --dereference ${templateArchive}) in 103 case $(file --brief --mime-type --dereference ${templateArchive}) in
96 application/zip) 104 application/zip)
97 unzip ${templateArchive} 105 unzip ${templateArchive}
98 unpack=false 106 unpack=false
99 ;; 107 ;;
100 application/vnd.debian.binary-package) 108 application/vnd.debian.binary-package)
101 nix shell nixos#binutils --command ar x ${templateArchive} 109 ar x ${templateArchive}
102 mkdir control data 110 mkdir control data
103 tar -C control -xvaf control.* 111 tar -C control -xvaf control.*
104 tar -C data -xvaf data.* 112 tar -C data -xvaf data.*
@@ -106,7 +114,7 @@ dir() {
106 ;; 114 ;;
107 application/x-rpm) 115 application/x-rpm)
108 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio") 116 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio")
109 nix shell nixos#busybox --command rpm2cpio ${templateArchive} > ${cpioArchive} 117 rpm2cpio ${templateArchive} > ${cpioArchive}
110 templateArchive=${cpioArchive} 118 templateArchive=${cpioArchive}
111 unpack=true 119 unpack=true
112 ;; 120 ;;
@@ -115,15 +123,19 @@ dir() {
115 unpack=false 123 unpack=false
116 ;; 124 ;;
117 application/pdf) 125 application/pdf)
118 nix shell nixos#ghostscript nixos#imagemagick -c convert -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png 126 magick -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png
119 unpack=false 127 unpack=false
120 ;; 128 ;;
121 application/octet-stream) 129 application/octet-stream)
122 if [[ $(file --brief --dereferenc ${templateArchive}) =~ Squashfs ]]; then 130 if [[ $(file --brief --dereference ${templateArchive}) =~ Squashfs ]]; then
123 nix shell nixos#squashfsTools -c unsquashfs -d . ${templateArchive} 131 unsquashfs -d . ${templateArchive}
124 unpack=false 132 unpack=false
125 fi 133 fi
126 ;; 134 ;;
135 application/x-iso9660-image)
136 7z x ${templateArchive}
137 unpack=false
138 ;;
127 *) 139 *)
128 tar -xvaf ${templateArchive} 140 tar -xvaf ${templateArchive}
129 unpack=false 141 unpack=false
@@ -134,25 +146,21 @@ dir() {
134 fi 146 fi
135 147
136 148
137 ${wormhole} && wormhole receive --accept-file 149 [[ $wormhole = "true" ]] && wormhole receive --accept-file
138 150
139 151
140 if ${quickserve}; then 152 if [[ ${#@} -gt 0 ]]; then
141 quickserve --root . --upload . --show-hidden --tar gz 153 ${@}
142 fi 154 fi
143 155
156 cd $(pwd) # Needed for mounting to work
144 157
145 if [[ ${#@} -eq 0 ]] || ${forceShell}; then 158 if [[ ${miniserve} = "true" ]]; then
146 if [[ ${#@} -gt 0 ]]; then 159 miniserve --random-route --hidden --enable-tar-gz --enable-zip . &
147 if [[ -z ${nixShell} ]]; then 160 echo $! > "${miniservePIDFile}"
148 ${@} 161 fi
149 else
150 nix-shell ${nixShell} --run "${@}"
151 fi
152 fi
153
154 cd $(pwd) # Needed for mounting to work
155 162
163 if [[ ${#@} -eq 0 ]] && [[ ${miniserve} != "true" ]] || [[ $forceShell = "true" ]]; then
156 isSingleDir() { 164 isSingleDir() {
157 typeset -a contents 165 typeset -a contents
158 contents=(*(N) .*(N)) 166 contents=(*(N) .*(N))
@@ -166,18 +174,9 @@ dir() {
166 } 174 }
167 while d=$(isSingleDir); do cd ${d}; done 175 while d=$(isSingleDir); do cd ${d}; done
168 176
169 177 zsh
170 if [[ -z ${nixShell} ]]; then 178 elif [[ ${miniserve} == "true" ]]; then
171 zsh 179 wait $(cat "${miniservePIDFile}")
172 else
173 nix-shell ${nixShell} --run zsh
174 fi
175 else
176 if [[ -z ${nixShell} ]]; then
177 ${@}
178 else
179 nix-shell ${nixShell} --run "${@}"
180 fi
181 fi 180 fi
182 ) 181 )
183} 182}
@@ -185,27 +184,30 @@ dir() {
185tmpdir() { 184tmpdir() {
186 cleanup() 185 cleanup()
187 { 186 {
188 cd / 187 cd /
189 unmount() { 188 unmount() {
190 printf "Unmounting %s\n" ${1} >&2 189 printf "Unmounting %s\n" ${1} >&2
191 fusermount -u ${1} || umount ${1} || sudo umount ${1} 190 fusermount -u ${1} || umount ${1} || sudo umount ${1}
192 } 191 }
193 192
194 if mountpoint -q -- ${dir}; then 193 if [[ -n ${dir} ]]; then
195 unmount ${dir} || return $? 194 if mountpoint -q -- ${dir}; then
196 else 195 unmount ${dir} || return $?
197 while read -d $'\0' subDir; do 196 else
198 mountpoint -q -- ${subDir} || continue 197 while read -d $'\0' subDir; do
199 unmount ${subDir} || return $? 198 mountpoint -q -- ${subDir} || continue
200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr) 199 unmount ${subDir} || return $?
201 fi 200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr)
202 201 fi
203 rm -rfv --one-file-system -- ${dir} 202
203 rm -rfv --one-file-system -- ${dir}
204 dir=""
205 fi
204 } 206 }
205 207
206 local tmpdir="" 208 local tmpdir=""
207 209
208 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 210 while getopts ':t:a:d:ir:wg:p:m' arg; do
209 case $arg in 211 case $arg in
210 "t") tmpdir="=${OPTARG}" ;; 212 "t") tmpdir="=${OPTARG}" ;;
211 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 213 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
@@ -213,6 +215,8 @@ tmpdir() {
213 done 215 done
214 216
215 ( 217 (
218 set -o localoptions -o localtraps
219 trap 'return 1' INT TERM
216 trap cleanup EXIT 220 trap cleanup EXIT
217 221
218 222
@@ -231,17 +235,7 @@ clock() {
231} 235}
232 236
233public-ip() { 237public-ip() {
234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip' 238 curl -sSf -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
235}
236
237nix-ghci() {
238 pkgExpr=""
239 if [[ ${#@} -gt 0 ]]; then
240 pkgExpr="${1}"
241 shift
242 fi
243
244 nix-shell -p "with (import <nixpkgs> {}); pkgs.haskellPackages.ghcWithPackages (p: with p; [${pkgExpr}])" --run "ghci ${@}"
245} 239}
246 240
247swap() { 241swap() {
@@ -271,14 +265,6 @@ l() {
271 ls --long --binary --git --time-style=iso --header $@ 265 ls --long --binary --git --time-style=iso --header $@
272} 266}
273 267
274re() {
275 systemctl --restart $@
276}
277
278ure() {
279 systemctl --user --restart $@
280}
281
282ssh-installer() { 268ssh-installer() {
283 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@ 269 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@
284} 270}
@@ -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