diff --git a/foreign/dotfiles b/foreign/dotfiles
deleted file mode 160000
index b76809f..0000000
--- a/foreign/dotfiles
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b76809f06d5460e8cd9cd4be770760e4e54a142f
diff --git a/foreign/dotfiles/LICENSE b/foreign/dotfiles/LICENSE
new file mode 100644
index 0000000..54ef845
--- /dev/null
+++ b/foreign/dotfiles/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2021 Mihai Fufezan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/foreign/dotfiles/README.md b/foreign/dotfiles/README.md
new file mode 100644
index 0000000..87e00fc
--- /dev/null
+++ b/foreign/dotfiles/README.md
@@ -0,0 +1,74 @@
+
fufexan/dotfiles
+
+# 🗒 About
+
+In-house baked configs for Home-Manager and NixOS. Borrowed bits sprinkled on
+top. Using [flakes](https://nixos.wiki/wiki/Flakes) and
+[flake-parts](https://github.com/hercules-ci/flake-parts).
+
+See an overview of the flake outputs by running
+`nix flake show github:fufexan/dotfiles`.
+
+## 🗃️ Contents
+
+- [modules](modules): NixOS common configs
+- [hosts](hosts): host-specific configuration
+- [home](home): my [Home Manager](https://github.com/nix-community/home-manager) config
+- [lib](lib): helper functions
+- [pkgs](pkgs): package definitions
+
+# 📦 Exported packages
+
+Run packages directly with:
+
+```console
+nix run github:fufexan/dotfiles#packageName
+```
+
+Or install from the `packages` output. For example:
+
+```nix
+# flake.nix
+{
+ inputs.fufexan-dotfiles.url = "github:fufexan/dotfiles";
+ # Override my nixpkgs, binary cache will have less hits
+ inputs.fufexan-dotfiles.inputs.nixpkgs.follows = "nixpkgs";
+}
+
+# configuration.nix
+{pkgs, inputs, ...}: {
+ environment.systemPackages = [
+ inputs.fufexan-dotfiles.packages."x86_64-linux".packageName
+ ];
+}
+```
+
+## 💻 Desktop preview
+
+
+
+
+*Hint: click to go to a video showcase*
+
+# 💾 Resources
+
+Other configurations from where I learned and copied:
+
+- [colemickens/nixcfg](https://github.com/colemickens/nixcfg)
+- [flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus)
+- [gytis-ivaskevicius/nixfiles](https://github.com/gytis-ivaskevicius/nixfiles)
+- [Mic92/dotfiles](https://github.com/Mic92/dotfiles)
+- [NobbZ/nixos-config](https://github.com/NobbZ/nixos-config)
+- [privatevoid-net/privatevoid-infrastructure](https://github.com/privatevoid-net/privatevoid-infrastructure)
+- [RicArch97/nixos-config](https://github.com/RicArch97/nixos-config)
+- [viperML/dotfiles](https://github.com/viperML/dotfiles)
+
+# 👥 People
+
+These are the people whom I've taken inspiration from while writing these
+configs. There surely are more but I tend to forget. Regardless, I am thankful
+to all of them.
+
+DieracDelta - gytis-ivaskevicius - hlissner - keksbg - Kranzes -
+matthewcroughan - max-privatevoid - Misterio77 - NobbZ - OPNA2608 -
+pnotequalnp - RicArch97 - tadeokondrak - viperML - Xe - yusdacra
diff --git a/foreign/dotfiles/flake.lock b/foreign/dotfiles/flake.lock
new file mode 100644
index 0000000..1901bbe
--- /dev/null
+++ b/foreign/dotfiles/flake.lock
@@ -0,0 +1,788 @@
+{
+ "nodes": {
+ "agenix": {
+ "inputs": {
+ "darwin": "darwin",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1677969766,
+ "narHash": "sha256-AIp/ZYZMNLDZR/H7iiAlaGpu4lcXsVt9JQpBlf43HRY=",
+ "owner": "ryantm",
+ "repo": "agenix",
+ "rev": "03b51fe8e459a946c4b88dcfb6446e45efb2c24e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ryantm",
+ "repo": "agenix",
+ "type": "github"
+ }
+ },
+ "crane": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1670900067,
+ "narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
+ "darwin": {
+ "inputs": {
+ "nixpkgs": [
+ "agenix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1673295039,
+ "narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
+ "owner": "lnl7",
+ "repo": "nix-darwin",
+ "rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
+ "type": "github"
+ },
+ "original": {
+ "owner": "lnl7",
+ "ref": "master",
+ "repo": "nix-darwin",
+ "type": "github"
+ }
+ },
+ "devshell": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1678957337,
+ "narHash": "sha256-Gw4nVbuKRdTwPngeOZQOzH/IFowmz4LryMPDiJN/ah4=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "3e0e60ab37cd0bf7ab59888f5c32499d851edb47",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "dream2nix": {
+ "inputs": {
+ "alejandra": [
+ "helix",
+ "nci"
+ ],
+ "all-cabal-json": [
+ "helix",
+ "nci"
+ ],
+ "crane": "crane",
+ "devshell": [
+ "helix",
+ "nci"
+ ],
+ "flake-parts": [
+ "helix",
+ "nci",
+ "parts"
+ ],
+ "flake-utils-pre-commit": [
+ "helix",
+ "nci"
+ ],
+ "ghc-utils": [
+ "helix",
+ "nci"
+ ],
+ "gomod2nix": [
+ "helix",
+ "nci"
+ ],
+ "mach-nix": [
+ "helix",
+ "nci"
+ ],
+ "nix-pypi-fetcher": [
+ "helix",
+ "nci"
+ ],
+ "nixpkgs": [
+ "helix",
+ "nci",
+ "nixpkgs"
+ ],
+ "poetry2nix": [
+ "helix",
+ "nci"
+ ],
+ "pre-commit-hooks": [
+ "helix",
+ "nci"
+ ],
+ "pruned-racket-catalog": [
+ "helix",
+ "nci"
+ ]
+ },
+ "locked": {
+ "lastModified": 1677289985,
+ "narHash": "sha256-lUp06cTTlWubeBGMZqPl9jODM99LpWMcwxRiscFAUJg=",
+ "owner": "nix-community",
+ "repo": "dream2nix",
+ "rev": "28b973a8d4c30cc1cbb3377ea2023a76bc3fb889",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "dream2nix",
+ "type": "github"
+ }
+ },
+ "eww": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "rust-overlay": [
+ "rust-overlay"
+ ]
+ },
+ "locked": {
+ "lastModified": 1678303550,
+ "narHash": "sha256-JlpoMXL+QIO0DUIyAcGRJte2G/jF/rSeO/zze5W7S/s=",
+ "owner": "elkowar",
+ "repo": "eww",
+ "rev": "45154bbf5962cad9c4e6c76f75d57dd8d740d307",
+ "type": "github"
+ },
+ "original": {
+ "owner": "elkowar",
+ "repo": "eww",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1650374568,
+ "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_2": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1678379998,
+ "narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1642700792,
+ "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "fu": {
+ "locked": {
+ "lastModified": 1678901627,
+ "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "helix": {
+ "inputs": {
+ "nci": "nci",
+ "nixpkgs": "nixpkgs",
+ "parts": [
+ "flake-parts"
+ ],
+ "rust-overlay": "rust-overlay"
+ },
+ "locked": {
+ "lastModified": 1678544833,
+ "narHash": "sha256-aLs6qjiViaxryP2XMjKPzrJGYfIQsGTbNWfbySfivmY=",
+ "owner": "SoraTenshi",
+ "repo": "helix",
+ "rev": "80782f4f3cd557617d09b0d8f42dbce780552630",
+ "type": "github"
+ },
+ "original": {
+ "owner": "SoraTenshi",
+ "ref": "daily-driver",
+ "repo": "helix",
+ "type": "github"
+ }
+ },
+ "hm": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "utils": "utils"
+ },
+ "locked": {
+ "lastModified": 1679067095,
+ "narHash": "sha256-G2dJQURL/CCi+8RP6jNJG8VqgtzEMCA+6mNodd3VR6E=",
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "rev": "3239e0b40f242f47bf6c0c37b2fd35ab3e76e370",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "type": "github"
+ }
+ },
+ "hyprland": {
+ "inputs": {
+ "hyprland-protocols": "hyprland-protocols",
+ "nixpkgs": "nixpkgs_2",
+ "wlroots": "wlroots",
+ "xdph": "xdph"
+ },
+ "locked": {
+ "lastModified": 1679166084,
+ "narHash": "sha256-yr+alTr1eGjEKpMiD06FTTMP6vaoNwYEZT6mW6dQ5rM=",
+ "owner": "hyprwm",
+ "repo": "Hyprland",
+ "rev": "06244555915339967864292dd0b83cd9732516d8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hyprwm",
+ "repo": "Hyprland",
+ "type": "github"
+ }
+ },
+ "hyprland-contrib": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1679036674,
+ "narHash": "sha256-2s3Hfq56jL8ePyc3+calPT34FNMK2zksqwPhIxAq20o=",
+ "owner": "hyprwm",
+ "repo": "contrib",
+ "rev": "1af47a008e850c595aeddc83bb3f04fd81935caa",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hyprwm",
+ "repo": "contrib",
+ "type": "github"
+ }
+ },
+ "hyprland-protocols": {
+ "inputs": {
+ "nixpkgs": [
+ "hyprland",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1671839510,
+ "narHash": "sha256-+PY1qqJfmZzzROgcIY4I7AkCwpnC+qBIYk2eFoA9RWc=",
+ "owner": "hyprwm",
+ "repo": "hyprland-protocols",
+ "rev": "b8f55e02a328c47ed373133c52483bbfa20a1b75",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hyprwm",
+ "repo": "hyprland-protocols",
+ "type": "github"
+ }
+ },
+ "kmonad": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "dir": "nix",
+ "lastModified": 1673185501,
+ "narHash": "sha256-uEtWPpl9nH7QqochHo1z+giPga1zXR1Ko3dOXHIapFY=",
+ "owner": "kmonad",
+ "repo": "kmonad",
+ "rev": "3413f1be996142c8ef4f36e246776a6df7175979",
+ "type": "github"
+ },
+ "original": {
+ "dir": "nix",
+ "owner": "kmonad",
+ "repo": "kmonad",
+ "type": "github"
+ }
+ },
+ "lowdown-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1633514407,
+ "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
+ "owner": "kristapsdz",
+ "repo": "lowdown",
+ "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "kristapsdz",
+ "repo": "lowdown",
+ "type": "github"
+ }
+ },
+ "mk-naked-shell": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1676572903,
+ "narHash": "sha256-oQoDHHUTxNVSURfkFcYLuAK+btjs30T4rbEUtCUyKy8=",
+ "owner": "yusdacra",
+ "repo": "mk-naked-shell",
+ "rev": "aeca9f8aa592f5e8f71f407d081cb26fd30c5a57",
+ "type": "github"
+ },
+ "original": {
+ "owner": "yusdacra",
+ "repo": "mk-naked-shell",
+ "type": "github"
+ }
+ },
+ "nci": {
+ "inputs": {
+ "dream2nix": "dream2nix",
+ "mk-naked-shell": "mk-naked-shell",
+ "nixpkgs": [
+ "helix",
+ "nixpkgs"
+ ],
+ "parts": "parts",
+ "rust-overlay": [
+ "helix",
+ "rust-overlay"
+ ]
+ },
+ "locked": {
+ "lastModified": 1677297103,
+ "narHash": "sha256-ArlJIbp9NGV9yvhZdV0SOUFfRlI/kHeKoCk30NbSiLc=",
+ "owner": "yusdacra",
+ "repo": "nix-cargo-integration",
+ "rev": "a79272a2cb0942392bb3a5bf9a3ec6bc568795b2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "yusdacra",
+ "repo": "nix-cargo-integration",
+ "type": "github"
+ }
+ },
+ "nix-gaming": {
+ "inputs": {
+ "nixpkgs": "nixpkgs_3"
+ },
+ "locked": {
+ "lastModified": 1678324469,
+ "narHash": "sha256-FWI0+K1zdfzOq8BbgGhRvy+qp2J4KauGL1geAc19PRQ=",
+ "owner": "fufexan",
+ "repo": "nix-gaming",
+ "rev": "18fac9dd032d0bb1d4c660b3257fa00df7f0145d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "fufexan",
+ "repo": "nix-gaming",
+ "type": "github"
+ }
+ },
+ "nix-super": {
+ "inputs": {
+ "lowdown-src": "lowdown-src",
+ "nixpkgs": "nixpkgs_4",
+ "nixpkgs-regression": "nixpkgs-regression"
+ },
+ "locked": {
+ "lastModified": 1677536397,
+ "narHash": "sha256-pKp+dmOJc3/9R3dBP30u2zXOyCuF5dVzgFlS1upSwZk=",
+ "owner": "privatevoid-net",
+ "repo": "nix-super",
+ "rev": "8eb40776e51819038fbb8a087d9885842451a333",
+ "type": "github"
+ },
+ "original": {
+ "owner": "privatevoid-net",
+ "repo": "nix-super",
+ "type": "github"
+ }
+ },
+ "nix-xilinx": {
+ "inputs": {
+ "flake-compat": "flake-compat_2",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1678957299,
+ "narHash": "sha256-myDk5QC9Q5FESSJl5N3Nf67w+dwq2KpvcguTwodKGqo=",
+ "owner": "doronbehar",
+ "repo": "nix-xilinx",
+ "rev": "e1533146984dd11bdefd6042c282cd123aca8d71",
+ "type": "gitlab"
+ },
+ "original": {
+ "owner": "doronbehar",
+ "repo": "nix-xilinx",
+ "type": "gitlab"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1677063315,
+ "narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "988cc958c57ce4350ec248d2d53087777f9e1949",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-regression": {
+ "locked": {
+ "lastModified": 1643052045,
+ "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1677676435,
+ "narHash": "sha256-6FxdcmQr5JeZqsQvfinIMr0XcTyTuR7EXX0H3ANShpQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "a08d6979dd7c82c4cef0dcc6ac45ab16051c1169",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
+ "lastModified": 1677995890,
+ "narHash": "sha256-eOnCn0o3I6LP48fAi8xWFcn49V2rL7oX5jCtJTeN1LI=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "a1240f6b4a0bcc84fc48008b396a140d9f3638f6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_4": {
+ "locked": {
+ "lastModified": 1670461440,
+ "narHash": "sha256-jy1LB8HOMKGJEGXgzFRLDU1CBGL0/LlkolgnqIsF0D8=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "04a75b2eecc0acf6239acf9dd04485ff8d14f425",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-22.11-small",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_5": {
+ "locked": {
+ "lastModified": 1678898370,
+ "narHash": "sha256-xTICr1j+uat5hk9FyuPOFGxpWHdJRibwZC+ATi0RbtE=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "ac718d02867a84b42522a0ece52d841188208f2c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "parts": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "helix",
+ "nci",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1675933616,
+ "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "47478a4a003e745402acf63be7f9a092d51b83d7",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "agenix": "agenix",
+ "devshell": "devshell",
+ "eww": "eww",
+ "flake-parts": "flake-parts",
+ "fu": "fu",
+ "helix": "helix",
+ "hm": "hm",
+ "hyprland": "hyprland",
+ "hyprland-contrib": "hyprland-contrib",
+ "kmonad": "kmonad",
+ "nix-gaming": "nix-gaming",
+ "nix-super": "nix-super",
+ "nix-xilinx": "nix-xilinx",
+ "nixpkgs": "nixpkgs_5",
+ "rust-overlay": "rust-overlay_2",
+ "spicetify-nix": "spicetify-nix"
+ }
+ },
+ "rust-overlay": {
+ "inputs": {
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": [
+ "helix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1677292251,
+ "narHash": "sha256-D+6q5Z2MQn3UFJtqsM5/AvVHi3NXKZTIMZt1JGq/spA=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "34cdbf6ad480ce13a6a526f57d8b9e609f3d65dc",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
+ "rust-overlay_2": {
+ "inputs": {
+ "flake-utils": [
+ "fu"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1679106165,
+ "narHash": "sha256-03Opt2yu4E/AIFjvlgib0/nhMn6B4B/t/nvwS2bzOGw=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "7313c06ac334d6262ddfe30a38b3abc3da6bd565",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
+ "spicetify-nix": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1675736383,
+ "narHash": "sha256-fjQmN3pYGstBNHCFdmPzDf1/Dt04v3b7+/w4cQ4gkKo=",
+ "owner": "the-argus",
+ "repo": "spicetify-nix",
+ "rev": "ec85c2a2f5d1035142b8e383cc77b68bb0b9ebc8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "the-argus",
+ "repo": "spicetify-nix",
+ "type": "github"
+ }
+ },
+ "utils": {
+ "locked": {
+ "lastModified": 1676283394,
+ "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "wlroots": {
+ "flake": false,
+ "locked": {
+ "host": "gitlab.freedesktop.org",
+ "lastModified": 1677789111,
+ "narHash": "sha256-dWrk+Q3bLdtFe5rkyaAKWCQJCeE/KFNllcu1DvBC38c=",
+ "owner": "wlroots",
+ "repo": "wlroots",
+ "rev": "5ae17de23f5fd9bb252a698f3771c840280e2c05",
+ "type": "gitlab"
+ },
+ "original": {
+ "host": "gitlab.freedesktop.org",
+ "owner": "wlroots",
+ "repo": "wlroots",
+ "type": "gitlab"
+ }
+ },
+ "xdph": {
+ "inputs": {
+ "hyprland-protocols": [
+ "hyprland",
+ "hyprland-protocols"
+ ],
+ "nixpkgs": [
+ "hyprland",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1673116118,
+ "narHash": "sha256-eR0yDSkR2XYMesfdRWJs25kAdXET2mbNNHu5t+KUcKA=",
+ "owner": "hyprwm",
+ "repo": "xdg-desktop-portal-hyprland",
+ "rev": "d479c846531fd0e1d2357c9588b8310a2b859ef2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hyprwm",
+ "repo": "xdg-desktop-portal-hyprland",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/foreign/dotfiles/flake.nix b/foreign/dotfiles/flake.nix
new file mode 100644
index 0000000..18a5944
--- /dev/null
+++ b/foreign/dotfiles/flake.nix
@@ -0,0 +1,111 @@
+{
+ description = "fufexan's NixOS and Home-Manager flake";
+
+ outputs = inputs:
+ inputs.flake-parts.lib.mkFlake {inherit inputs;} {
+ systems = ["x86_64-linux"];
+
+ imports = [
+ ./home/profiles
+ ./hosts
+ ./modules
+ ./pkgs
+ ./lib
+ {config._module.args._inputs = inputs // {inherit (inputs) self;};}
+ ];
+
+ perSystem = {
+ config,
+ inputs',
+ pkgs,
+ system,
+ ...
+ }: {
+ imports = [
+ {
+ _module.args.pkgs = inputs.self.legacyPackages.${system};
+ }
+ ];
+
+ devShells.default = inputs'.devshell.legacyPackages.mkShell {
+ packages = [
+ pkgs.alejandra
+ pkgs.git
+ config.packages.repl
+ ];
+ name = "dots";
+ };
+
+ formatter = pkgs.alejandra;
+ };
+ };
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ flake-parts = {
+ url = "github:hercules-ci/flake-parts";
+ inputs.nixpkgs-lib.follows = "nixpkgs";
+ };
+
+ agenix = {
+ url = "github:ryantm/agenix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ devshell = {
+ url = "github:numtide/devshell";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ eww = {
+ url = "github:elkowar/eww";
+ inputs.nixpkgs.follows = "nixpkgs";
+ inputs.rust-overlay.follows = "rust-overlay";
+ };
+
+ fu.url = "github:numtide/flake-utils";
+
+ helix = {
+ url = "github:SoraTenshi/helix/daily-driver";
+ inputs.parts.follows = "flake-parts";
+ };
+
+ hm = {
+ url = "github:nix-community/home-manager";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ hyprland.url = "github:hyprwm/Hyprland";
+
+ hyprland-contrib = {
+ url = "github:hyprwm/contrib";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ kmonad = {
+ url = "github:kmonad/kmonad?dir=nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ nix-gaming.url = "github:fufexan/nix-gaming";
+
+ nix-super.url = "github:privatevoid-net/nix-super";
+
+ nix-xilinx = {
+ url = "gitlab:doronbehar/nix-xilinx";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ rust-overlay = {
+ url = "github:oxalica/rust-overlay";
+ inputs.nixpkgs.follows = "nixpkgs";
+ inputs.flake-utils.follows = "fu";
+ };
+
+ spicetify-nix = {
+ url = "github:the-argus/spicetify-nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/README.md b/foreign/dotfiles/home/README.md
new file mode 100644
index 0000000..84d13f5
--- /dev/null
+++ b/foreign/dotfiles/home/README.md
@@ -0,0 +1,15 @@
+# Home config
+
+Home-Manager configurations for different hosts.
+
+Name | Description
+--------------- | -----------
+`default.nix` | Home-Manager specific configuration
+`editors` | Helix & Neovim
+`programs` | Programs
+`shell` | Zsh, Nix options, etc.
+`terminals` | Terminal configs
+`wayland` | Wayland-specific options, including Sway and Waybar configs
+
+`profiles` is a special dir where `homeConfigurations` are set up. They're
+basically the entrypoints of the configs.
diff --git a/foreign/dotfiles/home/default.nix b/foreign/dotfiles/home/default.nix
new file mode 100644
index 0000000..d3a469b
--- /dev/null
+++ b/foreign/dotfiles/home/default.nix
@@ -0,0 +1,18 @@
+{
+ home = {
+ username = "mihai";
+ homeDirectory = "/home/mihai";
+ stateVersion = "20.09";
+ extraOutputsToInstall = ["doc" "devdoc"];
+ };
+
+ # disable manuals as nmd fails to build often
+ manual = {
+ html.enable = false;
+ json.enable = false;
+ manpages.enable = false;
+ };
+
+ # let HM manage itself when in standalone mode
+ programs.home-manager.enable = true;
+}
diff --git a/foreign/dotfiles/home/editors/helix/default.nix b/foreign/dotfiles/home/editors/helix/default.nix
new file mode 100644
index 0000000..7489b14
--- /dev/null
+++ b/foreign/dotfiles/home/editors/helix/default.nix
@@ -0,0 +1,44 @@
+{
+ inputs,
+ pkgs,
+ ...
+} @ args: {
+ home.packages = [pkgs.shellcheck];
+
+ programs.helix = {
+ enable = true;
+ package = inputs.helix.packages.${pkgs.hostPlatform.system}.default;
+
+ languages = import ./languages.nix args;
+
+ settings = {
+ theme = "catppuccin_mocha";
+ editor = {
+ true-color = true;
+ color-modes = true;
+ cursorline = true;
+ cursor-shape = {
+ insert = "bar";
+ normal = "block";
+ select = "underline";
+ };
+ indent-guides = {
+ render = true;
+ rainbow-option = "dim";
+ };
+ rainbow-brackets = true;
+ statusline.center = ["position-percentage"];
+ whitespace.characters = {
+ newline = "↴";
+ tab = "⇥";
+ };
+ };
+
+ keys.normal.space.u = {
+ f = ":format"; # format using LSP formatter
+ w = ":set whitespace.render all";
+ W = ":set whitespace.render none";
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/editors/helix/languages.nix b/foreign/dotfiles/home/editors/helix/languages.nix
new file mode 100644
index 0000000..6f40bee
--- /dev/null
+++ b/foreign/dotfiles/home/editors/helix/languages.nix
@@ -0,0 +1,47 @@
+{pkgs, ...}:
+with pkgs; [
+ {
+ name = "bash";
+ language-server = {
+ command = "${nodePackages.bash-language-server}/bin/bash-language-server";
+ args = ["start"];
+ };
+ auto-format = true;
+ formatter = {
+ command = "${shfmt}/bin/shfmt";
+ args = ["-i" "2" "-"];
+ };
+ }
+ {
+ name = "cpp";
+ language-server = {
+ command = "${clang-tools}/bin/clangd";
+ clangd.fallbackFlags = ["-std=c++2b"];
+ };
+ }
+ {
+ name = "nix";
+ language-server = {command = lib.getExe nil;};
+ config.nil.formatting.command = ["alejandra" "-q"];
+ }
+ {
+ name = "clojure";
+ scope = "source.clojure";
+ injection-regex = "(clojure|clj|edn|boot|yuck)";
+ file-types = ["clj" "cljs" "cljc" "clje" "cljr" "cljx" "edn" "boot" "yuck"];
+ roots = ["project.clj" "build.boot" "deps.edn" "shadow-cljs.edn"];
+ comment-token = ";";
+ language-server = {command = "clojure-lsp";};
+ indent = {
+ tab-width = 2;
+ unit = " ";
+ };
+ }
+ {
+ name = "css";
+ language-server = {
+ command = "${nodePackages.vscode-css-languageserver-bin}/bin/css-languageserver";
+ args = ["--stdio"];
+ };
+ }
+]
diff --git a/foreign/dotfiles/home/editors/neovim/config/init.lua b/foreign/dotfiles/home/editors/neovim/config/init.lua
new file mode 100644
index 0000000..cc30859
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/init.lua
@@ -0,0 +1,14 @@
+vim.opt.expandtab = true
+vim.opt.hidden = true
+vim.opt.incsearch = true
+vim.opt.mouse = "a"
+vim.opt.number = true
+vim.opt.shiftwidth = 2
+vim.opt.splitbelow = true
+vim.opt.splitright = true
+vim.opt.signcolumn = "yes:3"
+vim.opt.tabstop = 2
+vim.opt.timeoutlen = 0
+vim.wo.wrap = false
+vim.opt.exrc = true
+vim.cmd("syntax on")
\ No newline at end of file
diff --git a/foreign/dotfiles/home/editors/neovim/config/lspconfig.lua b/foreign/dotfiles/home/editors/neovim/config/lspconfig.lua
new file mode 100644
index 0000000..428282b
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/lspconfig.lua
@@ -0,0 +1,28 @@
+local lspc = require'lspconfig'
+
+lspc.nil_ls.setup{}
+lspc.clangd.setup{}
+
+local buf_map = function(bufnr, mode, lhs, rhs, opts)
+ vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts or {
+ silent = true,
+ })
+end
+
+lspc.tsserver.setup({
+ on_attach = function(client, bufnr)
+ client.resolved_capabilities.document_formatting = false
+ client.resolved_capabilities.document_range_formatting = false
+
+ local ts_utils = require("nvim-lsp-ts-utils")
+ ts_utils.setup({})
+ ts_utils.setup_client(client)
+
+ buf_map(bufnr, "n", "gs", ":TSLspOrganize")
+ buf_map(bufnr, "n", "gi", ":TSLspRenameFile")
+ buf_map(bufnr, "n", "go", ":TSLspImportAll")
+
+ on_attach(client, bufnr)
+ end,
+})
+
diff --git a/foreign/dotfiles/home/editors/neovim/config/nvim-cmp.lua b/foreign/dotfiles/home/editors/neovim/config/nvim-cmp.lua
new file mode 100644
index 0000000..13bff54
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/nvim-cmp.lua
@@ -0,0 +1,73 @@
+local has_words_before = function()
+ local line, col = unpack(vim.api.nvim_win_get_cursor(0))
+ return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
+end
+
+local feedkey = function(key, mode)
+ vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true)
+end
+
+local cmp = require("cmp")
+local lspkind = require("lspkind")
+
+cmp.setup({
+ sources = {
+ { name = "nvim_lsp" },
+ { name = "cmp_tabnine" },
+ { name = "treesitter" },
+ { name = "buffer" },
+ { name = "path" },
+ { name = "vsnip" },
+ -- { name = "copilot" },
+ },
+
+ snippet = {
+ expand = function(args)
+ vim.fn["vsnip#anonymous"](args.body)
+ end,
+ },
+
+ formatting = {
+ format = lspkind.cmp_format({
+ with_text = true,
+ menu = {
+ buffer = "[Buf]",
+ nvim_lsp = "[LSP]",
+ nvim_lua = "[Lua]",
+ latex_symbols = "[Latex]",
+ treesitter = "[TS]",
+ cmp_tabnine = "[TN]",
+ vsnip = "[Snip]",
+ },
+ }),
+ },
+
+ mapping = {
+ [""] = cmp.mapping.confirm({ select = true }),
+ [""] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ elseif vim.fn["vsnip#available"](1) == 1 then
+ feedkey("(vsnip-expand-or-jump)", "")
+ elseif has_words_before() then
+ cmp.complete()
+ else
+ fallback()
+ end
+ end, {
+ "i",
+ "s",
+ }),
+
+ [""] = cmp.mapping(function()
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif vim.fn["vsnip#jumpable"](-1) == 1 then
+ feedkey("(vsnip-jump-prev)", "")
+ end
+ end, {
+ "i",
+ "s",
+ }),
+ },
+})
diff --git a/foreign/dotfiles/home/editors/neovim/config/theming.lua b/foreign/dotfiles/home/editors/neovim/config/theming.lua
new file mode 100644
index 0000000..091687e
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/theming.lua
@@ -0,0 +1,20 @@
+-- set colorscheme
+vim.cmd 'set termguicolors'
+
+vim.g.catppuccin_flavour = "mocha"
+
+require("catppuccin").setup()
+
+vim.cmd [[colorscheme catppuccin]]
+
+-- enable colorizer
+require'colorizer'.setup()
+
+-- set sign
+vim.cmd 'sign define DiagnosticSignError text= linehl= texthl=DiagnosticSignError numhl='
+vim.cmd 'sign define DiagnosticSignHint text= linehl= texthl=DiagnosticSignHint numhl='
+vim.cmd 'sign define DiagnosticSignInfo text= linehl= texthl=DiagnosticSignInfo numhl='
+vim.cmd 'sign define DiagnosticSignWarn text= linehl= texthl=DiagnosticSignWarn numhl='
+
+-- set lightline theme to horizon
+vim.g.lightline = { colorscheme = "catppuccin" }
diff --git a/foreign/dotfiles/home/editors/neovim/config/treesitter-textobjects.lua b/foreign/dotfiles/home/editors/neovim/config/treesitter-textobjects.lua
new file mode 100644
index 0000000..8f2c6d6
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/treesitter-textobjects.lua
@@ -0,0 +1,14 @@
+require'nvim-treesitter.configs'.setup {
+ textobjects = {
+ select = {
+ enable = true,
+ keymaps = {
+ -- You can use the capture groups defined in textobjects.scm
+ ["af"] = "@function.outer",
+ ["if"] = "@function.inner",
+ ["ac"] = "@class.outer",
+ ["ic"] = "@class.inner",
+ },
+ },
+ },
+}
diff --git a/foreign/dotfiles/home/editors/neovim/config/treesitter.lua b/foreign/dotfiles/home/editors/neovim/config/treesitter.lua
new file mode 100644
index 0000000..1f33ef0
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/treesitter.lua
@@ -0,0 +1,30 @@
+require("nvim-treesitter.configs").setup({
+ highlight = {
+ enable = true,
+ disable = {},
+ },
+ rainbow = {
+ enable = true,
+ extended_mode = true,
+ },
+ autotag = {
+ enable = true,
+ },
+ context_commentstring = {
+ enable = true,
+ },
+ incremental_selection = {
+ enable = true,
+ keymaps = {
+ init_selection = "gnn",
+ node_incremental = "grn",
+ scope_incremental = "grc",
+ node_decremental = "grm",
+ },
+ },
+})
+
+-- breaks highlight
+-- vim.cmd([[set foldmethod=expr]])
+-- vim.cmd([[set foldlevel=10]])
+-- vim.cmd([[set foldexpr=nvim_treesitter#foldexpr()]])
\ No newline at end of file
diff --git a/foreign/dotfiles/home/editors/neovim/config/utils.lua b/foreign/dotfiles/home/editors/neovim/config/utils.lua
new file mode 100644
index 0000000..e1a264c
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/utils.lua
@@ -0,0 +1,29 @@
+-- telescope
+require('telescope').load_extension('fzy_native')
+
+-- null-ls
+local nb = require('null-ls').builtins
+
+require('null-ls').setup({
+ sources = {
+ nb.formatting.alejandra,
+ nb.code_actions.statix,
+ nb.diagnostics.cppcheck,
+ nb.diagnostics.deadnix,
+ nb.diagnostics.statix,
+ nb.diagnostics.eslint,
+ nb.completion.spell,
+ },
+})
+
+require("gitsigns").setup()
+
+-- autopairs
+require('nvim-autopairs').setup{}
+
+-- copy to system clipboard
+vim.api.nvim_set_keymap( 'v', 'y', '"+y', {noremap = true})
+vim.api.nvim_set_keymap( 'n', 'y', ':%+y', {noremap = true})
+
+-- paste from system clipboard
+vim.api.nvim_set_keymap( 'n', 'p', '"+p', {noremap = true})
diff --git a/foreign/dotfiles/home/editors/neovim/config/which-key.lua b/foreign/dotfiles/home/editors/neovim/config/which-key.lua
new file mode 100644
index 0000000..ba3bc32
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/config/which-key.lua
@@ -0,0 +1,39 @@
+vim.g.mapleader = " "
+
+local wk = require("which-key")
+
+wk.setup({})
+
+wk.register({
+ [""] = {
+ b = { "Telescope buffers", "Buffers" },
+ ["/"] = { "Telescope live_grep", "Live Grep" },
+ f = { "Telescope find_files", "Find File" },
+ g = {
+ name = "Git / VCS",
+ i = { "lua require('telescope').extensions.gh.issues()", "Github Issues" },
+ p = { "lua require('telescope').extensions.gh.pull_request()", "Github PRs" },
+ b = { "ToggleBlameLine", "Toggle BlameLine" },
+ c = { "Neogit commit", "Commit" },
+ s = { "Neogit kind=split", "Staging" },
+ },
+ a = { "lua require('telescope.builtin').lsp_code_actions()", "Code Actions" },
+ d = { "lua require('telescope.builtin').lsp_document_diagnostics()", "LSP Diagnostics" },
+ k = { "lua vim.lsp.buf.signature_help()", "Signature Help" },
+ l = {
+ name = "LSP",
+ f = { "lua vim.lsp.buf.formatting_sync()", "Format file"},
+ q = { "lua vim.lsp.diagnostic.set_loclist()", "Set Loclist" },
+ e = { "lua vim.lsp.diagnostic.show_line_diagnostics()", "Show Line Diagnostics" },
+ },
+ p = { "\"+p", "Paste from clipboard" },
+ P = { "\"+P", "Paste from clipboard before cursor" },
+ y = { "\"+y", "Yank to clipboard" },
+ },
+ g = {
+ l = { "$", "Line end" },
+ h = { "0", "Line start" },
+ s = { "^", "First non-blank in line" },
+ e = { "G", "Bottom" },
+ },
+})
diff --git a/foreign/dotfiles/home/editors/neovim/default.nix b/foreign/dotfiles/home/editors/neovim/default.nix
new file mode 100644
index 0000000..ba0a8f5
--- /dev/null
+++ b/foreign/dotfiles/home/editors/neovim/default.nix
@@ -0,0 +1,62 @@
+{pkgs, ...}: {
+ programs.neovim = {
+ enable = true;
+
+ vimAlias = true;
+ viAlias = true;
+ vimdiffAlias = true;
+
+ plugins = with pkgs.vimPlugins; [
+ catppuccin-nvim
+ cmp-buffer
+ cmp-nvim-lsp
+ cmp-path
+ cmp-spell
+ cmp-treesitter
+ cmp-vsnip
+ friendly-snippets
+ gitsigns-nvim
+ lightline-vim
+ lspkind-nvim
+ neogit
+ null-ls-nvim
+ nvim-autopairs
+ nvim-cmp
+ nvim-colorizer-lua
+ nvim-lspconfig
+ nvim-tree-lua
+ nvim-ts-rainbow
+ (nvim-treesitter.withPlugins (_: pkgs.tree-sitter.allGrammars))
+ plenary-nvim
+ telescope-fzy-native-nvim
+ telescope-nvim
+ vim-floaterm
+ vim-sneak
+ vim-vsnip
+ which-key-nvim
+ ];
+
+ extraPackages = with pkgs; [gcc ripgrep fd];
+
+ extraConfig = let
+ luaRequire = module:
+ builtins.readFile (builtins.toString
+ ./config
+ + "/${module}.lua");
+ luaConfig = builtins.concatStringsSep "\n" (map luaRequire [
+ "init"
+ "lspconfig"
+ "nvim-cmp"
+ "theming"
+ "treesitter"
+ "treesitter-textobjects"
+ "utils"
+ "which-key"
+ ]);
+ in ''
+ lua <<
+ ${luaConfig}
+
+ '';
+ };
+}
diff --git a/foreign/dotfiles/home/profiles/default.nix b/foreign/dotfiles/home/profiles/default.nix
new file mode 100644
index 0000000..cfbcfe9
--- /dev/null
+++ b/foreign/dotfiles/home/profiles/default.nix
@@ -0,0 +1,44 @@
+{
+ inputs,
+ withSystem,
+ module_args,
+ ...
+}: let
+ sharedModules = [
+ ../.
+ ../shell
+ module_args
+ ];
+
+ homeImports = {
+ "mihai@io" =
+ [
+ ./io
+ inputs.spicetify-nix.homeManagerModule
+ inputs.hyprland.homeManagerModules.default
+ ]
+ ++ sharedModules;
+ server = sharedModules ++ [./server];
+ };
+
+ inherit (inputs.hm.lib) homeManagerConfiguration;
+in {
+ imports = [
+ {_module.args = {inherit homeImports;};}
+ ];
+
+ flake = {
+ homeConfigurations = withSystem "x86_64-linux" ({pkgs, ...}: {
+ "mihai@io" = homeManagerConfiguration {
+ modules = homeImports."mihai@io" ++ module_args;
+ inherit pkgs;
+ };
+ server = homeManagerConfiguration {
+ modules = homeImports.server ++ module_args;
+ inherit pkgs;
+ };
+ });
+
+ homeManagerModules.eww-hyprland = import ./programs/eww;
+ };
+}
diff --git a/foreign/dotfiles/home/profiles/io/default.nix b/foreign/dotfiles/home/profiles/io/default.nix
new file mode 100644
index 0000000..b798f79
--- /dev/null
+++ b/foreign/dotfiles/home/profiles/io/default.nix
@@ -0,0 +1,73 @@
+{
+ imports = [
+ ../../editors/helix
+ ../../editors/neovim
+ ../../programs
+ ../../programs/games.nix
+ ../../programs/dunst.nix
+ ../../wayland
+ ../../terminals/alacritty.nix
+ ../../terminals/wezterm.nix
+ ];
+
+ services = {
+ kanshi = {
+ # use 1.6 scaling: 2560 : 1.6 = 1600, exact division. good for offsets
+ # restart eww every time because it won't expand/contract automatically
+ enable = true;
+ profiles = {
+ undocked = {
+ outputs = [
+ {
+ criteria = "eDP-1";
+ position = "0,0";
+ }
+ ];
+ };
+ docked-all = {
+ outputs = [
+ {
+ criteria = "eDP-1";
+ position = "1366,0";
+ }
+ {
+ criteria = "DP-1";
+ position = "0,0";
+ }
+ {
+ criteria = "DP-2";
+ position = "1600,0";
+ }
+ ];
+ };
+
+ docked1 = {
+ outputs = [
+ {
+ criteria = "eDP-1";
+ position = "1366,0";
+ }
+ {
+ criteria = "DP-1";
+ position = "0,0";
+ }
+ ];
+ };
+
+ docked2 = {
+ outputs = [
+ {
+ criteria = "eDP-1";
+ position = "1366,0";
+ }
+ {
+ criteria = "DP-2";
+ position = "0,0";
+ }
+ ];
+ };
+ };
+ systemdTarget = "graphical-session.target";
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/profiles/server/default.nix b/foreign/dotfiles/home/profiles/server/default.nix
new file mode 100644
index 0000000..786b2b2
--- /dev/null
+++ b/foreign/dotfiles/home/profiles/server/default.nix
@@ -0,0 +1,3 @@
+{
+ programs.helix.enable = true;
+}
diff --git a/foreign/dotfiles/home/programs/cinny.nix b/foreign/dotfiles/home/programs/cinny.nix
new file mode 100644
index 0000000..79f7801
--- /dev/null
+++ b/foreign/dotfiles/home/programs/cinny.nix
@@ -0,0 +1,13 @@
+{pkgs, ...}: {
+ # use Cinny Matrix client
+ # create systemd service that serves it on localhost:9999
+ systemd.user.services.cinny = {
+ Unit.Description = "Cinny Service";
+ Service = {
+ Type = "simple";
+ ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${pkgs.cinny} --addr 127.0.0.1 --port 9999";
+ TimeoutStopSec = 5;
+ };
+ Install.WantedBy = ["default.target"];
+ };
+}
diff --git a/foreign/dotfiles/home/programs/default.nix b/foreign/dotfiles/home/programs/default.nix
new file mode 100644
index 0000000..b040fcc
--- /dev/null
+++ b/foreign/dotfiles/home/programs/default.nix
@@ -0,0 +1,57 @@
+{
+ pkgs,
+ config,
+ ...
+}: {
+ imports = [
+ ../shell/nix.nix
+ ./cinny.nix
+ ./files
+ ./media.nix
+ ./git.nix
+ ./gtk.nix
+ ./packages.nix
+ ./qt.nix
+ ./spicetify.nix
+ ./xdg.nix
+ ./zathura.nix
+ ];
+
+ programs = {
+ chromium = {
+ enable = true;
+ commandLineArgs = ["--enable-features=TouchpadOverscrollHistoryNavigation"];
+ extensions = [
+ {id = "cjpalhdlnbpafiamejdnhcphjbkeiagm";}
+ {id = "bkkmolkhemgaeaeggcmfbghljjjoofoh";}
+ ];
+ };
+
+ firefox = {
+ enable = true;
+ profiles.mihai = {};
+ };
+
+ gpg = {
+ enable = true;
+ homedir = "${config.xdg.dataHome}/gnupg";
+ };
+
+ password-store = {
+ enable = true;
+ package = pkgs.pass.withExtensions (exts: [exts.pass-otp]);
+ settings.PASSWORD_STORE_DIR = "${config.xdg.dataHome}/password-store";
+ };
+ };
+
+ services = {
+ gpg-agent = {
+ enable = true;
+ enableSshSupport = true;
+ pinentryFlavor = "gnome3";
+ sshKeys = ["73D1C4271E8C508E1E55259660C94BE828B07738"];
+ };
+
+ syncthing.enable = true;
+ };
+}
diff --git a/foreign/dotfiles/home/programs/dunst.nix b/foreign/dotfiles/home/programs/dunst.nix
new file mode 100644
index 0000000..49c2f5e
--- /dev/null
+++ b/foreign/dotfiles/home/programs/dunst.nix
@@ -0,0 +1,58 @@
+{
+ pkgs,
+ default,
+ ...
+}: {
+ # notification daemon
+ services.dunst = {
+ enable = true;
+ iconTheme = {
+ name = "Papirus-Dark";
+ package = pkgs.papirus-icon-theme;
+ };
+ settings = {
+ global = {
+ alignment = "center";
+ corner_radius = 16;
+ follow = "mouse";
+ font = "Roboto 10";
+ format = "%s\\n%b";
+ frame_width = 1;
+ offset = "5x5";
+ horizontal_padding = 8;
+ icon_position = "left";
+ indicate_hidden = "yes";
+ markup = "yes";
+ max_icon_size = 64;
+ mouse_left_click = "do_action";
+ mouse_middle_click = "close_all";
+ mouse_right_click = "close_current";
+ padding = 8;
+ plain_text = "no";
+ separator_color = "auto";
+ separator_height = 1;
+ show_indicators = false;
+ shrink = "no";
+ word_wrap = "yes";
+ };
+
+ fullscreen_delay_everything = {fullscreen = "delay";};
+
+ urgency_critical = {
+ background = default.xcolors.bg;
+ foreground = default.xcolors.fg;
+ frame_color = default.xcolors.red;
+ };
+ urgency_low = {
+ background = default.xcolors.bg;
+ foreground = default.xcolors.fg;
+ frame_color = default.xcolors.blue;
+ };
+ urgency_normal = {
+ background = default.xcolors.bg;
+ foreground = default.xcolors.fg;
+ frame_color = default.xcolors.green;
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/programs/eww/README.md b/foreign/dotfiles/home/programs/eww/README.md
new file mode 100644
index 0000000..fb27f74
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/README.md
@@ -0,0 +1,68 @@
+# Eww configuration
+
+This configuration aims to provide a fully working shell replacement for
+compositors/window managers. Features constantly get added and existing ones
+get improved.
+
+## 🗃️ Components
+
+The same daemon runs multiple windows which interact with each other:
+
+### bar
+
+
+
+### music window
+
+
+
+### calendar
+
+
+
+### system info
+
+
+
+## ❔ Usage
+
+### Home Manager
+
+If you use Home Manager, installing is as simple as adding my flake to your
+inputs, passing `inputs` to `extraSpecialArgs` and importing the relevant
+module:
+```nix
+{inputs, ...}: {
+ imports = [inputs.fufexan.homeManagerModules.eww-hyprland];
+
+ programs.eww-hyprland = {
+ enable = true;
+
+ # default package
+ package = pkgs.eww-wayland;
+
+ # if you want to change colors
+ colors = builtins.readFile ./macchiato.scss;
+
+ # set to true to reload on change
+ autoReload = false;
+ };
+}
+```
+
+Make sure to also add the fonts listed below.
+
+### Other distros
+
+To quickly install this config, grab all the files in this directory and put
+them in `~/.config/eww`. Then run `eww daemon` and `eww open bar`. Enjoy!
+
+Dependencies:
+- Icon fonts: `material-symbols-outline` (any variation can be used as long as you change the `font-family` property of `.icon`)
+- Text font: [Jost](https://fonts.google.com/specimen/Jost)
+- Script deps: everything in `default.nix`'s `dependencies` list.
+
+## 🎨 Theme
+
+The theme colors can be changed in `css/_colors.scss`. Currently the theme used
+is [Catppuccin Mocha](https://github.com/catppuccin/catppuccin).
diff --git a/foreign/dotfiles/home/programs/eww/css/_calendar.scss b/foreign/dotfiles/home/programs/eww/css/_calendar.scss
new file mode 100644
index 0000000..6e5a1e1
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_calendar.scss
@@ -0,0 +1,31 @@
+.calendar-win {
+ @include window;
+ background-color: $bg;
+ color: $fg;
+ padding: .2em;
+}
+
+calendar {
+ padding: 5px;
+
+ :selected {
+ color: $magenta;
+ }
+
+ .header {
+ color: $black;
+ }
+
+ .highlight {
+ color: $red;
+ font-weight: bold;
+ }
+
+ .button {
+ color: $blue;
+ }
+
+ :indeterminate {
+ color: $green;
+ }
+}
diff --git a/foreign/dotfiles/home/programs/eww/css/_colors.scss b/foreign/dotfiles/home/programs/eww/css/_colors.scss
new file mode 100644
index 0000000..f899781
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_colors.scss
@@ -0,0 +1,35 @@
+$rosewater: #f5e0dc;
+$flamingo: #f2cdcd;
+$pink: #f5c2e7;
+$mauve: #cba6f7;
+$red: #f38ba8;
+$maroon: #eba0ac;
+$peach: #fab387;
+$yellow: #f9e2af;
+$green: #a6e3a1;
+$teal: #94e2d5;
+$sky: #89dceb;
+$sapphire: #74c7ec;
+$blue: #89b4fa;
+$lavender: #b4befe;
+
+$text: #cdd6f4;
+$subtext1: #bac2de;
+$subtext0: #a6adc8;
+$overlay2: #9399b2;
+$overlay1: #7f849c;
+$overlay0: #6c7086;
+
+$surface2: #585b70;
+$surface1: #45475a;
+$surface0: #313244;
+
+$base: #1e1e2e;
+$mantle: #181825;
+$crust: #11111b;
+
+$fg: $text;
+$bg: rgba(30, 30, 46, 0.6);
+$bg1: rgba(49, 50, 68, 0.6);
+$border: #28283d;
+$shadow: $crust;
diff --git a/foreign/dotfiles/home/programs/eww/css/_music.scss b/foreign/dotfiles/home/programs/eww/css/_music.scss
new file mode 100644
index 0000000..b3d0ee4
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_music.scss
@@ -0,0 +1,66 @@
+.song-cover-art {
+ @include rounding;
+ background-position: center;
+ background-size: cover;
+ margin: 4px 5px 4px 0;
+ min-height: 24px;
+ min-width: 24px;
+}
+
+.music-window {
+ @include window;
+ background-color: $bg;
+ border: 1px solid $border;
+ color: $fg;
+}
+
+.music-cover-art {
+ background-position: center;
+ background-size: cover;
+ border-radius: 8px;
+ margin: 1em;
+ min-height: 170px;
+ min-width: 170px;
+}
+
+.music-box {
+ margin: 1rem 1rem 1rem 0;
+}
+
+.music-title {
+ font-size: 1.1rem;
+ font-weight: bold;
+}
+
+.music-artist {
+ color: $white_a;
+}
+
+.music-button label {
+ color: $white_a;
+ font-size: 2rem;
+}
+
+.music-time {
+ color: $white_a;
+ margin: 0 1rem;
+}
+
+.music-bar scale {
+ highlight {
+ background-image: linear-gradient(to right, $cyan, $blue_a);
+ border-radius: 24px;
+ }
+
+ trough {
+ background-color: $bg1;
+ border-radius: 24px;
+ margin-top: 0;
+ min-height: 10px;
+ min-width: 170px;
+ }
+}
+
+.playctl {
+ color: $yellow;
+}
diff --git a/foreign/dotfiles/home/programs/eww/css/_osd.scss b/foreign/dotfiles/home/programs/eww/css/_osd.scss
new file mode 100644
index 0000000..ab605a2
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_osd.scss
@@ -0,0 +1,36 @@
+.osd-part {
+ @include window;
+ background: $bg;
+ margin: 0 .5rem .5rem;
+
+ label {
+ color: $text;
+ font-size: 2rem;
+ margin: 0 .1rem 0 .2rem;
+ }
+
+ scale {
+ margin: -.2rem 0;
+ }
+
+ trough {
+ @include rounding;
+ background-color: $bg1;
+ margin: 1rem 0 .5rem;
+ min-height: 10rem;
+ min-width: .7rem;
+
+ highlight {
+ @include rounding;
+ min-width: 0;
+ }
+ }
+}
+
+.osd-volume highlight {
+ background-image: linear-gradient(to top, $cyan, $blue_a);
+}
+
+.osd-brightness highlight {
+ background-image: linear-gradient(to top, $yellow, $yellow_a);
+}
diff --git a/foreign/dotfiles/home/programs/eww/css/_sidebar.scss b/foreign/dotfiles/home/programs/eww/css/_sidebar.scss
new file mode 100644
index 0000000..58af83f
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_sidebar.scss
@@ -0,0 +1,127 @@
+.system-menu-box {
+ @include window;
+ background-color: $bg;
+ color: $text;
+}
+
+.separator {
+ font-size: 1rem;
+}
+
+.top-row {
+ margin: 1rem 1.5rem 0;
+
+ .time { font-size: 2rem; }
+
+ .date-box {
+ margin: 0 1rem;
+
+ label { font-size: 1.1rem; }
+
+ .date {
+ background: unset;
+ margin: 0 .5rem 0 0;
+ padding: 0;
+ }
+ }
+}
+
+.system-row {
+ margin: .5rem .7rem;
+
+ .airplane-box button {
+ padding: 1rem 3rem;
+ }
+
+ label {
+ font-size: 1rem;
+ margin: 0 .1rem;
+ }
+}
+
+.element {
+ @include rounding;
+ background-color: $black;
+ margin: .3rem;
+
+ button {
+ @include rounding;
+ padding: 1rem;
+
+ label {
+ font-size: 1.5rem;
+ }
+
+ &:hover {
+ background-color: $cyan_a;
+ }
+ }
+}
+
+.sliders {
+ @include rounding;
+ background-color: $black;
+ margin: .5rem 1rem;
+ padding: .6rem 1rem;
+
+ scale {
+ margin-right: -1rem;
+ min-width: 21.5rem;
+
+ trough { margin-right: 0; }
+ }
+
+ box { margin: .2rem 0; }
+ label { font-size: 1.2rem; }
+}
+
+.volume-slider-box,
+.brightness-slider-box {
+ trough { background-color: $base; }
+}
+
+.volume-bar highlight {
+ @include rounding;
+ background-image: linear-gradient(to right, $cyan, $blue_a);
+}
+
+.brightness-slider-box scale highlight {
+ @include rounding;
+ background-image: linear-gradient(to right, $yellow, $yellow_a);
+}
+
+.bottom-row {
+ margin: .5rem 1rem;
+
+ .battery-icon { font-size: 2rem; }
+ .battery-wattage { color: $magenta; }
+
+ .battery-status {
+ color: $green;
+ margin: 0 .5rem;
+ }
+
+ button {
+ background-color: $black;
+ border-radius: 50%;
+ margin-bottom: .1rem;
+ padding: 0 .5rem;
+
+ label { font-size: 1.5rem; }
+ &:hover { background-color: $black_a; }
+ }
+}
+
+.bt-connected {
+ background-color: $blue;
+ color: $red;
+
+ button:hover { background-color: rgba(0, 0, 0, .1); }
+}
+
+.wifi-connected {
+ background-color: $magenta;
+ color: $red;
+
+ button:hover { background-color: rgba(0, 0, 0, .1); }
+}
diff --git a/foreign/dotfiles/home/programs/eww/css/_system.scss b/foreign/dotfiles/home/programs/eww/css/_system.scss
new file mode 100644
index 0000000..b59736a
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_system.scss
@@ -0,0 +1,80 @@
+* {
+ transition: 1s;
+}
+
+.membar {
+ color: $yellow;
+}
+
+.cpubar {
+ color: $blue;
+}
+
+.batbar {
+ color: $green;
+}
+
+.membar,
+.cpubar,
+.batbar {
+ background-color: $bg1;
+}
+
+.iconmem {
+ color: $yellow;
+}
+
+.iconcpu {
+ color: $blue;
+}
+
+.icon-text {
+ font-size: 3rem;
+ padding: .7rem;
+}
+
+.sys-text-sub {
+ color: $text;
+}
+
+.sys-text-mem,
+.sys-text-cpu {
+ font-size: 1rem;
+ font-weight: bold;
+}
+
+.sys-icon-mem,
+.sys-icon-cpu {
+ font-size: 1.5rem;
+ margin: 1.5rem;
+}
+
+.system-info-box {
+ @include rounding;
+ background-color: $black;
+ margin: .5rem 1rem;
+ padding: .5rem;
+}
+
+.sys-mem,
+.sys-cpu {
+ background-color: $bg;
+}
+
+.sys-icon-mem,
+.sys-text-mem,
+.sys-mem {
+ color: $yellow;
+}
+
+.sys-icon-cpu,
+.sys-text-cpu,
+.sys-cpu {
+ color: $blue;
+}
+
+.sys-box {
+ margin: .3em;
+
+ box { margin-left: 1rem; }
+}
diff --git a/foreign/dotfiles/home/programs/eww/css/_volume.scss b/foreign/dotfiles/home/programs/eww/css/_volume.scss
new file mode 100644
index 0000000..1f340f6
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/css/_volume.scss
@@ -0,0 +1,5 @@
+.vol-icon { color: $green; }
+.volbar highlight {
+ background-image: linear-gradient(to right, $cyan, $blue_a);
+ border-radius: 10px;
+}
diff --git a/foreign/dotfiles/home/programs/eww/default.nix b/foreign/dotfiles/home/programs/eww/default.nix
new file mode 100644
index 0000000..5e2eb52
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/default.nix
@@ -0,0 +1,128 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ dependencies = with pkgs; [
+ config.wayland.windowManager.hyprland.package
+ cfg.package
+ bash
+ bc
+ blueberry
+ bluez
+ coreutils
+ dbus
+ dunst
+ findutils
+ gawk
+ gnome.gnome-control-center
+ gnused
+ gojq
+ imagemagick
+ jaq
+ light
+ networkmanager
+ pavucontrol
+ playerctl
+ procps
+ pulseaudio
+ ripgrep
+ socat
+ udev
+ upower
+ util-linux
+ wget
+ wireplumber
+ wlogout
+ socat
+ fuzzel
+ swaynotificationcenter
+ ];
+
+ reload_script = pkgs.writeShellScript "reload_eww" ''
+ windows=$(eww windows | rg '\*' | tr -d '*')
+
+ systemctl --user restart eww.service
+
+ echo $windows | while read -r w; do
+ eww open $w
+ done
+ '';
+
+ cfg = config.programs.eww-hyprland;
+in {
+ options.programs.eww-hyprland = {
+ enable = lib.mkEnableOption "eww Hyprland config";
+
+ package = lib.mkOption {
+ type = with lib.types; nullOr package;
+ default = pkgs.eww-wayland;
+ defaultText = lib.literalExpression "pkgs.eww-wayland";
+ description = "Eww package to use.";
+ };
+
+ autoReload = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ defaultText = lib.literalExpression "false";
+ description = "Whether to restart the eww daemon and windows on change.";
+ };
+
+ colors = lib.mkOption {
+ type = with lib.types; nullOr lines;
+ default = null;
+ defaultText = lib.literalExpression "null";
+ description = ''
+ SCSS file with colors defined in the same way as Catppuccin colors are,
+ to be used by eww.
+
+ Defaults to Catppuccin Mocha.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = [cfg.package];
+
+ # remove nix files
+ xdg.configFile."eww" = {
+ source = lib.cleanSourceWith {
+ filter = name: _type: let
+ baseName = baseNameOf (toString name);
+ in
+ !(lib.hasSuffix ".nix" baseName) && !(baseName == "_colors.scss");
+ src = lib.cleanSource ./.;
+ };
+
+ # links each file individually, which lets us insert the colors file separately
+ recursive = true;
+
+ onChange =
+ if cfg.autoReload
+ then reload_script.outPath
+ else "";
+ };
+
+ # colors file
+ xdg.configFile."eww/css/_colors.scss".text =
+ if cfg.colors != null
+ then cfg.colors
+ else (builtins.readFile ./css/_colors.scss);
+
+ systemd.user.services.eww = {
+ Unit = {
+ Description = "Eww Daemon";
+ # not yet implemented
+ # PartOf = ["tray.target"];
+ PartOf = ["graphical-session.target"];
+ };
+ Service = {
+ Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath dependencies}";
+ ExecStart = "${cfg.package}/bin/eww daemon --no-daemonize";
+ Restart = "on-failure";
+ };
+ Install.WantedBy = ["graphical-session.target"];
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/programs/eww/eww.scss b/foreign/dotfiles/home/programs/eww/eww.scss
new file mode 100644
index 0000000..e809818
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/eww.scss
@@ -0,0 +1,93 @@
+@import 'css/colors';
+
+@mixin rounding {
+ border-radius: 16px;
+}
+
+@mixin window {
+ border: 1px solid $border;
+ box-shadow: 0 2px 3px $shadow;
+ margin: 5px 5px 0px;
+ @include rounding;
+}
+
+* {
+ all: unset;
+ font-family: "Montreux Classic", "Jost *", Roboto, sans-serif;
+ transition: 200ms ease;
+}
+
+@import 'css/calendar';
+@import 'css/music';
+@import 'css/osd';
+@import 'css/sidebar';
+@import 'css/system';
+@import 'css/volume';
+
+.bar {
+ color: $fg;
+ label {
+ font-size: 1.4rem;
+ }
+}
+
+.vis {
+ background-color: $bg;
+ border-radius: 24px;
+ padding: 4px 24px;
+ margin-top: 12px;
+ margin-bottom: 0px;
+ border: 3px solid $outline;
+}
+
+.invis {
+ background-color: rgba(0, 0, 0, 0);
+}
+
+tooltip {
+ background: $bg;
+ border: 1px solid $border;
+ border-radius: 8px;
+
+ label {
+ font-size: 1rem;
+ }
+}
+
+.icon,
+.icon label { font-family: Material Symbols Outlined; }
+
+.module { margin: 0 5px; }
+
+.hour {
+ padding-left: 5px;
+}
+
+.minute {
+ padding-right: .7rem;
+}
+
+.date {
+ background: $bg;
+ color: $yellow;
+
+ label {
+ font-size: 1.2rem;
+ }
+}
+
+.bright-icon { color: $yellow_a; }
+.module-bt { font-size: 1.2rem; }
+
+scale trough {
+ background-color: $bg;
+ border-radius: 24px;
+ margin: 0 1rem;
+ min-height: 10px;
+ min-width: 70px;
+}
+
+.workspaces {
+ margin-left: 10px;
+ button { transition: color 2s ease; }
+}
diff --git a/foreign/dotfiles/home/programs/eww/eww.yuck b/foreign/dotfiles/home/programs/eww/eww.yuck
new file mode 100644
index 0000000..d1b2654
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/eww.yuck
@@ -0,0 +1,56 @@
+(include "./modules/clock.yuck")
+(include "./modules/music.yuck")
+(include "./modules/net.yuck")
+(include "./modules/sys.yuck")
+(include "./modules/variables.yuck")
+(include "./modules/volume.yuck")
+(include "./modules/workspaces.yuck")
+(include "./modules/current_win.yuck")
+
+(include "./windows/calendar.yuck")
+(include "./windows/music.yuck")
+(include "./windows/osd.yuck")
+(include "./windows/system-menu.yuck")
+
+(defwidget left []
+ (box
+ :space-evenly false
+ :halign "start"
+ :class "vis"
+ (workspaces)))
+
+(defwidget right []
+ (box
+ :space-evenly false
+ :halign "end"
+ :class "vis bar"
+ (music-module)
+ (volume-module)
+ (net)
+ (sys)
+ (clock_module)))
+
+(defwidget center []
+ (box
+ :space-evenly false
+ :halign "center"
+ :class "vis bar"
+ (current-win-module)))
+
+(defwidget bar-box []
+ (centerbox
+ :class "invis bar"
+ (left)
+ (center)
+ (right)))
+
+(defwindow bar
+ :monitor 0
+ :geometry (geometry :x "0%"
+ :y "0%"
+ :width "98%"
+ :height "24px"
+ :anchor "top center")
+ :stacking "fg"
+ :exclusive true
+ (bar-box))
diff --git a/foreign/dotfiles/home/programs/eww/modules/bluetooth.yuck b/foreign/dotfiles/home/programs/eww/modules/bluetooth.yuck
new file mode 100644
index 0000000..5b023b8
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/bluetooth.yuck
@@ -0,0 +1,8 @@
+(defwidget bluetooth []
+ (button
+ :class "module-bt module icon"
+ :onclick "blueberry"
+ :onrightclick "scripts/bluetooth toggle"
+ :tooltip "${bluetooth.text} ${bluetooth.battery}"
+ :style "color: ${bluetooth.color};"
+ {bluetooth.icon}))
diff --git a/foreign/dotfiles/home/programs/eww/modules/bright.yuck b/foreign/dotfiles/home/programs/eww/modules/bright.yuck
new file mode 100644
index 0000000..27b1b5f
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/bright.yuck
@@ -0,0 +1,9 @@
+(defwidget bright []
+ (box
+ :class "module"
+ (eventbox
+ :onscroll "echo {} | sed -e 's/up/-U 1/g' -e 's/down/-A 1/g' | xargs light"
+ (label
+ :text {brightness.icon}
+ :class "bright-icon icon"
+ :tooltip "brightness ${round(brightness.percent, 0)}%"))))
diff --git a/foreign/dotfiles/home/programs/eww/modules/clock.yuck b/foreign/dotfiles/home/programs/eww/modules/clock.yuck
new file mode 100644
index 0000000..4b13d9e
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/clock.yuck
@@ -0,0 +1,24 @@
+(defvar date_rev false)
+
+(defwidget clock_module []
+ (eventbox
+ :onhover "${EWW_CMD} update date_rev=true"
+ :onhoverlost "${EWW_CMD} update date_rev=false"
+ (overlay
+ :class "module"
+ (box
+ :space-evenly false
+ (label
+ :text {time.hour}
+ :class "hour")
+ (label
+ :text ":")
+ (label
+ :text {time.minute}
+ :class "minute"))
+ (revealer
+ :reveal date_rev
+ (button
+ :class "date"
+ :onclick "${EWW_CMD} open --toggle calendar"
+ {time.date})))))
diff --git a/foreign/dotfiles/home/programs/eww/modules/current_win.yuck b/foreign/dotfiles/home/programs/eww/modules/current_win.yuck
new file mode 100644
index 0000000..a4142d7
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/current_win.yuck
@@ -0,0 +1,8 @@
+(defwidget current-win-module []
+ (box
+ (button
+ :class "module"
+ {current_win.title}
+ )
+ )
+)
diff --git a/foreign/dotfiles/home/programs/eww/modules/music.yuck b/foreign/dotfiles/home/programs/eww/modules/music.yuck
new file mode 100644
index 0000000..0b7ae88
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/music.yuck
@@ -0,0 +1,28 @@
+(defwidget music-module []
+ (eventbox
+ :onhover "${EWW_CMD} update music_reveal=true"
+ :onhoverlost "${EWW_CMD} update music_reveal=false"
+ (box
+ :class "module"
+ :space-evenly false
+ (box
+ :class "song-cover-art"
+ :style "background-image: url(\"${music.cover}\");")
+ (button
+ :class "module"
+ :onclick "${EWW_CMD} open --toggle music"
+ {music.status == "Stopped" || music.status == "" ? "" :
+ "${music.artist} - ${music.title}"})
+ (box
+ :class "icon"
+ {music.static == "Stopped" || music.status == "" ? "" : music.status == "Playing" ? "" : "" })
+ (revealer
+ :class "playctl"
+ :transition "slideright"
+ :reveal music_reveal
+ :duration "350ms"
+ (box
+ :class "icon"
+ (button :class "song-button" :onclick "playerctl previous" "")
+ (button :class "song-button" :onclick "playerctl play-pause" {music.status == "Playing" ? "" : ""})
+ (button :class "song-button" :onclick "playerctl next" ""))))))
diff --git a/foreign/dotfiles/home/programs/eww/modules/net.yuck b/foreign/dotfiles/home/programs/eww/modules/net.yuck
new file mode 100644
index 0000000..3bfb4f8
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/net.yuck
@@ -0,0 +1,7 @@
+(defwidget net []
+ (button
+ :class "module icon"
+ :onclick "gnome-control-center &"
+ :tooltip {net.essid}
+ :style "color: ${net.color};"
+ {net.icon}))
diff --git a/foreign/dotfiles/home/programs/eww/modules/sys.yuck b/foreign/dotfiles/home/programs/eww/modules/sys.yuck
new file mode 100644
index 0000000..d5403a5
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/sys.yuck
@@ -0,0 +1,32 @@
+(defwidget sys []
+ (box
+ :class "module"
+ :space-evenly false
+ :spacing 5
+ (circular-progress
+ :value "${EWW_CPU.avg}"
+ :class "cpubar"
+ :thickness 3
+ (button
+ :tooltip "using ${round(EWW_CPU.avg,0)}% cpu"
+ :onclick "${EWW_CMD} open --toggle system-menu"
+ (label :class "icon-text" :text "")))
+
+ (circular-progress
+ :value {memory.percent}
+ :class "membar"
+ :thickness 3
+ (button
+ :tooltip "using ${round(memory.percent,0)}% ram"
+ :onclick "${EWW_CMD} open --toggle system-menu"
+ (label :class "icon-text" :text "")))
+
+ (circular-progress
+ :value "${EWW_BATTERY["BAT0"].capacity}"
+ :class "batbar"
+ :style "color: ${battery.color};"
+ :thickness 3
+ (button
+ :tooltip "battery on ${EWW_BATTERY["BAT0"].capacity}%"
+ :onclick "${EWW_CMD} open --toggle system-menu"
+ (label :class "icon-text" :text "")))))
diff --git a/foreign/dotfiles/home/programs/eww/modules/variables.yuck b/foreign/dotfiles/home/programs/eww/modules/variables.yuck
new file mode 100644
index 0000000..6af2136
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/variables.yuck
@@ -0,0 +1,21 @@
+(defvar bright_reveal false)
+(defvar bt_rev false)
+(defvar music_reveal false)
+(defvar net_rev false)
+(defvar time_rev false)
+(defvar vol_reveal false)
+(defvar osd-brightness false)
+(defvar osd-volume false)
+
+(defpoll time :interval "5s" `date +'{"date": "%d/%m", "hour": "%H", "minute": "%M", "day": "%A"}'`)
+
+(deflisten airplane "scripts/airplane")
+(deflisten battery "scripts/battery")
+(deflisten bluetooth "scripts/bluetooth")
+(deflisten brightness "scripts/brightness")
+(deflisten memory "scripts/memory")
+(deflisten music "scripts/music")
+(deflisten net "scripts/net")
+(deflisten volume "scripts/volume")
+(deflisten workspace "scripts/workspaces")
+(deflisten current_win "scripts/current_win")
diff --git a/foreign/dotfiles/home/programs/eww/modules/volume.yuck b/foreign/dotfiles/home/programs/eww/modules/volume.yuck
new file mode 100644
index 0000000..c4dceee
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/volume.yuck
@@ -0,0 +1,11 @@
+(defwidget volume-module []
+ (box
+ :class "module icon"
+ (eventbox
+ :onscroll "echo {} | sed -e 's/up/-/g' -e 's/down/+/g' | xargs -I% wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.005%"
+ :onclick "pavucontrol &"
+ :onrightclick "scripts/volume mute SINK"
+ (label
+ :class "vol-icon"
+ :tooltip "volume ${volume.percent}%"
+ :text {volume.icon}))))
diff --git a/foreign/dotfiles/home/programs/eww/modules/workspaces.yuck b/foreign/dotfiles/home/programs/eww/modules/workspaces.yuck
new file mode 100644
index 0000000..f8d0ac1
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/modules/workspaces.yuck
@@ -0,0 +1,13 @@
+(defwidget workspaces []
+ (eventbox
+ :onscroll "echo {} | sed -e \"s/up/-1/g\" -e \"s/down/+1/g\" | xargs hyprctl dispatch workspace"
+ (box
+ :class "module workspaces"
+ :spacing 5
+ (for ws in workspace
+ (button
+ :onclick "hyprctl dispatch workspace ${ws.number}"
+ :class "ws icon"
+ :style "color: ${ws.color};"
+ ; :tooltip {ws.tooltip}
+ "●")))))
diff --git a/foreign/dotfiles/home/programs/eww/scripts/airplane b/foreign/dotfiles/home/programs/eww/scripts/airplane
new file mode 100755
index 0000000..61db360
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/airplane
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+icon() {
+ if [ "$STATUS" = "no" ]; then
+ echo ""
+ else
+ echo ""
+ fi
+}
+
+toggle() {
+ if [ "$STATUS" = "no" ]; then
+ rfkill block all
+ notify-send --urgency=normal -i airplane-mode-symbolic "Airplane Mode" "Airplane mode has been turned on!"
+ else
+ rfkill unblock all
+ notify-send --urgency=normal -i airplane-mode-disabled-symbolic "Airplane Mode" "Airplane mode has been turned off!"
+ fi
+}
+
+if [ "$1" = "toggle" ]; then
+ toggle
+else
+ while true; do
+ STATUS="$(rfkill list | sed -n 2p | awk '{print $3}')"
+ icon
+ sleep 3;
+ done
+fi
diff --git a/foreign/dotfiles/home/programs/eww/scripts/battery b/foreign/dotfiles/home/programs/eww/scripts/battery
new file mode 100755
index 0000000..ad383d2
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/battery
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+
+icons=("" "" "" "" "" "" "" "")
+num_icons=$(bc <<< "100 / ${#icons[@]}")
+
+
+geticon() {
+ level=$(awk -v n="$CAPACITY" -v c="$num_icons" 'BEGIN{print int(n/c-1)}')
+ if [[ "$level" -lt 0 ]]; then
+ level=0
+ fi
+ echo "${icons[$level]}"
+}
+
+status() {
+ if [ "$STATE" = "Charging" ]; then
+ echo -n "charging"
+
+ if [ "$RATE" -gt 0 ]; then
+ echo ", $(gettime) left"
+ else
+ echo ""
+ fi
+ elif [ "$STATE" = "Discharging" ]; then
+ echo "$(gettime)h left"
+ else
+ echo "fully charged"
+ fi
+}
+
+color() {
+ if [ "$CAPACITY" -le 20 ]; then
+ echo '#f38ba8'
+ else
+ echo '#a6e3a1'
+ fi
+}
+
+wattage() {
+ microwatts=1000000
+ echo "$(bc -l <<< "scale=1; $RATE / $microwatts") W"
+}
+
+gettime() {
+ FULL=$(cat /sys/class/power_supply/BAT0/energy_full)
+ NOW=$(cat /sys/class/power_supply/BAT0/energy_now)
+
+ if [ "$RATE" -gt 0 ]; then
+ if [ "$STATE" = "Discharging" ]; then
+ EX="$NOW / $RATE"
+ else
+ EX="($FULL - $NOW) / $RATE"
+ fi
+ date -u -d@"$(bc -l <<< "$EX * 3600")" +%H:%M
+ fi
+}
+
+while true; do
+ RATE=$(cat /sys/class/power_supply/BAT0/power_now)
+ CAPACITY=$(cat /sys/class/power_supply/BAT0/capacity)
+ STATE=$(cat /sys/class/power_supply/BAT0/status)
+
+ echo '{ "icon": "'"$(geticon)"'", "percent": '"$CAPACITY"', "wattage": "'"$(wattage)"'", "status": "'"$(status)"'", "color": "'"$(color)"'" }'
+ sleep 3
+done
\ No newline at end of file
diff --git a/foreign/dotfiles/home/programs/eww/scripts/bluetooth b/foreign/dotfiles/home/programs/eww/scripts/bluetooth
new file mode 100755
index 0000000..63ce547
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/bluetooth
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+toggle() {
+ status=$(rfkill -J | jaq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1)
+
+ if [ "$status" = "unblocked" ]; then
+ rfkill block bluetooth
+ else
+ rfkill unblock bluetooth
+ fi
+}
+
+if [ "$1" = "toggle" ]; then
+ toggle
+else
+ while true; do
+ powered=$(bluetoothctl show | rg Powered | cut -f 2- -d ' ')
+ status=$(bluetoothctl info)
+ name=$(echo "$status" | rg Name | cut -f 2- -d ' ')
+ mac=$(echo "$status" | head -1 | awk '{print $2}' | tr ':' '_')
+
+ if [[ "$(echo "$status" | rg Percentage)" != "" ]]; then
+ battery="$(upower -i /org/freedesktop/UPower/devices/headset_dev_"$mac" | rg percentage | awk '{print $2}' | cut -f 1 -d '%')%"
+ else
+ battery=""
+ fi
+
+ if [ "$powered" = "yes" ]; then
+ if [ "$status" != "Missing device address argument" ]; then
+ text="$name"
+ icon=""
+ color="#89b4fa"
+ class="bt-connected"
+ else
+ icon=""
+ text="Disconnected"
+ color="#45475a"
+ class=""
+ fi
+ else
+ icon=""
+ text="Bluetooth off"
+ color="#45475a"
+ class=""
+ fi
+
+ echo '{ "icon": "'"$icon"'", "battery": "'"$battery"'", "text": "'"$text"'", "color": "'"$color"'", "class": "'"$class"'" }'
+
+ sleep 3
+ done
+fi
diff --git a/foreign/dotfiles/home/programs/eww/scripts/brightness b/foreign/dotfiles/home/programs/eww/scripts/brightness
new file mode 100755
index 0000000..5ca2dd1
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/brightness
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+icons=("" "" "")
+if [ ! "$XDG_CACHE_HOME" ]; then
+ XDG_CACHE_HOME="/home/mihai/.local/cache"
+fi
+date="$XDG_CACHE_HOME/eww/osd_brightness.date"
+
+osd() {
+ if [ ! -f "$date" ]; then
+ mkdir -p "$XDG_CACHE_HOME/eww"
+ fi
+ date +%s > "$date"
+}
+
+osd_handler() {
+ lock=0
+ rundate=0
+ if [ ! -f "$date" ]; then
+ mkdir -p "$XDG_CACHE_HOME/eww"
+ echo 0 > "$date"
+ fi
+
+ while true; do
+ # get dates
+ rundate=$(cat "$date")
+ currentdate=$(date +%s)
+
+ # handle showing
+ if [ "$rundate" = "$currentdate" ] && [ "$lock" -eq 0 ]; then
+ eww open osd
+ eww update osd-brightness=true
+ lock=1
+ elif [ "$((currentdate - rundate))" = "2" ] && [ "$lock" -eq 1 ]; then
+ eww update osd-brightness=false
+ lock=0
+ if [ "$(eww get osd-brightness)" = "false" ] && [ "$(eww get osd-volume)" = "false" ]; then
+ eww close osd
+ fi
+ fi
+
+ sleep 0.1
+ done
+
+ eww close osd
+}
+
+if [ "$1" = "osd" ]; then
+ osd
+else
+ osd_handler &
+ # initial
+ icon=${icons[$(awk -v n="$(light)" 'BEGIN{print int(n/34)}')]}
+ echo '{ "percent": '"$(light)"', "icon": "'"$icon"'" }'
+
+ udevadm monitor | rg --line-buffered "backlight" | while read -r _; do
+ icon="${icons[$(awk -v n="$(light)" 'BEGIN{print int(n/34)}')]}"
+
+ echo '{ "percent": '"$(light)"', "icon": "'"$icon"'" }'
+ done
+fi
diff --git a/foreign/dotfiles/home/programs/eww/scripts/current_win b/foreign/dotfiles/home/programs/eww/scripts/current_win
new file mode 100755
index 0000000..10ee3d2
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/current_win
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+socat -u UNIX-CLIENT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock STDOUT |
+while read line; do
+ if [[ "$line" == activewindow\>\>* ]]; then
+ windowtitle="$(echo $line | cut -d',' -f 2)"
+ echo '{"title": "'$windowtitle'"}'
+ fi
+done
+
diff --git a/foreign/dotfiles/home/programs/eww/scripts/memory b/foreign/dotfiles/home/programs/eww/scripts/memory
new file mode 100755
index 0000000..0ae499b
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/memory
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+while true; do
+ # human-readable
+ freeH=$(free -h --si | rg "Mem:")
+ # non-human-readable
+ freeN=$(free --mega | rg "Mem:")
+
+ total="$(echo "$freeH" | awk '{ print $2 }')"
+ used="$(echo "$freeH" | awk '{ print $3 }')"
+ t="$(echo "$freeN" | awk '{ print $2 }')"
+ u="$(echo "$freeN" | awk '{ print $3 }')"
+
+ free=$(printf '%.1fG' "$(bc -l <<< "($t - $u) / 1000")")
+ perc=$(printf '%.1f' "$(free -m | rg Mem | awk '{print ($3/$2)*100}')")
+
+ echo '{ "total": "'"$total"'", "used": "'"$used"'", "free": "'"$free"'", "percent": '"$perc"' }'
+
+ sleep 3
+done
diff --git a/foreign/dotfiles/home/programs/eww/scripts/music b/foreign/dotfiles/home/programs/eww/scripts/music
new file mode 100755
index 0000000..3128109
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/music
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+
+get_status() {
+ s=$1
+ if [ "$s" = "Playing" ]; then
+ echo ""
+ else
+ echo ""
+ fi
+}
+
+get_length_sec() {
+ len=$1
+ if [ -z "$len" ]; then
+ echo 0
+ else
+ bc <<<"$len / 1000000"
+ fi
+}
+
+get_length_time() {
+ len=$1
+ if [ -n "$len" ]; then
+ len=$(bc <<<"$len / 1000000 + 1")
+ date -d@"$len" +%M:%S
+ else
+ echo ""
+ fi
+}
+
+get_position() {
+ pos=$1
+ len=$2
+ if [ -n "$pos" ]; then
+ bc -l <<<"$pos / $len * 100"
+ else
+ echo 0
+ fi
+}
+
+get_position_time() {
+ pos=$1
+ len=$2
+ if [ -n "$pos" ]; then
+ date -d@"$(bc <<<"$pos / 1000000")" +%M:%S
+ else
+ echo ""
+ fi
+}
+
+get_cover() {
+ mkdir -p "$XDG_CACHE_HOME/eww_covers"
+ cd "$XDG_CACHE_HOME/eww_covers" || exit
+
+ IMGPATH="$XDG_CACHE_HOME/eww_covers/cover_art.png"
+
+ COVER_URL="$1"
+
+ if [[ "$COVER_URL" = https* ]]; then
+ if [ ! -e "$XDG_CACHE_HOME/eww_covers/$(basename "$COVER_URL")" ]; then
+ wget -N "$COVER_URL" -o /dev/null
+ fi
+
+ rm "$IMGPATH"
+ ln -s "$(basename "$COVER_URL")" "$IMGPATH"
+
+ IMG="${IMGPATH}"
+ elif [ "$COVER_URL" = "" ]; then
+ IMG=""
+ else
+ IMG="$COVER_URL"
+ fi
+
+ echo "$IMG"
+}
+
+sanitize() {
+ echo "$1" | sed 's/"/\"/g'
+}
+
+prevCover=''
+
+playerctl -F metadata -f '{{title}}\{{artist}}\{{status}}\{{position}}\{{mpris:length}}\{{mpris:artUrl}}' 2>/dev/null | while IFS="$(printf '\')" read -r title artist status position len cover; do
+ if [[ "$cover" != "$prevCover" ]]; then
+ COVER=$(get_cover "$cover")
+
+ if [ "$COVER" != "" ]; then
+ cols=$(convert "$COVER" -colors 2 -format "%c" histogram:info: | awk '{print $3}')
+ color1=$(echo "$cols" | head -1)
+ color1=$(printf "rgba(%d, %d, %d, 0.6)" ${color1:1:2} ${color1:3:2} ${color1:5:2})
+ color2=$(echo "$cols" | tail -1)
+ else
+ color1="#1e1e2e"
+ color2="#28283d"
+ fi
+ fi
+
+ jaq --null-input -r -c \
+ --arg artist "$(sanitize "$artist")" \
+ --arg title "$(sanitize "$title")" \
+ --arg status "$status" \
+ --arg pos "$(get_position "$position" "$len")" \
+ --arg pos_time "$(get_position_time "$position" "$len")" \
+ --arg length "$(get_length_time "$len")" \
+ --arg cover "$COVER" \
+ --arg color1 "$color1" \
+ --arg color2 "$color2" \
+ '{"artist": $artist, "title": $title, "status": $status, "position": $pos, "position_time": $pos_time, "length": $length, "cover": $cover, "color1": $color1, "color2": $color2}'
+
+ prevCover=$cover
+done
diff --git a/foreign/dotfiles/home/programs/eww/scripts/net b/foreign/dotfiles/home/programs/eww/scripts/net
new file mode 100755
index 0000000..a84b9b9
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/net
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+toggle() {
+ status=$(rfkill -J | jaq -r '.rfkilldevices[] | select(.type == "wlan") | .soft' | head -1)
+
+ if [ "$status" = "unblocked" ]; then
+ rfkill block wlan
+ else
+ rfkill unblock wlan
+ fi
+}
+
+if [ "$1" = "toggle" ]; then
+ toggle
+else
+ while true; do
+ status=$(nmcli -f state g| tail -1)
+ wifistatus=$(nmcli -t -f in-use,ssid,signal dev wifi | rg '\*' | sed 's/\"/\\"/g')
+ signal=$(echo "$wifistatus" | awk -F: '{print $3}')
+ essid=$(echo "$wifistatus" | awk -F: '{print $2}')
+
+ icons=("" "" "" "" "")
+
+ if [ "$status" = "disconnected" ] ; then
+ icon=""
+ color="#988ba2"
+ class=""
+ else
+ level=$(awk -v n="$signal" 'BEGIN{print int((n-1)/20)}')
+ if [ "$level" -gt 4 ]; then
+ level=4
+ fi
+
+ icon=${icons[$level]}
+ color="#cba6f7"
+ class="wifi-connected"
+ fi
+
+ echo '{ "essid": "'"$essid"'", "icon": "'"$icon"'", "color": "'"$color"'", "class": "'"$class"'" }'
+
+ sleep 3
+ done
+fi
diff --git a/foreign/dotfiles/home/programs/eww/scripts/volume b/foreign/dotfiles/home/programs/eww/scripts/volume
new file mode 100755
index 0000000..7e9b11d
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/volume
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+
+volicons=("" "" "")
+if [ ! "$XDG_CACHE_HOME" ]; then
+ XDG_CACHE_HOME="/home/mihai/.local/cache"
+fi
+date="$XDG_CACHE_HOME/eww/osd_vol.date"
+
+vol() {
+ wpctl get-volume @DEFAULT_AUDIO_"$1"@ | awk '{print int($2*100)}'
+}
+ismuted() {
+ wpctl get-volume @DEFAULT_AUDIO_"$1"@ | rg -i muted
+ echo $?
+}
+setvol() {
+ wpctl set-volume @DEFAULT_AUDIO_"$1"@ "$(awk -v n="$2" 'BEGIN{print (n / 100)}')"
+}
+setmute() {
+ wpctl set-mute @DEFAULT_AUDIO_"$1"@ toggle
+}
+
+osd() {
+ if [ ! -f "$date" ]; then
+ mkdir -p "$XDG_CACHE_HOME/eww"
+ fi
+ date +%s > "$date"
+}
+
+osd_handler() {
+ lock=0
+ rundate=0
+ if [ ! -f "$date" ]; then
+ mkdir -p "$XDG_CACHE_HOME/eww"
+ echo 0 > "$date"
+ fi
+
+ while true; do
+ # get dates
+ rundate=$(cat "$date")
+ currentdate=$(date +%s)
+
+ # handle showing
+ if [ "$rundate" = "$currentdate" ] && [ "$lock" -eq 0 ]; then
+ eww open osd
+ eww update osd-volume=true
+ lock=1
+ elif [ "$((currentdate - rundate))" = "2" ] && [ "$lock" -eq 1 ]; then
+ eww update osd-volume=false
+ lock=0
+ if [ "$(eww get osd-volume)" = "false" ] && [ "$(eww get osd-brightness)" = "false" ]; then
+ eww close osd
+ fi
+ fi
+
+ sleep 0.1
+ done
+
+ eww close osd
+}
+
+if [ "$1" = "mute" ]; then
+ if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then
+ echo "Can only mute SINK or SOURCE"; exit 1
+ fi
+ setmute "$2"
+elif [ "$1" = "setvol" ]; then
+ if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then
+ echo "Can only set volume for SINK or SOURCE"; exit 1
+ elif [ "$3" -lt 1 ] || [ "$3" -gt 100 ]; then
+ echo "Volume must be between 1 and 100"; exit 1
+ fi
+ setvol "$2" "$3"
+elif [ "$1" = "osd" ]; then
+ osd
+else
+ # initial values
+ lvl=$(awk -v n="$(vol "SINK")" 'BEGIN{print int(n/34)}')
+ ismuted=$(ismuted "SINK")
+
+ if [ "$ismuted" = 1 ]; then
+ icon="${volicons[$lvl]}"
+ else
+ icon=""
+ fi
+ echo '{ "icon": "'"$icon"'", "percent": "'"$(vol "SINK")"'", "microphone": "'"$(vol "SOURCE")"'" }'
+
+ osd_handler &
+
+ # event loop
+ pactl subscribe | rg --line-buffered "change" | while read -r _; do
+ lvl=$(awk -v n="$(vol "SINK")" 'BEGIN{print int(n/34)}')
+ ismuted=$(ismuted "SINK")
+
+ if [ "$ismuted" = 1 ]; then
+ icon="${volicons[$lvl]}"
+ else
+ icon=""
+ fi
+ echo '{ "icon": "'"$icon"'", "percent": "'"$(vol "SINK")"'", "microphone": "'"$(vol "SOURCE")"'" }'
+ done
+fi
diff --git a/foreign/dotfiles/home/programs/eww/scripts/workspaces b/foreign/dotfiles/home/programs/eww/scripts/workspaces
new file mode 100755
index 0000000..c7caf95
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/scripts/workspaces
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+# define colors
+# red peach green blue
+colors=("#f38ba8" "#fab387" "#a6e3a1" "#89b4fa")
+# pink yellow teal lavender
+dimmed=("#f5c2e7" "#f9e2af" "#94e2d5" "#b4befe")
+empty="#313244"
+
+# get initial focused workspace
+focusedws=$(hyprctl -j monitors | jaq -r '.[] | select(.focused == true) | .activeWorkspace.id')
+
+declare -A o=([1]=0 [2]=0 [3]=0 [4]=0)
+declare -A monitormap
+declare -A workspaces
+
+# set color for each workspace
+status() {
+ if [ "${o[$1]}" -eq 1 ]; then
+ mon=${monitormap[${workspaces[$1]}]}
+
+ if [ "$focusedws" -eq "$1" ]; then
+ echo -n "#27ae60"
+ # echo -n "${colors[$mon]}"
+ else
+ # echo -n "${dimmed[$mon]}"
+ echo -n "#fdbc4b"
+ fi
+ else
+ # echo -n "$empty"
+ echo -n "#2f343f"
+ fi
+}
+
+# handle workspace create/destroy
+workspace_event() {
+ o[$1]=$2
+ while read -r k v; do workspaces[$k]="$v"; done < <(hyprctl -j workspaces | gojq -r '.[]|"\(.id) \(.monitor)"')
+}
+# handle monitor (dis)connects
+monitor_event() {
+ while read -r k v; do monitormap["$k"]=$v; done < <(hyprctl -j monitors | gojq -r '.[]|"\(.name) \(.id) "')
+}
+
+# get all apps titles in a workspace
+applist() {
+ ws="$1"
+
+ apps=$(hyprctl -j clients | jaq -jr '.[] | select(.workspace.id == '"$ws"') | .title + "\\n"')
+ echo -En "${apps%"\n"}"
+}
+
+# generate the json for eww
+generate() {
+ echo -n '['
+
+ for i in {1..4}; do
+ echo -n ''"$([ "$i" -eq 1 ] || echo ,)" '{ "number": "'"$i"'", "color": "'"$(status "$i")"'" }' #, "tooltip": "'$(applist "$i")'" }'
+ done
+
+ echo ']'
+}
+
+# setup
+
+# add monitors
+monitor_event
+
+# add workspaces
+while read -r k v; do workspaces[$k]="$v"; done < <(hyprctl -j workspaces | gojq -r '.[]|"\(.id) \(.monitor)"')
+
+# check occupied workspaces
+for num in "${!workspaces[@]}"; do
+ o[$num]=1
+done
+# generate initial widget
+generate
+
+# main loop
+socat -u UNIX-CONNECT:/tmp/hypr/"$HYPRLAND_INSTANCE_SIGNATURE"/.socket2.sock - | rg --line-buffered "workspace|mon(itor)?" | while read -r line; do
+ case ${line%>>*} in
+ "workspace")
+ focusedws=${line#*>>}
+ ;;
+ "focusedmon")
+ focusedws=${line#*,}
+ ;;
+ "createworkspace")
+ workspace_event "${line#*>>}" 1
+ ;;
+ "destroyworkspace")
+ workspace_event "${line#*>>}" 0
+ ;;
+ "monitor"*)
+ monitor_event
+ ;;
+ esac
+ generate
+done
diff --git a/foreign/dotfiles/home/programs/eww/windows/calendar.yuck b/foreign/dotfiles/home/programs/eww/windows/calendar.yuck
new file mode 100644
index 0000000..403d732
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/windows/calendar.yuck
@@ -0,0 +1,14 @@
+(defwidget calendar-win []
+ (box
+ :class "calendar-win"
+ (calendar)))
+
+(defwindow calendar
+ :monitor 0
+ :geometry (geometry
+ :x "0%"
+ :y "0%"
+ :anchor "top right"
+ :width "0px"
+ :height "0px")
+ (calendar-win))
diff --git a/foreign/dotfiles/home/programs/eww/windows/music.yuck b/foreign/dotfiles/home/programs/eww/windows/music.yuck
new file mode 100644
index 0000000..4e7d5e7
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/windows/music.yuck
@@ -0,0 +1,55 @@
+(defwidget music []
+ (box
+ :class "music-window"
+ :space-evenly false
+ :style "background-color: ${music.color1}; border: 1px solid ${music.color2};"
+ (box
+ :class "music-cover-art"
+ :style "background-image: url(\"${music.cover}\");")
+ (box
+ :orientation "v"
+ :class "music-box"
+ (label
+ :class "music-title"
+ :wrap true
+ :text {music.title})
+ (label
+ :class "music-artist"
+ :wrap true
+ :text {music.artist})
+ (centerbox
+ :halign "center"
+ :class "music-button-box icon"
+ (button :class "music-button" :onclick "playerctl previous" "")
+ (button :class "music-button" :onclick "playerctl play-pause" {music.status})
+ (button :class "music-button" :onclick "playerctl next" ""))
+ (box
+ :orientation "v"
+ (box
+ (label
+ :xalign 0
+ :class "music-time"
+ :text {music.position_time})
+ (label
+ :xalign 1
+ :class "music-time"
+ :text {music.length}))
+ (box
+ :class "music-bar"
+ (scale
+ ; doesn't work, looking for other ways
+ ; :style "background: linear-gradient(to right, ${music.color1}, ${music.color2});"
+ :onchange "playerctl position `bc <<< \"{} * $(playerctl metadata mpris:length) / 1000000 / 100\"`"
+ :value {music.position}))))))
+
+(defwindow music
+ :stacking "fg"
+ :focusable false
+ :monitor 0
+ :geometry (geometry
+ :x "0%"
+ :y "0%"
+ :width "0%"
+ :height "0%"
+ :anchor "top center")
+ (music))
diff --git a/foreign/dotfiles/home/programs/eww/windows/osd.yuck b/foreign/dotfiles/home/programs/eww/windows/osd.yuck
new file mode 100644
index 0000000..138bc06
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/windows/osd.yuck
@@ -0,0 +1,43 @@
+(defwidget osd-part [icon value class]
+ (box
+ :class "osd-part osd-${class}"
+ :orientation "v"
+ :space-evenly false
+ (scale
+ :flipped true
+ :orientation "v"
+ :value value)
+ (label
+ :text icon)))
+
+(defwidget osd []
+ (box
+ :class "osd-container"
+ :space-evenly false
+ (revealer
+ :reveal {osd-brightness}
+ :transition "slideright"
+ (osd-part
+ :class "brightness"
+ :icon {brightness.icon}
+ :value {brightness.percent}))
+ (revealer
+ :reveal {osd-volume}
+ :transition "slideright"
+ (osd-part
+ :class "volume"
+ :icon {volume.icon}
+ :value {volume.percent})))
+)
+
+(defwindow osd
+ :stacking "fg"
+ :focusable false
+ :monitor 0
+ :geometry (geometry
+ :x "1%"
+ :y "0%"
+ :width "0%"
+ :height "0%"
+ :anchor "center left")
+ (osd))
diff --git a/foreign/dotfiles/home/programs/eww/windows/system-menu.yuck b/foreign/dotfiles/home/programs/eww/windows/system-menu.yuck
new file mode 100644
index 0000000..888b178
--- /dev/null
+++ b/foreign/dotfiles/home/programs/eww/windows/system-menu.yuck
@@ -0,0 +1,204 @@
+(defwidget system-menu []
+ (box
+ :class "system-menu-box"
+ :space-evenly false
+ :orientation "v"
+ (box
+ :class "top-row"
+ :space-evenly false
+ (label
+ :class "time"
+ :text "${time.hour}:${time.minute}")
+ (box
+ :class "date-box"
+ :space-evenly false
+ (label
+ :class "date"
+ :text {time.date})
+ (label
+ :class "day"
+ :text {time.day})))
+
+ (centerbox
+ :class "system-row"
+ (box
+ :class "wifi-box"
+ :space-evenly false
+ :orientation "v"
+ (box
+ :class "element icon ${net.class}"
+ :space-evenly false
+ (button
+ :class "wifi-button"
+ :onclick "scripts/net toggle"
+ {net.icon})
+ (label
+ :class "separator"
+ :text "│")
+ (button
+ :class "wifi-arrow-btn"
+ :onclick "eww close system-menu && gnome-control-center &"
+ ""))
+ (label
+ :text {net.essid}
+ :xalign 0.5
+ :limit-width 15))
+
+ (box
+ :class "bluetooth-box"
+ :space-evenly false
+ :orientation "v"
+ (box
+ :class "element icon ${bluetooth.class}"
+ :space-evenly false
+ (button
+ :class "bluetooth-button"
+ :onclick "scripts/bluetooth toggle"
+ {bluetooth.icon})
+ (label
+ :class "separator"
+ :text "│")
+ (button
+ :class "bluetooth-arrow-btn"
+ :onclick "eww close system-menu && blueberry"
+ ""))
+ (label
+ :text {bluetooth.text}
+ :xalign 0.5
+ :tooltip "${bluetooth.text} ${bluetooth.battery}"
+ :limit-width 15))
+
+ (box
+ :class "airplane-box"
+ :space-evenly false
+ :orientation "v"
+ (box
+ :class "element"
+ (button
+ :class "airplane-button"
+ :onclick "scripts/airplane toggle"
+ airplane))
+ (label
+ :text "Airplane Mode"
+ :xalign 0.5
+ :limit-width 16)))
+
+ (box
+ :class "sliders"
+ :orientation "v"
+ (box
+ :class "volume-slider-box"
+ :space-evenly false
+ (button
+ :class "volume-icon icon"
+ :onclick "scripts/volume mute SINK"
+ {volume.icon})
+ (scale
+ :class "volume-bar"
+ :value {volume.percent}
+ :tooltip "volume on ${volume.percent}%"
+ :onchange "scripts/volume setvol SINK {}"))
+ (box
+ :class "brightness-slider-box"
+ :space-evenly false
+ (button
+ :class "brightness-slider-icon icon"
+ {brightness.icon})
+ (scale
+ :class "brightness-slider"
+ :value {brightness.percent}
+ :marks true
+ :onchange "light -S {}")))
+
+ (box
+ :class "system-info-box"
+
+ ; cpu
+ (box
+ :class "sys-box"
+ :space-evenly false
+ :halign "start"
+ (circular-progress
+ :value "${EWW_CPU.avg}"
+ :class "sys-cpu"
+ :thickness 3
+ (label
+ :text ""
+ :class "sys-icon-cpu icon"))
+ (box
+ :orientation "v"
+ :vexpand false
+ (label
+ :text "cpu"
+ :halign "start"
+ :class "sys-text-cpu")
+ (label
+ :text "${round(EWW_CPU.avg,2)}%"
+ :halign "start"
+ :class "sys-text-sub")
+ (label
+ :text "${EWW_CPU.cores[0].freq} MHz"
+ :halign "start"
+ :class "sys-text-sub")))
+
+ ; memory
+ (box
+ :class "sys-box"
+ :space-evenly false
+ :halign "end"
+ (circular-progress
+ :value {memory.percent}
+ :class "sys-mem"
+ :thickness 3
+ (label
+ :text ""
+ :class "sys-icon-mem icon"))
+ (box
+ :orientation "v"
+ (label
+ :text "memory"
+ :halign "start"
+ :class "sys-text-mem")
+ (label
+ :text "${memory.used} | ${memory.total}"
+ :halign "start"
+ :class "sys-text-sub"))))
+
+ (centerbox
+ :class "bottom-row"
+ (box
+ :class "battery-box"
+ :space-evenly false
+ :halign "start"
+ (label
+ :class "battery-icon icon"
+ :style "color: ${battery.color}"
+ :text {battery.icon})
+ (label
+ :text {EWW_BATTERY["BAT0"].capacity})
+ (label
+ :class "battery-status"
+ :text {battery.status})
+ (label
+ :class "battery-wattage"
+ :text {battery.wattage}))
+ (label)
+ (box
+ :space-evenly false
+ :halign "end"
+ (button
+ :halign "end"
+ :class "power-button icon"
+ :onclick "wlogout -p layer-shell &"
+ "")))))
+
+(defwindow system-menu
+ :stacking "fg"
+ :monitor 0
+ :geometry (geometry
+ :x "0"
+ :y "0"
+ :width "0%"
+ :height "0%"
+ :anchor "right top")
+ (system-menu))
diff --git a/foreign/dotfiles/home/programs/files/config/wofi/config b/foreign/dotfiles/home/programs/files/config/wofi/config
new file mode 100644
index 0000000..b37c110
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/config/wofi/config
@@ -0,0 +1,7 @@
+width=40%
+height=30%
+show=drun
+prompt=Search
+allow_images=true
+allow_markup=true
+insensitive=true
\ No newline at end of file
diff --git a/foreign/dotfiles/home/programs/files/config/youtube-dl/config b/foreign/dotfiles/home/programs/files/config/youtube-dl/config
new file mode 100644
index 0000000..759e1c7
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/config/youtube-dl/config
@@ -0,0 +1,25 @@
+# youtube-dl config file
+
+# use .netrc for logins
+-n
+
+# ignore errors (unavailable videos, etc)
+-i
+
+# don't overwrite
+-w
+
+# make an archive of downloaded videos and only download unlisted items, then list them
+# useful for music playlists you update often
+--download-archive ~/Music/untagged/ignore.these
+
+# convert all downloads to 192kbps mp3
+-x
+--audio-format mp3 #opus
+--audio-quality 192K
+
+# embed thumbnail into audio
+#--embed-thumbnail
+
+# save all music in the following folder and format
+-o '~/Music/untagged/%(artist)s - %(title)s.%(ext)s'
diff --git a/foreign/dotfiles/home/programs/files/default.nix b/foreign/dotfiles/home/programs/files/default.nix
new file mode 100644
index 0000000..2c42964
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/default.nix
@@ -0,0 +1,34 @@
+{
+ pkgs,
+ lib,
+ config,
+ ...
+}:
+# manage files in ~
+{
+ imports = [
+ ./nix-index-update-db.nix
+ ./wlogout.nix
+ ./wofi-style.nix
+ ];
+
+ home.file.".config" = {
+ source = ./config;
+ recursive = true;
+ };
+
+ xdg.configFile = {
+ "btop/themes/catppuccin_mocha.theme".source = pkgs.fetchurl {
+ url = "https://raw.githubusercontent.com/catppuccin/btop/main/catppuccin_mocha.theme";
+ hash = "sha256-MGK5ECB5sXiHdi2A3Y4s/Sx7nSRQ+KLyZjEKElRPKf0=";
+ };
+
+ "xilinx/nix.sh" = {
+ executable = true;
+ text = ''
+ INSTALL_DIR=$HOME/Documents/code/xilinx/tools/Xilinx
+ VERSION=2022.2
+ '';
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/programs/files/nix-index-update-db.nix b/foreign/dotfiles/home/programs/files/nix-index-update-db.nix
new file mode 100644
index 0000000..93b76a9
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/nix-index-update-db.nix
@@ -0,0 +1,35 @@
+{
+ pkgs,
+ lib,
+ ...
+}: {
+ # set up nix-index
+ systemd.user.timers.nix-index-db-update = {
+ Timer = {
+ OnCalendar = "weekly";
+ Persistent = true;
+ RandomizedDelaySec = 0;
+ };
+ };
+
+ systemd.user.services.nix-index-db-update = {
+ Unit = {
+ Description = "nix-index database update";
+ PartOf = ["multi-user.target"];
+ };
+ Service = let
+ script = pkgs.writeShellScript "nix-index-update-db" ''
+ export filename="index-x86_64-$(uname | tr A-Z a-z)"
+ mkdir -p ~/.cache/nix-index
+ cd ~/.cache/nix-index
+ # -N will only download a new version if there is an update.
+ wget -N https://github.com/Mic92/nix-index-database/releases/latest/download/$filename
+ ln -f $filename files
+ '';
+ in {
+ Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath [pkgs.wget pkgs.coreutils]}";
+ ExecStart = "${script}";
+ };
+ Install.WantedBy = ["multi-user.target"];
+ };
+}
diff --git a/foreign/dotfiles/home/programs/files/wlogout.nix b/foreign/dotfiles/home/programs/files/wlogout.nix
new file mode 100644
index 0000000..0633039
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/wlogout.nix
@@ -0,0 +1,57 @@
+{
+ default,
+ pkgs,
+ ...
+}: let
+ w = pkgs.wlogout;
+in {
+ xdg.configFile."wlogout/style.css".text = ''
+ * {
+ background-image: none;
+ font-family: "Jost *", Roboto, sans-serif;
+ }
+ window {
+ background-color: rgba(12, 12, 12, 0.9);
+ }
+ button {
+ background: unset;
+ border-radius: 16px;
+ border: 1px solid #28283d;
+ color: ${default.xcolors.text};
+ margin: 1rem;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 25%;
+ }
+
+ button:focus, button:active, button:hover {
+ background-color: ${default.xcolors.blue};
+ color: ${default.xcolors.base};
+ outline-style: none;
+ }
+
+ #lock {
+ background-image: image(url("${w}/share/wlogout/icons/lock.png"), url("${w}/local/share/wlogout/icons/lock.png"));
+ }
+
+ #logout {
+ background-image: image(url("${w}/share/wlogout/icons/logout.png"), url("${w}/local/share/wlogout/icons/logout.png"));
+ }
+
+ #suspend {
+ background-image: image(url("${w}/share/wlogout/icons/suspend.png"), url("${w}/local/share/wlogout/icons/suspend.png"));
+ }
+
+ #hibernate {
+ background-image: image(url("${w}/share/wlogout/icons/hibernate.png"), url("${w}/local/share/wlogout/icons/hibernate.png"));
+ }
+
+ #shutdown {
+ background-image: image(url("${w}/share/wlogout/icons/shutdown.png"), url("${w}/local/share/wlogout/icons/shutdown.png"));
+ }
+
+ #reboot {
+ background-image: image(url("${w}/share/wlogout/icons/reboot.png"), url("${w}/local/share/wlogout/icons/reboot.png"));
+ }
+ '';
+}
diff --git a/foreign/dotfiles/home/programs/files/wofi-style.nix b/foreign/dotfiles/home/programs/files/wofi-style.nix
new file mode 100644
index 0000000..614a79c
--- /dev/null
+++ b/foreign/dotfiles/home/programs/files/wofi-style.nix
@@ -0,0 +1,46 @@
+{default, ...}: {
+ xdg.configFile."wofi/style.css".text = ''
+ window { background: unset; }
+ flowboxchild { outline-width: 0; }
+
+ #outer-box {
+ background: ${default.xcolors.base};
+ border: 1px solid ${default.xcolors.border};
+ border-radius: 24px;
+ box-shadow: 0 2px 3px ${default.xcolors.crust};
+ margin: 5px 5px 10px;
+ padding: 5px 5px 10px;
+ }
+
+ #input {
+ background-color: ${default.xcolors.crust};
+ border: none;
+ border-radius: 16px;
+ color: ${default.xcolors.text};
+ margin: 5px;
+ }
+
+ #inner-box {
+ background-color: ${default.xcolors.base};
+ border: none;
+ border-radius: 16px;
+ margin: 5px;
+ }
+
+ #scroll {
+ border: none;
+ margin: 0px;
+ }
+
+ #text {
+ color: ${default.xcolors.text};
+ margin: 5px;
+ }
+
+ #entry { border-radius: 16px; }
+
+ #entry:selected {
+ background-color: ${default.xcolors.surface0};
+ }
+ '';
+}
diff --git a/foreign/dotfiles/home/programs/games.nix b/foreign/dotfiles/home/programs/games.nix
new file mode 100644
index 0000000..9973495
--- /dev/null
+++ b/foreign/dotfiles/home/programs/games.nix
@@ -0,0 +1,12 @@
+{
+ pkgs,
+ inputs,
+ ...
+}:
+# games
+{
+ home.packages = with pkgs; [
+ inputs.nix-gaming.packages.${pkgs.hostPlatform.system}.osu-lazer-bin
+ gamescope
+ ];
+}
diff --git a/foreign/dotfiles/home/programs/git.nix b/foreign/dotfiles/home/programs/git.nix
new file mode 100644
index 0000000..b2648dc
--- /dev/null
+++ b/foreign/dotfiles/home/programs/git.nix
@@ -0,0 +1,52 @@
+{
+ pkgs,
+ default,
+ ...
+}: {
+ home.packages = [pkgs.gh];
+
+ programs.git = {
+ enable = true;
+
+ delta = {
+ enable = true;
+ options.map-styles = "bold purple => syntax ${default.xcolors.mauve}, bold cyan => syntax ${default.xcolors.blue}";
+ };
+
+ extraConfig = {
+ diff.colorMoved = "default";
+ merge.conflictstyle = "diff3";
+ };
+
+ aliases = {
+ a = "add";
+ b = "branch";
+ c = "commit";
+ ca = "commit --amend";
+ cm = "commit -m";
+ co = "checkout";
+ d = "diff";
+ ds = "diff --staged";
+ p = "push";
+ pf = "push --force-with-lease";
+ pl = "pull";
+ l = "log";
+ r = "rebase";
+ s = "status --short";
+ ss = "status";
+ forgor = "commit --amend --no-edit";
+ graph = "log --all --decorate --graph --oneline";
+ oops = "checkout --";
+ };
+
+ ignores = ["*~" "*.swp" "*result*" ".direnv" "node_modules"];
+
+ signing = {
+ key = "5899325F2F120900";
+ signByDefault = true;
+ };
+
+ userEmail = "fufexan@protonmail.com";
+ userName = "Mihai Fufezan";
+ };
+}
diff --git a/foreign/dotfiles/home/programs/gtk.nix b/foreign/dotfiles/home/programs/gtk.nix
new file mode 100644
index 0000000..4e324ac
--- /dev/null
+++ b/foreign/dotfiles/home/programs/gtk.nix
@@ -0,0 +1,38 @@
+{
+ pkgs,
+ config,
+ ...
+}: {
+ home.pointerCursor = {
+ package = pkgs.bibata-cursors;
+ name = "Bibata-Modern-Classic";
+ size = 24;
+ gtk.enable = true;
+ x11.enable = true;
+ };
+
+ gtk = {
+ enable = true;
+
+ font = {
+ name = "Roboto";
+ package = pkgs.roboto;
+ };
+
+ gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
+
+ iconTheme = {
+ name = "Papirus-Dark";
+ package = pkgs.papirus-icon-theme;
+ };
+
+ theme = {
+ name = "Catppuccin-Mocha-Compact-Mauve-Dark";
+ package = pkgs.catppuccin-gtk.override {
+ accents = ["mauve"];
+ size = "compact";
+ variant = "mocha";
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/programs/media.nix b/foreign/dotfiles/home/programs/media.nix
new file mode 100644
index 0000000..6d4b279
--- /dev/null
+++ b/foreign/dotfiles/home/programs/media.nix
@@ -0,0 +1,55 @@
+{
+ pkgs,
+ config,
+ ...
+}:
+# media - control and enjoy audio/video
+{
+ imports = [
+ ./spicetify.nix
+ ];
+
+ home.packages = with pkgs; [
+ # audio control
+ pavucontrol
+ playerctl
+ pulsemixer
+ # images
+ imv
+
+ spotify-tui
+ ];
+
+ programs = {
+ mpv = {
+ enable = true;
+ defaultProfiles = ["gpu-hq"];
+ scripts = [pkgs.mpvScripts.mpris];
+ };
+
+ obs-studio.enable = true;
+ };
+
+ services = {
+ playerctld.enable = true;
+
+ spotifyd = {
+ enable = true;
+ package = pkgs.spotifyd.override {withMpris = true;};
+ settings.global = {
+ autoplay = true;
+ backend = "pulseaudio";
+ bitrate = 320;
+ cache_path = "${config.xdg.cacheHome}/spotifyd";
+ device_type = "computer";
+ initial_volume = "100";
+ password_cmd = "tail -1 /run/agenix/spotify";
+ use_mpris = true;
+ username_cmd = "head -1 /run/agenix/spotify";
+ volume_normalisation = false;
+ };
+ };
+
+ udiskie.enable = true;
+ };
+}
diff --git a/foreign/dotfiles/home/programs/packages.nix b/foreign/dotfiles/home/programs/packages.nix
new file mode 100644
index 0000000..e666596
--- /dev/null
+++ b/foreign/dotfiles/home/programs/packages.nix
@@ -0,0 +1,33 @@
+{
+ pkgs,
+ inputs,
+ ...
+}: {
+ home.packages = with pkgs; [
+ # archives
+ zip
+ unzip
+ unrar
+
+ # office
+ libreoffice
+
+ # messaging
+ tdesktop
+
+ # school stuff
+ inputs.nix-xilinx.packages.${pkgs.hostPlatform.system}.vivado
+ jetbrains.idea-community
+
+ # torrents
+ transmission-remote-gtk
+
+ # misc
+ libnotify
+ xdg-utils
+
+ # productivity
+ obsidian
+ xournalpp
+ ];
+}
diff --git a/foreign/dotfiles/home/programs/qt.nix b/foreign/dotfiles/home/programs/qt.nix
new file mode 100644
index 0000000..f592842
--- /dev/null
+++ b/foreign/dotfiles/home/programs/qt.nix
@@ -0,0 +1,18 @@
+{pkgs, ...}:
+# Qt theming with Kvantum
+{
+ home.packages = with pkgs; [
+ libsForQt5.qtstyleplugin-kvantum
+ (catppuccin-kvantum.override {
+ accent = "Mauve";
+ variant = "Mocha";
+ })
+ ];
+ home.sessionVariables = {
+ QT_STYLE_OVERRIDE = "kvantum";
+ };
+
+ xdg.configFile."Kvantum/kvantum.kvconfig".source = (pkgs.formats.ini {}).generate "kvantum.kvconfig" {
+ General.Theme = "Catppuccin-Mocha-Mauve";
+ };
+}
diff --git a/foreign/dotfiles/home/programs/spicetify.nix b/foreign/dotfiles/home/programs/spicetify.nix
new file mode 100644
index 0000000..3871515
--- /dev/null
+++ b/foreign/dotfiles/home/programs/spicetify.nix
@@ -0,0 +1,24 @@
+{
+ pkgs,
+ inputs,
+ ...
+}: {
+ # themable spotify
+ programs.spicetify = let
+ spicePkgs = inputs.spicetify-nix.packages.${pkgs.hostPlatform.system}.default;
+ in {
+ enable = true;
+
+ spotifyPackage = inputs.self.packages.${pkgs.hostPlatform.system}.spotify;
+
+ theme = spicePkgs.themes.catppuccin-mocha;
+
+ colorScheme = "flamingo";
+
+ enabledExtensions = with spicePkgs.extensions; [
+ fullAppDisplay
+ hidePodcasts
+ shuffle
+ ];
+ };
+}
diff --git a/foreign/dotfiles/home/programs/xdg.nix b/foreign/dotfiles/home/programs/xdg.nix
new file mode 100644
index 0000000..1b22a8a
--- /dev/null
+++ b/foreign/dotfiles/home/programs/xdg.nix
@@ -0,0 +1,47 @@
+{config, ...}: let
+ browser = ["firefox.desktop"];
+
+ # XDG MIME types
+ associations = {
+ "application/x-extension-htm" = browser;
+ "application/x-extension-html" = browser;
+ "application/x-extension-shtml" = browser;
+ "application/x-extension-xht" = browser;
+ "application/x-extension-xhtml" = browser;
+ "application/xhtml+xml" = browser;
+ "text/html" = browser;
+ "x-scheme-handler/about" = browser;
+ "x-scheme-handler/chrome" = ["chromium-browser.desktop"];
+ "x-scheme-handler/ftp" = browser;
+ "x-scheme-handler/http" = browser;
+ "x-scheme-handler/https" = browser;
+ "x-scheme-handler/unknown" = browser;
+
+ "audio/*" = ["mpv.desktop"];
+ "video/*" = ["mpv.dekstop"];
+ "image/*" = ["imv.desktop"];
+ "application/json" = browser;
+ "application/pdf" = ["org.pwmt.zathura.desktop.desktop"];
+ "x-scheme-handler/discord" = ["discordcanary.desktop"];
+ "x-scheme-handler/spotify" = ["spotify.desktop"];
+ "x-scheme-handler/tg" = ["telegramdesktop.desktop"];
+ };
+in {
+ xdg = {
+ enable = true;
+ cacheHome = config.home.homeDirectory + "/.local/cache";
+
+ mimeApps = {
+ enable = true;
+ defaultApplications = associations;
+ };
+
+ userDirs = {
+ enable = true;
+ createDirectories = true;
+ extraConfig = {
+ XDG_SCREENSHOTS_DIR = "${config.xdg.userDirs.pictures}/Screenshots";
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/programs/zathura.nix b/foreign/dotfiles/home/programs/zathura.nix
new file mode 100644
index 0000000..98542d8
--- /dev/null
+++ b/foreign/dotfiles/home/programs/zathura.nix
@@ -0,0 +1,27 @@
+{pkgs, ...}: {
+ programs.zathura = {
+ enable = true;
+ options = {
+ recolor-lightcolor = "rgba(0,0,0,0)";
+ default-bg = "rgba(0,0,0,0.7)";
+
+ font = "Lexend 12";
+ selection-notification = true;
+
+ selection-clipboard = "clipboard";
+ adjust-open = "best-fit";
+ pages-per-row = "1";
+ scroll-page-aware = "true";
+ scroll-full-overlap = "0.01";
+ scroll-step = "100";
+ zoom-min = "10";
+ };
+
+ extraConfig = "include catppuccin-mocha";
+ };
+
+ xdg.configFile."zathura/catppuccin-mocha".source = pkgs.fetchurl {
+ url = "https://raw.githubusercontent.com/catppuccin/zathura/main/src/catppuccin-mocha";
+ hash = "sha256-/HXecio3My2eXTpY7JoYiN9mnXsps4PAThDPs4OCsAk=";
+ };
+}
diff --git a/foreign/dotfiles/home/shell/cli.nix b/foreign/dotfiles/home/shell/cli.nix
new file mode 100644
index 0000000..ef2dfde
--- /dev/null
+++ b/foreign/dotfiles/home/shell/cli.nix
@@ -0,0 +1,49 @@
+{pkgs, ...}: {
+ home.packages = with pkgs; [
+ # archives
+ zip
+ unzip
+ unrar
+
+ # utils
+ file
+ du-dust
+ duf
+ fd
+ ripgrep
+
+ # file managers
+ joshuto
+ ranger
+ ];
+
+ programs = {
+ bat = {
+ enable = true;
+ config = {
+ pager = "less -FR";
+ theme = "Catppuccin-mocha";
+ };
+ themes = {
+ Catppuccin-mocha = builtins.readFile (pkgs.fetchurl {
+ url = "https://raw.githubusercontent.com/catppuccin/bat/main/Catppuccin-mocha.tmTheme";
+ hash = "sha256-qMQNJGZImmjrqzy7IiEkY5IhvPAMZpq0W6skLLsng/w=";
+ });
+ };
+ };
+
+ btop.enable = true;
+ exa.enable = true;
+ ssh.enable = true;
+
+ skim = {
+ enable = true;
+ enableZshIntegration = true;
+ defaultCommand = "rg --files --hidden";
+ changeDirWidgetOptions = [
+ "--preview 'exa --icons --git --color always -T -L 3 {} | head -200'"
+ "--exact"
+ ];
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/shell/default.nix b/foreign/dotfiles/home/shell/default.nix
new file mode 100644
index 0000000..a2fe57a
--- /dev/null
+++ b/foreign/dotfiles/home/shell/default.nix
@@ -0,0 +1,28 @@
+{config, ...}: let
+ d = config.xdg.dataHome;
+ c = config.xdg.configHome;
+ cache = config.xdg.cacheHome;
+in {
+ imports = [
+ ./cli.nix
+ ./nushell
+ ./starship.nix
+ ./transient-services.nix
+ ./zsh.nix
+ ];
+
+ # add environment variables
+ home.sessionVariables = {
+ # clean up ~
+ LESSHISTFILE = cache + "/less/history";
+ LESSKEY = c + "/less/lesskey";
+ WINEPREFIX = d + "/wine";
+ XAUTHORITY = "$XDG_RUNTIME_DIR/Xauthority";
+
+ # enable scrolling in git diff
+ DELTA_PAGER = "less -R";
+
+ EDITOR = "hx";
+ MANPAGER = "sh -c 'col -bx | bat -l man -p'";
+ };
+}
diff --git a/foreign/dotfiles/home/shell/nix.nix b/foreign/dotfiles/home/shell/nix.nix
new file mode 100644
index 0000000..b1ec809
--- /dev/null
+++ b/foreign/dotfiles/home/shell/nix.nix
@@ -0,0 +1,21 @@
+{
+ pkgs,
+ inputs,
+ ...
+}:
+# nix tooling
+{
+ home.packages = with pkgs; [
+ alejandra
+ deadnix
+ nix-index
+ statix
+ inputs.self.packages.${pkgs.hostPlatform.system}.repl
+ ];
+
+ programs.direnv = {
+ enable = true;
+ nix-direnv.enable = true;
+ enableZshIntegration = true;
+ };
+}
diff --git a/foreign/dotfiles/home/shell/nushell/config.nu b/foreign/dotfiles/home/shell/nushell/config.nu
new file mode 100644
index 0000000..cfd0195
--- /dev/null
+++ b/foreign/dotfiles/home/shell/nushell/config.nu
@@ -0,0 +1,514 @@
+# Nushell Config File
+
+module completions {
+ # Custom completions for external commands (those outside of Nushell)
+ # Each completions has two parts: the form of the external command, including its flags and parameters
+ # and a helper command that knows how to complete values for those flags and parameters
+ #
+ # This is a simplified version of completions for git branches and git remotes
+ def "nu-complete git branches" [] {
+ ^git branch | lines | each { |line| $line | str replace '[\*\+] ' '' | str trim }
+ }
+
+ def "nu-complete git remotes" [] {
+ ^git remote | lines | each { |line| $line | str trim }
+ }
+
+ # Download objects and refs from another repository
+ export extern "git fetch" [
+ repository?: string@"nu-complete git remotes" # name of the repository to fetch
+ branch?: string@"nu-complete git branches" # name of the branch to fetch
+ --all # Fetch all remotes
+ --append(-a) # Append ref names and object names to .git/FETCH_HEAD
+ --atomic # Use an atomic transaction to update local refs.
+ --depth: int # Limit fetching to n commits from the tip
+ --deepen: int # Limit fetching to n commits from the current shallow boundary
+ --shallow-since: string # Deepen or shorten the history by date
+ --shallow-exclude: string # Deepen or shorten the history by branch/tag
+ --unshallow # Fetch all available history
+ --update-shallow # Update .git/shallow to accept new refs
+ --negotiation-tip: string # Specify which commit/glob to report while fetching
+ --negotiate-only # Do not fetch, only print common ancestors
+ --dry-run # Show what would be done
+ --write-fetch-head # Write fetched refs in FETCH_HEAD (default)
+ --no-write-fetch-head # Do not write FETCH_HEAD
+ --force(-f) # Always update the local branch
+ --keep(-k) # Keep dowloaded pack
+ --multiple # Allow several arguments to be specified
+ --auto-maintenance # Run 'git maintenance run --auto' at the end (default)
+ --no-auto-maintenance # Don't run 'git maintenance' at the end
+ --auto-gc # Run 'git maintenance run --auto' at the end (default)
+ --no-auto-gc # Don't run 'git maintenance' at the end
+ --write-commit-graph # Write a commit-graph after fetching
+ --no-write-commit-graph # Don't write a commit-graph after fetching
+ --prefetch # Place all refs into the refs/prefetch/ namespace
+ --prune(-p) # Remove obsolete remote-tracking references
+ --prune-tags(-P) # Remove any local tags that do not exist on the remote
+ --no-tags(-n) # Disable automatic tag following
+ --refmap: string # Use this refspec to map the refs to remote-tracking branches
+ --tags(-t) # Fetch all tags
+ --recurse-submodules: string # Fetch new commits of populated submodules (yes/on-demand/no)
+ --jobs(-j): int # Number of parallel children
+ --no-recurse-submodules # Disable recursive fetching of submodules
+ --set-upstream # Add upstream (tracking) reference
+ --submodule-prefix: string # Prepend to paths printed in informative messages
+ --upload-pack: string # Non-default path for remote command
+ --quiet(-q) # Silence internally used git commands
+ --verbose(-v) # Be verbose
+ --progress # Report progress on stderr
+ --server-option(-o): string # Pass options for the server to handle
+ --show-forced-updates # Check if a branch is force-updated
+ --no-show-forced-updates # Don't check if a branch is force-updated
+ -4 # Use IPv4 addresses, ignore IPv6 addresses
+ -6 # Use IPv6 addresses, ignore IPv4 addresses
+ --help # Display this help message
+ ]
+
+ # Check out git branches and files
+ export extern "git checkout" [
+ ...targets: string@"nu-complete git branches" # name of the branch or files to checkout
+ --conflict: string # conflict style (merge or diff3)
+ --detach(-d) # detach HEAD at named commit
+ --force(-f) # force checkout (throw away local modifications)
+ --guess # second guess 'git checkout ' (default)
+ --ignore-other-worktrees # do not check if another worktree is holding the given ref
+ --ignore-skip-worktree-bits # do not limit pathspecs to sparse entries only
+ --merge(-m) # perform a 3-way merge with the new branch
+ --orphan: string # new unparented branch
+ --ours(-2) # checkout our version for unmerged files
+ --overlay # use overlay mode (default)
+ --overwrite-ignore # update ignored files (default)
+ --patch(-p) # select hunks interactively
+ --pathspec-from-file: string # read pathspec from file
+ --progress # force progress reporting
+ --quiet(-q) # suppress progress reporting
+ --recurse-submodules: string # control recursive updating of submodules
+ --theirs(-3) # checkout their version for unmerged files
+ --track(-t) # set upstream info for new branch
+ -b: string # create and checkout a new branch
+ -B: string # create/reset and checkout a branch
+ -l # create reflog for new branch
+ --help # Display this help message
+ ]
+
+ # Push changes
+ export extern "git push" [
+ remote?: string@"nu-complete git remotes", # the name of the remote
+ ...refs: string@"nu-complete git branches" # the branch / refspec
+ --all # push all refs
+ --atomic # request atomic transaction on remote side
+ --delete(-d) # delete refs
+ --dry-run(-n) # dry run
+ --exec: string # receive pack program
+ --follow-tags # push missing but relevant tags
+ --force-with-lease # require old value of ref to be at this value
+ --force(-f) # force updates
+ --ipv4(-4) # use IPv4 addresses only
+ --ipv6(-6) # use IPv6 addresses only
+ --mirror # mirror all refs
+ --no-verify # bypass pre-push hook
+ --porcelain # machine-readable output
+ --progress # force progress reporting
+ --prune # prune locally removed refs
+ --push-option(-o): string # option to transmit
+ --quiet(-q) # be more quiet
+ --receive-pack: string # receive pack program
+ --recurse-submodules: string # control recursive pushing of submodules
+ --repo: string # repository
+ --set-upstream(-u) # set upstream for git pull/status
+ --signed: string # GPG sign the push
+ --tags # push tags (can't be used with --all or --mirror)
+ --thin # use thin pack
+ --verbose(-v) # be more verbose
+ --help # Display this help message
+ ]
+}
+
+# Get just the extern definitions without the custom completion commands
+use completions *
+
+# for more information on themes see
+# https://www.nushell.sh/book/coloring_and_theming.html
+let dark_theme = {
+ # color for nushell primitives
+ separator: white
+ leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off
+ header: green_bold
+ empty: blue
+ bool: white
+ int: white
+ filesize: white
+ duration: white
+ date: white
+ range: white
+ float: white
+ string: white
+ nothing: white
+ binary: white
+ cellpath: white
+ row_index: green_bold
+ record: white
+ list: white
+ block: white
+ hints: dark_gray
+
+ # shapes are used to change the cli syntax highlighting
+ shape_garbage: { fg: "#FFFFFF" bg: "#FF0000" attr: b}
+ shape_binary: purple_bold
+ shape_bool: light_cyan
+ shape_int: purple_bold
+ shape_float: purple_bold
+ shape_range: yellow_bold
+ shape_internalcall: cyan_bold
+ shape_external: cyan
+ shape_externalarg: green_bold
+ shape_literal: blue
+ shape_operator: yellow
+ shape_signature: green_bold
+ shape_string: green
+ shape_string_interpolation: cyan_bold
+ shape_datetime: cyan_bold
+ shape_list: cyan_bold
+ shape_table: blue_bold
+ shape_record: cyan_bold
+ shape_block: blue_bold
+ shape_filepath: cyan
+ shape_globpattern: cyan_bold
+ shape_variable: purple
+ shape_flag: blue_bold
+ shape_custom: green
+ shape_nothing: light_cyan
+}
+
+let light_theme = {
+ # color for nushell primitives
+ separator: dark_gray
+ leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off
+ header: green_bold
+ empty: blue
+ bool: dark_gray
+ int: dark_gray
+ filesize: dark_gray
+ duration: dark_gray
+ date: dark_gray
+ range: dark_gray
+ float: dark_gray
+ string: dark_gray
+ nothing: dark_gray
+ binary: dark_gray
+ cellpath: dark_gray
+ row_index: green_bold
+ record: white
+ list: white
+ block: white
+ hints: dark_gray
+
+ # shapes are used to change the cli syntax highlighting
+ shape_garbage: { fg: "#FFFFFF" bg: "#FF0000" attr: b}
+ shape_binary: purple_bold
+ shape_bool: light_cyan
+ shape_int: purple_bold
+ shape_float: purple_bold
+ shape_range: yellow_bold
+ shape_internalcall: cyan_bold
+ shape_external: cyan
+ shape_externalarg: green_bold
+ shape_literal: blue
+ shape_operator: yellow
+ shape_signature: green_bold
+ shape_string: green
+ shape_string_interpolation: cyan_bold
+ shape_datetime: cyan_bold
+ shape_list: cyan_bold
+ shape_table: blue_bold
+ shape_record: cyan_bold
+ shape_block: blue_bold
+ shape_filepath: cyan
+ shape_globpattern: cyan_bold
+ shape_variable: purple
+ shape_flag: blue_bold
+ shape_custom: green
+ shape_nothing: light_cyan
+}
+
+# External completer example
+# let carapace_completer = {|spans|
+# carapace $spans.0 nushell $spans | from json
+# }
+
+
+# The default config record. This is where much of your global configuration is setup.
+let-env config = {
+ external_completer: $nothing # check 'carapace_completer' above to as example
+ filesize_metric: false # true => (KB, MB, GB), false => (KiB, MiB, GiB)
+ table_mode: rounded # basic, compact, compact_double, light, thin, with_love, rounded, reinforced, heavy, none, other
+ use_ls_colors: true
+ rm_always_trash: false
+ color_config: $dark_theme # if you want a light theme, replace `$dark_theme` to `$light_theme`
+ use_grid_icons: true
+ footer_mode: "25" # always, never, number_of_rows, auto
+ quick_completions: true # set this to false to prevent auto-selecting completions when only one remains
+ partial_completions: true # set this to false to prevent partial filling of the prompt
+ completion_algorithm: "prefix" # prefix, fuzzy
+ float_precision: 2
+ # buffer_editor: "emacs" # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.EDITOR and $env.VISUAL
+ use_ansi_coloring: true
+ filesize_format: "auto" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib, auto
+ edit_mode: emacs # emacs, vi
+ max_history_size: 10000 # Session has to be reloaded for this to take effect
+ sync_history_on_enter: true # Enable to share the history between multiple sessions, else you have to close the session to persist history to file
+ history_file_format: "plaintext" # "sqlite" or "plaintext"
+ shell_integration: true # enables terminal markers and a workaround to arrow keys stop working issue
+ table_index_mode: always # "always" show indexes, "never" show indexes, "auto" = show indexes when a table has "index" column
+ cd_with_abbreviations: false # set to true to allow you to do things like cd s/o/f and nushell expand it to cd some/other/folder
+ case_sensitive_completions: false # set to true to enable case-sensitive completions
+ enable_external_completion: true # set to false to prevent nushell looking into $env.PATH to find more suggestions, `false` recommended for WSL users as this look up my be very slow
+ max_external_completion_results: 100 # setting it lower can improve completion performance at the cost of omitting some options
+ # A strategy of managing table view in case of limited space.
+ table_trim: {
+ methodology: wrapping, # truncating
+ # A strategy which will be used by 'wrapping' methodology
+ wrapping_try_keep_words: true,
+ # A suffix which will be used with 'truncating' methodology
+ # truncating_suffix: "..."
+ }
+ show_banner: false # true or false to enable or disable the banner
+ show_clickable_links_in_ls: true # true or false to enable or disable clickable links in the ls listing. your terminal has to support links.
+
+ hooks: {
+ pre_prompt: [{
+ $nothing # replace with source code to run before the prompt is shown
+ }]
+ pre_execution: [{
+ $nothing # replace with source code to run before the repl input is run
+ }]
+ env_change: {
+ PWD: [{|before, after|
+ $nothing # replace with source code to run if the PWD environment is different since the last repl input
+ }]
+ }
+ }
+ menus: [
+ # Configuration for default nushell menus
+ # Note the lack of souce parameter
+ {
+ name: completion_menu
+ only_buffer_difference: false
+ marker: "| "
+ type: {
+ layout: columnar
+ columns: 4
+ col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
+ col_padding: 2
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ }
+ {
+ name: history_menu
+ only_buffer_difference: true
+ marker: "? "
+ type: {
+ layout: list
+ page_size: 10
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ }
+ {
+ name: help_menu
+ only_buffer_difference: true
+ marker: "? "
+ type: {
+ layout: description
+ columns: 4
+ col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
+ col_padding: 2
+ selection_rows: 4
+ description_rows: 10
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ }
+ # Example of extra menus created using a nushell source
+ # Use the source field to create a list of records that populates
+ # the menu
+ {
+ name: commands_menu
+ only_buffer_difference: false
+ marker: "# "
+ type: {
+ layout: columnar
+ columns: 4
+ col_width: 20
+ col_padding: 2
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ source: { |buffer, position|
+ $nu.scope.commands
+ | where command =~ $buffer
+ | each { |it| {value: $it.command description: $it.usage} }
+ }
+ }
+ {
+ name: vars_menu
+ only_buffer_difference: true
+ marker: "# "
+ type: {
+ layout: list
+ page_size: 10
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ source: { |buffer, position|
+ $nu.scope.vars
+ | where name =~ $buffer
+ | sort-by name
+ | each { |it| {value: $it.name description: $it.type} }
+ }
+ }
+ {
+ name: commands_with_description
+ only_buffer_difference: true
+ marker: "# "
+ type: {
+ layout: description
+ columns: 4
+ col_width: 20
+ col_padding: 2
+ selection_rows: 4
+ description_rows: 10
+ }
+ style: {
+ text: green
+ selected_text: green_reverse
+ description_text: yellow
+ }
+ source: { |buffer, position|
+ $nu.scope.commands
+ | where command =~ $buffer
+ | each { |it| {value: $it.command description: $it.usage} }
+ }
+ }
+ ]
+ keybindings: [
+ {
+ name: completion_menu
+ modifier: none
+ keycode: tab
+ mode: emacs # Options: emacs vi_normal vi_insert
+ event: {
+ until: [
+ { send: menu name: completion_menu }
+ { send: menunext }
+ ]
+ }
+ }
+ {
+ name: completion_previous
+ modifier: shift
+ keycode: backtab
+ mode: [emacs, vi_normal, vi_insert] # Note: You can add the same keybinding to all modes by using a list
+ event: { send: menuprevious }
+ }
+ {
+ name: history_menu
+ modifier: control
+ keycode: char_r
+ mode: emacs
+ event: { send: menu name: history_menu }
+ }
+ {
+ name: next_page
+ modifier: control
+ keycode: char_x
+ mode: emacs
+ event: { send: menupagenext }
+ }
+ {
+ name: undo_or_previous_page
+ modifier: control
+ keycode: char_z
+ mode: emacs
+ event: {
+ until: [
+ { send: menupageprevious }
+ { edit: undo }
+ ]
+ }
+ }
+ {
+ name: yank
+ modifier: control
+ keycode: char_y
+ mode: emacs
+ event: {
+ until: [
+ {edit: pastecutbufferafter}
+ ]
+ }
+ }
+ {
+ name: unix-line-discard
+ modifier: control
+ keycode: char_u
+ mode: [emacs, vi_normal, vi_insert]
+ event: {
+ until: [
+ {edit: cutfromlinestart}
+ ]
+ }
+ }
+ {
+ name: kill-line
+ modifier: control
+ keycode: char_k
+ mode: [emacs, vi_normal, vi_insert]
+ event: {
+ until: [
+ {edit: cuttolineend}
+ ]
+ }
+ }
+ # Keybindings used to trigger the user defined menus
+ {
+ name: commands_menu
+ modifier: control
+ keycode: char_t
+ mode: [emacs, vi_normal, vi_insert]
+ event: { send: menu name: commands_menu }
+ }
+ {
+ name: vars_menu
+ modifier: alt
+ keycode: char_o
+ mode: [emacs, vi_normal, vi_insert]
+ event: { send: menu name: vars_menu }
+ }
+ {
+ name: commands_with_description
+ modifier: control
+ keycode: char_s
+ mode: [emacs, vi_normal, vi_insert]
+ event: { send: menu name: commands_with_description }
+ }
+ ]
+}
+
+source ~/.cache/starship/init.nu
diff --git a/foreign/dotfiles/home/shell/nushell/default.nix b/foreign/dotfiles/home/shell/nushell/default.nix
new file mode 100644
index 0000000..9165426
--- /dev/null
+++ b/foreign/dotfiles/home/shell/nushell/default.nix
@@ -0,0 +1,7 @@
+{
+ programs.nushell = {
+ enable = true;
+ configFile.source = ./config.nu;
+ envFile.source = ./env.nu;
+ };
+}
diff --git a/foreign/dotfiles/home/shell/nushell/env.nu b/foreign/dotfiles/home/shell/nushell/env.nu
new file mode 100644
index 0000000..f2e0c7f
--- /dev/null
+++ b/foreign/dotfiles/home/shell/nushell/env.nu
@@ -0,0 +1,36 @@
+# Nushell Environment Config File
+
+# Specifies how environment variables are:
+# - converted from a string to a value on Nushell startup (from_string)
+# - converted from a value back to a string when running external commands (to_string)
+# Note: The conversions happen *after* config.nu is loaded
+let-env ENV_CONVERSIONS = {
+ "PATH": {
+ from_string: { |s| $s | split row (char esep) | path expand -n }
+ to_string: { |v| $v | path expand -n | str join (char esep) }
+ }
+ "Path": {
+ from_string: { |s| $s | split row (char esep) | path expand -n }
+ to_string: { |v| $v | path expand -n | str join (char esep) }
+ }
+}
+
+# Directories to search for scripts when calling source or use
+#
+# By default, /scripts is added
+let-env NU_LIB_DIRS = [
+ ($nu.config-path | path dirname | path join 'scripts')
+]
+
+# Directories to search for plugin binaries when calling register
+#
+# By default, /plugins is added
+let-env NU_PLUGIN_DIRS = [
+ ($nu.config-path | path dirname | path join 'plugins')
+]
+
+# To add entries to PATH (on Windows you might use Path), you can use the following pattern:
+# let-env PATH = ($env.PATH | split row (char esep) | prepend '/some/path')
+
+mkdir ~/.cache/starship
+starship init nu | sed "s/size -c/size/" | save ~/.cache/starship/init.nu
diff --git a/foreign/dotfiles/home/shell/starship.nix b/foreign/dotfiles/home/shell/starship.nix
new file mode 100644
index 0000000..df99bfc
--- /dev/null
+++ b/foreign/dotfiles/home/shell/starship.nix
@@ -0,0 +1,13 @@
+{config, ...}: {
+ home.sessionVariables.STARSHIP_CACHE = "${config.xdg.cacheHome}/starship";
+
+ programs.starship = {
+ enable = true;
+ settings = {
+ character = {
+ success_symbol = "[›](bold green)";
+ error_symbol = "[›](bold red)";
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/shell/transient-services.nix b/foreign/dotfiles/home/shell/transient-services.nix
new file mode 100644
index 0000000..da5c1ec
--- /dev/null
+++ b/foreign/dotfiles/home/shell/transient-services.nix
@@ -0,0 +1,30 @@
+{
+ pkgs,
+ config,
+ lib,
+ ...
+}: let
+ apply-hm-env = pkgs.writeShellScript "apply-hm-env" ''
+ ${lib.optionalString (config.home.sessionPath != []) ''
+ export PATH=${builtins.concatStringsSep ":" config.home.sessionPath}:$PATH
+ ''}
+ ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (k: v: ''
+ export ${k}=${toString v}
+ '')
+ config.home.sessionVariables)}
+ ${config.home.sessionVariablesExtra}
+ exec "$@"
+ '';
+
+ # runs processes as systemd transient services
+ run-as-service = pkgs.writeShellScriptBin "run-as-service" ''
+ exec ${pkgs.systemd}/bin/systemd-run \
+ --slice=app-manual.slice \
+ --property=ExitType=cgroup \
+ --user \
+ --wait \
+ bash -lc "exec ${apply-hm-env} $@"
+ '';
+in {
+ home.packages = [run-as-service];
+}
diff --git a/foreign/dotfiles/home/shell/zsh.nix b/foreign/dotfiles/home/shell/zsh.nix
new file mode 100644
index 0000000..55ff509
--- /dev/null
+++ b/foreign/dotfiles/home/shell/zsh.nix
@@ -0,0 +1,76 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: {
+ programs.zsh = {
+ enable = true;
+ enableAutosuggestions = true;
+ autocd = true;
+ dirHashes = {
+ dl = "$HOME/Downloads";
+ docs = "$HOME/Documents";
+ code = "$HOME/Documents/code";
+ dots = "$HOME/Documents/code/dotfiles";
+ pics = "$HOME/Pictures";
+ vids = "$HOME/Videos";
+ nixpkgs = "$HOME/Documents/code/git/nixpkgs";
+ };
+ dotDir = ".config/zsh";
+ history = {
+ expireDuplicatesFirst = true;
+ path = "${config.xdg.dataHome}/zsh_history";
+ };
+
+ initExtra = ''
+ # search history based on what's typed in the prompt
+ autoload -U history-search-end
+ zle -N history-beginning-search-backward-end history-search-end
+ zle -N history-beginning-search-forward-end history-search-end
+ bindkey "^[OA" history-beginning-search-backward-end
+ bindkey "^[OB" history-beginning-search-forward-end
+
+ # case insensitive tab completion
+ zstyle ':completion:*' completer _complete _ignored _approximate
+ zstyle ':completion:*' list-colors '\'
+ zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
+ zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
+ zstyle ':completion:*' menu select
+ zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
+ zstyle ':completion:*' verbose true
+ _comp_options+=(globdots)
+
+ ${lib.optionalString config.services.gpg-agent.enable ''
+ gnupg_path=$(ls $XDG_RUNTIME_DIR/gnupg)
+ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/gnupg/$gnupg_path/S.gpg-agent.ssh"
+ ''}
+
+ ${lib.optionalString config.programs.kitty.enable ''
+ if test -n "$KITTY_INSTALLATION_DIR"; then
+ export KITTY_SHELL_INTEGRATION="enabled"
+ autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
+ kitty-integration
+ unfunction kitty-integration
+ fi
+ ''}
+
+ # run programs that are not in PATH with comma
+ command_not_found_handler() {
+ ${pkgs.comma}/bin/comma "$@"
+ }
+ '';
+
+ shellAliases = {
+ grep = "grep --color";
+ ip = "ip --color";
+ l = "exa -l";
+ la = "exa -la";
+ md = "mkdir -p";
+
+ us = "systemctl --user";
+ rs = "sudo systemctl";
+ };
+ shellGlobalAliases = {exa = "exa --icons --git";};
+ };
+}
diff --git a/foreign/dotfiles/home/terminals/alacritty.nix b/foreign/dotfiles/home/terminals/alacritty.nix
new file mode 100644
index 0000000..3fbb717
--- /dev/null
+++ b/foreign/dotfiles/home/terminals/alacritty.nix
@@ -0,0 +1,52 @@
+{default, ...}:
+# terminals
+let
+ inherit (default.terminal) font size opacity;
+ inherit (default) xcolors;
+in {
+ programs.alacritty = {
+ enable = true;
+ settings = {
+ window = {
+ decorations = "none";
+ dynamic_padding = true;
+ padding = {
+ x = 5;
+ y = 5;
+ };
+ startup_mode = "Maximized";
+ };
+
+ scrolling.history = 10000;
+
+ font = {
+ normal.family = font;
+ bold.family = font;
+ italic.family = font;
+ inherit size;
+ };
+
+ draw_bold_text_with_bright_colors = true;
+ colors = rec {
+ primary = {
+ background = xcolors.crust;
+ foreground = xcolors.fg;
+ };
+ normal = {
+ inherit (xcolors) red green yellow blue;
+ black = xcolors.mantle;
+ magenta = xcolors.mauve;
+ cyan = xcolors.sky;
+ white = xcolors.text;
+ };
+ bright =
+ normal
+ // {
+ black = xcolors.base;
+ white = xcolors.rosewater;
+ };
+ };
+ window.opacity = opacity;
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/terminals/kitty.nix b/foreign/dotfiles/home/terminals/kitty.nix
new file mode 100644
index 0000000..f72bf95
--- /dev/null
+++ b/foreign/dotfiles/home/terminals/kitty.nix
@@ -0,0 +1,54 @@
+{default, ...}: let
+ inherit (default) xcolors;
+in {
+ programs.kitty = {
+ enable = true;
+ font = {
+ inherit (default.terminal) size;
+ name = default.terminal.font;
+ };
+ settings = {
+ scrollback_lines = 10000;
+ placement_strategy = "center";
+
+ allow_remote_control = "yes";
+ enable_audio_bell = "no";
+ visual_bell_duration = "0.1";
+ visual_bell_color = xcolors.rosewater;
+
+ copy_on_select = "clipboard";
+
+ selection_foreground = "none";
+ selection_background = "none";
+
+ # colors
+ background_opacity = toString default.terminal.opacity;
+ foreground = xcolors.fg;
+ background = xcolors.crust;
+ # black
+ color0 = xcolors.mantle;
+ color8 = xcolors.base;
+ # red
+ color1 = xcolors.red;
+ color9 = xcolors.red;
+ # green
+ color2 = xcolors.green;
+ color10 = xcolors.green;
+ # yellow
+ color3 = xcolors.yellow;
+ color11 = xcolors.yellow;
+ # blue
+ color4 = xcolors.blue;
+ color12 = xcolors.blue;
+ # magenta
+ color5 = xcolors.pink;
+ color13 = xcolors.pink;
+ # cyan
+ color6 = xcolors.sky;
+ color14 = xcolors.sky;
+ # white
+ color7 = xcolors.text;
+ color15 = xcolors.rosewater;
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/terminals/wezterm.nix b/foreign/dotfiles/home/terminals/wezterm.nix
new file mode 100644
index 0000000..d914f7a
--- /dev/null
+++ b/foreign/dotfiles/home/terminals/wezterm.nix
@@ -0,0 +1,28 @@
+{default, ...}: {
+ programs.wezterm = {
+ enable = true;
+ extraConfig = ''
+ local wezterm = require "wezterm"
+
+ return {
+ font = wezterm.font_with_fallback({ "${default.terminal.font}", }, {
+ weight = "Regular",
+ }),
+ font_size = ${toString default.terminal.size},
+ color_scheme = "Catppuccin Mocha",
+ window_background_opacity = ${toString default.terminal.opacity},
+ enable_scroll_bar = false,
+ enable_tab_bar = false,
+ scrollback_lines = 10000,
+ window_padding = {
+ left = 10,
+ right = 10,
+ top = 10,
+ bottom = 10,
+ },
+ check_for_updates = false,
+ default_cursor_style = "SteadyBar",
+ }
+ '';
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/default.nix b/foreign/dotfiles/home/wayland/default.nix
new file mode 100644
index 0000000..5578f8c
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/default.nix
@@ -0,0 +1,68 @@
+{
+ pkgs,
+ lib,
+ inputs,
+ ...
+}:
+# Wayland config
+let
+ # use OCR and copy to clipboard
+ ocrScript = let
+ inherit (pkgs) grim libnotify slurp tesseract5 wl-clipboard;
+ _ = lib.getExe;
+ in
+ pkgs.writeShellScriptBin "wl-ocr" ''
+ ${_ grim} -g "$(${_ slurp})" -t ppm - | ${_ tesseract5} - - | ${wl-clipboard}/bin/wl-copy
+ ${_ libnotify} "$(${wl-clipboard}/bin/wl-paste)"
+ '';
+in {
+ imports = [
+ ../programs/eww
+ ./hyprland
+ ./sway.nix
+ ./swaybg.nix
+ ./swayidle.nix
+ ./swaylock.nix
+ ];
+
+ programs.eww-hyprland = {
+ enable = true;
+ package = inputs.eww.packages.${pkgs.hostPlatform.system}.eww-wayland;
+ };
+
+ home.packages = with pkgs; [
+ # screenshot
+ grim
+ slurp
+
+ # idle/lock
+ swaybg
+ swaylock-effects
+
+ # utils
+ ocrScript
+ wf-recorder
+ wl-clipboard
+ wlogout
+ wlr-randr
+ wofi
+ ];
+
+ # make stuff work on wayland
+ home.sessionVariables = {
+ QT_QPA_PLATFORM = "wayland";
+ SDL_VIDEODRIVER = "wayland";
+ XDG_SESSION_TYPE = "wayland";
+ };
+
+ programs.obs-studio.plugins = with pkgs.obs-studio-plugins; [wlrobs];
+
+ # fake a tray to let apps start
+ # https://github.com/nix-community/home-manager/issues/2064
+ systemd.user.targets.tray = {
+ Unit = {
+ Description = "Home Manager System Tray";
+ Requires = ["graphical-session-pre.target"];
+ };
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/hyprland/config.nix b/foreign/dotfiles/home/wayland/hyprland/config.nix
new file mode 100644
index 0000000..f743460
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/hyprland/config.nix
@@ -0,0 +1,248 @@
+{
+ config,
+ pkgs,
+ default,
+ ...
+}: let
+ inherit (default) colors;
+
+ pointer = config.home.pointerCursor;
+ homeDir = config.home.homeDirectory;
+
+ emoji = "${pkgs.wofi-emoji}/bin/wofi-emoji";
+ launcher = "wofi";
+in {
+ wayland.windowManager.hyprland.extraConfig = ''
+ $mod = SUPER
+
+ env = _JAVA_AWT_WM_NONREPARENTING,1
+ env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
+
+ # scale apps
+ env = GDK_SCALE,2
+ exec-once = xprop -root -f _XWAYLAND_GLOBAL_OUTPUT_SCALE 32c -set _XWAYLAND_GLOBAL_OUTPUT_SCALE 2
+
+ # set cursor for HL itself
+ exec-once = hyprctl setcursor ${pointer.name} ${toString pointer.size}
+
+ exec-once = systemctl --user start clight
+ exec-once = eww open bar
+
+ misc {
+ # disable auto polling for config file changes
+ disable_autoreload = true
+ focus_on_activate = true
+ # disable dragging animation
+ animate_mouse_windowdragging = false
+ }
+
+ # touchpad gestures
+ gestures {
+ workspace_swipe = true
+ workspace_swipe_forever = true
+ }
+
+ input {
+ kb_layout = ro
+
+ # focus change on cursor move
+ follow_mouse = 1
+ accel_profile = flat
+ touchpad {
+ scroll_factor = 0.3
+ }
+ }
+
+ device:MSFT0001:00 04F3:31EB Touchpad {
+ accel_profile = adaptive
+ natural_scroll = true
+ sensitivity = 0.1
+ }
+
+ general {
+ gaps_in = 5
+ gaps_out = 5
+ border_size = 2
+ col.active_border = rgb(${colors.blue}) rgb(${colors.mauve}) 270deg
+ col.inactive_border = rgb(${colors.crust}) rgb(${colors.lavender}) 270deg
+
+ # group borders
+ col.group_border_active = rgb(${colors.pink})
+ col.group_border = rgb(${colors.surface0})
+ }
+
+ decoration {
+ rounding = 16
+ blur = true
+ blur_size = 3
+ blur_passes = 3
+ blur_new_optimizations = true
+
+ drop_shadow = true
+ shadow_ignore_window = true
+ shadow_offset = 0 5
+ shadow_range = 50
+ shadow_render_power = 3
+ col.shadow = rgba(00000099)
+ }
+
+ animations {
+ enabled = true
+ animation = border, 1, 2, default
+ animation = fade, 1, 4, default
+ animation = windows, 1, 3, default, popin 80%
+ animation = workspaces, 1, 2, default, slide
+ }
+
+ dwindle {
+ # keep floating dimentions while tiling
+ pseudotile = true
+ preserve_split = true
+ }
+
+ # only allow shadows for floating windows
+ windowrulev2 = noshadow, floating:0
+
+ # telegram media viewer
+ windowrulev2 = float, title:^(Media viewer)$
+
+ # make Firefox PiP window floating and sticky
+ windowrulev2 = float, title:^(Picture-in-Picture)$
+ windowrulev2 = pin, title:^(Picture-in-Picture)$
+
+ # throw sharing indicators away
+ windowrulev2 = workspace special silent, title:^(Firefox — Sharing Indicator)$
+ windowrulev2 = workspace special silent, title:^(.*is sharing (your screen|a window)\.)$
+
+ # start spotify tiled in ws9
+ windowrulev2 = tile, class:^(Spotify)$
+ windowrulev2 = workspace 9 silent, class:^(Spotify)$
+
+ # start Discord/WebCord in ws2
+ windowrulev2 = workspace 2, title:^(.*(Disc|WebC)ord.*)$
+
+ # idle inhibit while watching videos
+ windowrulev2 = idleinhibit focus, class:^(mpv|.+exe)$
+ windowrulev2 = idleinhibit focus, class:^(firefox)$, title:^(.*YouTube.*)$
+ windowrulev2 = idleinhibit fullscreen, class:^(firefox)$
+
+ windowrulev2 = dimaround, class:^(gcr-prompter)$
+
+ # fix xwayland apps
+ windowrulev2 = rounding 0, xwayland:1, floating:1
+ windowrulev2 = center, class:^(.*jetbrains.*)$, title:^(Confirm Exit|Open Project|win424|win201|splash)$
+ windowrulev2 = size 640 400, class:^(.*jetbrains.*)$, title:^(splash)$
+
+ layerrule = blur, ^(gtk-layer-shell)$
+ layerrule = ignorezero, ^(gtk-layer-shell)$
+
+ # mouse movements
+ bindm = $mod, mouse:272, movewindow
+ bindm = $mod, mouse:273, resizewindow
+ bindm = $mod ALT, mouse:272, resizewindow
+
+ # compositor commands
+ bind = $mod SHIFT, E, exec, pkill Hyprland
+ bind = $mod, Q, killactive,
+ bind = $mod, F, fullscreen,
+ bind = $mod, G, togglegroup,
+ bind = $mod SHIFT, N, changegroupactive, f
+ bind = $mod SHIFT, P, changegroupactive, b
+ bind = $mod, R, togglesplit,
+ bind = $mod, T, togglefloating,
+ bind = $mod, P, pseudo,
+ bind = $mod ALT, ,resizeactive,
+ # toggle "monocle" (no_gaps_when_only)
+ $kw = dwindle:no_gaps_when_only
+ bind = $mod, M, exec, hyprctl keyword $kw $(($(hyprctl getoption $kw -j | jaq -r '.int') ^ 1))
+
+ # utility
+ # launcher
+ bindr = $mod, SUPER_L, exec, pkill .${launcher}-wrapped || run-as-service ${launcher}
+ # terminal
+ bind = $mod, Return, exec, run-as-service ${default.terminal.name}
+ # logout menu
+ bind = $mod, Escape, exec, wlogout -p layer-shell
+ # lock screen
+ bind = $mod, L, exec, loginctl lock-session
+ # emoji picker
+ bind = $mod, E, exec, ${emoji}
+ # select area to perform OCR on
+ bind = $mod, O, exec, run-as-service wl-ocr
+
+ # move focus
+ bind = $mod, left, movefocus, l
+ bind = $mod, right, movefocus, r
+ bind = $mod, up, movefocus, u
+ bind = $mod, down, movefocus, d
+
+ # window resize
+ bind = $mod, S, submap, resize
+
+ submap = resize
+ binde = , right, resizeactive, 10 0
+ binde = , left, resizeactive, -10 0
+ binde = , up, resizeactive, 0 -10
+ binde = , down, resizeactive, 0 10
+ bind = , escape, submap, reset
+ submap = reset
+
+ # media controls
+ bindl = , XF86AudioPlay, exec, playerctl play-pause
+ bindl = , XF86AudioPrev, exec, playerctl previous
+ bindl = , XF86AudioNext, exec, playerctl next
+
+ # volume
+ bindle = , XF86AudioRaiseVolume, exec, wpctl set-volume -l "1.0" @DEFAULT_AUDIO_SINK@ 6%+
+ binde = , XF86AudioRaiseVolume, exec, ${homeDir}/.config/eww/scripts/volume osd
+ bindle = , XF86AudioLowerVolume, exec, wpctl set-volume -l "1.0" @DEFAULT_AUDIO_SINK@ 6%-
+ binde = , XF86AudioLowerVolume, exec, ${homeDir}/.config/eww/scripts/volume osd
+ bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
+ bind = , XF86AudioMute, exec, ${homeDir}/.config/eww/scripts/volume osd
+ bindl = , XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
+
+ # backlight
+ bindle = , XF86MonBrightnessUp, exec, light -A 5
+ binde = , XF86MonBrightnessUp, exec, ${homeDir}/.config/eww/scripts/brightness osd
+ bindle = , XF86MonBrightnessDown, exec, light -U 5
+ binde = , XF86MonBrightnessDown, exec, ${homeDir}/.config/eww/scripts/brightness osd
+
+ # screenshot
+ # stop animations while screenshotting; makes black border go away
+ $screenshotarea = hyprctl keyword animation "fadeOut,0,0,default"; grimblast --notify copysave area; hyprctl keyword animation "fadeOut,1,4,default"
+ bind = , Print, exec, $screenshotarea
+ bind = $mod SHIFT, R, exec, $screenshotarea
+
+ bind = CTRL, Print, exec, grimblast --notify --cursor copysave output
+ bind = $mod SHIFT CTRL, R, exec, grimblast --notify --cursor copysave output
+
+ bind = ALT, Print, exec, grimblast --notify --cursor copysave screen
+ bind = $mod SHIFT ALT, R, exec, grimblast --notify --cursor copysave screen
+
+ # workspaces
+ # binds mod + [shift +] {1..10} to [move to] ws {1..10}
+ ${builtins.concatStringsSep "\n" (builtins.genList (
+ x: let
+ ws = let
+ c = (x + 1) / 10;
+ in
+ builtins.toString (x + 1 - (c * 10));
+ in ''
+ bind = $mod, ${ws}, workspace, ${toString (x + 1)}
+ bind = $mod SHIFT, ${ws}, movetoworkspace, ${toString (x + 1)}
+ ''
+ )
+ 10)}
+
+ # special workspace
+ bind = $mod SHIFT, grave, movetoworkspace, special
+ bind = $mod, grave, togglespecialworkspace, eDP-1
+
+ # cycle workspaces
+ bind = $mod, bracketleft, workspace, m-1
+ bind = $mod, bracketright, workspace, m+1
+ # cycle monitors
+ bind = $mod SHIFT, braceleft, focusmonitor, l
+ bind = $mod SHIFT, braceright, focusmonitor, r
+ '';
+}
diff --git a/foreign/dotfiles/home/wayland/hyprland/default.nix b/foreign/dotfiles/home/wayland/hyprland/default.nix
new file mode 100644
index 0000000..72745de
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/hyprland/default.nix
@@ -0,0 +1,20 @@
+{
+ inputs,
+ lib,
+ pkgs,
+ ...
+}: {
+ imports = [./config.nix];
+
+ home.packages = with pkgs; [
+ jaq
+ xorg.xprop
+ inputs.hyprland-contrib.packages.${pkgs.hostPlatform.system}.grimblast
+ ];
+
+ # start swayidle as part of hyprland, not sway
+ systemd.user.services.swayidle.Install.WantedBy = lib.mkForce ["hyprland-session.target"];
+
+ # enable hyprland
+ wayland.windowManager.hyprland.enable = true;
+}
diff --git a/foreign/dotfiles/home/wayland/sway.nix b/foreign/dotfiles/home/wayland/sway.nix
new file mode 100644
index 0000000..3a51634
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/sway.nix
@@ -0,0 +1,74 @@
+{
+ config,
+ pkgs,
+ lib,
+ inputs,
+ default,
+ ...
+}: {
+ wayland.windowManager.sway = {
+ enable = true;
+ package = inputs.self.packages.${pkgs.hostPlatform.system}.sway-hidpi;
+ config = {
+ keybindings = let
+ m = config.wayland.windowManager.sway.config.modifier;
+ in
+ lib.mkOptionDefault {
+ "${m}+Return" = "exec ${default.terminal.name}";
+ "${m}+q" = "kill";
+ "${m}+space" = "exec wofi";
+ "${m}+t" = "floating toggle";
+
+ # screenshots
+ "Print" = "grim -g \"$(slurp)\" - | wl-copy -t image/png";
+ "${m}+Shift+r" = "grim -g \"$(slurp)\" - | wl-copy -t image/png";
+ "Alt+Print" = "grim - | wl-copy -t image/png";
+ "${m}+Alt+Shift+r" = "grim - | wl-copy -t image/png";
+ };
+
+ keycodebindings = {
+ "--locked --no-repeat 121" = "exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; # mute
+ "--locked 122" = "exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 6%-"; # vol-
+ "--locked 123" = "exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 6%+"; # vol+
+ "--locked 171" = "exec playerctl next"; # next song
+ "--locked --no-repeat 172" = "exec playerctl play-pause"; # play/pause
+ "--locked 173" = "exec playerctl previous"; # prev song
+ "--locked --no-repeat 198" = "exec wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; # mic mute
+ "--locked 232" = "exec light -U 5"; # brightness-
+ "--locked 233" = "exec light -A 5"; # brightness+
+ };
+
+ menu = "wofi";
+ terminal = default.terminal.name;
+ modifier = "Mod4";
+ bars = [];
+
+ gaps = {
+ smartBorders = "on";
+ outer = 5;
+ inner = 5;
+ };
+
+ startup = [{command = "dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY";}];
+
+ input = {
+ "type:pointer" = {
+ accel_profile = "flat";
+ pointer_accel = "0";
+ };
+ "type:touchpad" = {
+ middle_emulation = "enabled";
+ natural_scroll = "enabled";
+ tap = "enabled";
+ };
+ };
+ output."*".bg = "~/.config/wallpaper.png fill";
+ };
+
+ extraConfig = ''
+ exec ${pkgs.xorg.xprop}/bin/xprop -root -f _XWAYLAND_GLOBAL_OUTPUT_SCALE 32c -set _XWAYLAND_GLOBAL_OUTPUT_SCALE 2
+ '';
+
+ wrapperFeatures.gtk = true;
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/swaybg.nix b/foreign/dotfiles/home/wayland/swaybg.nix
new file mode 100644
index 0000000..d6e6b27
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/swaybg.nix
@@ -0,0 +1,18 @@
+{
+ pkgs,
+ lib,
+ default,
+ ...
+}: {
+ systemd.user.services.swaybg = {
+ Unit = {
+ Description = "Wayland wallpaper daemon";
+ PartOf = ["graphical-session.target"];
+ };
+ Service = {
+ ExecStart = "${lib.getExe pkgs.swaybg} -i ${default.wallpaper} -m fill";
+ Restart = "on-failure";
+ };
+ Install.WantedBy = ["graphical-session.target"];
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/swayidle.nix b/foreign/dotfiles/home/wayland/swayidle.nix
new file mode 100644
index 0000000..bf98993
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/swayidle.nix
@@ -0,0 +1,34 @@
+{pkgs, ...}: let
+ suspendScript = pkgs.writeShellScript "suspend-script" ''
+ ${pkgs.pipewire}/bin/pw-cli i all | ${pkgs.ripgrep}/bin/rg running
+ # only suspend if audio isn't running
+ if [ $? == 1 ]; then
+ ${pkgs.systemd}/bin/systemctl suspend
+ fi
+ '';
+in {
+ # screen idle
+ services.swayidle = {
+ enable = true;
+ events = [
+ {
+ event = "before-sleep";
+ command = "${pkgs.systemd}/bin/loginctl lock-session";
+ }
+ {
+ event = "lock";
+ command = "${pkgs.swaylock-effects}/bin/swaylock -fF";
+ }
+ ];
+ timeouts = [
+ {
+ timeout = 310;
+ command = "${pkgs.systemd}/bin/loginctl lock-session";
+ }
+ {
+ timeout = 310;
+ command = suspendScript.outPath;
+ }
+ ];
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/swaylock.nix b/foreign/dotfiles/home/wayland/swaylock.nix
new file mode 100644
index 0000000..f8e161f
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/swaylock.nix
@@ -0,0 +1,31 @@
+{default, ...}: {
+ programs.swaylock.settings = let
+ inherit (default) xcolors;
+ in {
+ clock = true;
+ font = "Jost *";
+ image = default.wallpaper;
+ indicator = true;
+
+ bs-hl-color = xcolors.red;
+ key-hl-color = xcolors.text;
+ separator-color = xcolors.base;
+ text-color = xcolors.base;
+
+ inside-color = xcolors.mauve;
+ line-color = xcolors.mauve;
+ ring-color = xcolors.base;
+
+ inside-clear-color = xcolors.yellow;
+ line-clear-color = xcolors.yellow;
+ ring-clear-color = xcolors.base;
+
+ inside-ver-color = xcolors.lavender;
+ line-ver-color = xcolors.lavender;
+ ring-ver-color = xcolors.base;
+
+ inside-wrong-color = xcolors.red;
+ line-wrong-color = xcolors.red;
+ ring-wrong-color = xcolors.base;
+ };
+}
diff --git a/foreign/dotfiles/home/wayland/wayfire.nix b/foreign/dotfiles/home/wayland/wayfire.nix
new file mode 100644
index 0000000..5652f69
--- /dev/null
+++ b/foreign/dotfiles/home/wayland/wayfire.nix
@@ -0,0 +1,340 @@
+{
+ pkgs,
+ default,
+ ...
+}: let
+ inherit (default) xrgbaColors;
+in {
+ home.packages = [pkgs.wayfire];
+
+ xdg.configFile."wayfire.ini".text = ''
+ [alpha]
+ min_value = 0.100000
+ modifier =
+
+ [animate]
+ close_animation = fade
+ open_animation = zoom
+ startup_duration = 300
+ duration = 300
+ enabled_for = type equals "toplevel" | (type equals "x-or" & focusable equals true))
+
+ fade_duration = 400
+ fade_enabled_for = type equals "overlay"
+
+ [autostart]
+ 0_environment = dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY
+ 1_hm = systemctl --user start graphical-session.target
+ 2_eww = eww daemon
+
+ autostart_wf_shell = false
+ background = swaybg -i ~/.config/wallpaper.png
+ idle = swayidle -w \
+ timeout 360 'swaylock' \
+ before-sleep 'swaylock'
+ panel = eww open bar
+
+ [blur]
+ blur_by_default = type is "toplevel"
+
+ bokeh_degrade = 1
+ bokeh_iterations = 5
+ bokeh_offset = 5.000000
+
+ box_degrade = 1
+ box_iterations = 2
+ box_offset = 1.000000
+
+ gaussian_degrade = 1
+ gaussian_iterations = 2
+ gaussian_offset = 1.000000
+
+ kawase_degrade = 3
+ kawase_iterations = 3
+ kawase_offset = 1.000000
+
+ method = kawase
+ saturation = 1.000000
+ #toggle = KEY_B
+
+ [command]
+ binding_launcher = KEY_SPACE
+ binding_lock = KEY_L
+ binding_logout = KEY_ESC
+ binding_mute = KEY_MUTE
+ binding_mic_mute = KEY_F20
+ binding_next = KEY_NEXTSONG
+ binding_pause = KEY_PLAYPAUSE
+ binding_prev = KEY_PREVIOUSSONG
+ binding_screenshot = KEY_PRINT | KEY_R
+ binding_screenshot_interactive = KEY_PRINT | KEY_R
+ binding_terminal = KEY_ENTER
+
+ command_launcher = wofi --show=drun -I
+ command_light_down = light -U 5
+ command_light_up = light -A 5
+ command_lock = swaylock
+ command_logout = wlogout -p layer-shell
+ command_mute = wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
+ command_mic_mute = wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
+ command_next = playerctl next
+ command_pause = playerctl play-pause
+ command_prev = playerctl previous
+ command_screenshot = screenshot area
+ command_screenshot_interactive = screenshot monitor
+ command_terminal = ${default.terminal.name}
+ command_volume_down = pulsemixer --change-volume -6
+ command_volume_up = pulsemixer --change-volume +6
+
+ repeatable_binding_light_down = KEY_BRIGHTNESSDOWN
+ repeatable_binding_light_up = KEY_BRIGHTNESSUP
+ repeatable_binding_volume_down = KEY_VOLUMEDOWN
+ repeatable_binding_volume_up = KEY_VOLUMEUP
+
+ [core]
+ background_color = #${xrgbaColors.base00}
+ close_top_view = KEY_Q | KEY_F4
+ focus_button_with_modifiers = false
+ focus_buttons = BTN_LEFT | BTN_MIDDLE | BTN_RIGHT
+ focus_buttons_passthrough = true
+ plugins = autostart \
+ blur \
+ command \
+ decoration \
+ expo \
+ fast-switcher \
+ idle \
+ matcher \
+ move \
+ oswitch \
+ place \
+ resize \
+ simple-tile \
+ vswipe \
+ window-rules \
+ wrot \
+ zoom
+ vheight = 3
+ vwidth = 3
+ xwayland = true
+
+ [cube]
+ activate = BTN_LEFT
+ background = #${xrgbaColors.base00}
+ background_mode = simple
+ cubemap_image =
+ deform = 0
+ initial_animation = 350
+ light = true
+ rotate_left = KEY_LEFT
+ rotate_right = KEY_RIGHT
+ skydome_mirror = true
+ skydome_texture =
+ speed_spin_horiz = 0.020000
+ speed_spin_vert = 0.020000
+ speed_zoom = 0.070000
+ zoom = 0.100000
+
+ [decoration]
+ active_color = #${xrgbaColors.base00}
+ border_size = 0
+ button_order = minimize maximize close
+ font = Roboto
+ ignore_views = none
+ inactive_color = #${xrgbaColors.base04}
+ title_height = 20
+
+ [expo]
+ background = #${xrgbaColors.base00}
+ duration = 50
+ offset = 10
+ select_workspace_1 = KEY_1
+ select_workspace_2 = KEY_2
+ select_workspace_3 = KEY_3
+ select_workspace_4 = KEY_4
+ select_workspace_5 = KEY_5
+ select_workspace_6 = KEY_6
+ select_workspace_7 = KEY_7
+ select_workspace_8 = KEY_8
+ select_workspace_9 = KEY_9
+ toggle =
+
+ [extra-gestures]
+ close_fingers = 5
+ move_delay = 500
+ move_fingers = 3
+
+ [fast-switcher]
+ activate = KEY_ESC
+ activate_backward = KEY_ESC
+
+ [fisheye]
+ radius = 450.000000
+ toggle = KEY_F
+ zoom = 7.000000
+
+ [grid]
+ duration = 300
+ restore = KEY_DOWN | KEY_KP0
+ slot_b = KEY_KP2
+ slot_bl = KEY_KP1
+ slot_br = KEY_KP3
+ slot_c = KEY_UP | KEY_KP5
+ slot_l = KEY_LEFT | KEY_KP4
+ slot_r = KEY_RIGHT | KEY_KP6
+ slot_t = KEY_KP8
+ slot_tl = KEY_KP7
+ slot_tr = KEY_KP9
+ type = crossfade
+
+ [idle]
+ cube_max_zoom = 1.500000
+ cube_rotate_speed = 1.000000
+ cube_zoom_speed = 1000
+ disable_on_fullscreen = true
+ dpms_timeout = 300
+ screensaver_timeout = -1
+ toggle = none
+
+ [input]
+ click_method = default
+ cursor_size = 24
+ cursor_theme = "Bibata-Modern-Classic"
+ disable_touchpad_while_mouse = false
+ disable_touchpad_while_typing = false
+ gesture_sensitivity = 1.000000
+ middle_emulation = false
+ modifier_binding_timeout = 400
+ mouse_accel_profile = flat
+ natural_scroll = true
+ touchpad_scroll_speed = 0.3
+ xkb_layout = ro
+ xkb_rules = evdev
+
+ [invert]
+ preserve_hue = false
+ toggle = KEY_I
+
+ [move]
+ activate = BTN_LEFT
+ enable_snap = true
+ enable_snap_off = true
+ join_views = false
+ quarter_snap_threshold = 50
+ snap_off_threshold = 10
+ snap_threshold = 10
+ workspace_switch_after = -1
+
+ [oswitch]
+ next_output = KEY_O
+ next_output_with_win = KEY_O
+
+ [output]
+ mode = auto
+ position = auto
+ scale = 1.000000
+ transform = normal
+
+ [place]
+ mode = center
+
+ [preserve-output]
+ last_output_focus_timeout = 10000
+
+ [resize]
+ activate = BTN_RIGHT
+
+ [scale]
+ allow_zoom = false
+ bg_color = #${xrgbaColors.base00}
+ duration = 750
+ inactive_alpha = 0.750000
+ interact = false
+ middle_click_close = false
+ spacing = 50
+ text_color = #${xrgbaColors.base00}
+ title_font_size = 14
+ title_overlay = all
+ title_position = center
+ toggle = KEY_P
+ toggle_all =
+
+ [switcher]
+ next_view = KEY_TAB
+ prev_view = KEY_TAB
+ speed = 500
+ view_thumbnail_scale = 1.000000
+
+ [vswipe]
+ background = #${xrgbaColors.base00}
+ duration = 180
+ enable_smooth_transition = true
+ enable_vertical = true
+ fingers = 3
+ gap = 32.000000
+ speed_cap = 0.500000
+ speed_factor = 500.000000
+ #threshold = 0.250000
+
+ [vswitch]
+ background = #${xrgbaColors.base00}
+ binding_down = KEY_DOWN
+ binding_left = KEY_LEFT
+ binding_right = KEY_RIGHT
+ binding_up = KEY_UP
+ binding_win_down = KEY_DOWN
+ binding_win_left = KEY_LEFT
+ binding_win_right = KEY_RIGHT
+ binding_win_up = KEY_UP
+ duration = 300
+ gap = 20
+ wraparound = false
+
+ [window-rules]
+
+ [wm-actions]
+ minimize = none
+ toggle_always_on_top = none
+ toggle_fullscreen = KEY_F
+ toggle_maximize = none
+ toggle_showdesktop = none
+ toggle_sticky = none
+
+ [wobbly]
+ friction = 3.000000
+ grid_resolution = 6
+ spring_k = 8.000000
+
+ [workarounds]
+ all_dialogs_modal = true
+ app_id_mode = stock
+ dynamic_repaint_delay = false
+
+ [wrot]
+ activate = BTN_RIGHT
+ activate-3d = BTN_RIGHT
+ invert = false
+ reset = KEY_R
+ reset-one = KEY_R
+ reset_radius = 25.000000
+ sensitivity = 24
+
+ [zoom]
+ modifier =
+ smoothing_duration = 300
+ speed = 0.010000
+
+ [simple-tile]
+ button_move = BTN_LEFT
+ button_resize = BTN_RIGHT
+ inner_gap_size = 2
+ keep_fullscreen_on_adjacent = true
+ key_focus_above = KEY_K
+ key_focus_below = KEY_J
+ key_focus_left = KEY_H
+ key_focus_right = KEY_L
+ key_toggle = KEY_T
+ tile_by_default = type is "toplevel"
+ #wm-actions.toggle_always_on_top
+ '';
+}
diff --git a/foreign/dotfiles/hosts/README.md b/foreign/dotfiles/hosts/README.md
new file mode 100644
index 0000000..0a82810
--- /dev/null
+++ b/foreign/dotfiles/hosts/README.md
@@ -0,0 +1,9 @@
+# Hosts config
+
+Name | Description
+------------ | -----------
+`io` | Lenovo laptop, main machine
+`kiiro` | Previous main machine, retired and rarely used server now
+
+All the hosts have a shared config in `modules/minimal.nix`.
+Host specific configs are stored inside the specific host dir.
diff --git a/foreign/dotfiles/hosts/default.nix b/foreign/dotfiles/hosts/default.nix
new file mode 100644
index 0000000..b5317cb
--- /dev/null
+++ b/foreign/dotfiles/hosts/default.nix
@@ -0,0 +1,38 @@
+{
+ inputs,
+ withSystem,
+ sharedModules,
+ desktopModules,
+ homeImports,
+ ...
+}: {
+ flake.nixosConfigurations = withSystem "x86_64-linux" ({system, ...}: {
+ io = inputs.nixpkgs.lib.nixosSystem {
+ inherit system;
+
+ modules =
+ [
+ ./io
+ ../modules/greetd.nix
+ ../modules/desktop.nix
+ ../modules/gamemode.nix
+ ../modules/howdy
+ ../modules/linux-enable-ir-emitter.nix
+ {home-manager.users.mihai.imports = homeImports."mihai@io";}
+ ]
+ ++ sharedModules
+ ++ desktopModules;
+ };
+
+ kiiro = inputs.nixpkgs.lib.nixosSystem {
+ inherit system;
+
+ modules =
+ [
+ ./kiiro
+ {home-manager.users.mihai.imports = homeImports.server;}
+ ]
+ ++ sharedModules;
+ };
+ });
+}
diff --git a/foreign/dotfiles/hosts/io/default.nix b/foreign/dotfiles/hosts/io/default.nix
new file mode 100644
index 0000000..d7ab090
--- /dev/null
+++ b/foreign/dotfiles/hosts/io/default.nix
@@ -0,0 +1,162 @@
+{
+ config,
+ pkgs,
+ inputs,
+ ...
+} @ args: {
+ imports = [./hardware-configuration.nix];
+
+ age.secrets.spotify = {
+ file = "${inputs.self}/secrets/spotify.age";
+ owner = "mihai";
+ group = "users";
+ };
+
+ boot = {
+ initrd = {
+ systemd.enable = true;
+ supportedFilesystems = ["ext4"];
+ };
+
+ # load modules on boot
+ kernelModules = ["acpi_call" "amdgpu" "amd_pstate"];
+
+ # use latest kernel
+ kernelPackages = pkgs.linuxPackages_xanmod_latest;
+ # Panel Self Refresh
+ kernelParams = ["amdgpu.dcfeaturemask=0x8" "initcall_blacklist=acpi_cpufreq_init" "amd_pstate=passive" "amd_pstate.shared_mem=1"];
+
+ loader = {
+ # systemd-boot on UEFI
+ efi.canTouchEfiVariables = true;
+ systemd-boot.enable = true;
+ };
+
+ plymouth = {
+ enable = true;
+ themePackages = [inputs.self.packages.${pkgs.hostPlatform.system}.catppuccin-plymouth];
+ # theme = "catppuccin-mocha";
+ # font = "${pkgs.noto-fonts}/share/fonts/truetype/noto/NotoSans-Light.ttf";
+ };
+ };
+
+ environment.systemPackages = [config.boot.kernelPackages.cpupower];
+
+ hardware = {
+ bluetooth = {
+ enable = true;
+ # battery info support
+ package = pkgs.bluez5-experimental;
+ settings = {
+ # make Xbox Series X controller work
+ General = {
+ Class = "0x000100";
+ ControllerMode = "bredr";
+ FastConnectable = true;
+ JustWorksRepairing = "always";
+ Privacy = "device";
+ Experimental = true;
+ };
+ };
+ };
+
+ cpu.amd.updateMicrocode = true;
+
+ enableRedistributableFirmware = true;
+
+ opentabletdriver.enable = true;
+
+ video.hidpi.enable = true;
+
+ xpadneo.enable = true;
+ };
+
+ networking = {
+ hostName = "io";
+ firewall = {
+ allowedTCPPorts = [42355];
+ allowedUDPPorts = [5353];
+ };
+ };
+
+ programs = {
+ # enable hyprland and required options
+ hyprland.enable = true;
+
+ # backlight control
+ light.enable = true;
+
+ steam.enable = true;
+ };
+
+ security.tpm2 = {
+ enable = true;
+ abrmd.enable = true;
+ };
+
+ services = {
+ # for SSD/NVME
+ fstrim.enable = true;
+
+ howdy = {
+ enable = true;
+ package = inputs.self.packages.${pkgs.system}.howdy;
+ settings = {
+ core.no_confirmation = true;
+ video.device_path = "/dev/video2";
+ video.dark_threshold = 90;
+ };
+ };
+
+ linux-enable-ir-emitter.enable = true;
+
+ kmonad.keyboards = {
+ io = {
+ name = "io";
+ device = "/dev/input/by-path/platform-i8042-serio-0-event-kbd";
+ defcfg = {
+ enable = true;
+ fallthrough = true;
+ allowCommands = false;
+ };
+ config = builtins.readFile "${inputs.self}/modules/main.kbd";
+ };
+ };
+
+ # see https://github.com/fufexan/nix-gaming/#pipewire-low-latency
+ pipewire.lowLatency.enable = true;
+
+ printing.enable = true;
+
+ # configure mice
+ ratbagd.enable = true;
+
+ # power saving
+ tlp = {
+ enable = true;
+ settings = {
+ PCIE_ASPM_ON_BAT = "powersupersave";
+ CPU_SCALING_GOVERNOR_ON_AC = "performance";
+ CPU_SCALING_GOVERNOR_ON_BAT = "conservative";
+ NMI_WATCHDOG = 0;
+ };
+ };
+
+ udev.extraRules = let
+ inherit (import ./plugged.nix args) plugged unplugged;
+ in ''
+ # add my android device to adbusers
+ SUBSYSTEM=="usb", ATTR{idVendor}=="22d9", MODE="0666", GROUP="adbusers"
+
+ # start/stop services on power (un)plug
+ SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="${plugged}"
+ SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="${unplugged}"
+ '';
+
+ # add hyprland to display manager sessions
+ xserver.displayManager.sessionPackages = [inputs.hyprland.packages.${pkgs.hostPlatform.system}.default];
+ };
+
+ # https://github.com/NixOS/nixpkgs/issues/114222
+ systemd.user.services.telephony_client.enable = false;
+}
diff --git a/foreign/dotfiles/hosts/io/hardware-configuration.nix b/foreign/dotfiles/hosts/io/hardware-configuration.nix
new file mode 100644
index 0000000..52c35a3
--- /dev/null
+++ b/foreign/dotfiles/hosts/io/hardware-configuration.nix
@@ -0,0 +1,35 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{
+ config,
+ lib,
+ modulesPath,
+ ...
+}: {
+ imports = [
+ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "usb_storage" "sd_mod"];
+ boot.initrd.kernelModules = [];
+ boot.kernelModules = ["kvm-amd"];
+ boot.extraModulePackages = [];
+
+ fileSystems."/" = {
+ device = "/dev/disk/by-label/nixos";
+ fsType = "ext4";
+ };
+
+ fileSystems."/boot" = {
+ device = "/dev/disk/by-label/boot";
+ fsType = "vfat";
+ };
+
+ swapDevices = [];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+ # high-resolution display
+ hardware.video.hidpi.enable = lib.mkDefault true;
+}
diff --git a/foreign/dotfiles/hosts/io/plugged.nix b/foreign/dotfiles/hosts/io/plugged.nix
new file mode 100644
index 0000000..008a020
--- /dev/null
+++ b/foreign/dotfiles/hosts/io/plugged.nix
@@ -0,0 +1,24 @@
+{
+ pkgs,
+ lib,
+ inputs,
+ ...
+}: let
+ programs = lib.makeBinPath [inputs.hyprland.packages.${pkgs.hostPlatform.system}.default];
+in {
+ unplugged = pkgs.writeShellScript "unplugged" ''
+ export PATH=$PATH:${programs}
+ export HYPRLAND_INSTANCE_SIGNATURE=$(ls -w1 /tmp/hypr | tail -1)
+
+ systemctl --user --machine=1000@ stop easyeffects syncthing
+ hyprctl --batch 'keyword decoration:drop_shadow 0 ; keyword animations:enabled 0'
+ '';
+
+ plugged = pkgs.writeShellScript "plugged" ''
+ export PATH=$PATH:${programs}
+ export HYPRLAND_INSTANCE_SIGNATURE=$(ls -w1 /tmp/hypr | tail -1)
+
+ systemctl --user --machine=1000@ start easyeffects syncthing
+ hyprctl --batch 'keyword decoration:drop_shadow 1 ; keyword animations:enabled 1'
+ '';
+}
diff --git a/foreign/dotfiles/hosts/kiiro/default.nix b/foreign/dotfiles/hosts/kiiro/default.nix
new file mode 100644
index 0000000..20580f0
--- /dev/null
+++ b/foreign/dotfiles/hosts/kiiro/default.nix
@@ -0,0 +1,31 @@
+# biggest homeserver
+{lib, ...}: {
+ imports = [
+ ./hardware-configuration.nix
+ ./services.nix
+ ];
+
+ # used by tailscale for exit node
+ boot.kernel.sysctl = {
+ "net.ipv4.ip_forward" = 1;
+ "net.ipv6.conf.all.forwarding" = 1;
+ };
+
+ # bootloader
+ boot.loader = {
+ efi.canTouchEfiVariables = true;
+ systemd-boot.enable = true;
+ };
+
+ hardware = {
+ cpu.intel.updateMicrocode = true;
+
+ enableRedistributableFirmware = true;
+ };
+
+ networking.hostName = "kiiro";
+
+ services.btrfs.autoScrub.enable = true;
+
+ system.stateVersion = lib.mkForce "21.11";
+}
diff --git a/foreign/dotfiles/hosts/kiiro/hardware-configuration.nix b/foreign/dotfiles/hosts/kiiro/hardware-configuration.nix
new file mode 100644
index 0000000..96d30d1
--- /dev/null
+++ b/foreign/dotfiles/hosts/kiiro/hardware-configuration.nix
@@ -0,0 +1,30 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{modulesPath, ...}: {
+ imports = [
+ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = ["ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod"];
+ boot.initrd.kernelModules = [];
+ boot.kernelModules = ["kvm-intel"];
+ boot.extraModulePackages = [];
+
+ fileSystems."/" = {
+ device = "/dev/disk/by-uuid/a2fc0125-f3e6-4984-9eff-9010c154cb7b";
+ fsType = "btrfs";
+ options = ["subvol=root"];
+ };
+
+ fileSystems."/persist" = {
+ device = "/dev/disk/by-uuid/a2fc0125-f3e6-4984-9eff-9010c154cb7b";
+ fsType = "btrfs";
+ options = ["subvol=persist"];
+ };
+
+ fileSystems."/boot" = {
+ device = "/dev/disk/by-uuid/7B5A-471E";
+ fsType = "vfat";
+ };
+}
diff --git a/foreign/dotfiles/hosts/kiiro/services.nix b/foreign/dotfiles/hosts/kiiro/services.nix
new file mode 100644
index 0000000..d13cf0a
--- /dev/null
+++ b/foreign/dotfiles/hosts/kiiro/services.nix
@@ -0,0 +1,67 @@
+# server services
+{
+ security.acme.defaults = {
+ email = "fufexan@protonmail.com";
+ server = "https://acme-staging-v02.api.letsencrypt.org/directory";
+ };
+
+ services.minecraft-server = {
+ enable = false;
+ eula = true;
+ jvmOpts = ''
+ -Xmx6G -Xms1G -XX:+UseG1GC
+ -XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2
+ '';
+ openFirewall = true;
+ };
+
+ services.nginx = {
+ enable = false;
+
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedProxySettings = true;
+ recommendedTlsSettings = true;
+
+ sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
+
+ virtualHosts = {
+ "jellyfin.fufexan.net" = {
+ forceSSL = true;
+ enableACME = true;
+
+ locations."= /".return = "302 https://$host/web";
+ locations."/" = {
+ proxyPass = "http://127.0.0.1:8096";
+ extraConfig = "proxy_buffering off";
+ };
+ locations."= /web/".proxyPass = "http://127.0.0.1:8096/web/index.html";
+ locations."/socket" = {
+ proxyPass = "http://127.0.0.1:8096";
+ proxyWebsockets = true;
+ extraConfig = ''
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ '';
+ };
+ };
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = [80 139 443 445 5357 8384 8443];
+ networking.firewall.allowedUDPPorts = [137 138 3702];
+
+ services.syncthing = {
+ enable = true;
+ group = "users";
+ guiAddress = ":8384";
+ openDefaultPorts = true;
+ declarative = {};
+ };
+
+ services.transmission = {
+ openFirewall = true;
+ settings.rpc-bind-address = "0.0.0.0";
+ settings.rpc-whitelist-enables = false;
+ };
+}
diff --git a/foreign/dotfiles/lib/README.md b/foreign/dotfiles/lib/README.md
new file mode 100644
index 0000000..716632e
--- /dev/null
+++ b/foreign/dotfiles/lib/README.md
@@ -0,0 +1,9 @@
+# Lib
+
+Various functions I use throughout the config:
+
+Name | Description
+------------- | -----------
+`colors.nix` | Functions for dealing with colors. Used for `default`.
+`default.nix` | Module for flake-parts
+`repl.nix` | Cool Nix REPL wrapper
diff --git a/foreign/dotfiles/lib/colors.nix b/foreign/dotfiles/lib/colors.nix
new file mode 100644
index 0000000..10bf428
--- /dev/null
+++ b/foreign/dotfiles/lib/colors.nix
@@ -0,0 +1,67 @@
+lib:
+with lib; rec {
+ # color-related functions
+
+ # convert rrggbb hex to #rrggbb
+ x = c: "#${c}";
+
+ # convert rrggbb hex to rgba(r, g, b, a) css
+ rgba = c: let
+ r = toString (hexToDec (__substring 0 2 c));
+ g = toString (hexToDec (__substring 2 2 c));
+ b = toString (hexToDec (__substring 4 2 c));
+ res = "rgba(${r}, ${g}, ${b}, .5)";
+ in
+ res;
+
+ # general stuff
+
+ # functions copied from https://gist.github.com/corpix/f761c82c9d6fdbc1b3846b37e1020e11
+ # convert a hex value to an integer
+ hexToDec = v: let
+ hexToInt = {
+ "0" = 0;
+ "1" = 1;
+ "2" = 2;
+ "3" = 3;
+ "4" = 4;
+ "5" = 5;
+ "6" = 6;
+ "7" = 7;
+ "8" = 8;
+ "9" = 9;
+ "a" = 10;
+ "b" = 11;
+ "c" = 12;
+ "d" = 13;
+ "e" = 14;
+ "f" = 15;
+ "A" = 10;
+ "B" = 11;
+ "C" = 12;
+ "D" = 13;
+ "E" = 14;
+ "F" = 15;
+ };
+ chars = stringToCharacters v;
+ charsLen = length chars;
+ in
+ foldl
+ (a: v: a + v)
+ 0
+ (imap0
+ (k: v: hexToInt."${v}" * (pow 16 (charsLen - k - 1)))
+ chars);
+
+ pow = let
+ pow' = base: exponent: value:
+ # FIXME: It will silently overflow on values > 2**62 :(
+ # The value will become negative or zero in this case
+ if exponent == 0
+ then 1
+ else if exponent <= 1
+ then value
+ else (pow' base (exponent - 1) (value * base));
+ in
+ base: exponent: pow' base exponent base;
+}
diff --git a/foreign/dotfiles/lib/default.nix b/foreign/dotfiles/lib/default.nix
new file mode 100644
index 0000000..bdfb630
--- /dev/null
+++ b/foreign/dotfiles/lib/default.nix
@@ -0,0 +1,41 @@
+{inputs, ...}:
+# personal lib
+let
+ inherit (inputs.nixpkgs) lib;
+
+ colorlib = import ./colors.nix lib;
+ default = import ./theme {inherit colorlib lib;};
+in {
+ imports = [
+ {_module.args = {inherit default;};}
+ ];
+
+ perSystem = {system, ...}: {
+ legacyPackages = import inputs.nixpkgs {
+ inherit system;
+ config.allowUnfree = true;
+ config.overlays = [
+ (
+ _: prev: {
+ steam = prev.steam.override {
+ extraPkgs = pkgs:
+ with pkgs; [
+ keyutils
+ libkrb5
+ libpng
+ libpulseaudio
+ libvorbis
+ stdenv.cc.cc.lib
+ xorg.libXcursor
+ xorg.libXi
+ xorg.libXinerama
+ xorg.libXScrnSaver
+ ];
+ extraProfile = "export GDK_SCALE=2";
+ };
+ }
+ )
+ ];
+ };
+ };
+}
diff --git a/foreign/dotfiles/lib/repl.nix b/foreign/dotfiles/lib/repl.nix
new file mode 100644
index 0000000..7f14583
--- /dev/null
+++ b/foreign/dotfiles/lib/repl.nix
@@ -0,0 +1,49 @@
+{
+ flakePath ? null,
+ hostnamePath ? "/etc/hostname",
+ registryPath ? /etc/nix/registry.json,
+}: let
+ inherit (builtins) getFlake head match currentSystem readFile pathExists filter fromJSON;
+
+ selfFlake =
+ if pathExists registryPath
+ then filter (it: it.from.id == "self") (fromJSON (readFile registryPath)).flakes
+ else [];
+
+ flakePath' =
+ toString
+ (
+ if flakePath != null
+ then flakePath
+ else if selfFlake != []
+ then (head selfFlake).to.path
+ else "/etc/nixos"
+ );
+
+ flake =
+ if pathExists flakePath'
+ then getFlake flakePath'
+ else {};
+ hostname =
+ if pathExists hostnamePath
+ then head (match "([a-zA-Z0-9\\-]+)\n" (readFile hostnamePath))
+ else "";
+
+ nixpkgsFromInputsPath = flake.inputs.nixpkgs.outPath or "";
+ nixpkgs =
+ flake.pkgs.${currentSystem}.nixpkgs
+ or (
+ if nixpkgsFromInputsPath != ""
+ then import nixpkgsFromInputsPath {}
+ else {}
+ );
+
+ nixpkgsOutput = removeAttrs (nixpkgs // nixpkgs.lib or {}) ["options" "config"];
+in
+ {inherit flake;}
+ // flake
+ // builtins
+ // (flake.nixosConfigurations or {})
+ // flake.nixosConfigurations.${hostname} or {}
+ // nixpkgsOutput
+ // {getFlake = path: getFlake (toString path);}
diff --git a/foreign/dotfiles/lib/theme/colors.nix b/foreign/dotfiles/lib/theme/colors.nix
new file mode 100644
index 0000000..f468bf1
--- /dev/null
+++ b/foreign/dotfiles/lib/theme/colors.nix
@@ -0,0 +1,37 @@
+rec {
+ rosewater = "f5e0dc";
+ flamingo = "f2cdcd";
+ pink = "f5c2e7";
+ mauve = "cba6f7";
+ red = "f38ba8";
+ maroon = "eba0ac";
+ peach = "fab387";
+ yellow = "f9e2af";
+ green = "a6e3a1";
+ teal = "94e2d5";
+ sky = "89dceb";
+ sapphire = "74c7ec";
+ blue = "89b4fa";
+ lavender = "b4befe";
+
+ text = "cdd6f4";
+ subtext1 = "bac2de";
+ subtext0 = "a6adc8";
+ overlay2 = "9399b2";
+ overlay1 = "7f849c";
+ overlay0 = "6c7086";
+
+ surface2 = "585b70";
+ surface1 = "45475a";
+ surface0 = "313244";
+
+ base = "1e1e2e";
+ mantle = "181825";
+ crust = "11111b";
+
+ fg = text;
+ bg = base;
+ bg1 = surface0;
+ border = "28283d";
+ shadow = crust;
+}
diff --git a/foreign/dotfiles/lib/theme/default.nix b/foreign/dotfiles/lib/theme/default.nix
new file mode 100644
index 0000000..3d997f5
--- /dev/null
+++ b/foreign/dotfiles/lib/theme/default.nix
@@ -0,0 +1,25 @@
+{
+ colorlib,
+ lib,
+}: rec {
+ colors = import ./colors.nix;
+ # #RRGGBB
+ xcolors = lib.mapAttrs (_: colorlib.x) colors;
+ # rgba(,,,) colors (css)
+ rgbaColors = lib.mapAttrs (_: colorlib.rgba) colors;
+
+ browser = "firefox";
+
+ terminal = {
+ font = "JetBrainsMono Nerd Font";
+ name = "wezterm";
+ opacity = 0.9;
+ size = 11;
+ };
+
+ wallpaper = builtins.fetchurl rec {
+ name = "wallpaper-${sha256}.png";
+ url = "https://images.unsplash.com/photo-1567095716798-1d95d8f4c479?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8";
+ sha256 = "1x9y9rzqb9mpxc5lmgvc7jxqdyn3j7ryv16vn5lx6qrhpwp24kym";
+ };
+}
diff --git a/foreign/dotfiles/modules/README.md b/foreign/dotfiles/modules/README.md
new file mode 100644
index 0000000..ea50472
--- /dev/null
+++ b/foreign/dotfiles/modules/README.md
@@ -0,0 +1,15 @@
+# Modules
+
+As of now, there are multiple modules included:
+
+Name | Description
+-------------- | -----------
+`default.nix` | Flake-parts module
+Desktop | Config aimed at desktop usage
+Gamemode | Gamemode settings
+Gnome | GNOME config
+Greetd | Greetd + GTKGreet config
+Minimal | Shared configuration
+Nix | Nix-related options
+Security | Tweaks for a more secure system, borrowed from [hlissner](https://github.com/hlissner/dotfiles/blob/master/modules/security.nix)
+Xserver | Xorg config
diff --git a/foreign/dotfiles/modules/default.nix b/foreign/dotfiles/modules/default.nix
new file mode 100644
index 0000000..16aa0a4
--- /dev/null
+++ b/foreign/dotfiles/modules/default.nix
@@ -0,0 +1,47 @@
+{
+ _inputs,
+ inputs,
+ default,
+ ...
+}: let
+ module_args = {
+ _module.args = {
+ inputs = _inputs;
+ inherit default;
+ };
+ };
+in {
+ imports = [
+ {
+ _module.args = {
+ inherit module_args;
+
+ sharedModules = [
+ {home-manager.useGlobalPkgs = true;}
+ {disabledModules = ["security/pam.nix"];}
+ inputs.agenix.nixosModules.default
+ inputs.hm.nixosModule
+ ./minimal.nix
+ ./pam.nix
+ module_args
+ ./nix.nix
+ ./security.nix
+ ];
+
+ desktopModules = with inputs; [
+ hyprland.nixosModules.default
+ kmonad.nixosModules.default
+ nix-gaming.nixosModules.default
+ ];
+ };
+ }
+ ];
+
+ flake.nixosModules = {
+ desktop = import ./desktop.nix;
+ gamemode = import ./gamemode.nix;
+ greetd = import ./greetd.nix;
+ minimal = import ./minimal.nix;
+ nix = import ./nix.nix;
+ };
+}
diff --git a/foreign/dotfiles/modules/desktop.nix b/foreign/dotfiles/modules/desktop.nix
new file mode 100644
index 0000000..fc9aad6
--- /dev/null
+++ b/foreign/dotfiles/modules/desktop.nix
@@ -0,0 +1,153 @@
+{
+ config,
+ pkgs,
+ inputs,
+ ...
+}: {
+ fonts = {
+ fonts = with pkgs; [
+ # icon fonts
+ material-symbols
+
+ # normal fonts
+ jost
+ lexend
+ noto-fonts
+ noto-fonts-cjk
+ noto-fonts-emoji
+ roboto
+
+ # nerdfonts
+ (nerdfonts.override {fonts = ["FiraCode" "JetBrainsMono"];})
+ ];
+
+ # use fonts specified by user rather than default ones
+ enableDefaultFonts = false;
+
+ # user defined fonts
+ # the reason there's Noto Color Emoji everywhere is to override DejaVu's
+ # B&W emojis that would sometimes show instead of some Color emojis
+ fontconfig.defaultFonts = {
+ serif = ["Noto Serif" "Noto Color Emoji"];
+ sansSerif = ["Noto Sans" "Noto Color Emoji"];
+ monospace = ["JetBrainsMono Nerd Font" "Noto Color Emoji"];
+ emoji = ["Noto Color Emoji"];
+ };
+ };
+
+ # use Wayland where possible (electron)
+ environment.variables.NIXOS_OZONE_WL = "1";
+
+ # enable location service
+ location.provider = "geoclue2";
+
+ networking = {
+ firewall = {
+ # for Rocket League
+ allowedTCPPortRanges = [
+ {
+ from = 27015;
+ to = 27030;
+ }
+ {
+ from = 27036;
+ to = 27037;
+ }
+ ];
+ allowedUDPPorts = [4380 27036 34197];
+ allowedUDPPortRanges = [
+ {
+ from = 7000;
+ to = 9000;
+ }
+ {
+ from = 27000;
+ to = 27031;
+ }
+ ];
+
+ # Spotify track sync with other devices
+ allowedTCPPorts = [57621];
+ };
+ };
+
+ nix = {
+ # package = inputs.nix-super.packages.${pkgs.hostPlatform.system}.nix;
+ settings = {
+ substituters = [
+ "https://nix-gaming.cachix.org"
+ "https://hyprland.cachix.org"
+ "https://cache.privatevoid.net"
+ ];
+ trusted-public-keys = [
+ "nix-gaming.cachix.org-1:nbjlureqMbRAxR1gJ/f3hxemL9svXaZF/Ees8vCUUs4="
+ "hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="
+ "cache.privatevoid.net:SErQ8bvNWANeAvtsOESUwVYr2VJynfuc9JRwlzTTkVg="
+ ];
+ };
+ };
+
+ # make HM-managed GTK stuff work
+ programs.dconf.enable = true;
+
+ services = {
+ # use Ambient Light Sensors for auto brightness adjustment
+ clight = {
+ enable = true;
+ settings = {
+ verbose = true;
+ dpms.timeouts = [900 300];
+ dimmer.timeouts = [870 270];
+ screen.disabled = true;
+ };
+ };
+
+ # provide location
+ geoclue2.enable = true;
+
+ # keyboard remapping
+ kmonad = {
+ enable = true;
+ package = inputs.kmonad.packages.${pkgs.hostPlatform.system}.default;
+ keyboards = {
+ one2mini = {
+ device = "/dev/input/by-id/usb-Ducky_Ducky_One2_Mini_RGB_DK-V1.17-190813-event-kbd";
+ defcfg = {
+ enable = true;
+ fallthrough = true;
+ allowCommands = false;
+ };
+ config = builtins.readFile "${inputs.self}/modules/main.kbd";
+ };
+ };
+ };
+
+ pipewire = {
+ enable = true;
+ alsa.enable = true;
+ alsa.support32Bit = true;
+ jack.enable = true;
+ pulse.enable = true;
+ };
+
+ # battery info & stuff
+ upower.enable = true;
+
+ # needed for GNOME services outside of GNOME Desktop
+ dbus.packages = [pkgs.gcr];
+ udev.packages = with pkgs; [gnome.gnome-settings-daemon];
+ };
+
+ security = {
+ # allow wayland lockers to unlock the screen
+ pam.services.swaylock.text = "auth include login";
+
+ # userland niceness
+ rtkit.enable = true;
+ };
+
+ xdg.portal = {
+ enable = true;
+ extraPortals = [pkgs.xdg-desktop-portal-gtk];
+ };
+}
diff --git a/foreign/dotfiles/modules/gamemode.nix b/foreign/dotfiles/modules/gamemode.nix
new file mode 100644
index 0000000..1e601c0
--- /dev/null
+++ b/foreign/dotfiles/modules/gamemode.nix
@@ -0,0 +1,34 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ programs = lib.makeBinPath [config.programs.hyprland.package];
+
+ startscript = pkgs.writeShellScript "gamemode-start" ''
+ export PATH=$PATH:${programs}
+ export HYPRLAND_INSTANCE_SIGNATURE=$(ls -1 /tmp/hypr | tail -1)
+ hyprctl --batch 'keyword decoration:blur 0 ; keyword animations:enabled 0 ; keyword misc:no_vfr 1'
+ '';
+
+ endscript = pkgs.writeShellScript "gamemode-end" ''
+ export PATH=$PATH:${programs}
+ export HYPRLAND_INSTANCE_SIGNATURE=$(ls -1 /tmp/hypr | tail -1)
+ hyprctl --batch 'keyword decoration:blur 1 ; keyword animations:enabled 1 ; keyword misc:no_vfr 0'
+ '';
+in {
+ programs.gamemode = {
+ enable = true;
+ settings = {
+ general = {
+ softrealtime = "auto";
+ renice = 15;
+ };
+ custom = {
+ start = startscript.outPath;
+ end = endscript.outPath;
+ };
+ };
+ };
+}
diff --git a/foreign/dotfiles/modules/gnome.nix b/foreign/dotfiles/modules/gnome.nix
new file mode 100644
index 0000000..6a09b00
--- /dev/null
+++ b/foreign/dotfiles/modules/gnome.nix
@@ -0,0 +1,44 @@
+{
+ lib,
+ pkgs,
+ ...
+}:
+# GNOME 41 config
+{
+ environment.systemPackages = with pkgs.gnomeExtensions; [
+ appindicator
+ gsconnect
+ ideapad-mode
+ vitals
+ pkgs.gnome.gnome-tweaks
+ ];
+
+ # we're using pipewire instead
+ hardware.pulseaudio.enable = lib.mkForce false;
+
+ networking = {
+ # for GSConnect
+ firewall = {
+ allowedTCPPortRanges = [
+ {
+ from = 1714;
+ to = 1764;
+ }
+ ];
+ allowedUDPPortRanges = [
+ {
+ from = 1714;
+ to = 1764;
+ }
+ ];
+ };
+ };
+
+ services.gnome.games.enable = true;
+ services.power-profiles-daemon.enable = lib.mkForce false;
+ services.xserver = {
+ desktopManager.gnome = {
+ enable = true;
+ };
+ };
+}
diff --git a/foreign/dotfiles/modules/greetd.nix b/foreign/dotfiles/modules/greetd.nix
new file mode 100644
index 0000000..c229ccb
--- /dev/null
+++ b/foreign/dotfiles/modules/greetd.nix
@@ -0,0 +1,63 @@
+{
+ lib,
+ pkgs,
+ config,
+ inputs,
+ default,
+ ...
+}:
+# greetd display manager
+let
+ greetdSwayConfig = pkgs.writeText "greetd-sway-config" ''
+ exec "dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP"
+ input "type:touchpad" {
+ tap enabled
+ }
+ seat seat0 xcursor_theme Bibata-Modern-Classic 24
+
+ xwayland disable
+
+ bindsym XF86MonBrightnessUp exec light -A 5
+ bindsym XF86MonBrightnessDown exec light -U 5
+ bindsym Print exec ${lib.getExe pkgs.grim} /tmp/regreet.png
+ bindsym Mod4+shift+e exec swaynag \
+ -t warning \
+ -m 'What do you want to do?' \
+ -b 'Poweroff' 'systemctl poweroff' \
+ -b 'Reboot' 'systemctl reboot'
+
+ exec "${lib.getExe config.programs.regreet.package} -l debug; swaymsg exit"
+ '';
+in {
+ imports = [./regreet.nix];
+ environment.systemPackages = with pkgs; [
+ # theme packages
+ (catppuccin-gtk.override {
+ accents = ["mauve"];
+ size = "compact";
+ variant = "mocha";
+ })
+ bibata-cursors
+ papirus-icon-theme
+ ];
+
+ programs.regreet = {
+ enable = true;
+ package = inputs.self.packages.${pkgs.hostPlatform.system}.regreet;
+ settings = {
+ background = default.wallpaper;
+ background_fit = "Cover";
+ GTK = {
+ cursor_theme_name = "Bibata-Modern-Classic";
+ font_name = "Jost * 12";
+ icon_theme_name = "Papirus-Dark";
+ theme_name = "Catppuccin-Mocha-Compact-Mauve-Dark";
+ };
+ };
+ };
+
+ services.greetd.settings.default_session.command = "${inputs.self.packages.${pkgs.hostPlatform.system}.sway-hidpi}/bin/sway --config ${greetdSwayConfig}";
+
+ # unlock GPG keyring on login
+ security.pam.services.greetd.gnupg.enable = true;
+}
diff --git a/foreign/dotfiles/modules/howdy/config.nix b/foreign/dotfiles/modules/howdy/config.nix
new file mode 100644
index 0000000..c585871
--- /dev/null
+++ b/foreign/dotfiles/modules/howdy/config.nix
@@ -0,0 +1,45 @@
+{
+ core = {
+ detection_notice = false;
+ timeout_notice = true;
+ no_confirmation = false;
+ suppress_unknown = false;
+ abort_if_ssh = true;
+ abort_if_lid_closed = true;
+ disabled = false;
+ use_cnn = false;
+ workaround = "off";
+ };
+
+ video = {
+ certainty = 3.5;
+ timeout = 4;
+ device_path = "/dev/video2";
+ warn_no_device = true;
+ max_height = 320;
+ frame_width = -1;
+ frame_height = -1;
+ dark_threshold = 60;
+ recording_plugin = "opencv";
+ device_format = "v4l2";
+ force_mjpeg = false;
+ exposure = -1;
+ rotate = 0;
+ };
+
+ snapshots = {
+ save_failed = false;
+ save_successful = false;
+ };
+
+ rubberstamps = {
+ enabled = false;
+ stamp_rules = "nod 5s failsafe min_distance=12";
+ };
+
+ debug = {
+ end_report = false;
+ verbose_stamps = false;
+ gtk_stdout = false;
+ };
+}
diff --git a/foreign/dotfiles/modules/howdy/default.nix b/foreign/dotfiles/modules/howdy/default.nix
new file mode 100644
index 0000000..95506b2
--- /dev/null
+++ b/foreign/dotfiles/modules/howdy/default.nix
@@ -0,0 +1,45 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.services.howdy;
+ settingsType = pkgs.formats.ini {};
+in {
+ options = {
+ services.howdy = {
+ enable =
+ mkEnableOption (mdDoc "")
+ // {
+ description = mdDoc ''
+ Howdy and PAM module for face recognition. See
+ `services.linux-enable-ir-emitter` for enabling the IR emitter support.
+ '';
+ };
+
+ package = mkPackageOptionMD pkgs "howdy" {};
+
+ settings = mkOption {
+ inherit (settingsType) type;
+ default = import ./config.nix;
+ description = mdDoc ''
+ Howdy configuration file. Refer to
+
+ for options.
+ '';
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ environment.systemPackages = [cfg.package];
+ environment.etc."howdy/config.ini".source = settingsType.generate "howdy-config.ini" cfg.settings;
+ })
+ {
+ services.howdy.settings = mapAttrsRecursive (name: mkDefault) (import ./config.nix);
+ }
+ ];
+}
diff --git a/foreign/dotfiles/modules/linux-enable-ir-emitter.nix b/foreign/dotfiles/modules/linux-enable-ir-emitter.nix
new file mode 100644
index 0000000..1d8c48b
--- /dev/null
+++ b/foreign/dotfiles/modules/linux-enable-ir-emitter.nix
@@ -0,0 +1,64 @@
+{
+ config,
+ lib,
+ inputs,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.services.linux-enable-ir-emitter;
+in {
+ options = {
+ services.linux-enable-ir-emitter = {
+ enable = mkEnableOption {
+ description = ''
+ Linux Enable IR Emitter.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.linux-enable-ir-emitter or inputs.self.packages.${pkgs.system}.linux-enable-ir-emitter;
+ defaultText = "pkgs.linux-enable-ir-emitter";
+ description = ''
+ Package to use for the Linux Enable IR Emitter service.
+ '';
+ };
+
+ device = mkOption {
+ type = types.lines;
+ default = "video2";
+ defaultText = "video2";
+ description = ''
+ Emitter device to depend on. Find this with the command
+ {command}`realpath /dev/v4l/by-path/`.
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ environment.systemPackages = [cfg.package];
+
+ systemd.services.linux-enable-ir-emitter = let
+ targets = [
+ "multi-user.target"
+ "suspend.target"
+ "hybrid-sleep.target"
+ "hibernate.target"
+ "suspend-then-hibernate.target"
+ ];
+ in {
+ description = "Enable the infrared emitter.";
+
+ script = "${lib.getExe cfg.package} run";
+
+ wantedBy = targets;
+ after = targets ++ ["dev-${cfg.device}.device"];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /var/lib/linux-enable-ir-emitter 0755 root root - -"
+ ];
+ environment.etc."linux-enable-ir-emitter".source = "/var/lib/linux-enable-ir-emitter";
+ };
+}
diff --git a/foreign/dotfiles/modules/main.kbd b/foreign/dotfiles/modules/main.kbd
new file mode 100644
index 0000000..7c80c79
--- /dev/null
+++ b/foreign/dotfiles/modules/main.kbd
@@ -0,0 +1,49 @@
+(defsrc
+ grv 1 2 3 4 5 6 7 8 9 0 - = bspc
+ tab q w e r t y u i o p [ ] \
+ caps a s d f g h j k l ; ' ret
+ lsft z x c v b n m , . / rsft
+ lctl lmet lalt spc ralt rmet cmp rctl
+)
+
+(deflayer colemak
+ grv 1 2 3 4 5 6 7 8 9 0 - = bspc
+ tab q w f p b j l u y ; [ ] \
+ @esc a r s t g m n e i o ' ret
+ lsft x c d v z k h , . / rsft
+ @lay lmet lalt @spc ralt rmet cmp rctl
+)
+
+(deflayer qwerty
+ grv 1 2 3 4 5 6 7 8 9 0 - = bspc
+ tab q w e r t y u i o p [ ] \
+ @cps a s d f g h j k l ; ' ret
+ lsft z x c v b n m , . / rsft
+ lctl lmet lalt spc ralt rmet cmp rctl
+)
+
+(deflayer layouts
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ caps _ _ _ @cmk _ _ @qwe _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _
+)
+
+(deflayer symbols
+ _ f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 del
+ _ _ _ _ _ _ ins _ up _ pgup home prnt _
+ _ _ _ _ _ _ _ left down rght pgdn end _
+ _ _ _ _ _ _ _ mute vold volu _ _
+ pp _ prev _ next _ _ _
+)
+
+(defalias
+ lay (layer-toggle layouts)
+ cmk (layer-switch colemak)
+ qwe (layer-switch qwerty)
+ sym (layer-toggle symbols)
+ esc (tap-next-release esc lctl)
+ spc (tap-next-release spc @sym)
+ cps (tap-next-release caps @lay)
+)
diff --git a/foreign/dotfiles/modules/minimal.nix b/foreign/dotfiles/modules/minimal.nix
new file mode 100644
index 0000000..6d3704b
--- /dev/null
+++ b/foreign/dotfiles/modules/minimal.nix
@@ -0,0 +1,97 @@
+{
+ pkgs,
+ config,
+ lib,
+ inputs,
+ ...
+}:
+# configuration shared by all hosts
+{
+ # enable zsh autocompletion for system packages (systemd, etc)
+ environment.pathsToLink = ["/share/zsh"];
+
+ i18n = {
+ defaultLocale = "en_US.UTF-8";
+ # saves space
+ supportedLocales = ["en_US.UTF-8/UTF-8" "ja_JP.UTF-8/UTF-8" "ro_RO.UTF-8/UTF-8"];
+ };
+
+ # graphics drivers / HW accel
+ hardware.opengl.enable = true;
+
+ networking = {
+ # required to connect to Tailscale exit nodes
+ firewall.checkReversePath = "loose";
+
+ networkmanager = {
+ enable = true;
+ dns = "systemd-resolved";
+ wifi.powersave = true;
+ };
+ };
+
+ # pickup pkgs from flake export
+ nixpkgs.pkgs = inputs.self.legacyPackages.${config.nixpkgs.system};
+
+ # enable programs
+ programs = {
+ less.enable = true;
+
+ zsh = {
+ enable = true;
+ autosuggestions.enable = true;
+ syntaxHighlighting = {
+ enable = true;
+ patterns = {"rm -rf *" = "fg=white,bg=red";};
+ styles = {"alias" = "fg=magenta";};
+ highlighters = ["main" "brackets" "pattern"];
+ };
+ };
+ };
+
+ # don't ask for password for wheel group
+ security.sudo.wheelNeedsPassword = false;
+
+ services = {
+ # network discovery, mDNS
+ avahi = {
+ enable = true;
+ nssmdns = true;
+ publish.enable = true;
+ publish.domain = true;
+ publish.userServices = true;
+ };
+
+ openssh = {
+ enable = true;
+ settings.UseDns = true;
+ };
+
+ # DNS resolver
+ resolved.enable = true;
+
+ # inter-machine VPN
+ tailscale.enable = true;
+ };
+
+ # don't touch this
+ system.stateVersion = lib.mkDefault "20.09";
+
+ # Don't wait for network startup
+ # https://old.reddit.com/r/NixOS/comments/vdz86j/how_to_remove_boot_dependency_on_network_for_a
+ systemd = {
+ targets.network-online.wantedBy = pkgs.lib.mkForce []; # Normally ["multi-user.target"]
+ services.NetworkManager-wait-online.wantedBy = pkgs.lib.mkForce []; # Normally ["network-online.target"]
+ };
+
+ time.timeZone = lib.mkDefault "Europe/Bucharest";
+
+ users.users.mihai = {
+ isNormalUser = true;
+ shell = pkgs.zsh;
+ extraGroups = ["adbusers" "input" "libvirtd" "networkmanager" "plugdev" "transmission" "video" "wheel"];
+ };
+
+ # compresses half the ram for use as swap
+ zramSwap.enable = true;
+}
diff --git a/foreign/dotfiles/modules/nix.nix b/foreign/dotfiles/modules/nix.nix
new file mode 100644
index 0000000..4c1a78f
--- /dev/null
+++ b/foreign/dotfiles/modules/nix.nix
@@ -0,0 +1,71 @@
+{
+ config,
+ pkgs,
+ inputs,
+ lib,
+ ...
+}: {
+ # we need git for flakes
+ environment.systemPackages = [pkgs.git];
+
+ nix = {
+ # extra builders to offload work onto
+ # don't set a machine as a builder to itself (throws warnings)
+ buildMachines = lib.filter (x: x.hostName != config.networking.hostName) [
+ {
+ system = "aarch64-linux";
+ sshUser = "root";
+ sshKey = "/etc/ssh/ssh_host_ed25519_key";
+ maxJobs = 4;
+ hostName = "arm-server";
+ supportedFeatures = ["nixos-test" "benchmark" "kvm" "big-parallel"];
+ }
+ {
+ system = "x86_64-linux";
+ sshUser = "root";
+ sshKey = "/root/.ssh/id_ed25519";
+ maxJobs = 8;
+ hostName = "io";
+ supportedFeatures = ["nixos-test" "benchmark" "kvm" "big-parallel"];
+ }
+ ];
+ distributedBuilds = true;
+
+ # auto garbage collect
+ gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 7d";
+ };
+
+ # pin the registry to avoid downloading and evaling a new nixpkgs version every time
+ registry = lib.mapAttrs (_: v: {flake = v;}) inputs;
+
+ # set the path for channels compat
+ nixPath = lib.mapAttrsToList (key: _: "${key}=flake:${key}") config.nix.registry;
+
+ settings = {
+ auto-optimise-store = true;
+ builders-use-substitutes = true;
+ experimental-features = ["nix-command" "flakes"];
+ flake-registry = "/etc/nix/registry.json";
+
+ # for direnv GC roots
+ keep-derivations = true;
+ keep-outputs = true;
+
+ substituters = [
+ "https://nix-community.cachix.org"
+ "https://helix.cachix.org"
+ "https://fufexan.cachix.org"
+ ];
+ trusted-public-keys = [
+ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
+ "helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs="
+ "fufexan.cachix.org-1:LwCDjCJNJQf5XD2BV+yamQIMZfcKWR9ISIFy5curUsY="
+ ];
+
+ trusted-users = ["root" "@wheel"];
+ };
+ };
+}
diff --git a/foreign/dotfiles/modules/pam.nix b/foreign/dotfiles/modules/pam.nix
new file mode 100644
index 0000000..591e7ca
--- /dev/null
+++ b/foreign/dotfiles/modules/pam.nix
@@ -0,0 +1,1445 @@
+# This module provides configuration for the PAM (Pluggable
+# Authentication Modules) system.
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ parentConfig = config;
+
+ pamOpts = {
+ config,
+ name,
+ ...
+ }: let
+ cfg = config;
+ in let
+ config = parentConfig;
+ in {
+ options = {
+ name = mkOption {
+ example = "sshd";
+ type = types.str;
+ description = lib.mdDoc "Name of the PAM service.";
+ };
+
+ unixAuth = mkOption {
+ default = true;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether users can log in with passwords defined in
+ {file}`/etc/shadow`.
+ '';
+ };
+
+ rootOK = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, root doesn't need to authenticate (e.g. for the
+ {command}`useradd` service).
+ '';
+ };
+
+ p11Auth = mkOption {
+ default = config.security.pam.p11.enable;
+ defaultText = literalExpression "config.security.pam.p11.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, keys listed in
+ {file}`~/.ssh/authorized_keys` and
+ {file}`~/.eid/authorized_certificates`
+ can be used to log in with the associated PKCS#11 tokens.
+ '';
+ };
+
+ u2fAuth = mkOption {
+ default = config.security.pam.u2f.enable;
+ defaultText = literalExpression "config.security.pam.u2f.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, users listed in
+ {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+ {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
+ not set) are able to log in with the associated U2F key. Path can be
+ changed using {option}`security.pam.u2f.authFile` option.
+ '';
+ };
+
+ usshAuth = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, users with an SSH certificate containing an authorized principal
+ in their SSH agent are able to log in. Specific options are controlled
+ using the {option}`security.pam.ussh` options.
+
+ Note that the {option}`security.pam.ussh.enable` must also be
+ set for this option to take effect.
+ '';
+ };
+
+ yubicoAuth = mkOption {
+ default = config.security.pam.yubico.enable;
+ defaultText = literalExpression "config.security.pam.yubico.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, users listed in
+ {file}`~/.yubico/authorized_yubikeys`
+ are able to log in with the associated Yubikey tokens.
+ '';
+ };
+
+ googleAuthenticator = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, users with enabled Google Authenticator (created
+ {file}`~/.google_authenticator`) will be required
+ to provide Google Authenticator token to log in.
+ '';
+ };
+ };
+
+ usbAuth = mkOption {
+ default = config.security.pam.usb.enable;
+ defaultText = literalExpression "config.security.pam.usb.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, users listed in
+ {file}`/etc/pamusb.conf` are able to log in
+ with the associated USB key.
+ '';
+ };
+
+ otpwAuth = mkOption {
+ default = config.security.pam.enableOTPW;
+ defaultText = literalExpression "config.security.pam.enableOTPW";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, the OTPW system will be used (if
+ {file}`~/.otpw` exists).
+ '';
+ };
+
+ googleOsLoginAccountVerification = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, will use the Google OS Login PAM modules
+ (`pam_oslogin_login`,
+ `pam_oslogin_admin`) to verify possible OS Login
+ users and set sudoers configuration accordingly.
+ This only makes sense to enable for the `sshd` PAM
+ service.
+ '';
+ };
+
+ googleOsLoginAuthentication = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, will use the `pam_oslogin_login`'s user
+ authentication methods to authenticate users using 2FA.
+ This only makes sense to enable for the `sshd` PAM
+ service.
+ '';
+ };
+
+ mysqlAuth = mkOption {
+ default = config.users.mysql.enable;
+ defaultText = literalExpression "config.users.mysql.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, the `pam_mysql` module will be used to
+ authenticate users against a MySQL/MariaDB database.
+ '';
+ };
+
+ fprintAuth = mkOption {
+ default = config.services.fprintd.enable;
+ defaultText = literalExpression "config.services.fprintd.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, fingerprint reader will be used (if exists and
+ your fingerprints are enrolled).
+ '';
+ };
+
+ howdyAuth = mkOption {
+ default = config.services.howdy.enable;
+ defaultText = literalExpression "config.services.howdy.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, IR camera will be used (if exists and your
+ facial models are enrolled).
+ '';
+ };
+
+ oathAuth = mkOption {
+ default = config.security.pam.oath.enable;
+ defaultText = literalExpression "config.security.pam.oath.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, the OATH Toolkit will be used.
+ '';
+ };
+
+ sshAgentAuth = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, the calling user's SSH agent is used to authenticate
+ against the keys in the calling user's
+ {file}`~/.ssh/authorized_keys`. This is useful
+ for {command}`sudo` on password-less remote systems.
+ '';
+ };
+
+ duoSecurity = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, use the Duo Security pam module
+ `pam_duo` for authentication. Requires
+ configuration of {option}`security.duosec` options.
+ '';
+ };
+ };
+
+ startSession = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If set, the service will register a new session with
+ systemd's login manager. For local sessions, this will give
+ the user access to audio devices, CD-ROM drives. In the
+ default PolicyKit configuration, it also allows the user to
+ reboot the system.
+ '';
+ };
+
+ setEnvironment = mkOption {
+ type = types.bool;
+ default = true;
+ description = lib.mdDoc ''
+ Whether the service should set the environment variables
+ listed in {option}`environment.sessionVariables`
+ using `pam_env.so`.
+ '';
+ };
+
+ setLoginUid = mkOption {
+ type = types.bool;
+ description = lib.mdDoc ''
+ Set the login uid of the process
+ ({file}`/proc/self/loginuid`) for auditing
+ purposes. The login uid is only set by ‘entry points’ like
+ {command}`login` and {command}`sshd`, not by
+ commands like {command}`sudo`.
+ '';
+ };
+
+ ttyAudit = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Enable or disable TTY auditing for specified users
+ '';
+ };
+
+ enablePattern = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = lib.mdDoc ''
+ For each user matching one of comma-separated
+ glob patterns, enable TTY auditing
+ '';
+ };
+
+ disablePattern = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = lib.mdDoc ''
+ For each user matching one of comma-separated
+ glob patterns, disable TTY auditing
+ '';
+ };
+
+ openOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Set the TTY audit flag when opening the session,
+ but do not restore it when closing the session.
+ Using this option is necessary for some services
+ that don't fork() to run the authenticated session,
+ such as sudo.
+ '';
+ };
+ };
+
+ forwardXAuth = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether X authentication keys should be passed from the
+ calling user to the target user (e.g. for
+ {command}`su`)
+ '';
+ };
+
+ pamMount = mkOption {
+ default = config.security.pam.mount.enable;
+ defaultText = literalExpression "config.security.pam.mount.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enable PAM mount (pam_mount) system to mount filesystems on user login.
+ '';
+ };
+
+ allowNullPassword = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether to allow logging into accounts that have no password
+ set (i.e., have an empty password field in
+ {file}`/etc/passwd` or
+ {file}`/etc/group`). This does not enable
+ logging into disabled accounts (i.e., that have the password
+ field set to `!`). Note that regardless of
+ what the pam_unix documentation says, accounts with hashed
+ empty passwords are always allowed to log in.
+ '';
+ };
+
+ nodelay = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether the delay after typing a wrong password should be disabled.
+ '';
+ };
+
+ requireWheel = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether to permit root access only to members of group wheel.
+ '';
+ };
+
+ limits = mkOption {
+ default = [];
+ type = limitsType;
+ description = lib.mdDoc ''
+ Attribute set describing resource limits. Defaults to the
+ value of {option}`security.pam.loginLimits`.
+ The meaning of the values is explained in {manpage}`limits.conf(5)`.
+ '';
+ };
+
+ showMotd = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc "Whether to show the message of the day.";
+ };
+
+ makeHomeDir = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Whether to try to create home directories for users
+ with `$HOME`s pointing to nonexistent
+ locations on session login.
+ '';
+ };
+
+ updateWtmp = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc "Whether to update {file}`/var/log/wtmp`.";
+ };
+
+ logFailures = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc "Whether to log authentication failures in {file}`/var/log/faillog`.";
+ };
+
+ enableAppArmor = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enable support for attaching AppArmor profiles at the
+ user/group level, e.g., as part of a role based access
+ control scheme.
+ '';
+ };
+
+ enableKwallet = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If enabled, pam_wallet will attempt to automatically unlock the
+ user's default KDE wallet upon login. If the user has no wallet named
+ "kdewallet", or the login password does not match their wallet
+ password, KDE will prompt separately after login.
+ '';
+ };
+ sssdStrictAccess = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc "enforce sssd access control";
+ };
+
+ enableGnomeKeyring = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ If enabled, pam_gnome_keyring will attempt to automatically unlock the
+ user's default Gnome keyring upon login. If the user login password does
+ not match their keyring password, Gnome Keyring will prompt separately
+ after login.
+ '';
+ };
+
+ failDelay = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ If enabled, this will replace the `FAIL_DELAY` setting from `login.defs`.
+ Change the delay on failure per-application.
+ '';
+ };
+
+ delay = mkOption {
+ default = 3000000;
+ type = types.int;
+ example = 1000000;
+ description = lib.mdDoc "The delay time (in microseconds) on failure.";
+ };
+ };
+
+ gnupg = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ If enabled, pam_gnupg will attempt to automatically unlock the
+ user's GPG keys with the login password via
+ {command}`gpg-agent`. The keygrips of all keys to be
+ unlocked should be written to {file}`~/.pam-gnupg`,
+ and can be queried with {command}`gpg -K --with-keygrip`.
+ Presetting passphrases must be enabled by adding
+ `allow-preset-passphrase` in
+ {file}`~/.gnupg/gpg-agent.conf`.
+ '';
+ };
+
+ noAutostart = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Don't start {command}`gpg-agent` if it is not running.
+ Useful in conjunction with starting {command}`gpg-agent` as
+ a systemd user service.
+ '';
+ };
+
+ storeOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Don't send the password immediately after login, but store for PAM
+ `session`.
+ '';
+ };
+ };
+
+ text = mkOption {
+ type = types.nullOr types.lines;
+ description = lib.mdDoc "Contents of the PAM service file.";
+ };
+ };
+
+ # The resulting /etc/pam.d/* file contents are verified in
+ # nixos/tests/pam/pam-file-contents.nix. Please update tests there when
+ # changing the derivation.
+ config = {
+ name = mkDefault name;
+ setLoginUid = mkDefault cfg.startSession;
+ limits = mkDefault config.security.pam.loginLimits;
+
+ # !!! TODO: move the LDAP stuff to the LDAP module, and the
+ # Samba stuff to the Samba module. This requires that the PAM
+ # module provides the right hooks.
+ text =
+ mkDefault
+ (
+ ''
+ # Account management.
+ ''
+ + optionalString use_ldap ''
+ account sufficient ${pam_ldap}/lib/security/pam_ldap.so
+ ''
+ + optionalString cfg.mysqlAuth ''
+ account sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+ ''
+ + optionalString (config.services.sssd.enable && cfg.sssdStrictAccess == false) ''
+ account sufficient ${pkgs.sssd}/lib/security/pam_sss.so
+ ''
+ + optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) ''
+ account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so
+ ''
+ + optionalString config.security.pam.krb5.enable ''
+ account sufficient ${pam_krb5}/lib/security/pam_krb5.so
+ ''
+ + optionalString cfg.googleOsLoginAccountVerification ''
+ account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
+ account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
+ ''
+ + optionalString config.services.homed.enable ''
+ account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+ ''
+ +
+ # The required pam_unix.so module has to come after all the sufficient modules
+ # because otherwise, the account lookup will fail if the user does not exist
+ # locally, for example with MySQL- or LDAP-auth.
+ ''
+ account required pam_unix.so
+
+ # Authentication management.
+ ''
+ + optionalString cfg.googleOsLoginAuthentication ''
+ auth [success=done perm_denied=die default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
+ ''
+ + optionalString cfg.rootOK ''
+ auth sufficient pam_rootok.so
+ ''
+ + optionalString cfg.requireWheel ''
+ auth required pam_wheel.so use_uid
+ ''
+ + optionalString cfg.logFailures ''
+ auth required pam_faillock.so
+ ''
+ + optionalString cfg.mysqlAuth ''
+ auth sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+ ''
+ + optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) ''
+ auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}
+ ''
+ + (let
+ p11 = config.security.pam.p11;
+ in
+ optionalString cfg.p11Auth ''
+ auth ${p11.control} ${pkgs.pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so
+ '')
+ + (let
+ u2f = config.security.pam.u2f;
+ in
+ optionalString cfg.u2fAuth (''
+ auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ''
+ + '' ${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"} ${optionalString (u2f.appId != null) "appid=${u2f.appId}"} ${optionalString (u2f.origin != null) "origin=${u2f.origin}"}
+ ''))
+ + optionalString cfg.usbAuth ''
+ auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
+ ''
+ + (let
+ ussh = config.security.pam.ussh;
+ in
+ optionalString (config.security.pam.ussh.enable && cfg.usshAuth) ''
+ auth ${ussh.control} ${pkgs.pam_ussh}/lib/security/pam_ussh.so ${optionalString (ussh.caFile != null) "ca_file=${ussh.caFile}"} ${optionalString (ussh.authorizedPrincipals != null) "authorized_principals=${ussh.authorizedPrincipals}"} ${optionalString (ussh.authorizedPrincipalsFile != null) "authorized_principals_file=${ussh.authorizedPrincipalsFile}"} ${optionalString (ussh.group != null) "group=${ussh.group}"}
+ '')
+ + (let
+ oath = config.security.pam.oath;
+ in
+ optionalString cfg.oathAuth ''
+ auth requisite ${pkgs.oath-toolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
+ '')
+ + (let
+ yubi = config.security.pam.yubico;
+ in
+ optionalString cfg.yubicoAuth ''
+ auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.challengeResponsePath != null) "chalresp_path=${yubi.challengeResponsePath}"} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}
+ '')
+ + optionalString cfg.fprintAuth ''
+ auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so
+ ''
+ + optionalString cfg.howdyAuth ''
+ auth sufficient ${config.services.howdy.package}/lib/security/pam_howdy.so
+ ''
+ +
+ # Modules in this block require having the password set in PAM_AUTHTOK.
+ # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
+ # after it succeeds. Certain modules need to run after pam_unix
+ # prompts the user for password so we run it once with 'optional' at an
+ # earlier point and it will run again with 'sufficient' further down.
+ # We use try_first_pass the second time to avoid prompting password twice.
+ #
+ # The same principle applies to systemd-homed
+ (optionalString ((cfg.unixAuth || config.services.homed.enable)
+ && (config.security.pam.enableEcryptfs
+ || config.security.pam.enableFscrypt
+ || cfg.pamMount
+ || cfg.enableKwallet
+ || cfg.enableGnomeKeyring
+ || cfg.googleAuthenticator.enable
+ || cfg.gnupg.enable
+ || cfg.failDelay.enable
+ || cfg.duoSecurity.enable))
+ (
+ optionalString config.services.homed.enable ''
+ auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
+ ''
+ + optionalString cfg.unixAuth ''
+ auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
+ ''
+ + optionalString config.security.pam.enableEcryptfs ''
+ auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
+ ''
+ + optionalString config.security.pam.enableFscrypt ''
+ auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+ ''
+ + optionalString cfg.pamMount ''
+ auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
+ ''
+ + optionalString cfg.enableKwallet ''
+ auth optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
+ ''
+ + optionalString cfg.enableGnomeKeyring ''
+ auth optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so
+ ''
+ + optionalString cfg.gnupg.enable ''
+ auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
+ ''
+ + optionalString cfg.failDelay.enable ''
+ auth optional ${pkgs.pam}/lib/security/pam_faildelay.so delay=${toString cfg.failDelay.delay}
+ ''
+ + optionalString cfg.googleAuthenticator.enable ''
+ auth required ${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
+ ''
+ + optionalString cfg.duoSecurity.enable ''
+ auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
+ ''
+ ))
+ + optionalString config.services.homed.enable ''
+ auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+ ''
+ + optionalString cfg.unixAuth ''
+ auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
+ ''
+ + optionalString cfg.otpwAuth ''
+ auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so
+ ''
+ + optionalString use_ldap ''
+ auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass
+ ''
+ + optionalString config.services.sssd.enable ''
+ auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass
+ ''
+ + optionalString config.security.pam.krb5.enable ''
+ auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
+ auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
+ auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
+ ''
+ + ''
+ auth required pam_deny.so
+
+ # Password management.
+ ''
+ + optionalString config.services.homed.enable ''
+ password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+ ''
+ + ''
+ password sufficient pam_unix.so nullok sha512
+ ''
+ + optionalString config.security.pam.enableEcryptfs ''
+ password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
+ ''
+ + optionalString config.security.pam.enableFscrypt ''
+ password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+ ''
+ + optionalString cfg.pamMount ''
+ password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
+ ''
+ + optionalString use_ldap ''
+ password sufficient ${pam_ldap}/lib/security/pam_ldap.so
+ ''
+ + optionalString cfg.mysqlAuth ''
+ password sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+ ''
+ + optionalString config.services.sssd.enable ''
+ password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok
+ ''
+ + optionalString config.security.pam.krb5.enable ''
+ password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
+ ''
+ + optionalString cfg.enableGnomeKeyring ''
+ password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok
+ ''
+ + ''
+
+ # Session management.
+ ''
+ + optionalString cfg.setEnvironment ''
+ session required pam_env.so conffile=/etc/pam/environment readenv=0
+ ''
+ + ''
+ session required pam_unix.so
+ ''
+ + optionalString cfg.setLoginUid ''
+ session ${
+ if config.boot.isContainer
+ then "optional"
+ else "required"
+ } pam_loginuid.so
+ ''
+ + optionalString cfg.ttyAudit.enable (concatStringsSep " \\\n " (
+ [
+ "session required ${pkgs.pam}/lib/security/pam_tty_audit.so"
+ ]
+ ++ optional cfg.ttyAudit.openOnly "open_only"
+ ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
+ ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
+ ))
+ + optionalString config.services.homed.enable ''
+ session required ${config.systemd.package}/lib/security/pam_systemd_home.so
+ ''
+ + optionalString cfg.makeHomeDir ''
+ session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
+ ''
+ + optionalString cfg.updateWtmp ''
+ session required ${pkgs.pam}/lib/security/pam_lastlog.so silent
+ ''
+ + optionalString config.security.pam.enableEcryptfs ''
+ session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
+ ''
+ + optionalString config.security.pam.enableFscrypt ''
+ # Work around https://github.com/systemd/systemd/issues/8598
+ # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
+ # anyways.
+ # See also https://github.com/google/fscrypt/issues/95
+ session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
+ session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+ ''
+ + optionalString cfg.pamMount ''
+ session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
+ ''
+ + optionalString use_ldap ''
+ session optional ${pam_ldap}/lib/security/pam_ldap.so
+ ''
+ + optionalString cfg.mysqlAuth ''
+ session optional ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+ ''
+ + optionalString config.services.sssd.enable ''
+ session optional ${pkgs.sssd}/lib/security/pam_sss.so
+ ''
+ + optionalString config.security.pam.krb5.enable ''
+ session optional ${pam_krb5}/lib/security/pam_krb5.so
+ ''
+ + optionalString cfg.otpwAuth ''
+ session optional ${pkgs.otpw}/lib/security/pam_otpw.so
+ ''
+ + optionalString cfg.startSession ''
+ session optional ${config.systemd.package}/lib/security/pam_systemd.so
+ ''
+ + optionalString cfg.forwardXAuth ''
+ session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99
+ ''
+ + optionalString (cfg.limits != []) ''
+ session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}
+ ''
+ + optionalString (cfg.showMotd && (config.users.motd != null || config.users.motdFile != null)) ''
+ session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}
+ ''
+ + optionalString (cfg.enableAppArmor && config.security.apparmor.enable) ''
+ session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug
+ ''
+ + optionalString (cfg.enableKwallet) ''
+ session optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
+ ''
+ + optionalString (cfg.enableGnomeKeyring) ''
+ session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start
+ ''
+ + optionalString cfg.gnupg.enable ''
+ session optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.noAutostart " no-autostart"}
+ ''
+ + optionalString (config.virtualisation.lxc.lxcfs.enable) ''
+ session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all
+ ''
+ );
+ };
+ };
+
+ inherit (pkgs) pam_krb5 pam_ccreds;
+
+ use_ldap = config.users.ldap.enable && config.users.ldap.loginPam;
+ pam_ldap =
+ if config.users.ldap.daemon.enable
+ then pkgs.nss_pam_ldapd
+ else pkgs.pam_ldap;
+
+ # Create a limits.conf(5) file.
+ makeLimitsConf = limits:
+ pkgs.writeText "limits.conf"
+ (concatMapStrings ({
+ domain,
+ type,
+ item,
+ value,
+ }: "${domain} ${type} ${item} ${toString value}\n")
+ limits);
+
+ limitsType = with lib.types;
+ listOf (submodule ({...}: {
+ options = {
+ domain = mkOption {
+ description = lib.mdDoc "Username, groupname, or wildcard this limit applies to";
+ example = "@wheel";
+ type = str;
+ };
+
+ type = mkOption {
+ description = lib.mdDoc "Type of this limit";
+ type = enum ["-" "hard" "soft"];
+ default = "-";
+ };
+
+ item = mkOption {
+ description = lib.mdDoc "Item this limit applies to";
+ type = enum [
+ "core"
+ "data"
+ "fsize"
+ "memlock"
+ "nofile"
+ "rss"
+ "stack"
+ "cpu"
+ "nproc"
+ "as"
+ "maxlogins"
+ "maxsyslogins"
+ "priority"
+ "locks"
+ "sigpending"
+ "msgqueue"
+ "nice"
+ "rtprio"
+ ];
+ };
+
+ value = mkOption {
+ description = lib.mdDoc "Value of this limit";
+ type = oneOf [str int];
+ };
+ };
+ }));
+
+ motd =
+ if isNull config.users.motdFile
+ then pkgs.writeText "motd" config.users.motd
+ else config.users.motdFile;
+
+ makePAMService = name: service: {
+ name = "pam.d/${name}";
+ value.source = pkgs.writeText "${name}.pam" service.text;
+ };
+in {
+ imports = [
+ (mkRenamedOptionModule ["security" "pam" "enableU2F"] ["security" "pam" "u2f" "enable"])
+ ];
+
+ ###### interface
+
+ options = {
+ security.pam.loginLimits = mkOption {
+ default = [];
+ type = limitsType;
+ example = [
+ {
+ domain = "ftp";
+ type = "hard";
+ item = "nproc";
+ value = "0";
+ }
+ {
+ domain = "@student";
+ type = "-";
+ item = "maxlogins";
+ value = "4";
+ }
+ ];
+
+ description = lib.mdDoc ''
+ Define resource limits that should apply to users or groups.
+ Each item in the list should be an attribute set with a
+ {var}`domain`, {var}`type`,
+ {var}`item`, and {var}`value`
+ attribute. The syntax and semantics of these attributes
+ must be that described in {manpage}`limits.conf(5)`.
+
+ Note that these limits do not apply to systemd services,
+ whose limits can be changed via {option}`systemd.extraConfig`
+ instead.
+ '';
+ };
+
+ security.pam.services = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule pamOpts);
+ description = lib.mdDoc ''
+ This option defines the PAM services. A service typically
+ corresponds to a program that uses PAM,
+ e.g. {command}`login` or {command}`passwd`.
+ Each attribute of this set defines a PAM service, with the attribute name
+ defining the name of the service.
+ '';
+ };
+
+ security.pam.makeHomeDir.skelDirectory = mkOption {
+ type = types.str;
+ default = "/var/empty";
+ example = "/etc/skel";
+ description = lib.mdDoc ''
+ Path to skeleton directory whose contents are copied to home
+ directories newly created by `pam_mkhomedir`.
+ '';
+ };
+
+ security.pam.enableSSHAgentAuth = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Enable sudo logins if the user's SSH agent provides a key
+ present in {file}`~/.ssh/authorized_keys`.
+ This allows machines to exclusively use SSH keys instead of
+ passwords.
+ '';
+ };
+
+ security.pam.enableOTPW = mkEnableOption (lib.mdDoc "the OTPW (one-time password) PAM module");
+
+ security.pam.krb5 = {
+ enable = mkOption {
+ default = config.krb5.enable;
+ defaultText = literalExpression "config.krb5.enable";
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enables Kerberos PAM modules (`pam-krb5`,
+ `pam-ccreds`).
+
+ If set, users can authenticate with their Kerberos password.
+ This requires a valid Kerberos configuration
+ (`config.krb5.enable` should be set to
+ `true`).
+
+ Note that the Kerberos PAM modules are not necessary when using SSS
+ to handle Kerberos authentication.
+ '';
+ };
+ };
+
+ security.pam.p11 = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enables P11 PAM (`pam_p11`) module.
+
+ If set, users can log in with SSH keys and PKCS#11 tokens.
+
+ More information can be found [here](https://github.com/OpenSC/pam_p11).
+ '';
+ };
+
+ control = mkOption {
+ default = "sufficient";
+ type = types.enum ["required" "requisite" "sufficient" "optional"];
+ description = lib.mdDoc ''
+ This option sets pam "control".
+ If you want to have multi factor authentication, use "required".
+ If you want to use the PKCS#11 device instead of the regular password,
+ use "sufficient".
+
+ Read
+ {manpage}`pam.conf(5)`
+ for better understanding of this option.
+ '';
+ };
+ };
+
+ security.pam.u2f = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enables U2F PAM (`pam-u2f`) module.
+
+ If set, users listed in
+ {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+ {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
+ not set) are able to log in with the associated U2F key. The path can
+ be changed using {option}`security.pam.u2f.authFile` option.
+
+ File format is:
+ `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
+ This file can be generated using {command}`pamu2fcfg` command.
+
+ More information can be found [here](https://developers.yubico.com/pam-u2f/).
+ '';
+ };
+
+ authFile = mkOption {
+ default = null;
+ type = with types; nullOr path;
+ description = lib.mdDoc ''
+ By default `pam-u2f` module reads the keys from
+ {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+ {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
+ not set).
+
+ If you want to change auth file locations or centralize database (for
+ example use {file}`/etc/u2f-mappings`) you can set this
+ option.
+
+ File format is:
+ `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
+ This file can be generated using {command}`pamu2fcfg` command.
+
+ More information can be found [here](https://developers.yubico.com/pam-u2f/).
+ '';
+ };
+
+ appId = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ description = lib.mdDoc ''
+ By default `pam-u2f` module sets the application
+ ID to `pam://$HOSTNAME`.
+
+ When using {command}`pamu2fcfg`, you can specify your
+ application ID with the `-i` flag.
+
+ More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
+ '';
+ };
+
+ origin = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ description = lib.mdDoc ''
+ By default `pam-u2f` module sets the origin
+ to `pam://$HOSTNAME`.
+ Setting origin to an host independent value will allow you to
+ reuse credentials across machines
+
+ When using {command}`pamu2fcfg`, you can specify your
+ application ID with the `-o` flag.
+
+ More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
+ '';
+ };
+
+ control = mkOption {
+ default = "sufficient";
+ type = types.enum ["required" "requisite" "sufficient" "optional"];
+ description = lib.mdDoc ''
+ This option sets pam "control".
+ If you want to have multi factor authentication, use "required".
+ If you want to use U2F device instead of regular password, use "sufficient".
+
+ Read
+ {manpage}`pam.conf(5)`
+ for better understanding of this option.
+ '';
+ };
+
+ debug = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Debug output to stderr.
+ '';
+ };
+
+ interactive = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Set to prompt a message and wait before testing the presence of a U2F device.
+ Recommended if your device doesn’t have a tactile trigger.
+ '';
+ };
+
+ cue = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ By default `pam-u2f` module does not inform user
+ that he needs to use the u2f device, it just waits without a prompt.
+
+ If you set this option to `true`,
+ `cue` option is added to `pam-u2f`
+ module and reminder message will be displayed.
+ '';
+ };
+ };
+
+ security.pam.ussh = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enables Uber's USSH PAM (`pam-ussh`) module.
+
+ This is similar to `pam-ssh-agent`, except that
+ the presence of a CA-signed SSH key with a valid principal is checked
+ instead.
+
+ Note that this module must both be enabled using this option and on a
+ per-PAM-service level as well (using `usshAuth`).
+
+ More information can be found [here](https://github.com/uber/pam-ussh).
+ '';
+ };
+
+ caFile = mkOption {
+ default = null;
+ type = with types; nullOr path;
+ description = lib.mdDoc ''
+ By default `pam-ussh` reads the trusted user CA keys
+ from {file}`/etc/ssh/trusted_user_ca`.
+
+ This should be set the same as your `TrustedUserCAKeys`
+ option for sshd.
+ '';
+ };
+
+ authorizedPrincipals = mkOption {
+ default = null;
+ type = with types; nullOr commas;
+ description = lib.mdDoc ''
+ Comma-separated list of authorized principals to permit; if the user
+ presents a certificate with one of these principals, then they will be
+ authorized.
+
+ Note that `pam-ussh` also requires that the certificate
+ contain a principal matching the user's username. The principals from
+ this list are in addition to those principals.
+
+ Mutually exclusive with `authorizedPrincipalsFile`.
+ '';
+ };
+
+ authorizedPrincipalsFile = mkOption {
+ default = null;
+ type = with types; nullOr path;
+ description = lib.mdDoc ''
+ Path to a list of principals; if the user presents a certificate with
+ one of these principals, then they will be authorized.
+
+ Note that `pam-ussh` also requires that the certificate
+ contain a principal matching the user's username. The principals from
+ this file are in addition to those principals.
+
+ Mutually exclusive with `authorizedPrincipals`.
+ '';
+ };
+
+ group = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ description = lib.mdDoc ''
+ If set, then the authenticating user must be a member of this group
+ to use this module.
+ '';
+ };
+
+ control = mkOption {
+ default = "sufficient";
+ type = types.enum ["required" "requisite" "sufficient" "optional"];
+ description = lib.mdDoc ''
+ This option sets pam "control".
+ If you want to have multi factor authentication, use "required".
+ If you want to use the SSH certificate instead of the regular password,
+ use "sufficient".
+
+ Read
+ {manpage}`pam.conf(5)`
+ for better understanding of this option.
+ '';
+ };
+ };
+
+ security.pam.yubico = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Enables Yubico PAM (`yubico-pam`) module.
+
+ If set, users listed in
+ {file}`~/.yubico/authorized_yubikeys`
+ are able to log in with the associated Yubikey tokens.
+
+ The file must have only one line:
+ `username:yubikey_token_id1:yubikey_token_id2`
+ More information can be found [here](https://developers.yubico.com/yubico-pam/).
+ '';
+ };
+ control = mkOption {
+ default = "sufficient";
+ type = types.enum ["required" "requisite" "sufficient" "optional"];
+ description = lib.mdDoc ''
+ This option sets pam "control".
+ If you want to have multi factor authentication, use "required".
+ If you want to use Yubikey instead of regular password, use "sufficient".
+
+ Read
+ {manpage}`pam.conf(5)`
+ for better understanding of this option.
+ '';
+ };
+ id = mkOption {
+ example = "42";
+ type = types.str;
+ description = lib.mdDoc "client id";
+ };
+
+ debug = mkOption {
+ default = false;
+ type = types.bool;
+ description = lib.mdDoc ''
+ Debug output to stderr.
+ '';
+ };
+ mode = mkOption {
+ default = "client";
+ type = types.enum ["client" "challenge-response"];
+ description = lib.mdDoc ''
+ Mode of operation.
+
+ Use "client" for online validation with a YubiKey validation service such as
+ the YubiCloud.
+
+ Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
+ Challenge-Response configurations. See the man-page ykpamcfg(1) for further
+ details on how to configure offline Challenge-Response validation.
+
+ More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
+ '';
+ };
+ challengeResponsePath = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = lib.mdDoc ''
+ If not null, set the path used by yubico pam module where the challenge expected response is stored.
+
+ More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
+ '';
+ };
+ };
+
+ security.pam.enableEcryptfs = mkEnableOption (lib.mdDoc "eCryptfs PAM module (mounting ecryptfs home directory on login)");
+ security.pam.enableFscrypt = mkEnableOption (lib.mdDoc ''
+ Enables fscrypt to automatically unlock directories with the user's login password.
+
+ This also enables a service at security.pam.services.fscrypt which is used by
+ fscrypt to verify the user's password when setting up a new protector. If you
+ use something other than pam_unix to verify user passwords, please remember to
+ adjust this PAM service.
+ '');
+
+ users.motd = mkOption {
+ default = null;
+ example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
+ type = types.nullOr types.lines;
+ description = lib.mdDoc "Message of the day shown to users when they log in.";
+ };
+
+ users.motdFile = mkOption {
+ default = null;
+ example = "/etc/motd";
+ type = types.nullOr types.path;
+ description = lib.mdDoc "A file containing the message of the day shown to users when they log in.";
+ };
+ };
+
+ ###### implementation
+
+ config = {
+ assertions = [
+ {
+ assertion = isNull config.users.motd || isNull config.users.motdFile;
+ message = ''
+ Only one of users.motd and users.motdFile can be set.
+ '';
+ }
+ ];
+
+ environment.systemPackages =
+ # Include the PAM modules in the system path mostly for the manpages.
+ [pkgs.pam]
+ ++ optional config.users.ldap.enable pam_ldap
+ ++ optional config.services.sssd.enable pkgs.sssd
+ ++ optionals config.security.pam.krb5.enable [pam_krb5 pam_ccreds]
+ ++ optionals config.security.pam.enableOTPW [pkgs.otpw]
+ ++ optionals config.security.pam.oath.enable [pkgs.oath-toolkit]
+ ++ optionals config.security.pam.p11.enable [pkgs.pam_p11]
+ ++ optionals config.security.pam.enableFscrypt [pkgs.fscrypt-experimental]
+ ++ optionals config.security.pam.u2f.enable [pkgs.pam_u2f];
+
+ boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs ["ecryptfs"];
+
+ security.wrappers = {
+ unix_chkpwd = {
+ setuid = true;
+ owner = "root";
+ group = "root";
+ source = "${pkgs.pam}/bin/unix_chkpwd";
+ };
+ };
+
+ environment.etc = mapAttrs' makePAMService config.security.pam.services;
+
+ security.pam.services =
+ {
+ other.text = ''
+ auth required pam_warn.so
+ auth required pam_deny.so
+ account required pam_warn.so
+ account required pam_deny.so
+ password required pam_warn.so
+ password required pam_deny.so
+ session required pam_warn.so
+ session required pam_deny.so
+ '';
+
+ # Most of these should be moved to specific modules.
+ i3lock = {};
+ i3lock-color = {};
+ vlock = {};
+ xlock = {};
+ xscreensaver = {};
+
+ runuser = {
+ rootOK = true;
+ unixAuth = false;
+ setEnvironment = false;
+ };
+
+ /*
+ FIXME: should runuser -l start a systemd session? Currently
+ it complains "Cannot create session: Already running in a
+ session".
+ */
+ runuser-l = {
+ rootOK = true;
+ unixAuth = false;
+ };
+ }
+ // optionalAttrs (config.security.pam.enableFscrypt) {
+ # Allow fscrypt to verify login passphrase
+ fscrypt = {};
+ };
+
+ security.apparmor.includes."abstractions/pam" = let
+ isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
+ in
+ lib.concatMapStrings
+ (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
+ (attrNames config.security.pam.services)
+ + ''
+ mr ${getLib pkgs.pam}/lib/security/pam_filter/*,
+ mr ${getLib pkgs.pam}/lib/security/pam_*.so,
+ r ${getLib pkgs.pam}/lib/security/,
+ ''
+ + optionalString use_ldap ''
+ mr ${pam_ldap}/lib/security/pam_ldap.so,
+ ''
+ + optionalString config.services.sssd.enable ''
+ mr ${pkgs.sssd}/lib/security/pam_sss.so,
+ ''
+ + optionalString config.security.pam.krb5.enable ''
+ mr ${pam_krb5}/lib/security/pam_krb5.so,
+ mr ${pam_ccreds}/lib/security/pam_ccreds.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
+ mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
+ mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication)) ''
+ mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
+ ''
+ + optionalString (config.security.pam.enableSSHAgentAuth
+ && isEnabled (cfg: cfg.sshAgentAuth)) ''
+ mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.fprintAuth)) ''
+ mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.howdyAuth)) ''
+ mr ${config.services.howdy.package}/lib/security/pam_howdy.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.u2fAuth)) ''
+ mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.usbAuth)) ''
+ mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.usshAuth)) ''
+ mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.oathAuth)) ''
+ "mr ${pkgs.oath-toolkit}/lib/security/pam_oath.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.mysqlAuth)) ''
+ mr ${pkgs.pam_mysql}/lib/security/pam_mysql.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.yubicoAuth)) ''
+ mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.duoSecurity.enable)) ''
+ mr ${pkgs.duo-unix}/lib/security/pam_duo.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.otpwAuth)) ''
+ mr ${pkgs.otpw}/lib/security/pam_otpw.so,
+ ''
+ + optionalString config.security.pam.enableEcryptfs ''
+ mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
+ ''
+ + optionalString config.security.pam.enableFscrypt ''
+ mr ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.pamMount)) ''
+ mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.enableGnomeKeyring)) ''
+ mr ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.startSession)) ''
+ mr ${config.systemd.package}/lib/security/pam_systemd.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.enableAppArmor)
+ && config.security.apparmor.enable) ''
+ mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,
+ ''
+ + optionalString (isEnabled (cfg: cfg.enableKwallet)) ''
+ mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
+ ''
+ + optionalString config.virtualisation.lxc.lxcfs.enable ''
+ mr ${pkgs.lxc}/lib/security/pam_cgfs.so
+ ''
+ + optionalString config.services.homed.enable ''
+ mr ${config.systemd.package}/lib/security/pam_systemd_home.so
+ '';
+ };
+}
diff --git a/foreign/dotfiles/modules/regreet.nix b/foreign/dotfiles/modules/regreet.nix
new file mode 100644
index 0000000..494f0e6
--- /dev/null
+++ b/foreign/dotfiles/modules/regreet.nix
@@ -0,0 +1,73 @@
+{
+ lib,
+ pkgs,
+ config,
+ ...
+}: let
+ cfg = config.programs.regreet;
+ settingsFormat = pkgs.formats.toml {};
+in {
+ options.programs.regreet = {
+ enable =
+ lib.mkEnableOption null
+ // {
+ description = lib.mdDoc ''
+ Enable ReGreet, a clean and customizable greeter for greetd.
+
+ To use ReGreet, {option}`services.greetd` has to be enabled and
+ {option}`services.greetd.settings.default_session` should contain the
+ appropriate configuration to launch
+ {option}`config.programs.regreet.package`. For examples, see the
+ [ReGreet Readme](https://github.com/rharish101/ReGreet#set-as-default-session).
+ '';
+ };
+
+ package = lib.mkPackageOptionMD pkgs ["greetd" "regreet"] {};
+
+ settings = lib.mkOption {
+ type = lib.types.either lib.types.path settingsFormat.type;
+ default = {};
+ description = lib.mdDoc ''
+ ReGreet configuration file. Refer
+
+ for options.
+ '';
+ };
+
+ extraCss = lib.mkOption {
+ type = lib.types.either lib.types.path lib.types.lines;
+ default = "";
+ description = lib.mdDoc ''
+ Extra CSS rules to apply on top of the GTK theme. Refer to
+ [GTK CSS Properties](https://docs.gtk.org/gtk4/css-properties.html) for
+ modifiable properties.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.greetd = {
+ enable = lib.mkDefault true;
+ settings.default_session.command = lib.mkDefault "${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}";
+ };
+
+ environment.etc = {
+ "greetd/regreet.css" =
+ if lib.isPath cfg.extraCss
+ then {source = cfg.extraCss;}
+ else {text = cfg.extraCss;};
+
+ "greetd/regreet.toml".source =
+ if lib.isPath cfg.settings
+ then cfg.settings
+ else settingsFormat.generate "regreet.toml" cfg.settings;
+ };
+
+ systemd.tmpfiles.rules = let
+ user = config.services.greetd.settings.default_session.user;
+ in [
+ "d /var/log/regreet 0755 greeter ${user} - -"
+ "d /var/cache/regreet 0755 greeter ${user} - -"
+ ];
+ };
+}
diff --git a/foreign/dotfiles/modules/security.nix b/foreign/dotfiles/modules/security.nix
new file mode 100644
index 0000000..3bc2d1c
--- /dev/null
+++ b/foreign/dotfiles/modules/security.nix
@@ -0,0 +1,50 @@
+# security tweaks borrowed from @hlissner
+{
+ boot.kernel.sysctl = {
+ # The Magic SysRq key is a key combo that allows users connected to the
+ # system console of a Linux kernel to perform some low-level commands.
+ # Disable it, since we don't need it, and is a potential security concern.
+ "kernel.sysrq" = 0;
+
+ ## TCP hardening
+ # Prevent bogus ICMP errors from filling up logs.
+ "net.ipv4.icmp_ignore_bogus_error_responses" = 1;
+ # Reverse path filtering causes the kernel to do source validation of
+ # packets received from all interfaces. This can mitigate IP spoofing.
+ "net.ipv4.conf.default.rp_filter" = 1;
+ "net.ipv4.conf.all.rp_filter" = 1;
+ # Do not accept IP source route packets (we're not a router)
+ "net.ipv4.conf.all.accept_source_route" = 0;
+ "net.ipv6.conf.all.accept_source_route" = 0;
+ # Don't send ICMP redirects (again, we're on a router)
+ "net.ipv4.conf.all.send_redirects" = 0;
+ "net.ipv4.conf.default.send_redirects" = 0;
+ # Refuse ICMP redirects (MITM mitigations)
+ "net.ipv4.conf.all.accept_redirects" = 0;
+ "net.ipv4.conf.default.accept_redirects" = 0;
+ "net.ipv4.conf.all.secure_redirects" = 0;
+ "net.ipv4.conf.default.secure_redirects" = 0;
+ "net.ipv6.conf.all.accept_redirects" = 0;
+ "net.ipv6.conf.default.accept_redirects" = 0;
+ # Protects against SYN flood attacks
+ "net.ipv4.tcp_syncookies" = 1;
+ # Incomplete protection again TIME-WAIT assassination
+ "net.ipv4.tcp_rfc1337" = 1;
+
+ ## TCP optimization
+ # TCP Fast Open is a TCP extension that reduces network latency by packing
+ # data in the sender’s initial TCP SYN. Setting 3 = enable TCP Fast Open for
+ # both incoming and outgoing connections:
+ "net.ipv4.tcp_fastopen" = 3;
+ # Bufferbloat mitigations + slight improvement in throughput & latency
+ "net.ipv4.tcp_congestion_control" = "bbr";
+ "net.core.default_qdisc" = "cake";
+ };
+ boot.kernelModules = ["tcp_bbr"];
+
+ # So we don't have to do this later...
+ security.acme = {
+ acceptTerms = true;
+ defaults.email = "fufexan@proton.me";
+ };
+}
diff --git a/foreign/dotfiles/modules/xserver.nix b/foreign/dotfiles/modules/xserver.nix
new file mode 100644
index 0000000..3c95f05
--- /dev/null
+++ b/foreign/dotfiles/modules/xserver.nix
@@ -0,0 +1,16 @@
+{
+ services.xserver = {
+ enable = true;
+ displayManager.gdm.enable = true;
+
+ libinput = {
+ enable = true;
+ # disable mouse acceleration
+ mouse.accelProfile = "flat";
+ mouse.accelSpeed = "0";
+ mouse.middleEmulation = false;
+ # touchpad settings
+ touchpad.naturalScrolling = true;
+ };
+ };
+}
diff --git a/foreign/dotfiles/pkgs/README.md b/foreign/dotfiles/pkgs/README.md
new file mode 100644
index 0000000..8de390f
--- /dev/null
+++ b/foreign/dotfiles/pkgs/README.md
@@ -0,0 +1,17 @@
+# Packages & Overlays
+
+Here are all of the packages I couldn't find anywhere and packaged by myself,
+or overrides that I use throughout the configuration.
+
+Name | Description
+---- | -----------
+DiscordCanary | DiscordCanary + OpenASAR
+[GDB-Frontend](https://github.com/rohanrhu/gdb-frontend) | Easy, flexible and extensible GUI debugger
+iso | My system config built as an ISO
+`patches` | Various patches used in my system
+Repl | Cool Nix Repl that auto-loads the system flake or the current dir flake
+[SpotifyWM](https://github.com/dasJ/spotifywm) | Spotify preloader that sets WM_NAME on start
+Spotify Wrapped WM | Spotify wrapped with SpotifyWM
+[Sway-hidpi](https://github.com/swaywm/sway) | Sway with XWayland HiDPI patches
+[Technic](https://www.technicpack.net) | Technic Launcher derivation
+[Waveform](https://www.tracktion.com/welcome/waveform-free) | DAW from Tracktion that works on Linux
diff --git a/foreign/dotfiles/pkgs/catppuccin-plymouth/default.nix b/foreign/dotfiles/pkgs/catppuccin-plymouth/default.nix
new file mode 100644
index 0000000..22517dd
--- /dev/null
+++ b/foreign/dotfiles/pkgs/catppuccin-plymouth/default.nix
@@ -0,0 +1,36 @@
+{
+ lib,
+ stdenvNoCC,
+ fetchFromGitHub,
+}:
+stdenvNoCC.mkDerivation {
+ pname = "catppuccin-plymouth";
+ version = "unstable-2022-12-10";
+
+ src = fetchFromGitHub {
+ owner = "catppuccin";
+ repo = "plymouth";
+ rev = "d4105cf336599653783c34c4a2d6ca8c93f9281c";
+ hash = "sha256-quBSH8hx3gD7y1JNWAKQdTk3CmO4t1kVo4cOGbeWlNE=";
+ };
+
+ dontConfigure = true;
+ dontBuild = true;
+
+ installPhase = ''
+ runHook preInstall
+
+ mkdir -p $out/share/plymouth
+ cp -r themes $out/share/plymouth/
+
+ runHook postInstall
+ '';
+
+ meta = {
+ description = "Soothing pastel theme for Plymouth";
+ homepage = "https://github.com/catppuccin/plymouth";
+ license = lib.licenses.mit;
+ maintainers = with lib.maintainers; [fufexan];
+ platforms = lib.platforms.linux;
+ };
+}
diff --git a/foreign/dotfiles/pkgs/default.nix b/foreign/dotfiles/pkgs/default.nix
new file mode 100644
index 0000000..c3904b9
--- /dev/null
+++ b/foreign/dotfiles/pkgs/default.nix
@@ -0,0 +1,13 @@
+{
+ _inputs,
+ self,
+ ...
+}: {
+ systems = ["x86_64-linux"];
+
+ flake.overlays.default = import ./overlays.nix _inputs;
+
+ perSystem = {pkgs, ...}: {
+ packages = self.overlays.default null pkgs;
+ };
+}
diff --git a/foreign/dotfiles/pkgs/discord.nix b/foreign/dotfiles/pkgs/discord.nix
new file mode 100644
index 0000000..68d3e56
--- /dev/null
+++ b/foreign/dotfiles/pkgs/discord.nix
@@ -0,0 +1,59 @@
+{
+ lib,
+ pkgs,
+ inputs,
+ ...
+}:
+with pkgs; let
+ binaryName = "DiscordCanary";
+
+ disableBreakingUpdates =
+ runCommand "disable-breaking-updates.py"
+ {
+ pythonInterpreter = "${python3.interpreter}";
+ configDirName = lib.toLower binaryName;
+ } ''
+ mkdir -p $out/bin
+ cp "${inputs.nixpkgs}/pkgs/applications/networking/instant-messengers/discord/disable-breaking-updates.py" $out/bin/disable-breaking-updates.py
+ substituteAllInPlace $out/bin/disable-breaking-updates.py
+ chmod +x $out/bin/disable-breaking-updates.py
+ '';
+in
+ (discord-canary.override {
+ nss = pkgs.nss_latest;
+ withOpenASAR = true;
+ })
+ .overrideAttrs (old: rec {
+ libPath = old.libPath + ":${libglvnd}/lib";
+
+ installPhase = ''
+ runHook preInstall
+
+ mkdir -p $out/{bin,opt/${binaryName},share/pixmaps,share/icons/hicolor/256x256/apps}
+ mv * $out/opt/${binaryName}
+
+ chmod +x $out/opt/${binaryName}/${binaryName}
+ patchelf --set-interpreter ${stdenv.cc.bintools.dynamicLinker} \
+ $out/opt/${binaryName}/${binaryName}
+
+ wrapProgramShell $out/opt/${binaryName}/${binaryName} \
+ "''${gappsWrapperArgs[@]}" \
+ --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform=wayland --enable-features=WaylandWindowDecorations}}" \
+ --prefix XDG_DATA_DIRS : "${gtk3}/share/gsettings-schemas/${gtk3.name}/" \
+ --prefix LD_LIBRARY_PATH : ${libPath}:$out/opt/${binaryName} \
+ --run "${lib.getExe disableBreakingUpdates}"
+
+ ln -s $out/opt/${binaryName}/${binaryName} $out/bin/
+ # Without || true the install would fail on case-insensitive filesystems
+ ln -s $out/opt/${binaryName}/${binaryName} $out/bin/${
+ lib.strings.toLower binaryName
+ } || true
+
+ ln -s $out/opt/${binaryName}/discord.png $out/share/pixmaps/${old.pname}.png
+ ln -s $out/opt/${binaryName}/discord.png $out/share/icons/hicolor/256x256/apps/${old.pname}.png
+
+ ln -s "${old.desktopItem}/share/applications" $out/share/
+
+ runHook postInstall
+ '';
+ })
diff --git a/foreign/dotfiles/pkgs/gdb-frontend/default.nix b/foreign/dotfiles/pkgs/gdb-frontend/default.nix
new file mode 100644
index 0000000..49a4bb0
--- /dev/null
+++ b/foreign/dotfiles/pkgs/gdb-frontend/default.nix
@@ -0,0 +1,54 @@
+{
+ lib,
+ bash,
+ stdenv,
+ fetchFromGitHub,
+ python3,
+ gdb,
+ tmux,
+}:
+stdenv.mkDerivation rec {
+ pname = "gdb-frontend";
+ version = "0.10.3-beta";
+
+ src = fetchFromGitHub {
+ repo = pname;
+ owner = "rohanrhu";
+ rev = "v${version}";
+ sha256 = "sha256-+l1SguRKnLDqT4rgXcod9xhiCOJZEKdZlOZfTNzP7fk=";
+ };
+
+ buildInputs = [gdb python3 tmux];
+ propagatedUserEnvPkgs = [gdb python3 tmux];
+
+ dontConfigure = true;
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp -r . $out
+ echo "${python3}/bin/python $out/run.py \"\$@\"" > $out/bin/gdbfrontend
+ chmod +x $out/bin/gdbfrontend
+ '';
+
+ postPatch = ''
+ substituteInPlace "run.py" \
+ --replace "/bin/bash" "${bash}/bin/bash"
+ substituteInPlace "gdbfrontend-window" \
+ --replace "/bin/bash" "${bash}/bin/bash"
+ substituteInPlace "build_gdb.sh" \
+ --replace "/bin/bash\n" "${bash}/bin/bash\n"
+ substituteInPlace "url_modules/api/shell.py" \
+ --replace "/bin/bash" "${bash}/bin/bash"
+ substituteInPlace "commands/gdbfrontend" \
+ --replace "/bin/bash" "${bash}/bin/bash"
+ '';
+
+ meta = with lib; {
+ description = "GDBFrontend is an easy, flexible and extensionable gui debugger";
+ homepage = "https://github.com/rohanrhu/gdb-frontend";
+ mainProgram = "gdbfrontend";
+ platforms = platforms.linux;
+ maintainers = with maintainers; [fufexan];
+ };
+}
diff --git a/foreign/dotfiles/pkgs/howdy/default.nix b/foreign/dotfiles/pkgs/howdy/default.nix
new file mode 100644
index 0000000..62ab8a0
--- /dev/null
+++ b/foreign/dotfiles/pkgs/howdy/default.nix
@@ -0,0 +1,86 @@
+{
+ stdenv,
+ lib,
+ bzip2,
+ fetchFromGitHub,
+ fetchurl,
+ fmt,
+ gettext,
+ inih,
+ installShellFiles,
+ libevdev,
+ meson,
+ ninja,
+ pam,
+ pkg-config,
+ python3,
+} @ args: let
+ data = import ./sources.nix args;
+in
+ stdenv.mkDerivation {
+ pname = "howdy";
+ version = "unstable-2023-02-28";
+ inherit (data) src;
+
+ # fix paths
+ patches = [./howdy.patch];
+
+ postPatch = let
+ howdypath = "${placeholder "out"}/lib/security/howdy";
+ in ''
+ substituteInPlace howdy/src/cli/add.py --replace "@PATH@" "${howdypath}"
+ substituteInPlace howdy/src/cli/config.py --replace '/bin/nano' 'nano'
+ substituteInPlace howdy/src/cli/test.py --replace "@PATH@" "${howdypath}"
+
+ substituteInPlace howdy/src/pam/main.cc \
+ --replace "python3" "${data.py}/bin/python" \
+ --replace "/lib/security/howdy/compare.py" "${howdypath}/compare.py"
+
+ substituteInPlace howdy/src/compare.py \
+ --replace "/lib/security/howdy" "${howdypath}" \
+ --replace "@PATH@" "${howdypath}"
+ '';
+
+ nativeBuildInputs = [bzip2 installShellFiles meson ninja pkg-config];
+ buildInputs = [data.py fmt gettext inih libevdev pam];
+
+ # build howdy_pam
+ preConfigure = ''
+ cd howdy/src/pam
+ export DESTDIR=$out
+ '';
+
+ postInstall = let
+ libDir = "$out/lib/security/howdy";
+ inherit (lib) mapAttrsToList concatStrings;
+ in ''
+ # done with howdy_pam, go back to source root
+ cd ../../../..
+
+ mkdir -p $out/share/licenses/howdy
+ install -Dm644 LICENSE $out/share/licenses/howdy/LICENSE
+ rm -rf howdy/src/pam
+ mkdir -p ${libDir}
+ cp -r howdy/src/* ${libDir}
+
+ rm -rf ${libDir}/pam-config ${libDir}/dlib-data/*
+ ${concatStrings (mapAttrsToList (n: v: ''
+ bzip2 -dc ${v} > ${libDir}/dlib-data/${n}
+ '')
+ data.data)}
+
+ mkdir -p $out/bin
+ ln -s ${libDir}/cli.py $out/bin/howdy
+
+ mkdir -p "$out/share/bash-completion/completions"
+ installShellCompletion --bash howdy/src/autocomplete/howdy
+ '';
+
+ meta = {
+ description = "Windows Hello™ style facial authentication for Linux";
+ homepage = "https://github.com/boltgolt/howdy";
+ license = lib.licenses.mit;
+ platforms = lib.platforms.linux;
+ maintainers = with lib.maintainers; [fufexan];
+ };
+ }
diff --git a/foreign/dotfiles/pkgs/howdy/howdy.patch b/foreign/dotfiles/pkgs/howdy/howdy.patch
new file mode 100644
index 0000000..34a481e
--- /dev/null
+++ b/foreign/dotfiles/pkgs/howdy/howdy.patch
@@ -0,0 +1,155 @@
+diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py
+index 8951e31..4f793d7 100644
+--- a/howdy/src/cli/add.py
++++ b/howdy/src/cli/add.py
+@@ -30,9 +30,9 @@ import cv2
+ config_path = "/etc/howdy"
+
+ # Test if at lest 1 of the data files is there and abort if it's not
+-if not os.path.isfile(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
++if not os.path.isfile("@PATH@/dlib-data/shape_predictor_5_face_landmarks.dat"):
+ print(_("Data files have not been downloaded, please run the following commands:"))
+- print("\n\tcd " + config_path + "/dlib-data")
++ print("\n\tcd " + "@PATH@/dlib-data")
+ print("\tsudo ./install.sh\n")
+ sys.exit(1)
+
+@@ -42,23 +42,23 @@ config.read(config_path + "/config.ini")
+
+ use_cnn = config.getboolean("core", "use_cnn", fallback=False)
+ if use_cnn:
+- face_detector = dlib.cnn_face_detection_model_v1(config_path + "/dlib-data/mmod_human_face_detector.dat")
++ face_detector = dlib.cnn_face_detection_model_v1("@PATH@/dlib-data/mmod_human_face_detector.dat")
+ else:
+ face_detector = dlib.get_frontal_face_detector()
+
+-pose_predictor = dlib.shape_predictor(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat")
+-face_encoder = dlib.face_recognition_model_v1(config_path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
++pose_predictor = dlib.shape_predictor("@PATH@/dlib-data/shape_predictor_5_face_landmarks.dat")
++face_encoder = dlib.face_recognition_model_v1("@PATH@/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
+
+ user = builtins.howdy_user
+ # The permanent file to store the encoded model in
+-enc_file = config_path + "/models/" + user + ".dat"
++enc_file = "/var/lib/howdy/models/" + user + ".dat"
+ # Known encodings
+ encodings = []
+
+ # Make the ./models folder if it doesn't already exist
+-if not os.path.exists(config_path + "/models"):
++if not os.path.exists("/var/lib/howdy/models"):
+ print(_("No face model folder found, creating one"))
+- os.makedirs(config_path + "/models")
++ os.makedirs("/var/lib/howdy/models")
+
+ # To try read a premade encodings file if it exists
+ try:
+diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py
+index 6fa5f3e..fc7676c 100644
+--- a/howdy/src/cli/clear.py
++++ b/howdy/src/cli/clear.py
+@@ -8,7 +8,7 @@ import builtins
+ from i18n import _
+
+ # Get the full path to this file
+-path = "/etc/howdy/models"
++path = "/var/lib/howdy/models"
+ # Get the passed user
+ user = builtins.howdy_user
+
+diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py
+index 3532e9f..b9e2a31 100644
+--- a/howdy/src/cli/list.py
++++ b/howdy/src/cli/list.py
+@@ -10,7 +10,7 @@ import builtins
+ from i18n import _
+
+ # Get the absolute path and the username
+-path = "/etc/howdy"
++path = "/var/lib/howdy"
+ user = builtins.howdy_user
+
+ # Check if the models file has been created yet
+diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py
+index 6321e0b..7c13d79 100644
+--- a/howdy/src/cli/remove.py
++++ b/howdy/src/cli/remove.py
+@@ -9,7 +9,7 @@ import builtins
+ from i18n import _
+
+ # Get the absolute path and the username
+-path = "/etc/howdy"
++path = "/var/lib/howdy"
+ user = builtins.howdy_user
+
+ # Check if enough arguments have been passed
+diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py
+index d54929a..fa45500 100644
+--- a/howdy/src/cli/test.py
++++ b/howdy/src/cli/test.py
+@@ -59,20 +59,20 @@ use_cnn = config.getboolean('core', 'use_cnn', fallback=False)
+
+ if use_cnn:
+ face_detector = dlib.cnn_face_detection_model_v1(
+- path + "/dlib-data/mmod_human_face_detector.dat"
++ "@PATH@/dlib-data/mmod_human_face_detector.dat"
+ )
+ else:
+ face_detector = dlib.get_frontal_face_detector()
+
+-pose_predictor = dlib.shape_predictor(path + "/dlib-data/shape_predictor_5_face_landmarks.dat")
+-face_encoder = dlib.face_recognition_model_v1(path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
++pose_predictor = dlib.shape_predictor("@PATH@/dlib-data/shape_predictor_5_face_landmarks.dat")
++face_encoder = dlib.face_recognition_model_v1("@PATH@/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
+
+ encodings = []
+ models = None
+
+ try:
+ user = builtins.howdy_user
+- models = json.load(open(path + "/models/" + user + ".dat"))
++ models = json.load(open("/var/lib/howdy/models/" + user + ".dat"))
+
+ for model in models:
+ encodings += model["data"]
+diff --git a/howdy/src/compare.py b/howdy/src/compare.py
+index be19464..86a8d8f 100644
+--- a/howdy/src/compare.py
++++ b/howdy/src/compare.py
+@@ -48,22 +48,22 @@ def init_detector(lock):
+ global face_detector, pose_predictor, face_encoder
+
+ # Test if at lest 1 of the data files is there and abort if it's not
+- if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
++ if not os.path.isfile("@PATH@/dlib-data/shape_predictor_5_face_landmarks.dat"):
+ print(_("Data files have not been downloaded, please run the following commands:"))
+- print("\n\tcd " + PATH + "/dlib-data")
++ print("\n\tcd " + "@PATH@/dlib-data")
+ print("\tsudo ./install.sh\n")
+ lock.release()
+ exit(1)
+
+ # Use the CNN detector if enabled
+ if use_cnn:
+- face_detector = dlib.cnn_face_detection_model_v1(PATH + "/dlib-data/mmod_human_face_detector.dat")
++ face_detector = dlib.cnn_face_detection_model_v1("@PATH@/dlib-data/mmod_human_face_detector.dat")
+ else:
+ face_detector = dlib.get_frontal_face_detector()
+
+ # Start the others regardless
+- pose_predictor = dlib.shape_predictor(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat")
+- face_encoder = dlib.face_recognition_model_v1(PATH + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
++ pose_predictor = dlib.shape_predictor("@PATH@/dlib-data/shape_predictor_5_face_landmarks.dat")
++ face_encoder = dlib.face_recognition_model_v1("@PATH@/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
+
+ # Note the time it took to initialize detectors
+ timings["ll"] = time.time() - timings["ll"]
+@@ -129,7 +129,7 @@ face_encoder = None
+
+ # Try to load the face model from the models folder
+ try:
+- models = json.load(open(PATH + "/models/" + user + ".dat"))
++ models = json.load(open("/var/lib/howdy/models/" + user + ".dat"))
+
+ for model in models:
+ encodings += model["data"]
diff --git a/foreign/dotfiles/pkgs/howdy/sources.nix b/foreign/dotfiles/pkgs/howdy/sources.nix
new file mode 100644
index 0000000..6d3d19a
--- /dev/null
+++ b/foreign/dotfiles/pkgs/howdy/sources.nix
@@ -0,0 +1,33 @@
+{
+ fetchurl,
+ fetchFromGitHub,
+ python3,
+ ...
+}: {
+ data = {
+ "dlib_face_recognition_resnet_model_v1.dat" = fetchurl {
+ url = "https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2";
+ sha256 = "0fjm265l1fz5zdzx5n5yphl0v0vfajyw50ffamc4cd74848gdcdb";
+ };
+ "mmod_human_face_detector.dat" = fetchurl {
+ url = "https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2";
+ sha256 = "117wv582nsn585am2n9mg5q830qnn8skjr1yxgaiihcjy109x7nv";
+ };
+ "shape_predictor_5_face_landmarks.dat" = fetchurl {
+ url = "https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2";
+ sha256 = "0wm4bbwnja7ik7r28pv00qrl3i1h6811zkgnjfvzv7jwpyz7ny3f";
+ };
+ };
+
+ src = fetchFromGitHub {
+ owner = "fufexan";
+ repo = "howdy";
+ rev = "dceebf0f194e55429baaf9696ed5ae38ab9ddf92";
+ hash = "sha256-6C8AqB83pJ5LMq+0JdsrMfkGNxxn1uv8jfoCe8BAGeY=";
+ };
+
+ py = python3.withPackages (p: [
+ p.face_recognition
+ (p.opencv4.override {enableGtk3 = true;})
+ ]);
+}
diff --git a/foreign/dotfiles/pkgs/linux-enable-ir-emitter/default.nix b/foreign/dotfiles/pkgs/linux-enable-ir-emitter/default.nix
new file mode 100644
index 0000000..ef5d52c
--- /dev/null
+++ b/foreign/dotfiles/pkgs/linux-enable-ir-emitter/default.nix
@@ -0,0 +1,33 @@
+{
+ stdenv,
+ lib,
+ makeWrapper,
+ fetchFromGitHub,
+ fetchurl,
+ meson,
+ ninja,
+ pkg-config,
+ python3,
+ opencv,
+ usbutils,
+}:
+stdenv.mkDerivation rec {
+ pname = "linux-enable-ir-emitter";
+ version = "4.5.0";
+
+ src = fetchFromGitHub {
+ owner = "EmixamPP";
+ repo = pname;
+ rev = version;
+ hash = "sha256-Dv1ukn2TkXfBk1vc+6Uq7tw8WwCAfIcKl13BoOifz+Q=";
+ };
+
+ nativeBuildInputs = [meson ninja pkg-config makeWrapper];
+ buildInputs = [python3 opencv];
+
+ patches = [./remove-boot-set.patch];
+
+ postInstall = ''
+ wrapProgram $out/bin/${pname} --prefix PATH : ${lib.makeBinPath [usbutils]}
+ '';
+}
diff --git a/foreign/dotfiles/pkgs/linux-enable-ir-emitter/remove-boot-set.patch b/foreign/dotfiles/pkgs/linux-enable-ir-emitter/remove-boot-set.patch
new file mode 100644
index 0000000..9cd26f4
--- /dev/null
+++ b/foreign/dotfiles/pkgs/linux-enable-ir-emitter/remove-boot-set.patch
@@ -0,0 +1,11 @@
+diff --git a/sources/command/configure.py b/sources/command/configure.py
+index 2cd20d2..84ffb73 100644
+--- a/sources/command/configure.py
++++ b/sources/command/configure.py
+@@ -25,6 +25,5 @@ def configure(device: str, emitters: int, neg_answer_limit: int) -> NoReturn:
+ logging.info("Do not hesitate to visit the GitHub ! https://github.com/EmixamPP/linux-enable-ir-emitter/wiki")
+ else:
+ logging.info("The driver has been successfully generated.")
+- boot("enable")
+
+ exit(exit_code)
diff --git a/foreign/dotfiles/pkgs/overlays.nix b/foreign/dotfiles/pkgs/overlays.nix
new file mode 100644
index 0000000..51beb84
--- /dev/null
+++ b/foreign/dotfiles/pkgs/overlays.nix
@@ -0,0 +1,26 @@
+inputs: _: prev: {
+ # instant repl with automatic flake loading
+ repl = prev.callPackage ./repl {};
+
+ catppuccin-plymouth = prev.callPackage ./catppuccin-plymouth {};
+
+ discord-canary = prev.callPackage ./discord.nix {
+ pkgs = prev;
+ inherit inputs;
+ inherit (prev) lib;
+ };
+
+ gdb-frontend = prev.callPackage ./gdb-frontend {};
+
+ regreet = prev.callPackage ./regreet {};
+
+ howdy = prev.callPackage ./howdy {};
+
+ linux-enable-ir-emitter = prev.callPackage ./linux-enable-ir-emitter {};
+
+ waveform = prev.callPackage ./waveform {};
+
+ spotify = prev.callPackage ./spotify {};
+
+ sway-hidpi = import ./sway-hidpi.nix prev;
+}
diff --git a/foreign/dotfiles/pkgs/patches/xwayland-hidpi.patch b/foreign/dotfiles/pkgs/patches/xwayland-hidpi.patch
new file mode 100644
index 0000000..85f56e0
--- /dev/null
+++ b/foreign/dotfiles/pkgs/patches/xwayland-hidpi.patch
@@ -0,0 +1,498 @@
+diff --git a/hw/xwayland/xwayland-cursor.c b/hw/xwayland/xwayland-cursor.c
+index c4457cc2a61b2103b47f996b51dbbe9eb87bd715..4a33e1f33e73c35c1691564ef4852e7501b02245 100644
+--- a/hw/xwayland/xwayland-cursor.c
++++ b/hw/xwayland/xwayland-cursor.c
+@@ -171,6 +171,8 @@ xwl_cursor_attach_pixmap(struct xwl_seat *xwl_seat,
+ }
+
+ wl_surface_attach(xwl_cursor->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_cursor->surface,
++ xwl_seat->xwl_screen->global_output_scale);
+ xwl_surface_damage(xwl_seat->xwl_screen, xwl_cursor->surface, 0, 0,
+ xwl_seat->x_cursor->bits->width,
+ xwl_seat->x_cursor->bits->height);
+@@ -190,6 +192,7 @@ xwl_cursor_attach_pixmap(struct xwl_seat *xwl_seat,
+ void
+ xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
+ {
++ struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
+ struct xwl_cursor *xwl_cursor = &xwl_seat->cursor;
+ PixmapPtr pixmap;
+ CursorPtr cursor;
+@@ -220,8 +223,8 @@ xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
+ wl_pointer_set_cursor(xwl_seat->wl_pointer,
+ xwl_seat->pointer_enter_serial,
+ xwl_cursor->surface,
+- xwl_seat->x_cursor->bits->xhot,
+- xwl_seat->x_cursor->bits->yhot);
++ xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
++ xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
+
+ xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
+ }
+@@ -230,6 +233,7 @@ void
+ xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
+ {
+ struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
++ struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
+ struct xwl_cursor *xwl_cursor = &xwl_tablet_tool->cursor;
+ PixmapPtr pixmap;
+ CursorPtr cursor;
+@@ -258,9 +262,9 @@ xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
+ zwp_tablet_tool_v2_set_cursor(xwl_tablet_tool->tool,
+ xwl_tablet_tool->proximity_in_serial,
+ xwl_cursor->surface,
+- xwl_seat->x_cursor->bits->xhot,
+- xwl_seat->x_cursor->bits->yhot);
+-
++ xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
++ xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
++ wl_surface_set_buffer_scale(xwl_cursor->surface, xwl_screen->global_output_scale);
+ xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
+ }
+
+diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
+index 26b3630c73b62514fe3ba7824371f79868e953f3..55cd8d466a55db03948abe93ffa03bf129b5e17a 100644
+--- a/hw/xwayland/xwayland-input.c
++++ b/hw/xwayland/xwayland-input.c
+@@ -412,8 +412,8 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
+ DeviceIntPtr dev = get_pointer_device(xwl_seat);
+ DeviceIntPtr master;
+ int i;
+- int sx = wl_fixed_to_int(sx_w);
+- int sy = wl_fixed_to_int(sy_w);
++ int sx = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
++ int sy = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
+ int dx, dy;
+ ScreenPtr pScreen = xwl_seat->xwl_screen->screen;
+ ValuatorMask mask;
+@@ -592,13 +592,14 @@ pointer_handle_motion(void *data, struct wl_pointer *pointer,
+ uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
+ {
+ struct xwl_seat *xwl_seat = data;
++ int32_t scale = xwl_seat->xwl_screen->global_output_scale;
+
+ if (!xwl_seat->focus_window)
+ return;
+
+ xwl_seat->pending_pointer_event.has_absolute = TRUE;
+- xwl_seat->pending_pointer_event.x = sx_w;
+- xwl_seat->pending_pointer_event.y = sy_w;
++ xwl_seat->pending_pointer_event.x = sx_w * scale;
++ xwl_seat->pending_pointer_event.y = sy_w * scale;
+
+ if (wl_proxy_get_version((struct wl_proxy *) xwl_seat->wl_pointer) < 5)
+ dispatch_pointer_motion_event(xwl_seat);
+@@ -672,7 +673,8 @@ pointer_handle_axis(void *data, struct wl_pointer *pointer,
+ xorg_list_del(&pending->l);
+ free(pending);
+ } else {
+- valuator_mask_set_double(&mask, index, wl_fixed_to_double(value) / divisor);
++ double scaled_value = wl_fixed_to_double(value);
++ valuator_mask_set_double(&mask, index, scaled_value / divisor);
+ }
+
+ QueuePointerEvents(get_pointer_device(xwl_seat),
+@@ -740,12 +742,13 @@ relative_pointer_handle_relative_motion(void *data,
+ wl_fixed_t dy_unaccelf)
+ {
+ struct xwl_seat *xwl_seat = data;
++ int32_t scale = xwl_seat->xwl_screen->global_output_scale;
+
+ xwl_seat->pending_pointer_event.has_relative = TRUE;
+- xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf);
+- xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf);
+- xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf);
+- xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf);
++ xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf) * scale;
++ xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf) * scale;
++ xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf) * scale;
++ xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf) * scale;
+
+ if (!xwl_seat->focus_window)
+ return;
+@@ -1057,8 +1060,8 @@ touch_handle_down(void *data, struct wl_touch *wl_touch,
+
+ xwl_touch->window = wl_surface_get_user_data(surface);
+ xwl_touch->id = id;
+- xwl_touch->x = wl_fixed_to_int(sx_w);
+- xwl_touch->y = wl_fixed_to_int(sy_w);
++ xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
++ xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
+ xorg_list_add(&xwl_touch->link_touch, &xwl_seat->touches);
+
+ xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchBegin);
+@@ -1094,8 +1097,8 @@ touch_handle_motion(void *data, struct wl_touch *wl_touch,
+ if (!xwl_touch)
+ return;
+
+- xwl_touch->x = wl_fixed_to_int(sx_w);
+- xwl_touch->y = wl_fixed_to_int(sy_w);
++ xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;;
++ xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;;
+ xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchUpdate);
+ }
+
+@@ -1726,8 +1729,8 @@ tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool,
+ struct xwl_tablet_tool *xwl_tablet_tool = data;
+ struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
+ int32_t dx, dy;
+- double sx = wl_fixed_to_double(x);
+- double sy = wl_fixed_to_double(y);
++ double sx = wl_fixed_to_double(x) * xwl_seat->xwl_screen->global_output_scale;
++ double sy = wl_fixed_to_double(y) * xwl_seat->xwl_screen->global_output_scale;
+
+ if (!xwl_seat->tablet_focus_window)
+ return;
+@@ -2714,6 +2717,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
+ int x,
+ int y)
+ {
++ struct xwl_screen *xwl_screen;
+ struct zwp_locked_pointer_v1 *locked_pointer =
+ warp_emulator->locked_pointer;
+ WindowPtr window;
+@@ -2725,6 +2729,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
+ if (!warp_emulator->xwl_seat->focus_window)
+ return;
+
++ xwl_screen = warp_emulator->xwl_seat->xwl_screen;
+ window = warp_emulator->xwl_seat->focus_window->window;
+ if (x >= window->drawable.x ||
+ y >= window->drawable.y ||
+@@ -2733,8 +2738,8 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
+ sx = x - window->drawable.x;
+ sy = y - window->drawable.y;
+ zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer,
+- wl_fixed_from_int(sx),
+- wl_fixed_from_int(sy));
++ wl_fixed_from_int(xwl_scale_to(xwl_screen, sx)),
++ wl_fixed_from_int(xwl_scale_to(xwl_screen, sy)));
+ wl_surface_commit(warp_emulator->xwl_seat->focus_window->surface);
+ }
+ }
+diff --git a/hw/xwayland/xwayland-output.c b/hw/xwayland/xwayland-output.c
+index ef705bc01bf8c2d2f170cda9ba21ed8293f50559..b8f6cd51bd240ed5e16271eb4749db18868bea7b 100644
+--- a/hw/xwayland/xwayland-output.c
++++ b/hw/xwayland/xwayland-output.c
+@@ -191,6 +191,9 @@ update_screen_size(struct xwl_output *xwl_output, int width, int height)
+ {
+ struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
+
++ width *= xwl_screen->global_output_scale;
++ height *= xwl_screen->global_output_scale;
++
+ if (xwl_screen->root_clip_mode == ROOT_CLIP_FULL)
+ SetRootClip(xwl_screen->screen, ROOT_CLIP_NONE);
+
+@@ -497,14 +500,15 @@ xwl_output_set_emulated_mode(struct xwl_output *xwl_output, ClientPtr client,
+ xwl_output_set_randr_emu_props(xwl_output->xwl_screen, client);
+ }
+
+-static void
+-apply_output_change(struct xwl_output *xwl_output)
++void
++xwl_output_apply_changes(struct xwl_output *xwl_output)
+ {
+ struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
+ struct xwl_output *it;
+ int mode_width, mode_height, count;
+ int width = 0, height = 0, has_this_output = 0;
+ RRModePtr *randr_modes;
++ int32_t scale = xwl_screen->global_output_scale;
+
+ /* Clear out the "done" received flags */
+ xwl_output->wl_output_done = FALSE;
+@@ -523,10 +527,10 @@ apply_output_change(struct xwl_output *xwl_output)
+ }
+
+ /* Build a fresh modes array using the current refresh rate */
+- randr_modes = output_get_rr_modes(xwl_output, mode_width, mode_height, &count);
++ randr_modes = output_get_rr_modes(xwl_output, mode_width * scale, mode_height * scale, &count);
+ RROutputSetModes(xwl_output->randr_output, randr_modes, count, 1);
+ RRCrtcNotify(xwl_output->randr_crtc, randr_modes[0],
+- xwl_output->x, xwl_output->y,
++ xwl_output->x * scale, xwl_output->y * scale,
+ xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
+ /* RROutputSetModes takes ownership of the passed in modes, so we only
+ * have to free the pointer array.
+@@ -567,7 +571,7 @@ output_handle_done(void *data, struct wl_output *wl_output)
+ */
+ if (xwl_output->xdg_output_done || !xwl_output->xdg_output ||
+ zxdg_output_v1_get_version(xwl_output->xdg_output) >= 3)
+- apply_output_change(xwl_output);
++ xwl_output_apply_changes(xwl_output);
+ }
+
+ static void
+@@ -610,7 +614,7 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
+ xwl_output->xdg_output_done = TRUE;
+ if (xwl_output->wl_output_done &&
+ zxdg_output_v1_get_version(xdg_output) < 3)
+- apply_output_change(xwl_output);
++ xwl_output_apply_changes(xwl_output);
+ }
+
+ static void
+@@ -678,6 +682,8 @@ xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id)
+ RROutputSetConnection(xwl_output->randr_output, RR_Connected);
+ RRTellChanged(xwl_screen->screen);
+
++ xwl_output->scale = 1;
++
+ /* We want the output to be in the list as soon as created so we can
+ * use it when binding to the xdg-output protocol...
+ */
+diff --git a/hw/xwayland/xwayland-output.h b/hw/xwayland/xwayland-output.h
+index 02b9831083e82a33d85d4404e39d00f06f6c56fd..ec089757f44178dcd7f9c48907f790ce1b2a2729 100644
+--- a/hw/xwayland/xwayland-output.h
++++ b/hw/xwayland/xwayland-output.h
+@@ -53,7 +53,7 @@ struct xwl_output {
+ struct wl_output *output;
+ struct zxdg_output_v1 *xdg_output;
+ uint32_t server_output_id;
+- int32_t x, y, width, height, refresh;
++ int32_t x, y, width, height, refresh, scale;
+ Rotation rotation;
+ Bool wl_output_done;
+ Bool xdg_output_done;
+@@ -100,6 +100,8 @@ void xwl_output_set_emulated_mode(struct xwl_output *xwl_output,
+ void xwl_output_set_window_randr_emu_props(struct xwl_screen *xwl_screen,
+ WindowPtr window);
+
++void xwl_output_apply_changes(struct xwl_output *xwl_output);
++
+ void xwl_screen_init_xdg_output(struct xwl_screen *xwl_screen);
+
+ #endif /* XWAYLAND_OUTPUT_H */
+diff --git a/hw/xwayland/xwayland-present.c b/hw/xwayland/xwayland-present.c
+index c9cf8c2f569a319034e0789e7587414e50237065..5be0c208ca46b1a53a136885fdc8ab44251fe7ff 100644
+--- a/hw/xwayland/xwayland-present.c
++++ b/hw/xwayland/xwayland-present.c
+@@ -680,6 +680,8 @@ xwl_present_flip(WindowPtr present_window,
+
+ /* We can flip directly to the main surface (full screen window without clips) */
+ wl_surface_attach(xwl_window->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_window->surface,
++ xwl_window->xwl_screen->global_output_scale);
+
+ if (!xwl_window->frame_callback)
+ xwl_window_create_frame_callback(xwl_window);
+diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
+index bb18e5c94fbc7134c801e4e1979e8184079d352e..4ec2de7d123dd36315df07a1e95b1f417925f0f8 100644
+--- a/hw/xwayland/xwayland-screen.c
++++ b/hw/xwayland/xwayland-screen.c
+@@ -51,6 +51,7 @@
+ #include "xwayland-pixmap.h"
+ #include "xwayland-present.h"
+ #include "xwayland-shm.h"
++#include "xwayland-window-buffers.h"
+
+ #ifdef MITSHM
+ #include "shmint.h"
+@@ -110,6 +111,12 @@ xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen)
+ return xwl_screen->rootless && xwl_screen_has_viewport_support(xwl_screen);
+ }
+
++int
++xwl_scale_to(struct xwl_screen *xwl_screen, int value)
++{
++ return value / (double)xwl_screen->global_output_scale + 0.5;
++}
++
+ /* Return the output @ 0x0, falling back to the first output in the list */
+ struct xwl_output *
+ xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
+@@ -127,6 +134,37 @@ xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
+ return xorg_list_first_entry(&xwl_screen->output_list, struct xwl_output, link);
+ }
+
++static void
++xwl_screen_set_global_scale_from_property(struct xwl_screen *screen,
++ PropertyPtr prop)
++{
++ CARD32 *propdata;
++
++ if (prop->type != XA_CARDINAL || prop->format != 32 || prop->size != 1) {
++ // TODO: handle warnings more cleanly.
++ LogMessageVerb(X_WARNING, 0, "Bad value for property %s.\n",
++ NameForAtom(prop->propertyName));
++ return;
++ }
++
++ propdata = prop->data;
++ xwl_screen_set_global_scale(screen, propdata[0]);
++}
++
++static void
++xwl_screen_update_property(struct xwl_screen *screen,
++ PropertyStateRec *propstate)
++{
++ switch (propstate->state) {
++ case PropertyNewValue:
++ xwl_screen_set_global_scale_from_property(screen, propstate->prop);
++ break;
++ case PropertyDelete:
++ xwl_screen_set_global_scale(screen, 1);
++ break;
++ }
++}
++
+ static void
+ xwl_property_callback(CallbackListPtr *pcbl, void *closure,
+ void *calldata)
+@@ -134,19 +172,24 @@ xwl_property_callback(CallbackListPtr *pcbl, void *closure,
+ ScreenPtr screen = closure;
+ PropertyStateRec *rec = calldata;
+ struct xwl_screen *xwl_screen;
+- struct xwl_window *xwl_window;
+
+ if (rec->win->drawable.pScreen != screen)
+ return;
+
+- xwl_window = xwl_window_get(rec->win);
+- if (!xwl_window)
+- return;
+-
+ xwl_screen = xwl_screen_get(screen);
+
+- if (rec->prop->propertyName == xwl_screen->allow_commits_prop)
++ if (rec->prop->propertyName == xwl_screen->allow_commits_prop) {
++ struct xwl_window *xwl_window;
++
++ xwl_window = xwl_window_get(rec->win);
++ if (!xwl_window)
++ return;
++
+ xwl_window_update_property(xwl_window, rec);
++ }
++ else if (rec->prop->propertyName == xwl_screen->global_output_scale_prop) {
++ xwl_screen_update_property(xwl_screen, rec);
++ }
+ }
+
+ Bool
+@@ -521,8 +564,14 @@ void xwl_surface_damage(struct xwl_screen *xwl_screen,
+ {
+ if (wl_surface_get_version(surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)
+ wl_surface_damage_buffer(surface, x, y, width, height);
+- else
++ else {
++ x = xwl_scale_to(xwl_screen, x);
++ y = xwl_scale_to(xwl_screen, y);
++ width = xwl_scale_to(xwl_screen, width);
++ height = xwl_scale_to(xwl_screen, height);
++
+ wl_surface_damage(surface, x, y, width, height);
++ }
+ }
+
+ void
+@@ -538,10 +587,34 @@ xwl_screen_roundtrip(struct xwl_screen *xwl_screen)
+ xwl_give_up("could not connect to wayland server\n");
+ }
+
++void
++xwl_screen_set_global_scale(struct xwl_screen *xwl_screen, int32_t scale)
++{
++ struct xwl_output *it;
++ struct xwl_window *xwl_window;
++
++ xwl_screen->global_output_scale = scale;
++
++ /* change randr resolutions and positions */
++ xorg_list_for_each_entry(it, &xwl_screen->output_list, link) {
++ xwl_output_apply_changes(it);
++ }
++
++ if (!xwl_screen->rootless && xwl_screen->screen->root) {
++ /* Clear all the buffers, so that they'll be remade with the new sizes
++ * (this doesn't occur automatically because as far as Xorg is
++ * concerned, the window's size is the same) */
++ xorg_list_for_each_entry(xwl_window, &xwl_screen->window_list, link_window) {
++ xwl_window_buffers_recycle(xwl_window);
++ }
++ }
++}
++
+ Bool
+ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ {
+ static const char allow_commits[] = "_XWAYLAND_ALLOW_COMMITS";
++ static const char global_output_scale[] = "_XWAYLAND_GLOBAL_OUTPUT_SCALE";
+ struct xwl_screen *xwl_screen;
+ Pixel red_mask, blue_mask, green_mask;
+ int ret, bpc, green_bpc, i;
+@@ -573,6 +646,7 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ #ifdef XWL_HAS_GLAMOR
+ xwl_screen->glamor = 1;
+ #endif
++ xwl_screen->global_output_scale = 1;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-rootless") == 0) {
+@@ -743,6 +817,12 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ if (xwl_screen->allow_commits_prop == BAD_RESOURCE)
+ return FALSE;
+
++ xwl_screen->global_output_scale_prop = MakeAtom(global_output_scale,
++ strlen(global_output_scale),
++ TRUE);
++ if (xwl_screen->global_output_scale_prop == BAD_RESOURCE)
++ return FALSE;
++
+ AddCallback(&PropertyStateCallback, xwl_property_callback, pScreen);
+
+ xwl_screen_roundtrip(xwl_screen);
+diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
+index b965dddd7f964b1d100bbb9d10da1c35ab39810e..7446829d098fbe235e605084a016daff1a8eaea2 100644
+--- a/hw/xwayland/xwayland-screen.h
++++ b/hw/xwayland/xwayland-screen.h
+@@ -72,6 +72,8 @@ struct xwl_screen {
+ struct xorg_list damage_window_list;
+ struct xorg_list window_list;
+
++ int32_t global_output_scale;
++
+ int wayland_fd;
+ struct wl_display *display;
+ struct wl_registry *registry;
+@@ -107,6 +109,7 @@ struct xwl_screen {
+ struct glamor_context *glamor_ctx;
+
+ Atom allow_commits_prop;
++ Atom global_output_scale_prop;
+
+ /* The preferred GLVND vendor. If NULL, "mesa" is assumed. */
+ const char *glvnd_vendor;
+@@ -134,5 +137,7 @@ void xwl_screen_roundtrip (struct xwl_screen *xwl_screen);
+ void xwl_surface_damage(struct xwl_screen *xwl_screen,
+ struct wl_surface *surface,
+ int32_t x, int32_t y, int32_t width, int32_t height);
++int xwl_scale_to(struct xwl_screen *xwl_screen, int value);
++void xwl_screen_set_global_scale(struct xwl_screen *xwl_screen, int32_t scale);
+
+ #endif /* XWAYLAND_SCREEN_H */
+diff --git a/hw/xwayland/xwayland-window.c b/hw/xwayland/xwayland-window.c
+index 00f161eda084e335ac07471a2198176d75d9fcf0..ed3903853f0dab1dad390cd8429639541546157d 100644
+--- a/hw/xwayland/xwayland-window.c
++++ b/hw/xwayland/xwayland-window.c
+@@ -470,7 +470,8 @@ ensure_surface_for_window(WindowPtr window)
+ }
+
+ wl_region_add(region, 0, 0,
+- window->drawable.width, window->drawable.height);
++ xwl_scale_to(xwl_screen, window->drawable.width),
++ xwl_scale_to(xwl_screen, window->drawable.height));
+ wl_surface_set_opaque_region(xwl_window->surface, region);
+ wl_region_destroy(region);
+ }
+@@ -820,6 +821,7 @@ xwl_window_post_damage(struct xwl_window *xwl_window)
+ #endif
+
+ wl_surface_attach(xwl_window->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_window->surface, xwl_screen->global_output_scale);
+
+ /* Arbitrary limit to try to avoid flooding the Wayland
+ * connection. If we flood it too much anyway, this could
+
diff --git a/foreign/dotfiles/pkgs/patches/xwayland-vsync.patch b/foreign/dotfiles/pkgs/patches/xwayland-vsync.patch
new file mode 100644
index 0000000..375db88
--- /dev/null
+++ b/foreign/dotfiles/pkgs/patches/xwayland-vsync.patch
@@ -0,0 +1,12 @@
+--- a/hw/xwayland/xwayland-present.c
++++ b/hw/xwayland/xwayland-present.c
+@@ -824,7 +824,8 @@
+ dixDestroyPixmap(vblank->pixmap, vblank->pixmap->drawable.id);
+ vblank->pixmap = NULL;
+
+- if (xwl_present_queue_vblank(screen, window, vblank->crtc,
++ if (vblank->target_msc > crtc_msc &&
++ xwl_present_queue_vblank(screen, window, vblank->crtc,
+ vblank->event_id, crtc_msc + 1)
+ == Success)
+ return;
diff --git a/foreign/dotfiles/pkgs/regreet/default.nix b/foreign/dotfiles/pkgs/regreet/default.nix
new file mode 100644
index 0000000..e64de02
--- /dev/null
+++ b/foreign/dotfiles/pkgs/regreet/default.nix
@@ -0,0 +1,35 @@
+{
+ rustPlatform,
+ lib,
+ fetchFromGitHub,
+ pkg-config,
+ glib,
+ gtk4,
+ pango,
+}:
+rustPlatform.buildRustPackage {
+ pname = "regreet";
+ version = "unstable-2023-03-12";
+
+ src = fetchFromGitHub {
+ owner = "rharish101";
+ repo = "ReGreet";
+ rev = "afd124b369bd36be0985f246a59df75f27b1f171";
+ sha256 = "sha256-Mlyw+OI6f7YeykReEDDAKpdGHaIpl3HzUTuZjgiFzAk=";
+ };
+
+ cargoHash = "sha256-koz6bSmBvoW0R3sRgJU2Hizr2CVsGFDyqoPov/F0VL8=";
+
+ buildFeatures = ["gtk4_8"];
+
+ nativeBuildInputs = [pkg-config];
+ buildInputs = [glib gtk4 pango];
+
+ meta = with lib; {
+ description = "Clean and customizable greeter for greetd";
+ homepage = "https://github.com/rharish101/ReGreet";
+ license = licenses.gpl3Plus;
+ maintainers = with maintainers; [fufexan];
+ platforms = platforms.linux;
+ };
+}
diff --git a/foreign/dotfiles/pkgs/repl/default.nix b/foreign/dotfiles/pkgs/repl/default.nix
new file mode 100644
index 0000000..dd4d6ee
--- /dev/null
+++ b/foreign/dotfiles/pkgs/repl/default.nix
@@ -0,0 +1,25 @@
+# modified from https://github.com/gytis-ivaskevicius/flake-utils/plus
+{
+ coreutils,
+ gnused,
+ writeShellScriptBin,
+}: let
+ repl = ../../lib/repl.nix;
+ example = command: desc: ''\n\u001b[33m ${command}\u001b[0m - ${desc}'';
+in
+ writeShellScriptBin "repl" ''
+ case "$1" in
+ "-h"|"--help"|"help")
+ printf "%b\n\e[4mUsage\e[0m: \
+ ${example "repl" "Loads system flake if available."} \
+ ${example "repl /path/to/flake.nix" "Loads specified flake."}\n"
+ ;;
+ *)
+ if [ -z "$1" ]; then
+ nix repl ${repl}
+ else
+ nix repl --arg flakePath $(${coreutils}/bin/readlink -f $1 | ${gnused}/bin/sed 's|/flake.nix||') ${repl}
+ fi
+ ;;
+ esac
+ ''
diff --git a/foreign/dotfiles/pkgs/spotify/default.nix b/foreign/dotfiles/pkgs/spotify/default.nix
new file mode 100644
index 0000000..b975e02
--- /dev/null
+++ b/foreign/dotfiles/pkgs/spotify/default.nix
@@ -0,0 +1,209 @@
+{
+ fetchurl,
+ lib,
+ stdenv,
+ squashfsTools,
+ xorg,
+ alsa-lib,
+ makeWrapper,
+ wrapGAppsHook,
+ openssl,
+ freetype,
+ glib,
+ pango,
+ cairo,
+ atk,
+ gdk-pixbuf,
+ gtk3,
+ cups,
+ nspr,
+ nss,
+ libpng,
+ libnotify,
+ libgcrypt,
+ systemd,
+ fontconfig,
+ dbus,
+ expat,
+ ffmpeg,
+ curlWithGnuTls,
+ zlib,
+ gnome,
+ at-spi2-atk,
+ at-spi2-core,
+ libpulseaudio,
+ libdrm,
+ mesa,
+ libxkbcommon,
+ # High-DPI support: Spotify's --force-device-scale-factor argument
+ # not added if `null`, otherwise, should be a number.
+ deviceScaleFactor ? null,
+}: let
+ # TO UPDATE: just execute the ./update.sh script (won't do anything if there is no update)
+ # "rev" decides what is actually being downloaded
+ # If an update breaks things, one of those might have valuable info:
+ # https://aur.archlinux.org/packages/spotify/
+ # https://community.spotify.com/t5/Desktop-Linux
+ version = "1.1.99.878.g1e4ccc6e";
+ # To get the latest stable revision:
+ # curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/spotify?channel=stable' | jq '.download_url,.version,.last_updated'
+ # To get general information:
+ # curl -H 'Snap-Device-Series: 16' 'https://api.snapcraft.io/v2/snaps/info/spotify' | jq '.'
+ # More examples of api usage:
+ # https://github.com/canonical-websites/snapcraft.io/blob/master/webapp/publisher/snaps/views.py
+ rev = "62";
+
+ deps = [
+ alsa-lib
+ at-spi2-atk
+ at-spi2-core
+ atk
+ cairo
+ cups
+ curlWithGnuTls
+ dbus
+ expat
+ ffmpeg
+ fontconfig
+ freetype
+ gdk-pixbuf
+ glib
+ gtk3
+ libdrm
+ libgcrypt
+ libnotify
+ libpng
+ libpulseaudio
+ libxkbcommon
+ mesa
+ nss
+ pango
+ stdenv.cc.cc
+ systemd
+ xorg.libICE
+ xorg.libSM
+ xorg.libX11
+ xorg.libxcb
+ xorg.libXcomposite
+ xorg.libXcursor
+ xorg.libXdamage
+ xorg.libXext
+ xorg.libXfixes
+ xorg.libXi
+ xorg.libXrandr
+ xorg.libXrender
+ xorg.libXScrnSaver
+ xorg.libxshmfence
+ xorg.libXtst
+ zlib
+ ];
+in
+ stdenv.mkDerivation {
+ pname = "spotify";
+ inherit version;
+
+ # fetch from snapcraft instead of the debian repository most repos fetch from.
+ # That is a bit more cumbersome. But the debian repository only keeps the last
+ # two versions, while snapcraft should provide versions indefinately:
+ # https://forum.snapcraft.io/t/how-can-a-developer-remove-her-his-app-from-snap-store/512
+
+ # This is the next-best thing, since we're not allowed to re-distribute
+ # spotify ourselves:
+ # https://community.spotify.com/t5/Desktop-Linux/Redistribute-Spotify-on-Linux-Distributions/td-p/1695334
+ src = fetchurl {
+ url = "https://api.snapcraft.io/api/v1/snaps/download/pOBIoZ2LrCB3rDohMxoYGnbN14EHOgD7_${rev}.snap";
+ sha512 = "9b8f63c067632f15f5abf6449d09d28fd56839decfc9801d4526cf5b97b9cb5933ede179bcd0b0f2d8b6d4c7e72f55c7fc13dfb0ae4293ef447ead1d018b9cc6";
+ };
+
+ nativeBuildInputs = [makeWrapper wrapGAppsHook squashfsTools];
+
+ dontStrip = true;
+ dontPatchELF = true;
+
+ unpackPhase = ''
+ runHook preUnpack
+ unsquashfs "$src" '/usr/share/spotify' '/usr/bin/spotify' '/meta/snap.yaml'
+ cd squashfs-root
+ if ! grep -q 'grade: stable' meta/snap.yaml; then
+ # Unfortunately this check is not reliable: At the moment (2018-07-26) the
+ # latest version in the "edge" channel is also marked as stable.
+ echo "The snap package is marked as unstable:"
+ grep 'grade: ' meta/snap.yaml
+ echo "You probably chose the wrong revision."
+ exit 1
+ fi
+ if ! grep -q '${version}' meta/snap.yaml; then
+ echo "Package version differs from version found in snap metadata:"
+ grep 'version: ' meta/snap.yaml
+ echo "While the nix package specifies: ${version}."
+ echo "You probably chose the wrong revision or forgot to update the nix version."
+ exit 1
+ fi
+ runHook postUnpack
+ '';
+
+ # Prevent double wrapping
+ dontWrapGApps = true;
+
+ installPhase = ''
+ runHook preInstall
+
+ libdir=$out/lib/spotify
+ mkdir -p $libdir
+ mv ./usr/* $out/
+
+ cp meta/snap.yaml $out
+
+ # Work around Spotify referring to a specific minor version of
+ # OpenSSL.
+
+ ln -s ${lib.getLib openssl}/lib/libssl.so $libdir/libssl.so.1.0.0
+ ln -s ${lib.getLib openssl}/lib/libcrypto.so $libdir/libcrypto.so.1.0.0
+ ln -s ${nspr.out}/lib/libnspr4.so $libdir/libnspr4.so
+ ln -s ${nspr.out}/lib/libplc4.so $libdir/libplc4.so
+
+ ln -s ${ffmpeg.lib}/lib/libavcodec.so* $libdir
+ ln -s ${ffmpeg.lib}/lib/libavformat.so* $libdir
+
+ rpath="$out/share/spotify:$libdir"
+
+ patchelf \
+ --interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
+ --set-rpath $rpath $out/share/spotify/spotify
+
+ librarypath="${lib.makeLibraryPath deps}:$libdir"
+ wrapProgram $out/share/spotify/spotify \
+ ''${gappsWrapperArgs[@]} \
+ ${lib.optionalString (deviceScaleFactor != null) ''
+ --add-flags "--force-device-scale-factor=${toString deviceScaleFactor}" \
+ ''} \
+ --prefix LD_LIBRARY_PATH : "$librarypath" \
+ --prefix PATH : "${gnome.zenity}/bin"
+
+ # fix Icon line in the desktop file (#48062)
+ sed -i "s:^Icon=.*:Icon=spotify-client:" "$out/share/spotify/spotify.desktop"
+
+ # Desktop file
+ mkdir -p "$out/share/applications/"
+ cp "$out/share/spotify/spotify.desktop" "$out/share/applications/"
+
+ # Icons
+ for i in 16 22 24 32 48 64 128 256 512; do
+ ixi="$i"x"$i"
+ mkdir -p "$out/share/icons/hicolor/$ixi/apps"
+ ln -s "$out/share/spotify/icons/spotify-linux-$i.png" \
+ "$out/share/icons/hicolor/$ixi/apps/spotify-client.png"
+ done
+
+ runHook postInstall
+ '';
+
+ meta = with lib; {
+ homepage = "https://www.spotify.com/";
+ description = "Play music from the Spotify music service";
+ sourceProvenance = with sourceTypes; [binaryNativeCode];
+ license = licenses.unfree;
+ maintainers = with maintainers; [eelco ftrvxmtrx sheenobu mudri timokau ma27];
+ platforms = ["x86_64-linux"];
+ };
+ }
diff --git a/foreign/dotfiles/pkgs/sway-hidpi.nix b/foreign/dotfiles/pkgs/sway-hidpi.nix
new file mode 100644
index 0000000..85022d2
--- /dev/null
+++ b/foreign/dotfiles/pkgs/sway-hidpi.nix
@@ -0,0 +1,51 @@
+prev: let
+ sway-hidpi = prev.sway.override {
+ inherit sway-unwrapped;
+ withGtkWrapper = true;
+ };
+
+ sway-unwrapped =
+ (prev.sway-unwrapped.overrideAttrs (oa: {
+ src = prev.fetchFromGitHub {
+ owner = "swaywm";
+ repo = "sway";
+ rev = "8d8a21c9c321fa41b033caf9b5b62cdd584483c1";
+ sha256 = "sha256-WRvJsSvDv2+qirqkpaV2c7fFOgJAT3sXxPtKLure580=";
+ };
+
+ buildInputs = oa.buildInputs ++ [prev.pcre2 prev.xorg.xcbutilwm];
+ }))
+ .override {wlroots_0_16 = wlroots-sway;};
+
+ wlroots-sway =
+ (prev.wlroots.overrideAttrs (_: {
+ src = prev.fetchFromGitLab {
+ domain = "gitlab.freedesktop.org";
+ owner = "wlroots";
+ repo = "wlroots";
+ rev = "add44b3e2e4ff7ef98b16813fb3c9e1d8b398008";
+ sha256 = "sha256-/fJGHeDuZ9aLjCSxILqNSm2aMrvlLZMZpx/WzX5A/XU=";
+ };
+
+ patches = [
+ (prev.fetchpatch {
+ url = "https://gitlab.freedesktop.org/lilydjwg/wlroots/-/commit/6c5ffcd1fee9e44780a6a8792f74ecfbe24a1ca7.diff";
+ sha256 = "sha256-Eo1pTa/PIiJsRZwIUnHGTIFFIedzODVf0ZeuXb0a3TQ=";
+ })
+ (prev.fetchpatch {
+ url = "https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/18595000f3a21502fd60bf213122859cc348f9af.diff";
+ sha256 = "sha256-jvfkAMh3gzkfuoRhB4E9T5X1Hu62wgUjj4tZkJm0mrI=";
+ revert = true;
+ })
+ ];
+ }))
+ .override {inherit xwayland;};
+
+ xwayland = prev.xwayland.overrideAttrs (_: {
+ patches = [
+ ./patches/xwayland-vsync.patch
+ ./patches/xwayland-hidpi.patch
+ ];
+ });
+in
+ sway-hidpi
diff --git a/foreign/dotfiles/pkgs/waveform/default.nix b/foreign/dotfiles/pkgs/waveform/default.nix
new file mode 100644
index 0000000..f45c747
--- /dev/null
+++ b/foreign/dotfiles/pkgs/waveform/default.nix
@@ -0,0 +1,126 @@
+{
+ alsa-lib,
+ atk,
+ cairo,
+ cups,
+ curl,
+ dbus,
+ dpkg,
+ expat,
+ fetchurl,
+ fontconfig,
+ freetype,
+ gdk-pixbuf,
+ glib,
+ gnome2,
+ gtk3,
+ gtk4,
+ lib,
+ libX11,
+ libxcb,
+ libXScrnSaver,
+ libXcomposite,
+ libXcursor,
+ libXdamage,
+ libXext,
+ libXfixes,
+ libXi,
+ libXrandr,
+ libXrender,
+ libXtst,
+ libdrm,
+ libnotify,
+ libpulseaudio,
+ libuuid,
+ libxshmfence,
+ mesa,
+ nspr,
+ nss,
+ pango,
+ stdenv,
+ at-spi2-atk,
+ at-spi2-core,
+ autoPatchelfHook,
+ wrapGAppsHook,
+}: let
+ mirror = "https://cdn.tracktion.com/file/tracktiondownload/waveform";
+in
+ stdenv.mkDerivation rec {
+ pname = "waveform";
+ version = "11.5.18";
+ pversion = lib.concatStrings (lib.remove "." (lib.stringToCharacters version));
+
+ src = fetchurl {
+ url = "${mirror}/${pversion}/${pname}_64bit_v${version}.deb";
+ sha256 = "sha256-kRqgiIfgpmkPOwAuSuxhi/H18ezgicRo+wbTWeFaweU=";
+ };
+
+ unpackCmd = "${dpkg}/bin/dpkg-deb -x $curSrc .";
+
+ nativeBuildInputs = [
+ autoPatchelfHook
+ wrapGAppsHook
+ ];
+
+ # not sure how much of this all is needed
+ buildInputs = [
+ alsa-lib
+ at-spi2-atk
+ at-spi2-core
+ atk
+ cairo
+ cups
+ curl
+ dbus
+ expat
+ fontconfig.lib
+ freetype
+ gdk-pixbuf
+ glib
+ gnome2.GConf
+ gtk3
+ libX11
+ libXScrnSaver
+ libXcomposite
+ libXcursor
+ libXdamage
+ libXext
+ libXfixes
+ libXi
+ libXrandr
+ libXrender
+ libXtst
+ libdrm
+ libnotify
+ libuuid
+ libxcb
+ libxshmfence
+ mesa
+ nspr
+ nss
+ pango
+ stdenv.cc.cc.lib
+ ];
+
+ runtimeDependencies = [
+ # without this, it complains about not finding libcurl
+ curl.out
+
+ # not sure if needed
+ libpulseaudio.out
+ gtk3
+ gtk4
+ ];
+
+ installPhase = ''
+ mkdir -p $out
+ cp -r . $out/
+ '';
+
+ meta = with lib; {
+ homepage = "https://www.tracktion.com/welcome/waveform-free";
+ description = "Waveform DAW";
+ platforms = ["x86_64-linux"];
+ license = licenses.unfree;
+ };
+ }
diff --git a/foreign/dotfiles/secrets/secrets.nix b/foreign/dotfiles/secrets/secrets.nix
new file mode 100644
index 0000000..63d0b8a
--- /dev/null
+++ b/foreign/dotfiles/secrets/secrets.nix
@@ -0,0 +1,7 @@
+let
+ mihai = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH81M2NZOzd5tGGRsDv//wkSrVNJJpaiaLghPZBH8VTd";
+
+ io = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMRDsoSresP7/VnrQOYsWWO/5V+EdPEx5PwI0DxW9H00 root@io";
+in {
+ "spotify.age".publicKeys = [mihai io];
+}
diff --git a/foreign/dotfiles/secrets/spotify.age b/foreign/dotfiles/secrets/spotify.age
new file mode 100644
index 0000000..d1809ac
--- /dev/null
+++ b/foreign/dotfiles/secrets/spotify.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 gC7Y0g gNDFkWou+lP6bb/DeykjpLnU58Q02WSNf9MuIbKgtg8
+EFqUCkPKPV169Nq2Fxw77v/hLMMfsBZtiyfHibGCrso
+-> ssh-ed25519 4DWTmg Cb32NOKXPGY2vmdJjjpvDccA+NFqhLU+VN1h86jRlFM
+9s2hXLh51mxkq4vLdZIT9F9og2bPPGvQrC4EGmo0ekw
+-> 3%I0CmT-grease p = g)aHR,b0
+MvuI9zJMrw9+vKpsRGf6x4ltLg
+--- 4kybI/sbTweYmJf5sG5vu6eF3HTZlkCM+gqRgdsk86A
+
$Y̌4&)Xmd>Sf]V$
+>
\ No newline at end of file