my over complex system configurations dotfiles.isabelroses.com/
nixos nix flake dotfiles linux

Compare changes

Choose any two refs to compare.

+3
.gitignore
··· 4 4 # Ignore nixos build outputs 5 5 result* 6 6 .direnv/ 7 + 8 + # deploy script 9 + .deploy-summary
+1 -20
README.md
··· 10 10 11 11 <br /> 12 12 13 - ![Preview image](./docs/src/images/main.png) 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> 13 + ![Preview image](./docs/src/images/main.webp) 33 14 34 15 ![image of my flakes topology](./docs/src/images/topology.png) 35 16
+8
docs/src/README.md
··· 8 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 9 </div> 10 10 11 + <br /> 12 + 13 + ![Preview image](./docs/src/images/main.webp) 14 + 15 + ![image of my flakes topology](./docs/src/images/topology.png) 16 + 11 17 ### Foreword 12 18 13 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. ··· 22 28 - Sensible defaults, so you can get started quickly 23 29 - Docs kind of 24 30 - [Catppuccin](https://github.com/catppuccin/catppuccin) everywhere. 31 + 32 + [![Star History Chart](https://api.star-history.com/svg?repos=isabelroses/dotfiles&type=Date)](https://star-history.com/#isabelroses/dotfiles&Date)
-1
docs/src/SUMMARY.md
··· 35 35 # Misc 36 36 37 37 - [mirrors](misc/mirrors.md) 38 - - [previews](misc/previews.md)
docs/src/images/blur.png

This is a binary file and will not be displayed.

docs/src/images/blur.webp

This is a binary file and will not be displayed.

docs/src/images/lightmode.png

This is a binary file and will not be displayed.

docs/src/images/lightmode.webp

This is a binary file and will not be displayed.

docs/src/images/main.png

This is a binary file and will not be displayed.

docs/src/images/main.webp

This is a binary file and will not be displayed.

docs/src/images/nvim.png

This is a binary file and will not be displayed.

docs/src/images/nvim.webp

This is a binary file and will not be displayed.

