summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.sops.yaml3
-rw-r--r--_sources/generated.json68
-rw-r--r--_sources/generated.nix50
-rw-r--r--accounts/gkleen@sif/default.nix73
-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.el1
-rw-r--r--accounts/gkleen@sif/libvirt/default.nix1
-rw-r--r--accounts/gkleen@sif/niri/default.nix576
-rw-r--r--accounts/gkleen@sif/niri/mako.nix115
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix65
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix137
-rw-r--r--accounts/gkleen@sif/systemd.nix144
-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/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--flake.lock74
-rw-r--r--flake.nix4
-rw-r--r--hosts/sif/default.nix95
-rw-r--r--hosts/sif/greetd/default.nix44
-rw-r--r--hosts/sif/greetd/wallpaper.pngbin0 -> 6073128 bytes
-rw-r--r--hosts/sif/hw.nix3
-rw-r--r--hosts/sif/libvirt/default.nix1
-rw-r--r--hosts/sif/mail/default.nix2
-rw-r--r--modules/nix-access-tokens/default.nix24
-rw-r--r--modules/nix-access-tokens/nix.conf32
-rw-r--r--nvfetcher.toml20
-rw-r--r--overlays/batman-adv.nix15
-rw-r--r--overlays/keepassxc/database-open-dialog.patch126
-rw-r--r--overlays/keepassxc/default.nix8
-rw-r--r--overlays/mako.nix5
-rw-r--r--overlays/swayosd.nix27
-rwxr-xr-xoverlays/worktime/worktime/__main__.py119
55 files changed, 1465 insertions, 2528 deletions
diff --git a/.sops.yaml b/.sops.yaml
index 7b0507ee..948383b2 100644
--- a/.sops.yaml
+++ b/.sops.yaml
@@ -26,3 +26,6 @@ creation_rules:
26 - path_regex: ^hosts/sif/ 26 - path_regex: ^hosts/sif/
27 key_groups: 27 key_groups:
28 - age: [ *admin_gkleen, *machine_sif ] 28 - age: [ *admin_gkleen, *machine_sif ]
29 - path_regex: ^modules/nix-access-tokens/
30 key_groups:
31 - age: [ *admin_gkleen, *machine_sif, *machine_surtr, *machine_vidhar ]
diff --git a/_sources/generated.json b/_sources/generated.json
index 47e088c4..72f913ec 100644
--- a/_sources/generated.json
+++ b/_sources/generated.json
@@ -20,20 +20,6 @@
20 }, 20 },
21 "version": "8ef9a5b73e5d1063cf912c70027c655fb19d1109" 21 "version": "8ef9a5b73e5d1063cf912c70027c655fb19d1109"
22 }, 22 },
23 "batman-adv": {
24 "cargoLocks": null,
25 "date": null,
26 "extract": null,
27 "name": "batman-adv",
28 "passthru": null,
29 "pinned": false,
30 "src": {
31 "sha256": "sha256-VYyIkH5IFfKN6EOHZxSx6AaepD3a22/hhmLhqkle5Z0=",
32 "type": "tarball",
33 "url": "https://downloads.open-mesh.org/batman/stable/sources/batman-adv/batman-adv-2024.4.tar.gz"
34 },
35 "version": "2024.4"
36 },
37 "bpf-examples": { 23 "bpf-examples": {
38 "cargoLocks": null, 24 "cargoLocks": null,
39 "date": "2025-01-03", 25 "date": "2025-01-03",
@@ -111,6 +97,26 @@
111 }, 97 },
112 "version": "2.17" 98 "version": "2.17"
113 }, 99 },
100 "mako": {
101 "cargoLocks": null,
102 "date": "2025-01-19",
103 "extract": null,
104 "name": "mako",
105 "passthru": null,
106 "pinned": false,
107 "src": {
108 "deepClone": false,
109 "fetchSubmodules": false,
110 "leaveDotGit": false,
111 "name": null,
112 "rev": "57a258c1f8861200e0623153f1b79065d4ddabd8",
113 "sha256": "sha256-9PcZLpIfGR8SmZf5e2rDZhF+y3kfSaFw5DneDXHMGTc=",
114 "sparseCheckout": [],
115 "type": "git",
116 "url": "https://github.com/emersion/mako"
117 },
118 "version": "57a258c1f8861200e0623153f1b79065d4ddabd8"
119 },
114 "mpv-autosave": { 120 "mpv-autosave": {
115 "cargoLocks": null, 121 "cargoLocks": null,
116 "date": "2020-10-22", 122 "date": "2020-10-22",
@@ -359,6 +365,26 @@
359 }, 365 },
360 "version": "0.2.1" 366 "version": "0.2.1"
361 }, 367 },
368 "swayosd": {
369 "cargoLocks": null,
370 "date": "2025-01-27",
371 "extract": null,
372 "name": "swayosd",
373 "passthru": null,
374 "pinned": false,
375 "src": {
376 "deepClone": false,
377 "fetchSubmodules": false,
378 "leaveDotGit": false,
379 "name": null,
380 "rev": "993180b5e7db1dfc453a556bf208f05b04283c8f",
381 "sha256": "sha256-qwtGkRJlCYu+dO3xCmnRexX+E4QvXRAHXUslLO7mrAI=",
382 "sparseCheckout": [],
383 "type": "git",
384 "url": "https://github.com/ErikReider/SwayOSD"
385 },
386 "version": "993180b5e7db1dfc453a556bf208f05b04283c8f"
387 },
362 "tomorrow-night-paradise-theme": { 388 "tomorrow-night-paradise-theme": {
363 "cargoLocks": null, 389 "cargoLocks": null,
364 "date": "2012-06-04", 390 "date": "2012-06-04",
@@ -381,7 +407,7 @@
381 }, 407 },
382 "v4l2loopback": { 408 "v4l2loopback": {
383 "cargoLocks": null, 409 "cargoLocks": null,
384 "date": "2024-11-26", 410 "date": "2025-01-18",
385 "extract": null, 411 "extract": null,
386 "name": "v4l2loopback", 412 "name": "v4l2loopback",
387 "passthru": null, 413 "passthru": null,
@@ -393,12 +419,12 @@
393 "name": null, 419 "name": null,
394 "owner": "umlaeute", 420 "owner": "umlaeute",
395 "repo": "v4l2loopback", 421 "repo": "v4l2loopback",
396 "rev": "e750af9eb17d729b8c5257a4bcd2faba2b28029c", 422 "rev": "39ad8a43522c18b5e4f4363ce053f604312fc413",
397 "sha256": "sha256-ePA1LcxQInrLLpbZ7Wljv75lWl6V6s9KkdMp0tF1vhk=", 423 "sha256": "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8=",
398 "sparseCheckout": [], 424 "sparseCheckout": [],
399 "type": "github" 425 "type": "github"
400 }, 426 },
401 "version": "e750af9eb17d729b8c5257a4bcd2faba2b28029c" 427 "version": "39ad8a43522c18b5e4f4363ce053f604312fc413"
402 }, 428 },
403 "xcompose": { 429 "xcompose": {
404 "cargoLocks": null, 430 "cargoLocks": null,
@@ -430,10 +456,10 @@
430 "pinned": false, 456 "pinned": false,
431 "src": { 457 "src": {
432 "name": null, 458 "name": null,
433 "sha256": "sha256-rA5ytakBe6EEtCWFRiAafO3DjovSByfgxjt3yCm0Jek=", 459 "sha256": "sha256-HJc4JmkhrUPFaK0BrDNi+3x69Uknb77JK9cvFA2hYkA=",
434 "type": "url", 460 "type": "url",
435 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2024.12.23.tar.gz" 461 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.1.26.tar.gz"
436 }, 462 },
437 "version": "2024.12.23" 463 "version": "2025.1.26"
438 } 464 }
439} \ No newline at end of file 465} \ No newline at end of file
diff --git a/_sources/generated.nix b/_sources/generated.nix
index 161dc4e1..e25f1bda 100644
--- a/_sources/generated.nix
+++ b/_sources/generated.nix
@@ -16,14 +16,6 @@
16 }; 16 };
17 date = "2021-05-30"; 17 date = "2021-05-30";
18 }; 18 };
19 batman-adv = {
20 pname = "batman-adv";
21 version = "2024.4";
22 src = fetchTarball {
23 url = "https://downloads.open-mesh.org/batman/stable/sources/batman-adv/batman-adv-2024.4.tar.gz";
24 sha256 = "sha256-VYyIkH5IFfKN6EOHZxSx6AaepD3a22/hhmLhqkle5Z0=";
25 };
26 };
27 bpf-examples = { 19 bpf-examples = {
28 pname = "bpf-examples"; 20 pname = "bpf-examples";
29 version = "8d53e6fc46ae625bd16b38eb1007ece99460eada"; 21 version = "8d53e6fc46ae625bd16b38eb1007ece99460eada";
@@ -67,6 +59,20 @@
67 sha256 = "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk="; 59 sha256 = "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk=";
68 }; 60 };
69 }; 61 };
62 mako = {
63 pname = "mako";
64 version = "57a258c1f8861200e0623153f1b79065d4ddabd8";
65 src = fetchgit {
66 url = "https://github.com/emersion/mako";
67 rev = "57a258c1f8861200e0623153f1b79065d4ddabd8";
68 fetchSubmodules = false;
69 deepClone = false;
70 leaveDotGit = false;
71 sparseCheckout = [ ];
72 sha256 = "sha256-9PcZLpIfGR8SmZf5e2rDZhF+y3kfSaFw5DneDXHMGTc=";
73 };
74 date = "2025-01-19";
75 };
70 mpv-autosave = { 76 mpv-autosave = {
71 pname = "mpv-autosave"; 77 pname = "mpv-autosave";
72 version = "744c3ee61d2f0a8e9bb4e308dec6897215ae4704"; 78 version = "744c3ee61d2f0a8e9bb4e308dec6897215ae4704";
@@ -218,6 +224,20 @@
218 sha256 = "sha256-7d/0fepOvdswuBGJCCMULB2kXOFBLP78yqX4NmByCF8="; 224 sha256 = "sha256-7d/0fepOvdswuBGJCCMULB2kXOFBLP78yqX4NmByCF8=";
219 }; 225 };
220 }; 226 };
227 swayosd = {
228 pname = "swayosd";
229 version = "993180b5e7db1dfc453a556bf208f05b04283c8f";
230 src = fetchgit {
231 url = "https://github.com/ErikReider/SwayOSD";
232 rev = "993180b5e7db1dfc453a556bf208f05b04283c8f";
233 fetchSubmodules = false;
234 deepClone = false;
235 leaveDotGit = false;
236 sparseCheckout = [ ];
237 sha256 = "sha256-qwtGkRJlCYu+dO3xCmnRexX+E4QvXRAHXUslLO7mrAI=";
238 };
239 date = "2025-01-27";
240 };
221 tomorrow-night-paradise-theme = { 241 tomorrow-night-paradise-theme = {
222 pname = "tomorrow-night-paradise-theme"; 242 pname = "tomorrow-night-paradise-theme";
223 version = "70225a5bf90d495e13a9260bfdc268632ece0801"; 243 version = "70225a5bf90d495e13a9260bfdc268632ece0801";
@@ -234,15 +254,15 @@
234 }; 254 };
235 v4l2loopback = { 255 v4l2loopback = {
236 pname = "v4l2loopback"; 256 pname = "v4l2loopback";
237 version = "e750af9eb17d729b8c5257a4bcd2faba2b28029c"; 257 version = "39ad8a43522c18b5e4f4363ce053f604312fc413";
238 src = fetchFromGitHub { 258 src = fetchFromGitHub {
239 owner = "umlaeute"; 259 owner = "umlaeute";
240 repo = "v4l2loopback"; 260 repo = "v4l2loopback";
241 rev = "e750af9eb17d729b8c5257a4bcd2faba2b28029c"; 261 rev = "39ad8a43522c18b5e4f4363ce053f604312fc413";
242 fetchSubmodules = true; 262 fetchSubmodules = true;
243 sha256 = "sha256-ePA1LcxQInrLLpbZ7Wljv75lWl6V6s9KkdMp0tF1vhk="; 263 sha256 = "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8=";
244 }; 264 };
245 date = "2024-11-26"; 265 date = "2025-01-18";
246 }; 266 };
247 xcompose = { 267 xcompose = {
248 pname = "xcompose"; 268 pname = "xcompose";
@@ -258,10 +278,10 @@
258 }; 278 };
259 yt-dlp = { 279 yt-dlp = {
260 pname = "yt-dlp"; 280 pname = "yt-dlp";
261 version = "2024.12.23"; 281 version = "2025.1.26";
262 src = fetchurl { 282 src = fetchurl {
263 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2024.12.23.tar.gz"; 283 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.1.26.tar.gz";
264 sha256 = "sha256-rA5ytakBe6EEtCWFRiAafO3DjovSByfgxjt3yCm0Jek="; 284 sha256 = "sha256-HJc4JmkhrUPFaK0BrDNi+3x69Uknb77JK9cvFA2hYkA=";
265 }; 285 };
266 }; 286 };
267} 287}
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index bcfd1224..58cfb425 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -80,7 +80,7 @@ let
80 ]; 80 ];
81 }; 81 };
82 82
83 lockCommand = "${config.systemd.package}/bin/systemctl --user start gtklock.service"; 83 lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service";
84in { 84in {
85 imports = with flake.nixosModules.userProfiles.${userName}; [ 85 imports = with flake.nixosModules.userProfiles.${userName}; [
86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
@@ -185,7 +185,12 @@ in {
185 }; 185 };
186 }; 186 };
187 187
188 zathura.enable = true; 188 zathura = {
189 enable = true;
190 options = {
191 scroll-page-aware = true;
192 };
193 };
189 imv.enable = true; 194 imv.enable = true;
190 195
191 mpv.config = { 196 mpv.config = {
@@ -285,14 +290,6 @@ in {
285 }; 290 };
286 291
287 services = { 292 services = {
288 dunst = {
289 settings = import ./dunst-settings.nix inputs;
290 iconTheme = {
291 package = pkgs.paper-icon-theme;
292 name = "Paper";
293 };
294 enable = true;
295 };
296 emacs = { 293 emacs = {
297 enable = true; 294 enable = true;
298 socketActivation.enable = true; 295 socketActivation.enable = true;
@@ -321,8 +318,11 @@ in {
321 device_mounted = []; 318 device_mounted = [];
322 }; 319 };
323 device_config = [ 320 device_config = [
324 { mount_path = "/run/etc-metadata"; ignore = true; } 321 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; }
322 { mount_path = "/run/nixos-etc-metadata"; ignore = true; }
323 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; }
325 ]; 324 ];
325 icon_names.media = ["drive-removable-media-symbolic"];
326 }; 326 };
327 }; 327 };
328 network-manager-applet.enable = true; 328 network-manager-applet.enable = true;
@@ -359,31 +359,17 @@ in {
359 enable = true; 359 enable = true;
360 events = [ 360 events = [
361 { event = "before-sleep"; command = lockCommand; } 361 { event = "before-sleep"; command = lockCommand; }
362 # { event = "after-resume"; command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms on"; }
363 { event = "lock"; command = lockCommand; } 362 { event = "lock"; command = lockCommand; }
364 ]; 363 ];
365 timeouts = [ 364 timeouts = [
366 # { timeout = 300;
367 # command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off";
368 # }
369 { timeout = 330; command = lockCommand; } 365 { timeout = 330; command = lockCommand; }
370 ]; 366 ];
371 extraArgs = [ 367 extraArgs = [
368 "-w"
372 "idlehint" "30" 369 "idlehint" "30"
373 ]; 370 ];
374 }; 371 };
375 poweralertd.enable = true; 372 poweralertd.enable = true;
376 avizo = {
377 enable = true;
378 settings.default = {
379 time = "1.0";
380 background = "rgba(0, 0, 0, 0.8)";
381 border-color = "rgba(0, 0, 0, 1)";
382 bar-fg-color = "rgba(160, 160, 160, 1)";
383 bar-bg-color = "rgba(32, 32, 32, 0.96)";
384 # y-offset = "0.25";
385 };
386 };
387 }; 373 };
388 374
389 home.pointerCursor = { 375 home.pointerCursor = {
@@ -424,16 +410,18 @@ in {
424 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 410 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
425 mumble pulseaudio-ctl pamixer libnotify screen-message 411 mumble pulseaudio-ctl pamixer libnotify screen-message
426 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 412 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince
427 thunderbird zoom-us steam steam-run wireshark virt-manager 413 thunderbird zoom-us xdg-desktop-portal steam steam-run
428 rclone cached-nix-shell worktime fira-code-symbols 414 wireshark virt-manager rclone cached-nix-shell worktime
429 libreoffice xournalpp google-chrome nixos-shell virt-viewer 415 fira-code-symbols libreoffice xournalpp google-chrome
430 freerdp gnome-icon-theme paper-icon-theme sshpassSecret 416 nixos-shell virt-viewer freerdp gnome-icon-theme
431 weechat element-desktop matrix-synapse-tools.synadm 417 paper-icon-theme sshpassSecret weechat element-desktop
418 matrix-synapse-tools.synadm
432 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs 419 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs
433 sieve-connect gimp inkscape udiskie glab nitrokey-app 420 sieve-connect gimp inkscape udiskie glab nitrokey-app
434 pynitrokey gtklock wlrctl remmina openscad spice-record 421 pynitrokey gtklock wlrctl remmina openscad spice-record
435 libguestfs-with-appliance nerd-fonts.fira-mono 422 libguestfs-with-appliance nerd-fonts.fira-mono
436 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 423 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
424 swtpm
437 ]; 425 ];
438 426
439 file = { 427 file = {
@@ -468,13 +456,6 @@ in {
468 }; 456 };
469 457
470 xdg.configFile = { 458 xdg.configFile = {
471 "dunst/dunstrc.d" = {
472 source = ./dunstrc.d;
473 recursive = true;
474 onChange = ''
475 ${pkgs.systemd}/bin/systemctl --user try-restart dunst
476 '';
477 };
478 "wireplumber" = { 459 "wireplumber" = {
479 source = ./wireplumber; 460 source = ./wireplumber;
480 recursive = true; 461 recursive = true;
@@ -506,6 +487,18 @@ in {
506 }; 487 };
507 }; 488 };
508 "emacs/init.el".source = ./emacs.el; 489 "emacs/init.el".source = ./emacs.el;
490 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
491 [Unit]
492 After=graphical-session.target
493 '';
494 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = ''
495 [Unit]
496 Before=graphical-session-pre.target
497 '';
498 "pdfpc/pdfpcrc".text = ''
499 mouse 8 prev
500 mouse 9 next
501 '';
509 }; 502 };
510 503
511 xdg.dataFile = { 504 xdg.dataFile = {
@@ -623,7 +616,6 @@ in {
623 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 616 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
624 --property 'Environment=DSCP=46' \ 617 --property 'Environment=DSCP=46' \
625 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \ 618 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \
626 --force-device-scale-factor=1.5 \
627 --class=Rainbow \ 619 --class=Rainbow \
628 --kiosk "https://web.openrainbow.com" \ 620 --kiosk "https://web.openrainbow.com" \
629 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 621 --user-data-dir=''${HOME}/.config/google-chrome-rainbow
@@ -632,6 +624,9 @@ in {
632 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 624 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
633 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU="; 625 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU=";
634 }; 626 };
627 settings = {
628 StartupWMClass = "Rainbow";
629 };
635 }; 630 };
636 }; 631 };
637 632
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 183cb322..5cee16b0 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -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."
diff --git a/accounts/gkleen@sif/libvirt/default.nix b/accounts/gkleen@sif/libvirt/default.nix
index 70ac22b9..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 [
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 6aa4391c..7e187c84 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -2,31 +2,37 @@
2let 2let
3 niri = config.programs.niri.package; 3 niri = config.programs.niri.package;
4 terminal = lib.getExe config.programs.kitty.package; 4 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl"; 5 makoctl = lib.getExe' config.services.mako.package "makoctl";
6 volumectl = lib.getExe' config.services.avizo.package "volumectl";
7 dunstctl = lib.getExe' config.services.dunst.package "dunstctl";
8 loginctl = lib.getExe' hostConfig.systemd.package "loginctl"; 6 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl"; 7 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
8 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
10 9
11 focus-or-spawn = pkgs.writeShellApplication { 10 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn"; 11 name = "focus-or-spawn";
13 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ]; 12 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
14 text = '' 13 text = ''
15 app_id="$1" 14 window_select="$1"
16 shift 15 shift
17 workspace_name="$1" 16 workspace_name="$1"
18 shift 17 shift
19 18
20 workspaces_json="$(niri msg -j workspaces)" 19 workspaces_json="$(niri msg -j workspaces)"
21 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")" 20 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")" 21 # active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
23 niri msg action move-workspace-to-monitor --output "$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")" "$workspace_name" 22 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
24 socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}' 23 if [[ $workspace_output != "$active_output" ]]; then
25 niri msg action move-workspace-to-index --index 1 "$workspace_name" 24 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
25 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
26 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
27 fi
26 28
27 while IFS=$'\n' read -r window_json; do 29 while IFS=$'\n' read -r window_json; do
28 if jq -r '.app_id' <<<"$window_json" | grep -q "$app_id"; then 30 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
29 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 31 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
32 niri msg action focus-workspace-previous
33 else
34 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
35 fi
30 exit 0 36 exit 0
31 fi 37 fi
32 done < <(niri msg -j windows | jq -c '.[]') 38 done < <(niri msg -j windows | jq -c '.[]')
@@ -34,10 +40,93 @@ let
34 exec "$@" 40 exec "$@"
35 ''; 41 '';
36 }; 42 };
37 focus-or-spawn-action = app_id: workspace_name: config.lib.niri.actions.spawn (lib.getExe focus-or-spawn) (lib.escapeShellArg app_id) (lib.escapeShellArg workspace_name); 43 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
44 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
45
46 with_adjacent_workspace = pkgs.writeShellApplication {
47 name = "with-adjacent-workspace";
48 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
49 text = ''
50 blacklist="$1"
51 shift
52 direction="$1"
53 shift
54 action="$1"
55 shift
56
57 workspaces_json="$(niri msg -j workspaces)"
58 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
59 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
60 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
61
62 jq_script='map(select('
63 case "$direction" in
64 down)
65 # shellcheck disable=SC2016
66 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
67 up)
68 # shellcheck disable=SC2016
69 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
70 esac
71 # shellcheck disable=SC2016
72 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
73 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
74 jq_script=''${jq_script}' | .[0]'
75
76 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
77 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
78 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
79 '';
80 };
81 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$";
82 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
83 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
84
85 with_unnamed_workspace = pkgs.writeShellApplication {
86 name = "with-unnamed-workspace";
87 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
88 text = ''
89 action="$1"
90 shift
91
92 workspaces_json="$(niri msg -j workspaces)"
93 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
94 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
95
96 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
97 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")"
98 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
99 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
100 '';
101 };
102 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
103
104 with_select_window = pkgs.writeShellApplication {
105 name = "with-select-window";
106 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
107 text = ''
108 window_select="$1"
109 shift
110 action="$1"
111 shift
112
113 windows_json="$(niri msg -j windows)"
114 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
115 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu --index)"
116 # shellcheck disable=SC2016
117 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
118
119 [[ -z "$window_json" ]] && exit 1
120
121 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
122 '';
123 };
124 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
38in { 125in {
39 imports = [ 126 imports = [
40 ./waybar.nix 127 ./waybar.nix
128 ./mako.nix
129 ./swayosd.nix
41 ]; 130 ];
42 131
43 config = { 132 config = {
@@ -72,6 +161,113 @@ in {
72 ]; 161 ];
73 }; 162 };
74 163
164 systemd.user.sockets.niri-workspace-history = {
165 Socket = {
166 ListenStream = "%t/niri-workspace-history.sock";
167 SocketMode = "0600";
168 };
169 };
170 systemd.user.services.niri-workspace-history = {
171 Unit = {
172 BindsTo = [ "niri.service" ];
173 After = [ "niri.service" ];
174 };
175 Install = {
176 WantedBy = [ "niri.service" ];
177 };
178 Service = {
179 Type = "simple";
180 Sockets = [ "niri-workspace-history.socket" ];
181 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} ''
182 import os
183 import socket
184 import json
185 import sys
186 from collections import defaultdict
187 from threading import Thread, Lock
188 from socketserver import StreamRequestHandler, ThreadingTCPServer
189 from contextlib import contextmanager
190 from io import TextIOWrapper
191
192
193 @contextmanager
194 def detaching(thing):
195 try:
196 yield thing
197 finally:
198 thing.detach()
199
200
201 workspace_history = defaultdict(list)
202 history_lock = Lock()
203
204
205 def monitor_niri():
206 workspaces = list()
207
208 def focus_workspace(output, workspace):
209 global workspace_history, history_lock
210
211 with history_lock:
212 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501
213 print(json.dumps(workspace_history), file=sys.stderr)
214
215 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
216 sock.connect(os.environ["NIRI_SOCKET"])
217 sock.send(b"\"EventStream\"\n")
218 for line in sock.makefile(buffering=1, encoding='utf-8'):
219 if line_json := json.loads(line):
220 if "WorkspacesChanged" in line_json:
221 workspaces = line_json["WorkspacesChanged"]["workspaces"]
222 for ws in workspaces:
223 if ws["is_focused"]:
224 focus_workspace(ws["output"], ws["id"])
225 if "WorkspaceActivated" in line_json:
226 for ws in workspaces:
227 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
228 continue
229 focus_workspace(ws["output"], ws["id"])
230 break
231
232
233 class RequestHandler(StreamRequestHandler):
234 def handle(self):
235 global workspace_history, history_lock
236
237 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
238 with history_lock:
239 json.dump(workspace_history, out)
240
241
242 class Server(ThreadingTCPServer):
243 def __init__(self):
244 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501
245 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
246
247
248 def run_server():
249 with Server() as server:
250 server.serve_forever()
251
252
253 niri = Thread(target=monitor_niri)
254 niri.daemon = True
255 niri.start()
256
257 server_thread = Thread(target=run_server)
258 server_thread.daemon = True
259 server_thread.start()
260
261 while True:
262 server_thread.join(timeout=0.5)
263 niri.join(timeout=0.5)
264
265 if not (niri.is_alive() and server_thread.is_alive()):
266 break
267 '';
268 };
269 };
270
75 programs.niri.settings = { 271 programs.niri.settings = {
76 prefer-no-csd = true; 272 prefer-no-csd = true;
77 screenshot-path = "${config.home.homeDirectory}/screenshots"; 273 screenshot-path = "${config.home.homeDirectory}/screenshots";
@@ -79,10 +275,15 @@ in {
79 hotkey-overlay.skip-at-startup = true; 275 hotkey-overlay.skip-at-startup = true;
80 276
81 input = { 277 input = {
82 keyboard.xkb = { 278 keyboard = {
83 layout = "us,us"; 279 repeat-delay = 300;
84 variant = "dvp,"; 280 repeat-rate = 50;
85 options = "compose:caps,grp:win_space_toggle"; 281
282 xkb = {
283 layout = "us,us";
284 variant = "dvp,";
285 options = "compose:caps,grp:win_space_toggle";
286 };
86 }; 287 };
87 288
88 workspace-auto-back-and-forth = true; 289 workspace-auto-back-and-forth = true;
@@ -91,7 +292,7 @@ in {
91 }; 292 };
92 293
93 outputs = { 294 outputs = {
94 "Samsung Display Corp. 0x4141 Unknown" = { 295 "eDP-1" = {
95 scale = 1.5; 296 scale = 1.5;
96 position = { x = 0; y = 0; }; 297 position = { x = 0; y = 0; };
97 }; 298 };
@@ -99,38 +300,95 @@ in {
99 scale = 1.5; 300 scale = 1.5;
100 position = { x = 2560; y = 0; }; 301 position = { x = 2560; y = 0; };
101 }; 302 };
303 "HP Inc. HP 727pu CN4417143K" = {
304 mode = { width = 2560; height = 1440; refresh = 119.998; };
305 scale = 1;
306 position = { x = 2560; y = 0; };
307 variable-refresh-rate = "on-demand";
308 };
102 }; 309 };
103 310
104 environment = { 311 environment = {
105 NIXOS_OZONE_WL = "1"; 312 NIXOS_OZONE_WL = "1";
106 QT_QPA_PLATFORM = "wayland"; 313 QT_QPA_PLATFORM = "wayland";
314 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
107 GDK_BACKEND = "wayland"; 315 GDK_BACKEND = "wayland";
108 SDL_VIDEODRIVER = "wayland"; 316 SDL_VIDEODRIVER = "wayland";
317 DISPLAY = ":0";
318 };
319
320 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render";
321
322 animations = {
323 slowdown = 0.5;
324 workspace-switch = null;
109 }; 325 };
110 326
111 layout = { 327 layout = {
112 gaps = 8; 328 gaps = 8;
113 struts = { left = 8; right = 8; top = 0; bottom = 0; }; 329 struts = { left = 0; right = 0; top = 0; bottom = 0; };
114 focus-ring = { 330 focus-ring = {
115 width = 2; 331 width = 2;
332 active.gradient = {
333 from = "hsla(195 100% 60% 0.75)";
334 to = "hsla(155 100% 50% 0.75)";
335 angle = 29;
336 relative-to = "workspace-view";
337 };
338 inactive.gradient = {
339 from = "hsla(0 0% 42% 0.66)";
340 to = "hsla(0 0% 35% 0.66)";
341 angle = 29;
342 relative-to = "workspace-view";
343 };
116 }; 344 };
345
346 preset-column-widths = [
347 { proportion = 1. / 4.; }
348 { proportion = 1. / 3.; }
349 { proportion = 1. / 2.; }
350 { proportion = 2. / 3.; }
351 { proportion = 3. / 4.; }
352 ];
353 default-column-width.proportion = 1. / 2.;
354 preset-window-heights = [
355 { proportion = 1. / 3.; }
356 { proportion = 1. / 2.; }
357 { proportion = 2. / 3.; }
358 { proportion = 1.; }
359 ];
360
361 always-center-single-column = true;
117 }; 362 };
118 363
119 cursor.hide-when-typing = true; 364 cursor.hide-when-typing = true;
120 365
366 input = {
367 touchpad.enable = false;
368 trackball = {
369 scroll-method = "on-button-down";
370 scroll-button = 278;
371 };
372 };
373
121 workspaces = { 374 workspaces = {
122 "001".name = "pwctl"; 375 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
123 "002".name = "kpxc"; 376 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
124 "003".name = "bmgr"; 377 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
378 "004" = { name = "term"; open-on-output = "eDP-1"; };
379 "005" = { name = "edit"; open-on-output = "eDP-1"; };
380 "006" = { name = "eff"; open-on-output = "eDP-1"; };
125 "101".name = "comm"; 381 "101".name = "comm";
126 "102".name = "web"; 382 "102".name = "web";
127 "104".name = "read"; 383 # "104".name = "read";
128 "105".name = "mon"; 384 # "105".name = "mon";
129 "110".name = "vid"; 385 "110".name = "vid";
386 "120".name = "bmr";
130 }; 387 };
131 388
132 window-rules = [ 389 window-rules = [
133 { 390 {
391 matches = [ { is-floating = true; } ];
134 geometry-corner-radius = 392 geometry-corner-radius =
135 let 393 let
136 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; }; 394 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
@@ -140,50 +398,130 @@ in {
140 { 398 {
141 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ]; 399 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
142 open-on-workspace = "pwctl"; 400 open-on-workspace = "pwctl";
401 open-maximized = true;
402 }
403 {
404 matches = [ { app-id = "^com\.github\.wwmm\.easyeffects$"; } ];
405 open-on-workspace = "eff";
406 open-maximized = true;
143 } 407 }
144 { 408 {
145 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ]; 409 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
146 open-on-workspace = "bmgr"; 410 open-on-workspace = "bmgr";
411 open-maximized = true;
412 }
413 {
414 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
415 block-out-from = "screencast";
147 } 416 }
148 { 417 {
149 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 418 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
150 excludes = [ 419 excludes = [
151 { title = "^Unlock Database"; } 420 { title = "^Unlock Database.*"; }
152 { title = "^Access Request"; } 421 { title = "^Access Request.*"; }
153 { title = "^Passkey credentials"; } 422 { title = ".*Passkey credentials$"; }
154 ]; 423 ];
155 open-on-workspace = "kpxc"; 424 open-on-workspace = "kpxc";
425 open-maximized = true;
156 open-focused = false; 426 open-focused = false;
157 } 427 }
158 { 428 {
159 matches = [ 429 matches = [
430 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
431 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
432 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
433 ];
434 open-focused = true;
435 open-floating = true;
436 }
437 {
438 matches = [ { app-id = "^kitty-scratch$"; } ];
439 open-on-workspace = "term";
440 open-maximized = true;
441 }
442 {
443 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
444 open-on-workspace = "edit";
445 open-maximized = true;
446 }
447 {
448 matches = [
449 { app-id = "^emacs$"; }
450 { app-id = "^firefox$"; }
451 ];
452 default-column-width.proportion = 2. / 3.;
453 }
454 {
455 matches = [
456 { app-id = "^kitty$"; }
457 { app-id = "^kitty-play$"; }
458 ];
459 default-column-width.proportion = 1. / 3.;
460 }
461 {
462 matches = [
160 { app-id = "^thunderbird$"; } 463 { app-id = "^thunderbird$"; }
161 { app-id = "^Element$"; } 464 { app-id = "^Element$"; }
465 { app-id = "^Rainbow$"; }
162 ]; 466 ];
163 open-on-workspace = "comm"; 467 open-on-workspace = "comm";
164 } 468 }
165 { 469 {
166 matches = [ { app-id = "^firefox$"; } ]; 470 matches = [ { app-id = "^firefox$"; } ];
167 open-on-workspace = "web"; 471 open-on-workspace = "web";
472 open-maximized = true;
473 variable-refresh-rate = true;
168 } 474 }
475 # {
476 # matches = [
477 # { app-id = "^evince$"; }
478 # { app-id = "^imv$"; }
479 # { app-id = "^org\.pwmt\.zathura$"; }
480 # ];
481 # open-on-workspace = "read";
482 # }
169 { 483 {
170 matches = [ 484 matches = [ { app-id = "^mpv$"; } ];
171 { app-id = "^evince$"; } 485 open-on-workspace = "vid";
172 { app-id = "^imv$"; } 486 default-column-width.proportion = 1.;
173 { app-id = "^org\.pwmt\.zathura$"; } 487 variable-refresh-rate = true;
174 ];
175 open-on-workspace = "read";
176 } 488 }
177 { 489 {
178 matches = [ { app-id = "^mpv$"; } ]; 490 matches = [ { app-id = "^kitty-play$"; } ];
179 open-on-workspace = "vid"; 491 open-on-workspace = "vid";
492 open-focused = false;
493 }
494 # {
495 # matches = [
496 # { app-id = "^qemu$"; }
497 # { app-id = "^virt-manager$"; }
498 # ];
499 # open-on-workspace = "mon";
500 # }
501 {
502 matches = [ { app-id = "^pdfpc$"; } ];
503 default-column-width.proportion = 1.;
504 }
505 {
506 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
507 open-on-workspace = "bmr";
508 open-fullscreen = true;
180 } 509 }
181 { 510 {
182 matches = [ 511 matches = [
183 { app-id = "^qemu$"; } 512 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
184 { app-id = "^virt-manager$"; } 513 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
514 { app-id = "^xdg-desktop-portal-gtk$"; }
185 ]; 515 ];
186 open-on-workspace = "mon"; 516 open-floating = true;
517 }
518 ];
519 layer-rules = [
520 { matches = [
521 { namespace = "^notifications$"; }
522 { namespace = "^waybar$"; }
523 ];
524 block-out-from = "screencast";
187 } 525 }
188 ]; 526 ];
189 527
@@ -192,8 +530,93 @@ in {
192 530
193 "Mod+Return".action = spawn terminal; 531 "Mod+Return".action = spawn terminal;
194 "Mod+Q".action = close-window; 532 "Mod+Q".action = close-window;
195 "Mod+D".action = spawn (lib.getExe config.programs.fuzzel.package); 533 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
196 "Mod+Shift+D".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path"; 534 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
535
536 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
537 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
538 name = "queue-yt-dlp";
539 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
540 text = ''
541 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
542 '';
543 }));
544 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
545 name = "queue-yt-dlp";
546 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
547 text = ''
548 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
549 '';
550 }));
551
552 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
553 name = "qalc-fuzzel";
554 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
555 text = ''
556 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
557 prev() {
558 FOUND=false
559 while IFS= read -r line; do
560 [[ -n "$line" ]] || continue
561 FOUND=true
562 echo "$line"
563 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)
564 $FOUND || echo
565 }
566 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
567 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
568 QALC_RES="$FUZZEL_RES"
569 QALC_RET=0
570 else
571 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
572 QALC_RET=$?
573 fi
574 [[ -n "$QALC_RES" ]] || exit 1
575 EXISTING=false
576 set +o pipefail
577 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
578 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
579 set -o pipefail
580 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
581 set +o pipefail
582 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
583 set -o pipefail
584 cat >"$RES_FILE" <<<"$QALC_RES"
585 fi
586 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
587 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
588 notify-send "$QALC_RES"
589 '';
590 }));
591 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
592 name = "emoji-fuzzel";
593 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
594 text = ''
595 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
596 [[ -n "$FUZZEL_RES" ]] || exit 1
597 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
598 '';
599 }));
600 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
601 name = "screenshot";
602 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
603 text = ''
604 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
605 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
606 | wl-copy --type image/png
607 '';
608 }));
609 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
610 name = "screenshot";
611 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
612 text = ''
613 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
614 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
615 | wl-copy --type image/png
616 '';
617 }));
618 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
619 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
197 620
198 "Mod+H".action = focus-column-left; 621 "Mod+H".action = focus-column-left;
199 "Mod+T".action = focus-window-down; 622 "Mod+T".action = focus-window-down;
@@ -215,15 +638,33 @@ in {
215 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up; 638 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
216 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right; 639 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
217 640
218 "Mod+G".action = focus-workspace-down; 641 "Mod+G".action = focus-adjacent-workspace "down";
219 "Mod+C".action = focus-workspace-up; 642 "Mod+C".action = focus-adjacent-workspace "up";
220 643
221 "Mod+Shift+G".action = move-column-to-workspace-down; 644 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
222 "Mod+Shift+C".action = move-column-to-workspace-up; 645 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
223 646
224 "Mod+Shift+Control+G".action = move-workspace-down; 647 "Mod+Shift+Control+G".action = move-workspace-down;
225 "Mod+Shift+Control+C".action = move-workspace-up; 648 "Mod+Shift+Control+C".action = move-workspace-up;
226 649
650 "Mod+ParenLeft".action = focus-workspace "comm";
651 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
652
653 "Mod+ParenRight".action = focus-workspace "web";
654 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
655
656 "Mod+BraceRight".action = focus-workspace "read";
657 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
658
659 "Mod+BraceLeft".action = focus-workspace "mon";
660 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
661
662 "Mod+Asterisk".action = focus-workspace "vid";
663 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
664
665 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
666 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
667
227 "Mod+M".action = consume-or-expel-window-left; 668 "Mod+M".action = consume-or-expel-window-left;
228 "Mod+W".action = consume-or-expel-window-right; 669 "Mod+W".action = consume-or-expel-window-right;
229 670
@@ -233,8 +674,8 @@ in {
233 "Mod+Shift+F".action = maximize-column; 674 "Mod+Shift+F".action = maximize-column;
234 "Mod+Shift+Ctrl+F".action = fullscreen-window; 675 "Mod+Shift+Ctrl+F".action = fullscreen-window;
235 676
236 "Mod+B".action = switch-focus-between-floating-and-tiling; 677 "Mod+V".action = switch-focus-between-floating-and-tiling;
237 "Mod+Shift+B".action = toggle-window-floating; 678 "Mod+Shift+V".action = toggle-window-floating;
238 679
239 "Mod+Left".action = set-column-width "-10%"; 680 "Mod+Left".action = set-column-width "-10%";
240 "Mod+Down".action = set-window-height "-10%"; 681 "Mod+Down".action = set-window-height "-10%";
@@ -245,44 +686,57 @@ in {
245 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors"; 686 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
246 allow-when-locked = true; 687 allow-when-locked = true;
247 }; 688 };
248 "Mod+Shift+L" = { 689 "Mod+Shift+L".action = spawn loginctl "lock-session";
249 action = spawn loginctl "lock-session";
250 };
251 "Mod+Shift+E".action = quit; 690 "Mod+Shift+E".action = quit;
691 "Mod+Shift+Minus" = {
692 action = spawn systemctl "suspend";
693 allow-when-locked = true;
694 };
695 "Mod+Shift+Control+Minus" = {
696 action = spawn systemctl "hibernate";
697 allow-when-locked = true;
698 };
699 "Mod+Shift+P" = {
700 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
701 allow-when-locked = true;
702 };
252 703
253 "XF86MonBrightnessUp" = { 704 "XF86MonBrightnessUp" = {
254 action = spawn lightctl "-d" "-e4" "-n1" "up"; 705 action = spawn swayosd-client "--brightness" "raise";
255 allow-when-locked = true; 706 allow-when-locked = true;
256 }; 707 };
257 "XF86MonBrightnessDown" = { 708 "XF86MonBrightnessDown" = {
258 action = spawn lightctl "-d" "-e4" "-n1" "down"; 709 action = spawn swayosd-client "--brightness" "lower";
259 allow-when-locked = true; 710 allow-when-locked = true;
260 }; 711 };
261 "XF86AudioRaiseVolume" = { 712 "XF86AudioRaiseVolume" = {
262 action = spawn volumectl "-d" "-u" "up"; 713 action = spawn swayosd-client "--output-volume" "raise";
263 allow-when-locked = true; 714 allow-when-locked = true;
264 }; 715 };
265 "XF86AudioLowerVolume" = { 716 "XF86AudioLowerVolume" = {
266 action = spawn volumectl "-d" "-u" "down"; 717 action = spawn swayosd-client "--output-volume" "lower";
267 allow-when-locked = true; 718 allow-when-locked = true;
268 }; 719 };
269 "XF86AudioMute" = { 720 "XF86AudioMute" = {
270 action = spawn volumectl "-d" "toggle-mute"; 721 action = spawn swayosd-client "--output-volume" "mute-toggle";
271 allow-when-locked = true; 722 allow-when-locked = true;
272 }; 723 };
273 "XF86AudioMicMute" = { 724 "XF86AudioMicMute" = {
274 action = spawn volumectl "-d" "-m" "toggle-mute"; 725 action = spawn swayosd-client "--input-volume" "mute-toggle";
275 allow-when-locked = true; 726 allow-when-locked = true;
276 }; 727 };
277 728
278 "Mod+Semicolon".action = spawn dunstctl "close"; 729 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
279 "Mod+Shift+Semicolon".action = spawn dunstctl "close-all"; 730 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
280 "Mod+Period".action = spawn dunstctl "context"; 731 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
281 "Mod+Comma".action = spawn dunstctl "history-pop"; 732 "Mod+Comma".action = spawn makoctl "restore";
282 733
283 "Mod+Alt+A".action = focus-or-spawn-action "^com\.saivert\.pwvucontrol$" "pwctl" "pwvucontrol"; 734 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
284 "Mod+Alt+P".action = focus-or-spawn-action "^org\.keepassxc\.KeePassXC$" "kpxc" "keepassxc"; 735 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects";
285 "Mod+Alt+B".action = focus-or-spawn-action "^\.blueman-manager-wrapped$" "bmgr" "blueman-manager"; 736 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
737 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
738 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
739 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
286 }; 740 };
287 }; 741 };
288 }; 742 };
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
new file mode 100644
index 00000000..0a10555a
--- /dev/null
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -0,0 +1,115 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.mako = {
5 enable = true;
6 font = "Fira Sans 10";
7 format = "<i>%s</i>\\n%b";
8 margin = "2";
9 maxVisible = -1;
10 backgroundColor = "#000000dd";
11 progressColor = "source #223544ff";
12 width = 384;
13 extraConfig = ''
14 outer-margin=1
15 max-history=100
16 max-icon-size=48
17
18 [grouped]
19 format=<b>(%g)</b> <i>%s</i>\n%b
20
21 [urgency=low]
22 text-color=#999999ff
23
24 [urgency=critical]
25 background-color=#900000dd
26
27 [app-name=Element]
28 group-by=summary
29
30 [mode=silent]
31 invisible=1
32 '';
33 package = pkgs.symlinkJoin {
34 name = "${pkgs.mako.name}-wrapped";
35 paths = with pkgs; [ mako ];
36 inherit (pkgs.mako) meta;
37 postBuild = ''
38 rm -r $out/share/dbus-1
39 '';
40 };
41 };
42 systemd.user.services.mako = {
43 Unit = {
44 Description = "Mako notification daemon";
45 PartOf = [ "graphical-session.target" ];
46 };
47 Install = {
48 WantedBy = [ "graphical-session.target" ];
49 };
50 Service = {
51 Type = "dbus";
52 BusName = "org.freedesktop.Notifications";
53 ExecStart = lib.getExe config.services.mako.package;
54 RestartSec = 5;
55 Restart = "always";
56 };
57 };
58
59 systemd.user.services.mako-follows-focus = {
60 Unit = {
61 BindsTo = [ "niri.service" "mako.service" ];
62 After = [ "niri.service" "mako.service" ];
63 };
64 Service = {
65 Type = "simple";
66 Restart = "always";
67 ExecStart = pkgs.writers.writePython3 "mako-follows-focus" {
68 libraries = with pkgs.python3Packages; [];
69 } ''
70 import os
71 import socket
72 import json
73 import subprocess
74
75
76 current_output = None
77 workspaces = []
78
79
80 def output_changed(new_output):
81 global current_output
82
83 if current_output == new_output:
84 return
85
86 current_output = new_output
87 subprocess.run(["makoctl", "reload"])
88
89
90 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
91 sock.connect(os.environ["NIRI_SOCKET"])
92 sock.send(b"\"EventStream\"\n")
93 for line in sock.makefile(buffering=1, encoding='utf-8'):
94 if line_json := json.loads(line):
95 if "WorkspacesChanged" in line_json:
96 workspaces = line_json["WorkspacesChanged"]["workspaces"]
97 for workspace in workspaces:
98 if not workspace["is_focused"]:
99 continue
100 output_changed(workspace["output"])
101 break
102 if "WorkspaceActivated" in line_json and line_json["WorkspaceActivated"]["focused"]: # noqa: E501
103 for workspace in workspaces:
104 if not workspace["id"] == line_json["WorkspaceActivated"]["id"]: # noqa: E501
105 continue
106 output_changed(workspace["output"])
107 break
108 '';
109 };
110 Install = {
111 WantedBy = [ "mako.service" ];
112 };
113 };
114 };
115}
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
new file mode 100644
index 00000000..984927c2
--- /dev/null
+++ b/accounts/gkleen@sif/niri/swayosd.nix
@@ -0,0 +1,65 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.946154;
7 stylePath = pkgs.runCommand "style.css" {
8 src = pkgs.writeText "style.scss" ''
9 window#osd {
10 padding: 12px 20px;
11 border-radius: 999px;
12 border: none;
13 background: rgba(0, 0, 0, 0.87);
14
15 #container {
16 margin: 16px;
17 }
18
19 image,
20 label {
21 color: rgb(255, 255, 255);
22
23 &:disabled {
24 opacity: 1;
25 color: rgb(84, 84, 84);
26 }
27 }
28
29 progressbar {
30 min-height: 6px;
31 border-radius: 999px;
32 background: transparent;
33 border: none;
34
35 trough, progress {
36 min-height: inherit;
37 border-radius: inherit;
38 border: none;
39 }
40
41 trough {
42 background: rgb(127, 127, 127);
43 }
44 progress {
45 background: rgb(255, 255, 255);
46 }
47
48 &:disabled {
49 opacity: 1;
50
51 trough {
52 background: rgb(19, 19, 19);
53 }
54 progress {
55 background: rgb(38, 38, 38);
56 }
57 }
58 }
59 }
60 '';
61 buildInputs = with pkgs; [sass];
62 } "scss -C --sourcemap=none --style=compact $src $out";
63 };
64 };
65}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 2d00c6d8..3f1f8119 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -1,5 +1,7 @@
1{ lib, pkgs, ... }: 1{ lib, config, pkgs, ... }:
2{ 2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
3 config = { 5 config = {
4 programs.waybar = { 6 programs.waybar = {
5 enable = true; 7 enable = true;
@@ -22,16 +24,66 @@
22 output = [ "eDP-1" "DP-2" "DP-3" ]; 24 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ]; 25 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ]; 26 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
26 "custom/weather" 28 "custom/weather"
27 # "custom/keymap" 29 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
29 31
32 "custom/mako" = {
33 format = "{}";
34 return-type = "json";
35 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
36 from dbus_next.aio import MessageBus
37
38 import asyncio
39
40 import json
41
42
43 loop = asyncio.new_event_loop()
44 asyncio.set_event_loop(loop)
45
46
47 async def main():
48 bus = await MessageBus().connect()
49 # the introspection xml would normally be included in your project, but
50 # this is convenient for development
51 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
52
53 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
54 mako = obj.get_interface('fr.emersion.Mako')
55 properties = obj.get_interface('org.freedesktop.DBus.Properties')
56
57 async def print_mode():
58 modes = await mako.get_modes()
59 is_silent = "silent" in modes
60 icon = "&#xf009b;" if is_silent else "&#xf009a;"
61 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
62 if is_silent:
63 text = f"<span color=\"#ffffff\">{text}</span>"
64 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
65
66 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
67 if "Modes" not in invalidated_properties:
68 return
69
70 await print_mode()
71
72 properties.on_properties_changed(on_properties_changed)
73 await print_mode()
74
75 await loop.create_future()
76
77
78 loop.run_until_complete(main())
79 '';
80 on-click = "makoctl mode -t silent";
81 };
30 "custom/weather" = { 82 "custom/weather" = {
31 format = "{}"; 83 format = "{}";
32 tooltip = true; 84 tooltip = true;
33 interval = 3600; 85 interval = 3600;
34 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"120%\\\">{ICON}</span> {FeelsLikeC}°\""; 86 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
35 return-type = "json"; 87 return-type = "json";
36 }; 88 };
37 "custom/keymap" = { 89 "custom/keymap" = {
@@ -41,8 +93,6 @@
41 exec = pkgs.writers.writePython3 "keymap" {} '' 93 exec = pkgs.writers.writePython3 "keymap" {} ''
42 import os 94 import os
43 import socket 95 import socket
44 import re
45 import subprocess
46 import json 96 import json
47 97
48 98
@@ -55,32 +105,34 @@
55 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501 105 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
56 106
57 107
58 r = subprocess.run(["hyprctl", "devices", "-j"], check=True, stdout=subprocess.PIPE, text=True) # noqa: E501 108 keyboard_layouts = []
59 for keyboard in json.loads(r.stdout)['keyboards']:
60 if keyboard['name'] != "at-translated-set-2-keyboard":
61 continue
62 output(keyboard['active_keymap'])
63 109
64 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 110 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
65 sock.connect(os.environ["XDG_RUNTIME_DIR"] + "/hypr/" + os.environ["HYPRLAND_INSTANCE_SIGNATURE"] + "/.socket2.sock") # noqa: E501 111 sock.connect(os.environ["NIRI_SOCKET"])
66 expected = re.compile(r'^activelayout>>at-translated-set-2-keyboard,(?P<keymap>.+)$') # noqa: E501 112 sock.send(b"\"EventStream\"\n")
67 for line in sock.makefile(buffering=1, encoding='utf-8'): 113 for line in sock.makefile(buffering=1, encoding='utf-8'):
68 if match := expected.match(line): 114 if line_json := json.loads(line):
69 output(match.group("keymap")) 115 if "KeyboardLayoutsChanged" in line_json:
116 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
117 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
118 if "KeyboardLayoutSwitched" in line_json:
119 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
70 ''; 120 '';
71 on-click = "hyprctl switchxkblayout at-translated-set-2-keyboard next"; 121 on-click = "niri msg action switch-layout next";
72 }; 122 };
73 "custom/worktime" = { 123 "custom/worktime" = {
74 interval = 60; 124 interval = 60;
75 exec = lib.getExe pkgs.worktime; 125 exec = "${lib.getExe pkgs.worktime} time --waybar";
76 tooltip = false; 126 return-type = "json";
77 }; 127 };
78 "custom/worktime-today" = { 128 "custom/worktime-today" = {
79 interval = 60; 129 interval = 60;
80 exec = "${lib.getExe pkgs.worktime} today"; 130 exec = "${lib.getExe pkgs.worktime} today --waybar";
81 tooltip = false; 131 return-type = "json";
132 };
133 "niri/workspaces" = {
134 ignore = ["eff" "pwctl" "kpxc" "bmgr" "edit" "term"];
82 }; 135 };
83 "niri/workspaces" = {};
84 "niri/window" = { 136 "niri/window" = {
85 separate-outputs = true; 137 separate-outputs = true;
86 icon = true; 138 icon = true;
@@ -140,8 +192,8 @@
140 icon-size = iconSize; 192 icon-size = iconSize;
141 tooltip-format = "{percent}%"; 193 tooltip-format = "{percent}%";
142 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"]; 194 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
143 on-scroll-up = "lightctl -d -e4 -n1 up"; 195 on-scroll-up = "${swayosd-client} --brightness raise";
144 on-scroll-down = "lightctl -d -e4 -n1 down"; 196 on-scroll-down = "${swayosd-client} --brightness lower";
145 }; 197 };
146 wireplumber = { 198 wireplumber = {
147 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>"; 199 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
@@ -150,9 +202,9 @@
150 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"]; 202 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
151 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>"; 203 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
152 # ignored-sinks = ["Easy Effects Sink"]; 204 # ignored-sinks = ["Easy Effects Sink"];
153 on-scroll-up = "volumectl -d -u up"; 205 on-scroll-up = "${swayosd-client} --output-volume raise";
154 on-scroll-down = "volumectl -d -u down"; 206 on-scroll-down = "${swayosd-client} --output-volume lower";
155 on-click = "volumectl -d toggle-mute"; 207 on-click = "${swayosd-client} --output-volume mute-toggle";
156 }; 208 };
157 } 209 }
158 { 210 {
@@ -164,7 +216,9 @@
164 modules-center = [ "niri/window" ]; 216 modules-center = [ "niri/window" ];
165 modules-right = [ "clock" ]; 217 modules-right = [ "clock" ];
166 218
167 "niri/workspaces" = {}; 219 "niri/workspaces" = {
220 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
221 };
168 "niri/window" = { 222 "niri/window" = {
169 separate-outputs = true; 223 separate-outputs = true;
170 icon = true; 224 icon = true;
@@ -189,7 +243,7 @@
189 243
190 * { 244 * {
191 border: none; 245 border: none;
192 font-family: "Fira Sans Nerd Font"; 246 font-family: "Fira Sans";
193 font-size: 10pt; 247 font-size: 10pt;
194 min-height: 0; 248 min-height: 0;
195 } 249 }
@@ -200,10 +254,10 @@
200 } 254 }
201 255
202 .modules-left { 256 .modules-left {
203 margin-left: 9px; 257 margin-left: 12px;
204 } 258 }
205 .modules-right { 259 .modules-right {
206 margin-right: 9px; 260 margin-right: 12px;
207 } 261 }
208 262
209 .module { 263 .module {
@@ -228,21 +282,26 @@
228 color: @grey; 282 color: @grey;
229 margin: 0 5px; 283 margin: 0 5px;
230 } 284 }
231 #custom-weather, #custom-worktime-today { 285 #custom-weather {
232 margin-right: 3px; 286 margin-right: 3px;
233 } 287 }
234 #custom-keymap, #custom-weather { 288 #custom-keymap {
235 margin-left: 3px; 289 margin-left: 3px;
290 margin-right: 3px;
236 } 291 }
237 292
238 #tray { 293 #tray {
239 margin: 0; 294 margin: 0;
240 } 295 }
241 #battery, #idle_inhibitor, #backlight, #wireplumber { 296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
242 color: @grey; 297 color: @grey;
243 margin: 0 5px 0 2px; 298 margin: 0 5px 0 2px;
244 } 299 }
245 #idle_inhibitor { 300 #idle_inhibitor {
301 margin-right: 4px;
302 margin-left: 6px;
303 }
304 #custom-mako {
246 margin-right: 2px; 305 margin-right: 2px;
247 margin-left: 3px; 306 margin-left: 3px;
248 } 307 }
@@ -264,6 +323,12 @@
264 #idle_inhibitor.activated { 323 #idle_inhibitor.activated {
265 color: @white; 324 color: @white;
266 } 325 }
326 #custom-worktime.running, #custom-worktime-today.running {
327 color: @white;
328 }
329 #custom-worktime.over, #custom-worktime-today.over {
330 color: @orange;
331 }
267 332
268 #idle_inhibitor { 333 #idle_inhibitor {
269 padding-top: 1px; 334 padding-top: 1px;
@@ -271,7 +336,7 @@
271 336
272 #privacy { 337 #privacy {
273 color: @red; 338 color: @red;
274 margin: -1px 2px 0px 5px; 339 margin: -1px 4px 0px 3px;
275 } 340 }
276 #clock { 341 #clock {
277 /* margin-right: 5px; */ 342 /* margin-right: 5px; */
diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix
index 119d8cc3..a89b46c2 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -123,16 +123,8 @@ in {
123 }; 123 };
124 emacs = { 124 emacs = {
125 Unit = { 125 Unit = {
126 After = ["graphical-session-pre.target"]; 126 After = [ "graphical-session.target" ];
127 }; 127 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 }; 128 };
137 }; 129 };
138 keepassxc = { 130 keepassxc = {
@@ -144,8 +136,8 @@ in {
144 Environment = [ "QT_QPA_PLATFORM=wayland" ]; 136 Environment = [ "QT_QPA_PLATFORM=wayland" ];
145 }; 137 };
146 Unit = { 138 Unit = {
147 Requires = ["graphical-session-pre.target"]; 139 After = [ "graphical-session.target" ];
148 After = ["graphical-session-pre.target"]; 140 BindsTo = [ "graphical-session.target" ];
149 }; 141 };
150 }; 142 };
151 mpris-proxy = { 143 mpris-proxy = {
@@ -193,8 +185,8 @@ in {
193 WantedBy = ["graphical-session.target"]; 185 WantedBy = ["graphical-session.target"];
194 }; 186 };
195 Unit = { 187 Unit = {
196 Requires = ["graphical-session-pre.target"]; 188 After = [ "graphical-session.target" ];
197 After = ["graphical-session-pre.target"]; 189 PartOf = [ "graphical-session.target" ];
198 }; 190 };
199 Service = { 191 Service = {
200 ExecStart = lib.getExe pkgs.psi-notify; 192 ExecStart = lib.getExe pkgs.psi-notify;
@@ -207,6 +199,7 @@ in {
207 gtklock = { 199 gtklock = {
208 Unit = { 200 Unit = {
209 Requisite = ["graphical-session.target"]; 201 Requisite = ["graphical-session.target"];
202 After = [ "graphical-session.target" ];
210 PartOf = ["graphical-session.target"]; 203 PartOf = ["graphical-session.target"];
211 }; 204 };
212 Service = { 205 Service = {
@@ -214,53 +207,55 @@ in {
214 RuntimeDirectory = "gtklock"; 207 RuntimeDirectory = "gtklock";
215 CacheDirectory = "gtklock"; 208 CacheDirectory = "gtklock";
216 ExecStartPre = [ 209 ExecStartPre = [
217 "${pkgs.libsForQt5.qt5.qttools.bin}/bin/qdbus org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases" 210 "-${lib.getExe' pkgs.libsForQt5.qt5.qttools.bin "qdbus"} org.keepassxc.KeePassXC.MainWindow /keepassxc org.keepassxc.KeePassXC.MainWindow.lockAllDatabases"
218 "${config.systemd.package}/bin/systemctl --user stop gpg-agent.service" 211 "-${lib.getExe' config.systemd.package "systemctl"} --user stop gpg-agent.service"
219 (pkgs.writeShellScript "generate-css" '' 212 "-${lib.getExe pkgs.playerctl} -a pause"
220 set -x 213 "-${lib.getExe (pkgs.writeShellApplication {
221 export PATH="${lib.makeBinPath [cfg.programs.wpaperd.package pkgs.jq pkgs.coreutils pkgs.imagemagick pkgs.findutils]}:$PATH" 214 name = "generate-css";
215 runtimeInputs = with pkgs; [cfg.programs.wpaperd.package jq coreutils imagemagick findutils];
216 text = ''
217 declare -A monitors
218 monitors=()
219 while IFS= read -r entry; do
220 path=$(jq -r ".path" <<<"$entry")
221 [[ -z "$path" || ! -f "$path" ]] && continue
222 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}"
223 monitor=$(jq -r ".display" <<<"$entry")
224 if [[ ! -f "$blurred_path" ]]; then
225 mkdir -p "$(dirname "$blurred_path")"
226 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" &
227 fi
228 monitors+=([$monitor]="$blurred_path")
229 done < <(wpaperctl all-wallpapers -j | jq -c ".[]")
230 # wait
222 231
223 declare -A monitors 232 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" ''
224 monitors=() 233 #window-box {
225 while IFS= read -r entry; do 234 padding: 64px;
226 path=$(jq -r ".path" <<<"$entry") 235 /* border: 1px solid black; */
227 [[ -z "$path" || ! -f "$path" ]] && continue 236 border-radius: 4px;
228 blurred_path="$CACHE_DIRECTORY"/"$(b2sum -l 128 <<<"$path" | cut -d' ' -f1)"."''${path##*.}" 237 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
229 monitor=$(jq -r ".display" <<<"$entry") 238 /* background-color: white; */
230 if [[ ! -f "$blurred_path" ]]; then 239 background-color: rgba(0, 0, 0, 0.5);
231 mkdir -p "$(dirname "$blurred_path")" 240 }
232 magick "$path" -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$blurred_path" & 241 ''} "$RUNTIME_DIRECTORY"/style.css
233 fi 242 for monitor in "''${!monitors[@]}"; do
234 monitors+=([$monitor]="$blurred_path") 243 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF
235 done < <(wpaperctl all-wallpapers -j | jq -c ".[]") 244 window#''${monitor} {
236 wait 245 background-image: url("''${monitors[$monitor]}");
237 246 background-repeat: no-repeat;
238 cp --no-preserve=mode ${pkgs.writeText "gtklock.css" '' 247 background-size: 100% 100%;
239 #window-box { 248 background-origin: content-box;
240 padding: 64px;
241 /* border: 1px solid black; */
242 border-radius: 4px;
243 box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 12px;
244 /* background-color: white; */
245 background-color: rgba(0, 0, 0, 0.5);
246 } 249 }
247 ''} "$RUNTIME_DIRECTORY"/style.css 250 EOF
248 for monitor in "''${!monitors[@]}"; do 251 done
249 cat >>"$RUNTIME_DIRECTORY"/style.css <<EOF 252 '';
250 window#''${monitor} { 253 })}"
251 background-image: url("''${monitors[$monitor]}");
252 background-repeat: no-repeat;
253 background-size: 100% 100%;
254 background-origin: content-box;
255 }
256 EOF
257 done
258 '')
259 ]; 254 ];
260 NotifyAccess = "all"; 255 NotifyAccess = "all";
261 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" '' 256 ExecStart = ''${lib.getExe pkgs.gtklock} -s "''${RUNTIME_DIRECTORY}/style.css" -L ${pkgs.writeShellScript "after-lock" ''
262 ${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off 257 ${lib.getExe cfg.programs.niri.package} msg action power-off-monitors
263 ${config.systemd.package}/bin/systemd-notify --ready 258 ${lib.getExe' config.systemd.package "systemd-notify"} --ready
264 ''}''; 259 ''}'';
265 }; 260 };
266 }; 261 };
@@ -313,8 +308,8 @@ in {
313 WantedBy = ["graphical-session.target"]; 308 WantedBy = ["graphical-session.target"];
314 }; 309 };
315 Unit = { 310 Unit = {
316 BindsTo = ["graphical-session-pre.target"]; 311 After = [ "graphical-session.target" ];
317 After = ["graphical-session-pre.target"]; 312 PartOf = [ "graphical-session.target" ];
318 }; 313 };
319 Service = { 314 Service = {
320 ExecStart = lib.getExe cfg.programs.wpaperd.package; 315 ExecStart = lib.getExe cfg.programs.wpaperd.package;
@@ -323,6 +318,36 @@ in {
323 RestartSec = "2s"; 318 RestartSec = "2s";
324 }; 319 };
325 }; 320 };
321 xembed-sni-proxy = {
322 Unit = {
323 PartOf = lib.mkForce ["tray.target"];
324 BindsTo = ["xwayland-satellite.service"];
325 After = ["xwayland-satellite.service"];
326 };
327 };
328 poweralertd = {
329 Unit = {
330 After = ["graphical-session.target"];
331 };
332 };
333 network-manager-applet = {
334 Unit = {
335 PartOf = lib.mkForce ["tray.target"];
336 };
337 };
338 udiskie = {
339 Unit = {
340 PartOf = lib.mkForce ["tray.target"];
341 };
342 };
343 blueman-applet = {
344 Unit = {
345 PartOf = lib.mkForce ["tray.target"];
346 };
347 Install = {
348 WantedBy = lib.mkForce ["tray.target"];
349 };
350 };
326 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" { 351 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" {
327 Unit = { 352 Unit = {
328 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 353 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
@@ -373,6 +398,9 @@ in {
373 }; 398 };
374 tray = { 399 tray = {
375 Unit = { 400 Unit = {
401 PartOf = [ "graphical-session.target" ];
402 Requires = [ "waybar.service" ];
403 After = [ "graphical-session.target" "waybar.service" ];
376 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"]; 404 Wants = ["blueman-applet.service" "udiskie.service" "network-manager-applet.service"];
377 }; 405 };
378 }; 406 };
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/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/flake.lock b/flake.lock
index 27a157d5..876ebecf 100644
--- a/flake.lock
+++ b/flake.lock
@@ -322,11 +322,11 @@
322 ] 322 ]
323 }, 323 },
324 "locked": { 324 "locked": {
325 "lastModified": 1736014120, 325 "lastModified": 1737831749,
326 "narHash": "sha256-ZrI+mcuQfal5IfT4HsxVEiiFNAgV4qYh+B4/NyXxpAs=", 326 "narHash": "sha256-La1xZYZ1yHvT4h5MNl5WC2wxBi2P4vozce2n7V/9+2w=",
327 "owner": "gkleen", 327 "owner": "gkleen",
328 "repo": "home-manager", 328 "repo": "home-manager",
329 "rev": "99e8412a18eb7e0731aa2b77abeed00d6d1863ad", 329 "rev": "8b16ee252e38acc29ba634ab60672a051ebc9f59",
330 "type": "github" 330 "type": "github"
331 }, 331 },
332 "original": { 332 "original": {
@@ -359,11 +359,11 @@
359 }, 359 },
360 "impermanence": { 360 "impermanence": {
361 "locked": { 361 "locked": {
362 "lastModified": 1736688610, 362 "lastModified": 1737831083,
363 "narHash": "sha256-1Zl9xahw399UiZSJ9Vxs1W4WRFjO1SsNdVZQD4nghz0=", 363 "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=",
364 "owner": "nix-community", 364 "owner": "nix-community",
365 "repo": "impermanence", 365 "repo": "impermanence",
366 "rev": "c64bed13b562fc3bb454b48773d4155023ac31b7", 366 "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170",
367 "type": "github" 367 "type": "github"
368 }, 368 },
369 "original": { 369 "original": {
@@ -397,11 +397,11 @@
397 "xwayland-satellite-unstable": "xwayland-satellite-unstable" 397 "xwayland-satellite-unstable": "xwayland-satellite-unstable"
398 }, 398 },
399 "locked": { 399 "locked": {
400 "lastModified": 1736855225, 400 "lastModified": 1737961005,
401 "narHash": "sha256-2+ayH/0B37BLPJy4thO1titHIrVCoDdCtdnl0CyV8kc=", 401 "narHash": "sha256-b4hqJNgyx8lnngz7NFcJ1W+59xQnMQYF0EK5g0IOy7c=",
402 "owner": "sodiboo", 402 "owner": "sodiboo",
403 "repo": "niri-flake", 403 "repo": "niri-flake",
404 "rev": "b013bedcff63b5cdbb9cd9841ac339361fc5cfcc", 404 "rev": "e98ae62893568dd31e7a7e4e75e1dbbf23f759a0",
405 "type": "github" 405 "type": "github"
406 }, 406 },
407 "original": { 407 "original": {
@@ -431,11 +431,11 @@
431 "niri-unstable": { 431 "niri-unstable": {
432 "flake": false, 432 "flake": false,
433 "locked": { 433 "locked": {
434 "lastModified": 1736861309, 434 "lastModified": 1737795105,
435 "narHash": "sha256-RSCoXyngYF+7apD5pRq6lZfRbl8vHIUVI57bbihA5Ew=", 435 "narHash": "sha256-OsrjQ8O9t9NjDCwfG/EY8MT+K3lb+A5U6SZZ+4PyKzk=",
436 "owner": "gkleen", 436 "owner": "gkleen",
437 "repo": "niri", 437 "repo": "niri",
438 "rev": "80a7ee2971b2d43622f68dcdc3233ae8365338f6", 438 "rev": "78697d1cea20e6b53013e820999b0403c45d9f00",
439 "type": "github" 439 "type": "github"
440 }, 440 },
441 "original": { 441 "original": {
@@ -472,11 +472,11 @@
472 ] 472 ]
473 }, 473 },
474 "locked": { 474 "locked": {
475 "lastModified": 1736652904, 475 "lastModified": 1737861961,
476 "narHash": "sha256-8uolHABgroXqzs03QdulHp8H9e5kWQZnnhcda1MKbBM=", 476 "narHash": "sha256-LIRtMvAwLGb8pBoamzgEF67oKlNPz4LuXiRPVZf+TpE=",
477 "owner": "Mic92", 477 "owner": "Mic92",
478 "repo": "nix-index-database", 478 "repo": "nix-index-database",
479 "rev": "271e5bd7c57e1f001693799518b10a02d1123b12", 479 "rev": "79b7b8eae3243fc5aa9aad34ba6b9bbb2266f523",
480 "type": "github" 480 "type": "github"
481 }, 481 },
482 "original": { 482 "original": {
@@ -508,11 +508,11 @@
508 }, 508 },
509 "nixos-hardware": { 509 "nixos-hardware": {
510 "locked": { 510 "locked": {
511 "lastModified": 1736441705, 511 "lastModified": 1737751639,
512 "narHash": "sha256-OL7leZ6KBhcDF3nEKe4aZVfIm6xQpb1Kb+mxySIP93o=", 512 "narHash": "sha256-ZEbOJ9iT72iwqXsiEMbEa8wWjyFvRA9Ugx8utmYbpz4=",
513 "owner": "NixOS", 513 "owner": "NixOS",
514 "repo": "nixos-hardware", 514 "repo": "nixos-hardware",
515 "rev": "8870dcaff63dfc6647fb10648b827e9d40b0a337", 515 "rev": "dfad538f751a5aa5d4436d9781ab27a6128ec9d4",
516 "type": "github" 516 "type": "github"
517 }, 517 },
518 "original": { 518 "original": {
@@ -630,11 +630,11 @@
630 }, 630 },
631 "nixpkgs-stable_2": { 631 "nixpkgs-stable_2": {
632 "locked": { 632 "locked": {
633 "lastModified": 1736684107, 633 "lastModified": 1737885640,
634 "narHash": "sha256-vH5mXxEvZeoGNkqKoCluhTGfoeXCZ1seYhC2pbMN0sg=", 634 "narHash": "sha256-GFzPxJzTd1rPIVD4IW+GwJlyGwBDV1Tj5FLYwDQQ9sM=",
635 "owner": "NixOS", 635 "owner": "NixOS",
636 "repo": "nixpkgs", 636 "repo": "nixpkgs",
637 "rev": "635e887b48521e912a516625eee7df6cf0eba9c1", 637 "rev": "4e96537f163fad24ed9eb317798a79afc85b51b7",
638 "type": "github" 638 "type": "github"
639 }, 639 },
640 "original": { 640 "original": {
@@ -678,11 +678,11 @@
678 }, 678 },
679 "nixpkgs_2": { 679 "nixpkgs_2": {
680 "locked": { 680 "locked": {
681 "lastModified": 1736798957, 681 "lastModified": 1737885589,
682 "narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=", 682 "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=",
683 "owner": "NixOS", 683 "owner": "NixOS",
684 "repo": "nixpkgs", 684 "repo": "nixpkgs",
685 "rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3", 685 "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8",
686 "type": "github" 686 "type": "github"
687 }, 687 },
688 "original": { 688 "original": {
@@ -748,11 +748,11 @@
748 "treefmt-nix": "treefmt-nix" 748 "treefmt-nix": "treefmt-nix"
749 }, 749 },
750 "locked": { 750 "locked": {
751 "lastModified": 1736774291, 751 "lastModified": 1736884309,
752 "narHash": "sha256-1rEUm7R93L8rltgyBzon2/lzIN2udC/Kd8nyvuDN6ps=", 752 "narHash": "sha256-eiCqmKl0BIRiYk5/ZhZozwn4/7Km9CWTbc15Cv+VX5k=",
753 "owner": "nix-community", 753 "owner": "nix-community",
754 "repo": "poetry2nix", 754 "repo": "poetry2nix",
755 "rev": "499221030113adc5dea05886a1d7aa1cc3a315d1", 755 "rev": "75d0515332b7ca269f6d7abfd2c44c47a7cbca7b",
756 "type": "github" 756 "type": "github"
757 }, 757 },
758 "original": { 758 "original": {
@@ -891,11 +891,11 @@
891 ] 891 ]
892 }, 892 },
893 "locked": { 893 "locked": {
894 "lastModified": 1736808430, 894 "lastModified": 1737411508,
895 "narHash": "sha256-wlgdf/n7bJMLBheqt1jmPoxJFrUP6FByKQFXuM9YvIk=", 895 "narHash": "sha256-j9IdflJwRtqo9WpM0OfAZml47eBblUHGNQTe62OUqTw=",
896 "owner": "Mic92", 896 "owner": "Mic92",
897 "repo": "sops-nix", 897 "repo": "sops-nix",
898 "rev": "553c7cb22fed19fd60eb310423fdc93045c51ba8", 898 "rev": "015d461c16678fc02a2f405eb453abb509d4e1d4",
899 "type": "github" 899 "type": "github"
900 }, 900 },
901 "original": { 901 "original": {
@@ -966,16 +966,16 @@
966 ] 966 ]
967 }, 967 },
968 "locked": { 968 "locked": {
969 "lastModified": 1734278650, 969 "lastModified": 1737014022,
970 "narHash": "sha256-z9FiyHDbKC2nwfd/qsHCxLBEogzQj73zo85lW3zIlzY=", 970 "narHash": "sha256-5cG3lbjvrqvotI3oEPham3jGq8Fd96NfrqCGvC1e6Qw=",
971 "owner": "gkleen", 971 "owner": "gkleen",
972 "repo": "Waybar", 972 "repo": "Waybar",
973 "rev": "5432f9c1697a8d2d3e1264a5ce820d7eac26e2c6", 973 "rev": "83765e0f8e99a7d344eae511a4090a76a27e5791",
974 "type": "github" 974 "type": "github"
975 }, 975 },
976 "original": { 976 "original": {
977 "owner": "gkleen", 977 "owner": "gkleen",
978 "ref": "feat/privacy-ignore", 978 "ref": "feat/niri-workspaces-hide",
979 "repo": "Waybar", 979 "repo": "Waybar",
980 "type": "github" 980 "type": "github"
981 } 981 }
@@ -1000,11 +1000,11 @@
1000 "xwayland-satellite-unstable": { 1000 "xwayland-satellite-unstable": {
1001 "flake": false, 1001 "flake": false,
1002 "locked": { 1002 "locked": {
1003 "lastModified": 1736487362, 1003 "lastModified": 1737837494,
1004 "narHash": "sha256-4kGoOA7FgK9N2mzS+TFEn41kUUNY6KwdiA/0rqlr868=", 1004 "narHash": "sha256-wIMowP8Juas4ZwMRcpc+58sZ0kKTDu8fm13THPmv/F8=",
1005 "owner": "Supreeeme", 1005 "owner": "Supreeeme",
1006 "repo": "xwayland-satellite", 1006 "repo": "xwayland-satellite",
1007 "rev": "8f55e27f63a749881c4bbfbb6b1da028342a91d1", 1007 "rev": "3944c9a0e40e5629f16ad023bbc90dac80d35a0f",
1008 "type": "github" 1008 "type": "github"
1009 }, 1009 },
1010 "original": { 1010 "original": {
diff --git a/flake.nix b/flake.nix
index 47c03f23..5cc1e298 100644
--- a/flake.nix
+++ b/flake.nix
@@ -172,7 +172,7 @@
172 type = "github"; 172 type = "github";
173 owner = "gkleen"; 173 owner = "gkleen";
174 repo = "Waybar"; 174 repo = "Waybar";
175 ref = "feat/privacy-ignore"; 175 ref = "feat/niri-workspaces-hide";
176 inputs = { 176 inputs = {
177 nixpkgs.follows = "nixpkgs"; 177 nixpkgs.follows = "nixpkgs";
178 flake-compat.follows = "flake-compat"; 178 flake-compat.follows = "flake-compat";
@@ -335,7 +335,7 @@
335 nixosConfigurations = installerNixosConfigurations // nixImport rec { dir = ./hosts; _import = mkNixosConfiguration [] dir; }; 335 nixosConfigurations = installerNixosConfigurations // nixImport rec { dir = ./hosts; _import = mkNixosConfiguration [] dir; };
336 336
337 homeModules = nixImport rec { dir = ./home-modules; }; 337 homeModules = nixImport rec { dir = ./home-modules; };
338 homeConfigurations = listToAttrs (concatLists (mapAttrsToList (hostname: nixosConfig: mapAttrsToList (username: configuration: nameValuePair "${username}@${hostname}" { inherit (configuration.home) activationPackage; inherit (configuration) home-files; }) nixosConfig.config.home-manager.users) self.nixosConfigurations)); 338 homeConfigurations = listToAttrs (concatLists (mapAttrsToList (hostname: nixosConfig: mapAttrsToList (username: nameValuePair "${username}@${hostname}") nixosConfig.config.home-manager.users) self.nixosConfigurations));
339 339
340 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths; 340 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths;
341 341
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
index 6dc6f3a9..32651e14 100644
--- a/hosts/sif/default.nix
+++ b/hosts/sif/default.nix
@@ -12,7 +12,7 @@ let
12in { 12in {
13 imports = with flake.nixosModules.systemProfiles; [ 13 imports = with flake.nixosModules.systemProfiles; [
14 ./hw.nix 14 ./hw.nix
15 ./mail ./libvirt 15 ./mail ./libvirt ./greetd
16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager 16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager
17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1 17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1
18 flakeInputs.impermanence.nixosModules.impermanence 18 flakeInputs.impermanence.nixosModules.impermanence
@@ -63,15 +63,20 @@ in {
63 plymouth.enable = true; 63 plymouth.enable = true;
64 64
65 kernelPackages = pkgs.linuxPackages_latest; 65 kernelPackages = pkgs.linuxPackages_latest;
66 extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ];
67 kernelModules = ["v4l2loopback"];
68 kernelPatches = [ 66 kernelPatches = [
69 { name = "edac-config"; 67 { name = "edac-config";
70 patch = null; 68 patch = null;
71 extraConfig = '' 69 extraStructuredConfig = with lib.kernel; {
72 EDAC y 70 EDAC = yes;
73 EDAC_IE31200 y 71 EDAC_IE31200 = yes;
74 ''; 72 };
73 }
74 { name = "zswap-default";
75 patch = null;
76 extraStructuredConfig = with lib.kernel; {
77 ZSWAP_DEFAULT_ON = yes;
78 ZSWAP_SHRINKER_DEFAULT_ON = yes;
79 };
75 } 80 }
76 ]; 81 ];
77 82
@@ -440,7 +445,7 @@ in {
440 }; 445 };
441 446
442 xserver = { 447 xserver = {
443 enable = true; 448 enable = false;
444 449
445 xkb = { 450 xkb = {
446 layout = "us"; 451 layout = "us";
@@ -466,46 +471,15 @@ in {
466 }; 471 };
467 libinput.enable = true; 472 libinput.enable = true;
468 473
469 greetd = { 474 envfs.enable = false;
470 enable = true;
471 # settings.default_session.command = let
472 # cfg = config.programs.regreet;
473 # in pkgs.writeShellScript "greeter" ''
474 # modprobe -r nvidia_drm
475
476 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}
477 # '';
478 };
479 }; 475 };
480 476
481 programs.regreet = {
482 enable = true;
483 theme = {
484 package = pkgs.equilux-theme;
485 name = "Equilux-compact";
486 };
487 iconTheme = {
488 package = pkgs.paper-icon-theme;
489 name = "Paper-Mono-Dark";
490 };
491 font = {
492 package = pkgs.fira;
493 name = "Fira Sans";
494 # size = 6;
495 };
496 cageArgs = [ "-s" "-m" "last" ];
497 settings = {
498 GTK.application_prefer_dark_theme = true;
499 };
500 };
501 programs.niri.enable = true;
502
503 systemd.tmpfiles.settings = { 477 systemd.tmpfiles.settings = {
504 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime"; 478 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime";
505 479
506 "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" { 480 "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" {
507 last_user = "gkleen"; 481 last_user = "gkleen";
508 user_to_last_sess.gkleen = "Niri"; 482 user_to_last_sess.gkleen = "niri";
509 }); 483 });
510 }; 484 };
511 485
@@ -615,15 +589,15 @@ in {
615 }; 589 };
616 590
617 nvidia = { 591 nvidia = {
618 open = true; 592 open = false;
619 modesetting.enable = true; 593 modesetting.enable = true;
620 powerManagement.enable = true; 594 powerManagement.enable = true;
621 prime = { 595 # prime = {
622 nvidiaBusId = "PCI:1:0:0"; 596 # nvidiaBusId = "PCI:1:0:0";
623 intelBusId = "PCI:0:2:0"; 597 # intelBusId = "PCI:0:2:0";
624 reverseSync.enable = true; 598 # reverseSync.enable = true;
625 offload.enableOffloadCmd = true; 599 # offload.enableOffloadCmd = true;
626 }; 600 # };
627 }; 601 };
628 602
629 graphics = { 603 graphics = {
@@ -697,6 +671,7 @@ in {
697 671
698 services.dbus.packages = with pkgs; 672 services.dbus.packages = with pkgs;
699 [ dbus dconf 673 [ dbus dconf
674 xdg-desktop-portal-gtk
700 ]; 675 ];
701 676
702 services.udisks2.enable = true; 677 services.udisks2.enable = true;
@@ -705,12 +680,8 @@ in {
705 light.enable = true; 680 light.enable = true;
706 wireshark.enable = true; 681 wireshark.enable = true;
707 dconf.enable = true; 682 dconf.enable = true;
708 }; 683 niri.enable = true;
709 684 fuse.userAllowOther = true;
710 zramSwap = {
711 enable = true;
712 algorithm = "zstd";
713 writebackDevice = "/dev/disk/by-label/swap";
714 }; 685 };
715 686
716 services.pcscd.enable = true; 687 services.pcscd.enable = true;
@@ -730,6 +701,16 @@ in {
730 environment.sessionVariables."GTK_USE_PORTAL" = "1"; 701 environment.sessionVariables."GTK_USE_PORTAL" = "1";
731 xdg.portal = { 702 xdg.portal = {
732 enable = true; 703 enable = true;
704 extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
705 config.niri = {
706 default = ["gnome" "gtk"];
707 "org.freedesktop.impl.portal.FileChooser" = ["gtk"];
708 "org.freedesktop.impl.portal.OpenFile" = ["gtk"];
709 "org.freedesktop.impl.portal.Access" = ["gtk"];
710 "org.freedesktop.impl.portal.Notification" = ["gtk"];
711 "org.freedesktop.impl.portal.Secret" = ["gnome-keyring"];
712 "org.freedesktop.impl.portal.Inhibit" = ["none"];
713 };
733 }; 714 };
734 715
735 environment.persistence."/.bcachefs" = { 716 environment.persistence."/.bcachefs" = {
@@ -737,11 +718,11 @@ in {
737 directories = [ 718 directories = [
738 "/nix" 719 "/nix"
739 "/root" 720 "/root"
721 "/home"
740 "/var/log" 722 "/var/log"
741 "/var/lib/sops-nix" 723 "/var/lib/sops-nix"
742 "/var/lib/nixos" 724 "/var/lib/nixos"
743 "/var/lib/systemd" 725 "/var/lib/systemd"
744 "/home"
745 "/var/lib/chrony" 726 "/var/lib/chrony"
746 "/var/lib/fprint" 727 "/var/lib/fprint"
747 "/var/lib/bluetooth" 728 "/var/lib/bluetooth"
@@ -770,6 +751,10 @@ in {
770 751
771 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ]; 752 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ];
772 753
754 environment.pathsToLink = [
755 "share/zsh"
756 ];
757
773 system.stateVersion = "24.11"; 758 system.stateVersion = "24.11";
774 }; 759 };
775} 760}
diff --git a/hosts/sif/greetd/default.nix b/hosts/sif/greetd/default.nix
new file mode 100644
index 00000000..f609fc05
--- /dev/null
+++ b/hosts/sif/greetd/default.nix
@@ -0,0 +1,44 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.greetd = {
5 enable = true;
6 # settings.default_session.command = let
7 # cfg = config.programs.regreet;
8 # in pkgs.writeShellScript "greeter" ''
9 # modprobe -r nvidia_drm
10
11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}
12 # '';
13 };
14 programs.regreet = {
15 enable = true;
16 theme = {
17 package = pkgs.equilux-theme;
18 name = "Equilux-compact";
19 };
20 iconTheme = {
21 package = pkgs.paper-icon-theme;
22 name = "Paper-Mono-Dark";
23 };
24 font = {
25 package = pkgs.fira;
26 name = "Fira Sans";
27 # size = 6;
28 };
29 cageArgs = [ "-s" "-m" "last" ];
30 settings = {
31 GTK.application_prefer_dark_theme = true;
32 widget.clock.format = "%F %H:%M:%S%:z";
33 background = {
34 path = pkgs.runCommand "wallpaper.png" {
35 buildInputs = with pkgs; [ imagemagick ];
36 } ''
37 magick ${./wallpaper.png} -filter Gaussian -resize 6.25% -define filter:sigma=2.5 -resize 1600% "$out"
38 '';
39 fit = "Cover";
40 };
41 };
42 };
43 };
44}
diff --git a/hosts/sif/greetd/wallpaper.png b/hosts/sif/greetd/wallpaper.png
new file mode 100644
index 00000000..20fc761a
--- /dev/null
+++ b/hosts/sif/greetd/wallpaper.png
Binary files differ
diff --git a/hosts/sif/hw.nix b/hosts/sif/hw.nix
index d1fb2934..1bcf0261 100644
--- a/hosts/sif/hw.nix
+++ b/hosts/sif/hw.nix
@@ -19,6 +19,9 @@
19 "/var/lib/sops-nix".neededForBoot = true; 19 "/var/lib/sops-nix".neededForBoot = true;
20 "/var/lib/systemd".neededForBoot = true; 20 "/var/lib/systemd".neededForBoot = true;
21 }; 21 };
22 swapDevices = [
23 { label = "swap"; }
24 ];
22 # system.etc.overlay.enable = false; 25 # system.etc.overlay.enable = false;
23 26
24 boot.initrd.systemd.packages = [ 27 boot.initrd.systemd.packages = [
diff --git a/hosts/sif/libvirt/default.nix b/hosts/sif/libvirt/default.nix
index d0be7dff..9712d0d9 100644
--- a/hosts/sif/libvirt/default.nix
+++ b/hosts/sif/libvirt/default.nix
@@ -8,6 +8,7 @@ with flakeInputs.nixVirt.lib;
8 qemu.swtpm.enable = true; 8 qemu.swtpm.enable = true;
9 allowedBridges = ["virbr0" "rz-0971" "rz-2403"]; 9 allowedBridges = ["virbr0" "rz-0971" "rz-2403"];
10 }; 10 };
11 virtualisation.spiceUSBRedirection.enable = true;
11 virtualisation.libvirt = { 12 virtualisation.libvirt = {
12 enable = true; 13 enable = true;
13 swtpm.enable = true; 14 swtpm.enable = true;
diff --git a/hosts/sif/mail/default.nix b/hosts/sif/mail/default.nix
index f36cd599..8d6cd705 100644
--- a/hosts/sif/mail/default.nix
+++ b/hosts/sif/mail/default.nix
@@ -1,4 +1,4 @@
1{ config, pkgs, ... }: 1{ config, lib, pkgs, ... }:
2{ 2{
3 services.postfix = { 3 services.postfix = {
4 enable = true; 4 enable = true;
diff --git a/modules/nix-access-tokens/default.nix b/modules/nix-access-tokens/default.nix
new file mode 100644
index 00000000..a3b7abfa
--- /dev/null
+++ b/modules/nix-access-tokens/default.nix
@@ -0,0 +1,24 @@
1{ lib, config, hostName ,... }:
2
3let
4 cfg = config.nix.includeAccessTokens;
5in {
6 options = {
7 nix.includeAccessTokens.enable = lib.mkEnableOption "including access tokens in nix.conf" // { default = lib.elem hostName ["sif" "surtr" "vidhar"]; };
8 };
9
10 config = lib.mkIf cfg.enable {
11 nix = {
12 extraOptions = ''
13 !include ${config.sops.secrets.nixAccessTokens.path}
14 '';
15 };
16
17 sops.secrets.nixAccessTokens = {
18 format = "binary";
19 sopsFile = ./nix.conf;
20 mode = "0440";
21 group = "wheel";
22 };
23 };
24}
diff --git a/modules/nix-access-tokens/nix.conf b/modules/nix-access-tokens/nix.conf
new file mode 100644
index 00000000..f0b394ef
--- /dev/null
+++ b/modules/nix-access-tokens/nix.conf
@@ -0,0 +1,32 @@
1{
2 "data": "ENC[AES256_GCM,data:/cdBpvCAFpgm0YWhy1WYlA09KlU6PzVfBYVLBD0boqGqvP+8wuyDzj5KWbcKsdGhoiklODiKR0ODXNU+fA35y862PFXvSb4xVyfbdKRndYdIA4W6vyobtoC9h7B1yR9pkq9L+1tqlU30Dgy2Gndg9rWHlIo+1lO/1A==,iv:B1Px2+cxCaopHZThkEG5saOib+PNvurPIS6aeAv2uPo=,tag:K3JqRaX3/iIqD3c//YdqSQ==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5NkZUUGI3M2pQYWVXeFV6\na2h2czRTeUJFekJCS012YlBkL1FDdTd3ekZ3ClJsTVh0R2JQM0Jua1JjL285RVA1\nRHhlbjlLdmNBUXVLelFGY2NGYWpLejQKLS0tIDBUWUhJNm8zWGoyQ0pBYnV1ZjBh\ndktNRkNPS1lpWXFITC81aEZJbXlONk0Km2c1xVKwSankaVs7O/utGJwRRX395upz\ndPbsOElTnbGmkb0esGtvGSPboTvK+gjn9w/GhaPyTnNDoos7GaIfyg==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age1fj65apkhfkrwyv5tx6zcs9nkjg8267fy733qph30sc7zfn7vapjqkd5kne",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6bS9iY2lua3U4U3lJa1pK\nSlZNMmFZMEU5M1V2bWRjaXIwajZJVDJPMlM4Cmd3TTNFWjVuSGdtbC9iODltTS91\nOE5XOEVEQkh0SFpVVW5jc3IzbzNpTmMKLS0tIEtrSU54QUVPa2tBZDhLYlRFWitR\nc2x6MFlxL0tobDJTek42dEcyZXpoWDgKXzQfU+o6FkbJBwmm6oaHu4sDPi822uUR\n5VY6gY/h3g2kM4cuS03Q4NJmeRxuh7cx0UqGU3j5Mf8muE1LHpYEPw==\n-----END AGE ENCRYPTED FILE-----\n"
16 },
17 {
18 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
19 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaOVpNZ2lVT0VwbHVZNzFl\nenJsMGpnbkRvU0xOSU5obk5yT2p5ZVNzdXhNCnVlQzZtRjZNVmJLSUpKc3UwVXZs\nWi9EZ3kxZkJNeFJDSjl1L1IweTFNMXcKLS0tIDJUOTBwTldCUmlnU0tWVkZkNzJL\nejM4ajJVbVhvSm1YM2Vxa2JldllYN0UKAzxy2wkzRvCSiTy417AulpCu41z668HG\nto92eGF2ZRFfEG5LGlCKWeDcP3gM8QwKiVlm6wndbOkhMMfc4Sp3wA==\n-----END AGE ENCRYPTED FILE-----\n"
20 },
21 {
22 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
23 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2ejRHcGttNUxYZnFzTU5J\nMTFvY3daQ1VMM2xxYTgvLzZwT1owazVNenhzCktaWFF6K2s5UjI2b20rSHFNSS9E\nMVlJSmZhQm15eUs3U0hGTGpSRndmSDgKLS0tIDVrcjl4eDhwak1pRithbnRWWEZy\nVE9EOEpKdEJoRTFrTXpQVDc1cmsrU1kK/goTdUmpZPeMRbY1QzLXAa6Qpg4YYYYo\n3v3GK1bzdey8szfgIr1dHTtQEzqE2WX1swzZizDXj/RiUWx01Ky3GA==\n-----END AGE ENCRYPTED FILE-----\n"
24 }
25 ],
26 "lastmodified": "2025-01-25T19:58:58Z",
27 "mac": "ENC[AES256_GCM,data:Oza4XgnTX3vly89nGluLbEytk1dUYAiOhIYewQyDLLLSSlUIpXmWhV+X0HUQ9AX5kUrEhNbVzRdvUG/9YwoWjTJfvd7tw41IYeTqgykMNXJUfGssoutXfeij9YR+t5aJaRhlTkIWcBhUjXSUNyJCl6Z3XmzWstTPZXEU9VmAvuE=,iv:LqVwIiit+WqI5NWSboexWsmPzg7e63nWJYsNFEK1Uog=,tag:ClR6oI62WXEfIYYAY6vL0A==,type:str]",
28 "pgp": null,
29 "unencrypted_suffix": "_unencrypted",
30 "version": "3.9.3"
31 }
32} \ No newline at end of file
diff --git a/nvfetcher.toml b/nvfetcher.toml
index c0566373..ecaebba0 100644
--- a/nvfetcher.toml
+++ b/nvfetcher.toml
@@ -78,12 +78,12 @@ git.fetchSubmodules = true
78src.git = "https://github.com/jgreco/mpv-youtube-quality" 78src.git = "https://github.com/jgreco/mpv-youtube-quality"
79fetch.git = "https://github.com/jgreco/mpv-youtube-quality" 79fetch.git = "https://github.com/jgreco/mpv-youtube-quality"
80 80
81[batman-adv] 81# [batman-adv]
82src.webpage = "https://www.open-mesh.org/projects/open-mesh/wiki/Download" 82# src.webpage = "https://www.open-mesh.org/projects/open-mesh/wiki/Download"
83src.regex = "The latest version of <a[^\\>]*>batman-adv</a> is <a[^\\>]*>batman-adv-([0-9\\.]+).tar.gz</a>" 83# src.regex = "The latest version of <a[^\\>]*>batman-adv</a> is <a[^\\>]*>batman-adv-([0-9\\.]+).tar.gz</a>"
84src.from_pattern = "^.*batman-adv-([0-9\\.]+).tar.gz.*$" 84# src.from_pattern = "^.*batman-adv-([0-9\\.]+).tar.gz.*$"
85src.to_pattern = "\\1" 85# src.to_pattern = "\\1"
86fetch.tarball = "https://downloads.open-mesh.org/batman/stable/sources/batman-adv/batman-adv-$ver.tar.gz" 86# fetch.tarball = "https://downloads.open-mesh.org/batman/stable/sources/batman-adv/batman-adv-$ver.tar.gz"
87 87
88[scutiger] 88[scutiger]
89src.github_tag = "bk2204/scutiger" 89src.github_tag = "bk2204/scutiger"
@@ -107,3 +107,11 @@ fetch.tarball = "https://github.com/JonathonReinhart/spice-record/archive/refs/t
107[yt-dlp] 107[yt-dlp]
108src.pypi = "yt_dlp" 108src.pypi = "yt_dlp"
109fetch.pypi = "yt_dlp" 109fetch.pypi = "yt_dlp"
110
111[mako]
112src.git = "https://github.com/emersion/mako"
113fetch.git = "https://github.com/emersion/mako"
114
115[swayosd]
116src.git = "https://github.com/ErikReider/SwayOSD"
117fetch.git = "https://github.com/ErikReider/SwayOSD"
diff --git a/overlays/batman-adv.nix b/overlays/batman-adv.nix
deleted file mode 100644
index cce7dc4f..00000000
--- a/overlays/batman-adv.nix
+++ /dev/null
@@ -1,15 +0,0 @@
1{ final, prev, sources, ... }: {
2 linuxPackages_latest = prev.linuxPackages_latest.extend (self: super: {
3 batman_adv = super.batman_adv.overrideAttrs (oldAttrs: {
4 version = "${sources.batman-adv.version}-${self.kernel.version}";
5 inherit (sources.batman-adv) src;
6 });
7 });
8
9 linuxPackages_6_2 = prev.linuxPackages_6_2.extend (self: super: {
10 batman_adv = super.batman_adv.overrideAttrs (oldAttrs: {
11 version = "${sources.batman-adv.version}-${self.kernel.version}";
12 inherit (sources.batman-adv) src;
13 });
14 });
15}
diff --git a/overlays/keepassxc/database-open-dialog.patch b/overlays/keepassxc/database-open-dialog.patch
new file mode 100644
index 00000000..4916dc1b
--- /dev/null
+++ b/overlays/keepassxc/database-open-dialog.patch
@@ -0,0 +1,126 @@
1diff -u3 -r source.orig/src/browser/BrowserService.cpp source/src/browser/BrowserService.cpp
2--- source.orig/src/browser/BrowserService.cpp 2025-01-27 20:55:04.128198171 +0100
3+++ source/src/browser/BrowserService.cpp 2025-01-27 21:16:07.068959077 +0100
4@@ -249,7 +249,7 @@
5 return result;
6 }
7
8- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
9+ auto dialogResult = MessageBox::warning(nullptr,
10 tr("KeePassXC - Create a new group"),
11 tr("A request for creating a new group \"%1\" has been received.\n"
12 "Do you want to create this group?\n")
13@@ -422,7 +422,7 @@
14
15 m_dialogActive = true;
16 updateWindowState();
17- BrowserAccessControlDialog accessControlDialog(m_currentDatabaseWidget);
18+ BrowserAccessControlDialog accessControlDialog{};
19
20 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject()));
21
22@@ -512,7 +512,7 @@
23 QString id;
24
25 do {
26- QInputDialog keyDialog(m_currentDatabaseWidget);
27+ QInputDialog keyDialog{};
28 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &keyDialog, SLOT(reject()));
29 keyDialog.setWindowTitle(tr("KeePassXC - New key association request"));
30 keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
31@@ -535,7 +535,7 @@
32
33 contains = db->metadata()->customData()->contains(CustomData::BrowserKeyPrefix + id);
34 if (contains) {
35- dialogResult = MessageBox::warning(m_currentDatabaseWidget,
36+ dialogResult = MessageBox::warning(nullptr,
37 tr("KeePassXC - Overwrite existing key?"),
38 tr("A shared encryption key with the name \"%1\" "
39 "already exists.\nDo you want to overwrite it?")
40@@ -595,7 +595,7 @@
41 const auto existingEntries = getPasskeyEntriesWithUserHandle(rpId, userId, keyList);
42
43 raiseWindow();
44- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
45+ BrowserPasskeysConfirmationDialog confirmDialog{};
46 confirmDialog.registerCredential(username, rpId, existingEntries, timeout);
47
48 auto dialogResult = confirmDialog.exec();
49@@ -612,7 +612,7 @@
50 // If no entry is selected, show the import dialog for manual entry selection
51 auto selectedEntry = confirmDialog.getSelectedEntry();
52 if (!selectedEntry) {
53- PasskeyImporter passkeyImporter(m_currentDatabaseWidget);
54+ PasskeyImporter passkeyImporter{};
55 const auto result = passkeyImporter.showImportDialog(db,
56 nullptr,
57 origin,
58@@ -683,7 +683,7 @@
59 const auto timeout = publicKeyOptions["timeout"].toInt();
60
61 raiseWindow();
62- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
63+ BrowserPasskeysConfirmationDialog confirmDialog{};
64 confirmDialog.authenticateCredential(entries, rpId, timeout);
65 auto dialogResult = confirmDialog.exec();
66 if (dialogResult == QDialog::Accepted) {
67@@ -760,7 +760,7 @@
68
69 // Ask confirmation if entry already contains a Passkey
70 if (entry->hasPasskey()) {
71- if (MessageBox::question(m_currentDatabaseWidget,
72+ if (MessageBox::question(nullptr,
73 tr("KeePassXC - Update passkey"),
74 tr("Entry already has a passkey.\nDo you want to overwrite the passkey in %1 - %2?")
75 .arg(entry->title(), passkeyUtils()->getUsernameFromEntry(entry)),
76@@ -873,7 +873,7 @@
77 MessageBox::Button dialogResult = MessageBox::No;
78 if (!browserSettings()->alwaysAllowUpdate()) {
79 raiseWindow();
80- dialogResult = MessageBox::question(m_currentDatabaseWidget,
81+ dialogResult = MessageBox::question(nullptr,
82 tr("KeePassXC - Update Entry"),
83 tr("Do you want to update the information in %1 - %2?")
84 .arg(QUrl(entryParameters.siteUrl).host(), username),
85@@ -909,7 +909,7 @@
86 return false;
87 }
88
89- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
90+ auto dialogResult = MessageBox::warning(nullptr,
91 tr("KeePassXC - Delete entry"),
92 tr("A request for deleting entry \"%1\" has been received.\n"
93 "Do you want to delete the entry?\n")
94@@ -1536,7 +1536,7 @@
95 }
96 }
97
98- BrowserEntrySaveDialog browserEntrySaveDialog(m_currentDatabaseWidget);
99+ BrowserEntrySaveDialog browserEntrySaveDialog{};
100 int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
101 if (openDatabaseCount > 1) {
102 int res = browserEntrySaveDialog.exec();
103diff -u3 -r source.orig/src/fdosecrets/objects/Prompt.cpp source/src/fdosecrets/objects/Prompt.cpp
104--- source.orig/src/fdosecrets/objects/Prompt.cpp 2025-01-27 20:55:04.135942791 +0100
105+++ source/src/fdosecrets/objects/Prompt.cpp 2025-01-27 21:01:37.166710935 +0100
106@@ -313,7 +313,7 @@
107 if (!entries.isEmpty()) {
108 QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid());
109 auto ac = new AccessControlDialog(
110- findWindow(m_windowId), entries, app, client->processInfo(), AuthOption::Remember);
111+ nullptr, entries, app, client->processInfo(), AuthOption::Remember);
112 connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished);
113 connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater);
114 ac->open();
115diff -u3 -r source.orig/src/gui/DatabaseTabWidget.cpp source/src/gui/DatabaseTabWidget.cpp
116--- source.orig/src/gui/DatabaseTabWidget.cpp 2025-01-27 20:55:04.134589500 +0100
117+++ source/src/gui/DatabaseTabWidget.cpp 2025-01-27 21:07:09.785284837 +0100
118@@ -41,7 +41,7 @@
119 : QTabWidget(parent)
120 , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
121 , m_dbWidgetPendingLock(nullptr)
122- , m_databaseOpenDialog(new DatabaseOpenDialog(this))
123+ , m_databaseOpenDialog(new DatabaseOpenDialog())
124 , m_databaseOpenInProgress(false)
125 {
126 auto* tabBar = new QTabBar(this);
diff --git a/overlays/keepassxc/default.nix b/overlays/keepassxc/default.nix
new file mode 100644
index 00000000..25429a66
--- /dev/null
+++ b/overlays/keepassxc/default.nix
@@ -0,0 +1,8 @@
1{ final, prev, ... }:
2{
3 keepassxc = prev.keepassxc.overrideAttrs (oldAttrs: {
4 patches = (oldAttrs.patches or []) ++ [
5 ./database-open-dialog.patch
6 ];
7 });
8}
diff --git a/overlays/mako.nix b/overlays/mako.nix
new file mode 100644
index 00000000..1c1464fb
--- /dev/null
+++ b/overlays/mako.nix
@@ -0,0 +1,5 @@
1{ final, prev, sources, ... }: {
2 mako = prev.mako.overrideAttrs (oldAttrs: {
3 inherit (sources.mako) version src;
4 });
5}
diff --git a/overlays/swayosd.nix b/overlays/swayosd.nix
new file mode 100644
index 00000000..61c865e7
--- /dev/null
+++ b/overlays/swayosd.nix
@@ -0,0 +1,27 @@
1{ final, prev, sources, ... }: {
2 swayosd = prev.swayosd.overrideAttrs (oldAttrs: rec {
3 inherit (sources.swayosd) version src;
4 cargoDeps = prev.rustPlatform.fetchCargoTarball {
5 inherit (oldAttrs) pname;
6 inherit version src;
7 hash = "sha256-Anrk8p76HKZcNavYdi9l1oYahduLrb7Lf7knQK7Hy5E=";
8 };
9 nativeBuildInputs = with final; [
10 wrapGAppsHook4
11 pkg-config
12 meson
13 rustc
14 cargo
15 ninja
16 rustPlatform.cargoSetupHook
17 ];
18 buildInputs = with final; [
19 gtk4-layer-shell
20 libevdev
21 libinput
22 libpulseaudio
23 udev
24 sassc
25 ];
26 });
27}
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py
index 362c8da4..ba6c5ff6 100755
--- a/overlays/worktime/worktime/__main__.py
+++ b/overlays/worktime/worktime/__main__.py
@@ -23,7 +23,7 @@ import argparse
23from copy import deepcopy 23from copy import deepcopy
24 24
25import sys 25import sys
26from sys import stderr 26from sys import stderr, stdout
27 27
28from tabulate import tabulate 28from tabulate import tabulate
29 29
@@ -38,6 +38,7 @@ from collections import defaultdict
38 38
39import jsonpickle 39import jsonpickle
40from hashlib import blake2s 40from hashlib import blake2s
41import json
41 42
42class TogglAPISection(Enum): 43class TogglAPISection(Enum):
43 TOGGL = '/api/v9' 44 TOGGL = '/api/v9'
@@ -223,6 +224,7 @@ class Worktime(object):
223 leave_budget = dict() 224 leave_budget = dict()
224 time_per_day = None 225 time_per_day = None
225 workdays = None 226 workdays = None
227 pull_forward = dict()
226 228
227 @staticmethod 229 @staticmethod
228 @cache 230 @cache
@@ -390,8 +392,6 @@ class Worktime(object):
390 if e.errno != 2: 392 if e.errno != 2:
391 raise e 393 raise e
392 394
393 pull_forward = dict()
394
395 start_day = self.start_date.date() 395 start_day = self.start_date.date()
396 end_day = self.end_date.date() 396 end_day = self.end_date.date()
397 397
@@ -418,15 +418,15 @@ class Worktime(object):
418 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 418 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
419 else: 419 else:
420 if d >= self.end_date.date(): 420 if d >= self.end_date.date():
421 pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 421 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta()))
422 except IOError as e: 422 except IOError as e:
423 if e.errno != 2: 423 if e.errno != 2:
424 raise e 424 raise e
425 425
426 self.days_to_work = dict() 426 self.days_to_work = dict()
427 427
428 if pull_forward: 428 if self.pull_forward:
429 end_day = max(end_day, max(list(pull_forward))) 429 end_day = max(end_day, max(list(self.pull_forward)))
430 430
431 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 431 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
432 if day.isoweekday() in self.workdays: 432 if day.isoweekday() in self.workdays:
@@ -470,17 +470,17 @@ class Worktime(object):
470 self.extra_days_to_work[self.now.date()] = timedelta() 470 self.extra_days_to_work[self.now.date()] = timedelta()
471 471
472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) 472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta())
473 for day in [d for d in list(pull_forward) if d > self.end_date.date()]: 473 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]:
474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
476 days_forward = days_forward.union(extra_days_forward) 476 days_forward = days_forward.union(extra_days_forward)
477 477
478 extra_day_time_left = timedelta() 478 extra_day_time_left = timedelta()
479 for extra_day in extra_days_forward: 479 for extra_day in extra_days_forward:
480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
481 extra_day_time_left += day_time 481 extra_day_time_left += day_time
482 extra_day_time = min(extra_day_time_left, pull_forward[day]) 482 extra_day_time = min(extra_day_time_left, self.pull_forward[day])
483 time_forward = pull_forward[day] - extra_day_time 483 time_forward = self.pull_forward[day] - extra_day_time
484 if extra_day_time_left > timedelta(): 484 if extra_day_time_left > timedelta():
485 for extra_day in extra_days_forward: 485 for extra_day in extra_days_forward:
486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
@@ -518,7 +518,14 @@ def format_days(worktime, days, date_format=None):
518 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups)) 518 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups))
519 519
520 520
521def worktime(**args): 521def tooltip_timedelta(td):
522 if td < timedelta(seconds = 0):
523 return "-" + tooltip_timedelta(-td)
524 mm, ss = divmod(td.total_seconds(), 60)
525 hh, mm = divmod(mm, 60)
526 return "%d:%02d:%02d" % (hh, mm, ss)
527
528def worktime(pull_forward_cutoff, waybar, **args):
522 worktime = Worktime(**args) 529 worktime = Worktime(**args)
523 530
524 def format_worktime(worktime): 531 def format_worktime(worktime):
@@ -562,7 +569,12 @@ def worktime(**args):
562 else: 569 else:
563 return f"({difference_string})" 570 return f"({difference_string})"
564 571
565 if worktime.time_pulled_forward >= timedelta(minutes = 15): 572 out_class = "running" if worktime.running_entry else "stopped"
573 difference = worktime.time_to_work - worktime.time_worked
574 if worktime.running_entry and -min(timedelta(milliseconds=0), difference) > sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) or not worktime.running_entry and max(timedelta(milliseconds=0), difference) > worktime.time_per_day(worktime.now.date()) and worktime.now_is_workday:
575 out_class = "over"
576 tooltip = tooltip_timedelta(difference)
577 if worktime.time_pulled_forward >= min(pull_forward_cutoff, timedelta(seconds = 1)):
566 worktime_no_pulled_forward = deepcopy(worktime) 578 worktime_no_pulled_forward = deepcopy(worktime)
567 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 579 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
568 worktime_no_pulled_forward.time_pulled_forward = timedelta() 580 worktime_no_pulled_forward.time_pulled_forward = timedelta()
@@ -570,11 +582,24 @@ def worktime(**args):
570 difference_string = format_worktime(worktime) 582 difference_string = format_worktime(worktime)
571 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward) 583 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward)
572 584
573 print(f"{difference_string_no_pulled_forward}…{difference_string}") 585 tooltip = tooltip_timedelta(worktime_no_pulled_forward.time_to_work - worktime_no_pulled_forward.time_worked) + "…" + tooltip
586 if worktime.time_pulled_forward >= pull_forward_cutoff:
587 out_text = f"{difference_string_no_pulled_forward}…{difference_string}"
588 else:
589 out_text = format_worktime(worktime)
590 else:
591 out_text = format_worktime(worktime)
592
593 if waybar:
594 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
574 else: 595 else:
575 print(format_worktime(worktime)) 596 print(out_text)
597
598def pull_forward(**args):
599 worktime = Worktime(**args)
600 print(tooltip_timedelta(sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))))
576 601
577def time_worked(now, **args): 602def time_worked(now, waybar, **args):
578 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 603 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
579 if now.time() == time(): 604 if now.time() == time():
580 now = now + timedelta(days = 1) 605 now = now + timedelta(days = 1)
@@ -584,6 +609,9 @@ def time_worked(now, **args):
584 609
585 worked = now.time_worked - then.time_worked 610 worked = now.time_worked - then.time_worked
586 611
612 out_text = None
613 out_class = "stopped"
614 tooltip = tooltip_timedelta(worked)
587 if args['do_round']: 615 if args['do_round']:
588 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5)) 616 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5))
589 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60) 617 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60)
@@ -602,15 +630,25 @@ def time_worked(now, **args):
602 difference = target_time - worked 630 difference = target_time - worked
603 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5)) 631 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5))
604 clockout_time = now.now + difference 632 clockout_time = now.now + difference
633 exact_clockout_time = clockout_time
605 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1) 634 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
606 clockout_time = clockout_time.replace(second = 0, microsecond = 0) 635 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
607 636
608 if now.running_entry and clockout_time and clockout_difference >= 0: 637 if now.running_entry and clockout_time and clockout_difference >= 0:
609 print(f"{difference_string}/{clockout_time:%H:%M}") 638 out_class = "running"
639 out_text = f"{difference_string}/{clockout_time:%H:%M}"
640 tooltip = f"{tooltip_timedelta(worked)}/{exact_clockout_time:%H:%M}"
610 else: 641 else:
611 print(difference_string) 642 if now.running_entry:
643 out_class = "over"
644 out_text = difference_string
645 else:
646 out_text = str(worked)
647
648 if waybar:
649 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
612 else: 650 else:
613 print(worked) 651 print(out_text)
614 652
615def diff(now, **args): 653def diff(now, **args):
616 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 654 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
@@ -798,6 +836,38 @@ def classification(classification_name, table, table_format, **args):
798def main(): 836def main():
799 def isotime(s): 837 def isotime(s):
800 return datetime.fromisoformat(s).replace(tzinfo=tzlocal()) 838 return datetime.fromisoformat(s).replace(tzinfo=tzlocal())
839 def duration_minutes(s):
840 return timedelta(minutes = float(s))
841
842 def set_default_subparser(self, name, args=None, positional_args=0):
843 """default subparser selection. Call after setup, just before parse_args()
844 name: is the name of the subparser to call by default
845 args: if set is the argument list handed to parse_args()
846
847 , tested with 2.7, 3.2, 3.3, 3.4
848 it works with 2.6 assuming argparse is installed
849 """
850 subparser_found = False
851 for arg in sys.argv[1:]:
852 if arg in ['-h', '--help']: # global help if no subparser
853 break
854 else:
855 for x in self._subparsers._actions:
856 if not isinstance(x, argparse._SubParsersAction):
857 continue
858 for sp_name in x._name_parser_map.keys():
859 if sp_name in sys.argv[1:]:
860 subparser_found = True
861 if not subparser_found:
862 # insert default in last position before global positional
863 # arguments, this implies no global options are specified after
864 # first positional argument
865 if args is None:
866 sys.argv.insert(len(sys.argv) - positional_args, name)
867 else:
868 args.insert(len(args) - positional_args, name)
869
870 argparse.ArgumentParser.set_default_subparser = set_default_subparser
801 871
802 config = Worktime.config() 872 config = Worktime.config()
803 873
@@ -807,9 +877,13 @@ def main():
807 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') 877 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false')
808 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false') 878 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false')
809 subparsers = parser.add_subparsers(help = 'Subcommands') 879 subparsers = parser.add_subparsers(help = 'Subcommands')
810 parser.set_defaults(cmd = worktime) 880 worktime_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked'])
811 time_worked_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked', 'today']) 881 worktime_parser.add_argument('--pull-forward-cutoff', dest = 'pull_forward_cutoff', metavar = 'MINUTES', type = duration_minutes, default = timedelta(minutes = 15))
882 worktime_parser.add_argument('--waybar', action='store_true')
883 worktime_parser.set_defaults(cmd = worktime)
884 time_worked_parser = subparsers.add_parser('today')
812 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false') 885 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false')
886 time_worked_parser.add_argument('--waybar', action='store_true')
813 time_worked_parser.set_defaults(cmd = time_worked) 887 time_worked_parser.set_defaults(cmd = time_worked)
814 diff_parser = subparsers.add_parser('diff') 888 diff_parser = subparsers.add_parser('diff')
815 diff_parser.set_defaults(cmd = diff) 889 diff_parser.set_defaults(cmd = diff)
@@ -827,6 +901,9 @@ def main():
827 classification_parser.add_argument('--table', action = 'store_true') 901 classification_parser.add_argument('--table', action = 'store_true')
828 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid') 902 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid')
829 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name)) 903 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name))
904 pull_forward_parser = subparsers.add_parser('pull-forward')
905 pull_forward_parser.set_defaults(cmd = pull_forward)
906 parser.set_default_subparser('time_worked')
830 args = parser.parse_args() 907 args = parser.parse_args()
831 908
832 args.cmd(**vars(args)) 909 args.cmd(**vars(args))