├── .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 |
61 |
62 | *Hint: click to go to a video showcase*
63 |
64 |
65 |
66 | Light
67 |
68 |
69 |
70 |
71 |
72 |
73 | Previous versions
74 |
75 |
76 |
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 |
--------------------------------------------------------------------------------