-5
docs/src/misc/previews.md
··· 1 - ![Preview image](/images/main.png) 2 - ![images/lightmode.png](/images/lightmode.png) 3 - ![images/blur](/images/blur.png) 4 - ![images/nvim](/images/nvim.png) 5 - ![image of my flakes topology](/images/topology.png)
+51 -50
flake.lock
··· 24 24 ] 25 25 }, 26 26 "locked": { 27 - "lastModified": 1765990358, 28 - "narHash": "sha256-l8x0gU8mnYaGMl+gWrsSHKBJlZWD8KXJfHTkRlFiPI0=", 27 + "lastModified": 1767967164, 28 + "narHash": "sha256-Cx4VETh9dGoQYDtWhre7g66d7SAr+h1h6f+SSHxVrck=", 29 29 "owner": "catppuccin", 30 30 "repo": "nix", 31 - "rev": "de1b60ca45a578f59f7d84c8d338b346017b2161", 31 + "rev": "e973584280e3b0e1d5b5a1a5e9948dc222c54af7", 32 32 "type": "github" 33 33 }, 34 34 "original": { ··· 39 39 }, 40 40 "crane": { 41 41 "locked": { 42 - "lastModified": 1766774972, 43 - "narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=", 42 + "lastModified": 1767461147, 43 + "narHash": "sha256-TH/xTeq/RI+DOzo+c+4F431eVuBpYVwQwBxzURe7kcI=", 44 44 "owner": "ipetkov", 45 45 "repo": "crane", 46 - "rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82", 46 + "rev": "7d59256814085fd9666a2ae3e774dc5ee216b630", 47 47 "type": "github" 48 48 }, 49 49 "original": { ··· 59 59 ] 60 60 }, 61 61 "locked": { 62 - "lastModified": 1767028240, 63 - "narHash": "sha256-0/fLUqwJ4Z774muguUyn5t8AQ6wyxlNbHexpje+5hRo=", 64 - "owner": "nix-darwin", 62 + "lastModified": 1767469770, 63 + "narHash": "sha256-Rv1kumaBqlqCvLjVYuzdA38btSyjSRvXZ8UtwalGcHo=", 64 + "owner": "isabelroses", 65 65 "repo": "nix-darwin", 66 - "rev": "c31afa6e76da9bbc7c9295e39c7de9fca1071ea1", 66 + "rev": "1d46ab42afd10adf9751ab6cfba6b426bfb17e33", 67 67 "type": "github" 68 68 }, 69 69 "original": { 70 - "owner": "nix-darwin", 70 + "owner": "isabelroses", 71 + "ref": "darwin-rebuild", 71 72 "repo": "nix-darwin", 72 73 "type": "github" 73 74 } ··· 90 91 "flake-compat": { 91 92 "flake": false, 92 93 "locked": { 93 - "lastModified": 1761588595, 94 - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", 95 - "owner": "edolstra", 94 + "lastModified": 1767039857, 95 + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", 96 + "owner": "NixOS", 96 97 "repo": "flake-compat", 97 - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", 98 + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", 98 99 "type": "github" 99 100 }, 100 101 "original": { 101 - "owner": "edolstra", 102 + "owner": "NixOS", 102 103 "repo": "flake-compat", 103 104 "type": "github" 104 105 } ··· 110 111 ] 111 112 }, 112 113 "locked": { 113 - "lastModified": 1765835352, 114 - "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", 114 + "lastModified": 1767609335, 115 + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", 115 116 "owner": "hercules-ci", 116 117 "repo": "flake-parts", 117 - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", 118 + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", 118 119 "type": "github" 119 120 }, 120 121 "original": { ··· 173 174 ] 174 175 }, 175 176 "locked": { 176 - "lastModified": 1767104570, 177 - "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", 177 + "lastModified": 1767971841, 178 + "narHash": "sha256-TwDXF4MkmjI9c3Sly9FOWXf4sPbre6ZujG87v39G1Ig=", 178 179 "owner": "nix-community", 179 180 "repo": "home-manager", 180 - "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", 181 + "rev": "0e4217b2c4827e71e2e612accccb01981c16afda", 181 182 "type": "github" 182 183 }, 183 184 "original": { ··· 211 212 ] 212 213 }, 213 214 "locked": { 214 - "lastModified": 1767230498, 215 - "narHash": "sha256-eAENy8+m5KfcD/3HCDvF8e0BLlBSd5Se3snzGkaInoI=", 215 + "lastModified": 1768007596, 216 + "narHash": "sha256-8es5mAWk+UMlo9O3pEbdLhXVk5CTs3yE7S1531aD1ZQ=", 216 217 "owner": "isabelroses", 217 218 "repo": "izlix", 218 - "rev": "d57e291079ef70c0f77ad3200b5b7db586e26922", 219 + "rev": "450899c93ac2b101b1db2061f3bddbd369728815", 219 220 "type": "github" 220 221 }, 221 222 "original": { ··· 232 233 ] 233 234 }, 234 235 "locked": { 235 - "lastModified": 1767151551, 236 - "narHash": "sha256-OevEstP+kG3nTWYi41n+PIuscIVBXDffzIQ2ew10uL8=", 236 + "lastModified": 1767756498, 237 + "narHash": "sha256-a7SYG/6uUeeyfg1Xiq/sXpRb2RXs+kQ0GKrKMVlMw+4=", 237 238 "owner": "isabelroses", 238 239 "repo": "nvim", 239 - "rev": "d87c265a3ee73dfe6eddc63c0e3cda91249d3c31", 240 + "rev": "725ef38a0ea9b5bd8218aad272bd7458d430b2be", 240 241 "type": "github" 241 242 }, 242 243 "original": { ··· 255 256 "rust-overlay": "rust-overlay" 256 257 }, 257 258 "locked": { 258 - "lastModified": 1767013031, 259 - "narHash": "sha256-p8ANXBakAtfX/aEhLbU6w0tuQe3nrBvLdHbKirJP7ug=", 259 + "lastModified": 1767697030, 260 + "narHash": "sha256-0iVZ99H3kR5h6Lhw8kDDuUc5C/k6iismeWgCS1qWTQ4=", 260 261 "owner": "nix-community", 261 262 "repo": "lanzaboote", 262 - "rev": "c2a82339373daee8cbbcad5f51f22ae6b71069e0", 263 + "rev": "657469e8f036334db768daaf7732b1174676054b", 263 264 "type": "github" 264 265 }, 265 266 "original": { ··· 291 292 }, 292 293 "nixpkgs": { 293 294 "locked": { 294 - "lastModified": 1767151656, 295 - "narHash": "sha256-enBX1DxL9cttnrJ+rzOds/GXWqSj/vcB97+X9mgiJho=", 296 - "rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55", 295 + "lastModified": 1767966113, 296 + "narHash": "sha256-mb9aE5Y/wRCZz0cTB9EOe4BEKWpXAAl5Yai4TFrEf1E=", 297 + "rev": "5f02c91314c8ba4afe83b256b023756412218535", 297 298 "type": "tarball", 298 - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre920165.f665af0cdb70/nixexprs.tar.xz?lastModified=1767151656&rev=f665af0cdb70ed27e1bd8f9fdfecaf451260fc55" 299 + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre924980.5f02c91314c8/nixexprs.tar.xz?lastModified=1767966113&rev=5f02c91314c8ba4afe83b256b023756412218535" 299 300 }, 300 301 "original": { 301 302 "type": "tarball", ··· 312 313 ] 313 314 }, 314 315 "locked": { 315 - "lastModified": 1765911976, 316 - "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", 316 + "lastModified": 1767281941, 317 + "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", 317 318 "owner": "cachix", 318 319 "repo": "pre-commit-hooks.nix", 319 - "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", 320 + "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", 320 321 "type": "github" 321 322 }, 322 323 "original": { ··· 352 353 ] 353 354 }, 354 355 "locked": { 355 - "lastModified": 1766976750, 356 - "narHash": "sha256-w+o3AIBI56tzfMJRqRXg9tSXnpQRN5hAT15o2t9rxYw=", 356 + "lastModified": 1767495280, 357 + "narHash": "sha256-hEEgtE/RSRigw8xscchGymf/t1nluZwTfru4QF6O1CQ=", 357 358 "owner": "oxalica", 358 359 "repo": "rust-overlay", 359 - "rev": "9fe44e7f05b734a64a01f92fc51ad064fb0a884f", 360 + "rev": "cb24c5cc207ba8e9a4ce245eedd2d37c3a988bc1", 360 361 "type": "github" 361 362 }, 362 363 "original": { ··· 395 396 ] 396 397 }, 397 398 "locked": { 398 - "lastModified": 1766896955, 399 - "narHash": "sha256-BbAUnNjaBmfR7Mvho9BN0RfvDi5fpP19wd/Hs6DMX8k=", 399 + "lastModified": 1767848616, 400 + "narHash": "sha256-rAVR2ucz5vn9TOjvj5hVyEl0MnXrwIJo3ee12+p0EZE=", 400 401 "owner": "Mic92", 401 402 "repo": "sops-nix", 402 - "rev": "861c32b27cce26c4bb828dfd21bd23df0dba7df2", 403 + "rev": "994c9f014e5ce073de4bca7ebe21cf1cf90f2b0a", 403 404 "type": "github" 404 405 }, 405 406 "original": { ··· 417 418 "systems": "systems" 418 419 }, 419 420 "locked": { 420 - "lastModified": 1767195736, 421 - "narHash": "sha256-0xvPSbhIGeJzsJXNTkgJ3PjwdVItKm85wzYKA9NmSzI=", 421 + "lastModified": 1767502559, 422 + "narHash": "sha256-om0IPjW850vhhIrNZ5tiXjsYuqyoI44IdE+I9AwZ96I=", 422 423 "owner": "Gerg-L", 423 424 "repo": "spicetify-nix", 424 - "rev": "465adc0ab6ff0c4b9b1db1c6e7fd7eeb553b3261", 425 + "rev": "806c1fdeb7af3e013215d14f5d9f06685fa6650f", 425 426 "type": "github" 426 427 }, 427 428 "original": { ··· 452 453 ] 453 454 }, 454 455 "locked": { 455 - "lastModified": 1767229092, 456 - "narHash": "sha256-HFi+WvYK7mLVL6Cka2m9wKpy9lt2K5wPQ+oN/bmuc0Q=", 456 + "lastModified": 1768006445, 457 + "narHash": "sha256-WQ1yhsbYQM+EvhaWPSuS4UZPD4SjsskMWNG6azbEAgM=", 457 458 "owner": "tgirlcloud", 458 459 "repo": "pkgs", 459 - "rev": "1d1d8e13541eaf85492e758be2a032fd20412a33", 460 + "rev": "ae2de15f76978b6564c3eecdbe68d5ed0d0d2500", 460 461 "type": "github" 461 462 }, 462 463 "original": {
+2 -1
flake.nix
··· 25 25 # improved support for darwin 26 26 darwin = { 27 27 type = "github"; 28 - owner = "nix-darwin"; 28 + owner = "isabelroses"; 29 29 repo = "nix-darwin"; 30 + ref = "darwin-rebuild"; 30 31 inputs.nixpkgs.follows = "nixpkgs"; 31 32 }; 32 33
-5
home/isabel/cli/shell/shellAlias.nix
··· 4 4 mkdir = "mkdir -pv"; # always create pearent directory 5 5 df = "df -h"; # human readblity 6 6 rs = "systemctl reboot"; 7 - sysctl = "sudo systemctl"; 8 7 jctl = "journalctl -p 3 -xb"; # get error messages from journalctl 9 8 lg = "lazygit"; 10 9 11 10 zzzpl = "cd ~/.local/share/zzz ; git pull ; git push ; cd -"; 12 11 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 12 }; 18 13 }
+7 -1
home/isabel/gui/chromium.nix
··· 1 1 { 2 2 lib, 3 3 pkgs, 4 + config, 4 5 ... 5 6 }: 6 7 let ··· 187 188 }; 188 189 }; 189 190 191 + xdg.configFile = mkIf (pkgs.stdenv.hostPlatform.isLinux && config.programs.chromium.enable) { 192 + "chromium/NativeMessagingHosts/ff2mpv.json".source = 193 + "${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 194 + }; 195 + 190 196 home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin { 191 197 "Library/Application Support/Chromium/NativeMessagingHosts/ff2mpv.json".source = 192 - "${pkgs.ff2mpv}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 198 + "${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 193 199 }; 194 200 }
+17 -31
home/isabel/gui/hyprland.nix
··· 215 215 }; 216 216 217 217 layerrule = [ 218 - "blur,vicinae" 219 - "ignorealpha 0, vicinae" 220 - "noanim, vicinae" 218 + "blur on, ignore_alpha 0, match:namespace vicinae" 219 + "no_anim on, match:namespace vicinae" 221 220 ]; 222 221 223 222 general = { ··· 225 224 gaps_out = 8; 226 225 gaps_workspaces = 0; 227 226 border_size = 2; 228 - no_border_on_floating = true; 229 227 230 228 "col.active_border" = "$pink"; 231 229 "col.inactive_border" = "$surface1"; ··· 294 292 disable_autoreload = true; # autoreload is unnecessary on nixos, because the config is readonly anyway 295 293 }; 296 294 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" 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)$" 316 302 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" 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" 321 307 322 - "workspace 6, class:discord" # move discord to workspace 6 323 - "workspace 7, class:spotify" # move spotify to workspace 7 308 + "workspace 6, match:class discord" # move discord to workspace 6 309 + "workspace 7, match:class spotify" # move spotify to workspace 7 324 310 325 311 # throw sharing indicators away 326 - "workspace special silent, title:^(Firefox โ€” Sharing Indicator)$" 327 - "workspace special silent, title:^(.*is sharing (your screen|a window)\.)$" 312 + "workspace special silent, match:title ^(Firefox โ€” Sharing Indicator)$" 313 + "workspace special silent, match:title ^(.*is sharing (your screen|a window).)$" 328 314 ]; 329 315 }; 330 316
+11 -2
home/isabel/gui/media/listening.nix
··· 1 1 { 2 2 lib, 3 + pkgs, 3 4 config, 4 5 inputs, 5 6 inputs', ··· 16 17 config = mkIf config.garden.profiles.media.watching.enable { 17 18 programs.spicetify = { 18 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 + 19 30 enabledExtensions = with spicePkgs.extensions; [ 20 31 shuffle 21 32 copyToClipboard ··· 25 36 volumePercentage 26 37 aiBandBlocker 27 38 ]; 28 - theme = spicePkgs.themes.catppuccin; 29 - colorScheme = "mocha"; 30 39 }; 31 40 }; 32 41 }
+4 -1
home/isabel/gui/media/watching.nix
··· 32 32 33 33 scripts = 34 34 (with pkgs.mpvScripts; [ 35 - videoclip 36 35 sponsorblock 36 + 37 + # unify my clipboard 38 + (videoclip.override { wl-clipboard = pkgs.wl-clipboard-rs; }) 37 39 38 40 # modern ui 39 41 modernz ··· 111 113 stop-screensaver = "yes"; 112 114 cursor-autohide = 100; # auto hide cursor after 100ms 113 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"; 114 117 }; 115 118 116 119 profiles = {
+25 -5
home/isabel/gui/quickshell/components/Clock.qml
··· 2 2 import QtQuick.Layouts 3 3 import "root:/data" 4 4 5 - Text { 6 - id: clock 7 - font.pointSize: 13 8 - color: Settings.colors.foreground 5 + Item { 6 + id: root 7 + 8 + signal clicked() 9 + 9 10 Layout.alignment: Qt.AlignCenter 11 + implicitWidth: 30 12 + implicitHeight: 50 10 13 11 - text: Time.time 14 + Text { 15 + id: clockText 16 + anchors.centerIn: parent 17 + text: Time.time 18 + color: Settings.colors.foreground 19 + font { 20 + pixelSize: 14 21 + weight: Font.Medium 22 + } 23 + horizontalAlignment: Text.AlignHCenter 24 + } 25 + 26 + MouseArea { 27 + anchors.fill: parent 28 + hoverEnabled: true 29 + cursorShape: Qt.PointingHandCursor 30 + onClicked: root.clicked() 31 + } 12 32 }
+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
··· 2 2 import Quickshell 3 3 import Quickshell.Widgets 4 4 import "root:/data" 5 + import "root:/components" 5 6 6 7 Item { 7 - id: iconButton 8 - width: 16 9 - height: 16 8 + id: root 10 9 11 - property string icon: "" 12 - signal clicked 10 + property string icon: "" 11 + property int size: 18 12 + property bool invert: false 13 13 14 - MouseArea { 15 - anchors.fill: parent 16 - onClicked: iconButton.clicked() 17 - } 14 + signal clicked 18 15 19 - Text { 20 - anchors.fill: parent 21 - text: iconButton.icon 22 - color: Settings.colors.foreground 23 - font.pixelSize: 18 24 - } 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 + } 25 39 }
+16 -17
home/isabel/gui/quickshell/components/Launcher.qml
··· 2 2 import QtQuick.Layouts 3 3 import Quickshell 4 4 import Quickshell.Io 5 - import Quickshell.Widgets 5 + import "root:/data" 6 6 7 - IconImage { 8 - id: launcher 9 - source: Quickshell.iconPath("nix-snowflake") 7 + Item { 8 + id: root 10 9 11 - Layout.alignment: Qt.AlignCenter 10 + Layout.alignment: Qt.AlignCenter 11 + implicitWidth: 24 12 + implicitHeight: 24 12 13 13 - width: 16 14 - height: 16 14 + IconButton { 15 + anchors.centerIn: parent 16 + icon: "nix-snowflake" 17 + size: 20 18 + onClicked: launcherProcess.running = true 19 + } 15 20 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 - } 21 + Process { 22 + id: launcherProcess 23 + command: ["vicinae", "toggle"] 24 + } 26 25 }
+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
··· 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 + }
+28 -10
home/isabel/gui/quickshell/components/Network.qml
··· 1 - //@ pragma IconTheme Cosmic 2 - 1 + import QtQuick 3 2 import QtQuick.Layouts 4 - import Quickshell 5 - import Quickshell.Widgets 3 + import QtQuick.Controls.Basic 4 + import "root:/data" 5 + import "root:/components" 6 6 import "root:/services" 7 7 8 - IconImage { 9 - id: networkIcon 10 - source: Quickshell.iconPath(Networking.active.icon) 8 + Item { 9 + id: root 11 10 12 - width: 16 13 - height: 16 14 - Layout.alignment: Qt.AlignCenter 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 + 21 + MouseArea { 22 + id: mouseArea 23 + anchors.fill: parent 24 + hoverEnabled: true 25 + cursorShape: Qt.PointingHandCursor 26 + } 27 + 28 + ToolTip { 29 + visible: mouseArea.containsMouse 30 + text: Networking.statusText 31 + delay: 500 32 + } 15 33 }
+139 -327
home/isabel/gui/quickshell/components/Noti.qml
··· 1 1 import QtQuick 2 2 import QtQuick.Layouts 3 3 import QtQuick.Controls 4 - import QtQuick.Controls.Basic 5 4 import Quickshell 6 5 import Quickshell.Widgets 7 - import Quickshell.Services.Notifications 8 - import Quickshell.Services.Mpris 6 + import Quickshell.Services.Notifications as QsNotifications 9 7 import "root:/data" 10 8 import "root:/services" 11 - import "root:/components" 12 9 13 10 Item { 14 - id: noti 11 + id: root 15 12 16 - property bool showIndicator: Notifications.list.length > 0 || Media.players.length > 0 13 + property bool hasNotifications: Notifications.list.length > 0 17 14 18 - Layout.alignment: Qt.AlignCenter 19 - 20 - Text { 21 - text: "๏‘ถ" 22 - color: Settings.colors.foreground 23 - font.pixelSize: 16 15 + // Expose function to toggle popup (for Clock) 16 + function togglePopup() { 17 + notificationLoader.item.visible = !notificationLoader.item.visible; 18 + } 24 19 25 - anchors.horizontalCenter: parent.horizontalCenter 20 + visible: hasNotifications 26 21 27 - visible: noti.showIndicator 22 + Layout.alignment: Qt.AlignCenter 23 + implicitWidth: 24 24 + implicitHeight: 24 28 25 29 - MouseArea { 30 - anchors.fill: parent 31 - onClicked: notificationLoader.item.visible = !notificationLoader.item.visible 26 + IconButton { 27 + anchors.centerIn: parent 28 + icon: "preferences-system-notifications-symbolic" 29 + size: 18 30 + onClicked: root.togglePopup() 32 31 } 33 - } 34 32 35 - LazyLoader { 36 - id: notificationLoader 33 + LazyLoader { 34 + id: notificationLoader 35 + loading: true 37 36 38 - loading: true 37 + PopupWindow { 38 + id: popup 39 + anchor.window: root.QsWindow.window 40 + anchor.rect.x: parentWindow.width * 1.2 41 + visible: false 42 + color: "transparent" 39 43 40 - PopupWindow { 41 - id: popup 42 - anchor.window: noti.QsWindow.window 43 - anchor.rect.x: parentWindow.width * 1.2 44 - 45 - visible: false 44 + implicitWidth: 400 45 + implicitHeight: root.QsWindow.window.height 46 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 - } 47 + Rectangle { 48 + anchors.fill: parent 49 + radius: 12 50 + color: Settings.colors.background 123 51 124 52 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() 53 + spacing: 12 54 + anchors { 55 + fill: parent 56 + margins: 16 169 57 } 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 58 193 - onMoved: Media.selectedPlayer.position = value 59 + // Header 60 + RowLayout { 61 + Layout.fillWidth: true 62 + spacing: 8 194 63 195 - implicitHeight: parent.height 196 - implicitWidth: parent.width - positionText.width - lengthText.width - 10 64 + Text { 65 + text: "Notifications" 66 + color: Settings.colors.foreground 67 + font { 68 + pixelSize: 18 69 + weight: Font.Bold 70 + } 71 + } 197 72 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 73 + Item { Layout.fillWidth: true } 208 74 209 - Rectangle { 210 - implicitWidth: progress.visualPosition * parent.width 211 - implicitHeight: parent.height 212 - color: Settings.colors.accent 213 - radius: 2 75 + IconButton { 76 + icon: "edit-clear-all-symbolic" 77 + size: 16 78 + onClicked: { 79 + for (const n of Notifications.list) { 80 + n.dismiss(); 81 + } 82 + popup.visible = false; 83 + } 84 + } 214 85 } 215 - } 216 86 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 - } 87 + // Notifications List 88 + ListView { 89 + id: notiList 90 + model: Notifications.list 91 + Layout.fillWidth: true 92 + Layout.fillHeight: true 93 + spacing: 8 94 + clip: true 227 95 228 - Text { 229 - id: lengthText 230 - text: Math.round(Media.selectedPlayer.length) + "s" 231 - color: Settings.colors.foreground 232 - } 96 + ScrollBar.vertical: ScrollBar { 97 + policy: ScrollBar.AsNeeded 98 + } 233 99 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 - } 100 + delegate: Rectangle { 101 + required property QsNotifications.Notification modelData 102 + width: ListView.view.width 103 + height: 72 104 + radius: 8 105 + color: Settings.colors.backgroundLighter 244 106 245 - ListView { 246 - id: notiList 247 - model: Notifications.list 107 + RowLayout { 108 + anchors { 109 + fill: parent 110 + margins: 10 111 + } 112 + spacing: 10 248 113 249 - Layout.alignment: Qt.AlignCenter 250 - Layout.preferredWidth: parent.width 251 - Layout.preferredHeight: parent.height 114 + IconImage { 115 + source: Quickshell.iconPath(modelData.appIcon) 116 + implicitSize: 40 117 + Layout.alignment: Qt.AlignVCenter 118 + } 252 119 253 - ScrollBar.vertical: ScrollBar {} 120 + ColumnLayout { 121 + Layout.fillWidth: true 122 + Layout.fillHeight: true 123 + spacing: 2 254 124 255 - spacing: 15 256 - 257 - delegate: Item { 258 - required property Notification modelData 125 + Text { 126 + text: modelData.appName 127 + color: Settings.colors.foreground 128 + font { 129 + pixelSize: 13 130 + weight: Font.Medium 131 + } 132 + elide: Text.ElideRight 133 + Layout.fillWidth: true 134 + } 259 135 260 - width: parent.width 261 - height: 80 136 + Text { 137 + text: modelData.body 138 + color: Settings.colors.foreground 139 + opacity: 0.8 140 + font.pixelSize: 12 141 + elide: Text.ElideRight 142 + maximumLineCount: 2 143 + wrapMode: Text.WordWrap 144 + Layout.fillWidth: true 145 + } 146 + } 262 147 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 148 + IconButton { 149 + icon: "window-close-symbolic" 150 + size: 14 151 + Layout.alignment: Qt.AlignTop 152 + onClicked: { 153 + modelData.dismiss(); 154 + if (Notifications.list.length <= 0) { 155 + popup.visible = false; 156 + } 157 + } 158 + } 159 + } 160 + } 295 161 } 296 - } 297 162 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 - } 163 + // Separator before Media 164 + Rectangle { 165 + Layout.fillWidth: true 166 + Layout.preferredHeight: 1 167 + color: Settings.colors.backgroundLightest 168 + visible: Media.players.length > 0 324 169 } 325 - } 326 - } 327 170 328 - Repeater { 329 - model: modelData.actions 171 + // Media Players Section 172 + MediaPlayers {} 330 173 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 - 174 + // Separator 344 175 Rectangle { 345 - anchors.fill: parent 346 - color: Settings.colors.backgroundDarker 347 - radius: 5 176 + Layout.fillWidth: true 177 + Layout.preferredHeight: 1 178 + color: Settings.colors.backgroundLightest 179 + } 348 180 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 - } 181 + // Quick Settings Section 182 + QuickSettings {} 367 183 } 368 - } 369 184 } 370 - } 371 185 } 372 - } 373 186 } 374 - } 375 187 }
+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 + }
+387
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 Quickshell.Services.Pipewire 6 + import "root:/data" 7 + import "root:/services" 8 + 9 + ColumnLayout { 10 + id: root 11 + 12 + Layout.fillWidth: true 13 + spacing: 12 14 + 15 + Text { 16 + text: "Quick Settings" 17 + color: Settings.colors.foreground 18 + font { 19 + pixelSize: 14 20 + weight: Font.Bold 21 + } 22 + } 23 + 24 + // Connection Status Row (Ethernet indicator) 25 + RowLayout { 26 + Layout.fillWidth: true 27 + spacing: 8 28 + visible: Networking.ethernetConnected 29 + 30 + MyIcon { 31 + icon: "network-wired-symbolic" 32 + size: 16 33 + } 34 + 35 + Text { 36 + text: "Ethernet: " + Networking.ethernetDevice 37 + color: Settings.colors.foreground 38 + font.pixelSize: 12 39 + } 40 + 41 + Item { Layout.fillWidth: true } 42 + 43 + Rectangle { 44 + width: 8 45 + height: 8 46 + radius: 4 47 + color: Settings.colors.success 48 + } 49 + } 50 + 51 + // Network Panel (expandable) 52 + Rectangle { 53 + id: networkPanel 54 + Layout.fillWidth: true 55 + Layout.preferredHeight: visible ? Math.min(networkColumn.implicitHeight + 20, 350) : 0 56 + visible: false 57 + radius: 8 58 + color: Settings.colors.backgroundLighter 59 + clip: true 60 + 61 + Behavior on Layout.preferredHeight { 62 + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } 63 + } 64 + 65 + ColumnLayout { 66 + id: networkColumn 67 + anchors.fill: parent 68 + anchors.margins: 12 69 + spacing: 8 70 + 71 + RowLayout { 72 + Layout.fillWidth: true 73 + 74 + Text { 75 + text: "Networks" 76 + color: Settings.colors.foreground 77 + font { 78 + pixelSize: 13 79 + weight: Font.Medium 80 + } 81 + } 82 + 83 + Item { Layout.fillWidth: true } 84 + 85 + IconButton { 86 + icon: "view-refresh-symbolic" 87 + size: 14 88 + onClicked: Networking.reload() 89 + } 90 + } 91 + 92 + ScrollView { 93 + Layout.fillWidth: true 94 + Layout.preferredHeight: Math.min(networkListView.contentHeight, 280) 95 + clip: true 96 + 97 + ListView { 98 + id: networkListView 99 + width: parent.width 100 + model: Networking.networks 101 + spacing: 4 102 + 103 + delegate: PanelListItem { 104 + required property var modelData 105 + width: ListView.view.width 106 + icon: modelData.icon 107 + text: modelData.ssid || "Unknown" 108 + badge: modelData.strength + "%" 109 + active: modelData.active 110 + onClicked: { 111 + if (!modelData.active) { 112 + Networking.connectToNetwork(modelData.ssid); 113 + } 114 + } 115 + } 116 + } 117 + } 118 + 119 + Text { 120 + text: Networking.networks?.count === 0 ? "No networks found" : "" 121 + color: Settings.colors.foreground 122 + opacity: 0.5 123 + font.pixelSize: 12 124 + visible: Networking.networks?.count === 0 125 + Layout.alignment: Qt.AlignHCenter 126 + } 127 + 128 + Item { Layout.fillHeight: true } 129 + } 130 + } 131 + 132 + // Bluetooth Panel (expandable) 133 + Rectangle { 134 + id: bluetoothPanel 135 + Layout.fillWidth: true 136 + Layout.preferredHeight: visible ? Math.min(bluetoothColumn.implicitHeight + 20, 350) : 0 137 + visible: false 138 + radius: 8 139 + color: Settings.colors.backgroundLighter 140 + clip: true 141 + 142 + Behavior on Layout.preferredHeight { 143 + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } 144 + } 145 + 146 + ColumnLayout { 147 + id: bluetoothColumn 148 + anchors.fill: parent 149 + anchors.margins: 12 150 + spacing: 8 151 + 152 + RowLayout { 153 + Layout.fillWidth: true 154 + 155 + Text { 156 + text: "Bluetooth Devices" 157 + color: Settings.colors.foreground 158 + font { 159 + pixelSize: 13 160 + weight: Font.Medium 161 + } 162 + } 163 + 164 + Item { Layout.fillWidth: true } 165 + 166 + // Scan button 167 + IconButton { 168 + icon: Bluetooth.adapter?.discovering ? "process-stop-symbolic" : "view-refresh-symbolic" 169 + size: 14 170 + onClicked: { 171 + if (Bluetooth.adapter?.discovering) { 172 + Bluetooth.stopDiscovery(); 173 + } else { 174 + Bluetooth.startDiscovery(); 175 + } 176 + } 177 + } 178 + 179 + Text { 180 + text: Bluetooth.connected ? Bluetooth.connectedDevice?.name ?? "" : "" 181 + color: Settings.colors.accent 182 + font.pixelSize: 11 183 + visible: Bluetooth.connected 184 + } 185 + } 186 + 187 + ScrollView { 188 + Layout.fillWidth: true 189 + Layout.preferredHeight: Math.min(bluetoothListView.contentHeight, 280) 190 + clip: true 191 + 192 + ListView { 193 + id: bluetoothListView 194 + width: parent.width 195 + model: Bluetooth.devices 196 + spacing: 8 197 + clip: true 198 + 199 + delegate: PanelListItem { 200 + required property var modelData 201 + width: ListView.view.width 202 + icon: modelData.icon || "bluetooth-symbolic" 203 + text: modelData.name 204 + badge: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "") 205 + active: modelData.connected 206 + onClicked: { 207 + if (modelData.connected) { 208 + modelData.disconnect(); 209 + } else if (modelData.paired) { 210 + modelData.connect(); 211 + } else { 212 + // Pair and connect 213 + modelData.pair(); 214 + } 215 + } 216 + } 217 + } 218 + } 219 + 220 + Text { 221 + text: { 222 + if (!Bluetooth.adapter) return "No Bluetooth adapter"; 223 + if (Bluetooth.devices?.count === 0) return "No devices found"; 224 + return ""; 225 + } 226 + color: Settings.colors.foreground 227 + opacity: 0.5 228 + font.pixelSize: 12 229 + visible: !Bluetooth.adapter || Bluetooth.devices?.count === 0 230 + Layout.alignment: Qt.AlignHCenter 231 + } 232 + 233 + Item { Layout.fillHeight: true } 234 + } 235 + } 236 + 237 + // Toggle Buttons Row 238 + RowLayout { 239 + Layout.fillWidth: true 240 + spacing: 8 241 + 242 + // WiFi Toggle 243 + QuickSettingButton { 244 + icon: Networking.wifiEnabled 245 + ? (Networking.activeWifi?.icon ?? "network-wireless-acquiring-symbolic") 246 + : "network-wireless-disabled-symbolic" 247 + label: "WiFi" 248 + active: Networking.wifiEnabled 249 + onClicked: Networking.toggleWifi() 250 + onPressAndHold: networkPanel.visible = !networkPanel.visible 251 + } 252 + 253 + // Bluetooth Toggle 254 + QuickSettingButton { 255 + icon: Bluetooth.icon 256 + label: "Bluetooth" 257 + active: Bluetooth.powered 258 + onClicked: Bluetooth.toggle() 259 + onPressAndHold: bluetoothPanel.visible = !bluetoothPanel.visible 260 + } 261 + 262 + // Do Not Disturb Toggle 263 + QuickSettingButton { 264 + icon: Notifications.dndEnabled ? "notifications-disabled-symbolic" : "preferences-system-notifications-symbolic" 265 + label: "DND" 266 + active: Notifications.dndEnabled 267 + onClicked: Notifications.dndEnabled = !Notifications.dndEnabled 268 + } 269 + } 270 + 271 + // Volume Slider 272 + PwObjectTracker { 273 + objects: [Pipewire.defaultAudioSink] 274 + } 275 + 276 + StyledSlider { 277 + icon: { 278 + const muted = Pipewire.defaultAudioSink?.audio?.muted ?? false; 279 + const vol = Pipewire.defaultAudioSink?.audio?.volume ?? 0; 280 + if (muted) return "audio-volume-muted-symbolic"; 281 + if (vol > 0.66) return "audio-volume-high-symbolic"; 282 + if (vol > 0.33) return "audio-volume-medium-symbolic"; 283 + if (vol > 0) return "audio-volume-low-symbolic"; 284 + return "audio-volume-muted-symbolic"; 285 + } 286 + value: Pipewire.defaultAudioSink?.audio?.volume ?? 0 287 + iconClickable: true 288 + onIconClicked: { 289 + if (Pipewire.defaultAudioSink?.audio) { 290 + Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted; 291 + } 292 + } 293 + onMoved: (val) => { 294 + if (Pipewire.defaultAudioSink?.audio) { 295 + Pipewire.defaultAudioSink.audio.volume = val; 296 + } 297 + } 298 + } 299 + 300 + // Brightness Slider 301 + BrightnessHelper { id: brightnessHelper } 302 + 303 + StyledSlider { 304 + visible: brightnessHelper.available 305 + icon: "display-brightness-symbolic" 306 + value: brightnessHelper.brightness 307 + from: 0.05 308 + accentColor: Settings.colors.warning 309 + onMoved: (val) => brightnessHelper.setBrightness(val) 310 + } 311 + 312 + // Quick Setting Button Component 313 + component QuickSettingButton: Rectangle { 314 + id: qsButton 315 + Layout.fillWidth: true 316 + Layout.preferredHeight: 64 317 + radius: 8 318 + color: active ? Settings.colors.accent : Settings.colors.backgroundLighter 319 + 320 + property string icon: "" 321 + property string label: "" 322 + property bool active: false 323 + 324 + signal clicked() 325 + signal pressAndHold() 326 + 327 + ColumnLayout { 328 + anchors.centerIn: parent 329 + spacing: 6 330 + 331 + MyIcon { 332 + icon: qsButton.icon 333 + size: 20 334 + Layout.alignment: Qt.AlignHCenter 335 + invert: active 336 + } 337 + 338 + Text { 339 + text: qsButton.label 340 + color: qsButton.active ? Settings.colors.background : Settings.colors.foreground 341 + font.pixelSize: 11 342 + Layout.alignment: Qt.AlignHCenter 343 + } 344 + } 345 + 346 + MouseArea { 347 + anchors.fill: parent 348 + cursorShape: Qt.PointingHandCursor 349 + onClicked: qsButton.clicked() 350 + onPressAndHold: qsButton.pressAndHold() 351 + } 352 + } 353 + 354 + // Brightness Helper Component 355 + component BrightnessHelper: QtObject { 356 + id: brightnessObj 357 + property real brightness: 1.0 358 + property bool available: false 359 + 360 + function setBrightness(value) { 361 + brightness = value; 362 + setBrightnessProc.command = ["brightnessctl", "set", Math.round(value * 100) + "%"]; 363 + setBrightnessProc.running = true; 364 + } 365 + 366 + Component.onCompleted: getBrightness.running = true 367 + 368 + property Process getBrightness: Process { 369 + id: getBrightness 370 + command: ["bash", "-c", "brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'"] 371 + stdout: SplitParser { 372 + onRead: { 373 + const val = parseInt(data.trim()); 374 + if (!isNaN(val)) { 375 + brightnessObj.brightness = val / 100; 376 + brightnessObj.available = true; 377 + } 378 + } 379 + } 380 + } 381 + 382 + property Process setBrightnessProc: Process { 383 + id: setBrightnessProc 384 + } 385 + } 386 + } 387 +
+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 + }
+212 -82
home/isabel/gui/quickshell/components/SysTray.qml
··· 1 1 import QtQuick 2 2 import QtQuick.Layouts 3 - import Quickshell 4 3 import QtQuick.Controls 4 + import Quickshell 5 5 import Quickshell.Widgets 6 6 import Quickshell.Services.SystemTray 7 7 import "root:/data" 8 8 9 9 ColumnLayout { 10 - id: systray 10 + id: root 11 11 12 - Layout.alignment: Qt.AlignCenter 12 + Layout.alignment: Qt.AlignCenter 13 + spacing: 6 13 14 14 - Repeater { 15 - model: SystemTray.items 15 + Repeater { 16 + model: SystemTray.items 16 17 17 - delegate: Item { 18 - id: delagate 19 - required property SystemTrayItem modelData 18 + delegate: Item { 19 + id: trayItem 20 + required property SystemTrayItem modelData 20 21 21 - width: 24 22 - height: 24 22 + Layout.alignment: Qt.AlignHCenter 23 + implicitWidth: 24 24 + implicitHeight: 24 23 25 24 - IconImage { 25 - source: modelData.icon 26 - width: 16 27 - height: 16 28 - anchors.centerIn: parent 29 - } 26 + IconImage { 27 + source: modelData.icon 28 + implicitWidth: 18 29 + implicitHeight: 18 30 + anchors.centerIn: parent 31 + smooth: true 32 + } 30 33 31 - MouseArea { 32 - anchors.fill: parent 33 - hoverEnabled: true 34 + MouseArea { 35 + anchors.fill: parent 36 + hoverEnabled: true 37 + cursorShape: Qt.PointingHandCursor 38 + acceptedButtons: Qt.LeftButton | Qt.RightButton 39 + onClicked: (mouse) => { 40 + if (mouse.button === Qt.RightButton || modelData.onlyMenu) { 41 + popupLoader.item.visible = !popupLoader.item.visible; 42 + } else { 43 + modelData.activate(); 44 + } 45 + } 46 + } 34 47 35 - onClicked: popupLoader.item.visible = !popupLoader.item.visible 36 - } 48 + ToolTip { 49 + visible: trayMouseArea.containsMouse && modelData.tooltipTitle !== "" 50 + text: modelData.tooltipTitle 51 + delay: 500 52 + } 37 53 38 - QsMenuOpener { 39 - id: menu 40 - menu: modelData.menu 41 - } 54 + MouseArea { 55 + id: trayMouseArea 56 + anchors.fill: parent 57 + hoverEnabled: true 58 + acceptedButtons: Qt.NoButton 59 + } 42 60 43 - LazyLoader { 44 - id: popupLoader 61 + QsMenuOpener { 62 + id: menu 63 + menu: modelData.menu 64 + } 45 65 46 - loading: true 66 + LazyLoader { 67 + id: popupLoader 68 + loading: true 47 69 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 70 + PopupWindow { 71 + id: popup 72 + anchor.window: trayItem.QsWindow.window 73 + anchor.rect.x: parentWindow.width * 1.15 74 + anchor.rect.y: parentWindow.height / 1.25 75 + visible: false 76 + color: "transparent" 53 77 54 - color: "transparent" 78 + implicitWidth: 220 79 + implicitHeight: menuColumn.implicitHeight + 16 55 80 56 - implicitWidth: 200 57 - implicitHeight: 200 81 + Rectangle { 82 + anchors.fill: parent 83 + color: Settings.colors.background 84 + radius: 10 58 85 59 - Rectangle { 60 - anchors.fill: parent 61 - color: Settings.colors.background 62 - radius: 5 63 - } 86 + // Shadow effect 87 + layer.enabled: true 88 + layer.effect: null 89 + } 64 90 65 - ListView { 66 - model: menu.children 91 + ColumnLayout { 92 + id: menuColumn 93 + anchors { 94 + fill: parent 95 + margins: 8 96 + } 97 + spacing: 2 67 98 68 - anchors { 69 - top: parent.top 70 - topMargin: 5 71 - bottom: parent.bottom 72 - bottomMargin: 5 73 - } 99 + // Header with app name 100 + Text { 101 + text: modelData.title || modelData.id 102 + color: Settings.colors.foreground 103 + font { 104 + pixelSize: 12 105 + weight: Font.Bold 106 + } 107 + opacity: 0.7 108 + Layout.fillWidth: true 109 + Layout.leftMargin: 8 110 + Layout.bottomMargin: 4 111 + elide: Text.ElideRight 112 + visible: text !== "" 113 + } 74 114 75 - width: parent.width 76 - height: parent.height 77 - spacing: 5 115 + Rectangle { 116 + Layout.fillWidth: true 117 + Layout.preferredHeight: 1 118 + Layout.leftMargin: 4 119 + Layout.rightMargin: 4 120 + Layout.bottomMargin: 4 121 + color: Settings.colors.foreground 122 + opacity: 0.1 123 + visible: (modelData.title || modelData.id) !== "" 124 + } 78 125 79 - ScrollBar.vertical: ScrollBar {} 126 + Repeater { 127 + model: menu.children 128 + 129 + delegate: Loader { 130 + id: menuItemLoader 131 + required property var modelData 132 + 133 + Layout.fillWidth: true 134 + sourceComponent: modelData.isSeparator ? separatorComponent : menuItemComponent 135 + 136 + Component { 137 + id: separatorComponent 138 + 139 + Rectangle { 140 + height: 9 141 + color: "transparent" 80 142 81 - delegate: Item { 82 - required property QsMenuHandle modelData 143 + Rectangle { 144 + anchors.centerIn: parent 145 + width: parent.width - 16 146 + height: 1 147 + color: Settings.colors.foreground 148 + opacity: 0.1 149 + } 150 + } 151 + } 83 152 84 - width: parent.width 85 - height: 40 153 + Component { 154 + id: menuItemComponent 86 155 87 - Rectangle { 88 - anchors { 89 - fill: parent 90 - leftMargin: 5 91 - rightMargin: 5 92 - } 156 + Rectangle { 157 + id: menuItemRect 158 + height: 32 159 + radius: 6 160 + color: menuMouse.containsMouse && menuItemLoader.modelData.enabled 161 + ? Settings.colors.backgroundLighter 162 + : "transparent" 163 + opacity: menuItemLoader.modelData.enabled ? 1.0 : 0.5 164 + 165 + RowLayout { 166 + anchors { 167 + fill: parent 168 + leftMargin: 10 169 + rightMargin: 10 170 + } 171 + spacing: 8 172 + 173 + // Checkbox/Radio indicator 174 + Rectangle { 175 + visible: menuItemLoader.modelData.buttonType !== 0 // QsMenuButtonType.None 176 + Layout.preferredWidth: 16 177 + Layout.preferredHeight: 16 178 + radius: menuItemLoader.modelData.buttonType === 2 ? 8 : 3 // RadioButton = 2 179 + color: "transparent" 180 + border.width: 1.5 181 + border.color: menuItemLoader.modelData.checkState !== Qt.Unchecked 182 + ? Settings.colors.accent 183 + : Settings.colors.foreground 184 + opacity: menuItemLoader.modelData.checkState !== Qt.Unchecked ? 1.0 : 0.5 185 + 186 + Rectangle { 187 + anchors.centerIn: parent 188 + width: 8 189 + height: 8 190 + radius: menuItemLoader.modelData.buttonType === 2 ? 4 : 2 191 + color: Settings.colors.accent 192 + visible: menuItemLoader.modelData.checkState !== Qt.Unchecked 193 + } 194 + } 93 195 94 - color: Settings.colors.backgroundLighter 95 - radius: 5 196 + // Icon 197 + Image { 198 + visible: menuItemLoader.modelData.icon !== "" && menuItemLoader.modelData.buttonType === 0 199 + source: menuItemLoader.modelData.icon 200 + Layout.preferredWidth: 16 201 + Layout.preferredHeight: 16 202 + sourceSize.width: 16 203 + sourceSize.height: 16 204 + smooth: true 205 + } 96 206 97 - Text { 98 - anchors.centerIn: parent 99 - text: modelData.text 100 - color: Settings.colors.foreground 101 - font.pointSize: 12 102 - } 207 + // Text 208 + Text { 209 + text: menuItemLoader.modelData.text 210 + color: Settings.colors.foreground 211 + font.pixelSize: 13 212 + elide: Text.ElideRight 213 + Layout.fillWidth: true 214 + } 103 215 104 - MouseArea { 105 - anchors.fill: parent 106 - hoverEnabled: true 216 + // Submenu indicator 217 + MyIcon { 218 + visible: menuItemLoader.modelData.hasChildren 219 + icon: "go-next-symbolic" 220 + size: 12 221 + opacity: 0.6 222 + } 223 + } 107 224 108 - onClicked: { 109 - modelData.triggered(); 110 - popup.visible = false; 111 - } 225 + MouseArea { 226 + id: menuMouse 227 + anchors.fill: parent 228 + hoverEnabled: true 229 + cursorShape: menuItemLoader.modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor 230 + onClicked: { 231 + if (!menuItemLoader.modelData.enabled) return; 232 + 233 + if (menuItemLoader.modelData.hasChildren) { 234 + // TODO: Handle submenu 235 + } else { 236 + menuItemLoader.modelData.triggered(); 237 + popup.visible = false; 238 + } 239 + } 240 + } 241 + } 242 + } 243 + } 244 + } 245 + } 112 246 } 113 - } 114 247 } 115 - } 116 248 } 117 - } 118 249 } 119 - } 120 250 }
+36 -8
home/isabel/gui/quickshell/components/Volume.qml
··· 1 - import Quickshell 2 - import Quickshell.Widgets 1 + import QtQuick 3 2 import QtQuick.Layouts 3 + import Quickshell 4 + import Quickshell.Services.Pipewire 5 + import "root:/data" 4 6 5 - IconImage { 6 - id: volumeIcon 7 - source: Quickshell.iconPath("audio-volume-high-symbolic") 8 - width: 16 9 - height: 16 7 + Item { 8 + id: root 9 + 10 + Layout.alignment: Qt.AlignCenter 11 + implicitWidth: 20 12 + implicitHeight: 20 10 13 11 - Layout.alignment: Qt.AlignCenter 14 + PwObjectTracker { 15 + objects: [Pipewire.defaultAudioSink] 16 + } 17 + 18 + property PwNode sink: Pipewire.defaultAudioSink 19 + property real volume: sink?.audio?.volume ?? 0 20 + property bool muted: sink?.audio?.muted ?? false 21 + 22 + property string iconName: { 23 + if (muted) return "audio-volume-muted-symbolic"; 24 + if (volume > 0.66) return "audio-volume-high-symbolic"; 25 + if (volume > 0.33) return "audio-volume-medium-symbolic"; 26 + if (volume > 0) return "audio-volume-low-symbolic"; 27 + return "audio-volume-muted-symbolic"; 28 + } 29 + 30 + IconButton { 31 + anchors.centerIn: parent 32 + icon: root.iconName 33 + size: 18 34 + onClicked: { 35 + if (root.sink?.audio) { 36 + root.sink.audio.muted = !root.sink.audio.muted; 37 + } 38 + } 39 + } 12 40 }
+53 -28
home/isabel/gui/quickshell/components/Workspaces.qml
··· 4 4 import QtQuick.Layouts 5 5 import Quickshell.Hyprland 6 6 import "root:/data" 7 - import "root:/services" 8 7 9 8 Item { 10 - id: workspaces 9 + id: root 11 10 12 - Layout.alignment: Qt.AlignCenter 11 + Layout.alignment: Qt.AlignCenter 12 + implicitWidth: 24 13 + implicitHeight: workspaceColumn.implicitHeight 13 14 14 - width: 20 15 - height: 20 15 + ColumnLayout { 16 + id: workspaceColumn 17 + anchors.horizontalCenter: parent.horizontalCenter 18 + spacing: 4 16 19 17 - ColumnLayout { 18 - spacing: 20 20 + Repeater { 21 + model: Hyprland.workspaces 19 22 20 - anchors.horizontalCenter: parent.horizontalCenter 23 + delegate: Item { 24 + id: workspaceItem 25 + required property HyprlandWorkspace modelData 21 26 22 - Repeater { 23 - model: Hyprland.workspaces 27 + Layout.alignment: Qt.AlignHCenter 28 + implicitWidth: 24 29 + implicitHeight: 24 24 30 25 - delegate: Item { 26 - id: workspace 27 - required property HyprlandWorkspace modelData 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" 28 39 29 - implicitWidth: 10 30 - implicitHeight: 10 31 - 32 - MouseArea { 33 - anchors.fill: parent 34 - hoverEnabled: true 40 + Behavior on color { 41 + ColorAnimation { duration: 150 } 42 + } 35 43 36 - onClicked: modelData.activate() 37 - } 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 38 55 39 - Text { 40 - font.pointSize: 13 41 - Layout.alignment: Qt.AlignCenter 56 + Behavior on color { 57 + ColorAnimation { duration: 150 } 58 + } 59 + Behavior on opacity { 60 + NumberAnimation { duration: 150 } 61 + } 62 + } 63 + } 42 64 43 - color: modelData.focused === modelData.id ? Settings.colors.accent : Settings.colors.foreground 44 - text: modelData.id 65 + MouseArea { 66 + anchors.fill: parent 67 + hoverEnabled: true 68 + cursorShape: Qt.PointingHandCursor 69 + onClicked: workspaceItem.modelData.activate() 70 + } 71 + } 45 72 } 46 - } 47 73 } 48 - } 49 74 }
+5
home/isabel/gui/quickshell/data/Settings.qml
··· 23 23 } 24 24 25 25 property string wallpaper: "/home/isabel/media/pictures/wallpapers/flowers.jpg"; 26 + 27 + // Notification settings 28 + property list<string> notificationBlacklist: [ 29 + "Spotify" 30 + ] 26 31 }
+76 -58
home/isabel/gui/quickshell/modules/Bar.qml
··· 7 7 import "root:/components" 8 8 9 9 Scope { 10 - id: root 10 + id: root 11 11 12 - Variants { 13 - model: Quickshell.screens 12 + Variants { 13 + model: Quickshell.screens 14 14 15 - PanelWindow { 16 - property var modelData 17 - screen: modelData 18 - implicitWidth: 40 19 - color: "transparent" 15 + PanelWindow { 16 + property var modelData 17 + screen: modelData 18 + implicitWidth: 48 19 + color: "transparent" 20 20 21 - anchors { 22 - top: true 23 - left: true 24 - bottom: true 25 - } 21 + anchors { 22 + top: true 23 + left: true 24 + bottom: true 25 + } 26 26 27 - margins { 28 - left: 8 29 - right: 8 30 - top: 8 31 - bottom: 8 32 - } 27 + margins { 28 + left: 10 29 + top: 10 30 + bottom: 10 31 + } 33 32 34 - Rectangle { 35 - id: bar 36 - anchors.fill: parent 37 - radius: 10 38 - color: Settings.colors.background 33 + Rectangle { 34 + id: bar 35 + anchors.fill: parent 36 + radius: 12 37 + color: Settings.colors.background 39 38 40 - ColumnLayout { 41 - anchors { 42 - left: parent.left 43 - top: parent.top 44 - right: parent.right 45 - topMargin: 15 46 - } 39 + // Top section - Launcher & Workspaces 40 + ColumnLayout { 41 + anchors { 42 + horizontalCenter: parent.horizontalCenter 43 + top: parent.top 44 + topMargin: 12 45 + } 46 + spacing: 16 47 47 48 - spacing: 15 48 + Launcher {} 49 49 50 - Launcher {} 51 - Workspaces {} 52 - } 50 + Rectangle { 51 + Layout.alignment: Qt.AlignHCenter 52 + width: 20 53 + height: 1 54 + color: Settings.colors.foreground 55 + opacity: 0.2 56 + } 53 57 54 - ColumnLayout { 55 - anchors { 56 - left: parent.left 57 - right: parent.right 58 - top: parent.verticalCenter 59 - } 58 + Workspaces {} 59 + } 60 60 61 - spacing: 20 61 + // Center section - Clock & Notifications 62 + ColumnLayout { 63 + anchors { 64 + horizontalCenter: parent.horizontalCenter 65 + verticalCenter: parent.verticalCenter 66 + } 67 + spacing: 16 62 68 63 - Clock {} 64 - Noti {} 65 - } 69 + Clock { 70 + onClicked: notiPanel.togglePopup() 71 + } 72 + Noti { 73 + id: notiPanel 74 + } 75 + } 76 + 77 + // Bottom section - System tray, Volume, Network 78 + ColumnLayout { 79 + anchors { 80 + horizontalCenter: parent.horizontalCenter 81 + bottom: parent.bottom 82 + bottomMargin: 12 83 + } 84 + spacing: 12 66 85 67 - ColumnLayout { 68 - anchors { 69 - left: parent.left 70 - bottom: parent.bottom 71 - right: parent.right 72 - bottomMargin: 15 73 - } 86 + SysTray {} 74 87 75 - spacing: 15 88 + Rectangle { 89 + Layout.alignment: Qt.AlignHCenter 90 + width: 20 91 + height: 1 92 + color: Settings.colors.foreground 93 + opacity: 0.2 94 + } 76 95 77 - SysTray {} 78 - Volume {} 79 - Network {} 96 + Volume {} 97 + Network {} 98 + } 99 + } 80 100 } 81 - } 82 101 } 83 - } 84 102 }
+276
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.Pipewire 7 + import Quickshell.Services.Notifications as QsNotifications 8 + import "root:/data" 9 + import "root:/components" 10 + import "root:/services" 11 + 12 + Scope { 13 + id: root 14 + 15 + // OSD State 16 + property string osdType: "" 17 + property real progress: 0 18 + property string iconSource: "" 19 + property string osdText: "" 20 + property bool osdVisible: false 21 + 22 + // Notification State 23 + property var currentNotification: null 24 + property bool notificationVisible: false 25 + 26 + // Pipewire 27 + PwObjectTracker { 28 + objects: [Pipewire.defaultAudioSink] 29 + } 30 + 31 + property PwNode sink: Pipewire.defaultAudioSink 32 + property real volume: sink?.audio?.volume ?? 0 33 + property bool muted: sink?.audio?.muted ?? false 34 + 35 + onVolumeChanged: showVolumeOsd() 36 + onMutedChanged: showVolumeOsd() 37 + 38 + function showVolumeOsd(): void { 39 + root.osdType = "volume"; 40 + root.progress = root.volume; 41 + if (root.muted) { 42 + root.iconSource = "audio-volume-muted-symbolic"; 43 + } else if (root.volume > 0.66) { 44 + root.iconSource = "audio-volume-high-symbolic"; 45 + } else if (root.volume > 0.33) { 46 + root.iconSource = "audio-volume-medium-symbolic"; 47 + } else if (root.volume > 0) { 48 + root.iconSource = "audio-volume-low-symbolic"; 49 + } else { 50 + root.iconSource = "audio-volume-muted-symbolic"; 51 + } 52 + root.osdText = Math.round(root.volume * 100) + "%"; 53 + root.osdVisible = true; 54 + osdHideTimer.restart(); 55 + } 56 + 57 + function showNotification(notification: QsNotifications.Notification): void { 58 + root.currentNotification = notification; 59 + root.notificationVisible = true; 60 + notificationHideTimer.restart(); 61 + } 62 + 63 + Timer { 64 + id: osdHideTimer 65 + interval: 1500 66 + onTriggered: root.osdVisible = false 67 + } 68 + 69 + Timer { 70 + id: notificationHideTimer 71 + interval: 4000 72 + onTriggered: root.notificationVisible = false 73 + } 74 + 75 + // Connect to the shared notification service 76 + Connections { 77 + target: Notifications 78 + function onNewNotification(notification) { 79 + root.showNotification(notification); 80 + } 81 + } 82 + 83 + Variants { 84 + model: Quickshell.screens 85 + 86 + PanelWindow { 87 + id: osdWindow 88 + property var modelData 89 + screen: modelData 90 + 91 + visible: root.osdVisible 92 + color: "transparent" 93 + 94 + WlrLayershell.namespace: "osd" 95 + WlrLayershell.layer: WlrLayer.Overlay 96 + exclusionMode: ExclusionMode.Ignore 97 + 98 + anchors { 99 + bottom: true 100 + } 101 + 102 + implicitWidth: 320 103 + implicitHeight: 80 104 + 105 + margins { 106 + bottom: 80 107 + } 108 + 109 + Rectangle { 110 + id: osdContainer 111 + anchors.centerIn: parent 112 + width: 300 113 + height: 56 114 + radius: 14 115 + color: Settings.colors.background 116 + 117 + RowLayout { 118 + anchors { 119 + fill: parent 120 + leftMargin: 18 121 + rightMargin: 18 122 + } 123 + spacing: 14 124 + 125 + MyIcon { 126 + icon: root.iconSource 127 + size: 22 128 + Layout.alignment: Qt.AlignVCenter 129 + } 130 + 131 + Rectangle { 132 + Layout.fillWidth: true 133 + Layout.preferredHeight: 6 134 + Layout.alignment: Qt.AlignVCenter 135 + radius: 3 136 + color: Settings.colors.backgroundLighter 137 + 138 + Rectangle { 139 + width: parent.width * Math.min(root.progress, 1.0) 140 + height: parent.height 141 + radius: 3 142 + color: root.muted ? Settings.colors.error : Settings.colors.accent 143 + 144 + Behavior on width { 145 + SmoothedAnimation { velocity: 600 } 146 + } 147 + } 148 + } 149 + 150 + Text { 151 + text: root.osdText 152 + color: Settings.colors.foreground 153 + font { 154 + pixelSize: 13 155 + weight: Font.Medium 156 + } 157 + Layout.preferredWidth: 40 158 + Layout.alignment: Qt.AlignVCenter 159 + horizontalAlignment: Text.AlignRight 160 + } 161 + } 162 + } 163 + } 164 + } 165 + 166 + // Notification Popup 167 + Variants { 168 + model: Quickshell.screens 169 + 170 + PanelWindow { 171 + id: notificationWindow 172 + property var modelData 173 + screen: modelData 174 + 175 + visible: root.notificationVisible && root.currentNotification !== null 176 + color: "transparent" 177 + 178 + WlrLayershell.namespace: "notification" 179 + WlrLayershell.layer: WlrLayer.Overlay 180 + exclusionMode: ExclusionMode.Ignore 181 + 182 + anchors { 183 + top: true 184 + } 185 + 186 + implicitWidth: 420 187 + implicitHeight: 100 188 + 189 + margins { 190 + top: 16 191 + } 192 + 193 + Rectangle { 194 + id: notificationContainer 195 + anchors { 196 + fill: parent 197 + margins: 8 198 + } 199 + radius: 12 200 + color: Settings.colors.background 201 + 202 + RowLayout { 203 + anchors { 204 + fill: parent 205 + margins: 14 206 + } 207 + spacing: 12 208 + 209 + IconImage { 210 + source: Quickshell.iconPath(root.currentNotification.appIcon) 211 + implicitSize: 40 212 + Layout.alignment: Qt.AlignTop 213 + } 214 + 215 + ColumnLayout { 216 + Layout.fillWidth: true 217 + Layout.fillHeight: true 218 + spacing: 4 219 + 220 + Text { 221 + text: root.currentNotification?.appName ?? "" 222 + color: Settings.colors.foreground 223 + font { 224 + pixelSize: 13 225 + weight: Font.Bold 226 + } 227 + elide: Text.ElideRight 228 + Layout.fillWidth: true 229 + } 230 + 231 + Text { 232 + text: root.currentNotification?.summary ?? "" 233 + color: Settings.colors.foreground 234 + font { 235 + pixelSize: 12 236 + weight: Font.Medium 237 + } 238 + elide: Text.ElideRight 239 + Layout.fillWidth: true 240 + visible: text !== "" 241 + } 242 + 243 + Text { 244 + text: root.currentNotification?.body ?? "" 245 + color: Settings.colors.foreground 246 + opacity: 0.8 247 + font.pixelSize: 11 248 + elide: Text.ElideRight 249 + wrapMode: Text.WordWrap 250 + maximumLineCount: 2 251 + Layout.fillWidth: true 252 + } 253 + } 254 + 255 + IconButton { 256 + icon: "window-close-symbolic" 257 + size: 14 258 + Layout.alignment: Qt.AlignTop 259 + onClicked: { 260 + root.currentNotification?.dismiss(); 261 + root.notificationVisible = false; 262 + } 263 + } 264 + } 265 + 266 + // Click to dismiss 267 + MouseArea { 268 + anchors.fill: parent 269 + z: -1 270 + onClicked: root.notificationVisible = false 271 + } 272 + } 273 + } 274 + } 275 + } 276 +
+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 + }
+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 1 + // Network service with WiFi and Ethernet support 3 2 4 3 pragma Singleton 5 4 ··· 10 9 Singleton { 11 10 id: root 12 11 12 + // WiFi 13 13 readonly property list<AccessPoint> networks: [] 14 - readonly property AccessPoint active: networks.find(n => n.active) ?? null 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 + } 15 37 16 38 reloadableId: "network" 17 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 18 61 Process { 19 62 running: true 20 63 command: ["nmcli", "m"] 21 64 stdout: SplitParser { 22 - onRead: getNetworks.running = true 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: { 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 + } 23 104 } 24 105 } 25 106 26 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 { 27 119 id: getNetworks 28 - running: true 29 120 command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"] 30 - environment: ({ 31 - LANG: "C", 32 - LC_ALL: "C" 33 - }) 121 + environment: ({ LANG: "C", LC_ALL: "C" }) 34 122 stdout: StdioCollector { 35 123 onStreamFinished: { 36 124 const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; 37 125 const rep = new RegExp("\\\\:", "g"); 38 126 const rep2 = new RegExp(PLACEHOLDER, "g"); 39 127 40 - const networks = text.trim().split("\n").map(n => { 128 + const networks = text.trim().split("\n").filter(l => l.length > 0).map(n => { 41 129 const net = n.replace(rep, PLACEHOLDER).split(":"); 42 130 return { 43 131 active: net[0] === "yes", 44 - strength: parseInt(net[1]), 45 - frequency: parseInt(net[2]), 46 - ssid: net[3], 132 + strength: parseInt(net[1]) || 0, 133 + frequency: parseInt(net[2]) || 0, 134 + ssid: net[3] || "", 47 135 bssid: net[4]?.replace(rep2, ":") ?? "" 48 136 }; 49 - }); 137 + }).filter(n => n.ssid !== ""); 138 + 50 139 const rNetworks = root.networks; 51 140 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()); 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 + } 55 152 153 + // Update or add networks 56 154 for (const network of networks) { 57 - const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid); 155 + const match = rNetworks.find(n => 156 + n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid 157 + ); 58 158 if (match) { 59 159 match.lastIpcObject = network; 60 160 } else { 61 - rNetworks.push(apComp.createObject(root, { 62 - lastIpcObject: network 63 - })); 161 + rNetworks.push(apComp.createObject(root, { lastIpcObject: network })); 64 162 } 65 163 } 66 164 } ··· 75 173 readonly property int frequency: lastIpcObject.frequency 76 174 readonly property bool active: lastIpcObject.active 77 175 readonly property string icon: { 78 - if (!active) return "network-wireless-signal-none-symbolic"; 79 - 80 176 if (strength >= 75) return "network-wireless-signal-excellent-symbolic"; 81 177 if (strength >= 50) return "network-wireless-signal-good-symbolic"; 82 178 if (strength >= 25) return "network-wireless-signal-ok-symbolic"; 83 - return "network-wireless-signal-weak-symbolic"; 179 + if (strength > 0) return "network-wireless-signal-weak-symbolic"; 180 + return "network-wireless-signal-none-symbolic"; 84 181 } 85 182 } 86 183 87 184 Component { 88 185 id: apComp 89 - 90 186 AccessPoint {} 91 187 } 92 188 } 189 +
+12 -1
home/isabel/gui/quickshell/services/Notifications.qml
··· 3 3 import Quickshell 4 4 import Quickshell.Io 5 5 import Quickshell.Services.Notifications 6 + import "root:/data" 6 7 7 8 Singleton { 8 9 id: root 9 10 10 - property list<Notification> list: notifactionServer.trackedNotifications.values.filter(notification => notification.tracked) 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) 11 17 12 18 NotificationServer { 13 19 id: notifactionServer 14 20 onNotification: (notification) => { 15 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 + } 16 26 } 17 27 18 28 actionsSupported: true ··· 28 38 } 29 39 } 30 40 } 41 +
+4 -3
home/isabel/gui/quickshell/shell.qml
··· 1 - //@ pragma IconTheme Cosmic 1 + //@ pragma IconTheme Adwaita 2 2 3 3 import Quickshell 4 4 import "root:/modules" 5 5 6 6 Scope { 7 - Bar {} 8 - Desktop {} 7 + Bar {} 8 + Desktop {} 9 + Osd {} 9 10 }
+2
home/isabel/gui/quickshell.nix
··· 6 6 paths = [ 7 7 pkgs.quickshell 8 8 pkgs.kdePackages.qtimageformats 9 + pkgs.adwaita-icon-theme 10 + pkgs.kdePackages.kirigami.unwrapped 9 11 ]; 10 12 meta.mainProgram = pkgs.quickshell.meta.mainProgram; 11 13 };
+6 -5
home/isabel/gui/vicinae/default.nix
··· 14 14 extensions = map mkMyVicinaeExt [ 15 15 { 16 16 extName = "nix"; 17 - npmDepsHash = "sha256-Zx+QPVWWppz6mvQKyu4c6ND8E4TeeK12assE2khE/sA="; 17 + npmDepsHash = "sha256-HPWNUznCWVPz39PlPEBR7GpgbC0DuIAvVBdB2GAs47A="; 18 18 } 19 19 { 20 20 extName = "wifi-commander"; ··· 24 24 extName = "bluetooth"; 25 25 npmDepsHash = "sha256-cpyuJTc3a7oLibKUY2EhD33w8/35frfwIaGFKFezvts="; 26 26 } 27 - { 28 - extName = "mullvad"; 29 - npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0="; 30 - } 27 + # broken 28 + # { 29 + # extName = "mullvad"; 30 + # npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0="; 31 + # } 31 32 ]; 32 33 }; 33 34 }
+2 -2
home/isabel/gui/vicinae/extension.nix
··· 22 22 fetchFromGitHub { 23 23 owner = "vicinaehq"; 24 24 repo = "extensions"; 25 - rev = "ec7334e9bb636f4771580238bd3569b58dbce879"; 26 - hash = "sha256-C2b6upygLE6xUP/cTSKZfVjMXOXOOqpP5Xmgb9r2dhA="; 25 + rev = "62f81e63d0420d6a310092746a96d7c105f7a53e"; 26 + hash = "sha256-Tqd5BOxfCtVWY19Gl32Fq5xsV3sTepItub20OQYgPmU="; 27 27 } 28 28 + "/extensions/${extName}" 29 29 );
+1 -1
home/isabel/packages.nix
··· 54 54 # jellyfin-media-player 55 55 # insomnia # rest client 56 56 swappy # used for screenshot area selection 57 - wl-clipboard 57 + wl-clipboard-rs 58 58 wl-gammactl 59 59 # keep-sorted end 60 60 ;
+1 -1
home/isabel/system/ssh.nix
··· 36 36 37 37 "aphrodite".hostname = "95.111.208.153"; 38 38 39 - "athena".hostname = "192.168.86.3"; 39 + "athena".hostname = "192.168.1.135"; 40 40 41 41 "aur.archlinux.org" = { 42 42 user = "aur";
+63 -27
justfile
··· 3 3 # rebuild is also set as a var so you can add --set to change it if you need to 4 4 5 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" } 6 + system-args := if os() == "macos" { "" } else { "--sudo --no-reexec" } 8 7 9 8 [private] 10 9 default: ··· 13 12 # rebuild group 14 13 15 14 [group('rebuild')] 15 + [no-exit-message] 16 16 [private] 17 17 builder goal *args: 18 + #!/usr/bin/env bash 19 + set -euo pipefail 18 20 {{ rebuild }} {{ goal }} \ 19 21 --flake {{ flake }} \ 22 + --log-format internal-json \ 20 23 {{ system-args }} \ 21 24 {{ args }} \ 22 - |& {{ nom-cmd }} 25 + |& nom --json 23 26 24 27 [group('rebuild')] 28 + [no-exit-message] 25 29 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' 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 31 44 32 45 [group('rebuild')] 46 + [no-exit-message] 33 47 deploy-all: 34 - just deploy minerva 35 - just deploy aphrodite 36 - just deploy skadi 37 - just deploy hephaestus 38 - just deploy isis 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" 39 64 40 65 # rebuild the boot 41 66 [group('rebuild')] 67 + [no-exit-message] 42 68 boot *args: (builder "boot" args) 43 69 44 70 # test what happens when you switch 45 71 [group('rebuild')] 72 + [no-exit-message] 46 73 test *args: (builder "test" args) 47 74 48 75 # switch the new system configuration 49 76 [group('rebuild')] 77 + [no-exit-message] 50 78 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 79 + #!/usr/bin/env bash 80 + set -euo pipefail 81 + before=$(readlink /run/current-system) 82 + just builder switch {{ args }} 83 + lix diff "$before" 55 84 56 85 [group('rebuild')] 57 86 [macos] 87 + [no-exit-message] 58 88 provision host: 59 89 sudo nix run github:LnL7/nix-darwin -- switch --flake {{ flake }}#{{ host }} 60 90 sudo -i nix-env --uninstall lix # we need to remove the none declarative install of lix 61 91 62 92 # package group 63 - 64 93 # 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 94 72 95 # build the iso image, you must specify the image you want to build 73 96 [group('package')] 74 - iso image: (build "nixosConfigurations." + image + ".config.system.build.isoImage") 97 + [no-exit-message] 98 + iso image: 99 + nom build {{ flake }}#nixosConfigurations.{{ image }}.config.system.build.isoImage 75 100 76 101 # build the tarball, you must specify the host you want to build 77 102 [group('package')] 103 + [no-exit-message] 78 104 tar host: 79 105 sudo nix run {{ flake }}#nixosConfigurations.{{ host }}.config.system.build.tarballBuilder 80 106 ··· 82 108 83 109 # check the flake for errors 84 110 [group('dev')] 111 + [no-exit-message] 85 112 check *args: 86 113 nix flake check --option allow-import-from-derivation false {{ args }} 87 114 88 115 [group('dev')] 116 + [no-exit-message] 89 117 repl-host host=`hostname`: 90 118 nix repl .#nixosConfigurations.{{ host }} 91 119 92 120 # update a set of given inputs 93 121 [group('dev')] 122 + [no-exit-message] 94 123 update *input: 95 124 nix flake update {{ input }} \ 96 125 --refresh \ ··· 100 129 101 130 # build & serve the docs locally 102 131 [group('dev')] 132 + [no-exit-message] 103 133 serve: 104 134 nix run {{ flake }}#docs.serve 105 135 106 136 # push to the mirrors 107 137 [group('dev')] 138 + [no-exit-message] 108 139 push-mirrors: 109 140 git push git@gitlab.com:isabelroses/dotfiles.git 110 141 git push --mirror ssh://git@codeberg.org/isabel/dotfiles.git ··· 112 143 113 144 # rotate all secrets 114 145 [group('dev')] 146 + [no-exit-message] 115 147 roate-secrets: 116 - find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {} 148 + find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {} 117 149 118 150 # update the secret keys 119 151 [group('dev')] 152 + [no-exit-message] 120 153 update-secrets: 121 - find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {} 154 + find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {} 122 155 123 156 # utils group 124 157 ··· 126 159 127 160 # verify the integrity of the nix store 128 161 [group('utils')] 162 + [no-exit-message] 129 163 verify *args: 130 164 nix-store --verify {{ args }} 131 165 132 166 # repairs the nix store from any breakages it may have 133 167 [group('utils')] 168 + [no-exit-message] 134 169 repair: (verify "--check-contents --repair") 135 170 136 171 # clean the nix store and optimise it 137 172 [group('utils')] 173 + [no-exit-message] 138 174 clean: 139 175 nix-collect-garbage --delete-older-than 3d 140 176 nix store optimise
+1 -1
modules/nixos/hardware/cpu/default.nix
··· 20 20 ] 21 21 ); 22 22 default = null; 23 - description = "The manufacturer of the primary system gpu"; 23 + description = "The manufacturer of the primary system cpu"; 24 24 }; 25 25 }
+2
modules/nixos/hardware/gpu/nvidia.nix
··· 1 + # A worthy read before attempting to change anything here: 2 + # <https://discourse.nixos.org/t/can-we-solve-the-nvidia-situation/73198> 1 3 { 2 4 lib, 3 5 pkgs,
+17 -15
modules/nixos/networking/firewall.nix
··· 1 1 { 2 2 lib, 3 - pkgs, 4 3 config, 5 4 ... 6 5 }: ··· 16 15 # inactive until opensnitch UI is opened 17 16 # services.opensnitch.enable = device.type != "server"; 18 17 19 - networking.firewall = { 20 - enable = true; 21 - package = pkgs.iptables; 18 + networking = { 19 + nftables.enable = true; 22 20 23 - allowedTCPPorts = [ ]; 24 - allowedUDPPorts = [ ]; 21 + firewall = { 22 + enable = true; 25 23 26 - allowedTCPPortRanges = [ ]; 27 - allowedUDPPortRanges = [ ]; 24 + allowedTCPPorts = [ ]; 25 + allowedUDPPorts = [ ]; 28 26 29 - # allow servers to be pinnged, but not our clients 30 - allowPing = config.garden.profiles.server.enable; 27 + allowedTCPPortRanges = [ ]; 28 + allowedUDPPortRanges = [ ]; 31 29 32 - # make a much smaller and easier to read log 33 - logReversePathDrops = true; 34 - logRefusedConnections = false; 30 + # allow servers to be pinnged, but not our clients 31 + allowPing = config.garden.profiles.server.enable; 35 32 36 - # Don't filter DHCP packets, according to nixops-libvirtd 37 - checkReversePath = mkForce false; 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 + }; 38 40 }; 39 41 }; 40 42 }
+2 -2
modules/nixos/networking/networkmanager.nix
··· 5 5 ... 6 6 }: 7 7 let 8 - inherit (lib) mkIf optionalAttrs; 8 + inherit (lib) optionalAttrs; 9 9 in 10 10 { 11 11 garden.packages = optionalAttrs config.garden.profiles.graphical.enable { ··· 40 40 }; 41 41 42 42 # causes server to be unreachable over SSH 43 - ethernet.macAddress = mkIf (!config.garden.profiles.server.enable) "random"; 43 + # ethernet.macAddress = mkIf (!config.garden.profiles.server.enable) "random"; 44 44 }; 45 45 }
+1 -2
modules/nixos/networking/systemd.nix
··· 3 3 inherit (lib) mkForce concatMapAttrs genAttrs; 4 4 5 5 ethernetDevices = [ 6 - "wlp1s0f0u8" # wifi dongle 7 - "enp7s0" # ethernet interface on the motherboard 6 + "enp0s31f6" # ethernet interface on the motherboard 8 7 ]; 9 8 in 10 9 {
+4 -1
modules/nixos/networking/tailscale.nix
··· 20 20 in 21 21 { 22 22 options.garden.system.networking.tailscale = { 23 - enable = mkEnableOption "Tailscale VPN"; 23 + enable = mkEnableOption "Tailscale VPN" // { 24 + default = true; 25 + defaultText = "true"; 26 + }; 24 27 25 28 defaultFlags = mkOption { 26 29 type = listOf str;
+49 -16
modules/nixos/nix.nix
··· 1 1 { 2 - nix = { 3 - # set the nix store to clean every Monday at 3am 4 - gc.dates = "Mon *-*-* 03:00"; 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 + }; 5 24 6 - # automatically optimize /nix/store by removing hard links 7 - optimise = { 8 - automatic = true; 9 - dates = [ "04:00" ]; 10 - }; 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 + } 11 35 12 - # Make builds run with a low priority, keeping the system fast 13 - # daemonCPUSchedPolicy = "idle"; 14 - # daemonIOSchedClass = "idle"; 15 - # daemonIOSchedPriority = 7; 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 + }; 16 45 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 - }; 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 + ]; 21 54 }
+1 -1
modules/nixos/services/akkoma/default.nix
··· 29 29 30 30 "favicon.png" = pkgs.fetchurl { 31 31 url = "https://gravatar.com/avatar/c487c810e09878b4bd90df713a7c9523?size=512"; 32 - hash = "sha256-psRjtuG+U8KJtWWfa3HAYjOQrjAYQAymDEc6HqhQmnk="; 32 + hash = "sha256-LRDHPMkJmtkjA/Bd0fpMm5dLf4nhNSIXAAOG8u2ZSqs="; 33 33 }; 34 34 35 35 "emoji/blobs" = pkgs.blobs_gg;
+93
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 + transmission.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 + "${cfg.mediaDir}/downloads/incomplete" 80 + "${cfg.mediaDir}/downloads/watch" 81 + "${cfg.mediaDir}/downloads/sonarr" 82 + "${cfg.mediaDir}/downloads/radarr" 83 + ] 84 + (_: { 85 + d = { 86 + mode = "0755"; 87 + user = "transmission"; 88 + group = "media"; 89 + }; 90 + }); 91 + }; 92 + }; 93 + }
+5
modules/nixos/services/default.nix
··· 3 3 # keep-sorted start 4 4 ./akkoma 5 5 ./anubis.nix 6 + ./arr.nix 6 7 ./attic.nix 7 8 ./atuin.nix 8 9 ./blahaj.nix ··· 23 24 ./piper.nix 24 25 ./port-collector.nix 25 26 ./postgresql.nix 27 + ./prowlarr.nix 28 + ./radarr.nix 26 29 ./redis.nix 30 + ./sonarr.nix 31 + ./transmission.nix 27 32 ./uptime-kuma.nix 28 33 ./vaultwarden.nix 29 34 ./wakapi.nix
+2
modules/nixos/services/jellyfin.nix
··· 26 26 jellyfin = { 27 27 enable = true; 28 28 dataDir = "/srv/storage/jellyfin"; 29 + group = "media"; 30 + inherit (config.garden.services.arr) openFirewall; 29 31 }; 30 32 31 33 cloudflared.tunnels.${config.networking.hostName} = {
+26
modules/nixos/services/pds/default.nix
··· 44 44 enable = true; 45 45 pdsadmin.enable = true; 46 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 + 47 70 environmentFiles = [ config.sops.secrets.pds-env.path ]; 48 71 49 72 settings = { ··· 75 98 PDS_OAUTH_PROVIDER_ERROR_COLOR = "#F6598E"; 76 99 77 100 PDS_SERVICE_HANDLE_DOMAINS = ".tgirl.beauty"; 101 + 102 + # custom session duration: 30 days 103 + PDS_OAUTH_AUTHENTICATION_MAX_AGE = "2592000000"; 78 104 }; 79 105 }; 80 106
+1 -1
modules/nixos/services/port-collector.nix
··· 5 5 ... 6 6 }: 7 7 let 8 - srvs = lib.mapAttrs (_: srv: srv.port) config.garden.services; 8 + srvs = lib.mapAttrs (_: srv: srv.port or 0) config.garden.services; 9 9 10 10 # from port = 3000; to servicesOnPort.3000 = [ "pds" "kittr" ]; 11 11 portsToServices = lib.foldl' (
+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 + }
+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
··· 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 + }
+85
modules/nixos/services/transmission.nix
··· 1 + { 2 + lib, 3 + self, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) mkIf genAttrs mkForce; 9 + inherit (self.lib) mkServiceOption; 10 + 11 + cfg = config.garden.services.transmission; 12 + inherit (config.garden.services) arr; 13 + in 14 + { 15 + options.garden.services.transmission = mkServiceOption "transmission" { 16 + port = 3019; 17 + host = "0.0.0.0"; 18 + }; 19 + 20 + config = mkIf config.garden.services.transmission.enable { 21 + # i'm replacing this with systemd tempfiles 22 + system.activationScripts.transmission-daemon = mkForce ""; 23 + 24 + systemd.tmpfiles.settings."media-downloads-config" = 25 + genAttrs 26 + [ 27 + "${config.services.transmission.home}" 28 + "${config.services.transmission.home}/.config" 29 + "${config.services.transmission.home}/.config/transmission-daemon" 30 + ] 31 + (_: { 32 + d = { 33 + mode = "0750"; 34 + user = "transmission"; 35 + group = "media"; 36 + }; 37 + }); 38 + 39 + services.transmission = { 40 + enable = true; 41 + group = "media"; 42 + 43 + inherit (config.garden.services.arr) openFirewall; 44 + home = "/srv/storage/transmission"; 45 + 46 + settings = { 47 + download-dir = "${arr.mediaDir}/downloads"; 48 + incomplete-dir-enabled = true; 49 + incomplete-dir = "${arr.mediaDir}/downloads/incomplete"; 50 + watch-dir-enabled = true; 51 + watch-dir = "${arr.mediaDir}/downloads/watch"; 52 + 53 + rpc-port = cfg.port; 54 + rpc-bind-address = cfg.host; 55 + rpc-authentication-required = false; 56 + 57 + rpc-whitelist-enabled = true; 58 + rpc-whitelist = lib.concatStringsSep "," [ 59 + "127.0.0.1" 60 + "::1" 61 + "192.168.1.*" 62 + ]; 63 + 64 + blocklist-enabled = true; 65 + blocklist-url = "https://github.com/Naunter/BT_BlockLists/raw/master/bt_blocklists.gz"; 66 + 67 + anti-brute-force-enabled = true; 68 + anti-brute-force-threshold = 10; 69 + 70 + encryption = 1; 71 + port-forwarding-enabled = false; 72 + 73 + utp-enabled = false; 74 + umask = "002"; 75 + peer-limit-global = 500; 76 + cache-size-mb = 256; 77 + download-queue-enabled = true; 78 + download-queue-size = 20; 79 + speed-limit-up = 500; 80 + speed-limit-up-enabled = true; 81 + message-level = 4; 82 + }; 83 + }; 84 + }; 85 + }
+2 -2
modules/nixos/system/scheduler.nix
··· 1 - { pkgs, config, ... }: 1 + { pkgs, ... }: 2 2 { 3 3 services.scx = { 4 - inherit (config.garden.profiles.workstation) enable; 4 + # inherit (config.garden.profiles.workstation) enable; 5 5 scheduler = "scx_bpfland"; 6 6 package = pkgs.scx.rustscheds; 7 7 };
+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] 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 2 sops: 3 3 age: 4 4 - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2 ··· 71 71 lm9UxzoNda1OtptX52t1sWVYkx1pwvlTbTmuNy/8uVufZVekdPBz+pywuJbSJlh2 72 72 bH8K6Q== 73 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] 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 76 unencrypted_suffix: _unencrypted 77 77 version: 3.11.0
+1 -3
systems/amaterasu/default.nix
··· 25 25 keyboard = "us"; 26 26 }; 27 27 28 - services.xmrig.enable = true; 28 + # services.xmrig.enable = true; 29 29 30 30 system = { 31 31 boot = { ··· 43 43 bluetooth.enable = true; 44 44 printing.enable = false; 45 45 emulation.enable = true; 46 - 47 - security.auditd.enable = true; 48 46 }; 49 47 }; 50 48 }
+10 -11
systems/athena/default.nix
··· 1 + { lib, ... }: 1 2 { 2 3 imports = [ ./hardware.nix ]; 3 4 ··· 19 20 }; 20 21 }; 21 22 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; 23 + system.boot = { 24 + loader = "systemd-boot"; 25 + secureBoot = false; 26 + loadRecommendedModules = true; 27 + enableKernelTweaks = true; 28 + initrd.enableTweaks = true; 32 29 }; 33 30 34 31 services = { 35 32 cloudflared.enable = true; 36 33 immich.enable = true; 37 - jellyfin.enable = true; 34 + arr.enable = true; 38 35 borgbackup.enable = true; 39 36 }; 40 37 }; 38 + 39 + services.smartd.enable = lib.mkForce false; 41 40 }
+5
systems/athena/hardware.nix
··· 9 9 device = "/dev/disk/by-uuid/2524-71ED"; 10 10 fsType = "vfat"; 11 11 }; 12 + 13 + "/media" = { 14 + device = "/dev/disk/by-uuid/c4f7c302-f492-47dd-8bfd-e3073c1923bd"; 15 + fsType = "ext4"; 16 + }; 12 17 }; 13 18 14 19 swapDevices = [ { device = "/dev/disk/by-uuid/e45cd5a5-ec02-4933-9adb-5d968f270f54"; } ];
+1 -1
systems/skadi/default.nix
··· 9 9 }; 10 10 11 11 device = { 12 - cpu = "amd"; 12 + cpu = null; 13 13 gpu = null; 14 14 }; 15 15
-1
systems/valkyrie/default.nix
··· 33 33 emulation.enable = true; 34 34 35 35 bluetooth.enable = false; 36 - security.auditd.enable = true; 37 36 }; 38 37 }; 39 38 }