├── .envrc ├── .github └── workflows │ └── check.yml ├── .gitignore ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── home ├── README.md ├── default.nix ├── editors │ ├── helix │ │ ├── default.nix │ │ └── languages.nix │ └── jetbrains │ │ └── idea.nix ├── profiles │ ├── default.nix │ ├── io │ │ └── default.nix │ └── server │ │ └── default.nix ├── programs │ ├── anyrun │ │ ├── default.nix │ │ ├── style-dark.css │ │ └── style-light.css │ ├── browsers │ │ ├── chromium.nix │ │ ├── firefox.nix │ │ └── zen.nix │ ├── default.nix │ ├── games │ │ └── default.nix │ ├── gtk.nix │ ├── media │ │ ├── default.nix │ │ ├── mpv.nix │ │ └── rnnoise.nix │ ├── office │ │ ├── default.nix │ │ └── zathura.nix │ ├── qt.nix │ └── wayland │ │ ├── default.nix │ │ ├── hyprland │ │ ├── binds.nix │ │ ├── default.nix │ │ ├── rules.nix │ │ ├── settings.nix │ │ └── smartgaps.nix │ │ ├── hyprlock.nix │ │ └── wlogout.nix ├── services │ ├── ags │ │ ├── .gitignore │ │ ├── README.md │ │ ├── config.js │ │ ├── default.nix │ │ ├── dprint.json │ │ ├── imports.js │ │ ├── services │ │ │ ├── brightness.js │ │ │ └── osd.js │ │ ├── style.scss │ │ ├── style │ │ │ ├── bar.scss │ │ │ ├── colors-dark.scss │ │ │ ├── colors-light.scss │ │ │ ├── colors.scss │ │ │ ├── general.scss │ │ │ ├── music.scss │ │ │ ├── notifications.scss │ │ │ ├── osd.scss │ │ │ ├── prelude.scss │ │ │ └── system-menu.scss │ │ ├── tsconfig.json │ │ ├── utils │ │ │ ├── audio.js │ │ │ ├── battery.js │ │ │ ├── bluetooth.js │ │ │ ├── hyprland.js │ │ │ ├── icons.js │ │ │ ├── mpris.js │ │ │ ├── net.js │ │ │ └── popup_window.js │ │ └── windows │ │ │ ├── bar │ │ │ ├── main.js │ │ │ └── modules │ │ │ │ ├── battery.js │ │ │ │ ├── bluetooth.js │ │ │ │ ├── cpu_ram.js │ │ │ │ ├── date.js │ │ │ │ ├── music.js │ │ │ │ ├── net.js │ │ │ │ ├── tray.js │ │ │ │ └── workspaces.js │ │ │ ├── music │ │ │ ├── controls.js │ │ │ ├── cover.js │ │ │ ├── main.js │ │ │ ├── player_info.js │ │ │ ├── time_info.js │ │ │ └── title_artists.js │ │ │ ├── notifications │ │ │ └── popups.js │ │ │ ├── osd │ │ │ └── main.js │ │ │ └── system-menu │ │ │ ├── battery_info.js │ │ │ ├── main.js │ │ │ ├── powerprofiles.js │ │ │ ├── sliders.js │ │ │ └── toggles.js │ ├── cinny.nix │ ├── media │ │ ├── playerctl.nix │ │ └── spotifyd.nix │ ├── quickshell │ │ ├── components │ │ │ └── bar │ │ │ │ ├── Battery.qml │ │ │ │ ├── Clock.qml │ │ │ │ ├── Mpris.qml │ │ │ │ ├── Resources.qml │ │ │ │ ├── Text.qml │ │ │ │ ├── Tray.qml │ │ │ │ └── workspaces │ │ │ │ ├── Workspace.qml │ │ │ │ └── Workspaces.qml │ │ ├── default.nix │ │ ├── shell.qml │ │ ├── utils │ │ │ ├── Colors.qml │ │ │ ├── HyprlandUtils.qml │ │ │ ├── Resources.qml │ │ │ └── Time.qml │ │ └── windows │ │ │ └── Bar.qml │ ├── system │ │ ├── kdeconnect.nix │ │ ├── polkit-agent.nix │ │ ├── power-monitor.nix │ │ ├── syncthing.nix │ │ ├── tailray.nix │ │ ├── theme.nix │ │ └── udiskie.nix │ └── wayland │ │ ├── gammastep.nix │ │ ├── hypridle.nix │ │ ├── hyprpaper.nix │ │ └── wluma.nix ├── specialisations.nix └── terminal │ ├── default.nix │ ├── emulators │ ├── alacritty.nix │ ├── foot.nix │ ├── kitty.nix │ └── wezterm.nix │ ├── programs │ ├── bat.nix │ ├── btop.nix │ ├── cli.nix │ ├── default.nix │ ├── git.nix │ ├── nix.nix │ ├── skim.nix │ ├── xdg.nix │ └── yazi │ │ ├── default.nix │ │ └── theme │ │ ├── filetype.nix │ │ ├── icons.nix │ │ ├── manager.nix │ │ └── status.nix │ └── shell │ ├── starship.nix │ ├── zoxide.nix │ └── zsh.nix ├── hosts ├── README.md ├── default.nix ├── io │ ├── default.nix │ ├── hardware-configuration.nix │ ├── hyprland.nix │ └── powersave.nix └── wsl │ └── default.nix ├── lib ├── README.md ├── colors │ └── default.nix ├── default.nix └── repl.nix ├── modules ├── default.nix └── theme │ └── default.nix ├── pkgs ├── README.md ├── bibata-hyprcursor │ ├── configure.py │ └── default.nix ├── default.nix ├── repl │ └── default.nix └── wl-ocr │ └── default.nix ├── pre-commit-hooks.nix ├── secrets ├── secrets.nix └── spotify.age └── system ├── README.md ├── core ├── boot.nix ├── default.nix ├── lanzaboote.nix ├── security.nix └── users.nix ├── default.nix ├── hardware ├── bluetooth.nix ├── fwupd.nix └── graphics.nix ├── network ├── avahi.nix ├── default.nix ├── spotify.nix ├── syncthing.nix └── tailscale.nix ├── nix ├── builders.nix ├── default.nix ├── nh.nix ├── nixpkgs.nix └── substituters.nix ├── programs ├── default.nix ├── fonts.nix ├── gamemode.nix ├── games.nix ├── home-manager.nix ├── hyprland │ ├── binds.nix │ ├── default.nix │ ├── rules.nix │ ├── settings.nix │ └── smartgaps.nix ├── qt.nix ├── xdg.nix └── zsh.nix └── services ├── backlight.nix ├── default.nix ├── gnome-services.nix ├── greetd.nix ├── kanata ├── default.nix └── main.kbd ├── location.nix ├── pipewire.nix └── power.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | checks: 7 | name: Check expressions 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: cachix/install-nix-action@v26 13 | with: 14 | install_url: https://nixos.org/nix/install 15 | extra_nix_config: | 16 | auto-optimise-store = true 17 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 18 | experimental-features = nix-command flakes 19 | - run: nix flake check 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .pre-commit-config.yaml 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Mihai Fufezan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

fufexan/dotfiles

