+3
.gitignore
+3
.gitignore
+1
-20
README.md
+1
-20
README.md
···
10
11
<br />
12
13
-

14
-
15
-
<details>
16
-
<summary>more previews</summary>
17
-
18
-
<p align="center">
19
-
light mode
20
-
<img src="./docs/src/images/lightmode.png" width="800px" />
21
-
</p>
22
-
23
-
<p align="center">
24
-
wezterm + chromium
25
-
<img src="./docs/src/images/blur.png" width="800px" />
26
-
</p>
27
-
28
-
<p align="center">
29
-
neovim
30
-
<img src="./docs/src/images/nvim.png" width="800px" />
31
-
</p>
32
-
</details>
33
34

35
+2
-5
docs/book.toml
+2
-5
docs/book.toml
···
1
[book]
2
authors = ["isabel roses"]
3
language = "en"
4
-
multilingual = false
5
src = "src"
6
title = "dotfiles"
7
···
13
default-theme = "latte"
14
preferred-dark-theme = "mocha"
15
git-repository-url = "https://github.com/isabelroses/dotfiles"
16
-
git-repository-icon = "fa-code-fork"
17
cname = "dotfiles.isabelroses.com"
18
-
additional-css = ["./theme/catppuccin.css", "./theme/catppuccin-alerts.css"]
19
-
20
-
[preprocessor.alerts]
···
1
[book]
2
authors = ["isabel roses"]
3
language = "en"
4
src = "src"
5
title = "dotfiles"
6
···
12
default-theme = "latte"
13
preferred-dark-theme = "mocha"
14
git-repository-url = "https://github.com/isabelroses/dotfiles"
15
+
# git-repository-icon = "fa-code-fork"
16
cname = "dotfiles.isabelroses.com"
17
+
additional-css = ["./theme/catppuccin.css"]
+8
docs/src/README.md
+8
docs/src/README.md
···
8
<img alt="nixos-unstable" src="https://img.shields.io/badge/NixOS-unstable-blue.svg?style=for-the-badge&labelColor=303446&logo=NixOS&logoColor=white&color=91D7E3" />
9
</div>
10
11
### Foreword
12
13
This repository contains my **personal** configuration for my systems, so its really important that you know it's **personal** and not everything will fit your needs.
···
22
- Sensible defaults, so you can get started quickly
23
- Docs kind of
24
- [Catppuccin](https://github.com/catppuccin/catppuccin) everywhere.
···
8
<img alt="nixos-unstable" src="https://img.shields.io/badge/NixOS-unstable-blue.svg?style=for-the-badge&labelColor=303446&logo=NixOS&logoColor=white&color=91D7E3" />
9
</div>
10
11
+
<br />
12
+
13
+

14
+
15
+

16
+
17
### Foreword
18
19
This repository contains my **personal** configuration for my systems, so its really important that you know it's **personal** and not everything will fit your needs.
···
28
- Sensible defaults, so you can get started quickly
29
- Docs kind of
30
- [Catppuccin](https://github.com/catppuccin/catppuccin) everywhere.
31
+
32
+
[](https://star-history.com/#isabelroses/dotfiles&Date)
-1
docs/src/SUMMARY.md
-1
docs/src/SUMMARY.md
+2
-1
docs/src/further-reading.md
+2
-1
docs/src/further-reading.md
···
27
- [nixos-hardware](https://github.com/NixOS/nixos-hardware)
28
- [getchoo/flake](https://github.com/getchoo/flake)
29
- [uncenter/flake](https://github.com/uncenter/flake)
30
31
# People
32
33
Thanks to all these people otherwise this config and I would be lost.
34
35
-
- [comfysage](https://github.com/comfysage), for making my day that bit brighter and occasionally putting me on your back
36
- [getchoo](https://github.com/getchoo), for making catppucin/nix and helping a ton
37
- [nullishamy](https://github.com/nullishamy), for tricking me into using NixOS
38
- [nekowinston](https://github.com/nekowinston), for fixing half my problems
···
40
- [nyxkrage](https://github.com/nyxkrage), for being there to answer my questions
41
- [NotAShelf](https://github.com/notashelf), lots of valuable information
42
- [Minion3665](https://github.com/Minion3665), for making me write better docs
43
- [Thorn](https://git.avery.garden/thorn), picrewnix????
···
27
- [nixos-hardware](https://github.com/NixOS/nixos-hardware)
28
- [getchoo/flake](https://github.com/getchoo/flake)
29
- [uncenter/flake](https://github.com/uncenter/flake)
30
+
- [lilyinstarlight/foosteros](https://github.com/lilyinstarlight/foosteros)
31
32
# People
33
34
Thanks to all these people otherwise this config and I would be lost.
35
36
- [getchoo](https://github.com/getchoo), for making catppucin/nix and helping a ton
37
- [nullishamy](https://github.com/nullishamy), for tricking me into using NixOS
38
- [nekowinston](https://github.com/nekowinston), for fixing half my problems
···
40
- [nyxkrage](https://github.com/nyxkrage), for being there to answer my questions
41
- [NotAShelf](https://github.com/notashelf), lots of valuable information
42
- [Minion3665](https://github.com/Minion3665), for making me write better docs
43
+
- [comfysage](https://github.com/comfysage), for the help with neovim
44
- [Thorn](https://git.avery.garden/thorn), picrewnix????
docs/src/images/blur.png
docs/src/images/blur.png
This is a binary file and will not be displayed.
docs/src/images/blur.webp
docs/src/images/blur.webp
This is a binary file and will not be displayed.
docs/src/images/lightmode.png
docs/src/images/lightmode.png
This is a binary file and will not be displayed.
docs/src/images/lightmode.webp
docs/src/images/lightmode.webp
This is a binary file and will not be displayed.
docs/src/images/main.png
docs/src/images/main.png
This is a binary file and will not be displayed.
docs/src/images/main.webp
docs/src/images/main.webp
This is a binary file and will not be displayed.
docs/src/images/nvim.png
docs/src/images/nvim.png
This is a binary file and will not be displayed.
docs/src/images/nvim.webp
docs/src/images/nvim.webp
This is a binary file and will not be displayed.
-5
docs/src/misc/previews.md
-5
docs/src/misc/previews.md
+51
-50
flake.lock
+51
-50
flake.lock
···
24
]
25
},
26
"locked": {
27
-
"lastModified": 1765990358,
28
-
"narHash": "sha256-l8x0gU8mnYaGMl+gWrsSHKBJlZWD8KXJfHTkRlFiPI0=",
29
"owner": "catppuccin",
30
"repo": "nix",
31
-
"rev": "de1b60ca45a578f59f7d84c8d338b346017b2161",
32
"type": "github"
33
},
34
"original": {
···
39
},
40
"crane": {
41
"locked": {
42
-
"lastModified": 1766774972,
43
-
"narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=",
44
"owner": "ipetkov",
45
"repo": "crane",
46
-
"rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82",
47
"type": "github"
48
},
49
"original": {
···
59
]
60
},
61
"locked": {
62
-
"lastModified": 1767028240,
63
-
"narHash": "sha256-0/fLUqwJ4Z774muguUyn5t8AQ6wyxlNbHexpje+5hRo=",
64
-
"owner": "nix-darwin",
65
"repo": "nix-darwin",
66
-
"rev": "c31afa6e76da9bbc7c9295e39c7de9fca1071ea1",
67
"type": "github"
68
},
69
"original": {
70
-
"owner": "nix-darwin",
71
"repo": "nix-darwin",
72
"type": "github"
73
}
···
90
"flake-compat": {
91
"flake": false,
92
"locked": {
93
-
"lastModified": 1761588595,
94
-
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
95
-
"owner": "edolstra",
96
"repo": "flake-compat",
97
-
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
98
"type": "github"
99
},
100
"original": {
101
-
"owner": "edolstra",
102
"repo": "flake-compat",
103
"type": "github"
104
}
···
110
]
111
},
112
"locked": {
113
-
"lastModified": 1765835352,
114
-
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
115
"owner": "hercules-ci",
116
"repo": "flake-parts",
117
-
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
118
"type": "github"
119
},
120
"original": {
···
173
]
174
},
175
"locked": {
176
-
"lastModified": 1767104570,
177
-
"narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=",
178
"owner": "nix-community",
179
"repo": "home-manager",
180
-
"rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf",
181
"type": "github"
182
},
183
"original": {
···
211
]
212
},
213
"locked": {
214
-
"lastModified": 1767230498,
215
-
"narHash": "sha256-eAENy8+m5KfcD/3HCDvF8e0BLlBSd5Se3snzGkaInoI=",
216
"owner": "isabelroses",
217
"repo": "izlix",
218
-
"rev": "d57e291079ef70c0f77ad3200b5b7db586e26922",
219
"type": "github"
220
},
221
"original": {
···
232
]
233
},
234
"locked": {
235
-
"lastModified": 1767151551,
236
-
"narHash": "sha256-OevEstP+kG3nTWYi41n+PIuscIVBXDffzIQ2ew10uL8=",
237
"owner": "isabelroses",
238
"repo": "nvim",
239
-
"rev": "d87c265a3ee73dfe6eddc63c0e3cda91249d3c31",
240
"type": "github"
241
},
242
"original": {
···
255
"rust-overlay": "rust-overlay"
256
},
257
"locked": {
258
-
"lastModified": 1767013031,
259
-
"narHash": "sha256-p8ANXBakAtfX/aEhLbU6w0tuQe3nrBvLdHbKirJP7ug=",
260
"owner": "nix-community",
261
"repo": "lanzaboote",
262
-
"rev": "c2a82339373daee8cbbcad5f51f22ae6b71069e0",
263
"type": "github"
264
},
265
"original": {
···
291
},
292
"nixpkgs": {
293
"locked": {
294
-
"lastModified": 1767151656,
295
-
"narHash": "sha256-enBX1DxL9cttnrJ+rzOds/GXWqSj/vcB97+X9mgiJho=",
296
-
"rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55",
297
"type": "tarball",
298
-
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre920165.f665af0cdb70/nixexprs.tar.xz?lastModified=1767151656&rev=f665af0cdb70ed27e1bd8f9fdfecaf451260fc55"
299
},
300
"original": {
301
"type": "tarball",
···
312
]
313
},
314
"locked": {
315
-
"lastModified": 1765911976,
316
-
"narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=",
317
"owner": "cachix",
318
"repo": "pre-commit-hooks.nix",
319
-
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
320
"type": "github"
321
},
322
"original": {
···
352
]
353
},
354
"locked": {
355
-
"lastModified": 1766976750,
356
-
"narHash": "sha256-w+o3AIBI56tzfMJRqRXg9tSXnpQRN5hAT15o2t9rxYw=",
357
"owner": "oxalica",
358
"repo": "rust-overlay",
359
-
"rev": "9fe44e7f05b734a64a01f92fc51ad064fb0a884f",
360
"type": "github"
361
},
362
"original": {
···
395
]
396
},
397
"locked": {
398
-
"lastModified": 1766896955,
399
-
"narHash": "sha256-BbAUnNjaBmfR7Mvho9BN0RfvDi5fpP19wd/Hs6DMX8k=",
400
"owner": "Mic92",
401
"repo": "sops-nix",
402
-
"rev": "861c32b27cce26c4bb828dfd21bd23df0dba7df2",
403
"type": "github"
404
},
405
"original": {
···
417
"systems": "systems"
418
},
419
"locked": {
420
-
"lastModified": 1767195736,
421
-
"narHash": "sha256-0xvPSbhIGeJzsJXNTkgJ3PjwdVItKm85wzYKA9NmSzI=",
422
"owner": "Gerg-L",
423
"repo": "spicetify-nix",
424
-
"rev": "465adc0ab6ff0c4b9b1db1c6e7fd7eeb553b3261",
425
"type": "github"
426
},
427
"original": {
···
452
]
453
},
454
"locked": {
455
-
"lastModified": 1767229092,
456
-
"narHash": "sha256-HFi+WvYK7mLVL6Cka2m9wKpy9lt2K5wPQ+oN/bmuc0Q=",
457
"owner": "tgirlcloud",
458
"repo": "pkgs",
459
-
"rev": "1d1d8e13541eaf85492e758be2a032fd20412a33",
460
"type": "github"
461
},
462
"original": {
···
24
]
25
},
26
"locked": {
27
+
"lastModified": 1767967164,
28
+
"narHash": "sha256-Cx4VETh9dGoQYDtWhre7g66d7SAr+h1h6f+SSHxVrck=",
29
"owner": "catppuccin",
30
"repo": "nix",
31
+
"rev": "e973584280e3b0e1d5b5a1a5e9948dc222c54af7",
32
"type": "github"
33
},
34
"original": {
···
39
},
40
"crane": {
41
"locked": {
42
+
"lastModified": 1767461147,
43
+
"narHash": "sha256-TH/xTeq/RI+DOzo+c+4F431eVuBpYVwQwBxzURe7kcI=",
44
"owner": "ipetkov",
45
"repo": "crane",
46
+
"rev": "7d59256814085fd9666a2ae3e774dc5ee216b630",
47
"type": "github"
48
},
49
"original": {
···
59
]
60
},
61
"locked": {
62
+
"lastModified": 1767469770,
63
+
"narHash": "sha256-Rv1kumaBqlqCvLjVYuzdA38btSyjSRvXZ8UtwalGcHo=",
64
+
"owner": "isabelroses",
65
"repo": "nix-darwin",
66
+
"rev": "1d46ab42afd10adf9751ab6cfba6b426bfb17e33",
67
"type": "github"
68
},
69
"original": {
70
+
"owner": "isabelroses",
71
+
"ref": "darwin-rebuild",
72
"repo": "nix-darwin",
73
"type": "github"
74
}
···
91
"flake-compat": {
92
"flake": false,
93
"locked": {
94
+
"lastModified": 1767039857,
95
+
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
96
+
"owner": "NixOS",
97
"repo": "flake-compat",
98
+
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
99
"type": "github"
100
},
101
"original": {
102
+
"owner": "NixOS",
103
"repo": "flake-compat",
104
"type": "github"
105
}
···
111
]
112
},
113
"locked": {
114
+
"lastModified": 1767609335,
115
+
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=",
116
"owner": "hercules-ci",
117
"repo": "flake-parts",
118
+
"rev": "250481aafeb741edfe23d29195671c19b36b6dca",
119
"type": "github"
120
},
121
"original": {
···
174
]
175
},
176
"locked": {
177
+
"lastModified": 1768068402,
178
+
"narHash": "sha256-bAXnnJZKJiF7Xr6eNW6+PhBf1lg2P1aFUO9+xgWkXfA=",
179
"owner": "nix-community",
180
"repo": "home-manager",
181
+
"rev": "8bc5473b6bc2b6e1529a9c4040411e1199c43b4c",
182
"type": "github"
183
},
184
"original": {
···
212
]
213
},
214
"locked": {
215
+
"lastModified": 1768094542,
216
+
"narHash": "sha256-8+iFjse2gJSXmWa9wX1nmk1Fv2qcYqu3nvCM7wnm7Mc=",
217
"owner": "isabelroses",
218
"repo": "izlix",
219
+
"rev": "32cf9e0aa7fb8dd024512ba7e7dccf599be8e320",
220
"type": "github"
221
},
222
"original": {
···
233
]
234
},
235
"locked": {
236
+
"lastModified": 1767756498,
237
+
"narHash": "sha256-a7SYG/6uUeeyfg1Xiq/sXpRb2RXs+kQ0GKrKMVlMw+4=",
238
"owner": "isabelroses",
239
"repo": "nvim",
240
+
"rev": "725ef38a0ea9b5bd8218aad272bd7458d430b2be",
241
"type": "github"
242
},
243
"original": {
···
256
"rust-overlay": "rust-overlay"
257
},
258
"locked": {
259
+
"lastModified": 1767697030,
260
+
"narHash": "sha256-0iVZ99H3kR5h6Lhw8kDDuUc5C/k6iismeWgCS1qWTQ4=",
261
"owner": "nix-community",
262
"repo": "lanzaboote",
263
+
"rev": "657469e8f036334db768daaf7732b1174676054b",
264
"type": "github"
265
},
266
"original": {
···
292
},
293
"nixpkgs": {
294
"locked": {
295
+
"lastModified": 1768032153,
296
+
"narHash": "sha256-zvxtwlM8ZlulmZKyYCQAPpkm5dngSEnnHjmjV7Teloc=",
297
+
"rev": "3146c6aa9995e7351a398e17470e15305e6e18ff",
298
"type": "tarball",
299
+
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre925418.3146c6aa9995/nixexprs.tar.xz?lastModified=1768032153&rev=3146c6aa9995e7351a398e17470e15305e6e18ff"
300
},
301
"original": {
302
"type": "tarball",
···
313
]
314
},
315
"locked": {
316
+
"lastModified": 1767281941,
317
+
"narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=",
318
"owner": "cachix",
319
"repo": "pre-commit-hooks.nix",
320
+
"rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa",
321
"type": "github"
322
},
323
"original": {
···
353
]
354
},
355
"locked": {
356
+
"lastModified": 1767495280,
357
+
"narHash": "sha256-hEEgtE/RSRigw8xscchGymf/t1nluZwTfru4QF6O1CQ=",
358
"owner": "oxalica",
359
"repo": "rust-overlay",
360
+
"rev": "cb24c5cc207ba8e9a4ce245eedd2d37c3a988bc1",
361
"type": "github"
362
},
363
"original": {
···
396
]
397
},
398
"locked": {
399
+
"lastModified": 1768034555,
400
+
"narHash": "sha256-M0jmN1VSfOIsP9QGY8gPMB7dhHKpcqI7MfB3/T02HXg=",
401
"owner": "Mic92",
402
"repo": "sops-nix",
403
+
"rev": "aca3a51e38a02dc6f69e11af1192362c974d4e54",
404
"type": "github"
405
},
406
"original": {
···
418
"systems": "systems"
419
},
420
"locked": {
421
+
"lastModified": 1767502559,
422
+
"narHash": "sha256-om0IPjW850vhhIrNZ5tiXjsYuqyoI44IdE+I9AwZ96I=",
423
"owner": "Gerg-L",
424
"repo": "spicetify-nix",
425
+
"rev": "806c1fdeb7af3e013215d14f5d9f06685fa6650f",
426
"type": "github"
427
},
428
"original": {
···
453
]
454
},
455
"locked": {
456
+
"lastModified": 1768093180,
457
+
"narHash": "sha256-b3yaLToFvZc9vEcg12qevVNzgP/nKmQqXvuX805M2l0=",
458
"owner": "tgirlcloud",
459
"repo": "pkgs",
460
+
"rev": "fff1718059bcb18af327003018e012387dd3008b",
461
"type": "github"
462
},
463
"original": {
+2
-1
flake.nix
+2
-1
flake.nix
-5
home/isabel/cli/shell/shellAlias.nix
-5
home/isabel/cli/shell/shellAlias.nix
···
4
mkdir = "mkdir -pv"; # always create pearent directory
5
df = "df -h"; # human readblity
6
rs = "systemctl reboot";
7
-
sysctl = "sudo systemctl";
8
jctl = "journalctl -p 3 -xb"; # get error messages from journalctl
9
lg = "lazygit";
10
11
zzzpl = "cd ~/.local/share/zzz ; git pull ; git push ; cd -";
12
zzzbk = "cd ~/.local/share/zzz ; git add . ; git commit -m 'chore: sync changes' ; git push ; cd -";
13
-
14
-
# Remap docker to podman
15
-
docker = "podman";
16
-
docker-compose = "podman-compose";
17
};
18
}
···
4
mkdir = "mkdir -pv"; # always create pearent directory
5
df = "df -h"; # human readblity
6
rs = "systemctl reboot";
7
jctl = "journalctl -p 3 -xb"; # get error messages from journalctl
8
lg = "lazygit";
9
10
zzzpl = "cd ~/.local/share/zzz ; git pull ; git push ; cd -";
11
zzzbk = "cd ~/.local/share/zzz ; git add . ; git commit -m 'chore: sync changes' ; git push ; cd -";
12
};
13
}
+8
-1
home/isabel/gui/chromium.nix
+8
-1
home/isabel/gui/chromium.nix
···
1
{
2
lib,
3
pkgs,
4
...
5
}:
6
let
···
22
"jghecgabfgfdldnmbfkhmffcabddioke" # Volume Master
23
"ndcooeababalnlpkfedmmbbbgkljhpjf" # scriptcat
24
"ephjcajbkgplkjmelpglennepbpmdpjg" # ff2mpv
25
];
26
27
package = pkgs.chromium.override {
···
187
};
188
};
189
190
home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin {
191
"Library/Application Support/Chromium/NativeMessagingHosts/ff2mpv.json".source =
192
-
"${pkgs.ff2mpv}/etc/chromium/native-messaging-hosts/ff2mpv.json";
193
};
194
}
···
1
{
2
lib,
3
pkgs,
4
+
config,
5
...
6
}:
7
let
···
23
"jghecgabfgfdldnmbfkhmffcabddioke" # Volume Master
24
"ndcooeababalnlpkfedmmbbbgkljhpjf" # scriptcat
25
"ephjcajbkgplkjmelpglennepbpmdpjg" # ff2mpv
26
+
"kpmjjdhbcfebfjgdnpjagcndoelnidfj" # Control Panel for Twitter
27
];
28
29
package = pkgs.chromium.override {
···
189
};
190
};
191
192
+
xdg.configFile = mkIf (pkgs.stdenv.hostPlatform.isLinux && config.programs.chromium.enable) {
193
+
"chromium/NativeMessagingHosts/ff2mpv.json".source =
194
+
"${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json";
195
+
};
196
+
197
home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin {
198
"Library/Application Support/Chromium/NativeMessagingHosts/ff2mpv.json".source =
199
+
"${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json";
200
};
201
}
+17
-31
home/isabel/gui/hyprland.nix
+17
-31
home/isabel/gui/hyprland.nix
···
215
};
216
217
layerrule = [
218
-
"blur,vicinae"
219
-
"ignorealpha 0, vicinae"
220
-
"noanim, vicinae"
221
];
222
223
general = {
···
225
gaps_out = 8;
226
gaps_workspaces = 0;
227
border_size = 2;
228
-
no_border_on_floating = true;
229
230
"col.active_border" = "$pink";
231
"col.inactive_border" = "$surface1";
···
294
disable_autoreload = true; # autoreload is unnecessary on nixos, because the config is readonly anyway
295
};
296
297
-
windowrulev2 = [
298
-
"float, title:^(nm-connection-editor)$"
299
-
"float, title:^(Network)$"
300
-
"float, title:^(xdg-desktop-portal-gtk)$"
301
-
"float, class:gay.vaskel.soteria"
302
-
"float, title:^(Picture-in-Picture)$"
303
-
"float, class:^(download)$"
304
-
305
-
"center(1), initialTitle:(Open Files)"
306
-
"float, initialTitle:(Open Files)"
307
-
"size 40% 60%, initialTitle:(Open Files)"
308
-
309
-
"center(1), class:.blueman-manager-wrapped"
310
-
"float, class:.blueman-manager-wrapped"
311
-
"size 40% 60%, class:.blueman-manager-wrapped"
312
-
313
-
"center(1), class:com.saivert.pwvucontrol"
314
-
"float, class:com.saivert.pwvucontrol"
315
-
"size 40% 60%, class:com.saivert.pwvucontrol"
316
317
-
# we can't just use the tag because we want to capture the popup window
318
-
"float, title:Bitwarden"
319
-
"size 800 600, title:Bitwarden"
320
-
# "no_screenshare on, tag:bitwarden"
321
322
-
"workspace 6, class:discord" # move discord to workspace 6
323
-
"workspace 7, class:spotify" # move spotify to workspace 7
324
325
# throw sharing indicators away
326
-
"workspace special silent, title:^(Firefox โ Sharing Indicator)$"
327
-
"workspace special silent, title:^(.*is sharing (your screen|a window)\.)$"
328
];
329
};
330
···
215
};
216
217
layerrule = [
218
+
"blur on, ignore_alpha 0, match:namespace vicinae"
219
+
"no_anim on, match:namespace vicinae"
220
];
221
222
general = {
···
224
gaps_out = 8;
225
gaps_workspaces = 0;
226
border_size = 2;
227
228
"col.active_border" = "$pink";
229
"col.inactive_border" = "$surface1";
···
292
disable_autoreload = true; # autoreload is unnecessary on nixos, because the config is readonly anyway
293
};
294
295
+
windowrule = [
296
+
"float on, match:title ^(nm-connection-editor)$"
297
+
"float on, match:title ^(Network)$"
298
+
"float on, match:title ^(xdg-desktop-portal-gtk)$"
299
+
"float on, match:class gay.vaskel.soteria"
300
+
"float on, match:title ^(Picture-in-Picture)$"
301
+
"float on, match:class ^(download)$"
302
303
+
"center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:initial_title (Open Files)"
304
+
"center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:class .blueman-manager-wrapped"
305
+
"center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:class com.saivert.pwvucontrol"
306
+
"float on, size 800 600, match:title Bitwarden"
307
308
+
"workspace 6, match:class discord" # move discord to workspace 6
309
+
"workspace 7, match:class spotify" # move spotify to workspace 7
310
311
# throw sharing indicators away
312
+
"workspace special silent, match:title ^(Firefox โ Sharing Indicator)$"
313
+
"workspace special silent, match:title ^(.*is sharing (your screen|a window).)$"
314
];
315
};
316
+11
-2
home/isabel/gui/media/listening.nix
+11
-2
home/isabel/gui/media/listening.nix
···
1
{
2
lib,
3
config,
4
inputs,
5
inputs',
···
16
config = mkIf config.garden.profiles.media.watching.enable {
17
programs.spicetify = {
18
enable = true;
19
enabledExtensions = with spicePkgs.extensions; [
20
shuffle
21
copyToClipboard
···
25
volumePercentage
26
aiBandBlocker
27
];
28
-
theme = spicePkgs.themes.catppuccin;
29
-
colorScheme = "mocha";
30
};
31
};
32
}
···
1
{
2
lib,
3
+
pkgs,
4
config,
5
inputs,
6
inputs',
···
17
config = mkIf config.garden.profiles.media.watching.enable {
18
programs.spicetify = {
19
enable = true;
20
+
21
+
spotifyPackage =
22
+
if pkgs.stdenv.hostPlatform.isLinux then
23
+
pkgs.spotify.override { ffmpeg_4 = pkgs.ffmpeg; }
24
+
else
25
+
pkgs.spotify;
26
+
27
+
colorScheme = "mocha";
28
+
theme = spicePkgs.themes.catppuccin;
29
+
30
enabledExtensions = with spicePkgs.extensions; [
31
shuffle
32
copyToClipboard
···
36
volumePercentage
37
aiBandBlocker
38
];
39
};
40
};
41
}
+4
-1
home/isabel/gui/media/watching.nix
+4
-1
home/isabel/gui/media/watching.nix
···
32
33
scripts =
34
(with pkgs.mpvScripts; [
35
-
videoclip
36
sponsorblock
37
38
# modern ui
39
modernz
···
111
stop-screensaver = "yes";
112
cursor-autohide = 100; # auto hide cursor after 100ms
113
reset-on-next-file = "video-zoom,panscan,video-unscaled,video-rotate,video-align-x,video-align-y";
114
};
115
116
profiles = {
···
32
33
scripts =
34
(with pkgs.mpvScripts; [
35
sponsorblock
36
+
37
+
# unify my clipboard
38
+
(videoclip.override { wl-clipboard = pkgs.wl-clipboard-rs; })
39
40
# modern ui
41
modernz
···
113
stop-screensaver = "yes";
114
cursor-autohide = 100; # auto hide cursor after 100ms
115
reset-on-next-file = "video-zoom,panscan,video-unscaled,video-rotate,video-align-x,video-align-y";
116
+
ytdl-raw-options = "cookies=~/documents/yt-dlp-cookies.txt";
117
};
118
119
profiles = {
+11
home/isabel/gui/quickshell/components/BarDivider.qml
+11
home/isabel/gui/quickshell/components/BarDivider.qml
+37
home/isabel/gui/quickshell/components/Battery.qml
+37
home/isabel/gui/quickshell/components/Battery.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import QtQuick.Controls
4
+
import "root:/data"
5
+
import "root:/components"
6
+
import "root:/services"
7
+
8
+
Item {
9
+
id: root
10
+
11
+
Layout.alignment: Qt.AlignCenter
12
+
implicitWidth: 20
13
+
implicitHeight: 20
14
+
15
+
// Only show if battery is present
16
+
visible: UPower.isPresent
17
+
18
+
MyIcon {
19
+
anchors.centerIn: parent
20
+
icon: UPower.icon
21
+
size: 18
22
+
}
23
+
24
+
// Tooltip showing percentage and status
25
+
ToolTip {
26
+
id: tooltip
27
+
visible: mouseArea.containsMouse
28
+
text: `${Math.round(UPower.percentage)}% - ${UPower.statusText}`
29
+
delay: 500
30
+
}
31
+
32
+
MouseArea {
33
+
id: mouseArea
34
+
anchors.fill: parent
35
+
hoverEnabled: true
36
+
}
37
+
}
+16
-5
home/isabel/gui/quickshell/components/Clock.qml
+16
-5
home/isabel/gui/quickshell/components/Clock.qml
···
2
import QtQuick.Layouts
3
import "root:/data"
4
5
+
Item {
6
+
id: root
7
+
8
Layout.alignment: Qt.AlignCenter
9
+
implicitWidth: 30
10
+
implicitHeight: 50
11
12
+
Text {
13
+
id: clockText
14
+
anchors.centerIn: parent
15
+
text: Time.time
16
+
color: Settings.colors.foreground
17
+
font {
18
+
pixelSize: 14
19
+
weight: Font.Medium
20
+
}
21
+
horizontalAlignment: Text.AlignHCenter
22
+
}
23
}
+60
home/isabel/gui/quickshell/components/ExpandablePanel.qml
+60
home/isabel/gui/quickshell/components/ExpandablePanel.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import Quickshell
4
+
import Quickshell.Widgets
5
+
import "root:/data"
6
+
7
+
Rectangle {
8
+
id: root
9
+
10
+
property string title: ""
11
+
property string subtitle: ""
12
+
property bool expanded: false
13
+
property alias content: contentLoader.sourceComponent
14
+
property int contentHeight: 150
15
+
16
+
Layout.fillWidth: true
17
+
Layout.preferredHeight: expanded ? contentLoader.item?.height + 48 : 0
18
+
visible: expanded
19
+
radius: 8
20
+
color: Settings.colors.backgroundLighter
21
+
clip: true
22
+
23
+
Behavior on Layout.preferredHeight {
24
+
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
25
+
}
26
+
27
+
ColumnLayout {
28
+
anchors.fill: parent
29
+
anchors.margins: 12
30
+
spacing: 8
31
+
32
+
RowLayout {
33
+
Layout.fillWidth: true
34
+
35
+
Text {
36
+
text: root.title
37
+
color: Settings.colors.foreground
38
+
font {
39
+
pixelSize: 13
40
+
weight: Font.Medium
41
+
}
42
+
}
43
+
44
+
Item { Layout.fillWidth: true }
45
+
46
+
Text {
47
+
text: root.subtitle
48
+
color: Settings.colors.accent
49
+
font.pixelSize: 11
50
+
visible: root.subtitle !== ""
51
+
}
52
+
}
53
+
54
+
Loader {
55
+
id: contentLoader
56
+
Layout.fillWidth: true
57
+
Layout.preferredHeight: Math.min(item?.implicitHeight ?? 0, root.contentHeight)
58
+
}
59
+
}
60
+
}
+29
-15
home/isabel/gui/quickshell/components/IconButton.qml
+29
-15
home/isabel/gui/quickshell/components/IconButton.qml
···
2
import Quickshell
3
import Quickshell.Widgets
4
import "root:/data"
5
6
Item {
7
-
id: iconButton
8
-
width: 16
9
-
height: 16
10
11
-
property string icon: ""
12
-
signal clicked
13
14
-
MouseArea {
15
-
anchors.fill: parent
16
-
onClicked: iconButton.clicked()
17
-
}
18
19
-
Text {
20
-
anchors.fill: parent
21
-
text: iconButton.icon
22
-
color: Settings.colors.foreground
23
-
font.pixelSize: 18
24
-
}
25
}
···
2
import Quickshell
3
import Quickshell.Widgets
4
import "root:/data"
5
+
import "root:/components"
6
7
Item {
8
+
id: root
9
10
+
property string icon: ""
11
+
property int size: 18
12
+
property bool invert: false
13
14
+
signal clicked
15
16
+
implicitWidth: size
17
+
implicitHeight: size
18
+
19
+
MyIcon {
20
+
anchors.centerIn: parent
21
+
icon: root.icon
22
+
size: root.size
23
+
invert: root.invert
24
+
}
25
+
26
+
MouseArea {
27
+
id: mouseArea
28
+
anchors.fill: parent
29
+
hoverEnabled: true
30
+
cursorShape: Qt.PointingHandCursor
31
+
onClicked: root.clicked()
32
+
}
33
+
34
+
scale: mouseArea.pressed ? 0.9 : 1.0
35
+
36
+
Behavior on scale {
37
+
NumberAnimation { duration: 100 }
38
+
}
39
}
+16
-17
home/isabel/gui/quickshell/components/Launcher.qml
+16
-17
home/isabel/gui/quickshell/components/Launcher.qml
···
2
import QtQuick.Layouts
3
import Quickshell
4
import Quickshell.Io
5
-
import Quickshell.Widgets
6
7
-
IconImage {
8
-
id: launcher
9
-
source: Quickshell.iconPath("nix-snowflake")
10
11
-
Layout.alignment: Qt.AlignCenter
12
13
-
width: 16
14
-
height: 16
15
16
-
Process {
17
-
id: launcherProcess
18
-
command: ["vicinae", "toggle"]
19
-
}
20
-
21
-
MouseArea {
22
-
anchors.fill: parent
23
-
hoverEnabled: true
24
-
onClicked: launcherProcess.running = true;
25
-
}
26
}
···
2
import QtQuick.Layouts
3
import Quickshell
4
import Quickshell.Io
5
+
import "root:/data"
6
7
+
Item {
8
+
id: root
9
10
+
Layout.alignment: Qt.AlignCenter
11
+
implicitWidth: 24
12
+
implicitHeight: 24
13
14
+
IconButton {
15
+
anchors.centerIn: parent
16
+
icon: "nix-snowflake"
17
+
size: 20
18
+
onClicked: launcherProcess.running = true
19
+
}
20
21
+
Process {
22
+
id: launcherProcess
23
+
command: ["vicinae", "toggle"]
24
+
}
25
}
+186
home/isabel/gui/quickshell/components/MediaPlayers.qml
+186
home/isabel/gui/quickshell/components/MediaPlayers.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import Quickshell.Widgets
4
+
import Quickshell.Services.Mpris
5
+
import "root:/data"
6
+
import "root:/services"
7
+
8
+
ColumnLayout {
9
+
id: root
10
+
11
+
Layout.fillWidth: true
12
+
spacing: 8
13
+
visible: Media.players.length > 0
14
+
15
+
RowLayout {
16
+
Layout.fillWidth: true
17
+
spacing: 8
18
+
19
+
Text {
20
+
text: "Now Playing"
21
+
color: Settings.colors.foreground
22
+
font {
23
+
pixelSize: 14
24
+
weight: Font.Bold
25
+
}
26
+
}
27
+
28
+
Item { Layout.fillWidth: true }
29
+
30
+
// Player selector (if multiple players)
31
+
Row {
32
+
spacing: 4
33
+
visible: Media.players.length > 1
34
+
35
+
Repeater {
36
+
model: Media.players
37
+
38
+
Rectangle {
39
+
required property MprisPlayer modelData
40
+
required property int index
41
+
width: 8
42
+
height: 8
43
+
radius: 4
44
+
color: Media.selectedPlayer === modelData ? Settings.colors.accent : Settings.colors.backgroundLightest
45
+
46
+
MouseArea {
47
+
anchors.fill: parent
48
+
cursorShape: Qt.PointingHandCursor
49
+
onClicked: Media.selectedPlayer = modelData
50
+
}
51
+
}
52
+
}
53
+
}
54
+
}
55
+
56
+
// Current Player
57
+
ListView {
58
+
id: mediaPlayerList
59
+
Layout.fillWidth: true
60
+
Layout.preferredHeight: contentHeight
61
+
model: Media.players
62
+
spacing: 8
63
+
clip: true
64
+
interactive: false
65
+
66
+
delegate: Rectangle {
67
+
required property MprisPlayer modelData
68
+
required property int index
69
+
width: ListView.view.width
70
+
height: 90
71
+
radius: 8
72
+
color: Settings.colors.backgroundLighter
73
+
opacity: Media.selectedPlayer === modelData ? 1.0 : 0.6
74
+
75
+
MouseArea {
76
+
anchors.fill: parent
77
+
onClicked: Media.selectedPlayer = modelData
78
+
}
79
+
80
+
RowLayout {
81
+
anchors {
82
+
fill: parent
83
+
margins: 10
84
+
}
85
+
spacing: 10
86
+
87
+
// Album Art
88
+
ClippingWrapperRectangle {
89
+
radius: 6
90
+
Layout.preferredWidth: 70
91
+
Layout.preferredHeight: 70
92
+
93
+
Image {
94
+
anchors.fill: parent
95
+
source: modelData.trackArtUrl
96
+
fillMode: Image.PreserveAspectCrop
97
+
}
98
+
}
99
+
100
+
// Track Info & Controls
101
+
ColumnLayout {
102
+
Layout.fillWidth: true
103
+
Layout.fillHeight: true
104
+
spacing: 2
105
+
106
+
RowLayout {
107
+
Layout.fillWidth: true
108
+
spacing: 4
109
+
110
+
Text {
111
+
text: modelData.trackTitle ?? "Unknown"
112
+
color: Settings.colors.foreground
113
+
font {
114
+
pixelSize: 13
115
+
weight: Font.Medium
116
+
}
117
+
elide: Text.ElideRight
118
+
Layout.fillWidth: true
119
+
}
120
+
121
+
// Player app name badge
122
+
Rectangle {
123
+
visible: Media.players.length > 1
124
+
Layout.preferredHeight: 16
125
+
Layout.preferredWidth: appNameText.width + 8
126
+
radius: 4
127
+
color: Settings.colors.backgroundLightest
128
+
129
+
Text {
130
+
id: appNameText
131
+
anchors.centerIn: parent
132
+
text: modelData.identity ?? ""
133
+
color: Settings.colors.foreground
134
+
opacity: 0.7
135
+
font.pixelSize: 9
136
+
}
137
+
}
138
+
}
139
+
140
+
Text {
141
+
text: modelData.trackArtist ?? ""
142
+
color: Settings.colors.foreground
143
+
opacity: 0.7
144
+
font.pixelSize: 11
145
+
elide: Text.ElideRight
146
+
Layout.fillWidth: true
147
+
}
148
+
149
+
Item { Layout.fillHeight: true }
150
+
151
+
// Playback Controls
152
+
RowLayout {
153
+
spacing: 14
154
+
Layout.alignment: Qt.AlignLeft
155
+
156
+
IconButton {
157
+
icon: "media-skip-backward-symbolic"
158
+
size: 14
159
+
onClicked: modelData.previous()
160
+
}
161
+
162
+
IconButton {
163
+
icon: modelData.playbackState === MprisPlaybackState.Playing
164
+
? "media-playback-pause-symbolic"
165
+
: "media-playback-start-symbolic"
166
+
size: 18
167
+
onClicked: {
168
+
if (modelData.playbackState === MprisPlaybackState.Playing) {
169
+
modelData.pause();
170
+
} else {
171
+
modelData.play();
172
+
}
173
+
}
174
+
}
175
+
176
+
IconButton {
177
+
icon: "media-skip-forward-symbolic"
178
+
size: 14
179
+
onClicked: modelData.next()
180
+
}
181
+
}
182
+
}
183
+
}
184
+
}
185
+
}
186
+
}
+28
home/isabel/gui/quickshell/components/MyIcon.qml
+28
home/isabel/gui/quickshell/components/MyIcon.qml
···
···
1
+
import org.kde.kirigami
2
+
import QtQuick
3
+
import QtQuick.Effects
4
+
import Quickshell
5
+
import Quickshell.Widgets
6
+
import "root:/data"
7
+
8
+
Item {
9
+
id: root
10
+
11
+
property string icon: ""
12
+
property int size: 18
13
+
property bool invert: false
14
+
15
+
implicitWidth: size
16
+
implicitHeight: size
17
+
18
+
Icon {
19
+
id: iconSource
20
+
anchors.fill: parent
21
+
source: Quickshell.iconPath(root.icon)
22
+
23
+
isMask: true
24
+
// FIXME:
25
+
// color: Settings.colors.foreground
26
+
color: invert ? "#1e1e2e" : "#cdd6f4"
27
+
}
28
+
}
+15
-10
home/isabel/gui/quickshell/components/Network.qml
+15
-10
home/isabel/gui/quickshell/components/Network.qml
···
1
-
//@ pragma IconTheme Cosmic
2
-
3
import QtQuick.Layouts
4
-
import Quickshell
5
-
import Quickshell.Widgets
6
import "root:/services"
7
8
-
IconImage {
9
-
id: networkIcon
10
-
source: Quickshell.iconPath(Networking.active.icon)
11
12
-
width: 16
13
-
height: 16
14
-
Layout.alignment: Qt.AlignCenter
15
}
···
1
+
import QtQuick
2
import QtQuick.Layouts
3
+
import QtQuick.Controls.Basic
4
+
import "root:/data"
5
+
import "root:/components"
6
import "root:/services"
7
8
+
Item {
9
+
id: root
10
+
11
+
Layout.alignment: Qt.AlignCenter
12
+
implicitWidth: 20
13
+
implicitHeight: 20
14
15
+
MyIcon {
16
+
anchors.centerIn: parent
17
+
icon: Networking.icon
18
+
size: 18
19
+
}
20
}
+11
-360
home/isabel/gui/quickshell/components/Noti.qml
+11
-360
home/isabel/gui/quickshell/components/Noti.qml
···
1
import QtQuick
2
import QtQuick.Layouts
3
import QtQuick.Controls
4
-
import QtQuick.Controls.Basic
5
import Quickshell
6
import Quickshell.Widgets
7
-
import Quickshell.Services.Notifications
8
-
import Quickshell.Services.Mpris
9
import "root:/data"
10
import "root:/services"
11
-
import "root:/components"
12
13
Item {
14
-
id: noti
15
-
16
-
property bool showIndicator: Notifications.list.length > 0 || Media.players.length > 0
17
18
-
Layout.alignment: Qt.AlignCenter
19
20
-
Text {
21
-
text: "๏ถ"
22
-
color: Settings.colors.foreground
23
-
font.pixelSize: 16
24
25
-
anchors.horizontalCenter: parent.horizontalCenter
26
27
-
visible: noti.showIndicator
28
-
29
-
MouseArea {
30
-
anchors.fill: parent
31
-
onClicked: notificationLoader.item.visible = !notificationLoader.item.visible
32
}
33
-
}
34
-
35
-
LazyLoader {
36
-
id: notificationLoader
37
-
38
-
loading: true
39
-
40
-
PopupWindow {
41
-
id: popup
42
-
anchor.window: noti.QsWindow.window
43
-
anchor.rect.x: parentWindow.width * 1.2
44
-
45
-
visible: false
46
-
47
-
color: "transparent"
48
-
49
-
implicitWidth: 400
50
-
implicitHeight: noti.QsWindow.window.height
51
-
52
-
Rectangle {
53
-
anchors.fill: parent
54
-
radius: 10
55
-
color: Settings.colors.background
56
-
57
-
ColumnLayout {
58
-
spacing: 10
59
-
60
-
anchors {
61
-
fill: parent
62
-
topMargin: 15
63
-
bottomMargin: 15
64
-
leftMargin: 15
65
-
rightMargin: 15
66
-
}
67
-
68
-
Rectangle {
69
-
width: parent.width
70
-
height: 30
71
-
color: Settings.colors.background
72
-
radius: 5
73
-
74
-
Text {
75
-
text: "Notifications"
76
-
color: Settings.colors.foreground
77
-
font.pixelSize: 20
78
-
font.bold: true
79
-
80
-
anchors.centerIn: parent
81
-
}
82
-
}
83
-
84
-
Rectangle {
85
-
width: parent.width
86
-
height: 120
87
-
radius: 5
88
-
color: Settings.colors.backgroundLighter
89
-
90
-
ColumnLayout {
91
-
id: mprisRoot
92
-
spacing: 7
93
-
94
-
width: parent.width
95
-
height: parent.height
96
-
97
-
anchors {
98
-
fill: parent
99
-
leftMargin: 10
100
-
rightMargin: 20
101
-
topMargin: 10
102
-
bottomMargin: 10
103
-
}
104
-
105
-
Row {
106
-
spacing: 10
107
-
108
-
Layout.preferredWidth: parent.width
109
-
Layout.preferredHeight: 80
110
-
111
-
ClippingWrapperRectangle {
112
-
radius: 5
113
-
width: parent.height
114
-
height: parent.height
115
-
116
-
Image {
117
-
source: Media.selectedPlayer.trackArtUrl
118
-
fillMode: Image.PreserveAspectFit
119
-
width: parent.width
120
-
height: parent.height
121
-
}
122
-
}
123
-
124
-
ColumnLayout {
125
-
spacing: 5
126
-
127
-
height: 80
128
-
width: parent.width
129
-
130
-
Text {
131
-
text: Media.selectedPlayer.trackTitle
132
-
color: Settings.colors.foreground
133
-
font.pixelSize: 20
134
-
}
135
-
136
-
Text {
137
-
text: Media.selectedPlayer.trackArtist
138
-
color: Settings.colors.foreground
139
-
font.pixelSize: 14
140
-
}
141
-
142
-
Row {
143
-
spacing: (parent.width / 3) - 10
144
-
145
-
IconButton {
146
-
// icon: "media-skip-backward-symbolic"
147
-
icon: "๓ฐซ"
148
-
onClicked: Media.selectedPlayer.previous()
149
-
}
150
-
151
-
IconButton {
152
-
// icon: "media-playback-start-symbolic"
153
-
icon: "๓ฐ"
154
-
onClicked: Media.selectedPlayer.play()
155
-
visible: Media.selectedPlayer.playbackState == MprisPlaybackState.Paused
156
-
}
157
-
158
-
IconButton {
159
-
// icon: "media-playback-pause-symbolic"
160
-
icon: "๓ฐค"
161
-
onClicked: Media.selectedPlayer.pause()
162
-
visible: Media.selectedPlayer.playbackState == MprisPlaybackState.Playing
163
-
}
164
-
165
-
IconButton {
166
-
// icon: "media-skip-forward-symbolic"
167
-
icon: "๓ฐฌ"
168
-
onClicked: Media.selectedPlayer.next()
169
-
}
170
-
}
171
-
}
172
-
}
173
-
174
-
RowLayout {
175
-
id: progressBar
176
-
spacing: 10
177
-
178
-
Layout.preferredWidth: parent.width
179
-
Layout.preferredHeight: parent.height
180
-
181
-
Text {
182
-
id: positionText
183
-
text: Math.round(Media.selectedPlayer.position) + "s"
184
-
color: Settings.colors.foreground
185
-
}
186
-
187
-
Slider {
188
-
id: progress
189
-
from: 0
190
-
to: Media.selectedPlayer.length
191
-
value: Media.selectedPlayer.position
192
-
193
-
onMoved: Media.selectedPlayer.position = value
194
-
195
-
implicitHeight: parent.height
196
-
implicitWidth: parent.width - positionText.width - lengthText.width - 10
197
-
198
-
background: Rectangle {
199
-
id: sliderBackground
200
-
color: Settings.colors.backgroundDarker
201
-
x: progress.leftPadding
202
-
y: progress.topPadding + progress.availableHeight / 2 - height / 2
203
-
implicitWidth: parent.width
204
-
implicitHeight: 4
205
-
width: progress.availableWidth
206
-
height: implicitHeight
207
-
radius: 2
208
-
209
-
Rectangle {
210
-
implicitWidth: progress.visualPosition * parent.width
211
-
implicitHeight: parent.height
212
-
color: Settings.colors.accent
213
-
radius: 2
214
-
}
215
-
}
216
-
217
-
handle: Rectangle {
218
-
x: progress.leftPadding + progress.visualPosition * progress.availableWidth
219
-
y: progress.topPadding + progress.availableHeight / 2 - height / 2
220
-
implicitWidth: 20
221
-
implicitHeight: 20
222
-
radius: 13
223
-
color: Settings.colors.backgroundLightest
224
-
border.color: Settings.colors.border
225
-
}
226
-
}
227
-
228
-
Text {
229
-
id: lengthText
230
-
text: Math.round(Media.selectedPlayer.length) + "s"
231
-
color: Settings.colors.foreground
232
-
}
233
-
234
-
FrameAnimation {
235
-
running: Media.selectedPlayer.playbackState == MprisPlaybackState.Playing
236
-
onTriggered: {
237
-
progress.value = Media.selectedPlayer.position;
238
-
positionText.text = Math.round(Media.selectedPlayer.position) + "s";
239
-
}
240
-
}
241
-
}
242
-
}
243
-
}
244
-
245
-
ListView {
246
-
id: notiList
247
-
model: Notifications.list
248
-
249
-
Layout.alignment: Qt.AlignCenter
250
-
Layout.preferredWidth: parent.width
251
-
Layout.preferredHeight: parent.height
252
-
253
-
ScrollBar.vertical: ScrollBar {}
254
-
255
-
spacing: 15
256
-
257
-
delegate: Item {
258
-
required property Notification modelData
259
-
260
-
width: parent.width
261
-
height: 80
262
-
263
-
Rectangle {
264
-
anchors.fill: parent
265
-
color: Settings.colors.backgroundLighter
266
-
radius: 5
267
-
268
-
IconImage {
269
-
anchors {
270
-
left: parent.left
271
-
leftMargin: 10
272
-
verticalCenter: parent.verticalCenter
273
-
}
274
-
width: 48
275
-
height: 48
276
-
source: Quickshell.iconPath(modelData.appIcon)
277
-
}
278
-
279
-
ColumnLayout {
280
-
anchors {
281
-
left: parent.left
282
-
leftMargin: 75
283
-
top: parent.top
284
-
topMargin: 10
285
-
}
286
-
287
-
spacing: 5
288
-
289
-
Text {
290
-
text: modelData.appName
291
-
color: Settings.colors.foreground
292
-
font {
293
-
pixelSize: 18
294
-
bold: true
295
-
}
296
-
}
297
-
298
-
Text {
299
-
text: modelData.body
300
-
color: Settings.colors.foreground
301
-
font.pixelSize: 13
302
-
}
303
-
}
304
-
305
-
Text {
306
-
text: "x"
307
-
color: Settings.colors.error
308
-
font.pixelSize: 16
309
-
310
-
anchors {
311
-
top: parent.top
312
-
topMargin: 5
313
-
right: parent.right
314
-
rightMargin: 10
315
-
}
316
-
317
-
MouseArea {
318
-
anchors.fill: parent
319
-
onClicked: {
320
-
modelData.dismiss();
321
-
if (Notifications.list.length <= 0) {
322
-
popup.visible = false;
323
-
}
324
-
}
325
-
}
326
-
}
327
-
328
-
Repeater {
329
-
model: modelData.actions
330
-
331
-
Item {
332
-
required property NotificationAction actionData
333
-
334
-
width: 100
335
-
height: 30
336
-
337
-
anchors {
338
-
left: parent.left
339
-
leftMargin: 5
340
-
top: parent.top
341
-
topMargin: 5
342
-
}
343
-
344
-
Rectangle {
345
-
anchors.fill: parent
346
-
color: Settings.colors.backgroundDarker
347
-
radius: 5
348
-
349
-
Text {
350
-
text: actionData.text
351
-
color: Settings.colors.foreground
352
-
font.pixelSize: 12
353
-
354
-
anchors {
355
-
left: parent.left
356
-
leftMargin: 10
357
-
verticalCenter: parent.verticalCenter
358
-
}
359
-
}
360
-
361
-
MouseArea {
362
-
anchors.fill: parent
363
-
onClicked: actionData.invoke()
364
-
}
365
-
}
366
-
}
367
-
}
368
-
}
369
-
}
370
-
}
371
-
}
372
-
}
373
-
}
374
-
}
375
}
···
1
import QtQuick
2
import QtQuick.Layouts
3
import QtQuick.Controls
4
import Quickshell
5
import Quickshell.Widgets
6
+
import Quickshell.Services.Notifications as QsNotifications
7
import "root:/data"
8
import "root:/services"
9
10
Item {
11
+
id: root
12
13
+
property bool hasNotifications: Notifications.list.length > 0 || Notifications.dndEnabled
14
15
+
visible: hasNotifications
16
17
+
Layout.alignment: Qt.AlignCenter
18
+
implicitWidth: 24
19
+
implicitHeight: 24
20
21
+
MyIcon {
22
+
anchors.centerIn: parent
23
+
icon: Notifications.dndEnabled ? "notifications-disabled-symbolic" : "preferences-system-notifications-symbolic"
24
+
size: 18
25
}
26
}
+57
home/isabel/gui/quickshell/components/PanelListItem.qml
+57
home/isabel/gui/quickshell/components/PanelListItem.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import "root:/data"
4
+
5
+
Rectangle {
6
+
id: root
7
+
8
+
property string icon: ""
9
+
property string text: ""
10
+
property string badge: ""
11
+
property bool active: false
12
+
property bool showBadge: badge !== ""
13
+
14
+
signal clicked()
15
+
16
+
width: parent?.width ?? 100
17
+
height: 36
18
+
radius: 6
19
+
color: active ? Settings.colors.accent : "transparent"
20
+
21
+
MouseArea {
22
+
id: mouseArea
23
+
anchors.fill: parent
24
+
cursorShape: Qt.PointingHandCursor
25
+
hoverEnabled: true
26
+
onEntered: if (!root.active) parent.color = Settings.colors.backgroundLightest
27
+
onExited: parent.color = root.active ? Settings.colors.accent : "transparent"
28
+
onClicked: root.clicked()
29
+
}
30
+
31
+
RowLayout {
32
+
anchors.fill: parent
33
+
anchors.margins: 8
34
+
spacing: 8
35
+
36
+
MyIcon {
37
+
icon: root.icon
38
+
size: 16
39
+
}
40
+
41
+
Text {
42
+
text: root.text
43
+
color: root.active ? Settings.colors.background : Settings.colors.foreground
44
+
font.pixelSize: 12
45
+
elide: Text.ElideRight
46
+
Layout.fillWidth: true
47
+
}
48
+
49
+
Text {
50
+
visible: root.showBadge
51
+
text: root.badge
52
+
color: root.active ? Settings.colors.background : Settings.colors.foreground
53
+
opacity: 0.7
54
+
font.pixelSize: 11
55
+
}
56
+
}
57
+
}
+311
home/isabel/gui/quickshell/components/QuickSettings.qml
+311
home/isabel/gui/quickshell/components/QuickSettings.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import QtQuick.Controls.Basic
4
+
import Quickshell.Io
5
+
import "root:/data"
6
+
import "root:/services"
7
+
8
+
ColumnLayout {
9
+
id: root
10
+
11
+
Layout.fillWidth: true
12
+
spacing: 12
13
+
14
+
Text {
15
+
text: "Quick Settings"
16
+
color: Settings.colors.foreground
17
+
font {
18
+
pixelSize: 14
19
+
weight: Font.Bold
20
+
}
21
+
}
22
+
23
+
// Network Panel (expandable)
24
+
Rectangle {
25
+
id: networkPanel
26
+
Layout.fillWidth: true
27
+
Layout.preferredHeight: visible ? Math.min(networkColumn.implicitHeight + 20, 350) : 0
28
+
visible: false
29
+
radius: 8
30
+
color: Settings.colors.backgroundLighter
31
+
clip: true
32
+
33
+
Behavior on Layout.preferredHeight {
34
+
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
35
+
}
36
+
37
+
ColumnLayout {
38
+
id: networkColumn
39
+
anchors.fill: parent
40
+
anchors.margins: 12
41
+
spacing: 8
42
+
43
+
RowLayout {
44
+
Layout.fillWidth: true
45
+
46
+
Text {
47
+
text: "Networks"
48
+
color: Settings.colors.foreground
49
+
font {
50
+
pixelSize: 13
51
+
weight: Font.Medium
52
+
}
53
+
}
54
+
55
+
Item { Layout.fillWidth: true }
56
+
57
+
IconButton {
58
+
icon: "view-refresh-symbolic"
59
+
size: 14
60
+
onClicked: Networking.reload()
61
+
}
62
+
}
63
+
64
+
ScrollView {
65
+
Layout.fillWidth: true
66
+
Layout.preferredHeight: Math.min(networkListView.contentHeight, 280)
67
+
clip: true
68
+
69
+
ListView {
70
+
id: networkListView
71
+
width: parent.width
72
+
model: Networking.networks
73
+
spacing: 4
74
+
75
+
delegate: PanelListItem {
76
+
required property var modelData
77
+
width: ListView.view.width
78
+
icon: modelData.icon
79
+
text: modelData.ssid || "Unknown"
80
+
badge: modelData.strength + "%"
81
+
active: modelData.active
82
+
onClicked: {
83
+
if (!modelData.active) {
84
+
Networking.connectToNetwork(modelData.ssid);
85
+
}
86
+
}
87
+
}
88
+
}
89
+
}
90
+
91
+
Text {
92
+
text: Networking.networks?.count === 0 ? "No networks found" : ""
93
+
color: Settings.colors.foreground
94
+
opacity: 0.5
95
+
font.pixelSize: 12
96
+
visible: Networking.networks?.count === 0
97
+
Layout.alignment: Qt.AlignHCenter
98
+
}
99
+
100
+
Item { Layout.fillHeight: true }
101
+
}
102
+
}
103
+
104
+
// Bluetooth Panel (expandable)
105
+
Rectangle {
106
+
id: bluetoothPanel
107
+
Layout.fillWidth: true
108
+
Layout.preferredHeight: visible ? Math.min(bluetoothColumn.implicitHeight + 20, 350) : 0
109
+
visible: false
110
+
radius: 8
111
+
color: Settings.colors.backgroundLighter
112
+
clip: true
113
+
114
+
Behavior on Layout.preferredHeight {
115
+
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
116
+
}
117
+
118
+
ColumnLayout {
119
+
id: bluetoothColumn
120
+
anchors.fill: parent
121
+
anchors.margins: 12
122
+
spacing: 8
123
+
124
+
RowLayout {
125
+
Layout.fillWidth: true
126
+
127
+
Text {
128
+
text: "Bluetooth Devices"
129
+
color: Settings.colors.foreground
130
+
font {
131
+
pixelSize: 13
132
+
weight: Font.Medium
133
+
}
134
+
}
135
+
136
+
Item { Layout.fillWidth: true }
137
+
138
+
// Scan button
139
+
IconButton {
140
+
icon: Bluetooth.adapter?.discovering ? "process-stop-symbolic" : "view-refresh-symbolic"
141
+
size: 14
142
+
onClicked: {
143
+
if (Bluetooth.adapter?.discovering) {
144
+
Bluetooth.stopDiscovery();
145
+
} else {
146
+
Bluetooth.startDiscovery();
147
+
}
148
+
}
149
+
}
150
+
151
+
Text {
152
+
text: Bluetooth.connected ? Bluetooth.connectedDevice?.name ?? "" : ""
153
+
color: Settings.colors.accent
154
+
font.pixelSize: 11
155
+
visible: Bluetooth.connected
156
+
}
157
+
}
158
+
159
+
ScrollView {
160
+
Layout.fillWidth: true
161
+
Layout.preferredHeight: Math.min(bluetoothListView.contentHeight, 280)
162
+
clip: true
163
+
164
+
ListView {
165
+
id: bluetoothListView
166
+
width: parent.width
167
+
model: Bluetooth.devices
168
+
spacing: 8
169
+
clip: true
170
+
171
+
delegate: PanelListItem {
172
+
required property var modelData
173
+
width: ListView.view.width
174
+
icon: modelData.icon || "bluetooth-symbolic"
175
+
text: modelData.name
176
+
badge: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "")
177
+
active: modelData.connected
178
+
onClicked: {
179
+
if (modelData.connected) {
180
+
modelData.disconnect();
181
+
} else if (modelData.paired) {
182
+
modelData.connect();
183
+
} else {
184
+
// Pair and connect
185
+
modelData.pair();
186
+
}
187
+
}
188
+
}
189
+
}
190
+
}
191
+
192
+
Text {
193
+
text: {
194
+
if (!Bluetooth.adapter) return "No Bluetooth adapter";
195
+
if (Bluetooth.devices?.count === 0) return "No devices found";
196
+
return "";
197
+
}
198
+
color: Settings.colors.foreground
199
+
opacity: 0.5
200
+
font.pixelSize: 12
201
+
visible: !Bluetooth.adapter || Bluetooth.devices?.count === 0
202
+
Layout.alignment: Qt.AlignHCenter
203
+
}
204
+
205
+
Item { Layout.fillHeight: true }
206
+
}
207
+
}
208
+
209
+
// Toggle Buttons Row
210
+
RowLayout {
211
+
Layout.fillWidth: true
212
+
spacing: 8
213
+
214
+
// Netoworking Toggle
215
+
QuickSettingButton {
216
+
icon: Networking.icon
217
+
label: "Networking"
218
+
active: Networking.connected
219
+
onClicked: {
220
+
// Do nothing if ethernet is connected
221
+
if (Networking.ethernetConnected) return;
222
+
223
+
// otherwise lets actually do wifi stuff
224
+
Networking.toggleWifi()
225
+
}
226
+
onPressAndHold: {
227
+
if (Networking.ethernetConnected) return;
228
+
229
+
networkPanel.visible = !networkPanel.visible
230
+
}
231
+
}
232
+
233
+
// Bluetooth Toggle
234
+
QuickSettingButton {
235
+
icon: Bluetooth.icon
236
+
label: "Bluetooth"
237
+
active: Bluetooth.powered
238
+
onClicked: Bluetooth.toggle()
239
+
onPressAndHold: bluetoothPanel.visible = !bluetoothPanel.visible
240
+
}
241
+
242
+
// Do Not Disturb Toggle
243
+
QuickSettingButton {
244
+
icon: Notifications.dndEnabled ? "notifications-disabled-symbolic" : "preferences-system-notifications-symbolic"
245
+
label: "DND"
246
+
active: Notifications.dndEnabled
247
+
onClicked: Notifications.dndEnabled = !Notifications.dndEnabled
248
+
}
249
+
}
250
+
251
+
// Volume Slider
252
+
StyledSlider {
253
+
icon: Pipewire.sinkIcon
254
+
value: Pipewire.sinkVolume
255
+
iconClickable: true
256
+
onIconClicked: Pipewire.toggleSinkMute()
257
+
onMoved: (val) => Pipewire.setSinkVolume(val)
258
+
}
259
+
260
+
// Brightness Slider
261
+
StyledSlider {
262
+
visible: Brightness.available
263
+
icon: "display-brightness-symbolic"
264
+
value: Brightness.brightness
265
+
from: 0.05
266
+
accentColor: Settings.colors.warning
267
+
onMoved: (val) => Brightness.setBrightness(val)
268
+
}
269
+
270
+
// Quick Setting Button Component
271
+
component QuickSettingButton: Rectangle {
272
+
id: qsButton
273
+
Layout.fillWidth: true
274
+
Layout.preferredHeight: 64
275
+
radius: 8
276
+
color: active ? Settings.colors.accent : Settings.colors.backgroundLighter
277
+
278
+
property string icon: ""
279
+
property string label: ""
280
+
property bool active: false
281
+
282
+
signal clicked()
283
+
signal pressAndHold()
284
+
285
+
ColumnLayout {
286
+
anchors.centerIn: parent
287
+
spacing: 6
288
+
289
+
MyIcon {
290
+
icon: qsButton.icon
291
+
size: 20
292
+
Layout.alignment: Qt.AlignHCenter
293
+
invert: active
294
+
}
295
+
296
+
Text {
297
+
text: qsButton.label
298
+
color: qsButton.active ? Settings.colors.background : Settings.colors.foreground
299
+
font.pixelSize: 11
300
+
Layout.alignment: Qt.AlignHCenter
301
+
}
302
+
}
303
+
304
+
MouseArea {
305
+
anchors.fill: parent
306
+
cursorShape: Qt.PointingHandCursor
307
+
onClicked: qsButton.clicked()
308
+
onPressAndHold: qsButton.pressAndHold()
309
+
}
310
+
}
311
+
}
+73
home/isabel/gui/quickshell/components/StyledSlider.qml
+73
home/isabel/gui/quickshell/components/StyledSlider.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import QtQuick.Controls.Basic
4
+
import "root:/data"
5
+
import "root:/components"
6
+
7
+
RowLayout {
8
+
id: root
9
+
10
+
property string icon: ""
11
+
property real value: 0
12
+
property real from: 0
13
+
property real to: 1
14
+
property color accentColor: Settings.colors.accent
15
+
property bool showPercentage: true
16
+
property bool iconClickable: false
17
+
18
+
signal moved(real value)
19
+
signal iconClicked()
20
+
21
+
Layout.fillWidth: true
22
+
spacing: 12
23
+
24
+
IconButton {
25
+
icon: root.icon
26
+
size: 18
27
+
onClicked: root.iconClicked()
28
+
}
29
+
30
+
Slider {
31
+
id: slider
32
+
Layout.fillWidth: true
33
+
from: root.from
34
+
to: root.to
35
+
value: root.value
36
+
37
+
onMoved: root.moved(value)
38
+
39
+
background: Rectangle {
40
+
x: slider.leftPadding
41
+
y: slider.topPadding + slider.availableHeight / 2 - height / 2
42
+
width: slider.availableWidth
43
+
height: 4
44
+
radius: 2
45
+
color: Settings.colors.backgroundLightest
46
+
47
+
Rectangle {
48
+
width: slider.visualPosition * parent.width
49
+
height: parent.height
50
+
radius: 2
51
+
color: root.accentColor
52
+
}
53
+
}
54
+
55
+
handle: Rectangle {
56
+
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
57
+
y: slider.topPadding + slider.availableHeight / 2 - height / 2
58
+
width: 14
59
+
height: 14
60
+
radius: 7
61
+
color: slider.pressed ? root.accentColor : Settings.colors.foreground
62
+
}
63
+
}
64
+
65
+
Text {
66
+
visible: root.showPercentage
67
+
text: Math.round((slider.value - root.from) / (root.to - root.from) * 100) + "%"
68
+
color: Settings.colors.foreground
69
+
opacity: 0.7
70
+
font.pixelSize: 12
71
+
Layout.preferredWidth: 36
72
+
}
73
+
}
+180
-51
home/isabel/gui/quickshell/components/SysTray.qml
+180
-51
home/isabel/gui/quickshell/components/SysTray.qml
···
1
import QtQuick
2
import QtQuick.Layouts
3
-
import Quickshell
4
import QtQuick.Controls
5
import Quickshell.Widgets
6
import Quickshell.Services.SystemTray
7
import "root:/data"
8
9
ColumnLayout {
10
-
id: systray
11
12
Layout.alignment: Qt.AlignCenter
13
14
Repeater {
15
model: SystemTray.items
16
17
delegate: Item {
18
-
id: delagate
19
required property SystemTrayItem modelData
20
21
-
width: 24
22
-
height: 24
23
24
IconImage {
25
-
source: modelData.icon
26
-
width: 16
27
-
height: 16
28
anchors.centerIn: parent
29
}
30
31
MouseArea {
32
anchors.fill: parent
33
hoverEnabled: true
34
35
-
onClicked: popupLoader.item.visible = !popupLoader.item.visible
36
}
37
38
QsMenuOpener {
···
42
43
LazyLoader {
44
id: popupLoader
45
-
46
loading: true
47
48
PopupWindow {
49
id: popup
50
-
anchor.window: delagate.QsWindow.window
51
-
anchor.rect.x: parentWindow.width * 1.15
52
-
anchor.rect.y: parentWindow.height / 1.25
53
-
54
color: "transparent"
55
56
-
implicitWidth: 200
57
-
implicitHeight: 200
58
59
Rectangle {
60
anchors.fill: parent
61
color: Settings.colors.background
62
-
radius: 5
63
-
}
64
65
-
ListView {
66
-
model: menu.children
67
68
anchors {
69
-
top: parent.top
70
-
topMargin: 5
71
-
bottom: parent.bottom
72
-
bottomMargin: 5
73
}
74
75
-
width: parent.width
76
-
height: parent.height
77
-
spacing: 5
78
79
-
ScrollBar.vertical: ScrollBar {}
80
81
-
delegate: Item {
82
-
required property QsMenuHandle modelData
83
84
-
width: parent.width
85
-
height: 40
86
87
-
Rectangle {
88
-
anchors {
89
-
fill: parent
90
-
leftMargin: 5
91
-
rightMargin: 5
92
}
93
94
-
color: Settings.colors.backgroundLighter
95
-
radius: 5
96
97
-
Text {
98
-
anchors.centerIn: parent
99
-
text: modelData.text
100
-
color: Settings.colors.foreground
101
-
font.pointSize: 12
102
-
}
103
104
-
MouseArea {
105
-
anchors.fill: parent
106
-
hoverEnabled: true
107
108
-
onClicked: {
109
-
modelData.triggered();
110
-
popup.visible = false;
111
}
112
}
113
}
···
1
import QtQuick
2
import QtQuick.Layouts
3
import QtQuick.Controls
4
+
import Quickshell
5
import Quickshell.Widgets
6
import Quickshell.Services.SystemTray
7
import "root:/data"
8
9
ColumnLayout {
10
+
id: root
11
12
Layout.alignment: Qt.AlignCenter
13
+
spacing: 6
14
15
Repeater {
16
model: SystemTray.items
17
18
delegate: Item {
19
+
id: trayItem
20
required property SystemTrayItem modelData
21
22
+
Layout.alignment: Qt.AlignHCenter
23
+
implicitWidth: 24
24
+
implicitHeight: 24
25
26
IconImage {
27
+
source: trayItem.modelData.icon
28
+
implicitSize: 18
29
anchors.centerIn: parent
30
+
mipmap: true
31
}
32
33
MouseArea {
34
anchors.fill: parent
35
hoverEnabled: true
36
+
cursorShape: Qt.PointingHandCursor
37
+
acceptedButtons: Qt.LeftButton | Qt.RightButton
38
+
onClicked: (mouse) => {
39
+
if (mouse.button === Qt.RightButton || modelData.onlyMenu) {
40
+
popupLoader.item.visible = !popupLoader.item.visible;
41
+
} else {
42
+
modelData.activate();
43
+
}
44
+
}
45
+
}
46
47
+
ToolTip {
48
+
visible: trayMouseArea.containsMouse && modelData.tooltipTitle !== ""
49
+
text: modelData.tooltipTitle
50
+
delay: 500
51
+
}
52
+
53
+
MouseArea {
54
+
id: trayMouseArea
55
+
anchors.fill: parent
56
+
hoverEnabled: true
57
+
acceptedButtons: Qt.NoButton
58
}
59
60
QsMenuOpener {
···
64
65
LazyLoader {
66
id: popupLoader
67
loading: true
68
69
PopupWindow {
70
id: popup
71
+
anchor.window: trayItem.QsWindow.window
72
+
anchor.rect.x: (trayItem.QsWindow.window?.width ?? 0) * 1.15
73
+
anchor.rect.y: (trayItem.QsWindow.window?.height ?? 0) / 1.25
74
+
visible: false
75
color: "transparent"
76
77
+
implicitWidth: 220
78
+
implicitHeight: menuColumn.implicitHeight + 16
79
80
Rectangle {
81
anchors.fill: parent
82
color: Settings.colors.background
83
+
radius: 10
84
85
+
// Shadow effect
86
+
layer.enabled: true
87
+
layer.effect: null
88
+
}
89
90
+
ColumnLayout {
91
+
id: menuColumn
92
anchors {
93
+
fill: parent
94
+
margins: 8
95
}
96
+
spacing: 2
97
98
+
// Header with app name
99
+
Text {
100
+
text: modelData.title || modelData.id
101
+
color: Settings.colors.foreground
102
+
font {
103
+
pixelSize: 12
104
+
weight: Font.Bold
105
+
}
106
+
opacity: 0.7
107
+
Layout.fillWidth: true
108
+
Layout.leftMargin: 8
109
+
Layout.bottomMargin: 4
110
+
elide: Text.ElideRight
111
+
visible: text !== ""
112
+
}
113
114
+
Rectangle {
115
+
Layout.fillWidth: true
116
+
Layout.preferredHeight: 1
117
+
Layout.leftMargin: 4
118
+
Layout.rightMargin: 4
119
+
Layout.bottomMargin: 4
120
+
color: Settings.colors.foreground
121
+
opacity: 0.1
122
+
visible: (modelData.title || modelData.id) !== ""
123
+
}
124
+
125
+
Repeater {
126
+
model: menu.children
127
128
+
delegate: Loader {
129
+
id: menuItemLoader
130
+
required property var modelData
131
132
+
Layout.fillWidth: true
133
+
sourceComponent: modelData.isSeparator ? separatorComponent : menuItemComponent
134
+
135
+
Component {
136
+
id: separatorComponent
137
+
138
+
Rectangle {
139
+
height: 9
140
+
color: "transparent"
141
142
+
Rectangle {
143
+
anchors.centerIn: parent
144
+
width: parent.width - 16
145
+
height: 1
146
+
color: Settings.colors.foreground
147
+
opacity: 0.1
148
+
}
149
+
}
150
}
151
152
+
Component {
153
+
id: menuItemComponent
154
+
155
+
Rectangle {
156
+
id: menuItemRect
157
+
height: 32
158
+
radius: 6
159
+
color: menuMouse.containsMouse && menuItemLoader.modelData.enabled
160
+
? Settings.colors.backgroundLighter
161
+
: "transparent"
162
+
opacity: menuItemLoader.modelData.enabled ? 1.0 : 0.5
163
+
164
+
RowLayout {
165
+
anchors {
166
+
fill: parent
167
+
leftMargin: 10
168
+
rightMargin: 10
169
+
}
170
+
spacing: 8
171
+
172
+
// Checkbox/Radio indicator
173
+
Rectangle {
174
+
visible: menuItemLoader.modelData.buttonType !== 0 // QsMenuButtonType.None
175
+
Layout.preferredWidth: 16
176
+
Layout.preferredHeight: 16
177
+
radius: menuItemLoader.modelData.buttonType === 2 ? 8 : 3 // RadioButton = 2
178
+
color: "transparent"
179
+
border.width: 1.5
180
+
border.color: menuItemLoader.modelData.checkState !== Qt.Unchecked
181
+
? Settings.colors.accent
182
+
: Settings.colors.foreground
183
+
opacity: menuItemLoader.modelData.checkState !== Qt.Unchecked ? 1.0 : 0.5
184
+
185
+
Rectangle {
186
+
anchors.centerIn: parent
187
+
width: 8
188
+
height: 8
189
+
radius: menuItemLoader.modelData.buttonType === 2 ? 4 : 2
190
+
color: Settings.colors.accent
191
+
visible: menuItemLoader.modelData.checkState !== Qt.Unchecked
192
+
}
193
+
}
194
+
195
+
// Icon
196
+
Image {
197
+
visible: menuItemLoader.modelData.icon !== "" && menuItemLoader.modelData.buttonType === 0
198
+
source: menuItemLoader.modelData.icon
199
+
Layout.preferredWidth: 16
200
+
Layout.preferredHeight: 16
201
+
sourceSize.width: 16
202
+
sourceSize.height: 16
203
+
smooth: true
204
+
}
205
+
206
+
// Text
207
+
Text {
208
+
text: menuItemLoader.modelData.text
209
+
color: Settings.colors.foreground
210
+
font.pixelSize: 13
211
+
elide: Text.ElideRight
212
+
Layout.fillWidth: true
213
+
}
214
215
+
// Submenu indicator
216
+
MyIcon {
217
+
visible: menuItemLoader.modelData.hasChildren
218
+
icon: "go-next-symbolic"
219
+
size: 12
220
+
opacity: 0.6
221
+
}
222
+
}
223
224
+
MouseArea {
225
+
id: menuMouse
226
+
anchors.fill: parent
227
+
hoverEnabled: true
228
+
cursorShape: menuItemLoader.modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
229
+
onClicked: {
230
+
if (!menuItemLoader.modelData.enabled) return;
231
232
+
if (menuItemLoader.modelData.hasChildren) {
233
+
// TODO: Handle submenu
234
+
} else {
235
+
menuItemLoader.modelData.triggered();
236
+
popup.visible = false;
237
+
}
238
+
}
239
+
}
240
}
241
}
242
}
+14
-8
home/isabel/gui/quickshell/components/Volume.qml
+14
-8
home/isabel/gui/quickshell/components/Volume.qml
···
1
+
import QtQuick
2
import QtQuick.Layouts
3
+
import "root:/data"
4
+
import "root:/services"
5
6
+
Item {
7
+
id: root
8
9
+
Layout.alignment: Qt.AlignCenter
10
+
implicitWidth: 20
11
+
implicitHeight: 20
12
+
13
+
MyIcon {
14
+
anchors.centerIn: parent
15
+
icon: Pipewire.sinkIcon
16
+
size: 18
17
+
}
18
}
+53
-28
home/isabel/gui/quickshell/components/Workspaces.qml
+53
-28
home/isabel/gui/quickshell/components/Workspaces.qml
···
4
import QtQuick.Layouts
5
import Quickshell.Hyprland
6
import "root:/data"
7
-
import "root:/services"
8
9
Item {
10
-
id: workspaces
11
12
-
Layout.alignment: Qt.AlignCenter
13
14
-
width: 20
15
-
height: 20
16
17
-
ColumnLayout {
18
-
spacing: 20
19
20
-
anchors.horizontalCenter: parent.horizontalCenter
21
22
-
Repeater {
23
-
model: Hyprland.workspaces
24
25
-
delegate: Item {
26
-
id: workspace
27
-
required property HyprlandWorkspace modelData
28
29
-
implicitWidth: 10
30
-
implicitHeight: 10
31
-
32
-
MouseArea {
33
-
anchors.fill: parent
34
-
hoverEnabled: true
35
36
-
onClicked: modelData.activate()
37
-
}
38
39
-
Text {
40
-
font.pointSize: 13
41
-
Layout.alignment: Qt.AlignCenter
42
43
-
color: modelData.focused === modelData.id ? Settings.colors.accent : Settings.colors.foreground
44
-
text: modelData.id
45
}
46
-
}
47
}
48
-
}
49
}
···
4
import QtQuick.Layouts
5
import Quickshell.Hyprland
6
import "root:/data"
7
8
Item {
9
+
id: root
10
11
+
Layout.alignment: Qt.AlignCenter
12
+
implicitWidth: 24
13
+
implicitHeight: workspaceColumn.implicitHeight
14
15
+
ColumnLayout {
16
+
id: workspaceColumn
17
+
anchors.horizontalCenter: parent.horizontalCenter
18
+
spacing: 4
19
20
+
Repeater {
21
+
model: Hyprland.workspaces
22
23
+
delegate: Item {
24
+
id: workspaceItem
25
+
required property HyprlandWorkspace modelData
26
27
+
Layout.alignment: Qt.AlignHCenter
28
+
implicitWidth: 24
29
+
implicitHeight: 24
30
31
+
Rectangle {
32
+
anchors.centerIn: parent
33
+
width: 22
34
+
height: 22
35
+
radius: 6
36
+
color: workspaceItem.modelData.focused
37
+
? Settings.colors.accent
38
+
: "transparent"
39
40
+
Behavior on color {
41
+
ColorAnimation { duration: 150 }
42
+
}
43
44
+
Text {
45
+
anchors.centerIn: parent
46
+
text: workspaceItem.modelData.id
47
+
color: workspaceItem.modelData.focused
48
+
? Settings.colors.background
49
+
: Settings.colors.foreground
50
+
font {
51
+
pixelSize: 12
52
+
weight: Font.Medium
53
+
}
54
+
opacity: workspaceItem.modelData.focused ? 1.0 : 0.6
55
56
+
Behavior on color {
57
+
ColorAnimation { duration: 150 }
58
+
}
59
+
Behavior on opacity {
60
+
NumberAnimation { duration: 150 }
61
+
}
62
+
}
63
+
}
64
65
+
MouseArea {
66
+
anchors.fill: parent
67
+
hoverEnabled: true
68
+
cursorShape: Qt.PointingHandCursor
69
+
onClicked: workspaceItem.modelData.activate()
70
+
}
71
+
}
72
}
73
}
74
}
+5
home/isabel/gui/quickshell/data/Settings.qml
+5
home/isabel/gui/quickshell/data/Settings.qml
+47
-25
home/isabel/gui/quickshell/modules/Bar.qml
+47
-25
home/isabel/gui/quickshell/modules/Bar.qml
···
9
Scope {
10
id: root
11
12
Variants {
13
model: Quickshell.screens
14
15
PanelWindow {
16
property var modelData
17
screen: modelData
18
-
implicitWidth: 40
19
color: "transparent"
20
21
anchors {
···
25
}
26
27
margins {
28
-
left: 8
29
-
right: 8
30
-
top: 8
31
-
bottom: 8
32
}
33
34
Rectangle {
35
id: bar
36
anchors.fill: parent
37
-
radius: 10
38
color: Settings.colors.background
39
40
ColumnLayout {
41
anchors {
42
-
left: parent.left
43
top: parent.top
44
-
right: parent.right
45
-
topMargin: 15
46
}
47
-
48
-
spacing: 15
49
50
Launcher {}
51
Workspaces {}
52
}
53
54
ColumnLayout {
55
anchors {
56
-
left: parent.left
57
-
right: parent.right
58
-
top: parent.verticalCenter
59
}
60
-
61
-
spacing: 20
62
63
Clock {}
64
-
Noti {}
65
}
66
67
-
ColumnLayout {
68
anchors {
69
-
left: parent.left
70
bottom: parent.bottom
71
-
right: parent.right
72
-
bottomMargin: 15
73
}
74
75
-
spacing: 15
76
77
-
SysTray {}
78
-
Volume {}
79
-
Network {}
80
}
81
}
82
}
···
9
Scope {
10
id: root
11
12
+
Process {
13
+
id: quickSettingsToggle
14
+
command: ["qs", "ipc", "call", "quicksettings", "toggle"]
15
+
}
16
+
17
Variants {
18
model: Quickshell.screens
19
20
PanelWindow {
21
property var modelData
22
screen: modelData
23
+
implicitWidth: 48
24
color: "transparent"
25
26
anchors {
···
30
}
31
32
margins {
33
+
left: 10
34
+
top: 10
35
+
bottom: 10
36
}
37
38
Rectangle {
39
id: bar
40
anchors.fill: parent
41
+
radius: 12
42
color: Settings.colors.background
43
44
+
// Top section - Launcher & Workspaces
45
ColumnLayout {
46
anchors {
47
+
horizontalCenter: parent.horizontalCenter
48
top: parent.top
49
+
topMargin: 12
50
}
51
+
spacing: 16
52
53
Launcher {}
54
+
55
+
BarDivider {}
56
+
57
Workspaces {}
58
}
59
60
+
// Center section - Clock
61
ColumnLayout {
62
anchors {
63
+
horizontalCenter: parent.horizontalCenter
64
+
verticalCenter: parent.verticalCenter
65
}
66
+
spacing: 16
67
68
Clock {}
69
}
70
71
+
// Bottom section - System tray, Notis, Volume, Network
72
+
Item {
73
anchors {
74
+
horizontalCenter: parent.horizontalCenter
75
bottom: parent.bottom
76
+
bottomMargin: 12
77
}
78
+
implicitWidth: bottomColumn.implicitWidth
79
+
implicitHeight: bottomColumn.implicitHeight
80
81
+
MouseArea {
82
+
anchors.fill: parent
83
+
hoverEnabled: true
84
+
cursorShape: Qt.PointingHandCursor
85
+
onClicked: quickSettingsToggle.running = true;
86
+
}
87
88
+
ColumnLayout {
89
+
id: bottomColumn
90
+
anchors.fill: parent
91
+
spacing: 12
92
+
93
+
SysTray {}
94
+
95
+
BarDivider {}
96
+
97
+
Noti { }
98
+
Volume {}
99
+
Network {}
100
+
Battery {}
101
+
}
102
}
103
}
104
}
+259
home/isabel/gui/quickshell/modules/Osd.qml
+259
home/isabel/gui/quickshell/modules/Osd.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import Quickshell
4
+
import Quickshell.Widgets
5
+
import Quickshell.Wayland
6
+
import Quickshell.Services.Notifications as QsNotifications
7
+
import "root:/data"
8
+
import "root:/components"
9
+
import "root:/services"
10
+
11
+
Scope {
12
+
id: root
13
+
14
+
// OSD State
15
+
property string osdType: ""
16
+
property real progress: 0
17
+
property string iconSource: ""
18
+
property string osdText: ""
19
+
property bool osdVisible: false
20
+
21
+
// Notification State
22
+
property var currentNotification: null
23
+
property bool notificationVisible: false
24
+
25
+
// Connect to Pipewire service for volume changes
26
+
Connections {
27
+
target: Pipewire
28
+
function onSinkVolumeChanged() { showVolumeOsd(); }
29
+
function onSinkMutedChanged() { showVolumeOsd(); }
30
+
}
31
+
32
+
function showVolumeOsd(): void {
33
+
root.osdType = "volume";
34
+
root.progress = Pipewire.sinkVolume;
35
+
root.iconSource = Pipewire.sinkIcon;
36
+
root.osdText = Pipewire.sinkVolumeText;
37
+
root.osdVisible = true;
38
+
osdHideTimer.restart();
39
+
}
40
+
41
+
function showNotification(notification: QsNotifications.Notification): void {
42
+
root.currentNotification = notification;
43
+
root.notificationVisible = true;
44
+
notificationHideTimer.restart();
45
+
}
46
+
47
+
Timer {
48
+
id: osdHideTimer
49
+
interval: 1500
50
+
onTriggered: root.osdVisible = false
51
+
}
52
+
53
+
Timer {
54
+
id: notificationHideTimer
55
+
interval: 4000
56
+
onTriggered: root.notificationVisible = false
57
+
}
58
+
59
+
// Connect to the shared notification service
60
+
Connections {
61
+
target: Notifications
62
+
function onNewNotification(notification) {
63
+
root.showNotification(notification);
64
+
}
65
+
}
66
+
67
+
Variants {
68
+
model: Quickshell.screens
69
+
70
+
PanelWindow {
71
+
id: osdWindow
72
+
property var modelData
73
+
screen: modelData
74
+
75
+
visible: root.osdVisible
76
+
color: "transparent"
77
+
78
+
WlrLayershell.namespace: "osd"
79
+
WlrLayershell.layer: WlrLayer.Overlay
80
+
exclusionMode: ExclusionMode.Ignore
81
+
82
+
anchors {
83
+
bottom: true
84
+
}
85
+
86
+
implicitWidth: 320
87
+
implicitHeight: 80
88
+
89
+
margins {
90
+
bottom: 80
91
+
}
92
+
93
+
Rectangle {
94
+
id: osdContainer
95
+
anchors.centerIn: parent
96
+
width: 300
97
+
height: 56
98
+
radius: 14
99
+
color: Settings.colors.background
100
+
101
+
RowLayout {
102
+
anchors {
103
+
fill: parent
104
+
leftMargin: 18
105
+
rightMargin: 18
106
+
}
107
+
spacing: 14
108
+
109
+
MyIcon {
110
+
icon: root.iconSource
111
+
size: 22
112
+
Layout.alignment: Qt.AlignVCenter
113
+
}
114
+
115
+
Rectangle {
116
+
Layout.fillWidth: true
117
+
Layout.preferredHeight: 6
118
+
Layout.alignment: Qt.AlignVCenter
119
+
radius: 3
120
+
color: Settings.colors.backgroundLighter
121
+
122
+
Rectangle {
123
+
width: parent.width * Math.min(root.progress, 1.0)
124
+
height: parent.height
125
+
radius: 3
126
+
color: root.muted ? Settings.colors.error : Settings.colors.accent
127
+
128
+
Behavior on width {
129
+
SmoothedAnimation { velocity: 600 }
130
+
}
131
+
}
132
+
}
133
+
134
+
Text {
135
+
text: root.osdText
136
+
color: Settings.colors.foreground
137
+
font {
138
+
pixelSize: 13
139
+
weight: Font.Medium
140
+
}
141
+
Layout.preferredWidth: 40
142
+
Layout.alignment: Qt.AlignVCenter
143
+
horizontalAlignment: Text.AlignRight
144
+
}
145
+
}
146
+
}
147
+
}
148
+
}
149
+
150
+
// Notification Popup
151
+
Variants {
152
+
model: Quickshell.screens
153
+
154
+
PanelWindow {
155
+
id: notificationWindow
156
+
property var modelData
157
+
screen: modelData
158
+
159
+
visible: root.notificationVisible && root.currentNotification !== null
160
+
color: "transparent"
161
+
162
+
WlrLayershell.namespace: "notification"
163
+
WlrLayershell.layer: WlrLayer.Overlay
164
+
exclusionMode: ExclusionMode.Ignore
165
+
166
+
anchors {
167
+
top: true
168
+
}
169
+
170
+
implicitWidth: 420
171
+
implicitHeight: 100
172
+
173
+
margins {
174
+
top: 16
175
+
}
176
+
177
+
Rectangle {
178
+
id: notificationContainer
179
+
anchors {
180
+
fill: parent
181
+
margins: 8
182
+
}
183
+
radius: 12
184
+
color: Settings.colors.background
185
+
186
+
RowLayout {
187
+
anchors {
188
+
fill: parent
189
+
margins: 14
190
+
}
191
+
spacing: 12
192
+
193
+
IconImage {
194
+
source: Quickshell.iconPath(root.currentNotification?.appIcon ?? "application-x-executable")
195
+
implicitSize: 40
196
+
Layout.alignment: Qt.AlignTop
197
+
}
198
+
199
+
ColumnLayout {
200
+
Layout.fillWidth: true
201
+
Layout.fillHeight: true
202
+
spacing: 4
203
+
204
+
Text {
205
+
text: root.currentNotification?.appName ?? ""
206
+
color: Settings.colors.foreground
207
+
font {
208
+
pixelSize: 13
209
+
weight: Font.Bold
210
+
}
211
+
elide: Text.ElideRight
212
+
Layout.fillWidth: true
213
+
}
214
+
215
+
Text {
216
+
text: root.currentNotification?.summary ?? ""
217
+
color: Settings.colors.foreground
218
+
font {
219
+
pixelSize: 12
220
+
weight: Font.Medium
221
+
}
222
+
elide: Text.ElideRight
223
+
Layout.fillWidth: true
224
+
visible: text !== ""
225
+
}
226
+
227
+
Text {
228
+
text: root.currentNotification?.body ?? ""
229
+
color: Settings.colors.foreground
230
+
opacity: 0.8
231
+
font.pixelSize: 11
232
+
elide: Text.ElideRight
233
+
wrapMode: Text.WordWrap
234
+
maximumLineCount: 2
235
+
Layout.fillWidth: true
236
+
}
237
+
}
238
+
239
+
IconButton {
240
+
icon: "window-close-symbolic"
241
+
size: 14
242
+
Layout.alignment: Qt.AlignTop
243
+
onClicked: {
244
+
root.currentNotification?.dismiss();
245
+
root.notificationVisible = false;
246
+
}
247
+
}
248
+
}
249
+
250
+
// Click to dismiss
251
+
MouseArea {
252
+
anchors.fill: parent
253
+
z: -1
254
+
onClicked: root.notificationVisible = false
255
+
}
256
+
}
257
+
}
258
+
}
259
+
}
+185
home/isabel/gui/quickshell/modules/QuickSettingsPopup.qml
+185
home/isabel/gui/quickshell/modules/QuickSettingsPopup.qml
···
···
1
+
import QtQuick
2
+
import QtQuick.Layouts
3
+
import QtQuick.Controls
4
+
import Quickshell
5
+
import Quickshell.Widgets
6
+
import Quickshell.Wayland
7
+
import Quickshell.Io
8
+
import Quickshell.Services.Notifications as QsNotifications
9
+
import "root:/data"
10
+
import "root:/components"
11
+
import "root:/services"
12
+
13
+
14
+
Scope {
15
+
id: root
16
+
17
+
IpcHandler {
18
+
target: "quicksettings"
19
+
20
+
function toggle(): void {
21
+
if (loader.active) {
22
+
loader.active = false;
23
+
} else {
24
+
loader.activeAsync = true;
25
+
}
26
+
}
27
+
}
28
+
29
+
LazyLoader {
30
+
id: loader
31
+
32
+
PanelWindow {
33
+
id: popup
34
+
color: "transparent"
35
+
36
+
anchors.left: true
37
+
margins.left: 70
38
+
39
+
implicitWidth: screen.width * 0.21
40
+
implicitHeight: screen.height * 0.98
41
+
exclusionMode: ExclusionMode.Ignore
42
+
WlrLayershell.layer: WlrLayer.Overlay
43
+
44
+
Rectangle {
45
+
anchors.fill: parent
46
+
radius: 12
47
+
color: Settings.colors.background
48
+
49
+
ColumnLayout {
50
+
spacing: 12
51
+
anchors {
52
+
fill: parent
53
+
margins: 16
54
+
}
55
+
56
+
// Header
57
+
RowLayout {
58
+
Layout.fillWidth: true
59
+
spacing: 8
60
+
61
+
Text {
62
+
text: "Notifications"
63
+
color: Settings.colors.foreground
64
+
font {
65
+
pixelSize: 18
66
+
weight: Font.Bold
67
+
}
68
+
}
69
+
70
+
Item { Layout.fillWidth: true }
71
+
72
+
IconButton {
73
+
icon: "edit-clear-all-symbolic"
74
+
size: 16
75
+
onClicked: {
76
+
for (const n of Notifications.list) {
77
+
n.dismiss();
78
+
}
79
+
popup.visible = false;
80
+
}
81
+
}
82
+
}
83
+
84
+
// Notifications List
85
+
ListView {
86
+
id: notiList
87
+
model: Notifications.list
88
+
Layout.fillWidth: true
89
+
Layout.fillHeight: true
90
+
spacing: 8
91
+
clip: true
92
+
93
+
ScrollBar.vertical: ScrollBar {
94
+
policy: ScrollBar.AsNeeded
95
+
}
96
+
97
+
delegate: Rectangle {
98
+
required property QsNotifications.Notification modelData
99
+
width: ListView.view.width
100
+
height: 72
101
+
radius: 8
102
+
color: Settings.colors.backgroundLighter
103
+
104
+
RowLayout {
105
+
anchors {
106
+
fill: parent
107
+
margins: 10
108
+
}
109
+
spacing: 10
110
+
111
+
IconImage {
112
+
source: Quickshell.iconPath(modelData?.appIcon ?? "application-x-executable")
113
+
114
+
implicitSize: 40
115
+
Layout.alignment: Qt.AlignVCenter
116
+
}
117
+
118
+
ColumnLayout {
119
+
Layout.fillWidth: true
120
+
Layout.fillHeight: true
121
+
spacing: 2
122
+
123
+
Text {
124
+
text: modelData.appName
125
+
color: Settings.colors.foreground
126
+
font {
127
+
pixelSize: 13
128
+
weight: Font.Medium
129
+
}
130
+
elide: Text.ElideRight
131
+
Layout.fillWidth: true
132
+
}
133
+
134
+
Text {
135
+
text: modelData.body
136
+
color: Settings.colors.foreground
137
+
opacity: 0.8
138
+
font.pixelSize: 12
139
+
elide: Text.ElideRight
140
+
maximumLineCount: 2
141
+
wrapMode: Text.WordWrap
142
+
Layout.fillWidth: true
143
+
}
144
+
}
145
+
146
+
IconButton {
147
+
icon: "window-close-symbolic"
148
+
size: 14
149
+
Layout.alignment: Qt.AlignTop
150
+
onClicked: {
151
+
modelData.dismiss();
152
+
if (Notifications.list.length <= 0) {
153
+
popup.visible = false;
154
+
}
155
+
}
156
+
}
157
+
}
158
+
}
159
+
}
160
+
161
+
// Separator before Media
162
+
Rectangle {
163
+
Layout.fillWidth: true
164
+
Layout.preferredHeight: 1
165
+
color: Settings.colors.backgroundLightest
166
+
visible: Media.players.length > 0
167
+
}
168
+
169
+
// Media Players Section
170
+
MediaPlayers {}
171
+
172
+
// Separator
173
+
Rectangle {
174
+
Layout.fillWidth: true
175
+
Layout.preferredHeight: 1
176
+
color: Settings.colors.backgroundLightest
177
+
}
178
+
179
+
// Quick Settings Section
180
+
QuickSettings {}
181
+
}
182
+
}
183
+
}
184
+
}
185
+
}
+71
home/isabel/gui/quickshell/services/Bluetooth.qml
+71
home/isabel/gui/quickshell/services/Bluetooth.qml
···
···
1
+
pragma Singleton
2
+
3
+
import Quickshell
4
+
import Quickshell.Bluetooth
5
+
import QtQuick
6
+
7
+
Singleton {
8
+
id: root
9
+
10
+
// Adapter state
11
+
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
12
+
readonly property bool powered: adapter?.enabled ?? false
13
+
readonly property int state: adapter?.state ?? BluetoothAdapterState.Disabled
14
+
15
+
// Device state
16
+
readonly property var devices: adapter?.devices ?? []
17
+
readonly property BluetoothDevice connectedDevice: {
18
+
if (!adapter) return null;
19
+
for (let i = 0; i < devices.count; i++) {
20
+
const device = devices.values[i];
21
+
if (device.connected) return device;
22
+
}
23
+
return null;
24
+
}
25
+
readonly property bool connected: connectedDevice !== null
26
+
27
+
// Icon based on state
28
+
readonly property string icon: {
29
+
if (state === BluetoothAdapterState.Blocked) return "bluetooth-disabled-symbolic";
30
+
if (!powered) return "bluetooth-disabled-symbolic";
31
+
if (connected) return "bluetooth-active-symbolic";
32
+
return "bluetooth-symbolic";
33
+
}
34
+
35
+
readonly property string statusText: {
36
+
if (state === BluetoothAdapterState.Blocked) return "Blocked";
37
+
if (state === BluetoothAdapterState.Enabling) return "Enabling...";
38
+
if (state === BluetoothAdapterState.Disabling) return "Disabling...";
39
+
if (!powered) return "Bluetooth Off";
40
+
if (connected) return connectedDevice.name;
41
+
return "Not Connected";
42
+
}
43
+
44
+
reloadableId: "bluetooth"
45
+
46
+
function toggle() {
47
+
if (adapter) {
48
+
adapter.enabled = !adapter.enabled;
49
+
}
50
+
}
51
+
52
+
function connectDevice(device) {
53
+
device.connect();
54
+
}
55
+
56
+
function disconnectDevice(device) {
57
+
device.disconnect();
58
+
}
59
+
60
+
function startDiscovery() {
61
+
if (adapter) {
62
+
adapter.discovering = true;
63
+
}
64
+
}
65
+
66
+
function stopDiscovery() {
67
+
if (adapter) {
68
+
adapter.discovering = false;
69
+
}
70
+
}
71
+
}
+60
home/isabel/gui/quickshell/services/Brightness.qml
+60
home/isabel/gui/quickshell/services/Brightness.qml
···
···
1
+
pragma Singleton
2
+
3
+
import Quickshell
4
+
import Quickshell.Io
5
+
import QtQuick
6
+
7
+
Singleton {
8
+
id: root
9
+
10
+
// Brightness state
11
+
property real brightness: 1.0
12
+
property bool available: false
13
+
14
+
// Icon based on brightness level
15
+
readonly property string icon: {
16
+
if (brightness > 0.66) return "display-brightness-high-symbolic";
17
+
if (brightness > 0.33) return "display-brightness-medium-symbolic";
18
+
return "display-brightness-low-symbolic";
19
+
}
20
+
21
+
// Brightness percentage string
22
+
readonly property string brightnessText: Math.round(brightness * 100) + "%"
23
+
24
+
function setBrightness(value: real): void {
25
+
const clampedValue = Math.max(0.05, Math.min(1, value));
26
+
brightness = clampedValue;
27
+
setBrightnessProc.command = ["brightnessctl", "set", Math.round(clampedValue * 100) + "%"];
28
+
setBrightnessProc.running = true;
29
+
}
30
+
31
+
function increaseBrightness(step = 0.05): void {
32
+
setBrightness(brightness + step);
33
+
}
34
+
35
+
function decreaseBrightness(step = 0.05): void {
36
+
setBrightness(brightness - step);
37
+
}
38
+
39
+
Component.onCompleted: getBrightnessProc.running = true
40
+
41
+
Process {
42
+
id: getBrightnessProc
43
+
command: ["bash", "-c", "brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'"]
44
+
stdout: SplitParser {
45
+
onRead: data => {
46
+
const val = parseInt(data.trim());
47
+
if (!isNaN(val)) {
48
+
root.brightness = val / 100;
49
+
root.available = true;
50
+
}
51
+
}
52
+
}
53
+
}
54
+
55
+
Process {
56
+
id: setBrightnessProc
57
+
}
58
+
59
+
reloadableId: "brightness"
60
+
}
+122
-25
home/isabel/gui/quickshell/services/Networking.qml
+122
-25
home/isabel/gui/quickshell/services/Networking.qml
···
1
-
// stolen from https://github.com/caelestia-dots/shell/blob/cf1180df7ba9c8f1180ab3a902746d4f169c556f/services/Network.qml
2
-
// thankies :D
3
4
pragma Singleton
5
···
10
Singleton {
11
id: root
12
13
readonly property list<AccessPoint> networks: []
14
-
readonly property AccessPoint active: networks.find(n => n.active) ?? null
15
16
reloadableId: "network"
17
18
Process {
19
running: true
20
command: ["nmcli", "m"]
21
stdout: SplitParser {
22
-
onRead: getNetworks.running = true
23
}
24
}
25
26
Process {
27
id: getNetworks
28
-
running: true
29
command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"]
30
-
environment: ({
31
-
LANG: "C",
32
-
LC_ALL: "C"
33
-
})
34
stdout: StdioCollector {
35
onStreamFinished: {
36
const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED";
37
const rep = new RegExp("\\\\:", "g");
38
const rep2 = new RegExp(PLACEHOLDER, "g");
39
40
-
const networks = text.trim().split("\n").map(n => {
41
const net = n.replace(rep, PLACEHOLDER).split(":");
42
return {
43
active: net[0] === "yes",
44
-
strength: parseInt(net[1]),
45
-
frequency: parseInt(net[2]),
46
-
ssid: net[3],
47
bssid: net[4]?.replace(rep2, ":") ?? ""
48
};
49
-
});
50
const rNetworks = root.networks;
51
52
-
const destroyed = rNetworks.filter(rn => !networks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid));
53
-
for (const network of destroyed)
54
-
rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());
55
56
for (const network of networks) {
57
-
const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid);
58
if (match) {
59
match.lastIpcObject = network;
60
} else {
61
-
rNetworks.push(apComp.createObject(root, {
62
-
lastIpcObject: network
63
-
}));
64
}
65
}
66
}
···
75
readonly property int frequency: lastIpcObject.frequency
76
readonly property bool active: lastIpcObject.active
77
readonly property string icon: {
78
-
if (!active) return "network-wireless-signal-none-symbolic";
79
-
80
if (strength >= 75) return "network-wireless-signal-excellent-symbolic";
81
if (strength >= 50) return "network-wireless-signal-good-symbolic";
82
if (strength >= 25) return "network-wireless-signal-ok-symbolic";
83
-
return "network-wireless-signal-weak-symbolic";
84
}
85
}
86
87
Component {
88
id: apComp
89
-
90
AccessPoint {}
91
}
92
}
···
1
+
// Network service with WiFi and Ethernet support
2
3
pragma Singleton
4
···
9
Singleton {
10
id: root
11
12
+
// WiFi
13
readonly property list<AccessPoint> networks: []
14
+
readonly property AccessPoint activeWifi: networks.find(n => n.active) ?? null
15
+
readonly property bool wifiEnabled: internal.wifiEnabled
16
+
17
+
// Ethernet
18
+
readonly property bool ethernetConnected: internal.ethernetConnected
19
+
readonly property string ethernetDevice: internal.ethernetDevice
20
+
21
+
// Combined state
22
+
readonly property bool connected: ethernetConnected || activeWifi !== null
23
+
readonly property string connectionType: ethernetConnected ? "ethernet" : (activeWifi ? "wifi" : "none")
24
+
25
+
readonly property string icon: {
26
+
if (ethernetConnected) return "network-wired-symbolic";
27
+
if (activeWifi) return activeWifi.icon;
28
+
if (!wifiEnabled) return "network-wireless-disabled-symbolic";
29
+
return "network-wireless-offline-symbolic";
30
+
}
31
+
32
+
readonly property string statusText: {
33
+
if (ethernetConnected) return ethernetDevice;
34
+
if (activeWifi) return activeWifi.ssid;
35
+
return "Disconnected";
36
+
}
37
38
reloadableId: "network"
39
40
+
function toggleWifi() {
41
+
toggleWifiProc.running = true;
42
+
}
43
+
44
+
function reload() {
45
+
checkStatus.running = true;
46
+
}
47
+
48
+
function connectToNetwork(ssid) {
49
+
connectProc.command = ["nmcli", "d", "wifi", "connect", ssid];
50
+
connectProc.running = true;
51
+
}
52
+
53
+
QtObject {
54
+
id: internal
55
+
property bool wifiEnabled: true
56
+
property bool ethernetConnected: false
57
+
property string ethernetDevice: ""
58
+
}
59
+
60
+
// Monitor for network changes
61
Process {
62
running: true
63
command: ["nmcli", "m"]
64
stdout: SplitParser {
65
+
onRead: checkStatus.running = true
66
+
}
67
+
}
68
+
69
+
// Check overall network status
70
+
Process {
71
+
id: checkStatus
72
+
running: true
73
+
command: ["bash", "-c", `
74
+
# Check WiFi status
75
+
wifi_status=$(nmcli radio wifi 2>/dev/null)
76
+
echo "WIFI:$wifi_status"
77
+
78
+
# Check Ethernet
79
+
eth_info=$(nmcli -t -f TYPE,STATE,DEVICE,CONNECTION device 2>/dev/null | grep '^ethernet:connected' | head -1)
80
+
if [ -n "$eth_info" ]; then
81
+
eth_name=$(echo "$eth_info" | cut -d: -f4)
82
+
echo "ETH:connected:$eth_name"
83
+
else
84
+
echo "ETH:disconnected:"
85
+
fi
86
+
`]
87
+
environment: ({ LANG: "C", LC_ALL: "C" })
88
+
stdout: SplitParser {
89
+
onRead: data => {
90
+
const line = data.trim();
91
+
if (line.startsWith("WIFI:")) {
92
+
internal.wifiEnabled = line.includes("enabled");
93
+
} else if (line.startsWith("ETH:")) {
94
+
const parts = line.split(":");
95
+
internal.ethernetConnected = parts[1] === "connected";
96
+
internal.ethernetDevice = parts[2] || "";
97
+
}
98
+
}
99
+
}
100
+
onExited: {
101
+
if (internal.wifiEnabled) {
102
+
getNetworks.running = true;
103
+
}
104
}
105
}
106
107
Process {
108
+
id: toggleWifiProc
109
+
command: ["nmcli", "radio", "wifi", internal.wifiEnabled ? "off" : "on"]
110
+
onExited: checkStatus.running = true
111
+
}
112
+
113
+
Process {
114
+
id: connectProc
115
+
onExited: checkStatus.running = true
116
+
}
117
+
118
+
Process {
119
id: getNetworks
120
command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"]
121
+
environment: ({ LANG: "C", LC_ALL: "C" })
122
stdout: StdioCollector {
123
onStreamFinished: {
124
const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED";
125
const rep = new RegExp("\\\\:", "g");
126
const rep2 = new RegExp(PLACEHOLDER, "g");
127
128
+
const networks = text.trim().split("\n").filter(l => l.length > 0).map(n => {
129
const net = n.replace(rep, PLACEHOLDER).split(":");
130
return {
131
active: net[0] === "yes",
132
+
strength: parseInt(net[1]) || 0,
133
+
frequency: parseInt(net[2]) || 0,
134
+
ssid: net[3] || "",
135
bssid: net[4]?.replace(rep2, ":") ?? ""
136
};
137
+
}).filter(n => n.ssid !== "");
138
+
139
const rNetworks = root.networks;
140
141
+
// Remove networks that no longer exist
142
+
const destroyed = rNetworks.filter(rn => !networks.find(n =>
143
+
n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid
144
+
));
145
+
for (const network of destroyed) {
146
+
const idx = rNetworks.indexOf(network);
147
+
if (idx >= 0) {
148
+
rNetworks.splice(idx, 1);
149
+
network.destroy();
150
+
}
151
+
}
152
153
+
// Update or add networks
154
for (const network of networks) {
155
+
const match = rNetworks.find(n =>
156
+
n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid
157
+
);
158
if (match) {
159
match.lastIpcObject = network;
160
} else {
161
+
rNetworks.push(apComp.createObject(root, { lastIpcObject: network }));
162
}
163
}
164
}
···
173
readonly property int frequency: lastIpcObject.frequency
174
readonly property bool active: lastIpcObject.active
175
readonly property string icon: {
176
if (strength >= 75) return "network-wireless-signal-excellent-symbolic";
177
if (strength >= 50) return "network-wireless-signal-good-symbolic";
178
if (strength >= 25) return "network-wireless-signal-ok-symbolic";
179
+
if (strength > 0) return "network-wireless-signal-weak-symbolic";
180
+
return "network-wireless-signal-none-symbolic";
181
}
182
}
183
184
Component {
185
id: apComp
186
AccessPoint {}
187
}
188
}
189
+
+12
-1
home/isabel/gui/quickshell/services/Notifications.qml
+12
-1
home/isabel/gui/quickshell/services/Notifications.qml
···
3
import Quickshell
4
import Quickshell.Io
5
import Quickshell.Services.Notifications
6
7
Singleton {
8
id: root
9
10
-
property list<Notification> list: notifactionServer.trackedNotifications.values.filter(notification => notification.tracked)
11
12
NotificationServer {
13
id: notifactionServer
14
onNotification: (notification) => {
15
notification.tracked = true
16
}
17
18
actionsSupported: true
···
28
}
29
}
30
}
···
3
import Quickshell
4
import Quickshell.Io
5
import Quickshell.Services.Notifications
6
+
import "root:/data"
7
8
Singleton {
9
id: root
10
11
+
property bool dndEnabled: false
12
+
property list<Notification> list: notifactionServer.trackedNotifications.values.filter(
13
+
notification => notification.tracked && !root.dndEnabled && !Settings.notificationBlacklist.includes(notification.appName)
14
+
)
15
+
16
+
signal newNotification(Notification notification)
17
18
NotificationServer {
19
id: notifactionServer
20
onNotification: (notification) => {
21
notification.tracked = true
22
+
// Only emit signal if not in DND and not blacklisted
23
+
if (!root.dndEnabled && !Settings.notificationBlacklist.includes(notification.appName)) {
24
+
root.newNotification(notification)
25
+
}
26
}
27
28
actionsSupported: true
···
38
}
39
}
40
}
41
+
+90
home/isabel/gui/quickshell/services/Pipewire.qml
+90
home/isabel/gui/quickshell/services/Pipewire.qml
···
···
1
+
pragma Singleton
2
+
3
+
import Quickshell
4
+
import Quickshell.Services.Pipewire as QsPipewire
5
+
import QtQuick
6
+
7
+
Singleton {
8
+
id: root
9
+
10
+
// Track the default audio sink
11
+
QsPipewire.PwObjectTracker {
12
+
objects: [QsPipewire.Pipewire.defaultAudioSink, QsPipewire.Pipewire.defaultAudioSource]
13
+
}
14
+
15
+
// Default audio sink (speakers/headphones)
16
+
readonly property QsPipewire.PwNode sink: QsPipewire.Pipewire.defaultAudioSink
17
+
readonly property real sinkVolume: sink?.audio?.volume ?? 0
18
+
readonly property bool sinkMuted: sink?.audio?.muted ?? false
19
+
20
+
// Default audio source (microphone)
21
+
readonly property QsPipewire.PwNode source: QsPipewire.Pipewire.defaultAudioSource
22
+
readonly property real sourceVolume: source?.audio?.volume ?? 0
23
+
readonly property bool sourceMuted: source?.audio?.muted ?? false
24
+
25
+
// All nodes for detailed view
26
+
readonly property var nodes: QsPipewire.Pipewire.nodes
27
+
28
+
// Icon based on volume and mute state for sink
29
+
readonly property string sinkIcon: {
30
+
if (sinkMuted) return "audio-volume-muted-symbolic";
31
+
if (sinkVolume > 0.66) return "audio-volume-high-symbolic";
32
+
if (sinkVolume > 0.33) return "audio-volume-medium-symbolic";
33
+
if (sinkVolume > 0) return "audio-volume-low-symbolic";
34
+
return "audio-volume-muted-symbolic";
35
+
}
36
+
37
+
// Icon based on volume and mute state for source
38
+
readonly property string sourceIcon: {
39
+
if (sourceMuted) return "microphone-sensitivity-muted-symbolic";
40
+
if (sourceVolume > 0.66) return "microphone-sensitivity-high-symbolic";
41
+
if (sourceVolume > 0.33) return "microphone-sensitivity-medium-symbolic";
42
+
if (sourceVolume > 0) return "microphone-sensitivity-low-symbolic";
43
+
return "microphone-sensitivity-muted-symbolic";
44
+
}
45
+
46
+
// Volume percentage string
47
+
readonly property string sinkVolumeText: Math.round(sinkVolume * 100) + "%"
48
+
readonly property string sourceVolumeText: Math.round(sourceVolume * 100) + "%"
49
+
50
+
// Functions to control sink (speakers)
51
+
function setSinkVolume(volume: real): void {
52
+
if (sink?.audio) {
53
+
sink.audio.volume = Math.max(0, Math.min(1, volume));
54
+
}
55
+
}
56
+
57
+
function toggleSinkMute(): void {
58
+
if (sink?.audio) {
59
+
sink.audio.muted = !sink.audio.muted;
60
+
}
61
+
}
62
+
63
+
function setSinkMuted(muted: bool): void {
64
+
if (sink?.audio) {
65
+
sink.audio.muted = muted;
66
+
}
67
+
}
68
+
69
+
// Functions to control source (microphone)
70
+
function setSourceVolume(volume: real): void {
71
+
if (source?.audio) {
72
+
source.audio.volume = Math.max(0, Math.min(1, volume));
73
+
}
74
+
}
75
+
76
+
function toggleSourceMute(): void {
77
+
if (source?.audio) {
78
+
source.audio.muted = !source.audio.muted;
79
+
}
80
+
}
81
+
82
+
function setSourceMuted(muted: bool): void {
83
+
if (source?.audio) {
84
+
source.audio.muted = muted;
85
+
}
86
+
}
87
+
88
+
reloadableId: "pipewire"
89
+
}
90
+
+79
home/isabel/gui/quickshell/services/UPower.qml
+79
home/isabel/gui/quickshell/services/UPower.qml
···
···
1
+
pragma Singleton
2
+
3
+
import Quickshell
4
+
import Quickshell.Services.UPower as QsUPower
5
+
import QtQuick
6
+
7
+
Singleton {
8
+
id: root
9
+
10
+
// Display device represents the overall system battery
11
+
readonly property QsUPower.UPowerDevice displayDevice: QsUPower.UPower.displayDevice
12
+
13
+
// Battery state
14
+
readonly property bool isPresent: displayDevice?.isPresent ?? false
15
+
readonly property real percentage: displayDevice?.percentage ?? 0
16
+
readonly property int state: displayDevice?.state ?? QsUPower.UPowerDeviceState.Unknown
17
+
readonly property bool charging: state === QsUPower.UPowerDeviceState.Charging
18
+
readonly property bool discharging: state === QsUPower.UPowerDeviceState.Discharging
19
+
readonly property bool fullyCharged: state === QsUPower.UPowerDeviceState.FullyCharged
20
+
21
+
// Time estimates (in seconds)
22
+
readonly property real timeToEmpty: displayDevice?.timeToEmpty ?? 0
23
+
readonly property real timeToFull: displayDevice?.timeToFull ?? 0
24
+
25
+
// Energy info
26
+
readonly property real energy: displayDevice?.energy ?? 0
27
+
readonly property real energyFull: displayDevice?.energyFull ?? 0
28
+
readonly property real energyRate: displayDevice?.energyRate ?? 0
29
+
30
+
// Icon based on percentage and charging state
31
+
readonly property string icon: {
32
+
if (!isPresent) return "battery-missing-symbolic";
33
+
34
+
const level = percentage;
35
+
let iconName = "battery";
36
+
37
+
if (level >= 90) iconName = "battery-full";
38
+
else if (level >= 60) iconName = "battery-good";
39
+
else if (level >= 30) iconName = "battery-medium";
40
+
else if (level >= 10) iconName = "battery-low";
41
+
else iconName = "battery-empty";
42
+
43
+
if (charging) iconName += "-charging";
44
+
45
+
return iconName + "-symbolic";
46
+
}
47
+
48
+
// Status text
49
+
readonly property string statusText: {
50
+
if (!isPresent) return "No Battery";
51
+
if (fullyCharged) return "Fully Charged";
52
+
if (charging) {
53
+
const time = formatTime(timeToFull);
54
+
return time ? `Charging (${time})` : "Charging";
55
+
}
56
+
if (discharging) {
57
+
const time = formatTime(timeToEmpty);
58
+
return time ? `${time} remaining` : "Discharging";
59
+
}
60
+
return `${Math.round(percentage)}%`;
61
+
}
62
+
63
+
// Format time in seconds to human-readable string
64
+
function formatTime(seconds: real): string {
65
+
if (seconds <= 0) return "";
66
+
const hours = Math.floor(seconds / 3600);
67
+
const minutes = Math.floor((seconds % 3600) / 60);
68
+
if (hours > 0) {
69
+
return `${hours}h ${minutes}m`;
70
+
}
71
+
return `${minutes}m`;
72
+
}
73
+
74
+
// All power devices (for detailed view)
75
+
readonly property var devices: QsUPower.UPower.devices
76
+
77
+
reloadableId: "upower"
78
+
}
79
+
+6
-3
home/isabel/gui/quickshell/shell.qml
+6
-3
home/isabel/gui/quickshell/shell.qml
+2
home/isabel/gui/quickshell.nix
+2
home/isabel/gui/quickshell.nix
+6
-5
home/isabel/gui/vicinae/default.nix
+6
-5
home/isabel/gui/vicinae/default.nix
···
14
extensions = map mkMyVicinaeExt [
15
{
16
extName = "nix";
17
-
npmDepsHash = "sha256-Zx+QPVWWppz6mvQKyu4c6ND8E4TeeK12assE2khE/sA=";
18
}
19
{
20
extName = "wifi-commander";
···
24
extName = "bluetooth";
25
npmDepsHash = "sha256-cpyuJTc3a7oLibKUY2EhD33w8/35frfwIaGFKFezvts=";
26
}
27
-
{
28
-
extName = "mullvad";
29
-
npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0=";
30
-
}
31
];
32
};
33
}
···
14
extensions = map mkMyVicinaeExt [
15
{
16
extName = "nix";
17
+
npmDepsHash = "sha256-HPWNUznCWVPz39PlPEBR7GpgbC0DuIAvVBdB2GAs47A=";
18
}
19
{
20
extName = "wifi-commander";
···
24
extName = "bluetooth";
25
npmDepsHash = "sha256-cpyuJTc3a7oLibKUY2EhD33w8/35frfwIaGFKFezvts=";
26
}
27
+
# broken
28
+
# {
29
+
# extName = "mullvad";
30
+
# npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0=";
31
+
# }
32
];
33
};
34
}
+2
-2
home/isabel/gui/vicinae/extension.nix
+2
-2
home/isabel/gui/vicinae/extension.nix
+1
-1
home/isabel/packages.nix
+1
-1
home/isabel/packages.nix
+1
-1
home/isabel/system/ssh.nix
+1
-1
home/isabel/system/ssh.nix
+63
-27
justfile
+63
-27
justfile
···
3
# rebuild is also set as a var so you can add --set to change it if you need to
4
5
rebuild := if os() == "macos" { "sudo darwin-rebuild" } else { "nixos-rebuild" }
6
-
system-args := if os() == "macos" { "" } else { "--sudo --no-reexec --log-format internal-json" }
7
-
nom-cmd := if os() == "macos" { "nom" } else { "nom --json" }
8
9
[private]
10
default:
···
13
# rebuild group
14
15
[group('rebuild')]
16
[private]
17
builder goal *args:
18
{{ rebuild }} {{ goal }} \
19
--flake {{ flake }} \
20
{{ system-args }} \
21
{{ args }} \
22
-
|& {{ nom-cmd }}
23
24
[group('rebuild')]
25
deploy host *args:
26
-
#!/usr/bin/env bash
27
-
before=$(ssh {{ host }} 'readlink -f /run/current-system')
28
-
before=$(echo "$before" | tail -n 1)
29
-
just builder switch --target-host {{ host }} --use-substitutes {{ args }}
30
-
[ $? -eq 0 ] && ssh {{ host }} 'lix-diff "$before" /run/current-system'
31
32
[group('rebuild')]
33
deploy-all:
34
-
just deploy minerva
35
-
just deploy aphrodite
36
-
just deploy skadi
37
-
just deploy hephaestus
38
-
just deploy isis
39
40
# rebuild the boot
41
[group('rebuild')]
42
boot *args: (builder "boot" args)
43
44
# test what happens when you switch
45
[group('rebuild')]
46
test *args: (builder "test" args)
47
48
# switch the new system configuration
49
[group('rebuild')]
50
switch *args:
51
-
#!/usr/bin/env bash
52
-
before="$(readlink -f /run/current-system)"
53
-
just builder switch {{ args }}
54
-
[ $? -eq 0 ] && lix-diff "$before" /run/current-system
55
56
[group('rebuild')]
57
[macos]
58
provision host:
59
sudo nix run github:LnL7/nix-darwin -- switch --flake {{ flake }}#{{ host }}
60
sudo -i nix-env --uninstall lix # we need to remove the none declarative install of lix
61
62
# package group
63
-
64
# build the package, you must specify the package you want to build
65
-
[group('package')]
66
-
build pkg:
67
-
nix build {{ flake }}#{{ pkg }} \
68
-
--log-format internal-json \
69
-
-v \
70
-
|& nom --json
71
72
# build the iso image, you must specify the image you want to build
73
[group('package')]
74
-
iso image: (build "nixosConfigurations." + image + ".config.system.build.isoImage")
75
76
# build the tarball, you must specify the host you want to build
77
[group('package')]
78
tar host:
79
sudo nix run {{ flake }}#nixosConfigurations.{{ host }}.config.system.build.tarballBuilder
80
···
82
83
# check the flake for errors
84
[group('dev')]
85
check *args:
86
nix flake check --option allow-import-from-derivation false {{ args }}
87
88
[group('dev')]
89
repl-host host=`hostname`:
90
nix repl .#nixosConfigurations.{{ host }}
91
92
# update a set of given inputs
93
[group('dev')]
94
update *input:
95
nix flake update {{ input }} \
96
--refresh \
···
100
101
# build & serve the docs locally
102
[group('dev')]
103
serve:
104
nix run {{ flake }}#docs.serve
105
106
# push to the mirrors
107
[group('dev')]
108
push-mirrors:
109
git push git@gitlab.com:isabelroses/dotfiles.git
110
git push --mirror ssh://git@codeberg.org/isabel/dotfiles.git
···
112
113
# rotate all secrets
114
[group('dev')]
115
roate-secrets:
116
-
find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {}
117
118
# update the secret keys
119
[group('dev')]
120
update-secrets:
121
-
find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {}
122
123
# utils group
124
···
126
127
# verify the integrity of the nix store
128
[group('utils')]
129
verify *args:
130
nix-store --verify {{ args }}
131
132
# repairs the nix store from any breakages it may have
133
[group('utils')]
134
repair: (verify "--check-contents --repair")
135
136
# clean the nix store and optimise it
137
[group('utils')]
138
clean:
139
nix-collect-garbage --delete-older-than 3d
140
nix store optimise
···
3
# rebuild is also set as a var so you can add --set to change it if you need to
4
5
rebuild := if os() == "macos" { "sudo darwin-rebuild" } else { "nixos-rebuild" }
6
+
system-args := if os() == "macos" { "" } else { "--sudo --no-reexec" }
7
8
[private]
9
default:
···
12
# rebuild group
13
14
[group('rebuild')]
15
+
[no-exit-message]
16
[private]
17
builder goal *args:
18
+
#!/usr/bin/env bash
19
+
set -euo pipefail
20
{{ rebuild }} {{ goal }} \
21
--flake {{ flake }} \
22
+
--log-format internal-json \
23
{{ system-args }} \
24
{{ args }} \
25
+
|& nom --json
26
27
[group('rebuild')]
28
+
[no-exit-message]
29
deploy host *args:
30
+
#!/usr/bin/env bash
31
+
set -euo pipefail
32
+
before=$(ssh -q {{ host }} 'readlink /run/current-system')
33
+
just builder switch --target-host {{ host }} --use-substitutes {{ args }}
34
+
35
+
if [[ -n "${DEPLOY_SUMMARY:-}" ]]; then
36
+
{
37
+
echo "===== {{ host }} ====="
38
+
ssh -q {{ host }} lix diff "$before"
39
+
echo
40
+
} >> "$DEPLOY_SUMMARY"
41
+
else
42
+
ssh {{ host }} lix diff "$before"
43
+
fi
44
45
[group('rebuild')]
46
+
[no-exit-message]
47
deploy-all:
48
+
#!/usr/bin/env bash
49
+
set -euo pipefail
50
+
export DEPLOY_SUMMARY=".deploy-summary"
51
+
: > "$DEPLOY_SUMMARY"
52
+
53
+
just deploy minerva
54
+
just deploy athena
55
+
just deploy aphrodite
56
+
just deploy skadi
57
+
just deploy hephaestus
58
+
just deploy isis
59
+
60
+
echo
61
+
echo "===== DEPLOYMENT SUMMARY ====="
62
+
cat "$DEPLOY_SUMMARY"
63
+
rm "$DEPLOY_SUMMARY"
64
65
# rebuild the boot
66
[group('rebuild')]
67
+
[no-exit-message]
68
boot *args: (builder "boot" args)
69
70
# test what happens when you switch
71
[group('rebuild')]
72
+
[no-exit-message]
73
test *args: (builder "test" args)
74
75
# switch the new system configuration
76
[group('rebuild')]
77
+
[no-exit-message]
78
switch *args:
79
+
#!/usr/bin/env bash
80
+
set -euo pipefail
81
+
before=$(readlink /run/current-system)
82
+
just builder switch {{ args }}
83
+
lix diff "$before"
84
85
[group('rebuild')]
86
[macos]
87
+
[no-exit-message]
88
provision host:
89
sudo nix run github:LnL7/nix-darwin -- switch --flake {{ flake }}#{{ host }}
90
sudo -i nix-env --uninstall lix # we need to remove the none declarative install of lix
91
92
# package group
93
# build the package, you must specify the package you want to build
94
95
# build the iso image, you must specify the image you want to build
96
[group('package')]
97
+
[no-exit-message]
98
+
iso image:
99
+
nom build {{ flake }}#nixosConfigurations.{{ image }}.config.system.build.isoImage
100
101
# build the tarball, you must specify the host you want to build
102
[group('package')]
103
+
[no-exit-message]
104
tar host:
105
sudo nix run {{ flake }}#nixosConfigurations.{{ host }}.config.system.build.tarballBuilder
106
···
108
109
# check the flake for errors
110
[group('dev')]
111
+
[no-exit-message]
112
check *args:
113
nix flake check --option allow-import-from-derivation false {{ args }}
114
115
[group('dev')]
116
+
[no-exit-message]
117
repl-host host=`hostname`:
118
nix repl .#nixosConfigurations.{{ host }}
119
120
# update a set of given inputs
121
[group('dev')]
122
+
[no-exit-message]
123
update *input:
124
nix flake update {{ input }} \
125
--refresh \
···
129
130
# build & serve the docs locally
131
[group('dev')]
132
+
[no-exit-message]
133
serve:
134
nix run {{ flake }}#docs.serve
135
136
# push to the mirrors
137
[group('dev')]
138
+
[no-exit-message]
139
push-mirrors:
140
git push git@gitlab.com:isabelroses/dotfiles.git
141
git push --mirror ssh://git@codeberg.org/isabel/dotfiles.git
···
143
144
# rotate all secrets
145
[group('dev')]
146
+
[no-exit-message]
147
roate-secrets:
148
+
find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {}
149
150
# update the secret keys
151
[group('dev')]
152
+
[no-exit-message]
153
update-secrets:
154
+
find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {}
155
156
# utils group
157
···
159
160
# verify the integrity of the nix store
161
[group('utils')]
162
+
[no-exit-message]
163
verify *args:
164
nix-store --verify {{ args }}
165
166
# repairs the nix store from any breakages it may have
167
[group('utils')]
168
+
[no-exit-message]
169
repair: (verify "--check-contents --repair")
170
171
# clean the nix store and optimise it
172
[group('utils')]
173
+
[no-exit-message]
174
clean:
175
nix-collect-garbage --delete-older-than 3d
176
nix store optimise
+2
-11
modules/flake/packages/docs/package.nix
+2
-11
modules/flake/packages/docs/package.nix
···
1
{
2
stdenvNoCC,
3
mdbook,
4
-
mdbook-alerts,
5
fetchurl,
6
simple-http-server,
7
writeShellApplication,
···
16
17
src = self + /docs;
18
19
-
nativeBuildInputs = [
20
-
mdbook
21
-
mdbook-alerts
22
-
];
23
24
buildPhase = ''
25
runHook preBuild
26
27
mkdir -p theme
28
cp ${finalAttrs.passthru.catppuccin-mdbook} theme/catppuccin.css
29
-
cp ${finalAttrs.passthru.catppuccin-alerts} theme/catppuccin-alerts.css
30
31
cp -r ${libdoc} src/lib
32
···
45
'';
46
47
passthru = {
48
catppuccin-mdbook = fetchurl {
49
url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin.css";
50
hash = "sha256-WSl6UaRfx2jwcDg/ZlDlRbB5zwBD7YIuHHPwFj5ldKM=";
51
-
};
52
-
53
-
catppuccin-alerts = fetchurl {
54
-
url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin-alerts.css";
55
-
hash = "sha256-TnOuFi/0LeIj9pwg9dgfyDpd1oZVCN18dvjp8uB+K78=";
56
};
57
58
serve = writeShellApplication {
···
1
{
2
stdenvNoCC,
3
mdbook,
4
fetchurl,
5
simple-http-server,
6
writeShellApplication,
···
15
16
src = self + /docs;
17
18
+
nativeBuildInputs = [ mdbook ];
19
20
buildPhase = ''
21
runHook preBuild
22
23
mkdir -p theme
24
cp ${finalAttrs.passthru.catppuccin-mdbook} theme/catppuccin.css
25
26
cp -r ${libdoc} src/lib
27
···
40
'';
41
42
passthru = {
43
+
# TODO: update me when a new version is released
44
catppuccin-mdbook = fetchurl {
45
url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin.css";
46
hash = "sha256-WSl6UaRfx2jwcDg/ZlDlRbB5zwBD7YIuHHPwFj5ldKM=";
47
};
48
49
serve = writeShellApplication {
+1
-1
modules/nixos/boot/generic.nix
+1
-1
modules/nixos/boot/generic.nix
+1
-1
modules/nixos/hardware/cpu/default.nix
+1
-1
modules/nixos/hardware/cpu/default.nix
+2
modules/nixos/hardware/gpu/nvidia.nix
+2
modules/nixos/hardware/gpu/nvidia.nix
+17
-15
modules/nixos/networking/firewall.nix
+17
-15
modules/nixos/networking/firewall.nix
···
1
{
2
lib,
3
-
pkgs,
4
config,
5
...
6
}:
···
16
# inactive until opensnitch UI is opened
17
# services.opensnitch.enable = device.type != "server";
18
19
-
networking.firewall = {
20
-
enable = true;
21
-
package = pkgs.iptables;
22
23
-
allowedTCPPorts = [ ];
24
-
allowedUDPPorts = [ ];
25
26
-
allowedTCPPortRanges = [ ];
27
-
allowedUDPPortRanges = [ ];
28
29
-
# allow servers to be pinnged, but not our clients
30
-
allowPing = config.garden.profiles.server.enable;
31
32
-
# make a much smaller and easier to read log
33
-
logReversePathDrops = true;
34
-
logRefusedConnections = false;
35
36
-
# Don't filter DHCP packets, according to nixops-libvirtd
37
-
checkReversePath = mkForce false;
38
};
39
};
40
}
···
1
{
2
lib,
3
config,
4
...
5
}:
···
15
# inactive until opensnitch UI is opened
16
# services.opensnitch.enable = device.type != "server";
17
18
+
networking = {
19
+
nftables.enable = true;
20
21
+
firewall = {
22
+
enable = true;
23
24
+
allowedTCPPorts = [ ];
25
+
allowedUDPPorts = [ ];
26
27
+
allowedTCPPortRanges = [ ];
28
+
allowedUDPPortRanges = [ ];
29
30
+
# allow servers to be pinnged, but not our clients
31
+
allowPing = config.garden.profiles.server.enable;
32
33
+
# make a much smaller and easier to read log
34
+
logReversePathDrops = true;
35
+
logRefusedConnections = false;
36
+
37
+
# Don't filter DHCP packets, according to nixops-libvirtd
38
+
checkReversePath = mkForce false;
39
+
};
40
};
41
};
42
}
+2
-2
modules/nixos/networking/networkmanager.nix
+2
-2
modules/nixos/networking/networkmanager.nix
+1
-2
modules/nixos/networking/systemd.nix
+1
-2
modules/nixos/networking/systemd.nix
+4
-1
modules/nixos/networking/tailscale.nix
+4
-1
modules/nixos/networking/tailscale.nix
+49
-16
modules/nixos/nix.nix
+49
-16
modules/nixos/nix.nix
···
1
{
2
-
nix = {
3
-
# set the nix store to clean every Monday at 3am
4
-
gc.dates = "Mon *-*-* 03:00";
5
6
-
# automatically optimize /nix/store by removing hard links
7
-
optimise = {
8
-
automatic = true;
9
-
dates = [ "04:00" ];
10
-
};
11
12
-
# Make builds run with a low priority, keeping the system fast
13
-
# daemonCPUSchedPolicy = "idle";
14
-
# daemonIOSchedClass = "idle";
15
-
# daemonIOSchedPriority = 7;
16
17
-
# set the build dir to /var/tmp to avoid issues on tmpfs
18
-
# https://github.com/NixOS/nixpkgs/issues/293114#issuecomment-2663470083
19
-
settings.build-dir = "/var/tmp";
20
-
};
21
}
···
1
{
2
+
lib,
3
+
config,
4
+
options,
5
+
modulesPath,
6
+
...
7
+
}:
8
+
let
9
+
lixModuleMerged = lib.pathExists "${modulesPath}/programs/lix.nix";
10
+
nixDaemonCfg = config.systemd.services.nix-daemon;
11
+
in
12
+
{
13
+
config = lib.mkMerge [
14
+
{
15
+
nix = {
16
+
# set the nix store to clean every Monday at 3am
17
+
gc.dates = "Mon *-*-* 03:00";
18
+
19
+
# automatically optimize /nix/store by removing hard links
20
+
optimise = {
21
+
automatic = true;
22
+
dates = [ "04:00" ];
23
+
};
24
25
+
# Make builds run with a low priority, keeping the system fast
26
+
# daemonCPUSchedPolicy = "idle";
27
+
# daemonIOSchedClass = "idle";
28
+
# daemonIOSchedPriority = 7;
29
+
30
+
# set the build dir to /var/tmp to avoid issues on tmpfs
31
+
# https://github.com/NixOS/nixpkgs/issues/293114#issuecomment-2663470083
32
+
settings.build-dir = "/var/tmp";
33
+
};
34
+
}
35
36
+
# https://github.com/NixOS/nixpkgs/pull/469067
37
+
(lib.mkIf (!lixModuleMerged) {
38
+
systemd.services."nix-daemon@" = {
39
+
path = lib.subtractLists (options.systemd.services.type.getSubOptions "").path.value nixDaemonCfg.path;
40
+
environment = lib.filterAttrs (n: _v: n != "PATH") nixDaemonCfg.environment;
41
+
inherit (nixDaemonCfg) serviceConfig unitConfig;
42
+
stopIfChanged = false;
43
+
restartIfChanged = false;
44
+
};
45
46
+
# stc can't restart socket units. it can only reload them, but reloading sockets is in invalid operation!
47
+
systemd.services.lix-daemon-socket-permissions = {
48
+
overrideStrategy = "asDropin";
49
+
inherit (nixDaemonCfg) restartTriggers;
50
+
stopIfChanged = false;
51
+
};
52
+
})
53
+
];
54
}
+1
-1
modules/nixos/services/akkoma/default.nix
+1
-1
modules/nixos/services/akkoma/default.nix
+89
modules/nixos/services/arr.nix
+89
modules/nixos/services/arr.nix
···
···
1
+
{ lib, config, ... }:
2
+
let
3
+
inherit (lib)
4
+
mkEnableOption
5
+
mkOption
6
+
types
7
+
genAttrs
8
+
;
9
+
10
+
cfg = config.garden.services.arr;
11
+
in
12
+
{
13
+
options.garden.services.arr = {
14
+
enable = mkEnableOption "arr services";
15
+
16
+
mediaDir = mkOption {
17
+
type = types.str;
18
+
default = "/media";
19
+
description = "Directory for storing media files managed by arr services";
20
+
};
21
+
22
+
contentDir = mkOption {
23
+
type = types.str;
24
+
default = "${cfg.mediaDir}/content";
25
+
defaultText = "\${cfg.mediaDir}/content";
26
+
description = "Directory for storing application data for arr services";
27
+
};
28
+
29
+
mediaOwner = mkOption {
30
+
type = types.str;
31
+
default = "root";
32
+
description = "User that owns the media and content directories";
33
+
};
34
+
35
+
mediaGroup = mkOption {
36
+
type = types.str;
37
+
default = "media";
38
+
description = "Group that owns the media and content directories";
39
+
};
40
+
41
+
openFirewall = mkEnableOption "open the firewall for the arr services" // {
42
+
default = true;
43
+
defaultText = "true";
44
+
};
45
+
};
46
+
47
+
config = lib.mkIf cfg.enable {
48
+
garden.services = {
49
+
jellyfin.enable = true;
50
+
sonarr.enable = true;
51
+
radarr.enable = true;
52
+
prowlarr.enable = true;
53
+
qbittorrent.enable = true;
54
+
};
55
+
56
+
users.groups.media = { };
57
+
58
+
systemd.tmpfiles.settings = {
59
+
"media-content-dirs" =
60
+
genAttrs
61
+
[
62
+
"${cfg.mediaDir}/content"
63
+
"${cfg.mediaDir}/content/tv"
64
+
"${cfg.mediaDir}/content/movies"
65
+
"${cfg.mediaDir}/content/home"
66
+
]
67
+
(_: {
68
+
d = {
69
+
mode = "0775";
70
+
user = cfg.mediaOwner;
71
+
group = cfg.mediaGroup;
72
+
};
73
+
});
74
+
75
+
"media-downloads-dirs" =
76
+
genAttrs
77
+
[
78
+
"${cfg.mediaDir}/downloads"
79
+
]
80
+
(_: {
81
+
d = {
82
+
mode = "0755";
83
+
user = "qbittorrent";
84
+
group = "media";
85
+
};
86
+
});
87
+
};
88
+
};
89
+
}
+5
modules/nixos/services/default.nix
+5
modules/nixos/services/default.nix
···
3
# keep-sorted start
4
./akkoma
5
./anubis.nix
6
+
./arr.nix
7
./attic.nix
8
./atuin.nix
9
./blahaj.nix
···
24
./piper.nix
25
./port-collector.nix
26
./postgresql.nix
27
+
./prowlarr.nix
28
+
./qbittorent.nix
29
+
./radarr.nix
30
./redis.nix
31
+
./sonarr.nix
32
./uptime-kuma.nix
33
./vaultwarden.nix
34
./wakapi.nix
+2
modules/nixos/services/jellyfin.nix
+2
modules/nixos/services/jellyfin.nix
+26
modules/nixos/services/pds/default.nix
+26
modules/nixos/services/pds/default.nix
···
44
enable = true;
45
pdsadmin.enable = true;
46
47
+
package = pkgs.bluesky-pds.overrideAttrs (
48
+
finalAttrs: _: {
49
+
src = pkgs.fetchFromGitHub {
50
+
owner = "isabelroses";
51
+
repo = "pds-fork";
52
+
rev = "66c026acfcfd290ea962dd4d03379f0990d80071";
53
+
hash = "sha256-x+oh3YKcz2eWkAqg+jZKrh35UoGI6dLQXcEiGItTHKc=";
54
+
};
55
+
56
+
pnpmDeps = pkgs.fetchPnpmDeps {
57
+
inherit (finalAttrs)
58
+
pname
59
+
version
60
+
src
61
+
sourceRoot
62
+
;
63
+
pnpm = pkgs.pnpm_9;
64
+
fetcherVersion = 2;
65
+
hash = "sha256-4qKWkINpUHzatiMa7ZNYp1NauU2641W0jHDjmRL9ipI=";
66
+
};
67
+
}
68
+
);
69
+
70
environmentFiles = [ config.sops.secrets.pds-env.path ];
71
72
settings = {
···
98
PDS_OAUTH_PROVIDER_ERROR_COLOR = "#F6598E";
99
100
PDS_SERVICE_HANDLE_DOMAINS = ".tgirl.beauty";
101
+
102
+
# custom session duration: 30 days
103
+
PDS_OAUTH_AUTHENTICATION_MAX_AGE = "2592000000";
104
};
105
};
106
+1
-1
modules/nixos/services/port-collector.nix
+1
-1
modules/nixos/services/port-collector.nix
+30
modules/nixos/services/prowlarr.nix
+30
modules/nixos/services/prowlarr.nix
···
···
1
+
{
2
+
lib,
3
+
self,
4
+
config,
5
+
...
6
+
}:
7
+
let
8
+
inherit (lib) mkIf;
9
+
inherit (self.lib) mkServiceOption;
10
+
11
+
cfg = config.garden.services.prowlarr;
12
+
in
13
+
{
14
+
options.garden.services.prowlarr = mkServiceOption "prowlarr" {
15
+
port = 3022;
16
+
};
17
+
18
+
config = mkIf config.garden.services.prowlarr.enable {
19
+
services.prowlarr = {
20
+
enable = true;
21
+
inherit (config.garden.services.arr) openFirewall;
22
+
settings.server.port = cfg.port;
23
+
};
24
+
25
+
systemd.services.prowlarr.serviceConfig = {
26
+
User = "prowlarr";
27
+
Group = "media";
28
+
};
29
+
};
30
+
}
+74
modules/nixos/services/qbittorent.nix
+74
modules/nixos/services/qbittorent.nix
···
···
1
+
{
2
+
lib,
3
+
self,
4
+
config,
5
+
...
6
+
}:
7
+
let
8
+
inherit (lib) mkIf;
9
+
inherit (self.lib) mkServiceOption mkSecret;
10
+
11
+
cfg = config.garden.services.qbittorrent;
12
+
inherit (config.garden.services) arr;
13
+
in
14
+
{
15
+
# <https://github.com/NixOS/nixpkgs/pull/472934>
16
+
imports = [ ./qui.nix ];
17
+
18
+
options.garden.services.qbittorrent = mkServiceOption "qbittorrent" {
19
+
port = 3019;
20
+
host = "0.0.0.0";
21
+
};
22
+
23
+
config = mkIf cfg.enable {
24
+
networking.firewall.allowedTCPPorts = [
25
+
cfg.port
26
+
config.services.qbittorrent.torrentingPort
27
+
];
28
+
29
+
sops.secrets.qui = mkSecret {
30
+
file = "qui";
31
+
key = "qui";
32
+
};
33
+
34
+
services = {
35
+
qui = {
36
+
enable = true;
37
+
secretFile = config.sops.secrets.qui.path;
38
+
settings = {
39
+
inherit (cfg) host port;
40
+
checkForUpdates = false;
41
+
};
42
+
};
43
+
44
+
qbittorrent = {
45
+
enable = true;
46
+
group = arr.mediaGroup;
47
+
48
+
webuiPort = 4019;
49
+
torrentingPort = 43125;
50
+
51
+
serverConfig = {
52
+
LegalNotice.Accepted = true;
53
+
54
+
BitTorrent = {
55
+
"Session\\BTProtocol" = "TCP";
56
+
"Session\\DHTEnabled" = false;
57
+
"Session\\LSDEnabled" = false;
58
+
"Session\\PeXEnabled" = false;
59
+
"Session\\QueueingSystemEnabled" = false;
60
+
"Session\\DefaultSavePath" = "/media/downloads";
61
+
};
62
+
63
+
Preferences = {
64
+
"WebUI\\LocalHostAuth" = false;
65
+
66
+
# generate with <https://codeberg.org/feathecutie/qbittorrent_password>
67
+
"WebUI\\Password_PBKDF2" =
68
+
"@ByteArray(2PRai2N/GL+Lt+VDdda0kw==:X4+iM6WwTPXExbBwJGcrHqxVsEN0cBxrhACiTMbEeQ6RjTdbfnJSB+CyTn3r1iJzEMMa0/XZzq2U1cG4O6AZZg==)";
69
+
};
70
+
};
71
+
};
72
+
};
73
+
};
74
+
}
+192
modules/nixos/services/qui.nix
+192
modules/nixos/services/qui.nix
···
···
1
+
# https://github.com/NixOS/nixpkgs/pull/472934
2
+
{
3
+
config,
4
+
lib,
5
+
pkgs,
6
+
...
7
+
}:
8
+
9
+
let
10
+
inherit (lib)
11
+
getExe
12
+
maintainers
13
+
mkEnableOption
14
+
mkIf
15
+
mkOption
16
+
mkPackageOption
17
+
;
18
+
inherit (lib.types)
19
+
bool
20
+
path
21
+
port
22
+
str
23
+
submodule
24
+
;
25
+
cfg = config.services.qui;
26
+
27
+
stateDir = "/var/lib/qui";
28
+
configFormat = pkgs.formats.toml { };
29
+
configFile = configFormat.generate "qui.toml" cfg.settings;
30
+
in
31
+
{
32
+
options = {
33
+
services.qui = {
34
+
enable = mkEnableOption "qui";
35
+
36
+
package = mkPackageOption pkgs "qui" { };
37
+
38
+
user = mkOption {
39
+
type = str;
40
+
default = "qui";
41
+
description = "User to run qui as.";
42
+
};
43
+
44
+
group = mkOption {
45
+
type = str;
46
+
default = "qui";
47
+
example = "torrents";
48
+
description = "Group to run qui as.";
49
+
};
50
+
51
+
openFirewall = mkOption {
52
+
type = bool;
53
+
default = false;
54
+
description = "Whether or not to open ports in the firewall for qui.";
55
+
};
56
+
57
+
secretFile = mkOption {
58
+
type = path;
59
+
example = "/run/secrets/qui-session.txt";
60
+
description = ''
61
+
Path to a file that contains the session secret. The session secret
62
+
can be generated with `openssl rand -hex 32`.
63
+
'';
64
+
};
65
+
66
+
settings = mkOption {
67
+
default = { };
68
+
example = {
69
+
port = 7777;
70
+
logLevel = "DEBUG";
71
+
metricsEnabled = true;
72
+
};
73
+
type = submodule {
74
+
freeformType = configFormat.type;
75
+
options = {
76
+
host = mkOption {
77
+
type = str;
78
+
default = "127.0.0.1";
79
+
description = "The host address qui listens on.";
80
+
};
81
+
82
+
port = mkOption {
83
+
type = port;
84
+
default = 7476;
85
+
description = "The port qui listens on.";
86
+
};
87
+
};
88
+
};
89
+
description = ''
90
+
qui configuration options.
91
+
92
+
Refer to the [template config](https://github.com/autobrr/qui/blob/main/internal/config/config.go)
93
+
in the source code for the available options.
94
+
The documentation contains the available [environment variables](https://getqui.com/docs/configuration/environment/),
95
+
this can be used to get an overview.
96
+
'';
97
+
};
98
+
99
+
};
100
+
};
101
+
102
+
config = mkIf cfg.enable {
103
+
assertions = [
104
+
{
105
+
assertion = !(cfg.settings ? sessionSecret);
106
+
message = ''
107
+
Session secrets should not be passed via settings, as
108
+
these are stored in the world-readable nix store.
109
+
110
+
Use the secretFile option instead.'';
111
+
}
112
+
];
113
+
114
+
systemd.services.qui = {
115
+
description = "qui: alternative qBittorrent webUI";
116
+
after = [ "network-online.target" ];
117
+
wants = [ "network-online.target" ];
118
+
wantedBy = [ "multi-user.target" ];
119
+
120
+
serviceConfig = {
121
+
Type = "simple";
122
+
User = cfg.user;
123
+
Group = cfg.group;
124
+
125
+
LoadCredential = "sessionSecret:${cfg.secretFile}";
126
+
Environment = [ "QUI__SESSION_SECRET_FILE=%d/sessionSecret" ];
127
+
StateDirectory = "qui";
128
+
129
+
ExecStartPre = ''
130
+
${pkgs.coreutils}/bin/install -m 600 '${configFile}' '%S/qui/config.toml'
131
+
'';
132
+
ExecStart = "${getExe cfg.package} serve --config-dir %S/qui";
133
+
Restart = "on-failure";
134
+
135
+
# Based on qbittorrent and nemorosa hardening settings
136
+
# Similar to what systemd hardening helper suggests
137
+
CapabilityBoundingSet = "";
138
+
LockPersonality = true;
139
+
MemoryDenyWriteExecute = true;
140
+
NoNewPrivileges = true;
141
+
PrivateDevices = true;
142
+
PrivateNetwork = false;
143
+
PrivateTmp = true;
144
+
PrivateUsers = true;
145
+
ProcSubset = "pid";
146
+
ProtectClock = true;
147
+
ProtectControlGroups = true;
148
+
ProtectHome = "yes";
149
+
ProtectHostname = true;
150
+
ProtectKernelLogs = true;
151
+
ProtectKernelModules = true;
152
+
ProtectKernelTunables = true;
153
+
ProtectProc = "invisible";
154
+
# This should allow for hardlinking to torrent client files
155
+
ProtectSystem = "full";
156
+
RemoveIPC = true;
157
+
RestrictAddressFamilies = [
158
+
"AF_INET"
159
+
"AF_INET6"
160
+
"AF_NETLINK"
161
+
"AF_UNIX"
162
+
];
163
+
RestrictNamespaces = true;
164
+
RestrictRealtime = true;
165
+
RestrictSUIDSGID = true;
166
+
SystemCallArchitectures = "native";
167
+
SystemCallFilter = [ "@system-service" ];
168
+
};
169
+
};
170
+
171
+
networking.firewall = mkIf cfg.openFirewall {
172
+
allowedTCPPorts = [ cfg.settings.port ];
173
+
};
174
+
175
+
users = {
176
+
users = mkIf (cfg.user == "qui") {
177
+
qui = {
178
+
inherit (cfg) group;
179
+
description = "qui user";
180
+
isSystemUser = true;
181
+
home = stateDir;
182
+
};
183
+
};
184
+
185
+
groups = mkIf (cfg.group == "qui") {
186
+
qui = { };
187
+
};
188
+
};
189
+
};
190
+
191
+
meta.maintainers = with maintainers; [ undefined-landmark ];
192
+
}
+27
modules/nixos/services/radarr.nix
+27
modules/nixos/services/radarr.nix
···
···
1
+
{
2
+
lib,
3
+
self,
4
+
config,
5
+
...
6
+
}:
7
+
let
8
+
inherit (lib) mkIf;
9
+
inherit (self.lib) mkServiceOption;
10
+
11
+
cfg = config.garden.services.radarr;
12
+
in
13
+
{
14
+
options.garden.services.radarr = mkServiceOption "radarr" {
15
+
port = 3021;
16
+
};
17
+
18
+
config = mkIf config.garden.services.radarr.enable {
19
+
services.radarr = {
20
+
inherit (cfg) enable;
21
+
group = "media";
22
+
dataDir = "/srv/storage/ranarr";
23
+
inherit (config.garden.services.arr) openFirewall;
24
+
settings.server.port = cfg.port;
25
+
};
26
+
};
27
+
}
+27
modules/nixos/services/sonarr.nix
+27
modules/nixos/services/sonarr.nix
···
···
1
+
{
2
+
lib,
3
+
self,
4
+
config,
5
+
...
6
+
}:
7
+
let
8
+
inherit (lib) mkIf;
9
+
inherit (self.lib) mkServiceOption;
10
+
11
+
cfg = config.garden.services.sonarr;
12
+
in
13
+
{
14
+
options.garden.services.sonarr = mkServiceOption "sonarr" {
15
+
port = 3020;
16
+
};
17
+
18
+
config = mkIf config.garden.services.sonarr.enable {
19
+
services.sonarr = {
20
+
inherit (cfg) enable;
21
+
group = "media";
22
+
dataDir = "/srv/storage/sonarr";
23
+
inherit (config.garden.services.arr) openFirewall;
24
+
settings.server.port = cfg.port;
25
+
};
26
+
};
27
+
}
+2
-2
modules/nixos/system/scheduler.nix
+2
-2
modules/nixos/system/scheduler.nix
+3
-3
secrets/services/piper.yaml
+3
-3
secrets/services/piper.yaml
···
1
-
env: ENC[AES256_GCM,data:scYfFEXl6esjrYaTLbFbeqQVnOw6h2ZzZVQ2EUsCxnYzaI+rhCTVqkrqXgowFAILWwQOiYM7B5ukgqTytiB5h6C2Bh/xa/VdMOPdY3EXqRed7NzZC96LZt3+dKDKKDbkw1wyddWvX/phQFk/cHq6BR5CQZfQ7nPXRr2ofZGoK/3m+GhouyJq2I4RHHRGxv0WXYniHkR9zgdc+myg1KsFphu9XaoR4WN/uaGACv59pguMJWRcBzr+wKPxdWtrzb4vTIVQiF8wpTSdWBmnwmKq29y/pBSNivDdhWB5N1/VHGFDRge5RzxXCg98Hv4fegebx4+YPiVlwoC2peGSi/q8EeNLFES+waywaKmbQ1r54AAFqGnXIIp16Ib5lCUa+VwY2jQl9c+QZOcNEJkVZ6jWmuoUguO1q2y4WbLvcaWksjRXJHs/NOsQoF4=,iv:b8O+3GksPP4hTuH57IxgAeYe6MAiYGO83nJKn7MVn6Q=,tag:V1dFrgABjL6SXN7af1DtgA==,type:str]
2
sops:
3
age:
4
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2
···
71
lm9UxzoNda1OtptX52t1sWVYkx1pwvlTbTmuNy/8uVufZVekdPBz+pywuJbSJlh2
72
bH8K6Q==
73
-----END AGE ENCRYPTED FILE-----
74
-
lastmodified: "2025-11-14T16:08:46Z"
75
-
mac: ENC[AES256_GCM,data:jd968uS+JcdIg1eDyo1ssHSVw/cxXVwuIDE6YdJrZCHpCf/XSHIWvlu6+L3LILixhV45eMdI81GKCMR3uBnXaXf6SOEPWZ2BJc9cT+p1seAPR1surmVA8d2GGKSWuGmi7mFW30kLsDJhRk1pybKpIHf/Jxdgx0cuh95DEoOtoPg=,iv:+XUddLha4hj68zCcnHiH5aEJU6buzKV+FNUO8ZXQofY=,tag:wlmooDkxQJyPD883cwRdSA==,type:str]
76
unencrypted_suffix: _unencrypted
77
version: 3.11.0
···
1
+
env: ENC[AES256_GCM,data:/bJSpMnpO/P5pen9RKdsRF47veCmkRstBFUwbdyu+fp3UDayUrL2u+/397XQQxBQYl266wJOQB7sjJ5lsOI/yRiM/89yv4zow7Qh/akPnsYyhxiLAjdIdJHFzh6tQMBZiHEHNF16edhejKMgIz0qOIvIQZu0yHX6ozvWfc7KKzYH3CUohbhIjgjqbJfzP/YMFNnqu6Aa4mlksprCeVPg/ZX3vf4z/DS9vIhmUbkc91064gjytMq3EFZvVt01aD3Wror/ApBCtCZ1Pt8zAbH/mrQd2Dsx9pOKueqYhTGcF6nsU5fxkKG62K0nK3xGOS2vOpl4k9m220k5BF1CbSKbWXFBafvtvorYbBBK+N4caBKI6DQz66/eA75D0DwA0KbBKadboqzRZOWFsvqQwIuiqnkzJottJZ2swGbyVjhLsAQEmZI/b6Ie0I0=,iv:Z1gnEHoJ8eBDWn1IorUn/pT+ghxrB7+nk9boHx7efWg=,tag:zNJRN/JeWP98TDxfFDdElw==,type:str]
2
sops:
3
age:
4
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2
···
71
lm9UxzoNda1OtptX52t1sWVYkx1pwvlTbTmuNy/8uVufZVekdPBz+pywuJbSJlh2
72
bH8K6Q==
73
-----END AGE ENCRYPTED FILE-----
74
+
lastmodified: "2026-01-07T01:01:47Z"
75
+
mac: ENC[AES256_GCM,data:ThIfvJcDdWqprQ0i66bTy29QOcPzWjIYCB8aYhdeEIPzBOjQDKgVh3YNXVaMC7/2tBJmC/IQbxvJ/pIPP/UICSxFvqS+Ta6wipRlTs2A23gaoaVXwj7nY8oMwUH4w04NbWuNFavQ/gnKYBEOmqDBsLrZ6+v5IPxfxx/fRapMKMM=,iv:OYGE7Ja0X6WNHswwYwQ8VsDSlb3U/sm47tsTnJ5wspc=,tag:5Pcfd0/eSL36rFNp8sH6qA==,type:str]
76
unencrypted_suffix: _unencrypted
77
version: 3.11.0
+77
secrets/services/qui.yaml
+77
secrets/services/qui.yaml
···
···
1
+
qui: ENC[AES256_GCM,data:SkU/OLmyCUPRg5DlGaiEtQyLVoilLUGXRQM+m+LbqQAmwBv7LhDghmNAZgZ2XWoc+6yMfC7UjfZSFfLXGN3xQw==,iv:Ez9tZX7Hzb4VHF4NisatdoNNr+1eq0nqWGqLnfpUp7U=,tag:f/w121+Ia/odoofgLNCXjw==,type:str]
2
+
sops:
3
+
age:
4
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2
5
+
enc: |
6
+
-----BEGIN AGE ENCRYPTED FILE-----
7
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDk1NDQzZyA3M1RP
8
+
YkF0Sy9GelVkVHZPMkNMN3pVVitGMkxJYlkzOXZnMGhXUTFSb1VJCmVYWXRJNDBw
9
+
UlpQbU55NnhFcEdMUzA4V0ZNS0VGL1p3c1d0MXhzcjRJUUkKLS0tIDZ6cFlhU1ha
10
+
RWcwTlU2cUoxZkREZFBXZlgwM2pJeHE3ZWlzUS9LRklETVUKsXMTiH+E7+kXlNdZ
11
+
YfIpYCQhDLIeLBjsoz6dcJcXKskv8Snyb8akTLXKpU2Xt7XTxus0gODbk9xEcaik
12
+
6TL9dw==
13
+
-----END AGE ENCRYPTED FILE-----
14
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAomF8O3ZqZBpLRlAkS1+FwRllSMrREHtndw07trrfcA
15
+
enc: |
16
+
-----BEGIN AGE ENCRYPTED FILE-----
17
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IENXL2NhQSAvRDZN
18
+
SWtzeHduQzMxQTIrSHNUdEpwMmJpRVRmSnMrRUhuRWhCbzQ5c1FzCklGMWdINng1
19
+
SkhQMDBKemlKS1ZIK1dpZTkycXpCeVRNRVpZeGg5Rm9mOVEKLS0tIGNhNVpCMzV3
20
+
OHcvQ3FOalo3QzRPT3FDbnF3a3JtYndkMDZzOERsL1E3aXcKVCElom5e6Wy/6/1s
21
+
n0acAXybuuh0M3VOU37Vod5VDVFAhBNe5cRSheiMjsQz1kN5X1lcf6/XK8E8vntz
22
+
3LffQw==
23
+
-----END AGE ENCRYPTED FILE-----
24
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKV3N2r1cX3kZMHxuJYaFsjWJRtik/2b/KZ5ru38IqAT
25
+
enc: |
26
+
-----BEGIN AGE ENCRYPTED FILE-----
27
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFh6aDdpUSA4cUkw
28
+
UVVycHdyd3dUSHJCdHY5M2UrR1JvdmhleEp3WkMySDBYdk01LzN3Cks2VUJUb1FM
29
+
U1FpQ012cS80WTZsNmNSckRINUR1b0Ivb0R2UXRUT1MwSEkKLS0tIHpKL1BaLy9B
30
+
cmlZa3gxM1NrZ2h6aEQ1Z3FFRjU0UHNjRWVOSTVyTVhYcEEKVvHwiJ6U8E/pJmzb
31
+
25xhRR/XCAvXqL5NUb49egdQQpn106AR2D4Ph+ERoKw8XGmyIo4Oybv6RwbwLinz
32
+
r31UIQ==
33
+
-----END AGE ENCRYPTED FILE-----
34
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN2pZDnEwnOsrf9aPUVkBigeFwAirWPVGliSs5AgcEFr
35
+
enc: |
36
+
-----BEGIN AGE ENCRYPTED FILE-----
37
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFZTUGdBUSBsM0dQ
38
+
SGhibGo1Wm96bWVDY21SOVc2S2p0dyswQittYWNhT3IycVlMcmlvCldScjU4T3JN
39
+
MEVlWXZtQkRkN0pqNVRUVmFYOG9XcmUxaUtKZVVXVzJpK1UKLS0tIDRmc1ZhMWsx
40
+
QTlGT1ZZOUtvRnNtOG5CUVI1cFU4Zkw0U25SemFGOE5Md00Kk2VoPff3AH3YEA2p
41
+
6WyT4QtaVbqdpTBJeuUpE20yG1SjeEsDAwEifnpsZ9rGgCum/lREWk18mJHw3GUk
42
+
sm+zQg==
43
+
-----END AGE ENCRYPTED FILE-----
44
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICOXg0Qq1bdnrYp+KL6tfLZsMf3KHFfpaP2GMjwmpug2
45
+
enc: |
46
+
-----BEGIN AGE ENCRYPTED FILE-----
47
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGRaVG9vQSBBdnkw
48
+
M3V6ci80M3AyaGRTZU1iSkV3VlBXU3Z4QTNoWUhYaEI4cEtORVI4CjY3TTM2L3VR
49
+
Ly9iN09yb3BxNHlYaG1wY1QxSDg0elBVeFNJTjMyRnRPSncKLS0tIDhOVW1xWlpR
50
+
Y1U0YVAvQkpCYjFDOEZOSHJnbG1BUGVJTCtTSWZxUnlHQTAKCRGKSvk2nVgJ/r/C
51
+
ULwpJscVGX7Pb8i/toimZ8D0X303Gu237BgTO9TQnD8yCTutUym04vOqWuppG1ra
52
+
pYcTLg==
53
+
-----END AGE ENCRYPTED FILE-----
54
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFecrTXoINyGQkeOLQetR5BC6s9VlJHlCbY02WY5FAnJ
55
+
enc: |
56
+
-----BEGIN AGE ENCRYPTED FILE-----
57
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDgrRFVnUSBmbmtE
58
+
V0l1eFg2ZDJhMW1CVE15b0dDR3BpNjlKYWYvbk5VcFBLM0QrZmxjClhsMkN3bHhv
59
+
eXpMeldRTVIzNnp6ZEUxZnVuU29TUnJCQitPdHgwMzVMTmsKLS0tIFUyWVlIQXFq
60
+
QngvUWZNMEdYZ01tekwzcERsblJwaExmVk5lMStCbUR6WHcKMzUdkSaFSUKZOhvN
61
+
bPvciYd9h9Qq0dyh0KRK+T52T8U38izSdC5XSc4qWXmCbjac4FsLMgHPIjrORbnU
62
+
uxBsNA==
63
+
-----END AGE ENCRYPTED FILE-----
64
+
- recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErl+4GkgJOoaUUOU9gblWJ6FpIls/adHx0YpOUXXxJA
65
+
enc: |
66
+
-----BEGIN AGE ENCRYPTED FILE-----
67
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEptYzFtUSBQZWdY
68
+
SzFxbHV4dUR5ak1kUExRNWwvM1o4dDd1U2Z4WlJuejcrdkRGaVFRCi81d09keU5N
69
+
aTQzNWo0MDk3aTlYZlN0RWVDeERiOGRaazNFZTE3K3p3MlkKLS0tIFlWQ2hodzNW
70
+
YU5yK2tIUVFoazRSbW5FRUVyR05JWkNteXova05MVkh1Z3MK3cdep/MFRjRABDIk
71
+
rpjZna7G7eTZ8jG3L+x9GovzgXijUA7nsw7qY6Igzwq1+Mrdupgv7GbPt2+57+88
72
+
fcmFIg==
73
+
-----END AGE ENCRYPTED FILE-----
74
+
lastmodified: "2026-01-11T15:37:29Z"
75
+
mac: ENC[AES256_GCM,data:7wy+KnaJiEXFNYVSCZk4k4sK1gBt2gbjxn6yvPwEbbE9F0E6Cx1fIa5alQEXTpyWzyzb0brGbrHI9HunUjf/xYVfF/ebbI5luy7sj9oJgfPLsYaZ4hVhZ3NnH6VFdrkFUF565Y5uf9TVG3jp8iNYM5JesIyszugtasF5GAu27Yo=,iv:pOza+xfiexKeqUE1wC4/6cQTIBDKOf0ZbU1yj4kS/Sw=,tag:LSA5l9DKYMGD7ESuFAH8Rg==,type:str]
76
+
unencrypted_suffix: _unencrypted
77
+
version: 3.11.0
+1
-3
systems/amaterasu/default.nix
+1
-3
systems/amaterasu/default.nix
+10
-11
systems/athena/default.nix
+10
-11
systems/athena/default.nix
···
1
{
2
imports = [ ./hardware.nix ];
3
···
19
};
20
};
21
22
-
system = {
23
-
boot = {
24
-
loader = "systemd-boot";
25
-
secureBoot = false;
26
-
loadRecommendedModules = true;
27
-
enableKernelTweaks = true;
28
-
initrd.enableTweaks = true;
29
-
};
30
-
31
-
security.auditd.enable = true;
32
};
33
34
services = {
35
cloudflared.enable = true;
36
immich.enable = true;
37
-
jellyfin.enable = true;
38
borgbackup.enable = true;
39
};
40
};
41
}
···
1
+
{ lib, ... }:
2
{
3
imports = [ ./hardware.nix ];
4
···
20
};
21
};
22
23
+
system.boot = {
24
+
loader = "systemd-boot";
25
+
secureBoot = false;
26
+
loadRecommendedModules = true;
27
+
enableKernelTweaks = true;
28
+
initrd.enableTweaks = true;
29
};
30
31
services = {
32
cloudflared.enable = true;
33
immich.enable = true;
34
+
arr.enable = true;
35
borgbackup.enable = true;
36
};
37
};
38
+
39
+
services.smartd.enable = lib.mkForce false;
40
}
+5
systems/athena/hardware.nix
+5
systems/athena/hardware.nix
+1
-1
systems/skadi/default.nix
+1
-1
systems/skadi/default.nix