2 | 3 | # 🗒 About 4 | 5 | In-house baked configs for Home-Manager and NixOS. Borrowed bits sprinkled on 6 | top. Using [flakes](https://nixos.wiki/wiki/Flakes) and 7 | [flake-parts](https://github.com/hercules-ci/flake-parts). 8 | 9 | See an overview of the flake outputs by running 10 | `nix flake show github:fufexan/dotfiles`. 11 | 12 | ## 🗃️ Contents 13 | 14 | - [hosts](./hosts): host-specific configuration 15 | - [home](./home): [Home Manager](https://github.com/nix-community/home-manager) 16 | config 17 | - [lib](./lib): helper functions 18 | - [modules](./modules): NixOS modules 19 | - [pkgs](./pkgs): package definitions 20 | - [system](./system): common NixOS configurations 21 | 22 | # 📦 Exported packages 23 | 24 | Run packages directly with: 25 | 26 | ```console 27 | nix run github:fufexan/dotfiles#packageName 28 | ``` 29 | 30 | Or install from the `packages` output. For example: 31 | 32 | ```nix 33 | # flake.nix 34 | { 35 | inputs.fufexan-dotfiles = { 36 | url = "github:fufexan/dotfiles"; 37 | inputs.nixpkgs.follows = "nixpkgs"; 38 | }; 39 | } 40 | 41 | # configuration.nix 42 | {pkgs, inputs, ...}: { 43 | environment.systemPackages = [ 44 | inputs.fufexan-dotfiles.packages."x86_64-linux".packageName 45 | ]; 46 | } 47 | ``` 48 | 49 | ## 💻 Desktop preview 50 | 51 | Currently, my widgets are created using [Ags](https://github.com/Aylur/ags/). If 52 | you're looking for the [Eww](https://github.com/elkowar/eww) version, you can 53 | find it [here](https://github.com/fufexan/dotfiles/tree/eww). 54 | 55 |
56 | 57 | Dark 58 | 59 | 60 | Desktop Preview Dark 61 | 62 | *Hint: click to go to a video showcase* 63 |
64 |
65 | 66 | Light 67 | 68 | Desktop Preview Light 69 |
70 | 71 |
72 | 73 | Previous versions 74 | 75 | Desktop Preview 76 | Desktop Preview 77 |
78 | 79 | # 💾 Resources 80 | 81 | Other configurations from where I learned and copied: 82 | 83 | - [colemickens/nixcfg](https://github.com/colemickens/nixcfg) 84 | - [flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus) 85 | - [gytis-ivaskevicius/nixfiles](https://github.com/gytis-ivaskevicius/nixfiles) 86 | - [Mic92/dotfiles](https://github.com/Mic92/dotfiles) 87 | - [NobbZ/nixos-config](https://github.com/NobbZ/nixos-config) 88 | - [privatevoid-net/privatevoid-infrastructure](https://github.com/privatevoid-net/privatevoid-infrastructure) 89 | - [RicArch97/nixos-config](https://github.com/RicArch97/nixos-config) 90 | - [viperML/dotfiles](https://github.com/viperML/dotfiles) 91 | 92 | # 👥 People 93 | 94 | These are the people whom I've taken inspiration from while writing these 95 | configs. There surely are more but I tend to forget. Regardless, I am thankful 96 | to all of them. 97 | 98 | DieracDelta - gytis-ivaskevicius - hlissner - keksbg - Kranzes - 99 | matthewcroughan - max-privatevoid - Misterio77 - NobbZ - OPNA2608 - 100 | pnotequalnp - RicArch97 - tadeokondrak - viperML - Xe - yusdacra 101 | -------------------------------------------------------------------------------- /home/README.md: -------------------------------------------------------------------------------- 1 | # Home config 2 | 3 | Home-Manager configurations for different hosts. 4 | 5 | | Name | Description | 6 | | --------------------- | ---------------------------------------------------- | 7 | | `default.nix` | Home-Manager specific configuration | 8 | | `editors` | Helix & Neovim | 9 | | `profiles` | Per-device/user profiles, entry point of the configs | 10 | | `programs` | Programs, games, media, etc | 11 | | `services` | Services like `ags`, etc | 12 | | `terminal` | Terminal programs, shells, emulators, etc | 13 | | `specialisations.nix` | Light/Dark theme specialisations | 14 | -------------------------------------------------------------------------------- /home/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | self, 3 | inputs, 4 | ... 5 | }: { 6 | imports = [ 7 | ./specialisations.nix 8 | ./terminal 9 | inputs.nix-index-db.hmModules.nix-index 10 | inputs.tailray.homeManagerModules.default 11 | self.nixosModules.theme 12 | ]; 13 | 14 | home = { 15 | username = "mihai"; 16 | homeDirectory = "/home/mihai"; 17 | stateVersion = "23.11"; 18 | extraOutputsToInstall = ["doc" "devdoc"]; 19 | }; 20 | 21 | # disable manuals as nmd fails to build often 22 | manual = { 23 | html.enable = false; 24 | json.enable = false; 25 | manpages.enable = false; 26 | }; 27 | 28 | # let HM manage itself when in standalone mode 29 | programs.home-manager.enable = true; 30 | } 31 | -------------------------------------------------------------------------------- /home/editors/helix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: { 6 | imports = [./languages.nix]; 7 | 8 | programs.helix = { 9 | enable = true; 10 | package = inputs.helix.packages.${pkgs.system}.default; 11 | extraPackages = with pkgs; [ 12 | markdown-oxide 13 | nodePackages.vscode-langservers-extracted 14 | shellcheck 15 | ]; 16 | 17 | settings = { 18 | theme = "onedark"; 19 | editor = { 20 | color-modes = true; 21 | completion-trigger-len = 1; 22 | completion-replace = true; 23 | cursorline = true; 24 | cursor-shape = { 25 | insert = "bar"; 26 | normal = "block"; 27 | select = "underline"; 28 | }; 29 | indent-guides.render = true; 30 | inline-diagnostics = { 31 | cursor-line = "hint"; 32 | other-lines = "error"; 33 | }; 34 | lsp.display-inlay-hints = true; 35 | soft-wrap.enable = true; 36 | statusline.center = ["position-percentage"]; 37 | true-color = true; 38 | whitespace.characters = { 39 | newline = "↴"; 40 | tab = "⇥"; 41 | }; 42 | }; 43 | 44 | keys = { 45 | normal = { 46 | w = "move_next_sub_word_start"; 47 | b = "move_prev_sub_word_start"; 48 | e = "move_next_sub_word_end"; 49 | "A-w" = "move_next_word_start"; 50 | "A-b" = "move_prev_word_start"; 51 | "A-e" = "move_next_word_end"; 52 | }; 53 | normal.space.u = { 54 | f = ":format"; # format using LSP formatter 55 | w = ":set whitespace.render all"; 56 | W = ":set whitespace.render none"; 57 | }; 58 | }; 59 | }; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /home/editors/jetbrains/idea.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: let 2 | idea = pkgs.jetbrains.idea-ultimate; 3 | in { 4 | home.packages = [idea]; 5 | 6 | # Ensure running on Wayland 7 | xdg.configFile."JetBrains/IntelliJIdea${idea.version}/idea64.vmoptions".text = "-Dawt.toolkit.name=WLToolkit"; 8 | } 9 | -------------------------------------------------------------------------------- /home/profiles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | "mihai@io" = [ 3 | ../. 4 | ./io 5 | ]; 6 | 7 | server = [ 8 | ../. 9 | ./server 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /home/profiles/io/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | # editors 4 | ../../editors/helix 5 | ../../editors/jetbrains/idea.nix 6 | 7 | # programs 8 | ../../programs 9 | ../../programs/games 10 | ../../programs/wayland 11 | 12 | # services 13 | ../../services/ags 14 | ../../services/quickshell 15 | # ../../services/cinny.nix 16 | 17 | # media services 18 | ../../services/media/playerctl.nix 19 | # ../../services/media/spotifyd.nix 20 | 21 | # system services 22 | ../../services/system/kdeconnect.nix 23 | ../../services/system/polkit-agent.nix 24 | ../../services/system/power-monitor.nix 25 | ../../services/system/syncthing.nix 26 | ../../services/system/tailray.nix 27 | ../../services/system/theme.nix 28 | ../../services/system/udiskie.nix 29 | 30 | # wayland-specific 31 | ../../services/wayland/gammastep.nix 32 | ../../services/wayland/hyprpaper.nix 33 | ../../services/wayland/hypridle.nix 34 | # ../../services/wayland/wluma.nix 35 | 36 | # terminal emulators 37 | ../../terminal/emulators/foot.nix 38 | ../../terminal/emulators/wezterm.nix 39 | ]; 40 | 41 | wayland.windowManager.hyprland.settings = let 42 | # Generated using https://gist.github.com/fufexan/e6bcccb7787116b8f9c31160fc8bc543 43 | accelpoints = "0.5 0.000 0.053 0.115 0.189 0.280 0.391 0.525 0.687 0.880 1.108 1.375 1.684 2.040 2.446 2.905 3.422 4.000 4.643 5.355 6.139"; 44 | in { 45 | monitor = [ 46 | # "DP-1, preferred, auto-left, auto" 47 | # "DP-2, preferred, auto-left, auto" 48 | "eDP-1, preferred, auto, 1.600000" 49 | ]; 50 | 51 | device = { 52 | name = "elan2841:00-04f3:31eb-touchpad"; 53 | accel_profile = "custom ${accelpoints}"; 54 | scroll_points = accelpoints; 55 | natural_scroll = true; 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /home/profiles/server/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../../editors/helix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /home/programs/anyrun/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: { 6 | programs.anyrun = { 7 | enable = true; 8 | package = inputs.anyrun.packages.x86_64-linux.default; 9 | 10 | config = { 11 | plugins = with inputs.anyrun.packages.${pkgs.system}; [ 12 | uwsm_app 13 | randr 14 | rink 15 | shell 16 | symbols 17 | ]; 18 | 19 | width.fraction = 0.25; 20 | y.fraction = 0.3; 21 | hidePluginInfo = true; 22 | closeOnClick = true; 23 | }; 24 | 25 | extraCss = builtins.readFile (./. + "/style-dark.css"); 26 | 27 | extraConfigFiles = { 28 | "uwsm_app.ron".text = '' 29 | Config( 30 | desktop_actions: false, 31 | max_entries: 5, 32 | ) 33 | ''; 34 | 35 | "shell.ron".text = '' 36 | Config( 37 | prefix: ">" 38 | ) 39 | ''; 40 | 41 | "randr.ron".text = '' 42 | Config( 43 | prefi: ":dp", 44 | max_entries: 5, 45 | ) 46 | ''; 47 | }; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /home/programs/anyrun/style-dark.css: -------------------------------------------------------------------------------- 1 | * { 2 | all: unset; 3 | font-size: 1.2rem; 4 | } 5 | 6 | #window, 7 | #match, 8 | #entry, 9 | #plugin, 10 | #main { 11 | background: transparent; 12 | } 13 | 14 | #match.activatable { 15 | border-radius: 8px; 16 | margin: 4px 0; 17 | padding: 4px; 18 | /* transition: 100ms ease-out; */ 19 | } 20 | #match.activatable:first-child { 21 | margin-top: 12px; 22 | } 23 | #match.activatable:last-child { 24 | margin-bottom: 0; 25 | } 26 | 27 | #match:hover { 28 | background: rgba(255, 255, 255, 0.05); 29 | } 30 | #match:selected { 31 | background: rgba(255, 255, 255, 0.1); 32 | } 33 | 34 | #entry { 35 | background: rgba(255, 255, 255, 0.05); 36 | border: 1px solid rgba(255, 255, 255, 0.1); 37 | border-radius: 8px; 38 | padding: 4px 8px; 39 | } 40 | 41 | box#main { 42 | background: rgba(0, 0, 0, 0.5); 43 | box-shadow: 44 | inset 0 0 0 1px rgba(255, 255, 255, 0.1), 45 | 0 30px 30px 15px rgba(0, 0, 0, 0.5); 46 | border-radius: 20px; 47 | padding: 12px; 48 | } 49 | -------------------------------------------------------------------------------- /home/programs/anyrun/style-light.css: -------------------------------------------------------------------------------- 1 | * { 2 | all: unset; 3 | font-size: 1.2rem; 4 | color: black; 5 | } 6 | 7 | #window, 8 | #match, 9 | #entry, 10 | #plugin, 11 | #main { 12 | background: transparent; 13 | } 14 | 15 | #match.activatable { 16 | border-radius: 8px; 17 | margin: 4px 0; 18 | padding: 4px; 19 | /* transition: 100ms ease-out; */ 20 | } 21 | #match.activatable:first-child { 22 | margin-top: 12px; 23 | } 24 | #match.activatable:last-child { 25 | margin-bottom: 0; 26 | } 27 | 28 | #match:hover { 29 | background: rgba(255, 255, 255, 0.05); 30 | } 31 | #match:selected { 32 | background: rgba(255, 255, 255, 0.1); 33 | } 34 | 35 | #entry { 36 | background: rgba(255, 255, 255, 0.05); 37 | border: 1px solid rgba(255, 255, 255, 0.1); 38 | border-radius: 8px; 39 | padding: 4px 8px; 40 | } 41 | 42 | box#main { 43 | background: rgba(200, 200, 200, 0.5); 44 | box-shadow: 45 | inset 0 0 0 1px rgba(255, 255, 255, 0.1), 46 | 0 30px 30px 15px rgba(0, 0, 0, 0.5); 47 | border-radius: 20px; 48 | padding: 12px; 49 | } 50 | -------------------------------------------------------------------------------- /home/programs/browsers/chromium.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.chromium = { 3 | enable = true; 4 | commandLineArgs = ["--enable-features=TouchpadOverscrollHistoryNavigation"]; 5 | extensions = [ 6 | {id = "cjpalhdlnbpafiamejdnhcphjbkeiagm";} 7 | {id = "bkkmolkhemgaeaeggcmfbghljjjoofoh";} 8 | ]; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /home/programs/browsers/firefox.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.firefox = { 3 | enable = true; 4 | profiles.mihai = { 5 | settings = { 6 | "apz.overscroll.enabled" = true; 7 | "browser.aboutConfig.showWarning" = false; 8 | "general.autoScroll" = true; 9 | "toolkit.legacyUserProfileCustomizations.stylesheets" = true; 10 | }; 11 | }; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /home/programs/browsers/zen.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: { 6 | home.packages = [ 7 | inputs.zen-browser.packages.${pkgs.system}.default 8 | ]; 9 | } 10 | -------------------------------------------------------------------------------- /home/programs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: { 6 | imports = [ 7 | ./anyrun 8 | ./browsers/chromium.nix 9 | ./browsers/firefox.nix 10 | ./browsers/zen.nix 11 | ./media 12 | ./gtk.nix 13 | ./office 14 | ./qt.nix 15 | ]; 16 | 17 | home.packages = with pkgs; [ 18 | halloy 19 | signal-desktop 20 | tdesktop 21 | 22 | gnome-calculator 23 | gnome-control-center 24 | 25 | overskride 26 | resources 27 | wineWowPackages.wayland 28 | 29 | inputs.nix-matlab.packages.${pkgs.system}.matlab 30 | zotero 31 | 32 | unityhub 33 | ]; 34 | 35 | xdg.configFile."matlab/nix.sh".text = "INSTALL_DIR=$XDG_DATA_HOME/matlab/installation"; 36 | } 37 | -------------------------------------------------------------------------------- /home/programs/games/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | # games 7 | { 8 | home.packages = with pkgs; [ 9 | # Default latency sometimes crackles 10 | (inputs.nix-gaming.packages.${pkgs.system}.osu-lazer-bin.override {pipewire_latency = "128/48000";}) 11 | gamescope 12 | prismlauncher 13 | # (lutris.override {extraPkgs = p: [p.libnghttp2];}) 14 | winetricks 15 | protontricks 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /home/programs/gtk.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | config, 5 | ... 6 | }: { 7 | home.pointerCursor = { 8 | package = pkgs.bibata-cursors; 9 | name = "Bibata-Modern-Classic"; 10 | size = 16; 11 | gtk.enable = true; 12 | x11.enable = true; 13 | }; 14 | 15 | gtk = { 16 | enable = true; 17 | 18 | font = { 19 | name = "Inter"; 20 | package = pkgs.google-fonts.override {fonts = ["Inter"];}; 21 | size = 9; 22 | }; 23 | 24 | gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc"; 25 | 26 | iconTheme = { 27 | name = "Adwaita"; 28 | package = pkgs.adwaita-icon-theme; 29 | }; 30 | 31 | theme = { 32 | name = "adw-gtk3-dark"; 33 | package = pkgs.adw-gtk3; 34 | }; 35 | }; 36 | 37 | xdg.configFile."gtk-4.0/gtk.css".enable = lib.mkForce false; 38 | } 39 | -------------------------------------------------------------------------------- /home/programs/media/default.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: 2 | # media - control and enjoy audio/video 3 | { 4 | imports = [ 5 | ./mpv.nix 6 | ./rnnoise.nix 7 | ]; 8 | 9 | home.packages = with pkgs; [ 10 | # audio control 11 | pulsemixer 12 | pwvucontrol 13 | helvum 14 | 15 | # audio 16 | amberol 17 | spotify 18 | 19 | # images 20 | loupe 21 | 22 | # videos 23 | celluloid 24 | 25 | # torrents 26 | transmission_4-gtk 27 | ]; 28 | } 29 | -------------------------------------------------------------------------------- /home/programs/media/mpv.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.mpv = { 3 | enable = true; 4 | defaultProfiles = ["gpu-hq"]; 5 | scripts = [pkgs.mpvScripts.mpris]; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /home/programs/media/rnnoise.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: let 2 | json = pkgs.formats.json {}; 3 | 4 | pw_rnnoise_config = { 5 | "context.modules" = [ 6 | { 7 | "name" = "libpipewire-module-filter-chain"; 8 | "args" = { 9 | "node.description" = "Noise Canceling source"; 10 | "media.name" = "Noise Canceling source"; 11 | "filter.graph" = { 12 | "nodes" = [ 13 | { 14 | "type" = "ladspa"; 15 | "name" = "rnnoise"; 16 | "plugin" = "${pkgs.rnnoise-plugin}/lib/ladspa/librnnoise_ladspa.so"; 17 | "label" = "noise_suppressor_stereo"; 18 | "control" = { 19 | "VAD Threshold (%)" = 50.0; 20 | }; 21 | } 22 | ]; 23 | }; 24 | "audio.position" = ["FL" "FR"]; 25 | "capture.props" = { 26 | "node.name" = "effect_input.rnnoise"; 27 | "node.passive" = true; 28 | }; 29 | "playback.props" = { 30 | "node.name" = "effect_output.rnnoise"; 31 | "media.class" = "Audio/Source"; 32 | }; 33 | }; 34 | } 35 | ]; 36 | }; 37 | in { 38 | xdg.configFile."pipewire/pipewire.conf.d/99-input-denoising.conf" = { 39 | source = json.generate "99-input-denoising.conf" pw_rnnoise_config; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /home/programs/office/default.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | imports = [ 3 | ./zathura.nix 4 | ]; 5 | 6 | home.packages = with pkgs; [ 7 | libreoffice 8 | obsidian 9 | rnote 10 | xournalpp 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /home/programs/office/zathura.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.zathura = { 3 | enable = true; 4 | options = { 5 | recolor-lightcolor = "rgba(0,0,0,0)"; 6 | default-bg = "rgba(0,0,0,0.7)"; 7 | 8 | font = "Inter 12"; 9 | selection-notification = true; 10 | 11 | selection-clipboard = "clipboard"; 12 | adjust-open = "best-fit"; 13 | pages-per-row = "1"; 14 | scroll-page-aware = "true"; 15 | scroll-full-overlap = "0.01"; 16 | scroll-step = "100"; 17 | zoom-min = "10"; 18 | }; 19 | 20 | extraConfig = "include catppuccin-mocha"; 21 | }; 22 | 23 | xdg.configFile = { 24 | "zathura/catppuccin-latte".source = pkgs.fetchurl { 25 | url = "https://raw.githubusercontent.com/catppuccin/zathura/main/src/catppuccin-latte"; 26 | hash = "sha256-nb0ZiHJ9zwlmpN/iHKm3/eRmx4se1om3qCVrfge8B8c="; 27 | }; 28 | "zathura/catppuccin-mocha".source = pkgs.fetchurl { 29 | url = "https://raw.githubusercontent.com/catppuccin/zathura/main/src/catppuccin-mocha"; 30 | hash = "sha256-/HXecio3My2eXTpY7JoYiN9mnXsps4PAThDPs4OCsAk="; 31 | }; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /home/programs/qt.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | config, 5 | ... 6 | }: let 7 | KvLibadwaita = pkgs.fetchFromGitHub { 8 | owner = "GabePoel"; 9 | repo = "KvLibadwaita"; 10 | rev = "87c1ef9f44ec48855fd09ddab041007277e30e37"; 11 | hash = "sha256-K/2FYOtX0RzwdcGyeurLXAh3j8ohxMrH2OWldqVoLwo="; 12 | sparseCheckout = ["src"]; 13 | }; 14 | 15 | qtctConf = { 16 | Appearance = { 17 | custom_palette = false; 18 | icon_theme = config.gtk.iconTheme.name; 19 | standard_dialogs = "xdgdesktopportal"; 20 | style = "kvantum"; 21 | }; 22 | }; 23 | 24 | defaultFont = "${config.gtk.font.name},${builtins.toString config.gtk.font.size}"; 25 | in { 26 | qt = { 27 | enable = true; 28 | platformTheme.name = "qtct"; 29 | }; 30 | 31 | home.packages = [ 32 | pkgs.qt6Packages.qtstyleplugin-kvantum 33 | pkgs.qt6Packages.qt6ct 34 | pkgs.libsForQt5.qtstyleplugin-kvantum 35 | pkgs.libsForQt5.qt5ct 36 | ]; 37 | 38 | xdg.configFile = { 39 | # Kvantum config 40 | "Kvantum" = { 41 | source = "${KvLibadwaita}/src"; 42 | recursive = true; 43 | }; 44 | 45 | "Kvantum/kvantum.kvconfig".text = '' 46 | [General] 47 | theme=KvLibadwaitaDark 48 | ''; 49 | 50 | # qtct config 51 | "qt5ct/qt5ct.conf".text = let 52 | default = ''"${defaultFont},-1,5,50,0,0,0,0,0"''; 53 | in 54 | lib.generators.toINI {} ( 55 | qtctConf 56 | // { 57 | Fonts = { 58 | fixed = default; 59 | general = default; 60 | }; 61 | } 62 | ); 63 | 64 | "qt6ct/qt6ct.conf".text = let 65 | default = ''"${defaultFont},-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular"''; 66 | in 67 | lib.generators.toINI {} ( 68 | qtctConf 69 | // { 70 | Fonts = { 71 | fixed = default; 72 | general = default; 73 | }; 74 | } 75 | ); 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /home/programs/wayland/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | self, 4 | lib, 5 | ... 6 | }: 7 | # Wayland config 8 | { 9 | imports = [ 10 | # ./hyprland 11 | ./hyprlock.nix 12 | ./wlogout.nix 13 | ]; 14 | 15 | home.packages = with pkgs; [ 16 | # screenshot 17 | grim 18 | slurp 19 | 20 | # utils 21 | self.packages.${pkgs.system}.wl-ocr 22 | wl-clipboard 23 | # wl-screenrec 24 | wlr-randr 25 | ]; 26 | 27 | # make stuff work on wayland 28 | home.sessionVariables = { 29 | QT_QPA_PLATFORM = "wayland"; 30 | SDL_VIDEODRIVER = "wayland"; 31 | XDG_SESSION_TYPE = "wayland"; 32 | }; 33 | 34 | systemd.user.targets.tray.Unit.Requires = lib.mkForce ["graphical-session.target"]; 35 | } 36 | -------------------------------------------------------------------------------- /home/programs/wayland/hyprland/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | inputs, 4 | pkgs, 5 | ... 6 | }: let 7 | cursor = "Bibata-Modern-Classic-Hyprcursor"; 8 | cursorPackage = inputs.self.packages.${pkgs.system}.bibata-hyprcursor; 9 | in { 10 | imports = [ 11 | ./binds.nix 12 | ./rules.nix 13 | ./settings.nix 14 | ./smartgaps.nix 15 | ]; 16 | 17 | home.packages = [ 18 | inputs.hyprland-contrib.packages.${pkgs.system}.grimblast 19 | ]; 20 | 21 | xdg.dataFile."icons/${cursor}".source = "${cursorPackage}/share/icons/${cursor}"; 22 | 23 | # enable hyprland 24 | wayland.windowManager.hyprland = { 25 | enable = true; 26 | 27 | package = inputs.hyprland.packages.${pkgs.system}.default; 28 | 29 | plugins = with inputs.hyprland-plugins.packages.${pkgs.system}; [ 30 | # hyprbars 31 | # hyprexpo 32 | ]; 33 | 34 | systemd = { 35 | enable = false; 36 | variables = ["--all"]; 37 | extraCommands = [ 38 | "systemctl --user stop graphical-session.target" 39 | "systemctl --user start hyprland-session.target" 40 | ]; 41 | }; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /home/programs/wayland/hyprland/rules.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | wayland.windowManager.hyprland.settings = { 3 | # layer rules 4 | layerrule = let 5 | toRegex = list: let 6 | elements = lib.concatStringsSep "|" list; 7 | in "^(${elements})$"; 8 | 9 | lowopacity = [ 10 | "bar" 11 | "calendar" 12 | "notifications" 13 | "system-menu" 14 | ]; 15 | 16 | highopacity = [ 17 | "anyrun" 18 | "osd" 19 | "logout_dialog" 20 | ]; 21 | 22 | blurred = lib.concatLists [ 23 | lowopacity 24 | highopacity 25 | ]; 26 | in [ 27 | "blur, ${toRegex blurred}" 28 | "xray 1, ${toRegex ["bar"]}" 29 | "ignorealpha 0.5, ${toRegex (highopacity ++ ["music"])}" 30 | "ignorealpha 0.2, ${toRegex lowopacity}" 31 | ]; 32 | 33 | # window rules 34 | windowrulev2 = [ 35 | # telegram media viewer 36 | "float, title:^(Media viewer)$" 37 | 38 | # Bitwarden extension 39 | "float, title:^(.*Bitwarden Password Manager.*)$" 40 | 41 | # gnome calculator 42 | "float, class:^(org.gnome.Calculator)$" 43 | "size 360 490, class:^(org.gnome.Calculator)$" 44 | 45 | # allow tearing in games 46 | "immediate, class:^(osu\!|cs2)$" 47 | 48 | # make Firefox/Zen PiP window floating and sticky 49 | "float, title:^(Picture-in-Picture)$" 50 | "pin, title:^(Picture-in-Picture)$" 51 | 52 | # throw sharing indicators away 53 | "workspace special silent, title:^(Firefox — Sharing Indicator)$" 54 | "workspace special silent, title:^(Zen — Sharing Indicator)$" 55 | "workspace special silent, title:^(.*is sharing (your screen|a window)\.)$" 56 | 57 | # start Spotify and YouTube Music in ws9 58 | "workspace 9 silent, title:^(Spotify( Premium)?)$" 59 | "workspace 9 silent, title:^(YouTube Music)$" 60 | 61 | # idle inhibit while watching videos 62 | "idleinhibit focus, class:^(mpv|.+exe|celluloid)$" 63 | "idleinhibit focus, class:^(zen)$, title:^(.*YouTube.*)$" 64 | "idleinhibit fullscreen, class:^(zen)$" 65 | 66 | "dimaround, class:^(gcr-prompter)$" 67 | "dimaround, class:^(xdg-desktop-portal-gtk)$" 68 | "dimaround, class:^(polkit-gnome-authentication-agent-1)$" 69 | "dimaround, class:^(zen)$, title:^(File Upload)$" 70 | 71 | # fix xwayland apps 72 | "rounding 0, xwayland:1" 73 | "center, class:^(.*jetbrains.*)$, title:^(Confirm Exit|Open Project|win424|win201|splash)$" 74 | "size 640 400, class:^(.*jetbrains.*)$, title:^(splash)$" 75 | 76 | # Matlab 77 | "tile, title:MATLAB" 78 | "noanim on, class:MATLAB, title:DefaultOverlayManager.JWindow" 79 | "noblur on, class:MATLAB, title:DefaultOverlayManager.JWindow" 80 | "noborder on, class:MATLAB, title:DefaultOverlayManager.JWindow" 81 | "noshadow on, class:MATLAB, title:DefaultOverlayManager.JWindow" 82 | "plugin:hyprbars:nobar, class:MATLAB, title:DefaultOverlayManager.JWindow" 83 | 84 | # don't render hyprbars on tiling windows 85 | "plugin:hyprbars:nobar, floating:0" 86 | 87 | # less sensitive scroll for some windows 88 | # browser(-based) 89 | "scrolltouchpad 0.1, class:^(zen|firefox|chromium-browser|chrome-.*)$" 90 | "scrolltouchpad 0.1, class:^(obsidian)$" 91 | # GTK3 92 | "scrolltouchpad 0.1, class:^(com.github.xournalpp.xournalpp)$" 93 | "scrolltouchpad 0.1, class:^(libreoffice.*)$" 94 | "scrolltouchpad 0.1, class:^(.virt-manager-wrapped)$" 95 | "scrolltouchpad 0.1, class:^(xdg-desktop-portal-gtk)$" 96 | # Qt5 97 | "scrolltouchpad 0.1, class:^(org.prismlauncher.PrismLauncher)$" 98 | "scrolltouchpad 0.1, class:^(org.kde.kdeconnect.app)$" 99 | # Others 100 | "scrolltouchpad 0.1, class:^(org.pwmt.zathura)$" 101 | ]; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /home/programs/wayland/hyprland/settings.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: let 2 | pointer = config.home.pointerCursor; 3 | 4 | cursorName = "Bibata-Modern-Classic-Hyprcursor"; 5 | in { 6 | wayland.windowManager.hyprland.settings = { 7 | "$mod" = "SUPER"; 8 | env = [ 9 | "QT_WAYLAND_DISABLE_WINDOWDECORATION,1" 10 | "HYPRCURSOR_THEME,${cursorName}" 11 | "HYPRCURSOR_SIZE,${toString pointer.size}" 12 | ]; 13 | 14 | exec-once = [ 15 | # finalize startup 16 | "uwsm finalize" 17 | # set cursor for HL itself 18 | "hyprctl setcursor ${cursorName} ${toString pointer.size}" 19 | "hyprlock" 20 | ]; 21 | 22 | general = { 23 | gaps_in = 5; 24 | gaps_out = 5; 25 | border_size = 1; 26 | "col.active_border" = "rgba(88888888)"; 27 | "col.inactive_border" = "rgba(00000088)"; 28 | 29 | allow_tearing = true; 30 | resize_on_border = true; 31 | }; 32 | 33 | decoration = { 34 | rounding = 10; 35 | rounding_power = 3; 36 | blur = { 37 | enabled = true; 38 | brightness = 1.0; 39 | contrast = 1.0; 40 | noise = 0.01; 41 | 42 | vibrancy = 0.2; 43 | vibrancy_darkness = 0.5; 44 | 45 | passes = 4; 46 | size = 7; 47 | 48 | popups = true; 49 | popups_ignorealpha = 0.2; 50 | }; 51 | 52 | shadow = { 53 | enabled = true; 54 | color = "rgba(00000055)"; 55 | ignore_window = true; 56 | offset = "0 15"; 57 | range = 100; 58 | render_power = 2; 59 | scale = 0.97; 60 | }; 61 | }; 62 | 63 | animations = { 64 | enabled = true; 65 | animation = [ 66 | "border, 1, 2, default" 67 | "fade, 1, 4, default" 68 | "windows, 1, 3, default, popin 80%" 69 | "workspaces, 1, 2, default, slide" 70 | ]; 71 | }; 72 | 73 | group = { 74 | groupbar = { 75 | font_size = 10; 76 | gradients = false; 77 | text_color = "rgb(b6c4ff)"; 78 | }; 79 | 80 | "col.border_active" = "rgba(35447988)"; 81 | "col.border_inactive" = "rgba(dce1ff88)"; 82 | }; 83 | 84 | input = { 85 | kb_layout = "ro"; 86 | 87 | # focus change on cursor move 88 | follow_mouse = 1; 89 | accel_profile = "flat"; 90 | tablet.output = "current"; 91 | }; 92 | 93 | dwindle = { 94 | # keep floating dimentions while tiling 95 | pseudotile = true; 96 | preserve_split = true; 97 | }; 98 | 99 | misc = { 100 | # disable auto polling for config file changes 101 | disable_autoreload = true; 102 | 103 | force_default_wallpaper = 0; 104 | 105 | # disable dragging animation 106 | animate_mouse_windowdragging = false; 107 | 108 | # enable variable refresh rate (effective depending on hardware) 109 | vrr = 1; 110 | }; 111 | 112 | render = { 113 | direct_scanout = true; 114 | # Fixes some apps stuttering (xournalpp, hyprlock). Possibly an amdgpu bug 115 | explicit_sync = 0; 116 | explicit_sync_kms = 0; 117 | }; 118 | 119 | # touchpad gestures 120 | gestures = { 121 | workspace_swipe = true; 122 | workspace_swipe_forever = true; 123 | }; 124 | 125 | xwayland.force_zero_scaling = true; 126 | 127 | debug.disable_logs = false; 128 | 129 | plugin = { 130 | csgo-vulkan-fix = { 131 | res_w = 1280; 132 | res_h = 800; 133 | class = "cs2"; 134 | }; 135 | 136 | hyprbars = { 137 | bar_height = 20; 138 | bar_precedence_over_border = true; 139 | 140 | # order is right-to-left 141 | hyprbars-button = [ 142 | # close 143 | "rgb(ffb4ab), 15, , hyprctl dispatch killactive" 144 | # maximize 145 | "rgb(b6c4ff), 15, , hyprctl dispatch fullscreen 1" 146 | ]; 147 | }; 148 | 149 | hyprexpo = { 150 | columns = 3; 151 | gap_size = 4; 152 | bg_col = "rgb(000000)"; 153 | 154 | enable_gesture = true; 155 | gesture_distance = 300; 156 | gesture_positive = false; 157 | }; 158 | }; 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /home/programs/wayland/hyprland/smartgaps.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | ... 6 | }: let 7 | inherit (config.wayland.windowManager.hyprland.settings.general) gaps_in gaps_out border_size; 8 | inherit (config.wayland.windowManager.hyprland.settings.decoration) rounding; 9 | inherit (builtins) concatStringsSep; 10 | inherit (lib.lists) flatten; 11 | 12 | workspaceSelectors = ["w[t1]" "w[tg1]" "f[1]"]; 13 | 14 | toggleSmartGaps = let 15 | forEach = f: concatStringsSep "\n" (map f workspaceSelectors); 16 | in 17 | pkgs.writeShellScript "toggleSmartGaps" '' 18 | hyprctl -j workspacerules | ${lib.getExe pkgs.jaq} -e 'any(.[]; select(.workspaceString == "w[t1]" or .workspaceString == "w[tg1]" or .workspaceString == "w[f1]") | (.gapsIn | all(. == 0)) and (.gapsOut | all(. == 0)))' > /dev/null 19 | 20 | if [ $? -eq 0 ]; then 21 | ${forEach (selector: '' 22 | hyprctl keyword workspace "${selector}, gapsout:${toString gaps_out}, gapsin:${toString gaps_in}" 23 | hyprctl keyword windowrulev2 "bordersize ${toString border_size}, floating:0, onworkspace:${selector}" 24 | hyprctl keyword windowrulev2 "rounding ${toString rounding}, floating:0, onworkspace:${selector}" 25 | '')} 26 | else 27 | ${forEach (selector: '' 28 | hyprctl keyword workspace "${selector}, gapsout:0, gapsin:0" 29 | hyprctl keyword windowrulev2 "bordersize 0, floating:0, onworkspace:${selector}" 30 | hyprctl keyword windowrulev2 "rounding 0, floating:0, onworkspace:${selector}" 31 | '')} 32 | fi 33 | ''; 34 | in { 35 | # Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/ 36 | # "Smart gaps" / "No gaps when only" 37 | wayland.windowManager.hyprland.settings = { 38 | workspace = map (x: "${x}, gapsout:0, gapsin:0") workspaceSelectors; 39 | 40 | windowrulev2 = flatten (map (x: [ 41 | "bordersize 0, floating:0, onworkspace:${x}" 42 | "rounding 0, floating:0, onworkspace:${x}" 43 | ]) 44 | workspaceSelectors); 45 | 46 | bind = [ 47 | "$mod, M, exec, ${toggleSmartGaps}" 48 | ]; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /home/programs/wayland/hyprlock.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | inputs, 4 | pkgs, 5 | ... 6 | }: { 7 | programs.hyprlock = { 8 | enable = true; 9 | 10 | package = inputs.hyprlock.packages.${pkgs.system}.hyprlock; 11 | 12 | settings = { 13 | general = { 14 | disable_loading_bar = true; 15 | immediate_render = true; 16 | hide_cursor = false; 17 | no_fade_in = true; 18 | }; 19 | 20 | animation = [ 21 | "inputFieldDots, 1, 2, linear" 22 | "fadeIn, 0" 23 | ]; 24 | 25 | background = [ 26 | { 27 | monitor = ""; 28 | path = config.theme.wallpaper; 29 | } 30 | ]; 31 | 32 | input-field = [ 33 | { 34 | monitor = "eDP-1"; 35 | 36 | size = "300, 50"; 37 | valign = "bottom"; 38 | position = "0%, 10%"; 39 | 40 | outline_thickness = 1; 41 | 42 | font_color = "rgb(b6c4ff)"; 43 | outer_color = "rgba(180, 180, 180, 0.5)"; 44 | inner_color = "rgba(200, 200, 200, 0.1)"; 45 | check_color = "rgba(247, 193, 19, 0.5)"; 46 | fail_color = "rgba(255, 106, 134, 0.5)"; 47 | 48 | fade_on_empty = false; 49 | placeholder_text = "Enter Password"; 50 | 51 | dots_spacing = 0.2; 52 | dots_center = true; 53 | dots_fade_time = 100; 54 | 55 | shadow_color = "rgba(0, 0, 0, 0.1)"; 56 | shadow_size = 7; 57 | shadow_passes = 2; 58 | } 59 | ]; 60 | 61 | label = [ 62 | { 63 | monitor = ""; 64 | text = "$TIME"; 65 | font_size = 150; 66 | color = "rgb(b6c4ff)"; 67 | 68 | position = "0%, 30%"; 69 | 70 | valign = "center"; 71 | halign = "center"; 72 | 73 | shadow_color = "rgba(0, 0, 0, 0.1)"; 74 | shadow_size = 20; 75 | shadow_passes = 2; 76 | shadow_boost = 0.3; 77 | } 78 | { 79 | monitor = ""; 80 | text = "cmd[update:3600000] date +'%a %b %d'"; 81 | font_size = 20; 82 | color = "rgb(b6c4ff)"; 83 | 84 | position = "0%, 40%"; 85 | 86 | valign = "center"; 87 | halign = "center"; 88 | 89 | shadow_color = "rgba(0, 0, 0, 0.1)"; 90 | shadow_size = 20; 91 | shadow_passes = 2; 92 | shadow_boost = 0.3; 93 | } 94 | ]; 95 | }; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /home/programs/wayland/wlogout.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: let 6 | bgImageSection = name: '' 7 | #${name} { 8 | background-image: image(url("${pkgs.wlogout}/share/wlogout/icons/${name}.png")); 9 | } 10 | ''; 11 | in { 12 | programs.wlogout = { 13 | enable = true; 14 | 15 | style = '' 16 | * { 17 | background: none; 18 | } 19 | 20 | window { 21 | background-color: rgba(0, 0, 0, .5); 22 | } 23 | 24 | button { 25 | background: rgba(0, 0, 0, .05); 26 | border-radius: 8px; 27 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1), 0 0 rgba(0, 0, 0, .5); 28 | margin: 1rem; 29 | background-repeat: no-repeat; 30 | background-position: center; 31 | background-size: 25%; 32 | } 33 | 34 | button:focus, button:active, button:hover { 35 | background-color: rgba(255, 255, 255, 0.2); 36 | outline-style: none; 37 | } 38 | 39 | ${lib.concatMapStringsSep "\n" bgImageSection [ 40 | "lock" 41 | "logout" 42 | "suspend" 43 | "hibernate" 44 | "shutdown" 45 | "reboot" 46 | ]} 47 | ''; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /home/services/ags/.gitignore: -------------------------------------------------------------------------------- 1 | style.css 2 | style.css.map 3 | types 4 | -------------------------------------------------------------------------------- /home/services/ags/README.md: -------------------------------------------------------------------------------- 1 | # Ags Configuration 2 | 3 | This configuration aims to provide a shell replacement for compositors/window 4 | managers. Features constantly get added and existing ones get improved. 5 | 6 | This builds upon my 7 | [previous work on Eww](https://github.com/fufexan/dotfiles/tree/eww/home/services/eww). 8 | 9 | ## 🗃️ Components 10 | 11 | ### bar 12 | 13 |
14 | 15 | Modules 16 | 17 | 18 | #### Workspaces 19 | 20 | - Focused indicator. Inspiration taken from GNOME 45. 21 | - Generated dynamically as you activate them. 22 | - They are visible (active or not), up to the last visible (highest ID). If you 23 | close that one, then the next lower ID will be the last shown. 24 | - Per-monitor indication. Currently supports up to 4 monitors with a color for 25 | each (red, yellow, green, blue). Can be adjusted to take more. 26 | - Click to go to a workspace, or scroll to cycle to the next/previous. 27 | 28 | #### Music 29 | 30 | - View and control any MPRIS player. 31 | - Thumbnail and title for visual indication of the current track. They auto-hide 32 | when no player is active. 33 | - Hovering over the thumbnail or title will reveal clickable player controls. 34 | - Clicking over the thumbnail or title will reveal a 35 | [bigger music window](#music-window). 36 | 37 | #### Tray 38 | 39 | - Shows apps that use the SystemNotifierItem functionality. 40 | - Left-click to execute the primary action of the item. 41 | - Right-click to open the item's menu. 42 | 43 | #### CPU/MEM indicator 44 | 45 | - Visual indication of system usage. Updates every 2 seconds. 46 | 47 | #### System info 48 | 49 | - Shows network, Bluetooth, and, optionally, battery info. 50 | - Hover over the icons to reveal more information. 51 | - Click on any of them to open the [system-menu](#system-menu). 52 | 53 | #### Date 54 | 55 | - Shows the current date and time. 56 | 57 | #### Notification popups 58 | 59 | - Shows notifications 60 | - Primary clicks activates the "default" action, if it exists 61 | - Secondary click dismisses the notification 62 | - Middle click dismisses all popup notifications 63 | - Actions other than "default" are shown as buttons 64 | 65 |
66 | 67 | ### Music window 68 | 69 | - Shows information about the current player, including its icon. 70 | - Cover art visualisation, with a blurred version of it as the window 71 | background. 72 | - Media controls. 73 | - Position/length indicators and progress bar. Click it to skip to that 74 | position. 75 | 76 | ### System menu 77 | 78 | - Toggles for WiFi, Bluetooth (click on the labels to bring up applications for 79 | controlling them). 80 | - Power profiles toggle. Similar to GNOME, you can click it to reveal a list of 81 | power profiles. Click one to activate it. 82 | - Volume/brightness sliders. Click anywhere to change the value. Click on the 83 | volume icon to bring up PulseAudio Volume Control. 84 | - Battery information. Hover over the battery icon to show the energy rate. 85 | - Power button. Click it to reveal `wlogout`. 86 | 87 | ## 🗒 Notes 88 | 89 | Some things don't work as expected: 90 | 91 | - MPRIS will not detect players started before Ags. I've yet to find a solution. 92 | 93 | ## ❔ Usage 94 | 95 | Still work in progress, so the configuration is not handled through Home Manager 96 | yet. 97 | 98 | You can do `ln -s ~/path/to/config/ags ~/.config/ags` to link it into your home 99 | directory, but you'll have to add the dependencies manually (listed in 100 | `default.nix`, under the `dependencies` key). 101 | 102 | ## 🎨 Theme 103 | 104 | The theme colors can be changed in `style/colors.scss`. There are dark/light 105 | variants which can be symlinked to `colors.scss` to change the theme. 106 | -------------------------------------------------------------------------------- /home/services/ags/config.js: -------------------------------------------------------------------------------- 1 | import { App, Audio, Notifications, Utils } from "./imports.js"; 2 | import Bar from "./windows/bar/main.js"; 3 | import Music from "./windows/music/main.js"; 4 | import NotificationPopup from "./windows/notifications/popups.js"; 5 | import Osd from "./windows/osd/main.js"; 6 | import SystemMenu from "./windows/system-menu/main.js"; 7 | 8 | const scss = App.configDir + "/style.scss"; 9 | const css = App.configDir + "/style.css"; 10 | 11 | Utils.exec(`sass ${scss} ${css}`); 12 | 13 | App.connect("config-parsed", () => print("config parsed")); 14 | 15 | App.config({ 16 | style: css, 17 | closeWindowDelay: { 18 | "system-menu": 200, 19 | }, 20 | }); 21 | 22 | Notifications.popupTimeout = 5000; 23 | Notifications.forceTimeout = false; 24 | Notifications.cacheActions = true; 25 | Audio.maxStreamVolume = 1; 26 | 27 | function reloadCss() { 28 | console.log("scss change detected"); 29 | Utils.exec(`sass ${scss} ${css}`); 30 | App.resetCss(); 31 | App.applyCss(css); 32 | } 33 | 34 | Utils.monitorFile(`${App.configDir}/style`, reloadCss); 35 | 36 | /** 37 | * @param {import("types/widgets/window.js").Window[]} windows 38 | */ 39 | function addWindows(windows) { 40 | windows.forEach((win) => App.addWindow(win)); 41 | } 42 | 43 | addWindows( 44 | [ 45 | Bar(), 46 | Music(), 47 | Osd(), 48 | SystemMenu(), 49 | NotificationPopup(), 50 | ], 51 | ); 52 | 53 | export {}; 54 | -------------------------------------------------------------------------------- /home/services/ags/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | lib, 5 | config, 6 | ... 7 | }: let 8 | requiredDeps = with pkgs; [ 9 | config.wayland.windowManager.hyprland.package 10 | bash 11 | coreutils 12 | dart-sass 13 | gawk 14 | imagemagick 15 | inotify-tools 16 | procps 17 | ripgrep 18 | util-linux 19 | ]; 20 | 21 | guiDeps = with pkgs; [ 22 | gnome-control-center 23 | resources 24 | overskride 25 | wlogout 26 | ]; 27 | 28 | dependencies = requiredDeps ++ guiDeps; 29 | 30 | cfg = config.programs.ags; 31 | in { 32 | imports = [ 33 | inputs.ags.homeManagerModules.default 34 | ]; 35 | 36 | programs.ags.enable = true; 37 | 38 | systemd.user.services.ags = { 39 | Unit = { 40 | Description = "Aylur's Gtk Shell"; 41 | PartOf = [ 42 | "tray.target" 43 | "graphical-session.target" 44 | ]; 45 | After = "graphical-session.target"; 46 | }; 47 | Service = { 48 | Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath dependencies}"; 49 | ExecStart = "${cfg.package}/bin/ags"; 50 | Restart = "on-failure"; 51 | }; 52 | Install.WantedBy = ["graphical-session.target"]; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /home/services/ags/dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript": { 3 | }, 4 | "excludes": [ 5 | "**/node_modules" 6 | ], 7 | "plugins": [ 8 | "https://plugins.dprint.dev/typescript-0.88.10.wasm" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /home/services/ags/imports.js: -------------------------------------------------------------------------------- 1 | // Required components 2 | import GLib from "gi://GLib"; 3 | import App from "resource:///com/github/Aylur/ags/app.js"; 4 | import Service from "resource:///com/github/Aylur/ags/service.js"; 5 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 6 | import Variable from "resource:///com/github/Aylur/ags/variable.js"; 7 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 8 | 9 | // Services 10 | import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; 11 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 12 | import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js"; 13 | import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; 14 | import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; 15 | import Network from "resource:///com/github/Aylur/ags/service/network.js"; 16 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 17 | import PowerProfiles from "resource:///com/github/Aylur/ags/service/powerprofiles.js"; 18 | import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; 19 | 20 | import Icons from "./utils/icons.js"; 21 | 22 | export { 23 | App, 24 | Audio, 25 | Battery, 26 | Bluetooth, 27 | GLib, 28 | Hyprland, 29 | Icons, 30 | Mpris, 31 | Network, 32 | Notifications, 33 | PowerProfiles, 34 | Service, 35 | SystemTray, 36 | Utils, 37 | Variable, 38 | Widget, 39 | }; 40 | -------------------------------------------------------------------------------- /home/services/ags/services/brightness.js: -------------------------------------------------------------------------------- 1 | import Gio from "gi://Gio"; 2 | import GLib from "gi://GLib"; 3 | import { Service, Utils } from "../imports.js"; 4 | 5 | const clamp = (num, min, max) => Math.min(Math.max(num, min), max); 6 | 7 | class BrightnessService extends Service { 8 | static { 9 | Service.register( 10 | this, 11 | { "screen-changed": ["float"] }, 12 | { "screen-value": ["float", "rw"] }, 13 | ); 14 | } 15 | 16 | #screenValue = 0; 17 | 18 | #interface = Utils.exec("sh -c 'ls -w1 /sys/class/backlight | head -1'"); 19 | #path = `/sys/class/backlight/${this.#interface}`; 20 | #brightness = `${this.#path}/brightness`; 21 | 22 | #max = Number(Utils.readFile(`${this.#path}/max_brightness`)); 23 | 24 | get screen_value() { 25 | return this.#screenValue; 26 | } 27 | 28 | set screen_value(percent) { 29 | percent = clamp(percent, 0, 1); 30 | this.#screenValue = percent; 31 | 32 | const file = Gio.File.new_for_path(this.#brightness); 33 | const string = `${Math.round(percent * this.#max)}`; 34 | 35 | new Promise((resolve, _) => { 36 | file.replace_contents_bytes_async( 37 | new GLib.Bytes(new TextEncoder().encode(string)), 38 | null, 39 | false, 40 | Gio.FileCreateFlags.NONE, 41 | null, 42 | (self, res) => { 43 | try { 44 | self.replace_contents_finish(res); 45 | resolve(self); 46 | } catch (error) { 47 | print(error); 48 | } 49 | }, 50 | ); 51 | }); 52 | } 53 | 54 | constructor() { 55 | super(); 56 | 57 | this.#updateScreenValue(); 58 | // Utils.monitorFile(this.#brightness, () => this.#onChange()); 59 | Utils.subprocess([ 60 | "inotifywait", 61 | "--event", 62 | "create,modify", 63 | "-m", 64 | this.#brightness, 65 | ], () => this.#onChange()); 66 | } 67 | 68 | #updateScreenValue() { 69 | this.#screenValue = Number(Utils.readFile(this.#brightness)) / this.#max; 70 | } 71 | 72 | #onChange() { 73 | this.#updateScreenValue(); 74 | 75 | this.notify("screen-value"); 76 | this.emit("screen-changed", this.#screenValue); 77 | } 78 | 79 | connectWidget(widget, callback, event = "screen-changed") { 80 | super.connectWidget(widget, callback, event); 81 | } 82 | } 83 | 84 | const service = new BrightnessService(); 85 | 86 | export default service; 87 | -------------------------------------------------------------------------------- /home/services/ags/services/osd.js: -------------------------------------------------------------------------------- 1 | import { Audio, Icons, Service, Utils } from "../imports.js"; 2 | import { audioIcon, micIcon } from "../utils/audio.js"; 3 | import { getBluetoothDevice } from "../utils/bluetooth.js"; 4 | import Brightness from "./brightness.js"; 5 | 6 | class Indicator extends Service { 7 | static { 8 | Service.register(this, { 9 | "popup": ["jsobject", "boolean"], 10 | }); 11 | } 12 | 13 | #delay = 1500; 14 | #count = 0; 15 | 16 | popup(value, label, icon, showProgress = true) { 17 | const props = { 18 | value, 19 | label, 20 | icon, 21 | showProgress, 22 | }; 23 | this.emit("popup", props, true); 24 | this.#count++; 25 | Utils.timeout(this.#delay, () => { 26 | this.#count--; 27 | 28 | if (this.#count === 0) { 29 | this.emit("popup", props, false); 30 | } 31 | }); 32 | } 33 | 34 | bluetooth(addr) { 35 | this.popup( 36 | 0, 37 | getBluetoothDevice(addr), 38 | Icons.bluetooth.active, 39 | false, 40 | ); 41 | } 42 | 43 | speaker() { 44 | this.popup( 45 | Audio.speaker?.volume ?? 0, 46 | Audio.speaker?.description ?? "", 47 | audioIcon(), 48 | ); 49 | } 50 | 51 | mic() { 52 | this.popup( 53 | Audio.microphone?.volume || 0, 54 | Audio.microphone?.description || "", 55 | micIcon(), 56 | ); 57 | } 58 | 59 | display() { 60 | // brightness is async, so lets wait a bit 61 | Utils.timeout(10, () => 62 | this.popup( 63 | Brightness.screenValue, 64 | "Brightness", 65 | Icons.brightness, 66 | )); 67 | } 68 | 69 | connect(event = "popup", callback) { 70 | return super.connect(event, callback); 71 | } 72 | } 73 | 74 | export default new Indicator(); 75 | -------------------------------------------------------------------------------- /home/services/ags/style.scss: -------------------------------------------------------------------------------- 1 | /* style aggregator */ 2 | 3 | /* setup */ 4 | @import "style/prelude"; 5 | @import "style/colors"; 6 | @import "style/general"; 7 | 8 | /* modules & windows */ 9 | @import "style/bar"; 10 | @import "style/music"; 11 | @import "style/osd"; 12 | @import "style/system-menu"; 13 | @import "style/notifications"; 14 | -------------------------------------------------------------------------------- /home/services/ags/style/bar.scss: -------------------------------------------------------------------------------- 1 | .bar { 2 | background: $bar-bg; 3 | min-height: 32px; 4 | 5 | .module { 6 | margin: 0 0.5rem; 7 | } 8 | } 9 | 10 | /* workspaces */ 11 | .bar .workspaces { 12 | margin: 0.2rem 0.5rem; 13 | 14 | button { 15 | background: rgba(0, 0, 0, 0.3); 16 | border-radius: 2rem; 17 | margin: 0.7rem 0.2rem; 18 | min-width: 1rem; 19 | transition: 100ms linear; 20 | } 21 | 22 | .focused { 23 | min-width: 2rem; 24 | } 25 | 26 | .monitor0 { 27 | background: $red; 28 | } 29 | .monitor1 { 30 | background: $yellow; 31 | } 32 | .monitor2 { 33 | background: $green; 34 | } 35 | .monitor3 { 36 | background: $blue; 37 | } 38 | } 39 | 40 | /* music */ 41 | .bar .music { 42 | & > box { 43 | @include animate; 44 | border-radius: $round2; 45 | margin: 0.4rem; 46 | } 47 | 48 | &.active > box { 49 | background: $surface; 50 | } 51 | 52 | .cover { 53 | background-size: cover; 54 | background-position: center; 55 | border-radius: 50%; 56 | min-width: 2rem; 57 | min-height: 2rem; 58 | } 59 | } 60 | 61 | /* tray */ 62 | .tray button { 63 | @include button; 64 | background: none; 65 | margin: 0.5rem 0; 66 | 67 | &:not(:last-child) { 68 | margin-right: 0.3rem; 69 | } 70 | 71 | &.active { 72 | background: $surface; 73 | } 74 | } 75 | 76 | menu { 77 | background: $tooltip-bg; 78 | border-radius: $round; 79 | 80 | separator { 81 | background-color: $surface; 82 | } 83 | 84 | menuitem { 85 | @include button; 86 | border-radius: 0; 87 | padding: 0.4rem 0.7rem; 88 | 89 | &:first-child { 90 | border-radius: $round $round 0 0; 91 | } 92 | &:last-child { 93 | border-radius: 0 0 $round $round; 94 | } 95 | &:only-child { 96 | border-radius: $round; 97 | } 98 | } 99 | } 100 | 101 | /* system-info */ 102 | .bar .system-info { 103 | margin: 0 0.2rem; 104 | 105 | & > box { 106 | margin: 0 0.3rem; 107 | } 108 | 109 | .type { 110 | font-size: 0.55rem; 111 | font-weight: 300; 112 | } 113 | 114 | .value { 115 | font-size: 0.8rem; 116 | } 117 | } 118 | 119 | .system-menu-toggler { 120 | box { 121 | @include animate; 122 | margin: 0.4rem 0; 123 | border-radius: $round2; 124 | } 125 | 126 | &.active box { 127 | background: $surface; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /home/services/ags/style/colors-dark.scss: -------------------------------------------------------------------------------- 1 | $red: #f38ba8; 2 | $yellow: #f9e2af; 3 | $green: #a6e3a1; 4 | $blue: #89b4fa; 5 | 6 | $tooltip-bg: #000000; 7 | $fg: #ffffff; 8 | $bg: rgba(0, 0, 0, 0.3); 9 | $bar-bg: rgba(0, 0, 0, 0.21); 10 | 11 | $surface: rgba(255, 255, 255, 0.15); 12 | $overlay: rgba(255, 255, 255, 0.7); 13 | 14 | $accent: #9d5b7a; 15 | 16 | /* buttons */ 17 | $button-enabled: $accent; 18 | $button-enabled-hover: adjust_color($button-enabled, $lightness: -10%); 19 | 20 | $button-disabled: $surface; 21 | $button-disabled-hover: adjust_color($button-disabled, $alpha: +0.1); 22 | 23 | * { 24 | text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); 25 | } 26 | 27 | @mixin border { 28 | // border: 1px solid rgba(0, 0, 0, 0.1); 29 | box-shadow: 30 | // inset 0 0 0 1px rgba(255, 255, 255, 0.1), 31 | 0 3px 5px 1px rgba(0, 0, 0, 0.3); 32 | } 33 | -------------------------------------------------------------------------------- /home/services/ags/style/colors-light.scss: -------------------------------------------------------------------------------- 1 | $red: #f38ba8; 2 | $yellow: #f9e2af; 3 | $green: #a6e3a1; 4 | $blue: #89b4fa; 5 | 6 | $tooltip-bg: #ffffff; 7 | $fg: #000000; 8 | $bg: rgba(255, 255, 255, 0.5); 9 | $bar-bg: rgba(255, 255, 255, 0.3); 10 | 11 | $surface: rgba(255, 255, 255, 0.3); 12 | $overlay: rgba(0, 0, 0, 0.5); 13 | 14 | $accent: #ddbaef; 15 | 16 | /* buttons */ 17 | $button-enabled: $accent; 18 | $button-enabled-hover: adjust_color($button-enabled, $lightness: -10%); 19 | 20 | $button-disabled: $surface; 21 | $button-disabled-hover: adjust_color($button-disabled, $alpha: +0.1); 22 | 23 | * { 24 | text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); 25 | } 26 | 27 | @mixin border { 28 | // border: 1px solid rgba(0, 0, 0, 0.1); 29 | box-shadow: 30 | // inset 0 0 0 1px rgba(255, 255, 255, 0.1), 31 | 0 3px 5px 1px rgba(0, 0, 0, 0.3); 32 | } 33 | -------------------------------------------------------------------------------- /home/services/ags/style/colors.scss: -------------------------------------------------------------------------------- 1 | colors-dark.scss -------------------------------------------------------------------------------- /home/services/ags/style/general.scss: -------------------------------------------------------------------------------- 1 | /* general styles */ 2 | 3 | $round: 8px; 4 | $round2: calc($round * 2); 5 | $margin: 0.4rem; 6 | $padding: 0.4rem; 7 | $border-width: 2px; 8 | $scale: 0.5rem; 9 | 10 | @mixin animate { 11 | transition: 200ms; 12 | } 13 | 14 | * { 15 | color: $fg; 16 | } 17 | 18 | /* mixins */ 19 | @mixin window-rounding { 20 | border-radius: $round2; 21 | } 22 | 23 | @mixin rounding { 24 | border-radius: calc($round2 - $padding - $border-width); 25 | } 26 | 27 | @mixin window-box { 28 | @include rounding; 29 | 30 | background: $surface; 31 | box-shadow: 0 1px 5px -5px rgba(0, 0, 0, 0.5); 32 | margin: $margin; 33 | padding: $padding; 34 | } 35 | 36 | @mixin window { 37 | @include border; 38 | @include window-rounding; 39 | 40 | background: $bg; 41 | margin: 5px 10px 15px; 42 | padding: $padding; 43 | } 44 | 45 | tooltip { 46 | background: $tooltip-bg; 47 | box-shadow: 48 | inset 0 0 0 1px rgba(255, 255, 255, 0.1), 49 | 0 0 rgba(0, 0, 0, 0.4); 50 | border-radius: $round; 51 | } 52 | 53 | /* scales & progress bars */ 54 | scale, 55 | progressbar { 56 | trough { 57 | background-color: $surface; 58 | border-radius: $scale; 59 | min-width: calc($scale * 10); 60 | padding: 0 calc($scale / 2); 61 | } 62 | 63 | highlight, 64 | progress { 65 | background: $overlay; 66 | border-radius: $scale; 67 | margin: 0 calc(0px - $scale / 2); 68 | min-height: $scale; 69 | } 70 | } 71 | 72 | @mixin button-active { 73 | @include animate; 74 | background: $button-enabled; 75 | border-radius: 5rem; 76 | padding: 0.4rem; 77 | 78 | &:hover { 79 | background: $button-enabled-hover; 80 | } 81 | } 82 | 83 | @mixin button { 84 | @include animate; 85 | background: $button-disabled; 86 | border-radius: 5rem; 87 | padding: 0.4rem; 88 | 89 | &:hover { 90 | background: $button-disabled-hover; 91 | } 92 | } 93 | 94 | .button { 95 | @include button-active; 96 | } 97 | 98 | .button.disabled { 99 | @include button; 100 | } 101 | -------------------------------------------------------------------------------- /home/services/ags/style/music.scss: -------------------------------------------------------------------------------- 1 | .music.window { 2 | @include window; 3 | 4 | .cover { 5 | background-position: center; 6 | background-size: cover; 7 | border-radius: $round; 8 | box-shadow: 0 1px 2px -1px $bg; 9 | margin: 0.4rem; 10 | min-height: 13rem; 11 | min-width: 13rem; 12 | } 13 | } 14 | 15 | .music.window .info { 16 | margin: 0.5rem; 17 | 18 | label, 19 | scale { 20 | margin: 0.3rem 0; 21 | } 22 | 23 | label.position, 24 | label.length { 25 | font-size: 0.8rem; 26 | margin-bottom: 0; 27 | } 28 | 29 | scale { 30 | margin-top: 0; 31 | margin-bottom: 0; 32 | } 33 | 34 | .title { 35 | font-size: 1.5rem; 36 | font-weight: bold; 37 | min-width: 14rem; 38 | } 39 | } 40 | 41 | .music.window .controls { 42 | button { 43 | margin: 0 0.2rem; 44 | font-size: 1.5rem; 45 | } 46 | } 47 | 48 | .music.window .player-info { 49 | margin-bottom: 0; 50 | 51 | .player-icon { 52 | font-size: 1.2rem; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /home/services/ags/style/notifications.scss: -------------------------------------------------------------------------------- 1 | .notification { 2 | @include window; 3 | margin: 5px 5px 5px 10px; 4 | 5 | min-width: 25rem; 6 | 7 | border-radius: $round2; 8 | background-color: $bg; 9 | 10 | &.critical { 11 | border: 1px solid red; 12 | } 13 | } 14 | 15 | .notifications widget:last-child .notification { 16 | margin-bottom: 15px; 17 | } 18 | 19 | .notification .icon { 20 | image { 21 | font-size: 5rem; 22 | margin: 0.5rem; 23 | min-height: 5rem; 24 | min-width: 5rem; 25 | } 26 | 27 | > box { 28 | border-radius: $round; 29 | margin: 0.5rem; 30 | min-height: 5rem; 31 | min-width: 5rem; 32 | } 33 | } 34 | 35 | .notification .actions .action-button { 36 | @include window-box; 37 | @include animate; 38 | padding: 0.5rem 0; 39 | 40 | &:hover { 41 | background: $button-disabled-hover; 42 | } 43 | } 44 | 45 | .notification .text { 46 | margin: 0.5rem; 47 | 48 | .title { 49 | margin-bottom: 0.2rem; 50 | font-weight: 500; 51 | } 52 | 53 | .body { 54 | color: rgba(255, 255, 255, 0.7); 55 | font-weight: 500; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /home/services/ags/style/osd.scss: -------------------------------------------------------------------------------- 1 | .osd { 2 | @include window; 3 | padding: 0; 4 | margin-bottom: 2rem; 5 | 6 | image { 7 | margin-left: 1rem; 8 | color: rgba(0, 0, 0, 0.6); 9 | } 10 | 11 | progressbar trough { 12 | border-radius: 16px; 13 | background: none; 14 | min-width: 12.5rem; 15 | min-height: 2.5rem; 16 | } 17 | 18 | progressbar progress { 19 | border-radius: 0; 20 | border-radius: 16px; 21 | min-height: 2.5rem; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /home/services/ags/style/prelude.scss: -------------------------------------------------------------------------------- 1 | /* get rid of GTK theme's styles and set defaults */ 2 | * { 3 | all: unset; 4 | font-family: Inter, Roboto, sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /home/services/ags/style/system-menu.scss: -------------------------------------------------------------------------------- 1 | /* general */ 2 | .system-menu { 3 | @include window; 4 | margin-top: 4px; 5 | margin-right: 4px; 6 | 7 | & > box { 8 | @include window-box; 9 | } 10 | } 11 | 12 | /* toggles */ 13 | .system-menu .toggle { 14 | min-width: 20rem; 15 | &:not(:last-child) { 16 | margin-bottom: 0.3rem; 17 | } 18 | 19 | .button { 20 | margin-right: 0.5rem; 21 | } 22 | } 23 | 24 | /* power profiles */ 25 | .system-menu .power-profiles { 26 | padding: 0; 27 | 28 | .current-profile { 29 | padding: 0.3rem; 30 | } 31 | 32 | image, 33 | label { 34 | margin: 0.3rem; 35 | } 36 | 37 | .options { 38 | padding: 0; 39 | 40 | widget { 41 | @include button; 42 | border-radius: 0; 43 | 44 | &:last-child { 45 | border-radius: 0 0 $round $round; 46 | } 47 | 48 | box { 49 | padding: 0.3rem; 50 | } 51 | } 52 | } 53 | } 54 | 55 | /* sliders */ 56 | .system-menu .sliders { 57 | image { 58 | margin: 0.3rem; 59 | } 60 | 61 | scale { 62 | margin: 0 0.5rem; 63 | } 64 | } 65 | 66 | .system-menu .battery-box { 67 | image, 68 | label { 69 | margin: 0 0.3rem; 70 | } 71 | 72 | .time { 73 | color: rgba(255, 255, 255, 0.7); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /home/services/ags/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "lib": [ 6 | "ES2022" 7 | ], 8 | "allowJs": true, 9 | "checkJs": true, 10 | "strict": true, 11 | "noImplicitAny": false, 12 | "baseUrl": ".", 13 | "typeRoots": [ 14 | "./types" 15 | ], 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /home/services/ags/utils/audio.js: -------------------------------------------------------------------------------- 1 | import { Audio, Icons } from "../imports.js"; 2 | 3 | export const audioIcon = () => { 4 | if (Audio.speaker?.stream.isMuted) return Icons.volume.muted; 5 | 6 | const vol = Audio.speaker?.volume * 100; 7 | const icon = [ 8 | [101, "overamplified"], 9 | [67, "high"], 10 | [34, "medium"], 11 | [1, "low"], 12 | [0, "muted"], 13 | ].find(([threshold]) => threshold <= vol)[1]; 14 | 15 | return Icons.volume[icon]; 16 | }; 17 | 18 | export const micIcon = () => { 19 | if (Audio.microphone?.stream.isMuted) return Icons.microphone.muted; 20 | 21 | const vol = Audio.microphone?.volume * 100; 22 | const icon = [ 23 | [67, "high"], 24 | [34, "medium"], 25 | [1, "low"], 26 | [0, "muted"], 27 | ].find(([threshold]) => threshold <= vol)[1]; 28 | 29 | return Icons.microphone[icon]; 30 | }; 31 | -------------------------------------------------------------------------------- /home/services/ags/utils/battery.js: -------------------------------------------------------------------------------- 1 | import { Battery } from "../imports.js"; 2 | 3 | export const toTime = (time) => { 4 | const MINUTE = 60; 5 | const HOUR = MINUTE * 60; 6 | 7 | if (time > 24 * HOUR) return ""; 8 | 9 | const hours = Math.round(time / HOUR); 10 | const minutes = Math.round((time - hours * HOUR) / MINUTE); 11 | 12 | const hoursDisplay = hours > 0 ? `${hours}h ` : ""; 13 | const minutesDisplay = minutes > 0 ? `${minutes}m ` : ""; 14 | 15 | return `${hoursDisplay}${minutesDisplay}`; 16 | }; 17 | 18 | export const batteryTime = () => { 19 | return Battery.timeRemaining > 0 && toTime(Battery.timeRemaining) != "" 20 | ? `${toTime(Battery.timeRemaining)}remaining` 21 | : ""; 22 | }; 23 | -------------------------------------------------------------------------------- /home/services/ags/utils/bluetooth.js: -------------------------------------------------------------------------------- 1 | import { Bluetooth, Icons } from "../imports.js"; 2 | 3 | export const getBluetoothDevice = (addr) => 4 | Bluetooth.getDevice(addr).alias ?? Bluetooth.getDevice(addr).name; 5 | 6 | export const getBluetoothIcon = (connected) => { 7 | if (!Bluetooth.enabled) return Icons.bluetooth.disabled; 8 | if (connected.length > 0) return Icons.bluetooth.active; 9 | return Icons.bluetooth.disconnected; 10 | }; 11 | 12 | export const getBluetoothText = (connected) => { 13 | if (!Bluetooth.enabled) return "Bluetooth off"; 14 | 15 | if (connected.length > 0) { 16 | const dev = Bluetooth.getDevice(connected[0].address); 17 | let battery_str = ""; 18 | 19 | if (dev.battery_percentage > 0) { 20 | battery_str += ` ${dev.battery_percentage}%`; 21 | } 22 | 23 | return dev.name + battery_str; 24 | } 25 | 26 | return "Bluetooth on"; 27 | }; 28 | -------------------------------------------------------------------------------- /home/services/ags/utils/hyprland.js: -------------------------------------------------------------------------------- 1 | import { Hyprland } from "../imports.js"; 2 | 3 | export let DEFAULT_MONITOR; 4 | const connID = Hyprland.connect("notify::workspaces", () => { 5 | Hyprland.disconnect(connID); 6 | 7 | DEFAULT_MONITOR = { 8 | name: Hyprland.monitors[0].name, 9 | id: Hyprland.monitors[0].id, 10 | }; 11 | }); 12 | 13 | export const changeWorkspace = (ws) => Hyprland.messageAsync(`dispatch workspace ${ws}`); 14 | 15 | export const focusedSwitch = (self) => { 16 | const id = Hyprland.active.workspace.id; 17 | if (self.lastFocused == id) return; 18 | 19 | self.children[self.lastFocused - 1].toggleClassName("focused", false); 20 | self.children[id - 1].toggleClassName("focused", true); 21 | self.lastFocused = id; 22 | }; 23 | 24 | export const added = (self, name) => { 25 | if (!name) return; 26 | const ws = Hyprland.workspaces.find((e) => e.name == name); 27 | const id = ws?.id ?? Number(name); 28 | const child = self.children[id - 1]; 29 | 30 | child.monitor = { 31 | name: ws?.monitor ?? DEFAULT_MONITOR.name, 32 | id: ws?.monitorID ?? DEFAULT_MONITOR.id, 33 | }; 34 | 35 | child.active = true; 36 | child.toggleClassName(`monitor${child.monitor.id}`, true); 37 | 38 | // if this id is bigger than the last biggest id, visibilise all other ws before it 39 | if (id > self.biggestId) { 40 | for (let i = self.biggestId; i <= id; i++) { 41 | self.children[i - 1].visible = true; 42 | } 43 | self.biggestId = id; 44 | } 45 | }; 46 | 47 | const makeInvisible = (self, id) => { 48 | if (id <= 1) return; 49 | 50 | const child = self.children[id - 1]; 51 | if (child.active) { 52 | self.biggestId = id; 53 | return; 54 | } 55 | 56 | child.visible = false; 57 | makeInvisible(self, id - 1); 58 | }; 59 | 60 | export const removed = (self, name) => { 61 | if (!name) return; 62 | 63 | const id = Number(name); 64 | const child = self.children[id - 1]; 65 | 66 | child.toggleClassName(`monitor${child.monitor.id}`, false); 67 | child.active = false; 68 | 69 | // if this id is the biggest id, unvisibilise it and all other inactives until the next active before it 70 | if (id == self.biggestId) { 71 | makeInvisible(self, id); 72 | } 73 | }; 74 | 75 | export const moveWorkspace = (self, data) => { 76 | const [id, name] = data.split(","); 77 | 78 | const child = self.children[id - 1]; 79 | 80 | // remove previous monitor class 81 | child.toggleClassName(`monitor${child.monitor.id}`, false); 82 | 83 | // add new monitor and class 84 | const monitor = Hyprland.monitors.find((e) => e.name == name); 85 | 86 | child.monitor = { 87 | name, 88 | id: monitor?.id ?? DEFAULT_MONITOR.id, 89 | }; 90 | 91 | print(`child ${id}: monitor ${name} ${child.monitor.id}`); 92 | child.toggleClassName(`monitor${child.monitor.id}`, true); 93 | }; 94 | 95 | export const sortWorkspaces = () => { 96 | return Hyprland.workspaces 97 | .sort((x, y) => { 98 | return x.id - y.id; 99 | }) 100 | .filter((x) => { 101 | return x.name.indexOf("special") == -1; 102 | }); 103 | }; 104 | 105 | export const getLastWorkspaceId = () => sortWorkspaces().slice(-1)[0].id; 106 | export const workspaceActive = (id) => sortWorkspaces().some((e) => e.id == id); 107 | -------------------------------------------------------------------------------- /home/services/ags/utils/icons.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bluetooth: { 3 | active: "bluetooth-active-symbolic", 4 | disabled: "bluetooth-disabled-symbolic", 5 | disconnected: "bluetooth-disconnected-symbolic", 6 | }, 7 | 8 | brightness: "display-brightness-symbolic", 9 | 10 | media: { 11 | play: "media-playback-start-symbolic", 12 | pause: "media-playback-pause-symbolic", 13 | next: "media-skip-forward-symbolic", 14 | previous: "media-skip-backward-symbolic", 15 | 16 | player: "multimedia-player-symbolic", 17 | }, 18 | 19 | volume: { 20 | muted: "audio-volume-muted-symbolic", 21 | low: "audio-volume-low-symbolic", 22 | medium: "audio-volume-medium-symbolic", 23 | high: "audio-volume-high-symbolic", 24 | overamplified: "audio-volume-overamplified-symbolic", 25 | }, 26 | 27 | microphone: { 28 | muted: "microphone-sensitivity-muted-symbolic", 29 | low: "microphone-sensitivity-low-symbolic", 30 | medium: "microphone-sensitivity-medium-symbolic", 31 | high: "microphone-sensitivity-high-symbolic", 32 | }, 33 | 34 | powerButton: "system-shutdown-symbolic", 35 | }; 36 | -------------------------------------------------------------------------------- /home/services/ags/utils/mpris.js: -------------------------------------------------------------------------------- 1 | import { Icons, Utils } from "../imports.js"; 2 | import GLib from "gi://GLib"; 3 | 4 | export const findPlayer = (players) => { 5 | // try to get the first active player 6 | const activePlayer = players.find((p) => p.playBackStatus == "Playing"); 7 | if (activePlayer != null) return activePlayer; 8 | 9 | // otherwise get the first "working" player 10 | for (const p of players) { 11 | if (p.title != "undefined") return p; 12 | } 13 | }; 14 | 15 | export const mprisStateIcon = (status) => { 16 | const state = status == "Playing" ? "pause" : "play"; 17 | return Icons.media[state]; 18 | }; 19 | 20 | export const MEDIA_CACHE_PATH = Utils.CACHE_DIR + "/media"; 21 | export const blurredPath = MEDIA_CACHE_PATH + "/blurred"; 22 | 23 | export const generateBackground = (cover_path) => { 24 | const url = cover_path; 25 | if (!url) return ""; 26 | 27 | const makeBg = (bg) => `background: center/cover url('${bg}')`; 28 | 29 | const blurred = blurredPath + 30 | url.substring(MEDIA_CACHE_PATH.length); 31 | 32 | if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) { 33 | return makeBg(blurred); 34 | } 35 | 36 | Utils.ensureDirectory(blurredPath); 37 | Utils.exec(`convert ${url} -blur 0x22 ${blurred}`); 38 | 39 | return makeBg(blurred); 40 | }; 41 | 42 | export function lengthStr(length) { 43 | const min = Math.floor(length / 60); 44 | const sec = Math.floor(length % 60); 45 | const sec0 = sec < 10 ? "0" : ""; 46 | return `${min}:${sec0}${sec}`; 47 | } 48 | -------------------------------------------------------------------------------- /home/services/ags/utils/net.js: -------------------------------------------------------------------------------- 1 | import { Network } from "../imports.js"; 2 | 3 | export const getNetIcon = () => { 4 | if (Network.connectivity == "none") return ""; 5 | if (Network.primary == "wired") return "network-wired-symbolic"; 6 | 7 | return Network.wifi.icon_name; 8 | }; 9 | 10 | export const getNetText = () => { 11 | // no connection 12 | if (Network.connectivity == "none") return "No connection"; 13 | 14 | // wired 15 | if (Network.primary == "wired") return "Wired"; 16 | 17 | // wifi 18 | const wifi = Network.wifi; 19 | switch (wifi.internet) { 20 | case "connected": 21 | return wifi.ssid; 22 | case "connecting": 23 | return "Connecting"; 24 | case "disconnected": 25 | return "Disconnected"; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /home/services/ags/utils/popup_window.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import { Widget } from "../imports.js"; 3 | const { Box, Revealer, Window } = Widget; 4 | 5 | export default ( 6 | { 7 | name, 8 | child, 9 | revealerSetup = null, 10 | transition = "crossfade", 11 | transitionDuration = 200, 12 | ...props 13 | }, 14 | ) => { 15 | const window = Window({ 16 | name, 17 | popup: false, 18 | focusable: false, 19 | visible: false, 20 | ...props, 21 | 22 | setup: (self) => self.getChild = () => child, 23 | 24 | child: Box({ 25 | css: ` 26 | min-height: 1px; 27 | min-width: 1px; 28 | padding: 1px; 29 | `, 30 | child: Revealer({ 31 | transition, 32 | transitionDuration, 33 | child: child, 34 | 35 | setup: revealerSetup ?? ((self) => 36 | self 37 | .hook( 38 | App, 39 | (self, currentName, visible) => { 40 | if (currentName === name) { 41 | self.reveal_child = visible; 42 | } 43 | }, 44 | )), 45 | }), 46 | }), 47 | }); 48 | 49 | return window; 50 | }; 51 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/main.js: -------------------------------------------------------------------------------- 1 | import { App, Widget } from "../../imports.js"; 2 | import Battery from "./modules/battery.js"; 3 | import Bluetooth from "./modules/bluetooth.js"; 4 | import Date from "./modules/date.js"; 5 | import Music from "./modules/music.js"; 6 | import Net from "./modules/net.js"; 7 | import CpuRam from "./modules/cpu_ram.js"; 8 | import Tray from "./modules/tray.js"; 9 | import Workspaces from "./modules/workspaces.js"; 10 | 11 | const SystemInfo = () => 12 | Widget.EventBox({ 13 | className: "system-menu-toggler", 14 | onPrimaryClick: () => App.toggleWindow("system-menu"), 15 | 16 | child: Widget.Box({ 17 | children: [ 18 | Net(), 19 | Bluetooth(), 20 | Battery(), 21 | ], 22 | }), 23 | }) 24 | .hook( 25 | App, 26 | (self, window, visible) => { 27 | if (window === "system-menu") { 28 | self.toggleClassName("active", visible); 29 | } 30 | }, 31 | ); 32 | 33 | const Start = () => 34 | Widget.Box({ 35 | hexpand: true, 36 | hpack: "start", 37 | children: [ 38 | Workspaces(), 39 | // Indicators 40 | ], 41 | }); 42 | 43 | const Center = () => 44 | Widget.Box({ 45 | children: [ 46 | Music(), 47 | ], 48 | }); 49 | 50 | const End = () => 51 | Widget.Box({ 52 | hexpand: true, 53 | hpack: "end", 54 | 55 | children: [ 56 | Tray(), 57 | CpuRam(), 58 | SystemInfo(), 59 | Date(), 60 | ], 61 | }); 62 | 63 | export default () => 64 | Widget.Window({ 65 | monitor: 0, 66 | name: `bar`, 67 | anchor: ["top", "left", "right"], 68 | exclusivity: "exclusive", 69 | 70 | child: Widget.CenterBox({ 71 | className: "bar", 72 | 73 | startWidget: Start(), 74 | centerWidget: Center(), 75 | endWidget: End(), 76 | }), 77 | }); 78 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/battery.js: -------------------------------------------------------------------------------- 1 | import { Battery, Widget } from "../../../imports.js"; 2 | 3 | export default () => 4 | Widget.Icon({ className: "battery module" }) 5 | .bind("icon", Battery, "icon-name") 6 | .bind("tooltip-text", Battery, "percent", (p) => `Battery on ${p}%`); 7 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/bluetooth.js: -------------------------------------------------------------------------------- 1 | import { Bluetooth, Widget } from "../../../imports.js"; 2 | import { 3 | getBluetoothIcon, 4 | getBluetoothText, 5 | } from "../../../utils/bluetooth.js"; 6 | 7 | export default () => 8 | Widget.Icon({ className: "bluetooth module" }) 9 | .bind("icon", Bluetooth, "connected-devices", getBluetoothIcon) 10 | .bind("tooltip-text", Bluetooth, "connected-devices", getBluetoothText); 11 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/cpu_ram.js: -------------------------------------------------------------------------------- 1 | import { Utils, Widget } from "../../../imports.js"; 2 | 3 | const Indicator = (props) => 4 | Widget.Box({ 5 | vertical: true, 6 | vexpand: true, 7 | vpack: "center", 8 | 9 | children: [ 10 | Widget.Label({ 11 | className: "type", 12 | label: props.type, 13 | }), 14 | Widget.Label({ className: "value" }) 15 | .poll(2000, props.poll), 16 | ], 17 | }).poll(2000, props.boxpoll); 18 | 19 | const cpu = { 20 | type: "CPU", 21 | 22 | poll: (self) => 23 | Utils.execAsync([ 24 | "sh", 25 | "-c", 26 | `top -bn1 | rg '%Cpu' | tail -1 | awk '{print 100-$8}'`, 27 | ]) 28 | .then((r) => (self.label = Math.round(Number(r)) + "%")) 29 | .catch((err) => print(err)), 30 | 31 | boxpoll: (self) => 32 | Utils.execAsync([ 33 | "sh", 34 | "-c", 35 | "lscpu --parse=MHZ", 36 | ]) 37 | .then((r) => { 38 | const mhz = r.split("\n").slice(4); 39 | const freq = mhz.reduce((acc, e) => acc + Number(e), 0) / mhz.length; 40 | self.tooltipText = Math.round(freq) + " MHz"; 41 | }) 42 | .catch((err) => print(err)), 43 | }; 44 | 45 | const ram = { 46 | type: "MEM", 47 | poll: (self) => 48 | Utils.execAsync([ 49 | "sh", 50 | "-c", 51 | `free | tail -2 | head -1 | awk '{print $3/$2*100}'`, 52 | ]) 53 | .then((r) => (self.label = Math.round(Number(r)) + "%")) 54 | .catch((err) => print(err)), 55 | 56 | boxpoll: (self) => 57 | Utils.execAsync([ 58 | "sh", 59 | "-c", 60 | "free --si -h | tail -2 | head -1 | awk '{print $3}'", 61 | ]) 62 | .then((r) => self.tooltipText = r) 63 | .catch((err) => print(err)), 64 | }; 65 | 66 | export default () => 67 | Widget.EventBox({ 68 | onPrimaryClick: () => Utils.execAsync(["resources"]), 69 | 70 | child: Widget.Box({ 71 | className: "system-info module", 72 | 73 | children: [ 74 | Indicator(cpu), 75 | Indicator(ram), 76 | ], 77 | }), 78 | }); 79 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/date.js: -------------------------------------------------------------------------------- 1 | import { Utils, Widget } from "../../../imports.js"; 2 | 3 | export default () => 4 | Widget.EventBox({ 5 | child: Widget.Label({ className: "date module" }) 6 | .poll( 7 | 1000, 8 | (self) => 9 | Utils.execAsync(["date", "+%a %b %d %H:%M"]).then((r) => 10 | self.label = r 11 | ), 12 | ), 13 | }); 14 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/music.js: -------------------------------------------------------------------------------- 1 | import { Mpris, Widget } from "../../../imports.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import { findPlayer } from "../../../utils/mpris.js"; 4 | 5 | const Cover = (player) => 6 | Widget.Box({ className: "cover" }) 7 | .bind( 8 | "css", 9 | player, 10 | "cover-path", 11 | (cover) => `background-image: url('${cover ?? ""}');`, 12 | ); 13 | 14 | const Title = (player) => 15 | Widget.Label({ className: "title module" }) 16 | .bind( 17 | "label", 18 | player, 19 | "track-title", 20 | (title) => (title ?? "") == "Unknown title" ? "" : title, 21 | ); 22 | 23 | export const MusicBox = (player) => 24 | Widget.Box({ 25 | children: [ 26 | Cover(player), 27 | Title(player), 28 | ], 29 | }); 30 | 31 | export default () => 32 | Widget.EventBox({ 33 | className: "music", 34 | onPrimaryClick: () => App.toggleWindow("music"), 35 | }) 36 | .hook( 37 | App, 38 | (self, window, visible) => { 39 | if (window === "music") { 40 | self.toggleClassName("active", visible); 41 | } 42 | }, 43 | ) 44 | .bind("visible", Mpris, "players", (p) => p.length > 0) 45 | .bind("child", Mpris, "players", (players) => { 46 | if (players.length == 0) return Widget.Box(); 47 | return MusicBox(findPlayer(players)); 48 | }); 49 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/net.js: -------------------------------------------------------------------------------- 1 | import { Network, Widget } from "../../../imports.js"; 2 | import { getNetIcon, getNetText } from "../../../utils/net.js"; 3 | 4 | export default () => 5 | Widget.Icon({ className: "net module" }) 6 | .bind( 7 | "icon", 8 | Network, 9 | "connectivity", 10 | getNetIcon, 11 | ) 12 | .bind( 13 | "tooltip-text", 14 | Network, 15 | "connectivity", 16 | getNetText, 17 | ); 18 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/tray.js: -------------------------------------------------------------------------------- 1 | import { SystemTray, Widget } from "../../../imports.js"; 2 | import Gdk from "gi://Gdk?version=3.0"; 3 | 4 | const Item = (item) => 5 | Widget.Button({ 6 | child: Widget.Icon().bind("icon", item, "icon"), 7 | 8 | onPrimaryClick: (_, ev) => { 9 | try { 10 | item.activate(ev); 11 | } catch (err) { 12 | print(err); 13 | } 14 | }, 15 | 16 | setup: (self) => { 17 | const id = item.menu?.connect("popped-up", (menu) => { 18 | self.toggleClassName("active"); 19 | menu.connect("notify::visible", (menu) => { 20 | self.toggleClassName("active", menu.visible); 21 | }); 22 | menu.disconnect(id); 23 | }); 24 | 25 | if (id) { 26 | self.connect("destroy", () => item.menu?.disconnect(id)); 27 | } 28 | 29 | self.bind("tooltip-markup", item, "tooltip-markup"); 30 | }, 31 | 32 | onSecondaryClick: (btn) => 33 | item.menu?.popup_at_widget( 34 | btn, 35 | Gdk.Gravity.SOUTH, 36 | Gdk.Gravity.NORTH, 37 | null, 38 | ), 39 | }); 40 | 41 | export default () => 42 | Widget.Box({ className: "tray module" }) 43 | .bind("children", SystemTray, "items", (items) => items.map(Item)); 44 | -------------------------------------------------------------------------------- /home/services/ags/windows/bar/modules/workspaces.js: -------------------------------------------------------------------------------- 1 | import { Hyprland, Widget } from "../../../imports.js"; 2 | import { 3 | added, 4 | changeWorkspace, 5 | DEFAULT_MONITOR, 6 | focusedSwitch, 7 | getLastWorkspaceId, 8 | moveWorkspace, 9 | removed, 10 | workspaceActive, 11 | } from "../../../utils/hyprland.js"; 12 | 13 | globalThis.hyprland = Hyprland; 14 | 15 | const makeWorkspaces = () => 16 | [...Array(10)].map((_, i) => { 17 | const id = i + 1; 18 | 19 | return Widget.Button({ 20 | onPrimaryClick: () => changeWorkspace(id), 21 | 22 | visible: getLastWorkspaceId() >= id, 23 | 24 | setup: (self) => { 25 | const ws = Hyprland.getWorkspace(id); 26 | self.id = id; 27 | self.active = workspaceActive(id); 28 | self.monitor = DEFAULT_MONITOR; 29 | 30 | if (self.active) { 31 | self.monitor = { 32 | name: ws?.monitor ?? DEFAULT_MONITOR.name, 33 | id: ws?.monitorID ?? DEFAULT_MONITOR.id, 34 | }; 35 | self.toggleClassName(`monitor${self.monitor.id}`, true); 36 | } 37 | }, 38 | }); 39 | }); 40 | 41 | export default () => 42 | Widget.EventBox({ 43 | onScrollUp: () => changeWorkspace("+1"), 44 | onScrollDown: () => changeWorkspace("-1"), 45 | 46 | child: Widget.Box({ 47 | className: "workspaces module", 48 | 49 | // The Hyprland service is ready later than ags is done parsing the config, 50 | // so only build the widget when we receive a signal from it. 51 | setup: (self) => { 52 | const connID = Hyprland.connect("notify::workspaces", () => { 53 | Hyprland.disconnect(connID); 54 | 55 | self.children = makeWorkspaces(); 56 | self.lastFocused = Hyprland.active.workspace.id; 57 | self.biggestId = getLastWorkspaceId(); 58 | self 59 | .hook(Hyprland.active.workspace, focusedSwitch) 60 | .hook(Hyprland, added, "workspace-added") 61 | .hook(Hyprland, removed, "workspace-removed") 62 | .hook(Hyprland, (self, name, data) => { 63 | if (name === "moveworkspace") moveWorkspace(self, data); 64 | }, "event"); 65 | }); 66 | }, 67 | }), 68 | }); 69 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/controls.js: -------------------------------------------------------------------------------- 1 | import { Icons, Widget } from "../../imports.js"; 2 | import { mprisStateIcon } from "../../utils/mpris.js"; 3 | 4 | export default (player) => 5 | Widget.CenterBox({ 6 | className: "controls", 7 | hpack: "center", 8 | 9 | startWidget: Widget.Button({ 10 | onClicked: () => player.previous(), 11 | child: Widget.Icon(Icons.media.previous), 12 | }), 13 | 14 | centerWidget: Widget.Button({ 15 | onClicked: () => player.playPause(), 16 | 17 | child: Widget.Icon().bind( 18 | "icon", 19 | player, 20 | "play-back-status", 21 | mprisStateIcon, 22 | ), 23 | }), 24 | 25 | endWidget: Widget.Button({ 26 | onClicked: () => player.next(), 27 | child: Widget.Icon(Icons.media.next), 28 | }), 29 | }); 30 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/cover.js: -------------------------------------------------------------------------------- 1 | import { Widget } from "../../imports.js"; 2 | 3 | export default (player) => 4 | Widget.Box({ className: "cover" }) 5 | .bind( 6 | "css", 7 | player, 8 | "cover-path", 9 | (cover) => `background-image: url('${cover ?? ""}')`, 10 | ); 11 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/main.js: -------------------------------------------------------------------------------- 1 | import { Mpris, Widget } from "../../imports.js"; 2 | import { findPlayer, generateBackground } from "../../utils/mpris.js"; 3 | import PopupWindow from "../../utils/popup_window.js"; 4 | 5 | import Cover from "./cover.js"; 6 | import { Artists, Title } from "./title_artists.js"; 7 | import TimeInfo from "./time_info.js"; 8 | import Controls from "./controls.js"; 9 | import PlayerInfo from "./player_info.js"; 10 | 11 | const Info = (player) => 12 | Widget.Box({ 13 | className: "info", 14 | vertical: true, 15 | vexpand: false, 16 | hexpand: false, 17 | homogeneous: true, 18 | 19 | children: [ 20 | PlayerInfo(player), 21 | Title(player), 22 | Artists(player), 23 | Controls(player), 24 | TimeInfo(player), 25 | ], 26 | }); 27 | 28 | const MusicBox = (player) => 29 | Widget.Box({ 30 | className: "music window", 31 | children: [ 32 | Cover(player), 33 | Info(player), 34 | ], 35 | }) 36 | .bind( 37 | "css", 38 | player, 39 | "cover-path", 40 | generateBackground, 41 | ); 42 | 43 | export default () => 44 | PopupWindow({ 45 | monitor: 0, 46 | anchor: ["top"], 47 | name: "music", 48 | child: Widget.Box(), 49 | }) 50 | .bind( 51 | "child", 52 | Mpris, 53 | "players", 54 | (players) => { 55 | if (players.length == 0) return Widget.Box(); 56 | return MusicBox(findPlayer(players)); 57 | }, 58 | ); 59 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/player_info.js: -------------------------------------------------------------------------------- 1 | import { Icons, Utils, Widget } from "../../imports.js"; 2 | 3 | export default (player) => 4 | Widget.Box({ 5 | className: "player-info", 6 | vexpand: true, 7 | vpack: "start", 8 | 9 | children: [ 10 | Widget.Icon({ 11 | hexpand: true, 12 | hpack: "end", 13 | className: "player-icon", 14 | tooltipText: player.identity ?? "", 15 | }) 16 | .bind( 17 | "icon", 18 | player, 19 | "entry", 20 | (entry) => { 21 | // the Spotify icon is called spotify-client 22 | if (entry == "spotify") entry = "spotify-client"; 23 | return Utils.lookUpIcon(entry ?? "") ? entry : Icons.media.player; 24 | }, 25 | ), 26 | ], 27 | }); 28 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/time_info.js: -------------------------------------------------------------------------------- 1 | import { Widget } from "../../imports.js"; 2 | import { lengthStr } from "../../utils/mpris.js"; 3 | 4 | export const PositionLabel = (player) => 5 | Widget.Label({ 6 | className: "position", 7 | hexpand: true, 8 | xalign: 0, 9 | 10 | setup: (self) => { 11 | const update = (_, time) => { 12 | player.length > 0 13 | ? self.label = lengthStr(time || player.position) 14 | : self.visible = !!player; 15 | }; 16 | 17 | self 18 | .hook(player, update, "position") 19 | .poll(1000, update); 20 | }, 21 | }); 22 | 23 | export const LengthLabel = (player) => 24 | Widget.Label({ 25 | className: "length", 26 | hexpand: true, 27 | xalign: 1, 28 | }) 29 | .bind("visible", player, "length", (length) => length > 0) 30 | .bind("label", player, "length", (length) => lengthStr(length)); 31 | 32 | export const Position = (player) => 33 | Widget.Slider({ 34 | className: "position", 35 | draw_value: false, 36 | 37 | onChange: ({ value }) => player.position = player.length * value, 38 | 39 | setup: (self) => { 40 | const update = () => { 41 | if (self.dragging) return; 42 | 43 | self.visible = player.length > 0; 44 | 45 | if (player.length > 0) { 46 | self.value = player.position / player.length; 47 | } 48 | }; 49 | 50 | self 51 | .hook(player, update) 52 | .hook(player, update, "position") 53 | .poll(1000, update); 54 | }, 55 | }); 56 | 57 | export default (player) => 58 | Widget.Box({ 59 | vertical: true, 60 | vexpand: true, 61 | vpack: "end", 62 | 63 | children: [ 64 | Widget.Box({ 65 | hexpand: true, 66 | children: [ 67 | PositionLabel(player), 68 | LengthLabel(player), 69 | ], 70 | }), 71 | Position(player), 72 | ], 73 | }); 74 | -------------------------------------------------------------------------------- /home/services/ags/windows/music/title_artists.js: -------------------------------------------------------------------------------- 1 | import { Widget } from "../../imports.js"; 2 | 3 | export const Title = (player) => 4 | Widget.Scrollable({ 5 | className: "title", 6 | vscroll: "never", 7 | hscroll: "automatic", 8 | 9 | child: Widget.Label({ 10 | className: "title", 11 | label: "Nothing playing", 12 | }) 13 | .bind( 14 | "label", 15 | player, 16 | "track-title", 17 | (title) => title ?? "Nothing playing", 18 | ), 19 | }); 20 | 21 | export const Artists = (player) => 22 | Widget.Scrollable({ 23 | className: "artists", 24 | vscroll: "never", 25 | hscroll: "automatic", 26 | 27 | child: Widget.Label({ className: "artists" }) 28 | .bind( 29 | "label", 30 | player, 31 | "track-artists", 32 | (artists) => artists.join(", ") ?? "", 33 | ), 34 | }); 35 | -------------------------------------------------------------------------------- /home/services/ags/windows/notifications/popups.js: -------------------------------------------------------------------------------- 1 | import { Hyprland, Notifications, Utils, Widget } from "../../imports.js"; 2 | 3 | const closeAll = () => { 4 | Notifications.popups.map(n => n.dismiss()); 5 | }; 6 | 7 | /** @param {import("types/service/notifications").Notification} n */ 8 | const NotificationIcon = ({ app_entry, app_icon, image }) => { 9 | if (image) { 10 | return Widget.Box({ 11 | css: ` 12 | background-image: url("${image}"); 13 | background-size: contain; 14 | background-repeat: no-repeat; 15 | background-position: center; 16 | `, 17 | }); 18 | } 19 | 20 | if (Utils.lookUpIcon(app_icon)) { 21 | return Widget.Icon(app_icon); 22 | } 23 | 24 | if (app_entry && Utils.lookUpIcon(app_entry)) { 25 | return Widget.Icon(app_entry); 26 | } 27 | 28 | return null; 29 | }; 30 | 31 | /** @param {import('types/service/notifications').Notification} n */ 32 | export const Notification = (n) => { 33 | const icon = Widget.Box({ 34 | vpack: "start", 35 | class_name: "icon", 36 | // @ts-ignore 37 | setup: self => { 38 | let icon = NotificationIcon(n); 39 | if (icon !== null) { 40 | self.child = icon; 41 | } 42 | }, 43 | }); 44 | 45 | const title = Widget.Label({ 46 | class_name: "title", 47 | xalign: 0, 48 | justification: "left", 49 | hexpand: true, 50 | max_width_chars: 24, 51 | truncate: "end", 52 | wrap: true, 53 | label: n.summary, 54 | use_markup: true, 55 | }); 56 | 57 | const body = Widget.Label({ 58 | class_name: "body", 59 | hexpand: true, 60 | use_markup: true, 61 | xalign: 0, 62 | justification: "left", 63 | max_width_chars: 100, 64 | wrap: true, 65 | label: n.body, 66 | }); 67 | 68 | const actions = Widget.Box({ 69 | class_name: "actions", 70 | children: n.actions.filter(({ id }) => id != "default").map(({ id, label }) => 71 | Widget.Button({ 72 | class_name: "action-button", 73 | on_clicked: () => n.invoke(id), 74 | hexpand: true, 75 | child: Widget.Label(label), 76 | }) 77 | ), 78 | }); 79 | 80 | return Widget.EventBox({ 81 | on_primary_click: () => { 82 | if (n.actions.length > 0) n.invoke(n.actions[0].id); 83 | }, 84 | on_middle_click: closeAll, 85 | on_secondary_click: () => n.dismiss(), 86 | child: Widget.Box({ 87 | class_name: `notification ${n.urgency}`, 88 | vertical: true, 89 | 90 | children: [ 91 | Widget.Box({ 92 | class_name: "info", 93 | children: [ 94 | icon, 95 | Widget.Box({ 96 | vertical: true, 97 | class_name: "text", 98 | vpack: "center", 99 | 100 | setup: self => { 101 | if (n.body.length > 0) { 102 | self.children = [title, body]; 103 | } else { 104 | self.children = [title]; 105 | } 106 | }, 107 | }), 108 | ], 109 | }), 110 | actions, 111 | ], 112 | }), 113 | }); 114 | }; 115 | 116 | let lastMonitor; 117 | export const notificationPopup = () => 118 | Widget.Window({ 119 | name: "notifications", 120 | anchor: ["top", "right"], 121 | child: Widget.Box({ 122 | css: "padding: 1px;", 123 | class_name: "notifications", 124 | vertical: true, 125 | // @ts-ignore 126 | children: Notifications.bind("popups").transform((popups) => { 127 | return popups.map(Notification); 128 | }), 129 | }), 130 | }) 131 | .hook( 132 | Hyprland.active, 133 | (self) => { 134 | // prevent useless resets 135 | if (lastMonitor === Hyprland.active.monitor) return; 136 | 137 | self.monitor = Hyprland.active.monitor.id; 138 | }, 139 | ); 140 | 141 | export default notificationPopup; 142 | -------------------------------------------------------------------------------- /home/services/ags/windows/osd/main.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import { Audio, Hyprland, Widget } from "../../imports.js"; 3 | 4 | import Brightness from "../../services/brightness.js"; 5 | import Indicators from "../../services/osd.js"; 6 | import PopupWindow from "../../utils/popup_window.js"; 7 | 8 | // connections 9 | Audio.connect("speaker-changed", () => 10 | Audio.speaker.connect( 11 | "changed", 12 | () => { 13 | if (!App.getWindow("system-menu")?.visible) { 14 | Indicators.speaker(); 15 | } 16 | }, 17 | )); 18 | Audio.connect( 19 | "microphone-changed", 20 | () => Audio.microphone.connect("changed", () => Indicators.mic()), 21 | ); 22 | 23 | Brightness.connect("screen-changed", () => { 24 | if (!App.getWindow("system-menu")?.visible) { 25 | Indicators.display(); 26 | } 27 | }); 28 | 29 | let lastMonitor; 30 | 31 | const child = () => 32 | Widget.Box({ 33 | className: "osd", 34 | 35 | children: [ 36 | Widget.Overlay({ 37 | hexpand: true, 38 | visible: false, 39 | passThrough: true, 40 | 41 | child: Widget.ProgressBar({ 42 | hexpand: true, 43 | vertical: false, 44 | }) 45 | .hook( 46 | Indicators, 47 | (self, props) => { 48 | self.value = props?.value ?? 0; 49 | self.visible = props?.showProgress ?? false; 50 | }, 51 | ), 52 | 53 | overlays: [ 54 | Widget.Box({ 55 | hexpand: true, 56 | 57 | children: [ 58 | Widget.Icon().hook( 59 | Indicators, 60 | (self, props) => self.icon = props?.icon ?? "", 61 | ), 62 | Widget.Box({ 63 | hexpand: true, 64 | }), 65 | ], 66 | }), 67 | ], 68 | }), 69 | ], 70 | }); 71 | 72 | export default () => 73 | PopupWindow({ 74 | name: "osd", 75 | monitor: 0, 76 | layer: "overlay", 77 | child: child(), 78 | click_through: true, 79 | anchor: ["bottom"], 80 | revealerSetup: (self) => 81 | self 82 | .hook(Indicators, (revealer, _, visible) => { 83 | revealer.reveal_child = visible; 84 | }), 85 | }) 86 | .hook( 87 | Hyprland.active, 88 | (self) => { 89 | // prevent useless resets 90 | if (lastMonitor === Hyprland.active.monitor) return; 91 | 92 | self.monitor = Hyprland.active.monitor.id; 93 | }, 94 | ) 95 | .hook(Indicators, (win, _, visible) => { 96 | win.visible = visible; 97 | }); 98 | -------------------------------------------------------------------------------- /home/services/ags/windows/system-menu/battery_info.js: -------------------------------------------------------------------------------- 1 | import { App, Battery, Icons, Utils, Widget } from "../../imports.js"; 2 | import { batteryTime } from "../../utils/battery.js"; 3 | 4 | const batteryEnergy = () => { 5 | return Battery.energyRate > 0.1 ? `${Battery.energyRate.toFixed(1)} W ` : ""; 6 | }; 7 | 8 | const BatteryIcon = () => 9 | Widget.Icon() 10 | .bind("icon", Battery, "percent", () => Battery.iconName) 11 | .bind("tooltip-text", Battery, "energy-rate", batteryEnergy); 12 | 13 | const BatteryPercent = () => 14 | Widget.Label() 15 | .bind( 16 | "label", 17 | Battery, 18 | "percent", 19 | (percent) => `${percent}%`, 20 | ); 21 | 22 | const BatteryTime = () => 23 | Widget.Label({ 24 | className: "time", 25 | vexpand: true, 26 | vpack: "center", 27 | }) 28 | .bind("label", Battery, "charging", batteryTime) 29 | .bind("label", Battery, "energy-rate", batteryTime); 30 | 31 | const BatteryBox = () => 32 | Widget.Box({ 33 | className: "battery-box", 34 | visible: Battery.available, 35 | 36 | children: [ 37 | BatteryIcon(), 38 | BatteryPercent(), 39 | BatteryTime(), 40 | ], 41 | }); 42 | 43 | const PowerButton = () => 44 | Widget.Button({ 45 | className: "button disabled", 46 | hexpand: true, 47 | hpack: "end", 48 | 49 | onPrimaryClick: () => { 50 | App.toggleWindow("system-menu"); 51 | Utils.exec("wlogout"); 52 | }, 53 | 54 | child: Widget.Icon(Icons.powerButton), 55 | }); 56 | 57 | export default () => 58 | Widget.Box({ 59 | className: "battery-info", 60 | 61 | children: [ 62 | BatteryBox(), 63 | PowerButton(), 64 | ], 65 | }); 66 | -------------------------------------------------------------------------------- /home/services/ags/windows/system-menu/main.js: -------------------------------------------------------------------------------- 1 | import { Widget } from "../../imports.js"; 2 | import PopupWindow from "../../utils/popup_window.js"; 3 | 4 | import Toggles from "./toggles.js"; 5 | import PowerProfiles from "./powerprofiles.js"; 6 | import Sliders from "./sliders.js"; 7 | import BatteryInfo from "./battery_info.js"; 8 | 9 | const SystemMenuBox = () => 10 | Widget.Box({ 11 | className: "system-menu", 12 | vertical: true, 13 | 14 | children: [ 15 | Toggles(), 16 | PowerProfiles(), 17 | Sliders(), 18 | BatteryInfo(), 19 | ], 20 | }); 21 | 22 | export default () => 23 | PopupWindow({ 24 | monitor: 0, 25 | anchor: ["top", "right"], 26 | name: "system-menu", 27 | child: SystemMenuBox(), 28 | }); 29 | -------------------------------------------------------------------------------- /home/services/ags/windows/system-menu/powerprofiles.js: -------------------------------------------------------------------------------- 1 | import { PowerProfiles, Variable, Widget } from "../../imports.js"; 2 | 3 | const showList = Variable(false); 4 | 5 | const Profile = (args) => 6 | Widget.EventBox({ 7 | onPrimaryClick: args.primaryClickAction, 8 | hexpand: true, 9 | child: Widget.Box({ 10 | ...args.props ?? {}, 11 | hexpand: true, 12 | hpack: "start", 13 | 14 | children: [ 15 | Widget.Icon({ 16 | icon: args.icon ?? "", 17 | setup: args.iconSetup, 18 | }), 19 | Widget.Label({ 20 | label: args.label ?? "", 21 | setup: args.labelSetup, 22 | }), 23 | ], 24 | }), 25 | }); 26 | 27 | const prettyName = (n) => 28 | n.charAt(0).toUpperCase() + 29 | n.substring(1).replace("-", " "); 30 | 31 | const makeProfiles = (profiles) => 32 | profiles.map((e) => 33 | Profile({ 34 | primaryClickAction: () => { 35 | PowerProfiles.activeProfile = e.Profile; 36 | showList.value = false; 37 | }, 38 | icon: `power-profile-${e.Profile}-symbolic`, 39 | label: prettyName(e.Profile), 40 | }) 41 | ); 42 | 43 | const ActiveProfile = () => 44 | Profile({ 45 | props: { 46 | className: "current-profile", 47 | }, 48 | primaryClickAction: () => showList.value = !showList.value, 49 | iconSetup: (self) => self.bind("icon", PowerProfiles, "icon-name"), 50 | labelSetup: (self) => 51 | self.bind("label", PowerProfiles, "active-profile", prettyName), 52 | }); 53 | 54 | const ProfileRevealer = () => 55 | Widget.Revealer({ 56 | revealChild: false, 57 | transition: "slide_down", 58 | 59 | child: Widget.Box({ 60 | className: "options", 61 | vertical: true, 62 | }) 63 | .bind( 64 | "children", 65 | PowerProfiles, 66 | "profiles", 67 | makeProfiles, 68 | ), 69 | }) 70 | .bind("reveal-child", showList); 71 | 72 | export default () => 73 | Widget.Box({ 74 | className: "power-profiles", 75 | vertical: true, 76 | 77 | children: [ 78 | Widget.Box({ 79 | vertical: true, 80 | children: [ 81 | ActiveProfile(), 82 | ProfileRevealer(), 83 | ], 84 | }), 85 | ], 86 | }); 87 | -------------------------------------------------------------------------------- /home/services/ags/windows/system-menu/sliders.js: -------------------------------------------------------------------------------- 1 | import { App, Audio, Icons, Utils, Widget } from "../../imports.js"; 2 | import Brightness from "../../services/brightness.js"; 3 | import { audioIcon } from "../../utils/audio.js"; 4 | 5 | const Slider = (args) => 6 | Widget.Box({ 7 | ...args.props ?? {}, 8 | className: args.name, 9 | 10 | children: [ 11 | Widget.Button({ 12 | onPrimaryClick: args.icon.action ?? null, 13 | child: Widget.Icon({ 14 | icon: args.icon.icon ?? "", 15 | setup: args.icon.setup, 16 | }), 17 | }), 18 | Widget.Slider({ 19 | drawValue: false, 20 | hexpand: true, 21 | setup: args.slider.setup, 22 | onChange: args.slider.onChange ?? null, 23 | }), 24 | ], 25 | }); 26 | 27 | const vol = () => { 28 | return { 29 | name: "volume", 30 | icon: { 31 | icon: "", 32 | action: () => { 33 | App.toggleWindow("system-menu"); 34 | Utils.execAsync("pwvucontrol"); 35 | }, 36 | setup: (self) => 37 | self 38 | .bind("icon", Audio.speaker, "volume", audioIcon) 39 | .bind("icon", Audio.speaker.stream, "is-muted", audioIcon), 40 | }, 41 | slider: { 42 | setup: (self) => self.bind("value", Audio.speaker, "volume"), 43 | onChange: ({ value }) => Audio.speaker.volume = value, 44 | }, 45 | }; 46 | }; 47 | 48 | const brightness = () => { 49 | return { 50 | name: "brightness", 51 | icon: { 52 | icon: Icons.brightness, 53 | }, 54 | slider: { 55 | setup: (self) => self.bind("value", Brightness, "screen-value"), 56 | onChange: ({ value }) => Brightness.screenValue = value, 57 | }, 58 | }; 59 | }; 60 | 61 | export default () => 62 | Widget.Box({ 63 | className: "sliders", 64 | vertical: true, 65 | 66 | // The Audio service is ready later than ags is done parsing the config, 67 | // so only build the widget when we receive a signal from it. 68 | setup: (self) => { 69 | const connID = Audio.connect("notify::speaker", () => { 70 | Audio.disconnect(connID); 71 | self.children = [ 72 | Slider(vol()), 73 | Slider(brightness()), 74 | ]; 75 | }); 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /home/services/ags/windows/system-menu/toggles.js: -------------------------------------------------------------------------------- 1 | import { App, Bluetooth, Network, Utils, Widget } from "../../imports.js"; 2 | 3 | import { getNetIcon, getNetText } from "../../utils/net.js"; 4 | import { getBluetoothIcon, getBluetoothText } from "../../utils/bluetooth.js"; 5 | 6 | const Toggle = (args) => 7 | Widget.Box({ 8 | ...args.props ?? {}, 9 | className: `toggle ${args.name}`, 10 | hexpand: true, 11 | hpack: "start", 12 | 13 | children: [ 14 | Widget.Button({ 15 | className: "button", 16 | 17 | child: Widget.Icon({ 18 | setup: args.icon.setup, 19 | }), 20 | setup: args.icon.buttonSetup, 21 | }), 22 | Widget.Button({ 23 | hexpand: true, 24 | child: Widget.Label({ 25 | hpack: "start", 26 | setup: args.label.setup, 27 | }), 28 | setup: args.label.buttonSetup, 29 | }), 30 | ], 31 | }); 32 | 33 | const net = { 34 | name: "net", 35 | icon: { 36 | setup: (self) => 37 | self 38 | .bind("icon", Network, "connectivity", getNetIcon) 39 | .bind("icon", Network.wifi, "icon-name", getNetIcon), 40 | 41 | buttonSetup: (self) => { 42 | self.onPrimaryClick = () => Network.toggleWifi(); 43 | self.hook( 44 | Network, 45 | (btn) => 46 | btn.toggleClassName("disabled", Network.connectivity != "full"), 47 | "notify::connectivity", 48 | ); 49 | }, 50 | }, 51 | label: { 52 | setup: (self) => 53 | self 54 | .bind("label", Network, "connectivity", () => getNetText()) 55 | .bind("label", Network.wifi, "ssid", () => getNetText()), 56 | 57 | buttonSetup: (self) => { 58 | self.onPrimaryClick = () => { 59 | App.toggleWindow("system-menu"); 60 | Utils.execAsync([ 61 | "sh", 62 | "-c", 63 | "XDG_CURRENT_DESKTOP=GNOME gnome-control-center", 64 | ]); 65 | }; 66 | }, 67 | }, 68 | }; 69 | 70 | const bt = { 71 | name: "bluetooth", 72 | icon: { 73 | setup: (self) => 74 | self.bind( 75 | "icon", 76 | Bluetooth, 77 | "connected-devices", 78 | getBluetoothIcon, 79 | ), 80 | buttonSetup: (self) => { 81 | self.onPrimaryClick = () => Bluetooth.toggle(); 82 | self.hook( 83 | Bluetooth, 84 | (btn) => btn.toggleClassName("disabled", !Bluetooth.enabled), 85 | "notify::enabled", 86 | ); 87 | }, 88 | }, 89 | label: { 90 | setup: (self) => 91 | self.bind( 92 | "label", 93 | Bluetooth, 94 | "connected-devices", 95 | getBluetoothText, 96 | ), 97 | buttonSetup: (self) => { 98 | self.onPrimaryClick = () => { 99 | App.toggleWindow("system-menu"); 100 | Utils.execAsync("overskride"); 101 | }; 102 | }, 103 | }, 104 | }; 105 | 106 | export default () => 107 | Widget.Box({ 108 | className: "toggles", 109 | vertical: true, 110 | 111 | children: [ 112 | Toggle(net), 113 | Toggle(bt), 114 | ], 115 | }); 116 | -------------------------------------------------------------------------------- /home/services/cinny.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | # use Cinny Matrix client 3 | # create systemd service that serves it on localhost:9999 4 | systemd.user.services.cinny = { 5 | Unit.Description = "Cinny Service"; 6 | Service = { 7 | Type = "simple"; 8 | ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${pkgs.cinny} --addr 127.0.0.1 --port 9999"; 9 | TimeoutStopSec = 5; 10 | }; 11 | Install.WantedBy = ["default.target"]; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /home/services/media/playerctl.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | home.packages = [pkgs.playerctl]; 3 | 4 | services.playerctld.enable = true; 5 | } 6 | -------------------------------------------------------------------------------- /home/services/media/spotifyd.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | ... 5 | }: { 6 | home.packages = with pkgs; [ 7 | spotify-tui 8 | ]; 9 | 10 | services.spotifyd = { 11 | enable = true; 12 | package = pkgs.spotifyd.override {withMpris = true;}; 13 | settings.global = { 14 | autoplay = true; 15 | backend = "pulseaudio"; 16 | bitrate = 320; 17 | cache_path = "${config.xdg.cacheHome}/spotifyd"; 18 | device_type = "computer"; 19 | initial_volume = "100"; 20 | password_cmd = "tail -1 /run/agenix/spotify"; 21 | use_mpris = true; 22 | username_cmd = "head -1 /run/agenix/spotify"; 23 | volume_normalisation = false; 24 | }; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Battery.qml: -------------------------------------------------------------------------------- 1 | import Quickshell 2 | import Quickshell.Services.UPower 3 | import QtQuick 4 | import QtQuick.Layouts 5 | import org.kde.kirigami 6 | 7 | Rectangle { 8 | id: bat 9 | 10 | Layout.preferredWidth: batIcon.width 11 | Layout.fillHeight: true 12 | color: 'transparent' 13 | 14 | readonly property var battery: UPower.displayDevice 15 | readonly property int percentage: Math.round(battery.percentage * 100) 16 | property var size: height * 0.4 17 | 18 | visible: battery.isLaptopBattery 19 | 20 | Icon { 21 | id: batIcon 22 | anchors.centerIn: parent 23 | 24 | implicitHeight: bat.size 25 | implicitWidth: bat.size 26 | 27 | // This recolors the entire svg, instead of only classless components. 28 | // Hopefully in the future classes can be selected for recoloring. 29 | isMask: true 30 | color: 'white' 31 | 32 | source: { 33 | const nearestTen = Math.round(bat.percentage / 10) * 10; 34 | const number = nearestTen.toString().padStart(2, "0"); 35 | let charging; 36 | 37 | if (bat.battery.state == UPowerDeviceState.Charging) { 38 | charging = "-charging"; 39 | } else if (bat.battery.state.toString() == UPowerDeviceState.FullyCharged) { 40 | charging = "-charged"; 41 | } else { 42 | charging = ""; 43 | } 44 | 45 | return Quickshell.iconPath(`battery-level-${number}${charging}-symbolic`); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Clock.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | import "../../utils" 4 | 5 | Rectangle { 6 | Layout.fillHeight: true 7 | color: "transparent" 8 | implicitWidth: clockText.width 9 | 10 | Text { 11 | id: clockText 12 | text: Time.time 13 | color: Colors.fg 14 | anchors.centerIn: parent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Mpris.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | 4 | Rectangle { 5 | Layout.fillHeight: true 6 | color: "salmon" 7 | implicitWidth: mprisText.width 8 | 9 | Text { 10 | id: mprisText 11 | text: "Mpris" 12 | anchors.centerIn: parent 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Resources.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | import QtQuick.Controls 4 | import "../../utils" 5 | 6 | Rectangle { 7 | id: resources 8 | 9 | Layout.fillHeight: true 10 | color: "transparent" 11 | implicitWidth: rowLayout.width 12 | 13 | property int valueSize: 8 14 | property int textSize: 6 15 | 16 | property string valueColor: "white" 17 | property string textColor: "lightgray" 18 | 19 | RowLayout { 20 | id: rowLayout 21 | anchors.centerIn: parent 22 | 23 | ColumnLayout { 24 | id: cpuColumn 25 | Label { 26 | color: textColor 27 | font.pointSize: textSize 28 | text: "CPU" 29 | Layout.alignment: Qt.AlignCenter 30 | } 31 | Label { 32 | color: valueColor 33 | font.pointSize: valueSize 34 | text: Resources.cpu_percent + "%" 35 | Layout.alignment: Qt.AlignCenter 36 | } 37 | } 38 | 39 | ColumnLayout { 40 | Label { 41 | color: textColor 42 | font.pointSize: textSize 43 | text: "MEM" 44 | Layout.alignment: Qt.AlignCenter 45 | } 46 | Label { 47 | color: valueColor 48 | font.pointSize: valueSize 49 | text: Resources.mem_percent + "%" 50 | Layout.alignment: Qt.AlignCenter 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Text.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | Text { 4 | renderType: Text.NativeRendering 5 | } 6 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/Tray.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | 4 | Rectangle { 5 | Layout.fillHeight: true 6 | color: "lightblue" 7 | implicitWidth: trayText.width 8 | 9 | Text { 10 | id: trayText 11 | text: "Tray" 12 | anchors.centerIn: parent 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/workspaces/Workspace.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | 4 | Rectangle { 5 | id: ws 6 | 7 | property bool hovered: false 8 | 9 | Layout.preferredWidth: parent.height * 0.4 10 | Layout.preferredHeight: parent.height * 0.4 11 | Layout.alignment: Qt.AlignHCenter 12 | radius: height / 2 13 | 14 | MouseArea { 15 | anchors.fill: parent 16 | hoverEnabled: true 17 | 18 | onEntered: () => { 19 | ws.hovered = true; 20 | } 21 | onExited: () => { 22 | ws.hovered = false; 23 | } 24 | onClicked: () => console.log(`workspace ?`) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /home/services/quickshell/components/bar/workspaces/Workspaces.qml: -------------------------------------------------------------------------------- 1 | pragma ComponentBehavior: Bound 2 | 3 | import QtQuick 4 | import QtQuick.Layouts 5 | import "../../../utils" 6 | import Quickshell.Hyprland 7 | 8 | Rectangle { 9 | id: workspaces 10 | 11 | color: 'transparent' 12 | 13 | width: workspacesRow.implicitWidth 14 | Layout.fillHeight: true 15 | 16 | RowLayout { 17 | id: workspacesRow 18 | 19 | height: parent.height 20 | implicitWidth: (parent.height * 0.5 + spacing) * 2 - spacing 21 | anchors.centerIn: parent 22 | 23 | spacing: height / 7 24 | 25 | Repeater { 26 | id: repeater 27 | 28 | model: HyprlandUtils.maxWorkspace 29 | 30 | Workspace { 31 | id: ws 32 | required property int index 33 | property HyprlandWorkspace currWorkspace: Hyprland.workspaces.values.find(e => e.id == index + 1) || null 34 | property bool nonexistent: currWorkspace === null 35 | property bool focused: index + 1 === Hyprland.focusedMonitor.activeWorkspace.id 36 | 37 | Layout.preferredWidth: { 38 | if (focused) { 39 | return parent.height * 0.8; 40 | } else { 41 | return parent.height * 0.4; 42 | } 43 | } 44 | 45 | color: { 46 | if (nonexistent) { 47 | return Colors.bgBlur; 48 | } else { 49 | return Colors.monitorColors[Hyprland.monitors.values.indexOf(Hyprland.workspaces.values.find(e => e.id === index + 1).monitor)]; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /home/services/quickshell/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | lib, 5 | ... 6 | }: let 7 | quickshell = inputs.quickshell.packages.${pkgs.system}.default; 8 | in { 9 | home.packages = [quickshell]; 10 | 11 | home.sessionVariables.QML2_IMPORT_PATH = lib.concatStringsSep ":" [ 12 | "${quickshell}/lib/qt-6/qml" 13 | "${pkgs.kdePackages.qtdeclarative}/lib/qt-6/qml" 14 | "${pkgs.kdePackages.kirigami.unwrapped}/lib/qt-6/qml" 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /home/services/quickshell/shell.qml: -------------------------------------------------------------------------------- 1 | import "./windows" 2 | import Quickshell // for ShellRoot and PanelWindow 3 | 4 | ShellRoot { 5 | Bar {} 6 | } 7 | -------------------------------------------------------------------------------- /home/services/quickshell/utils/Colors.qml: -------------------------------------------------------------------------------- 1 | import Quickshell 2 | pragma Singleton 3 | 4 | Singleton { 5 | property var bgBar: Qt.rgba(0, 0, 0, 0.21) 6 | property var bgBlur: Qt.rgba(0, 0, 0, 0.3) 7 | property var fg: "white" 8 | property list monitorColors: ["#e06c75", "#e5c07b", "#98c379", "#61afef"] 9 | } 10 | -------------------------------------------------------------------------------- /home/services/quickshell/utils/HyprlandUtils.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import Quickshell 4 | import Quickshell.Hyprland 5 | import QtQuick 6 | 7 | Singleton { 8 | id: hyprland 9 | 10 | property list workspaces: sortWorkspaces(Hyprland.workspaces.values) 11 | property HyprlandWorkspace focusedWorkspace: Hyprland.focusedMonitor?.activeWorkspace 12 | property int maxWorkspace: findMaxId() 13 | 14 | function sortWorkspaces(ws) { 15 | return [...ws].sort((a, b) => a?.id - b?.id); 16 | } 17 | 18 | function switchWorkspace(w: int): void { 19 | console.log(`workspace: focus ${focusedWorkspace.id} -> ${w}`); 20 | Hyprland.dispatch(`workspace ${w}`); 21 | } 22 | 23 | function findMaxId(): int { 24 | let num = hyprland.workspaces.length; 25 | return hyprland.workspaces[num - 1]?.id; 26 | } 27 | 28 | Connections { 29 | target: Hyprland 30 | function onRawEvent(event) { 31 | // console.log("EVENT NAME", event.name); 32 | // consow.wg("EVENT DATA", event.data); 33 | let eventName = event.name; 34 | 35 | switch (eventName) { 36 | // Both of these are required in order to detect workspace changes 37 | // even when switching monitors. 38 | // case "workspacev2": 39 | // { 40 | // // hyprland.focusedWorkspace = Hyprland.focusedMonitor?.activeWorkspace; 41 | // console.log(`workspace: ${hyprland.focusedWorkspace.id}`); 42 | // console.log(`num workspaces ${hyprland.workspaces.length}`) 43 | // console.log(`num workspaces (real) ${Hyprland.workspaces.values.length}`) 44 | // break; 45 | // } 46 | // case "focusedmonv2": 47 | // { 48 | // // hyprland.focusedWorkspace = Hyprland.focusedMonitor?.activeWorkspace; 49 | // console.log(`workspace: ${hyprland.focusedWorkspace.id}`); 50 | // console.log(`num workspaces ${hyprland.workspaces.length}`) 51 | // console.log(`num workspaces (real) ${Hyprland.workspaces.values.length}`) 52 | // break; 53 | // } 54 | case "createworkspacev2": 55 | { 56 | hyprland.workspaces = hyprland.sortWorkspaces(Hyprland.workspaces.values); 57 | hyprland.maxWorkspace = findMaxId(); 58 | } 59 | case "destroyworkspacev2": 60 | { 61 | hyprland.workspaces = hyprland.sortWorkspaces(Hyprland.workspaces.values); 62 | hyprland.maxWorkspace = findMaxId(); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /home/services/quickshell/utils/Resources.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import Quickshell 4 | import Quickshell.Io 5 | import QtQuick 6 | 7 | Singleton { 8 | property int cpu_percent 9 | property string cpu_freq 10 | property int mem_percent 11 | property string mem_used 12 | 13 | Process { 14 | id: process_cpu_percent 15 | running: true 16 | command: ["sh", "-c", "top -bn1 | rg '%Cpu' | awk '{print 100-$8}'"] 17 | stdout: SplitParser { 18 | onRead: data => cpu_percent = Math.round(data) 19 | } 20 | } 21 | 22 | Process { 23 | id: process_cpu_freq 24 | running: true 25 | command: ["sh", "-c", "lscpu --parse=MHZ"] 26 | stdout: SplitParser { 27 | onRead: data => { 28 | // delete the first 4 lines (comments) 29 | const mhz = data.split("\n").slice(4); 30 | // compute mean frequency 31 | const freq = mhz.reduce((acc, e) => acc + Number(e), 0) / mhz.length; 32 | 33 | cpu_freq = Math.round(freq) + " MHz"; 34 | } 35 | } 36 | } 37 | 38 | Process { 39 | id: process_mem_percent 40 | running: true 41 | command: ["sh", "-c", "free | awk 'NR==2{print $3/$2*100}'"] 42 | stdout: SplitParser { 43 | onRead: data => mem_percent = Math.round(data) 44 | } 45 | } 46 | 47 | Process { 48 | id: process_mem_used 49 | running: true 50 | command: ["sh", "-c", "free --si -h | awk 'NR==2{print $3}'"] 51 | stdout: SplitParser { 52 | onRead: data => mem_used = data 53 | } 54 | } 55 | 56 | Timer { 57 | interval: 2000 58 | running: true 59 | repeat: true 60 | onTriggered: () => { 61 | process_cpu_percent.running = true 62 | process_cpu_freq.running = true 63 | process_mem_percent.running = true 64 | process_mem_used.running = true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /home/services/quickshell/utils/Time.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import Quickshell 4 | import Quickshell.Io 5 | import QtQuick 6 | 7 | Singleton { 8 | property var locale: Qt.locale() 9 | 10 | function createDate(): string { 11 | let date = new Date(); 12 | let hh = date.getHours().toString().padStart(2, 0); 13 | let mm = date.getMinutes().toString().padStart(2, 0) 14 | let weekday = locale.dayName(date.getDay(), Locale.ShortFormat) 15 | let month = locale.monthName(date.getMonth(), Locale.ShortFormat) 16 | let day = date.getDate() 17 | 18 | return `${weekday} ${month} ${day} ${hh}:${mm}` 19 | } 20 | 21 | property var time: createDate() 22 | 23 | Timer { 24 | interval: 1000 25 | running: true 26 | repeat: true 27 | onTriggered: time = createDate() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /home/services/quickshell/windows/Bar.qml: -------------------------------------------------------------------------------- 1 | //@ pragma NativeTextRendering 2 | 3 | import Quickshell 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import "../utils" 7 | import "../components/bar" 8 | import "../components/bar/workspaces" 9 | 10 | Scope { 11 | PanelWindow { 12 | id: barWindow 13 | screen: Quickshell.screens[0] 14 | 15 | anchors { 16 | top: true 17 | left: true 18 | right: true 19 | } 20 | height: 32 21 | 22 | color: "transparent" 23 | 24 | Rectangle { 25 | id: bar 26 | anchors.fill: parent 27 | 28 | color: Colors.bgBlur 29 | 30 | // left 31 | RowLayout { 32 | id: barLeft 33 | 34 | anchors.bottom: parent.bottom 35 | anchors.left: parent.left 36 | anchors.top: parent.top 37 | 38 | anchors.leftMargin: height / 4 39 | anchors.rightMargin: height / 4 40 | spacing: height / 4 41 | 42 | Workspaces {} 43 | } 44 | 45 | // middle 46 | RowLayout { 47 | id: barMiddle 48 | 49 | anchors.bottom: parent.bottom 50 | anchors.horizontalCenter: parent.horizontalCenter 51 | anchors.top: parent.top 52 | 53 | anchors.leftMargin: height / 4 54 | anchors.rightMargin: height / 4 55 | spacing: height / 4 56 | 57 | Mpris {} 58 | } 59 | 60 | // right 61 | RowLayout { 62 | id: barRight 63 | 64 | anchors.bottom: parent.bottom 65 | anchors.right: parent.right 66 | anchors.top: parent.top 67 | 68 | anchors.leftMargin: height / 4 69 | anchors.rightMargin: height / 4 70 | spacing: height / 4 71 | 72 | Tray {} 73 | Resources {} 74 | Battery {} 75 | Clock {} 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /home/services/system/kdeconnect.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | services.kdeconnect = { 3 | enable = true; 4 | indicator = true; 5 | }; 6 | 7 | systemd.user.services = { 8 | kdeconnect.Unit.After = lib.mkForce ["graphical-session.target"]; 9 | kdeconnect-indicator.Unit.After = lib.mkForce ["graphical-session.target"]; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /home/services/system/polkit-agent.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | systemd.user.services.polkit-gnome-authentication-agent-1 = { 3 | Unit.Description = "polkit-gnome-authentication-agent-1"; 4 | 5 | Install = { 6 | WantedBy = ["graphical-session.target"]; 7 | Wants = ["graphical-session.target"]; 8 | After = ["graphical-session.target"]; 9 | }; 10 | 11 | Service = { 12 | Type = "simple"; 13 | ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; 14 | Restart = "on-failure"; 15 | RestartSec = 1; 16 | TimeoutStopSec = 10; 17 | }; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /home/services/system/power-monitor.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: let 6 | script = pkgs.writeShellScript "power_monitor.sh" '' 7 | BAT=$(echo /sys/class/power_supply/BAT*) 8 | BAT_STATUS="$BAT/status" 9 | BAT_CAP="$BAT/capacity" 10 | 11 | AC_PROFILE="performance" 12 | BAT_PROFILE="balanced" # power-saver is too choppy to use 13 | 14 | # wait a while if needed 15 | [ -z "$STARTUP_WAIT" ] || sleep "$STARTUP_WAIT" 16 | 17 | # start the monitor loop 18 | currentStatus=$(cat "$BAT_STATUS") 19 | prevProfile=$AC_PROFILE 20 | prevStatus=Charging 21 | 22 | # initial run 23 | if [ "$currentStatus" = "Discharging" ]; then 24 | profile="$BAT_PROFILE" 25 | else 26 | profile="$AC_PROFILE" 27 | fi 28 | 29 | # set the initial profile 30 | echo setting power profile to "$profile" 31 | powerprofilesctl set "$profile" 32 | 33 | prevProfile="$profile" 34 | prevStatus="$currentStatus" 35 | 36 | # event loop 37 | while true; do 38 | currentStatus=$(cat "$BAT_STATUS") 39 | if [ "$currentStatus" != "$prevStatus" ]; then 40 | # read the current state 41 | if [ "$currentStatus" = "Discharging" ]; then 42 | profile="$BAT_PROFILE" 43 | else 44 | profile="$AC_PROFILE" 45 | fi 46 | 47 | # set the new profile 48 | if [ $prevProfile != "$profile" ]; then 49 | echo setting power profile to "$profile" 50 | powerprofilesctl set "$profile" 51 | fi 52 | 53 | prevProfile="$profile" 54 | prevStatus="$currentStatus" 55 | fi 56 | 57 | # wait for the next power change event 58 | inotifywait -qq "$BAT_STATUS" "$BAT_CAP" 59 | done 60 | ''; 61 | 62 | dependencies = with pkgs; [ 63 | coreutils 64 | power-profiles-daemon 65 | inotify-tools 66 | ]; 67 | in { 68 | # Power state monitor. Switches Power profiles based on charging state. 69 | # Plugged in - performance 70 | # Unplugged - power-saver 71 | systemd.user.services.power-monitor = { 72 | Unit = { 73 | Description = "Power Monitor"; 74 | After = ["power-profiles-daemon.service"]; 75 | }; 76 | 77 | Service = { 78 | Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath dependencies}"; 79 | Type = "simple"; 80 | ExecStart = script; 81 | Restart = "on-failure"; 82 | }; 83 | 84 | Install.WantedBy = ["default.target"]; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /home/services/system/syncthing.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.syncthing = { 3 | enable = true; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /home/services/system/tailray.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | services.tailray.enable = true; 3 | systemd.user.services.tailray.Unit.After = lib.mkForce "graphical-session.target"; 4 | } 5 | -------------------------------------------------------------------------------- /home/services/system/theme.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | ... 5 | }: { 6 | systemd.user.timers = { 7 | theme-toggle-dark = { 8 | Unit.Description = "Toggle dark theme"; 9 | Timer.OnCalendar = [ 10 | "*-*-* 18:00:00" 11 | ]; 12 | Install.WantedBy = ["graphical-session.target"]; 13 | }; 14 | 15 | theme-toggle-light = { 16 | Unit.Description = "Toggle light theme"; 17 | Timer.OnCalendar = [ 18 | "*-*-* 06:00:00" 19 | ]; 20 | Install.WantedBy = ["graphical-session.target"]; 21 | }; 22 | }; 23 | 24 | systemd.user.services = { 25 | theme-toggle-dark = { 26 | Unit.Description = "Toggle dark theme"; 27 | Service = { 28 | Type = "simple"; 29 | ExecStart = "${lib.getExe pkgs.dconf} write /org/gnome/desktop/interface/color-scheme \"'prefer-dark'\""; 30 | TimeoutStopSec = 5; 31 | }; 32 | }; 33 | theme-toggle-light = { 34 | Unit.Description = "Toggle light theme"; 35 | Service = { 36 | Type = "simple"; 37 | ExecStart = "${lib.getExe pkgs.dconf} write /org/gnome/desktop/interface/color-scheme \"'prefer-light'\""; 38 | TimeoutStopSec = 5; 39 | }; 40 | }; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /home/services/system/udiskie.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | services.udiskie.enable = true; 3 | systemd.user.services.udiskie.Unit.After = lib.mkForce "graphical-session.target"; 4 | } 5 | -------------------------------------------------------------------------------- /home/services/wayland/gammastep.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.gammastep = { 3 | enable = true; 4 | tray = true; 5 | 6 | # stopgap until geoclue's wifi location is fixed 7 | provider = "manual"; 8 | latitude = 45.0; 9 | longitude = 25.0; 10 | 11 | enableVerboseLogging = true; 12 | 13 | settings.general.adjustment-method = "wayland"; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /home/services/wayland/hypridle.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | config, 5 | inputs, 6 | ... 7 | }: let 8 | lock = "${pkgs.systemd}/bin/loginctl lock-session"; 9 | 10 | brillo = lib.getExe pkgs.brillo; 11 | 12 | # timeout after which DPMS kicks in 13 | timeout = 300; 14 | in { 15 | # screen idle 16 | services.hypridle = { 17 | enable = true; 18 | 19 | package = inputs.hypridle.packages.${pkgs.system}.hypridle; 20 | 21 | settings = { 22 | general = { 23 | before_sleep_cmd = "loginctl lock-session"; 24 | after_sleep_cmd = "hyprctl dispatch dpms on"; 25 | lock_cmd = "pgrep hyprlock || ${lib.getExe config.programs.hyprlock.package}"; 26 | }; 27 | 28 | listener = [ 29 | { 30 | timeout = timeout - 10; 31 | # save the current brightness and dim the screen over a period of 32 | # 500 ms 33 | on-timeout = "${brillo} -O; ${brillo} -u 500000 -S 10"; 34 | # brighten the screen over a period of 250ms to the saved value 35 | on-resume = "${brillo} -I -u 250000"; 36 | } 37 | { 38 | inherit timeout; 39 | on-timeout = "hyprctl dispatch dpms off"; 40 | on-resume = "hyprctl dispatch dpms on"; 41 | } 42 | { 43 | timeout = timeout + 10; 44 | on-timeout = lock; 45 | } 46 | ]; 47 | }; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /home/services/wayland/hyprpaper.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | config, 5 | ... 6 | }: { 7 | services.hyprpaper = { 8 | enable = true; 9 | package = inputs.hyprpaper.packages.${pkgs.system}.default; 10 | 11 | settings = { 12 | preload = ["${config.theme.wallpaper}"]; 13 | wallpaper = [", ${config.theme.wallpaper}"]; 14 | }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /home/services/wayland/wluma.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: { 6 | systemd.user.services.wluma = { 7 | Unit = { 8 | Description = "Automatic backlight control"; 9 | PartOf = ["graphical-session.target"]; 10 | }; 11 | Service = { 12 | ExecStart = lib.getExe pkgs.wluma; 13 | Restart = "on-failure"; 14 | }; 15 | Install.WantedBy = ["graphical-session.target"]; 16 | }; 17 | 18 | xdg.configFile."wluma/config.toml".source = (pkgs.formats.toml {}).generate "wluma-config" { 19 | als.iio = { 20 | path = "/sys/bus/iio/devices"; 21 | thresholds = { 22 | "0" = "night"; 23 | "10" = "dark"; 24 | "20" = "dim"; 25 | "100" = "normal"; 26 | "200" = "bright"; 27 | "500" = "outdoors"; 28 | }; 29 | }; 30 | 31 | output.backlight = [ 32 | { 33 | capturer = "none"; 34 | name = "eDP-1"; 35 | path = "/sys/class/backlight/amdgpu_bl0"; 36 | } 37 | ]; 38 | 39 | # need to fix ddcutil first 40 | # output.ddcutil = [ 41 | # { 42 | # capturer = "none"; 43 | # name = "BenQ BL2283"; 44 | # } 45 | # ]; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /home/specialisations.nix: -------------------------------------------------------------------------------- 1 | { 2 | theme = { 3 | wallpaper = let 4 | url = "https://images.unsplash.com/photo-1529528744093-6f8abeee511d?ixlib=rb-4.0.3&q=85&fm=jpg&crop=fit&cs=srgb&w=2560"; 5 | sha256 = "18r5hmzglifysjmwn5j89gbbk56lbfb3f2jzwp432lr8gb5n7q8v"; 6 | ext = "jpg"; 7 | in 8 | builtins.fetchurl { 9 | name = "wallpaper-${sha256}.${ext}"; 10 | inherit url sha256; 11 | }; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /home/terminal/default.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: let 2 | data = config.xdg.dataHome; 3 | conf = config.xdg.configHome; 4 | cache = config.xdg.cacheHome; 5 | in { 6 | imports = [ 7 | ./programs 8 | ./shell/starship.nix 9 | ./shell/zsh.nix 10 | ./shell/zoxide.nix 11 | ]; 12 | 13 | # add environment variables 14 | home.sessionVariables = { 15 | # clean up ~ 16 | LESSHISTFILE = "${cache}/less/history"; 17 | LESSKEY = "${conf}/less/lesskey"; 18 | 19 | WINEPREFIX = "${data}/wine"; 20 | XAUTHORITY = "$XDG_RUNTIME_DIR/Xauthority"; 21 | 22 | EDITOR = "hx"; 23 | DIRENV_LOG_FORMAT = ""; 24 | 25 | # auto-run programs using nix-index-database 26 | NIX_AUTO_RUN = "1"; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /home/terminal/emulators/alacritty.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.alacritty = { 3 | enable = true; 4 | settings = { 5 | window = { 6 | decorations = "none"; 7 | dynamic_padding = true; 8 | padding = { 9 | x = 5; 10 | y = 5; 11 | }; 12 | startup_mode = "Maximized"; 13 | }; 14 | 15 | scrolling.history = 10000; 16 | 17 | font = { 18 | normal.family = "JetBrains Mono"; 19 | bold.family = "JetBrains Mono"; 20 | italic.family = "JetBrains Mono"; 21 | size = 10; 22 | }; 23 | 24 | draw_bold_text_with_bright_colors = true; 25 | window.opacity = 0.9; 26 | 27 | imports = [ 28 | (pkgs.fetchurl { 29 | url = "https://raw.githubusercontent.com/catppuccin/alacritty/3c808cbb4f9c87be43ba5241bc57373c793d2f17/catppuccin-mocha.yml"; 30 | hash = "sha256-28Tvtf8A/rx40J9PKXH6NL3h/OKfn3TQT1K9G8iWCkM="; 31 | }) 32 | ]; 33 | }; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /home/terminal/emulators/foot.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: let 6 | colors = { 7 | dark = { 8 | foreground = "abb2bf"; 9 | background = "1e2127"; 10 | regular0 = "1e2127"; # black 11 | regular1 = "e06c75"; # red 12 | regular2 = "98c379"; # green 13 | regular3 = "d19a66"; # yellow 14 | regular4 = "61afef"; # blue 15 | regular5 = "c678dd"; # magenta 16 | regular6 = "56b6c2"; # cyan 17 | regular7 = "abb2bf"; # white 18 | bright0 = "5c6370"; # bright black 19 | bright1 = "e06c75"; # bright red 20 | bright2 = "98c379"; # bright green 21 | bright3 = "d19a66"; # bright yellow 22 | bright4 = "61afef"; # bright blue 23 | bright5 = "c678dd"; # bright magenta 24 | bright6 = "56b6c2"; # bright cyan 25 | bright7 = "ffffff"; # bright white 26 | }; 27 | 28 | light = { 29 | foreground = "383a42"; # Text 30 | background = "f9f9f9"; # Base 31 | regular0 = "000000"; # Surface 1 32 | regular1 = "e45649"; # red 33 | regular2 = "50a14f"; # green 34 | regular3 = "986801"; # yellow 35 | regular4 = "4078f2"; # blue 36 | regular5 = "a626a4"; # maroon 37 | regular6 = "0184bc"; # teal 38 | regular7 = "a0a1a7"; # Subtext 1 39 | bright0 = "383a42"; # Surface 2 40 | bright1 = "e45649"; # red 41 | bright2 = "50a14f"; # green 42 | bright3 = "986801"; # yellow 43 | bright4 = "4078f2"; # blue 44 | bright5 = "a626a4"; # maroon 45 | bright6 = "0184bc"; # teal 46 | bright7 = "ffffff"; # Subtext 0 47 | }; 48 | }; 49 | in { 50 | programs.foot = { 51 | enable = true; 52 | 53 | settings = { 54 | main = { 55 | font = "JetBrains Mono Nerd Font:size=10"; 56 | horizontal-letter-offset = 0; 57 | vertical-letter-offset = 0; 58 | pad = "4x4 center"; 59 | selection-target = "clipboard"; 60 | }; 61 | 62 | bell = { 63 | urgent = "yes"; 64 | notify = "yes"; 65 | }; 66 | 67 | desktop-notifications = { 68 | command = "${lib.getExe pkgs.libnotify} -a \${app-id} -i \${app-id} \${title} \${body}"; 69 | }; 70 | 71 | scrollback = { 72 | lines = 10000; 73 | multiplier = 3; 74 | indicator-position = "relative"; 75 | indicator-format = "line"; 76 | }; 77 | 78 | url = { 79 | launch = "${pkgs.xdg-utils}/bin/xdg-open \${url}"; 80 | }; 81 | 82 | cursor = { 83 | style = "beam"; 84 | beam-thickness = 1; 85 | }; 86 | 87 | colors = 88 | { 89 | alpha = 0.9; 90 | } 91 | // colors.dark; 92 | }; 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /home/terminal/emulators/kitty.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.kitty = { 3 | enable = true; 4 | font = { 5 | size = 10; 6 | name = "JetBrains Mono"; 7 | }; 8 | 9 | settings = { 10 | scrollback_lines = 10000; 11 | placement_strategy = "center"; 12 | 13 | allow_remote_control = "yes"; 14 | enable_audio_bell = "no"; 15 | visual_bell_duration = "0.1"; 16 | 17 | copy_on_select = "clipboard"; 18 | 19 | selection_foreground = "none"; 20 | selection_background = "none"; 21 | 22 | # colors 23 | background_opacity = "0.9"; 24 | }; 25 | 26 | theme = "Catppuccin-Mocha"; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /home/terminal/emulators/wezterm.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.wezterm = { 3 | enable = true; 4 | 5 | extraConfig = '' 6 | local wezterm = require "wezterm" 7 | 8 | -- wezterm.gui is not available to the mux server, so take care to 9 | -- do something reasonable when this config is evaluated by the mux 10 | function get_appearance() 11 | if wezterm.gui then 12 | return wezterm.gui.get_appearance() 13 | end 14 | return 'Dark' 15 | end 16 | 17 | function scheme_for_appearance(appearance) 18 | if appearance:find 'Dark' then 19 | return 'Catppuccin Mocha' 20 | else 21 | return 'Catppuccin Latte' 22 | end 23 | end 24 | 25 | return { 26 | check_for_updates = false, 27 | color_scheme = scheme_for_appearance(get_appearance()), 28 | default_cursor_style = 'SteadyBar', 29 | enable_scroll_bar = true, 30 | font_size = 10, 31 | hide_tab_bar_if_only_one_tab = true, 32 | scrollback_lines = 10000, 33 | window_background_opacity = 0.9, 34 | } 35 | ''; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /home/terminal/programs/bat.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.bat = { 3 | enable = true; 4 | config = { 5 | pager = "less -FR"; 6 | theme = "Catppuccin-mocha"; 7 | }; 8 | themes = let 9 | src = pkgs.fetchFromGitHub { 10 | owner = "catppuccin"; 11 | repo = "bat"; 12 | rev = "ba4d16880d63e656acced2b7d4e034e4a93f74b1"; 13 | hash = "sha256-6WVKQErGdaqb++oaXnY3i6/GuH2FhTgK0v4TN4Y0Wbw="; 14 | }; 15 | in { 16 | Catppuccin-mocha = { 17 | inherit src; 18 | file = "Catppuccin-mocha.tmTheme"; 19 | }; 20 | Catppuccin-latte = { 21 | inherit src; 22 | file = "Catppuccin-latte.tmTheme"; 23 | }; 24 | }; 25 | }; 26 | 27 | home.sessionVariables = { 28 | MANPAGER = "sh -c 'col -bx | bat -l man -p'"; 29 | MANROFFOPT = "-c"; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /home/terminal/programs/btop.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.btop = { 3 | enable = true; 4 | settings.color_theme = "catppuccin_mocha"; 5 | }; 6 | 7 | xdg.configFile = { 8 | "btop/themes/catppuccin_latte.theme".source = pkgs.fetchurl { 9 | url = "https://raw.githubusercontent.com/catppuccin/btop/7109eac2884e9ca1dae431c0d7b8bc2a7ce54e54/themes/catppuccin_latte.theme"; 10 | hash = "sha256-Dp/4A4USHAri+QgIM/dJFQyLSR6KlWtMc7aYlFgmHr0="; 11 | }; 12 | "btop/themes/catppuccin_mocha.theme".source = pkgs.fetchurl { 13 | url = "https://raw.githubusercontent.com/catppuccin/btop/7109eac2884e9ca1dae431c0d7b8bc2a7ce54e54/themes/catppuccin_mocha.theme"; 14 | hash = "sha256-KnXUnp2sAolP7XOpNhX2g8m26josrqfTycPIBifS90Y="; 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /home/terminal/programs/cli.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | ... 5 | }: { 6 | home.packages = with pkgs; [ 7 | # archives 8 | zip 9 | unzip 10 | unrar 11 | 12 | # misc 13 | libnotify 14 | sshfs 15 | 16 | # utils 17 | du-dust 18 | duf 19 | fd 20 | file 21 | jaq 22 | ripgrep 23 | ripdrag 24 | ]; 25 | 26 | programs = { 27 | eza.enable = true; 28 | ssh = { 29 | enable = true; 30 | 31 | matchBlocks."cloudut" = { 32 | hostname = "10.20.7.115"; 33 | user = "cloud7115"; 34 | identityFile = "${config.home.homeDirectory}/.ssh/cloud7115_id_ed25519"; 35 | }; 36 | }; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /home/terminal/programs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ./bat.nix 4 | ./btop.nix 5 | ./cli.nix 6 | ./git.nix 7 | ./nix.nix 8 | ./skim.nix 9 | ./yazi 10 | ./xdg.nix 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /home/terminal/programs/git.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: let 6 | cfg = config.programs.git; 7 | key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOq9Gew1rgfdIyuriJ/Ne0B8FE1s8O/U2ajErVQLUDu9 mihai@io"; 8 | in { 9 | home.packages = [pkgs.gh]; 10 | 11 | # enable scrolling in git diff 12 | home.sessionVariables.DELTA_PAGER = "less -R"; 13 | 14 | programs.git = { 15 | enable = true; 16 | 17 | delta = { 18 | enable = true; 19 | options.dark = true; 20 | }; 21 | 22 | extraConfig = { 23 | diff.colorMoved = "default"; 24 | merge.conflictstyle = "diff3"; 25 | }; 26 | 27 | aliases = let 28 | log = "log --show-notes='*' --abbrev-commit --pretty=format:'%Cred%h %Cgreen(%aD)%Creset -%C(bold red)%d%Creset %s %C(bold blue)<%an>% %Creset' --graph"; 29 | in { 30 | a = "add --patch"; # make it a habit to consciosly add hunks 31 | ad = "add"; 32 | 33 | b = "branch"; 34 | ba = "branch -a"; # list remote branches 35 | bd = "branch --delete"; 36 | bdd = "branch -D"; 37 | 38 | c = "commit"; 39 | ca = "commit --amend"; 40 | cm = "commit --message"; 41 | 42 | co = "checkout"; 43 | cb = "checkout -b"; 44 | pc = "checkout --patch"; 45 | 46 | cl = "clone"; 47 | 48 | d = "diff"; 49 | ds = "diff --staged"; 50 | 51 | h = "show"; 52 | h1 = "show HEAD^"; 53 | h2 = "show HEAD^^"; 54 | h3 = "show HEAD^^^"; 55 | h4 = "show HEAD^^^^"; 56 | h5 = "show HEAD^^^^^"; 57 | 58 | p = "push"; 59 | pf = "push --force-with-lease"; 60 | 61 | pl = "pull"; 62 | 63 | l = log; 64 | lp = "${log} --patch"; 65 | la = "${log} --all"; 66 | 67 | r = "rebase"; 68 | ra = "rebase --abort"; 69 | rc = "rebase --continue"; 70 | ri = "rebase --interactive"; 71 | 72 | rs = "reset"; 73 | rsh = "reset --hard"; 74 | 75 | s = "status --short --branch"; 76 | ss = "status"; 77 | 78 | st = "stash"; 79 | stc = "stash clear"; 80 | sth = "stash show --patch"; 81 | stl = "stash list"; 82 | stp = "stash pop"; 83 | 84 | forgor = "commit --amend --no-edit"; 85 | oops = "checkout --"; 86 | }; 87 | 88 | ignores = ["*~" "*.swp" "*result*" ".direnv" "node_modules"]; 89 | 90 | signing = { 91 | key = "${config.home.homeDirectory}/.ssh/id_ed25519"; 92 | signByDefault = true; 93 | format = "ssh"; 94 | }; 95 | 96 | extraConfig = { 97 | gpg.ssh.allowedSignersFile = config.home.homeDirectory + "/" + config.xdg.configFile."git/allowed_signers".target; 98 | 99 | pull.rebase = true; 100 | }; 101 | 102 | userEmail = "mihai@fufexan.net"; 103 | userName = "Mihai Fufezan"; 104 | }; 105 | 106 | xdg.configFile."git/allowed_signers".text = '' 107 | ${cfg.userEmail} namespaces="git" ${key} 108 | ''; 109 | } 110 | -------------------------------------------------------------------------------- /home/terminal/programs/nix.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | self, 4 | ... 5 | }: 6 | # nix tooling 7 | { 8 | home.packages = with pkgs; [ 9 | alejandra 10 | deadnix 11 | statix 12 | self.packages.${pkgs.system}.repl 13 | ]; 14 | 15 | programs.direnv = { 16 | enable = true; 17 | nix-direnv.enable = true; 18 | enableZshIntegration = true; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /home/terminal/programs/skim.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.skim = { 3 | enable = true; 4 | enableZshIntegration = true; 5 | 6 | defaultCommand = "rg --files --hidden"; 7 | 8 | changeDirWidgetOptions = [ 9 | "--preview 'eza --icons --git --color always -T -L 3 {} | head -200'" 10 | "--exact" 11 | ]; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /home/terminal/programs/xdg.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: let 6 | browser = ["zen"]; 7 | imageViewer = ["org.gnome.Loupe"]; 8 | videoPlayer = ["io.github.celluloid_player.Celluloid"]; 9 | audioPlayer = ["io.bassi.Amberol"]; 10 | 11 | xdgAssociations = type: program: list: 12 | builtins.listToAttrs (map (e: { 13 | name = "${type}/${e}"; 14 | value = program; 15 | }) 16 | list); 17 | 18 | image = xdgAssociations "image" imageViewer ["png" "svg" "jpeg" "gif"]; 19 | video = xdgAssociations "video" videoPlayer ["mp4" "avi" "mkv"]; 20 | audio = xdgAssociations "audio" audioPlayer ["mp3" "flac" "wav" "aac"]; 21 | browserTypes = 22 | (xdgAssociations "application" browser [ 23 | "json" 24 | "x-extension-htm" 25 | "x-extension-html" 26 | "x-extension-shtml" 27 | "x-extension-xht" 28 | "x-extension-xhtml" 29 | "xhtml+xml" 30 | ]) 31 | // (xdgAssociations "x-scheme-handler" browser [ 32 | "about" 33 | "chrome" 34 | "ftp" 35 | "http" 36 | "https" 37 | "unknown" 38 | ]); 39 | 40 | # XDG MIME types 41 | associations = builtins.mapAttrs (_: v: (map (e: "${e}.desktop") v)) ({ 42 | "application/pdf" = ["org.pwmt.zathura-pdf-mupdf"]; 43 | "text/html" = browser; 44 | "text/plain" = ["Helix"]; 45 | "inode/directory" = ["yazi"]; 46 | "x-scheme-handler/magnet" = ["transmission-gtk"]; 47 | # Full entry is org.telegram.desktop.desktop 48 | "x-scheme-handler/tg" = ["org.telegram.desktop"]; 49 | "x-scheme-handler/tonsite" = ["org.telegram.desktop"]; 50 | } 51 | // image 52 | // video 53 | // audio 54 | // browserTypes); 55 | in { 56 | xdg = { 57 | enable = true; 58 | cacheHome = config.home.homeDirectory + "/.local/cache"; 59 | 60 | mimeApps = { 61 | enable = true; 62 | defaultApplications = associations; 63 | }; 64 | 65 | userDirs = { 66 | enable = true; 67 | createDirectories = true; 68 | extraConfig = { 69 | XDG_SCREENSHOTS_DIR = "${config.xdg.userDirs.pictures}/Screenshots"; 70 | }; 71 | }; 72 | }; 73 | 74 | home.packages = [ 75 | # used by `gio open` and xdp-gtk 76 | (pkgs.writeShellScriptBin "xdg-terminal-exec" '' 77 | foot "$@" 78 | '') 79 | pkgs.xdg-utils 80 | ]; 81 | } 82 | -------------------------------------------------------------------------------- /home/terminal/programs/yazi/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | inputs, 5 | lib, 6 | ... 7 | }: { 8 | imports = [ 9 | ./theme/filetype.nix 10 | ./theme/icons.nix 11 | ./theme/manager.nix 12 | ./theme/status.nix 13 | ]; 14 | 15 | # general file info 16 | home.packages = [pkgs.exiftool]; 17 | 18 | # yazi file manager 19 | programs.yazi = { 20 | enable = true; 21 | 22 | package = inputs.yazi.packages.${pkgs.system}.default; 23 | 24 | enableBashIntegration = config.programs.bash.enable; 25 | enableZshIntegration = config.programs.zsh.enable; 26 | shellWrapperName = "y"; 27 | 28 | settings = { 29 | manager = { 30 | layout = [1 4 3]; 31 | sort_by = "alphabetical"; 32 | sort_sensitive = true; 33 | sort_reverse = false; 34 | sort_dir_first = true; 35 | linemode = "none"; 36 | show_hidden = false; 37 | show_symlink = true; 38 | }; 39 | 40 | preview = { 41 | tab_size = 2; 42 | max_width = 600; 43 | max_height = 900; 44 | cache_dir = config.xdg.cacheHome; 45 | }; 46 | }; 47 | 48 | # Run ripdrag when pressing C-n 49 | keymap.manager.prepend_keymap = [ 50 | { 51 | on = [""]; 52 | run = ''shell '${lib.getExe pkgs.ripdrag} "$@" -x 2>/dev/null &' --confirm''; 53 | } 54 | ]; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /home/terminal/programs/yazi/theme/manager.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.yazi.theme.manager = { 3 | cwd = {fg = "cyan";}; 4 | 5 | # Hovered 6 | hovered = { 7 | fg = "black"; 8 | bg = "lightblue"; 9 | }; 10 | 11 | preview_hovered = { 12 | fg = "black"; 13 | bg = "lightblue"; 14 | }; 15 | 16 | # Find 17 | find_keyword = { 18 | fg = "yellow"; 19 | italic = true; 20 | }; 21 | find_position = { 22 | fg = "magenta"; 23 | bg = "reset"; 24 | italic = true; 25 | }; 26 | 27 | # Marker 28 | marker_selected = { 29 | fg = "lightgreen"; 30 | bg = "lightgreen"; 31 | }; 32 | marker_copied = { 33 | fg = "lightyellow"; 34 | bg = "lightyellow"; 35 | }; 36 | marker_cut = { 37 | fg = "lightred"; 38 | bg = "lightred"; 39 | }; 40 | 41 | # Tab 42 | tab_active = { 43 | fg = "black"; 44 | bg = "lightblue"; 45 | }; 46 | tab_inactive = { 47 | fg = "white"; 48 | bg = "darkgray"; 49 | }; 50 | tab_width = 1; 51 | 52 | # Border; 53 | border_symbol = "│"; 54 | border_style = {fg = "gray";}; 55 | 56 | # Offset; 57 | folder_offset = [1 0 1 0]; 58 | preview_offset = [1 1 1 1]; 59 | 60 | # Highlighting; 61 | syntect_theme = ""; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /home/terminal/programs/yazi/theme/status.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.yazi.theme.status = { 3 | separator_open = ""; 4 | separator_close = ""; 5 | separator_style = { 6 | fg = "darkgray"; 7 | bg = "darkgray"; 8 | }; 9 | 10 | # Mode; 11 | mode_normal = { 12 | fg = "black"; 13 | bg = "lightblue"; 14 | bold = true; 15 | }; 16 | mode_select = { 17 | fg = "black"; 18 | bg = "lightgreen"; 19 | bold = true; 20 | }; 21 | mode_unset = { 22 | fg = "black"; 23 | bg = "lightmagenta"; 24 | bold = true; 25 | }; 26 | 27 | # Progress; 28 | progress_label = {bold = true;}; 29 | progress_normal = { 30 | fg = "blue"; 31 | bg = "black"; 32 | }; 33 | progress_error = { 34 | fg = "red"; 35 | bg = "black"; 36 | }; 37 | 38 | # Permissions; 39 | permissions_t = {fg = "blue";}; 40 | permissions_r = {fg = "lightyellow";}; 41 | permissions_w = {fg = "lightred";}; 42 | permissions_x = {fg = "lightgreen";}; 43 | permissions_s = {fg = "darkgray";}; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /home/terminal/shell/starship.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: { 2 | home.sessionVariables.STARSHIP_CACHE = "${config.xdg.cacheHome}/starship"; 3 | 4 | programs.starship = { 5 | enable = true; 6 | settings = { 7 | character = { 8 | success_symbol = "[›](bold green)"; 9 | error_symbol = "[›](bold red)"; 10 | }; 11 | 12 | git_status = { 13 | deleted = "✗"; 14 | modified = "✶"; 15 | staged = "✓"; 16 | stashed = "≡"; 17 | }; 18 | 19 | nix_shell = { 20 | symbol = " "; 21 | heuristic = true; 22 | }; 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /home/terminal/shell/zoxide.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.zoxide.enable = true; 3 | } 4 | -------------------------------------------------------------------------------- /home/terminal/shell/zsh.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: { 6 | programs.zsh = { 7 | enable = true; 8 | autosuggestion.enable = true; 9 | autocd = true; 10 | dirHashes = { 11 | dl = "$HOME/Downloads"; 12 | docs = "$HOME/Documents"; 13 | code = "$HOME/Documents/code"; 14 | dots = "$HOME/Documents/code/dotfiles"; 15 | pics = "$HOME/Pictures"; 16 | vids = "$HOME/Videos"; 17 | nixpkgs = "$HOME/Documents/code/git/nixpkgs"; 18 | }; 19 | dotDir = ".config/zsh"; 20 | history = { 21 | expireDuplicatesFirst = true; 22 | path = "${config.xdg.dataHome}/zsh_history"; 23 | }; 24 | 25 | initContent = '' 26 | # search history based on what's typed in the prompt 27 | autoload -U history-search-end 28 | zle -N history-beginning-search-backward-end history-search-end 29 | zle -N history-beginning-search-forward-end history-search-end 30 | bindkey "^[OA" history-beginning-search-backward-end 31 | bindkey "^[OB" history-beginning-search-forward-end 32 | 33 | # C-right / C-left for word skips 34 | bindkey "^[[1;5C" forward-word 35 | bindkey "^[[1;5D" backward-word 36 | 37 | # C-Backspace / C-Delete for word deletions 38 | bindkey "^[[3;5~" forward-kill-word 39 | bindkey "^H" backward-kill-word 40 | 41 | # Home/End 42 | bindkey "^[[OH" beginning-of-line 43 | bindkey "^[[OF" end-of-line 44 | 45 | # open commands in $EDITOR with C-e 46 | autoload -z edit-command-line 47 | zle -N edit-command-line 48 | bindkey "^e" edit-command-line 49 | 50 | # case insensitive tab completion 51 | zstyle ':completion:*' completer _complete _ignored _approximate 52 | zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s 53 | zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 54 | zstyle ':completion:*' menu select 55 | zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s 56 | zstyle ':completion:*' verbose true 57 | 58 | # use cache for completions 59 | zstyle ':completion:*' use-cache on 60 | zstyle ':completion:*' cache-path "$XDG_CACHE_HOME/zsh/.zcompcache" 61 | _comp_options+=(globdots) 62 | 63 | # Allow foot to pipe command output 64 | function precmd { 65 | if ! builtin zle; then 66 | print -n "\e]133;D\e\\" 67 | fi 68 | } 69 | 70 | function preexec { 71 | print -n "\e]133;C\e\\" 72 | } 73 | 74 | ${lib.optionalString config.services.gpg-agent.enable '' 75 | gnupg_path=$(ls $XDG_RUNTIME_DIR/gnupg) 76 | export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/gnupg/$gnupg_path/S.gpg-agent.ssh" 77 | ''} 78 | ''; 79 | 80 | shellAliases = 81 | { 82 | g = "git"; 83 | grep = "grep --color"; 84 | ip = "ip --color"; 85 | l = "eza -l"; 86 | la = "eza -la"; 87 | md = "mkdir -p"; 88 | ppc = "powerprofilesctl"; 89 | pf = "powerprofilesctl launch -p performance"; 90 | 91 | us = "systemctl --user"; # mnemonic for user systemctl 92 | rs = "sudo systemctl"; # mnemonic for root systemctl 93 | } 94 | // lib.optionalAttrs config.programs.bat.enable {cat = "bat";}; 95 | shellGlobalAliases = {eza = "eza --icons --git";}; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /hosts/README.md: -------------------------------------------------------------------------------- 1 | # Hosts config 2 | 3 | | Name | Description | 4 | | ------- | --------------------------------------------------------- | 5 | | `io` | Lenovo laptop, main machine | 6 | | `kiiro` | Previous main machine, retired and rarely used server now | 7 | | `rog` | Temporary machine, used while `io` was in service | 8 | 9 | All the hosts have a shared config in `modules/core.nix`. Host specific configs 10 | are stored inside the specific host dir. Each host imports its own modules 11 | inside `default.nix`. 12 | -------------------------------------------------------------------------------- /hosts/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | self, 3 | inputs, 4 | ... 5 | }: { 6 | flake.nixosConfigurations = let 7 | # shorten paths 8 | inherit (inputs.nixpkgs.lib) nixosSystem; 9 | 10 | # howdy = inputs.nixpkgs-howdy; 11 | 12 | homeImports = import "${self}/home/profiles"; 13 | 14 | mod = "${self}/system"; 15 | # get the basic config to build on top of 16 | inherit (import mod) laptop; 17 | 18 | # get these into the module system 19 | specialArgs = {inherit inputs self;}; 20 | in { 21 | io = nixosSystem { 22 | inherit specialArgs; 23 | modules = 24 | laptop 25 | ++ [ 26 | ./io 27 | "${mod}/core/lanzaboote.nix" 28 | 29 | "${mod}/programs/gamemode.nix" 30 | "${mod}/programs/hyprland" 31 | "${mod}/programs/games.nix" 32 | 33 | "${mod}/network/spotify.nix" 34 | "${mod}/network/syncthing.nix" 35 | 36 | "${mod}/services/kanata" 37 | "${mod}/services/gnome-services.nix" 38 | "${mod}/services/location.nix" 39 | 40 | { 41 | home-manager = { 42 | users.mihai.imports = homeImports."mihai@io"; 43 | extraSpecialArgs = specialArgs; 44 | backupFileExtension = ".hm-backup"; 45 | }; 46 | } 47 | 48 | # enable unmerged Howdy 49 | # {disabledModules = ["security/pam.nix"];} 50 | # "${howdy}/nixos/modules/security/pam.nix" 51 | # "${howdy}/nixos/modules/services/security/howdy" 52 | # "${howdy}/nixos/modules/services/misc/linux-enable-ir-emitter.nix" 53 | 54 | inputs.agenix.nixosModules.default 55 | inputs.chaotic.nixosModules.default 56 | ]; 57 | }; 58 | 59 | nixos = nixosSystem { 60 | inherit specialArgs; 61 | modules = [ 62 | ./wsl 63 | "${mod}/core/users.nix" 64 | "${mod}/nix" 65 | "${mod}/programs/zsh.nix" 66 | "${mod}/programs/home-manager.nix" 67 | { 68 | home-manager = { 69 | users.mihai.imports = homeImports.server; 70 | extraSpecialArgs = specialArgs; 71 | backupFileExtension = ".hm-backup"; 72 | }; 73 | } 74 | ]; 75 | }; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /hosts/io/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | self, 4 | # inputs, 5 | lib, 6 | ... 7 | }: { 8 | imports = [ 9 | ./hardware-configuration.nix 10 | ./hyprland.nix 11 | ./powersave.nix 12 | ]; 13 | 14 | age.secrets.spotify = { 15 | file = "${self}/secrets/spotify.age"; 16 | owner = "mihai"; 17 | group = "users"; 18 | }; 19 | 20 | boot = { 21 | kernelModules = ["i2c-dev"]; 22 | kernelPackages = lib.mkForce pkgs.linuxPackages_latest; 23 | kernelParams = [ 24 | "amd_pstate=active" 25 | "ideapad_laptop.allow_v4_dytc=Y" 26 | ''acpi_osi="Windows 2020"'' 27 | ]; 28 | }; 29 | 30 | # nh default flake 31 | environment.variables.NH_FLAKE = "/home/mihai/Documents/code/dotfiles"; 32 | 33 | hardware = { 34 | # xpadneo.enable = true; 35 | sensor.iio.enable = true; 36 | }; 37 | 38 | networking.hostName = "io"; 39 | 40 | security.tpm2.enable = true; 41 | 42 | services = { 43 | # for SSD/NVME 44 | fstrim.enable = true; 45 | 46 | # howdy = { 47 | # enable = true; 48 | # package = inputs.nixpkgs-howdy.legacyPackages.${pkgs.system}.howdy; 49 | # settings = { 50 | # core = { 51 | # no_confirmation = true; 52 | # abort_if_ssh = true; 53 | # }; 54 | # video.dark_threshold = 90; 55 | # }; 56 | # }; 57 | 58 | # linux-enable-ir-emitter = { 59 | # enable = true; 60 | # package = inputs.nixpkgs-howdy.legacyPackages.${pkgs.system}.linux-enable-ir-emitter; 61 | # }; 62 | 63 | kanata.keyboards.io = { 64 | config = builtins.readFile "${self}/system/services/kanata/main.kbd"; 65 | devices = ["/dev/input/by-path/platform-i8042-serio-0-event-kbd"]; 66 | }; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /hosts/io/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | # Do not modify this file! It was generated by ‘nixos-generate-config’ 2 | # and may be overwritten by future invocations. Please make changes 3 | # to /etc/nixos/configuration.nix instead. 4 | { 5 | config, 6 | lib, 7 | modulesPath, 8 | ... 9 | }: { 10 | imports = [ 11 | (modulesPath + "/installer/scan/not-detected.nix") 12 | ]; 13 | 14 | boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "usb_storage" "sd_mod"]; 15 | boot.initrd.kernelModules = []; 16 | boot.kernelModules = ["kvm-amd"]; 17 | boot.extraModulePackages = []; 18 | 19 | fileSystems."/" = { 20 | device = "/dev/disk/by-label/NixOS"; 21 | fsType = "btrfs"; 22 | options = ["subvol=root" "noatime" "compress=zstd"]; 23 | }; 24 | 25 | fileSystems."/home" = { 26 | device = "/dev/disk/by-label/NixOS"; 27 | fsType = "btrfs"; 28 | options = ["subvol=home" "noatime" "compress=zstd"]; 29 | }; 30 | 31 | fileSystems."/boot" = { 32 | device = "/dev/disk/by-label/EFI"; 33 | fsType = "vfat"; 34 | }; 35 | 36 | swapDevices = []; 37 | 38 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 39 | hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 40 | } 41 | -------------------------------------------------------------------------------- /hosts/io/hyprland.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.hyprland.settings = let 3 | # Generated using https://gist.github.com/fufexan/e6bcccb7787116b8f9c31160fc8bc543 4 | accelpoints = "0.5 0.000 0.053 0.115 0.189 0.280 0.391 0.525 0.687 0.880 1.108 1.375 1.684 2.040 2.446 2.905 3.422 4.000 4.643 5.355 6.139"; 5 | in { 6 | monitor = [ 7 | # "DP-1, preferred, auto-left, auto" 8 | # "DP-2, preferred, auto-left, auto" 9 | "eDP-1, preferred, auto, 1.600000" 10 | ]; 11 | 12 | "device[elan2841:00-04f3:31eb-touchpad]" = { 13 | accel_profile = "custom ${accelpoints}"; 14 | scroll_points = accelpoints; 15 | natural_scroll = true; 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /hosts/io/powersave.nix: -------------------------------------------------------------------------------- 1 | let 2 | script = '' 3 | echo 1500 > /proc/sys/vm/dirty_writeback_centisecs 4 | echo 1 > /sys/module/snd_hda_intel/parameters/power_save 5 | echo 0 > /proc/sys/kernel/nmi_watchdog 6 | 7 | for i in /sys/bus/pci/devices/*; do 8 | echo auto > "$i/power/control" 9 | done 10 | ''; 11 | in { 12 | systemd.services.powersave = { 13 | enable = true; 14 | 15 | description = "Apply power saving tweaks"; 16 | wantedBy = ["multi-user.target"]; 17 | 18 | inherit script; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /hosts/wsl/default.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: { 2 | imports = [ 3 | inputs.nixos-wsl.nixosModules.default 4 | ]; 5 | # nh default flake 6 | environment.variables.NH_FLAKE = "/home/mihai/Documents/code/dotfiles"; 7 | 8 | wsl = { 9 | enable = true; 10 | defaultUser = "mihai"; 11 | }; 12 | 13 | nixpkgs.hostPlatform = "x86_64-linux"; 14 | } 15 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # Lib 2 | 3 | Various functions I use throughout the config: 4 | 5 | | Name | Description | 6 | | ------------- | ---------------------------------- | 7 | | `colors` | Functions for dealing with colors. | 8 | | `default.nix` | Module for flake-parts | 9 | | `repl.nix` | Cool Nix REPL wrapper | 10 | -------------------------------------------------------------------------------- /lib/colors/default.nix: -------------------------------------------------------------------------------- 1 | lib: 2 | with lib; rec { 3 | # color-related functions 4 | 5 | # convert rrggbb hex to #rrggbb 6 | x = c: "#${c}"; 7 | 8 | # convert rrggbb hex to rgba(r, g, b, a) css 9 | rgba = c: let 10 | r = toString (hexToDec (__substring 0 2 c)); 11 | g = toString (hexToDec (__substring 2 2 c)); 12 | b = toString (hexToDec (__substring 4 2 c)); 13 | res = "rgba(${r}, ${g}, ${b}, .5)"; 14 | in 15 | res; 16 | 17 | # general stuff 18 | 19 | # functions copied from https://gist.github.com/corpix/f761c82c9d6fdbc1b3846b37e1020e11 20 | # convert a hex value to an integer 21 | hexToDec = v: let 22 | hexToInt = { 23 | "0" = 0; 24 | "1" = 1; 25 | "2" = 2; 26 | "3" = 3; 27 | "4" = 4; 28 | "5" = 5; 29 | "6" = 6; 30 | "7" = 7; 31 | "8" = 8; 32 | "9" = 9; 33 | "a" = 10; 34 | "b" = 11; 35 | "c" = 12; 36 | "d" = 13; 37 | "e" = 14; 38 | "f" = 15; 39 | "A" = 10; 40 | "B" = 11; 41 | "C" = 12; 42 | "D" = 13; 43 | "E" = 14; 44 | "F" = 15; 45 | }; 46 | chars = stringToCharacters v; 47 | charsLen = length chars; 48 | in 49 | foldl 50 | (a: v: a + v) 51 | 0 52 | (imap0 53 | (k: v: hexToInt."${v}" * (pow 16 (charsLen - k - 1))) 54 | chars); 55 | 56 | pow = let 57 | pow' = base: exponent: value: 58 | # FIXME: It will silently overflow on values > 2**62 :( 59 | # The value will become negative or zero in this case 60 | if exponent == 0 61 | then 1 62 | else if exponent <= 1 63 | then value 64 | else (pow' base (exponent - 1) (value * base)); 65 | in 66 | base: exponent: pow' base exponent base; 67 | 68 | # #RRGGBB 69 | xcolors = colors: lib.mapAttrsRecursive (_: x) colors; 70 | # rgba(,,,) colors (css) 71 | rgbaColors = colors: lib.mapAttrsRecursive (_: rgba) colors; 72 | } 73 | -------------------------------------------------------------------------------- /lib/default.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: 2 | # personal lib 3 | { 4 | _module.args = { 5 | colors = import ./colors lib; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /lib/repl.nix: -------------------------------------------------------------------------------- 1 | { 2 | flakePath ? null, 3 | hostnamePath ? "/etc/hostname", 4 | registryPath ? /etc/nix/registry.json, 5 | }: let 6 | inherit (builtins) getFlake head match currentSystem readFile pathExists filter fromJSON; 7 | 8 | selfFlake = 9 | if pathExists registryPath 10 | then filter (it: it.from.id == "self") (fromJSON (readFile registryPath)).flakes 11 | else []; 12 | 13 | flakePath' = 14 | toString 15 | ( 16 | if flakePath != null 17 | then flakePath 18 | else if selfFlake != [] 19 | then (head selfFlake).to.path 20 | else "/etc/nixos" 21 | ); 22 | 23 | flake = 24 | if pathExists flakePath' 25 | then getFlake flakePath' 26 | else {}; 27 | hostname = 28 | if pathExists hostnamePath 29 | then head (match "([a-zA-Z0-9\\-]+)\n" (readFile hostnamePath)) 30 | else ""; 31 | 32 | nixpkgsFromInputsPath = flake.inputs.nixpkgs.outPath or ""; 33 | nixpkgs = 34 | flake.pkgs.${ 35 | currentSystem 36 | }.nixpkgs 37 | or ( 38 | if nixpkgsFromInputsPath != "" 39 | then import nixpkgsFromInputsPath {} 40 | else {} 41 | ); 42 | 43 | nixpkgsOutput = removeAttrs (nixpkgs // nixpkgs.lib or {}) ["options" "config"]; 44 | in 45 | {inherit flake;} 46 | // flake 47 | // builtins 48 | // (flake.nixosConfigurations or {}) 49 | // flake.nixosConfigurations.${hostname} or {} 50 | // nixpkgsOutput 51 | // {getFlake = path: getFlake (toString path);} 52 | -------------------------------------------------------------------------------- /modules/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | flake.nixosModules = { 3 | theme = import ./theme; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /modules/theme/default.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | options.theme = { 3 | name = lib.mkOption { 4 | description = '' 5 | Name of the theme to use throughout the system. 6 | 7 | This option can be used as a simple "light/dark" switch that does nothing by itself, 8 | or it can be used by a more elaborate module/theme manager that can switch entire 9 | programs' themes based on this option. 10 | ''; 11 | type = lib.types.str; 12 | example = lib.literalExample "catppuccin-latte"; 13 | default = "dark"; 14 | }; 15 | 16 | wallpaper = lib.mkOption { 17 | description = '' 18 | Location of the wallpaper to use throughout the system. 19 | ''; 20 | type = lib.types.path; 21 | example = lib.literalExample "./wallpaper.png"; 22 | }; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /pkgs/README.md: -------------------------------------------------------------------------------- 1 | # Packages & Overlays 2 | 3 | Here are all of the packages I couldn't find anywhere and packaged by myself, or 4 | overrides that I use throughout the configuration. 5 | 6 | | Name | Description | 7 | | ------ | ----------------------------------------------------------------------- | 8 | | Repl | Cool Nix Repl that auto-loads the system flake or the current dir flake | 9 | | wl-ocr | Script I use to retrieve text from screenshots in the clipboard | 10 | -------------------------------------------------------------------------------- /pkgs/bibata-hyprcursor/configure.py: -------------------------------------------------------------------------------- 1 | # stolen from https://github.com/diniamo/niqspkgs/blob/544c3b2c69fd1b5ab3407e7b35c76060801a8bcf/pkgs/bibata-hyprcursor/default.nix 2 | 3 | from sys import argv 4 | import os 5 | from os import path 6 | from pathlib import Path 7 | import tomli 8 | import tomli_w 9 | 10 | 11 | def fallback_value(config, cursor, field): 12 | return (config.get(cursor, None) or {}).get(field, None) or ( 13 | config["fallback_settings"].get(field, None) 14 | ) 15 | 16 | 17 | def filter_none_dict(**kwargs): 18 | return {k: v for k, v in kwargs.items() if v is not None} 19 | 20 | 21 | def construct_meta(config, name, sizes): 22 | meta = filter_none_dict( 23 | define_size=";".join(sizes), 24 | define_override=( 25 | None 26 | if (overrides := fallback_value(config, name, "x11_symlinks")) is None 27 | else ";".join(overrides) 28 | ), 29 | hotspot_x=fallback_value(config, name, "x_hotspot") / 256, 30 | hotspot_y=fallback_value(config, name, "y_hotspot") / 256, 31 | ) 32 | 33 | with open(f"{name}/meta.toml", "wb") as file: 34 | tomli_w.dump({"General": meta}, file) 35 | 36 | 37 | with open(argv[1], "rb") as file: 38 | config = tomli.load(file)["cursors"] 39 | 40 | os.chdir(argv[2]) 41 | 42 | for cursor in os.listdir("."): 43 | if path.isfile(cursor): 44 | name = Path(cursor).stem 45 | 46 | os.mkdir(name) 47 | os.rename(cursor, f"{name}/{cursor}") 48 | 49 | construct_meta(config, name, [f"0,{cursor}"]) 50 | else: 51 | delay = fallback_value(config, cursor, "x11_delay") 52 | construct_meta( 53 | config, cursor, map(lambda c: f"0,{c},{delay}", os.listdir(cursor)) 54 | ) 55 | -------------------------------------------------------------------------------- /pkgs/bibata-hyprcursor/default.nix: -------------------------------------------------------------------------------- 1 | # stolen from https://github.com/diniamo/niqspkgs/blob/544c3b2c69fd1b5ab3407e7b35c76060801a8bcf/pkgs/bibata-hyprcursor/default.nix 2 | { 3 | lib, 4 | stdenvNoCC, 5 | fetchFromGitHub, 6 | python3, 7 | python3Packages, 8 | hyprcursor, 9 | variant ? "modern", 10 | baseColor ? "#000000", 11 | outlineColor ? "#FFFFFF", 12 | watchBackgroundColor ? "#000000", 13 | colorName ? "classic", 14 | }: let 15 | capitalize = str: let 16 | capital_letter = builtins.substring 0 1 str; 17 | non_capital = lib.removePrefix capital_letter str; 18 | in 19 | lib.toUpper capital_letter + non_capital; 20 | 21 | themeName = "Bibata-${capitalize variant}-${capitalize colorName}-Hyprcursor"; 22 | in 23 | assert builtins.elem variant ["modern" "modern-right" "original" "original-right"]; 24 | stdenvNoCC.mkDerivation (final: { 25 | pname = "bibata-hyprcursor"; 26 | version = "v2.0.7"; 27 | 28 | src = fetchFromGitHub { 29 | owner = "ful1e5"; 30 | repo = "Bibata_Cursor"; 31 | rev = final.version; 32 | hash = "sha256-kIKidw1vditpuxO1gVuZeUPdWBzkiksO/q2R/+DUdEc="; 33 | }; 34 | 35 | nativeBuildInputs = [ 36 | python3 37 | python3Packages.tomli 38 | python3Packages.tomli-w 39 | hyprcursor 40 | ]; 41 | 42 | phases = ["unpackPhase" "configurePhase" "buildPhase" "installPhase"]; 43 | 44 | unpackPhase = '' 45 | runHook preUnpack 46 | 47 | cp $src/configs/${ 48 | if lib.hasSuffix "right" variant 49 | then "right" 50 | else "normal" 51 | }/x.build.toml config.toml 52 | 53 | mkdir cursors 54 | for cursor in $src/svg/${variant}/*; do 55 | cp -r $src/svg/${variant}/$(readlink $cursor) cursors 56 | done 57 | 58 | chmod -R u+w . 59 | 60 | runHook postUnpack 61 | ''; 62 | 63 | configurePhase = '' 64 | runHook preConfigure 65 | 66 | cat << EOF > manifest.hl 67 | name = ${themeName} 68 | description = The Bibata Cursor theme packaged for hyprcursor. 69 | version = ${final.version} 70 | cursors_directory = cursors 71 | EOF 72 | 73 | find cursors -type f -name '*.svg' | xargs sed -i -e 's/#00FF00/${baseColor}/g' -e 's/#0000FF/${outlineColor}/g' -e 's/#FF0000/${watchBackgroundColor}/g' 74 | 75 | python ${./configure.py} config.toml cursors 76 | 77 | runHook postConfigure 78 | ''; 79 | 80 | buildPhase = '' 81 | runHook preBuild 82 | hyprcursor-util --create . --output . 83 | runHook postBuild 84 | ''; 85 | 86 | installPhase = '' 87 | runHook preInstall 88 | 89 | mkdir -p $out/share/icons 90 | cp -r theme_${themeName} $out/share/icons/${themeName} 91 | 92 | runHook postInstall 93 | ''; 94 | }) 95 | -------------------------------------------------------------------------------- /pkgs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | systems = ["x86_64-linux"]; 3 | 4 | perSystem = {pkgs, ...}: { 5 | packages = { 6 | # instant repl with automatic flake loading 7 | repl = pkgs.callPackage ./repl {}; 8 | 9 | bibata-hyprcursor = pkgs.callPackage ./bibata-hyprcursor {}; 10 | 11 | wl-ocr = pkgs.callPackage ./wl-ocr {}; 12 | }; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /pkgs/repl/default.nix: -------------------------------------------------------------------------------- 1 | # modified from https://github.com/gytis-ivaskevicius/flake-utils/plus 2 | { 3 | coreutils, 4 | gnused, 5 | writeShellScriptBin, 6 | }: let 7 | repl = ../../lib/repl.nix; 8 | example = command: desc: ''\n\u001b[33m ${command}\u001b[0m - ${desc}''; 9 | in 10 | writeShellScriptBin "repl" '' 11 | case "$1" in 12 | "-h"|"--help"|"help") 13 | printf "%b\n\e[4mUsage\e[0m: \ 14 | ${example "repl" "Loads system flake if available."} \ 15 | ${example "repl /path/to/flake.nix" "Loads specified flake."}\n" 16 | ;; 17 | *) 18 | if [ -z "$1" ]; then 19 | nix repl ${repl} 20 | else 21 | nix repl --arg flakePath $(${coreutils}/bin/readlink -f $1 | ${gnused}/bin/sed 's|/flake.nix||') ${repl} 22 | fi 23 | ;; 24 | esac 25 | '' 26 | -------------------------------------------------------------------------------- /pkgs/wl-ocr/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | writeShellScriptBin, 3 | lib, 4 | grim, 5 | libnotify, 6 | slurp, 7 | tesseract5, 8 | wl-clipboard, 9 | langs ? "eng+hun+fra+jpn+jpn_vert+kor+kor_vert+pol+ron+spa", 10 | }: let 11 | _ = lib.getExe; 12 | in 13 | writeShellScriptBin "wl-ocr" '' 14 | ${_ grim} -g "$(${_ slurp})" -t ppm - | ${_ tesseract5} -l ${langs} - - | ${wl-clipboard}/bin/wl-copy 15 | echo "$(${wl-clipboard}/bin/wl-paste)" 16 | ${_ libnotify} -- "$(${wl-clipboard}/bin/wl-paste)" 17 | '' 18 | -------------------------------------------------------------------------------- /pre-commit-hooks.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: { 2 | imports = [inputs.pre-commit-hooks.flakeModule]; 3 | 4 | perSystem.pre-commit = { 5 | settings.excludes = ["flake.lock"]; 6 | 7 | settings.hooks = { 8 | alejandra.enable = true; 9 | prettier = { 10 | enable = true; 11 | excludes = [".js" ".md" ".ts"]; 12 | }; 13 | }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /secrets/secrets.nix: -------------------------------------------------------------------------------- 1 | let 2 | mihai = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOq9Gew1rgfdIyuriJ/Ne0B8FE1s8O/U2ajErVQLUDu9 mihai@io"; 3 | 4 | io = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIFMR4XHc7mhSs0Diy2gWtXurueQiQ1gKjyzW2fuqtqv root@io"; 5 | in { 6 | "spotify.age".publicKeys = [mihai io]; 7 | } 8 | -------------------------------------------------------------------------------- /secrets/spotify.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fufexan/dotfiles/31d8aca6810f629a52f6efc6cf0e8bb355192b83/secrets/spotify.age -------------------------------------------------------------------------------- /system/README.md: -------------------------------------------------------------------------------- 1 | # System 2 | 3 | Common configuration files shared across hosts. 4 | 5 | | Name | Description | 6 | | ------------- | ------------------------------------------------------ | 7 | | `default.nix` | Flake-parts module, entry point | 8 | | core | Core configurations, including boot, security, users | 9 | | hardware | Controls hardware, such as Bluetooth, video cards, etc | 10 | | network | Network-related software configuration | 11 | | nix | Nix-related options | 12 | | programs | `programs.*` configuration | 13 | | services | `services.*` configurtaion | 14 | -------------------------------------------------------------------------------- /system/core/boot.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | ... 5 | }: { 6 | boot = { 7 | bootspec.enable = true; 8 | 9 | initrd = { 10 | systemd.enable = true; 11 | supportedFilesystems = ["ext4"]; 12 | }; 13 | 14 | # use latest kernel 15 | kernelPackages = pkgs.linuxPackages_latest; 16 | 17 | consoleLogLevel = 3; 18 | kernelParams = [ 19 | "quiet" 20 | "systemd.show_status=auto" 21 | "rd.udev.log_level=3" 22 | "plymouth.use-simpledrm" 23 | ]; 24 | 25 | loader = { 26 | # systemd-boot on UEFI 27 | efi.canTouchEfiVariables = true; 28 | systemd-boot.enable = true; 29 | }; 30 | 31 | plymouth.enable = true; 32 | }; 33 | 34 | environment.systemPackages = [config.boot.kernelPackages.cpupower]; 35 | } 36 | -------------------------------------------------------------------------------- /system/core/default.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: 2 | # default configuration shared by all hosts 3 | { 4 | imports = [ 5 | ./security.nix 6 | ./users.nix 7 | ../nix 8 | ../programs/zsh.nix 9 | ]; 10 | 11 | documentation.dev.enable = true; 12 | 13 | i18n = { 14 | defaultLocale = "en_US.UTF-8"; 15 | # saves space 16 | supportedLocales = [ 17 | "en_US.UTF-8/UTF-8" 18 | "ja_JP.UTF-8/UTF-8" 19 | "ro_RO.UTF-8/UTF-8" 20 | ]; 21 | }; 22 | 23 | # don't touch this 24 | system.stateVersion = lib.mkDefault "23.11"; 25 | 26 | time.timeZone = lib.mkDefault "Europe/Bucharest"; 27 | 28 | # compresses half the ram for use as swap 29 | zramSwap.enable = true; 30 | } 31 | -------------------------------------------------------------------------------- /system/core/lanzaboote.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | inputs, 5 | ... 6 | }: 7 | # lanzaboote config 8 | { 9 | imports = [ 10 | inputs.lanzaboote.nixosModules.lanzaboote 11 | ]; 12 | 13 | boot = { 14 | lanzaboote = { 15 | enable = true; 16 | pkiBundle = "/etc/secureboot"; 17 | }; 18 | 19 | # we let lanzaboote install systemd-boot 20 | loader.systemd-boot.enable = lib.mkForce false; 21 | }; 22 | 23 | environment.systemPackages = [pkgs.sbctl]; 24 | } 25 | -------------------------------------------------------------------------------- /system/core/security.nix: -------------------------------------------------------------------------------- 1 | # security tweaks borrowed from @hlissner 2 | { 3 | boot.kernel.sysctl = { 4 | # The Magic SysRq key is a key combo that allows users connected to the 5 | # system console of a Linux kernel to perform some low-level commands. 6 | # Disable it, since we don't need it, and is a potential security concern. 7 | "kernel.sysrq" = 0; 8 | 9 | ## TCP hardening 10 | # Prevent bogus ICMP errors from filling up logs. 11 | "net.ipv4.icmp_ignore_bogus_error_responses" = 1; 12 | # Reverse path filtering causes the kernel to do source validation of 13 | # packets received from all interfaces. This can mitigate IP spoofing. 14 | "net.ipv4.conf.default.rp_filter" = 1; 15 | "net.ipv4.conf.all.rp_filter" = 1; 16 | # Do not accept IP source route packets (we're not a router) 17 | "net.ipv4.conf.all.accept_source_route" = 0; 18 | "net.ipv6.conf.all.accept_source_route" = 0; 19 | # Don't send ICMP redirects (again, we're not a router) 20 | "net.ipv4.conf.all.send_redirects" = 0; 21 | "net.ipv4.conf.default.send_redirects" = 0; 22 | # Refuse ICMP redirects (MITM mitigations) 23 | "net.ipv4.conf.all.accept_redirects" = 0; 24 | "net.ipv4.conf.default.accept_redirects" = 0; 25 | "net.ipv4.conf.all.secure_redirects" = 0; 26 | "net.ipv4.conf.default.secure_redirects" = 0; 27 | "net.ipv6.conf.all.accept_redirects" = 0; 28 | "net.ipv6.conf.default.accept_redirects" = 0; 29 | # Protects against SYN flood attacks 30 | "net.ipv4.tcp_syncookies" = 1; 31 | # Incomplete protection again TIME-WAIT assassination 32 | "net.ipv4.tcp_rfc1337" = 1; 33 | 34 | ## TCP optimization 35 | # TCP Fast Open is a TCP extension that reduces network latency by packing 36 | # data in the sender’s initial TCP SYN. Setting 3 = enable TCP Fast Open for 37 | # both incoming and outgoing connections: 38 | "net.ipv4.tcp_fastopen" = 3; 39 | # Bufferbloat mitigations + slight improvement in throughput & latency 40 | "net.ipv4.tcp_congestion_control" = "bbr"; 41 | "net.core.default_qdisc" = "cake"; 42 | }; 43 | 44 | boot.kernelModules = ["tcp_bbr"]; 45 | 46 | security = { 47 | # allow wayland lockers to unlock the screen 48 | pam.services.hyprlock.text = "auth include login"; 49 | 50 | # userland niceness 51 | rtkit.enable = true; 52 | 53 | # don't ask for password for wheel group 54 | sudo.wheelNeedsPassword = false; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /system/core/users.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | users.users.mihai = { 3 | isNormalUser = true; 4 | shell = pkgs.zsh; 5 | extraGroups = [ 6 | "input" 7 | "libvirtd" 8 | "networkmanager" 9 | "plugdev" 10 | "transmission" 11 | "video" 12 | "wheel" 13 | ]; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /system/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | desktop = [ 3 | ./core 4 | ./core/boot.nix 5 | 6 | ./hardware/fwupd.nix 7 | ./hardware/graphics.nix 8 | 9 | ./network 10 | ./network/avahi.nix 11 | ./network/tailscale.nix 12 | 13 | ./programs 14 | 15 | ./services 16 | ./services/greetd.nix 17 | ./services/pipewire.nix 18 | ]; 19 | 20 | laptop = 21 | desktop 22 | ++ [ 23 | ./hardware/bluetooth.nix 24 | 25 | ./services/backlight.nix 26 | ./services/power.nix 27 | ]; 28 | in { 29 | inherit desktop laptop; 30 | } 31 | -------------------------------------------------------------------------------- /system/hardware/bluetooth.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | hardware.bluetooth = { 3 | enable = true; 4 | package = pkgs.bluez5-experimental; 5 | settings = { 6 | # make Xbox Series X controller work 7 | General = { 8 | Class = "0x000100"; 9 | ControllerMode = "bredr"; 10 | FastConnectable = true; 11 | JustWorksRepairing = "always"; 12 | Privacy = "device"; 13 | # Battery info for Bluetooth devices 14 | Experimental = true; 15 | }; 16 | }; 17 | }; 18 | 19 | # https://github.com/NixOS/nixpkgs/issues/114222 20 | systemd.user.services.telephony_client.enable = false; 21 | } 22 | -------------------------------------------------------------------------------- /system/hardware/fwupd.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.fwupd.enable = true; 3 | } 4 | -------------------------------------------------------------------------------- /system/hardware/graphics.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | # graphics drivers / HW accel 3 | hardware.graphics = { 4 | enable = true; 5 | 6 | extraPackages = with pkgs; [ 7 | libva 8 | vaapiVdpau 9 | libvdpau-va-gl 10 | ]; 11 | extraPackages32 = with pkgs.pkgsi686Linux; [ 12 | vaapiVdpau 13 | libvdpau-va-gl 14 | ]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /system/network/avahi.nix: -------------------------------------------------------------------------------- 1 | { 2 | # network discovery, mDNS 3 | services.avahi = { 4 | enable = true; 5 | nssmdns4 = true; 6 | publish = { 7 | enable = true; 8 | domain = true; 9 | userServices = true; 10 | }; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /system/network/default.nix: -------------------------------------------------------------------------------- 1 | # networking configuration 2 | {pkgs, ...}: { 3 | networking = { 4 | # use quad9 with DNS over TLS 5 | nameservers = ["9.9.9.9#dns.quad9.net"]; 6 | 7 | networkmanager = { 8 | enable = true; 9 | dns = "systemd-resolved"; 10 | wifi.powersave = true; 11 | }; 12 | }; 13 | 14 | programs.ssh.extraConfig = '' 15 | Host neushore 16 | User builder 17 | HostName build.neushore.dev 18 | IdentityFile /home/mihai/.ssh/id_ed25519 19 | Port 30 20 | ''; 21 | 22 | services = { 23 | openssh = { 24 | enable = true; 25 | settings.UseDns = true; 26 | }; 27 | 28 | # DNS resolver 29 | resolved = { 30 | enable = true; 31 | dnsovertls = "opportunistic"; 32 | }; 33 | }; 34 | 35 | systemd.services.NetworkManager-wait-online.serviceConfig.ExecStart = ["" "${pkgs.networkmanager}/bin/nm-online -q"]; 36 | } 37 | -------------------------------------------------------------------------------- /system/network/spotify.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Spotify track sync with other devices 3 | networking.firewall.allowedTCPPorts = [57621]; 4 | } 5 | -------------------------------------------------------------------------------- /system/network/syncthing.nix: -------------------------------------------------------------------------------- 1 | { 2 | networking.firewall = { 3 | allowedUDPPorts = [ 4 | # syncthing QUIC 5 | 22000 6 | # syncthing discovery broadcast on ipv4 and multicast ipv6 7 | 21027 8 | ]; 9 | 10 | allowedTCPPorts = [ 11 | 42355 12 | # syncthing 13 | 22000 14 | ]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /system/network/tailscale.nix: -------------------------------------------------------------------------------- 1 | { 2 | networking.firewall = { 3 | trustedInterfaces = ["tailscale0"]; 4 | # required to connect to Tailscale exit nodes 5 | checkReversePath = "loose"; 6 | }; 7 | 8 | # inter-machine VPN 9 | services.tailscale = { 10 | enable = true; 11 | openFirewall = true; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /system/nix/builders.nix: -------------------------------------------------------------------------------- 1 | { 2 | nix = { 3 | distributedBuilds = true; 4 | settings.builders-use-substitutes = true; 5 | buildMachines = [ 6 | { 7 | hostName = "neushore"; 8 | protocol = "ssh"; # ssh-ng not supported on this machine 9 | maxJobs = 16; 10 | speedFactor = 2; 11 | supportedFeatures = ["benchmark" "nixos-test" "kvm" "big-parallel"]; 12 | systems = ["aarch64-linux" "i686-linux" "x86_64-linux"]; 13 | } 14 | ]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /system/nix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | inputs, 5 | lib, 6 | ... 7 | }: { 8 | imports = [ 9 | ./builders.nix 10 | ./nh.nix 11 | ./nixpkgs.nix 12 | ./substituters.nix 13 | ]; 14 | 15 | # we need git for flakes 16 | environment.systemPackages = [pkgs.git]; 17 | 18 | nix = let 19 | flakeInputs = lib.filterAttrs (_: v: lib.isType "flake" v) inputs; 20 | in { 21 | package = pkgs.lix; 22 | 23 | # pin the registry to avoid downloading and evaling a new nixpkgs version every time 24 | registry = lib.mapAttrs (_: v: {flake = v;}) flakeInputs; 25 | 26 | # set the path for channels compat 27 | nixPath = lib.mapAttrsToList (key: _: "${key}=flake:${key}") config.nix.registry; 28 | 29 | settings = { 30 | auto-optimise-store = true; 31 | builders-use-substitutes = true; 32 | experimental-features = ["nix-command" "flakes"]; 33 | flake-registry = "/etc/nix/registry.json"; 34 | 35 | # for direnv GC roots 36 | keep-derivations = true; 37 | keep-outputs = true; 38 | 39 | trusted-users = ["root" "@wheel"]; 40 | 41 | accept-flake-config = false; 42 | }; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /system/nix/nh.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.nh = { 3 | enable = true; 4 | # weekly cleanup 5 | clean = { 6 | enable = true; 7 | extraArgs = "--keep-since 30d"; 8 | }; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /system/nix/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | {self, ...}: { 2 | nixpkgs = { 3 | config.allowUnfree = true; 4 | config.permittedInsecurePackages = [ 5 | "electron-25.9.0" 6 | ]; 7 | 8 | overlays = [ 9 | (final: prev: { 10 | lib = 11 | prev.lib 12 | // { 13 | colors = import "${self}/lib/colors" prev.lib; 14 | }; 15 | }) 16 | ]; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /system/nix/substituters.nix: -------------------------------------------------------------------------------- 1 | { 2 | nix.settings = { 3 | substituters = [ 4 | # high priority since it's almost always used 5 | "https://cache.nixos.org?priority=10" 6 | 7 | "https://anyrun.cachix.org" 8 | "https://fufexan.cachix.org" 9 | "https://helix.cachix.org" 10 | "https://hyprland.cachix.org" 11 | "https://nix-community.cachix.org" 12 | "https://nix-gaming.cachix.org" 13 | "https://yazi.cachix.org" 14 | ]; 15 | 16 | trusted-public-keys = [ 17 | "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 18 | 19 | "anyrun.cachix.org-1:pqBobmOjI7nKlsUMV25u9QHa9btJK65/C8vnO3p346s=" 20 | "fufexan.cachix.org-1:LwCDjCJNJQf5XD2BV+yamQIMZfcKWR9ISIFy5curUsY=" 21 | "helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs=" 22 | "hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=" 23 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 24 | "nix-gaming.cachix.org-1:nbjlureqMbRAxR1gJ/f3hxemL9svXaZF/Ees8vCUUs4=" 25 | "yazi.cachix.org-1:Dcdz63NZKfvUCbDGngQDAZq6kOroIrFoyO064uvLh8k=" 26 | ]; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /system/programs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ./fonts.nix 4 | ./home-manager.nix 5 | # ./qt.nix 6 | ./xdg.nix 7 | ]; 8 | 9 | programs = { 10 | # make HM-managed GTK stuff work 11 | dconf.enable = true; 12 | 13 | kdeconnect.enable = true; 14 | 15 | seahorse.enable = true; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /system/programs/fonts.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | fonts = { 3 | packages = with pkgs; [ 4 | # icon fonts 5 | material-symbols 6 | 7 | # Sans(Serif) fonts 8 | libertinus 9 | noto-fonts 10 | noto-fonts-cjk-sans 11 | noto-fonts-emoji 12 | roboto 13 | (google-fonts.override {fonts = ["Inter"];}) 14 | 15 | # monospace fonts 16 | jetbrains-mono 17 | 18 | # nerdfonts 19 | nerd-fonts.jetbrains-mono 20 | nerd-fonts.symbols-only 21 | ]; 22 | 23 | # causes more issues than it solves 24 | enableDefaultPackages = false; 25 | 26 | # user defined fonts 27 | fontconfig.defaultFonts = { 28 | serif = ["Libertinus Serif"]; 29 | sansSerif = ["Inter"]; 30 | monospace = ["JetBrains Mono Nerd Font"]; 31 | emoji = ["Noto Color Emoji"]; 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /system/programs/gamemode.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: { 2 | programs.gamemode = { 3 | enable = true; 4 | settings = { 5 | general = { 6 | softrealtime = "auto"; 7 | renice = 15; 8 | }; 9 | }; 10 | }; 11 | 12 | # see https://github.com/fufexan/nix-gaming/#pipewire-low-latency 13 | services.pipewire.lowLatency.enable = true; 14 | imports = [ 15 | inputs.nix-gaming.nixosModules.pipewireLowLatency 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /system/programs/games.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs = { 3 | gamescope = { 4 | enable = true; 5 | capSysNice = true; 6 | args = [ 7 | "--rt" 8 | "--expose-wayland" 9 | ]; 10 | }; 11 | 12 | steam = { 13 | enable = true; 14 | 15 | extraCompatPackages = [ 16 | pkgs.proton-ge-bin 17 | ]; 18 | 19 | gamescopeSession.enable = true; 20 | }; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /system/programs/home-manager.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: { 2 | imports = [ 3 | inputs.hm.nixosModules.default 4 | ]; 5 | 6 | home-manager = { 7 | useGlobalPkgs = true; 8 | useUserPackages = true; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /system/programs/hyprland/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: { 6 | imports = [ 7 | inputs.hyprland.nixosModules.default 8 | 9 | ./binds.nix 10 | ./rules.nix 11 | ./settings.nix 12 | ./smartgaps.nix 13 | ]; 14 | 15 | environment.systemPackages = [ 16 | inputs.hyprland-contrib.packages.${pkgs.system}.grimblast 17 | inputs.self.packages.${pkgs.system}.bibata-hyprcursor 18 | ]; 19 | 20 | environment.pathsToLink = ["/share/icons"]; 21 | 22 | # enable hyprland and required options 23 | programs.hyprland = { 24 | enable = true; 25 | withUWSM = true; 26 | 27 | plugins = with inputs.hyprland-plugins.packages.${pkgs.system}; [ 28 | hyprbars 29 | # hyprexpo 30 | ]; 31 | }; 32 | 33 | # tell Electron/Chromium to run on Wayland 34 | environment.variables.NIXOS_OZONE_WL = "1"; 35 | } 36 | -------------------------------------------------------------------------------- /system/programs/hyprland/rules.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | programs.hyprland.settings = { 3 | # layer rules 4 | layerrule = let 5 | toRegex = list: let 6 | elements = lib.concatStringsSep "|" list; 7 | in "^(${elements})$"; 8 | 9 | lowopacity = [ 10 | "bar" 11 | "calendar" 12 | "notifications" 13 | "system-menu" 14 | ]; 15 | 16 | highopacity = [ 17 | "anyrun" 18 | "osd" 19 | "logout_dialog" 20 | ]; 21 | 22 | blurred = lib.concatLists [ 23 | lowopacity 24 | highopacity 25 | ]; 26 | in [ 27 | "blur, ${toRegex blurred}" 28 | "xray 1, ${toRegex ["bar"]}" 29 | "ignorealpha 0.5, ${toRegex (highopacity ++ ["music"])}" 30 | "ignorealpha 0.2, ${toRegex lowopacity}" 31 | ]; 32 | 33 | # window rules 34 | windowrulev2 = [ 35 | # telegram media viewer 36 | "float, title:^(Media viewer)$" 37 | 38 | # Bitwarden extension 39 | "float, title:^(.*Bitwarden Password Manager.*)$" 40 | 41 | # gnome calculator 42 | "float, class:^(org.gnome.Calculator)$" 43 | "size 360 490, class:^(org.gnome.Calculator)$" 44 | 45 | # allow tearing in games 46 | "immediate, class:^(osu\!|cs2)$" 47 | 48 | # make Firefox/Zen PiP window floating and sticky 49 | "float, title:^(Picture-in-Picture)$" 50 | "pin, title:^(Picture-in-Picture)$" 51 | 52 | # throw sharing indicators away 53 | "workspace special silent, title:^(Firefox — Sharing Indicator)$" 54 | "workspace special silent, title:^(Zen — Sharing Indicator)$" 55 | "workspace special silent, title:^(.*is sharing (your screen|a window)\.)$" 56 | 57 | # start Spotify and YouTube Music in ws9 58 | "workspace 9 silent, title:^(Spotify( Premium)?)$" 59 | "workspace 9 silent, title:^(YouTube Music)$" 60 | 61 | # idle inhibit while watching videos 62 | "idleinhibit focus, class:^(mpv|.+exe|celluloid)$" 63 | "idleinhibit focus, class:^(zen)$, title:^(.*YouTube.*)$" 64 | "idleinhibit fullscreen, class:^(zen)$" 65 | 66 | "dimaround, class:^(gcr-prompter)$" 67 | "dimaround, class:^(xdg-desktop-portal-gtk)$" 68 | "dimaround, class:^(polkit-gnome-authentication-agent-1)$" 69 | "dimaround, class:^(zen)$, title:^(File Upload)$" 70 | 71 | # fix xwayland apps 72 | "rounding 0, xwayland:1" 73 | "center, class:^(.*jetbrains.*)$, title:^(Confirm Exit|Open Project|win424|win201|splash)$" 74 | "size 640 400, class:^(.*jetbrains.*)$, title:^(splash)$" 75 | 76 | # Matlab 77 | "tile, title:MATLAB" 78 | "noanim on, class:MATLAB, title:DefaultOverlayManager.JWindow" 79 | "noblur on, class:MATLAB, title:DefaultOverlayManager.JWindow" 80 | "noborder on, class:MATLAB, title:DefaultOverlayManager.JWindow" 81 | "noshadow on, class:MATLAB, title:DefaultOverlayManager.JWindow" 82 | "plugin:hyprbars:nobar, class:MATLAB, title:DefaultOverlayManager.JWindow" 83 | 84 | # don't render hyprbars on tiling windows 85 | "plugin:hyprbars:nobar, floating:0" 86 | 87 | # less sensitive scroll for some windows 88 | # browser(-based) 89 | "scrolltouchpad 0.1, class:^(zen|firefox|chromium-browser|chrome-.*)$" 90 | "scrolltouchpad 0.1, class:^(obsidian)$" 91 | "scrolltouchpad 0.1, class:^(steam)$" 92 | "scrolltouchpad 0.1, class:^(Zotero)$" 93 | # GTK3 94 | "scrolltouchpad 0.1, class:^(com.github.xournalpp.xournalpp)$" 95 | "scrolltouchpad 0.1, class:^(libreoffice.*)$" 96 | "scrolltouchpad 0.1, class:^(.virt-manager-wrapped)$" 97 | "scrolltouchpad 0.1, class:^(xdg-desktop-portal-gtk)$" 98 | # Qt5 99 | "scrolltouchpad 0.1, class:^(org.prismlauncher.PrismLauncher)$" 100 | "scrolltouchpad 0.1, class:^(org.kde.kdeconnect.app)$" 101 | # Others 102 | "scrolltouchpad 0.1, class:^(org.pwmt.zathura)$" 103 | ]; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /system/programs/hyprland/settings.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | ... 6 | }: let 7 | # pointer = config.home.pointerCursor; 8 | cursorName = "Bibata-Modern-Classic-Hyprcursor"; 9 | in { 10 | programs.hyprland.settings = { 11 | "$mod" = "SUPER"; 12 | env = [ 13 | "QT_WAYLAND_DISABLE_WINDOWDECORATION,1" 14 | "HYPRCURSOR_THEME,${cursorName}" 15 | "HYPRCURSOR_SIZE,${toString 16}" 16 | # See https://github.com/hyprwm/contrib/issues/142 17 | "GRIMBLAST_NO_CURSOR,0" 18 | ]; 19 | 20 | exec-once = [ 21 | # finalize startup 22 | "uwsm finalize" 23 | # set cursor for HL itself 24 | "hyprctl setcursor ${cursorName} ${toString 16}" 25 | "hyprlock" 26 | ]; 27 | 28 | general = { 29 | gaps_in = 4; 30 | gaps_out = 8; 31 | border_size = 1; 32 | "col.active_border" = "rgba(88888888)"; 33 | "col.inactive_border" = "rgba(00000088)"; 34 | 35 | allow_tearing = true; 36 | resize_on_border = true; 37 | }; 38 | 39 | decoration = { 40 | rounding = 10; 41 | rounding_power = 3; 42 | blur = { 43 | enabled = true; 44 | brightness = 1.0; 45 | contrast = 1.0; 46 | noise = 0.01; 47 | 48 | vibrancy = 0.2; 49 | vibrancy_darkness = 0.5; 50 | 51 | passes = 4; 52 | size = 7; 53 | 54 | popups = true; 55 | popups_ignorealpha = 0.2; 56 | }; 57 | 58 | shadow = { 59 | enabled = true; 60 | color = "rgba(00000055)"; 61 | ignore_window = true; 62 | offset = "0 15"; 63 | range = 100; 64 | render_power = 2; 65 | scale = 0.97; 66 | }; 67 | }; 68 | 69 | animations.enabled = true; 70 | 71 | animation = [ 72 | "border, 1, 2, default" 73 | "fade, 1, 4, default" 74 | "windows, 1, 3, default, popin 80%" 75 | "workspaces, 1, 2, default, slide" 76 | ]; 77 | 78 | group = { 79 | groupbar = { 80 | font_size = 10; 81 | gradients = false; 82 | text_color = "rgb(b6c4ff)"; 83 | }; 84 | 85 | "col.border_active" = "rgba(35447988)"; 86 | "col.border_inactive" = "rgba(dce1ff88)"; 87 | }; 88 | 89 | input = { 90 | kb_layout = "ro"; 91 | 92 | # focus change on cursor move 93 | follow_mouse = 1; 94 | accel_profile = "flat"; 95 | tablet.output = "current"; 96 | }; 97 | 98 | dwindle = { 99 | # keep floating dimentions while tiling 100 | pseudotile = true; 101 | preserve_split = true; 102 | }; 103 | 104 | misc = { 105 | force_default_wallpaper = 0; 106 | 107 | # disable dragging animation 108 | animate_mouse_windowdragging = false; 109 | 110 | # enable variable refresh rate (effective depending on hardware) 111 | vrr = 1; 112 | }; 113 | 114 | render.direct_scanout = true; 115 | 116 | # touchpad gestures 117 | gestures = { 118 | workspace_swipe = true; 119 | workspace_swipe_forever = true; 120 | }; 121 | 122 | permission = [ 123 | # Allow xdph and grim 124 | "${config.programs.hyprland.portalPackage}/libexec/.xdg-desktop-portal-hyprland-wrapped, screencopy, allow" 125 | "${lib.getExe pkgs.grim}, screencopy, allow" 126 | # Optionally allow non-pipewire capturing 127 | "${lib.getExe pkgs.wl-screenrec}, screencopy, allow" 128 | ]; 129 | 130 | xwayland.force_zero_scaling = true; 131 | 132 | debug.disable_logs = false; 133 | 134 | plugin.hyprbars = { 135 | bar_height = 20; 136 | # bar_precedence_over_border = true; 137 | icon_on_hover = true; 138 | }; 139 | 140 | # order is right-to-left 141 | hyprbars-button = [ 142 | # close 143 | "rgb(ffb4ab), 15, , hyprctl dispatch killactive" 144 | # maximize 145 | "rgb(b6c4ff), 15, , hyprctl dispatch fullscreen 1" 146 | ]; 147 | 148 | # csgo-vulkan-fix = { 149 | # res_w = 1280; 150 | # res_h = 800; 151 | # class = "cs2"; 152 | # }; 153 | 154 | # hyprexpo = { 155 | # columns = 3; 156 | # gap_size = 4; 157 | # bg_col = "rgb(000000)"; 158 | 159 | # enable_gesture = true; 160 | # gesture_distance = 300; 161 | # gesture_positive = false; 162 | # }; 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /system/programs/hyprland/smartgaps.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | ... 6 | }: let 7 | inherit (config.programs.hyprland.settings.general) gaps_in gaps_out border_size; 8 | inherit (config.programs.hyprland.settings.decoration) rounding; 9 | inherit (builtins) concatStringsSep; 10 | inherit (lib.lists) flatten; 11 | 12 | workspaceSelectors = ["w[t1]" "w[tg1]" "f[1]"]; 13 | 14 | toggleSmartGaps = let 15 | forEach = f: concatStringsSep "\n" (map f workspaceSelectors); 16 | in 17 | pkgs.writeShellScript "toggleSmartGaps" '' 18 | hyprctl -j workspacerules | ${lib.getExe pkgs.jaq} -e 'any(.[]; select(.workspaceString == "w[t1]" or .workspaceString == "w[tg1]" or .workspaceString == "w[f1]") | (.gapsIn | all(. == 0)) and (.gapsOut | all(. == 0)))' > /dev/null 19 | 20 | if [ $? -eq 0 ]; then 21 | ${forEach (selector: '' 22 | hyprctl keyword workspace "${selector}, gapsout:${toString gaps_out}, gapsin:${toString gaps_in}" 23 | hyprctl keyword windowrulev2 "bordersize ${toString border_size}, floating:0, onworkspace:${selector}" 24 | hyprctl keyword windowrulev2 "rounding ${toString rounding}, floating:0, onworkspace:${selector}" 25 | '')} 26 | else 27 | ${forEach (selector: '' 28 | hyprctl keyword workspace "${selector}, gapsout:0, gapsin:0" 29 | hyprctl keyword windowrulev2 "bordersize 0, floating:0, onworkspace:${selector}" 30 | hyprctl keyword windowrulev2 "rounding 0, floating:0, onworkspace:${selector}" 31 | '')} 32 | fi 33 | ''; 34 | in { 35 | # Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/ 36 | # "Smart gaps" / "No gaps when only" 37 | programs.hyprland.settings = { 38 | workspace = map (x: "${x}, gapsout:0, gapsin:0") workspaceSelectors; 39 | 40 | windowrulev2 = flatten (map (x: [ 41 | "bordersize 0, floating:0, onworkspace:${x}" 42 | "rounding 0, floating:0, onworkspace:${x}" 43 | ]) 44 | workspaceSelectors); 45 | 46 | bind = [ 47 | "$mod, M, exec, ${toggleSmartGaps}" 48 | ]; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /system/programs/qt.nix: -------------------------------------------------------------------------------- 1 | { 2 | qt = { 3 | enable = true; 4 | platformTheme.name = "gtk2"; 5 | style = "gtk2"; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /system/programs/xdg.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | xdg.portal = { 3 | enable = true; 4 | xdgOpenUsePortal = true; 5 | config = { 6 | common.default = ["gtk"]; 7 | hyprland.default = ["gtk" "hyprland"]; 8 | }; 9 | 10 | extraPortals = [ 11 | pkgs.xdg-desktop-portal-gtk 12 | ]; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /system/programs/zsh.nix: -------------------------------------------------------------------------------- 1 | { 2 | # enable zsh autocompletion for system packages (systemd, etc) 3 | environment.pathsToLink = ["/share/zsh"]; 4 | 5 | programs = { 6 | less.enable = true; 7 | 8 | zsh = { 9 | enable = true; 10 | autosuggestions.enable = true; 11 | syntaxHighlighting = { 12 | enable = true; 13 | patterns = {"rm -rf *" = "fg=black,bg=red";}; 14 | styles = {"alias" = "fg=magenta";}; 15 | highlighters = ["main" "brackets" "pattern"]; 16 | }; 17 | }; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /system/services/backlight.nix: -------------------------------------------------------------------------------- 1 | { 2 | # smooth backlight control 3 | hardware.brillo.enable = true; 4 | } 5 | -------------------------------------------------------------------------------- /system/services/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | services = { 3 | dbus.implementation = "broker"; 4 | 5 | # profile-sync-daemon 6 | psd = { 7 | enable = true; 8 | resyncTimer = "10m"; 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /system/services/gnome-services.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | services = { 3 | # needed for GNOME services outside of GNOME Desktop 4 | dbus.packages = with pkgs; [ 5 | gcr 6 | gnome-settings-daemon 7 | ]; 8 | 9 | gnome.gnome-keyring.enable = true; 10 | 11 | gvfs.enable = true; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /system/services/greetd.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: { 6 | # greetd display manager 7 | services.greetd = let 8 | session = { 9 | command = "${lib.getExe config.programs.uwsm.package} start hyprland-uwsm.desktop"; 10 | user = "mihai"; 11 | }; 12 | in { 13 | enable = true; 14 | settings = { 15 | terminal.vt = 1; 16 | default_session = session; 17 | initial_session = session; 18 | }; 19 | }; 20 | 21 | # unlock GPG keyring on login 22 | # disabled as it doesn't work with autologin 23 | # security.pam.services.greetd.enableGnomeKeyring = true; 24 | } 25 | -------------------------------------------------------------------------------- /system/services/kanata/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | # keyboard remapping 3 | services.kanata = { 4 | enable = true; 5 | 6 | keyboards.one2mini = { 7 | devices = ["/dev/input/by-id/usb-Ducky_Ducky_One2_Mini_RGB_DK-V1.17-190813-event-kbd"]; 8 | config = builtins.readFile (./. + "/main.kbd"); 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /system/services/kanata/main.kbd: -------------------------------------------------------------------------------- 1 | (defsrc 2 | grv 1 2 3 4 5 6 7 8 9 0 - = bspc 3 | tab q w e r t y u i o p [ ] \ 4 | caps a s d f g h j k l ; ' ret 5 | lsft z x c v b n m , . / rsft 6 | lctl lmet lalt spc ralt rmet cmp rctl 7 | ) 8 | 9 | (deflayer colemak 10 | grv 1 2 3 4 5 6 7 8 9 0 - = bspc 11 | tab q w f p b j l u y ; [ ] \ 12 | @esc a r s t g m n e i o ' ret 13 | lsft x c d v z k h , . / rsft 14 | @lay lmet lalt @spc ralt rmet cmp rctl 15 | ) 16 | 17 | (deflayer qwerty 18 | grv 1 2 3 4 5 6 7 8 9 0 - = bspc 19 | tab q w e r t y u i o p [ ] \ 20 | @cps a s d f g h j k l ; ' ret 21 | lsft z x c v b n m , . / rsft 22 | lctl lmet lalt spc ralt rmet cmp rctl 23 | ) 24 | 25 | (deflayer layouts 26 | _ _ _ _ _ _ _ _ _ _ _ _ _ _ 27 | _ _ _ _ _ _ _ _ _ _ _ _ _ _ 28 | caps _ _ _ @cmk _ _ @qwe _ _ _ _ _ 29 | _ _ _ _ _ _ _ _ _ _ _ _ 30 | _ _ _ _ _ _ _ _ 31 | ) 32 | 33 | (deflayer symbols 34 | _ f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 del 35 | _ _ _ _ _ _ ins home up end pgup _ prnt _ 36 | _ _ _ _ _ _ _ left down rght pgdn _ _ 37 | _ _ _ _ _ _ _ mute vold volu _ _ 38 | pp _ prev _ next _ _ _ 39 | ) 40 | 41 | (defalias 42 | lay (layer-toggle layouts) 43 | cmk (layer-switch colemak) 44 | qwe (layer-switch qwerty) 45 | sym (layer-toggle symbols) 46 | esc (tap-hold-release 200 200 esc lctl) 47 | spc (tap-hold-release 200 200 spc @sym) 48 | cps (tap-hold-release 200 200 caps @lay) 49 | ) 50 | -------------------------------------------------------------------------------- /system/services/location.nix: -------------------------------------------------------------------------------- 1 | { 2 | # enable location service 3 | location.provider = "geoclue2"; 4 | 5 | # provide location 6 | services.geoclue2 = { 7 | enable = true; 8 | geoProviderUrl = "https://beacondb.net/v1/geolocate"; 9 | submissionUrl = "https://beacondb.net/v2/geosubmit"; 10 | submissionNick = "geoclue"; 11 | 12 | appConfig.gammastep = { 13 | isAllowed = true; 14 | isSystem = false; 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /system/services/pipewire.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: { 2 | services.pipewire = { 3 | enable = true; 4 | alsa.enable = true; 5 | alsa.support32Bit = true; 6 | jack.enable = true; 7 | pulse.enable = true; 8 | 9 | wireplumber.extraConfig."wireplumber.profiles".main."monitor.libcamera" = "disabled"; 10 | }; 11 | 12 | services.pulseaudio.enable = lib.mkForce false; 13 | } 14 | -------------------------------------------------------------------------------- /system/services/power.nix: -------------------------------------------------------------------------------- 1 | { 2 | services = { 3 | logind.powerKey = "suspend"; 4 | 5 | power-profiles-daemon.enable = true; 6 | 7 | # battery info 8 | upower.enable = true; 9 | }; 10 | } 11 | --------------------------------------------------------------------------------