Compare commits

...
24 Commits
Author SHA1 Message Date
imxyy1soope1 39b5ec89be splayer: replace by splayer-next 2026-09-05 20:18:55 +08:00
imxyy1soope1 925aac68ef flake.lock: update 2026-09-05 20:18:35 +08:00
imxyy1soope1 786d8f9ee3 noctalia: enable audio 2026-08-30 17:57:59 +08:00
imxyy1soope1 43f6edcf02 starship: increase scan_timeout to 1500ms 2026-08-30 15:40:15 +08:00
imxyy1soope1 83cfd6969c imxyy-nix: disable amdgpu runtime power management 2026-08-30 15:39:29 +08:00
imxyy1soope1 1beb5a3338 noctalia: noctification.layer = "top" 2026-08-30 12:40:23 +08:00
imxyy1soope1 0d6deec01e noctalia: fix sync-noctalia skipping new sections when no other changes 2026-08-30 12:03:44 +08:00
imxyy1soope1 818c9f6af8 noctalia: fold bar/widget GUI overrides into config 2026-08-29 11:30:20 +08:00
imxyy1soope1 8a9384de15 noctalia: add just sync-noctalia to fold GUI overrides 2026-08-29 11:28:58 +08:00
imxyy1soope1 8b141b9da0 fmt: reformat *.toml with tombi, add tombi to treefmt.nix 2026-08-29 09:49:32 +08:00
imxyy1soope1 dd0097a1f8 desktop: disable speechd (enabled by default) 2026-08-29 09:16:40 +08:00
imxyy1soope1 5f8c8b3948 agents: disable pi & oh-my-pi 2026-08-28 17:22:55 +08:00
imxyy1soope1 097b02ecef langs: rust: drop evcxr 2026-08-28 17:14:15 +08:00
imxyy1soope1 c04405c0fb noctalia: update media widget config 2026-08-28 17:02:43 +08:00
imxyy1soope1 e2a8892148 coding: persist ~/.config/gh 2026-08-28 16:23:56 +08:00
imxyy1soope1 724220504f noctalia: clipboard_history_max_entries = 1000 2026-08-28 16:13:20 +08:00
imxyy1soope1 7c37843808 noctalia: migrate to noctalia v5 2026-08-28 14:25:36 +08:00
imxyy1soope1 527812781f jj: persist ~/.config/jj 2026-08-27 12:21:32 +08:00
imxyy1soope1 e16b8ce5ea flake.lock: update 2026-08-27 12:21:25 +08:00
imxyy1soope1 7c67ec4c5e coding: langs: c: add meson and ninja 2026-08-23 16:50:27 +08:00
imxyy1soope1 92b388abb1 selector4nix: listen 0.0.0.0 2026-08-22 22:50:56 +08:00
imxyy1soope1 6e7bf06d97 server: net: use nixpkgs' ddns-go 2026-08-22 22:50:06 +08:00
imxyy1soope1 7cdc66d415 nix: add THU's mirror; add select4nix's binary cache 2026-08-22 11:04:42 +08:00
imxyy1soope1 be47a92dd8 flake.lock: update 2026-08-21 19:00:06 +08:00
42 changed files with 1052 additions and 389 deletions
+8
View File
@@ -49,3 +49,11 @@ alias offline := switch-offline
@fmt:
echo "Formatting files..."
nix fmt
@sync-noctalia:
echo "Folding noctalia GUI overrides back into the repo..."
python3 scripts/sync-noctalia.py --apply
@sync-noctalia-preview:
echo "Previewing noctalia config sync (no changes)..."
python3 scripts/sync-noctalia.py
Generated
+73 -99
View File
@@ -108,26 +108,27 @@
]
},
"locked": {
"lastModified": 1784665499,
"narHash": "sha256-9BMxlTxCCDAeoNLtb1a/st7udtTIJep+wpUzquA29VU=",
"owner": "nix-community",
"lastModified": 1788011267,
"narHash": "sha256-mEq2kU+ljompTToZ44Afvm7d/rHJ5iMBl0tjZuyShJs=",
"owner": "Mic92",
"repo": "bun2nix",
"rev": "0f2a1f0b6f42cebe3b149bf62d38754c5e0e9729",
"rev": "5765b0614591f75ee8ba5596e81ae85c167d1071",
"type": "github"
},
"original": {
"owner": "nix-community",
"owner": "Mic92",
"ref": "fix-structured-attrs-hook",
"repo": "bun2nix",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1784564969,
"narHash": "sha256-TkTWNhJV/8Wk3czlbz4bwHZkFCaCHPsOy+oJe4PUiXg=",
"lastModified": 1787326676,
"narHash": "sha256-lWhBbBvC05/xwivKBBiM2YNizpmgqCgyOIzomvRuwxs=",
"owner": "ipetkov",
"repo": "crane",
"rev": "7930f6c291de6f83c257839d434592aa085f290a",
"rev": "692f7e9ef2ece8125b466f66f2af532b3edaed0d",
"type": "github"
},
"original": {
@@ -144,11 +145,11 @@
]
},
"locked": {
"lastModified": 1780519775,
"narHash": "sha256-u12imjPk4ZhOen/PgnLiNPML+5NmuKO0Ja4wQKU/Y8E=",
"lastModified": 1787592910,
"narHash": "sha256-a2+yPzqXAlnErn1KGW28UBlqapBmySvLPWOskVFB/Hg=",
"owner": "Bali10050",
"repo": "Darkly",
"rev": "11c27e2d98025f4d4c1598f07a185280b36f35f7",
"rev": "65f6fa62675e3c30986f8fc8e4fdbba053096491",
"type": "github"
},
"original": {
@@ -168,11 +169,11 @@
]
},
"locked": {
"lastModified": 1786625301,
"narHash": "sha256-KNAjsTgfweOoqYxMAsIolrDcIV5AikvrSW9EpJ7fnK8=",
"lastModified": 1788439409,
"narHash": "sha256-AvaugOI0Hb8ZG+2goIbISKpYfW6ovdvDm/uf7PT8PO8=",
"owner": "Mic92",
"repo": "fast-nix-gc",
"rev": "254a2ba0a4f1570b3880bc10bb6166afe1e25936",
"rev": "18d82a8ce3e938ff97b5e11c7d2033f0333af5bb",
"type": "github"
},
"original": {
@@ -189,11 +190,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1786693132,
"narHash": "sha256-Mat1FwhwqpX0ziRp8bg1u3Qxxf8KIc2MxocRiw+lm9o=",
"lastModified": 1788603547,
"narHash": "sha256-/ioB+JvyRtHtQOpgZYC95KiD6Gw76ZDdSbkiItR4FG4=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ed4c4c6f0f5c15ff0783b6de488eec027f712585",
"rev": "03864c059200a8a96f2ee6bb050c69eae96f57ca",
"type": "github"
},
"original": {
@@ -240,11 +241,11 @@
]
},
"locked": {
"lastModified": 1785627969,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"lastModified": 1788450739,
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"type": "github"
},
"original": {
@@ -336,11 +337,11 @@
]
},
"locked": {
"lastModified": 1786656209,
"narHash": "sha256-ybGtuwGKTUCefKYsplzvw4xcCqznto0c7BaiQhgILtA=",
"lastModified": 1788487777,
"narHash": "sha256-Ro/e1N4ZR8/XaFF+sF9SgfPK79HM5YNEppzFj8p+0Ak=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "c554d3441f725537854e877519f01cbd60680174",
"rev": "693e8ce0fb240a73c116a03cfd7b19269c87af88",
"type": "github"
},
"original": {
@@ -404,11 +405,11 @@
]
},
"locked": {
"lastModified": 1786704638,
"narHash": "sha256-/fVVDevImdj8PFczYgd9WFm0k8nxESbfWVgmy4pFR/k=",
"lastModified": 1788605532,
"narHash": "sha256-kqEZkn2YPC9mgJn6Wh6L1uuukvYYMbQryzaFHO41GxU=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "901c200318d2b06ce3c31e667fd63e979a22ec42",
"rev": "3a3523b87e7bb7253fac39c95895ec20f241d672",
"type": "github"
},
"original": {
@@ -419,11 +420,11 @@
},
"multiverse": {
"locked": {
"lastModified": 1786656216,
"narHash": "sha256-znAyCzzU/xz/56FaAGKRZGRYhYYQuCuirYa6Z1cQPOo=",
"lastModified": 1788596457,
"narHash": "sha256-6iJ0N8hUClykevMfX8Qwt2ObOBaQF6yK7wf12OoOovk=",
"owner": "fzakaria",
"repo": "nixpkgs-multiverse",
"rev": "ff816217c1255b0e7cdaba04511ed5fa03815177",
"rev": "5caf7ed36b0ddf54878ef30a738fd327c9682ffa",
"type": "github"
},
"original": {
@@ -456,11 +457,11 @@
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
},
"locked": {
"lastModified": 1786665887,
"narHash": "sha256-Ye1UgeYHuUi+xagVwjGCQ1W60wfc8ZIDqhdvTml1Hn0=",
"lastModified": 1788566543,
"narHash": "sha256-7snI9zOQMvM0ghxW9+7Tl/8pvDa4KGmQBCmRzbY9TxI=",
"ref": "refs/heads/main",
"rev": "96f2b99fb3bf6d5788756faf23b73087c67a4460",
"revCount": 168,
"rev": "2bce88af7bbeff97ac132d870e2efd08a294c8f0",
"revCount": 181,
"type": "git",
"url": "https://codeberg.org/bananad3v/niri-nix"
},
@@ -472,11 +473,11 @@
"niri-unstable": {
"flake": false,
"locked": {
"lastModified": 1786626375,
"narHash": "sha256-Yq5bu6KDiG5xttJ9wVsE/dKQ0AvO4RYx2YQmDaYVsXg=",
"lastModified": 1787337984,
"narHash": "sha256-BNZUEVR2H96hCKENNKoLSSTFT8W4smp7v94hJc4Ehfc=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "720c388427c5589cc630f8188d44153d93ff8b0c",
"rev": "dd75865f547f0eac0e9b6c4d86d2cd00c0744252",
"type": "github"
},
"original": {
@@ -519,11 +520,11 @@
]
},
"locked": {
"lastModified": 1786373799,
"narHash": "sha256-/MGTSKLPC6uERKHyilzRCVPDeX/AwG+4B8bf1vMHAJg=",
"lastModified": 1787652563,
"narHash": "sha256-ec73Anjp5QDIid2Z/iNC9jaGRqO9aLtYOJOo95dd5YU=",
"owner": "Mic92",
"repo": "nix-tree-rs",
"rev": "31d54916fa110e9462fec799f1663224baddbadd",
"rev": "9654bd185c7043c60ae999b4d3a9b038e85b111b",
"type": "github"
},
"original": {
@@ -557,11 +558,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1786410043,
"narHash": "sha256-JTbODJPDcd3d8U7lO4MVUU3Yo0jz2KQSAHXka+IWBEo=",
"lastModified": 1788400875,
"narHash": "sha256-JIHQ6xNAN53cdo6zZ72LRcQ9lpvnABCnNE4hldJ5uZU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8e2eeb9477c9d40009a5bd51cd3eef2f5abb26f1",
"rev": "5545adfad2e98de106a5544ca7067e03010410bd",
"type": "github"
},
"original": {
@@ -573,11 +574,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1786599213,
"narHash": "sha256-yNJd40f11EzXBjSByCB7IPpeFFAdeoSKKM67dGkfFoU=",
"lastModified": 1788531059,
"narHash": "sha256-hLD4l3QOGBQhkVp3mQ2lJ/YbEi99qUgKapb40KovZ88=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0e251e24a4f24e036a084b6b4b2d2491af4167f4",
"rev": "801bef6abd86b91e51083066b83fb354a11fc640",
"type": "github"
},
"original": {
@@ -591,47 +592,20 @@
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"noctalia-qs": "noctalia-qs"
},
"locked": {
"lastModified": 1775169040,
"narHash": "sha256-0xoCuJSRSWcn4mCX382lCxqLbnuOrrqS4dOcdpoUmZg=",
"owner": "noctalia-dev",
"repo": "noctalia-shell",
"rev": "8b0c7c288063e5048a439ca308dd65f7044f42b6",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"ref": "v4.7.5",
"repo": "noctalia-shell",
"type": "github"
}
},
"noctalia-qs": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
],
"treefmt-nix": [
"treefmt"
]
},
"locked": {
"lastModified": 1775008123,
"narHash": "sha256-zsd/kbsgfX2YQAlbao40JxyeVDwsUmBGyUDZc/Vo1C4=",
"lastModified": 1787858564,
"narHash": "sha256-WijEuINvjcXMO/e/zMqwG1lyGiWNosnVt1QY+ko0Rw8=",
"owner": "noctalia-dev",
"repo": "noctalia-qs",
"rev": "b87f41771c050d3c101d1528532de53293cba6da",
"repo": "noctalia-shell",
"rev": "74e6c2790dd8f39bf496e90e479a9ae370846eed",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"repo": "noctalia-qs",
"ref": "v5.0.0-beta.10",
"repo": "noctalia-shell",
"type": "github"
}
},
@@ -693,11 +667,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1786661453,
"narHash": "sha256-OylLsJCF9/znjtYAPl6Hm7MJ+j2dVzk3HUGsFmADjEM=",
"lastModified": 1788445421,
"narHash": "sha256-sKEuAgygSpMZFvrj7IcCPm+3al2bQWukulM6pMEpCcc=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "baabc5825f3f6640e99fe32887bbeced640f825e",
"rev": "e8f7e90aa3e7b26aa9a000200f606c1078da99ec",
"type": "github"
},
"original": {
@@ -718,11 +692,11 @@
]
},
"locked": {
"lastModified": 1786639709,
"narHash": "sha256-7cAzmCBAxG4KWGDKAKJF8f8yZryzFZp3hNnXNjemHrk=",
"lastModified": 1788514031,
"narHash": "sha256-3nWq8IMC6GPtEqixSvpYX05nOfFqdnOfmkl0DOiWcQc=",
"owner": "StarryReverie",
"repo": "selector4nix",
"rev": "99d058d235409dcbde0f5c973176254d419f08c9",
"rev": "1ff44ddb817ff943df9667ac1a0a79791b509a20",
"type": "github"
},
"original": {
@@ -738,11 +712,11 @@
]
},
"locked": {
"lastModified": 1786629091,
"narHash": "sha256-gkig4nPi1CWc4Z50GBsjE4ygSE7hMpl/TwID2an2Cck=",
"lastModified": 1788337237,
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "a8627b21b9107c5711c96b84f32a9a4b3d45295f",
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
"type": "github"
},
"original": {
@@ -775,11 +749,11 @@
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1786531493,
"narHash": "sha256-F93yabBSW2TpxKYMcArQOnFuCqBgAdbfZsdl0w5Pojg=",
"lastModified": 1787771653,
"narHash": "sha256-DkkJSBOXWV/mja5Vy8az5g1KpZlsbUwlmH0BsQhowaQ=",
"owner": "danth",
"repo": "stylix",
"rev": "1e6ccadeda179d96728b4a9f20fc9d4dcf6b6059",
"rev": "5e3809851f486e7fc7e84b40f174c74b60ecc784",
"type": "github"
},
"original": {
@@ -894,11 +868,11 @@
]
},
"locked": {
"lastModified": 1785945821,
"narHash": "sha256-NLSyTCW4K4ofhNBllt3omPasm6QpralXH1DBZOc91Dw=",
"lastModified": 1786901030,
"narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "ae7910970dddc408fe6ab1c8e4b277bb21d72dc0",
"rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa",
"type": "github"
},
"original": {
@@ -910,11 +884,11 @@
"xwayland-satellite-unstable": {
"flake": false,
"locked": {
"lastModified": 1786221058,
"narHash": "sha256-hF2atQzapHBLX5NFqJMcKZAWHfGkKQeNu1eQr46+AGg=",
"lastModified": 1788436361,
"narHash": "sha256-i0QYbUlC03SrOomU+37TdZ9EdtEWLaUtsCQjC7+KhNU=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "3bc915f09dd62bb2651a715114f9d140344bc6c1",
"rev": "ea474b95a55ace75ad60a9bc0092a81ff5a1ba89",
"type": "github"
},
"original": {
@@ -933,11 +907,11 @@
]
},
"locked": {
"lastModified": 1786688050,
"narHash": "sha256-qo3nSI7PgaGiVTMkSvU6EMx1A/7dUyCvH4lTEpEan4M=",
"lastModified": 1788405419,
"narHash": "sha256-wuGizAVEmY0u15MYrhgy7AoQdHg+4Y2QiC+v/mxA8dc=",
"owner": "0xc000022070",
"repo": "zen-browser-flake",
"rev": "771b9a9c443d0e95815ebe2ac75e8acff99ac631",
"rev": "fdb83f8fce835213fab7eab52c375926fe1a32dc",
"type": "github"
},
"original": {
+1 -4
View File
@@ -96,11 +96,8 @@
inputs.treefmt-nix.follows = "treefmt";
};
noctalia = {
url = "github:noctalia-dev/noctalia-shell/v4.7.5";
url = "github:noctalia-dev/noctalia-shell/v5.0.0-beta.10";
inputs.nixpkgs.follows = "nixpkgs";
inputs.noctalia-qs.inputs.nixpkgs.follows = "nixpkgs";
inputs.noctalia-qs.inputs.systems.follows = "systems";
inputs.noctalia-qs.inputs.treefmt-nix.follows = "treefmt";
};
selector4nix = {
url = "github:StarryReverie/selector4nix";
-7
View File
@@ -16,13 +16,6 @@
lib.infuse prev {
cage.__output.patches.__append = [ ./cage-specify-output-name.patch ];
bottles.__input.removeWarningPopup.__assign = true;
jetbrains-mono.__assign = lib.warn ''
nanoemoji https://github.com/NixOS/nixpkgs/pull/552075
'' (final.mv.at "2026-08-12").jetbrains-mono;
moonlight-qt.__assign = lib.warn ''
moonlight-qt https://github.com/NixOS/nixpkgs/pull/552544
'' (final.mv.at "2026-08-10").moonlight-qt;
}
)
]
+2 -19
View File
@@ -178,29 +178,12 @@
};
my.services.frp.webServers = [ "oidc.imxyy.top" ];
systemd.services.ddns-go =
let
version = "6.6.7";
ddns-go = pkgs.buildGoModule {
inherit version;
pname = "ddns-go";
src = pkgs.fetchFromGitHub {
owner = "jeessy2";
repo = "ddns-go";
rev = "v${version}";
hash = "sha256-Ejoe6e9GFhHxQ9oIBDgDRQW9Xx1XZK+qSAXiRXLdn+c=";
};
meta.mainProgram = "ddns-go";
vendorHash = "sha256-XZii7gV3DmTunYyGYzt5xXhv/VpTPIoYKbW4LnmlAgs=";
doCheck = false;
};
in
{
systemd.services.ddns-go = {
description = "Go Dynamic DNS";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${lib.getExe ddns-go} -l :9876 -f 10 -cacheTimes 180 -c /var/lib/ddns-go/config.yaml";
ExecStart = "${lib.getExe pkgs.ddns-go} -l :9876 -f 10 -cacheTimes 180 -c /var/lib/ddns-go/config.yaml";
Restart = "always";
RestartSec = 120;
};
+3
View File
@@ -34,6 +34,9 @@ in
resumeDevice = btrfs;
kernelParams = [
"resume_offset=6444127"
# Fix connector hotplug at ly display manager
"amdgpu.runpm=0"
];
zswap.enable = true;
+1 -1
View File
@@ -53,7 +53,7 @@ symbol = " "
symbol = " "
[git_commit]
tag_symbol = ''
tag_symbol = ""
[golang]
symbol = " "
+1 -2
View File
@@ -17,8 +17,7 @@ in
enable = true;
settings = lib.recursiveUpdate (with builtins; fromTOML (readFile ./starship-preset.toml)) {
add_newline = false;
command_timeout = 2000;
nix_shell.disabled = true;
scan_timeout = 1500;
format =
let
dedupDollar =
+3
View File
@@ -17,6 +17,9 @@ in
};
config = lib.mkIf cfg.enable {
my.persist.homeDirs = [
".config/jj"
];
my.hm = {
programs.jujutsu = {
enable = true;
+2 -2
View File
@@ -19,8 +19,8 @@ in
# codex
claude-code
# opencode
pi
omp
# pi
# omp
])
++ (with pkgs; [
codex
@@ -81,6 +81,7 @@ local servers = {
java_language_server = {},
jsonls = {},
pyrefly = {},
ruff = {},
-- taplo = {},
tombi = {},
ts_ls = {},
+2
View File
@@ -18,6 +18,8 @@ in
(lib.hiPrio clang)
clang-tools
cmake
ninja
meson
];
};
}
+7 -1
View File
@@ -14,9 +14,15 @@ in
config = lib.mkIf cfg.enable {
my.hm.home.packages = with pkgs; [
python3
(python3.withPackages (
ps: with ps; [
tomli
tomli-w
]
))
uv
pyrefly
ruff
];
};
}
-1
View File
@@ -24,7 +24,6 @@ in
"rust-analyzer"
])
stdenv.cc # linker
evcxr # rust repl
pest-ide-tools
];
+17
View File
@@ -19,6 +19,22 @@ in
gnumake
github-cli # gh
];
programs.starship.settings = {
nix_shell.disabled = true;
# direnv =
# let
# esc = builtins.fromJSON ''"\u001b"'';
# in
# {
# disabled = false;
# format = "$loaded$allowed ";
# loaded_msg = "${esc}[1;92m ${esc}[0m"; # green
# unloaded_msg = "";
# allowed_msg = "";
# not_allowed_msg = "${esc}[1;93m ${esc}[0m"; # yellow
# denied_msg = "${esc}[1;91m ${esc}[0m"; # red
# };
};
programs.direnv = {
enable = true;
nix-direnv.enable = true;
@@ -48,6 +64,7 @@ in
};
};
my.persist.homeDirs = [
".config/gh"
".local/share/direnv"
];
};
+19 -21
View File
@@ -45,38 +45,36 @@ in
enable = true;
configureSubstituter = "keep";
settings = {
server.port = 5496;
substituters = [
{
url = "https://mirror.sjtu.edu.cn/nix-channels/store";
}
{
url = "https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store";
}
{
url = "https://mirrors.ustc.edu.cn/nix-channels/store";
}
{
url = "https://nix-community.cachix.org";
}
{
url = "https://cache.numtide.com";
}
{
url = "https://cache.nixos.org";
}
server = {
ip = "0.0.0.0";
port = 5496;
};
substituters = map (subst: if builtins.isString subst then { url = subst; } else subst) [
"https://mirror.sjtu.edu.cn/nix-channels/store"
"https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store"
"https://mirrors.ustc.edu.cn/nix-channels/store"
"https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"
"https://nix-community.cachix.org"
"https://selector4nix.cachix.org/"
"https://cache.numtide.com"
"https://cache.nixos.org"
];
};
};
nix.settings = {
experimental-features = "nix-command flakes pipe-operators";
experimental-features = [
"nix-command"
"flakes"
"pipe-operators"
];
substituters = lib.mkForce [
"http://127.0.0.1:${toString (config.services.selector4nix.settings.server.port or 5496)}"
"https://cache.nixos.org"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"selector4nix.cachix.org-1:wovVlT07In5JCVz2tFgxPQTLpnN8hZT6P/RwfFcz3KE="
"niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g="
];
download-buffer-size = 536870912; # 512 MiB
+2 -2
View File
@@ -14,10 +14,10 @@ in
config = lib.mkIf cfg.enable {
my.hm.home.packages = [
pkgs.splayer
pkgs.splayer-next
];
my.persist.homeDirs = [
".config/SPlayer"
".config/SPlayer-Next"
];
};
}
@@ -6,44 +6,44 @@
# Default colors
[colors.primary]
background = '#24283b'
foreground = '#c0caf5'
background = "#24283b"
foreground = "#c0caf5"
[colors.cursor]
cursor = '#c0caf5'
text = '#24283b'
cursor = "#c0caf5"
text = "#24283b"
[colors.selection]
text = '#c0caf5'
background = '#2e3c64'
text = "#c0caf5"
background = "#2e3c64"
# Normal colors
[colors.normal]
black = '#1d202f'
red = '#f7768e'
green = '#9ece6a'
yellow = '#e0af68'
blue = '#7aa2f7'
magenta = '#bb9af7'
cyan = '#7dcfff'
white = '#a9b1d6'
black = "#1d202f"
red = "#f7768e"
green = "#9ece6a"
yellow = "#e0af68"
blue = "#7aa2f7"
magenta = "#bb9af7"
cyan = "#7dcfff"
white = "#a9b1d6"
# Bright colors
[colors.bright]
black = '#414868'
red = '#ff899d'
green = '#9fe044'
yellow = '#faba4a'
blue = '#8db0ff'
magenta = '#c7a9ff'
cyan = '#a4daff'
white = '#c0caf5'
black = "#414868"
red = "#ff899d"
green = "#9fe044"
yellow = "#faba4a"
blue = "#8db0ff"
magenta = "#c7a9ff"
cyan = "#a4daff"
white = "#c0caf5"
# Indexed Colors
[[colors.indexed_colors]]
index = 16
color = '#ff9e64'
color = "#ff9e64"
[[colors.indexed_colors]]
index = 17
color = '#db4b4b'
color = "#db4b4b"
+6 -6
View File
@@ -1,15 +1,15 @@
binds {
Ctrl+Alt+Escape { spawn "missioncenter"; }
Mod+R { spawn "noctalia-shell" "ipc" "call" "launcher" "toggle"; }
Mod+V { spawn "noctalia-shell" "ipc" "call" "launcher" "clipboard"; }
Mod+W { spawn "noctalia-shell" "ipc" "call" "launcher" "windows"; }
Mod+R { spawn "noctalia" "msg" "panel-toggle" "launcher"; }
Mod+V { spawn "noctalia" "msg" "panel-toggle" "clipboard"; }
Mod+W { spawn "noctalia" "msg" "panel-toggle" "launcher" "/win"; }
XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_SINK@" "2%+"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_SINK@" "2%-"; }
XF86AudioMute allow-when-locked=true { spawn "noctalia-shell" "ipc" "call" "media" "playPause"; }
Super+XF86AudioRaiseVolume allow-when-locked=true { spawn "noctalia-shell" "ipc" "call" "media" "next"; }
Super+XF86AudioLowerVolume allow-when-locked=true { spawn "noctalia-shell" "ipc" "call" "media" "previous"; }
XF86AudioMute allow-when-locked=true { spawn "noctalia" "msg" "media" "playPause"; }
Super+XF86AudioRaiseVolume allow-when-locked=true { spawn "noctalia" "msg" "media" "next"; }
Super+XF86AudioLowerVolume allow-when-locked=true { spawn "noctalia" "msg" "media" "previous"; }
XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "+5%"; }
XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "5%-"; }
@@ -46,6 +46,12 @@ layout {
}
}
gestures {
hot-corners {
off
}
}
prefer-no-csd
hotkey-overlay {
+4 -176
View File
@@ -2,7 +2,7 @@
lib,
config,
pkgs,
assets,
impure,
...
}:
let
@@ -69,17 +69,6 @@ in
X-RestartIfChanged=false
'';
};
systemd.user.services.niri-polkit = {
description = "PolicyKit Authentication Agent";
wantedBy = [ "niri.service" ];
after = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1";
Restart = "on-failure";
};
};
services.system76-scheduler.enable = true;
@@ -93,177 +82,16 @@ in
mission-center
noctalia-shell
xdg-terminal-exec
];
services.system76-scheduler-niri.enable = true;
programs.noctalia-shell = {
programs.noctalia = {
enable = true;
# modified from official Tokyo-Night theme
colors = {
mError = "#f7768e";
mHover = "#bb9af7";
mOnError = "#16161e";
mOnHover = "#16161e";
mOnPrimary = "#16161e";
mOnSecondary = "#16161e";
mOnSurface = "#c0caf5";
mOnSurfaceVariant = "#9aa5ce";
mOnTertiary = "#16161e";
mOutline = "#353D57";
mPrimary = "#7aa2f7";
mSecondary = "#9a9ef7";
mShadow = "#15161e";
mSurface = "#1a1b26";
mSurfaceVariant = "#24283b";
mTertiary = "#bb9af7";
};
settings = {
appLauncher = {
enableClipboardHistory = true;
useApp2Unit = true;
terminalCommand = "kitty -e";
};
audio.mprisBlacklist = [
"firefox"
"chromium"
"zen"
];
bar = {
density = "comfortable";
floating = true;
marginHorizontal = 0.5;
marginVertical = 0.5;
showCapsule = false;
widgets = {
left = [
{
id = "ControlCenter";
useDistroLogo = true;
}
{
id = "Clock";
formatHorizontal = "HH:mm MM/dd ddd";
useCustomFont = false;
usePrimaryColor = true;
}
{
id = "SystemMonitor";
showCpuTemp = false;
showCpuUsage = true;
showDiskUsage = false;
showMemoryAsPercent = false;
showMemoryUsage = true;
showNetworkStats = true;
}
{
id = "Workspace";
hideUnoccupied = false;
labelMode = "none";
}
];
center = [
{
id = "MediaMini";
hideMode = "hidden";
scrollingMode = "hover";
showAlbumArt = true;
showVisualizer = true;
visualizerType = "wave";
}
];
right = [
{
id = "NotificationHistory";
hideWhenZero = true;
showUnreadBadge = true;
}
{
id = "Tray";
drawerEnabled = false;
colorizeIcons = false;
}
{
id = "Volume";
displayMode = "onhover";
}
{
id = "Microphone";
displayMode = "onhover";
}
{
id = "Battery";
hideIfNotDetected = true;
}
];
};
};
controlCenter = {
cards = [
{
id = "profile-card";
enabled = true;
}
{
id = "shortcuts-card";
enabled = true;
}
{
id = "audio-card";
enabled = true;
}
{
id = "weather-card";
enabled = false;
}
{
id = "media-sysmon-card";
enabled = true;
}
];
position = "top_left";
shortcuts = {
left = [
{ id = "Bluetooth"; }
{ id = "Network"; }
];
right = [ { id = "Notifications"; } ];
};
};
dock.enabled = false;
general = {
avatarImage = "${assets.avatar}";
scaleRatio = 1.05;
radiusRatio = 0.8;
enableShadows = false;
telemetryEnabled = false;
};
location.weatherEnabled = false;
network.wifiEnabled = false;
notifications = {
alwaysOnTop = true;
location = "top";
};
osd = {
alwaysOnTop = true;
# enable all types
enabledTypes = [ ];
location = "top";
};
setupCompleted = true;
ui = {
# I love Jetbrains Mono
fontDefault = "Monospace";
fontFixed = "Monospace";
panelsAttachedToBar = false;
panelsOverlayLayer = true;
tooltipsEnabled = true;
};
wallpaper.enabled = false;
};
systemd.enable = true;
};
xdg.configFile."noctalia".source = impure.mkImpureLink ./noctalia;
};
};
}
@@ -0,0 +1,2 @@
[accessibility]
ui_scale = 1
@@ -0,0 +1,2 @@
[audio]
enable_sounds = true
+34
View File
@@ -0,0 +1,34 @@
[bar.main]
capsule = false
capsule_thickness = 0.75
center = [
"media",
]
end = [
"notifications",
"Spacer-12",
"tray",
"Spacer-12",
"output_volume",
"input_volume",
]
font_family = "monospace"
margin_edge = 11
margin_ends = 15
padding = 10
position = "top"
radius = 16
scale = 1.15
shadow = false
start = [
"control-center",
"Spacer-12",
"clock",
"Spacer-12",
"sysmon_cpu",
"sysmon_ram",
"battery",
"Spacer-12",
"workspaces",
]
thickness = 38
@@ -0,0 +1,4 @@
[control_center]
hidden_tabs = ["weather"]
shortcuts = []
show_shortcut_labels = false
@@ -0,0 +1,10 @@
[desktop_widgets]
schema_version = 2
widget_order = []
[desktop_widgets.grid]
cell_size = 16
major_interval = 4
visible = true
[desktop_widgets.widget]
@@ -0,0 +1,2 @@
[dock]
enabled = false
@@ -0,0 +1,7 @@
[hot_corners]
delay_ms = 200
enabled = true
[hot_corners.top_left]
action = "command"
command = "niri msg action toggle-overview"
@@ -0,0 +1,17 @@
[idle]
behavior_order = ["lock", "screen-off", "lock-and-suspend"]
[idle.behavior.lock]
action = "lock"
enabled = true
timeout = 300.0
[idle.behavior.lock-and-suspend]
action = "lock_and_suspend"
enabled = false
timeout = 900.0
[idle.behavior.screen-off]
action = "screen_off"
enabled = true
timeout = 600.0
@@ -0,0 +1,103 @@
[lockscreen]
wallpaper = "~/workspace/nixos-dotfiles/assets/wallpaper.png"
[lockscreen_widgets]
enabled = true
schema_version = 2
widget_order = [
"lockscreen-login-box@DP-1",
"lockscreen-login-box@DP-2",
"lockscreen-widget-clock@DP-1",
"lockscreen-widget-clock@DP-2",
]
[lockscreen_widgets.grid]
cell_size = 16
major_interval = 4
visible = true
[lockscreen_widgets.widget."lockscreen-login-box@DP-1"]
box_height = 70.0
box_width = 400.0
cx = 1024.0
cy = 675.0
output = "DP-1"
placement_height = 1152.0
placement_width = 2048.0
rotation = 0.0
type = "login_box"
[lockscreen_widgets.widget."lockscreen-login-box@DP-1".settings]
background_color = "surface_variant"
background_opacity = 0.88
background_radius = 12.0
center_password_text = false
input_opacity = 1.0
input_radius = 6.0
layout = "compact"
show_caps_lock = true
show_keyboard_layout = false
show_login_button = false
show_media = true
show_session_buttons = true
show_unlock_hint = false
show_weather = true
[lockscreen_widgets.widget."lockscreen-login-box@DP-2"]
box_height = 70.0
box_width = 400.0
cx = 1024.0
cy = 675.0
output = "DP-2"
placement_height = 1152.0
placement_width = 2048.0
rotation = 0.0
type = "login_box"
[lockscreen_widgets.widget."lockscreen-login-box@DP-2".settings]
background_color = "surface_variant"
background_opacity = 0.88
background_radius = 12.0
center_password_text = false
input_opacity = 1.0
input_radius = 6.0
layout = "compact"
show_caps_lock = true
show_keyboard_layout = false
show_login_button = false
show_media = true
show_session_buttons = true
show_unlock_hint = false
show_weather = true
[lockscreen_widgets.widget."lockscreen-widget-clock@DP-1"]
box_height = 144.0
box_width = 240.0
cx = 1024.0
cy = 480.0
output = "DP-1"
placement_height = 1152.0
placement_width = 2048.0
rotation = 0.0
type = "clock"
[lockscreen_widgets.widget."lockscreen-widget-clock@DP-1".settings]
background = false
clock_style = "digital"
format = "{:%H:%M}"
[lockscreen_widgets.widget."lockscreen-widget-clock@DP-2"]
box_height = 144.0
box_width = 240.0
cx = 1024.0
cy = 480.0
output = "DP-2"
placement_height = 1152.0
placement_width = 2048.0
rotation = 0.0
type = "clock"
[lockscreen_widgets.widget."lockscreen-widget-clock@DP-2".settings]
background = false
clock_style = "digital"
format = "{:%H:%M}"
@@ -0,0 +1,2 @@
[notification]
layer = "top"
@@ -0,0 +1,6 @@
[osd]
position = "top_center"
[osd.kinds]
keyboard_layout = false
media = false
@@ -0,0 +1,48 @@
{
"dark": {
"mPrimary": "#7aa2f7",
"mOnPrimary": "#16161e",
"mSecondary": "#9a9ef7",
"mOnSecondary": "#16161e",
"mTertiary": "#bb9af7",
"mOnTertiary": "#16161e",
"mError": "#f7768e",
"mOnError": "#16161e",
"mSurface": "#1a1b26",
"mOnSurface": "#c0caf5",
"mSurfaceVariant": "#24283b",
"mOnSurfaceVariant": "#9aa5ce",
"mOutline": "#353d57",
"mShadow": "#15161e",
"mHover": "#bb9af7",
"mOnHover": "#16161e",
"terminal": {
"background": "#1a1b26",
"foreground": "#c0caf5",
"cursor": "#c0caf5",
"cursorText": "#1a1b26",
"selectionFg": "#c0caf5",
"selectionBg": "#283457",
"normal": {
"black": "#16161e",
"red": "#f7768e",
"green": "#9ece6a",
"yellow": "#e0af68",
"blue": "#7aa2f7",
"magenta": "#bb9af7",
"cyan": "#7dcfff",
"white": "#c0caf5"
},
"bright": {
"black": "#1a1b26",
"red": "#ff7a93",
"green": "#b9f27c",
"yellow": "#ffc777",
"blue": "#7da6ff",
"magenta": "#bb9af7",
"cyan": "#89ddff",
"white": "#d5d6e3"
}
}
}
}
@@ -0,0 +1,39 @@
# tombi: format.rules.table-keys-order.disabled = true
[shell]
avatar_path = "~/workspace/nixos-dotfiles/assets/avatar.jpg"
corner_radius_scale = 1.5
date_format = "%Y/%m/%d"
font_family = "monospace"
lang = "en"
launch_apps_as_systemd_services = true
panel_anchor_bar = "main"
polkit_agent = true
screen_time_enabled = true
telemetry_enabled = false
clipboard_enabled = true
clipboard_history_max_entries = 1000
[shell.animation]
speed = 0.8
[shell.launcher]
sort_by_usage = true
[shell.mpris]
blacklist = ["firefox", "chromium", "zen"]
[shell.panel]
borders = false
clipboard_placement = "floating"
control_center_placement = "floating"
launcher_placement = "floating"
open_near_click_control_center = true
session_placement = "floating"
session_position = "center"
shadow = true
transparency_mode = "soft"
wallpaper_placement = "floating"
[shell.shadow]
alpha = 0.0
@@ -0,0 +1,4 @@
[theme]
custom_palette = "TokyoNight"
mode = "dark"
source = "custom"
@@ -0,0 +1,2 @@
[wallpaper]
enabled = false
@@ -0,0 +1,2 @@
[weather]
enabled = false
@@ -0,0 +1,58 @@
[widget.Spacer-12]
length = 12
type = "spacer"
[widget.battery]
scale = 1.2
[widget.clock]
format = "{:%H:%M %m/%d}"
scale = 0.9
[widget.control-center]
custom_image = "/run/current-system/sw/share/icons/hicolor/scalable/apps/nix-snowflake.svg"
scale = 1.25
[widget.input_volume]
show_label = false
[widget.media]
art_size = 30
font_scale = 0.84
title_scroll = "always"
hide_when_no_media = true
[widget.mic]
device = "input"
type = "volume"
[widget.notifications]
capsule_radius = "auto"
hide_when_no_unread = false
scale = 1.05
[widget.output_volume]
show_label = false
[widget.sysmon_cpu]
scale = 1.2
show_glyph = false
show_value = false
stat = "cpu_usage"
type = "sysmon"
visualization = "graph"
[widget.sysmon_ram]
label_show_units = false
show_value = false
stat = "ram_used"
type = "sysmon"
[widget.tray]
capsule_radius = 16
drawer = false
[widget.workspaces]
hide_when_empty = false
pill_scale = 0.9
show_labels = false
+2
View File
@@ -35,6 +35,8 @@
# GVFS for virtual filesystems
services.gvfs.enable = true;
services.speechd.enable = false;
# Enable desktop-related modules by default
my = {
audio.enable = true;
+491
View File
@@ -0,0 +1,491 @@
#!/usr/bin/env python3
"""Fold noctalia GUI overrides back into the declarative dotfiles config.
This script folds `noctalia config export merged` back into the per-aspect
*.toml files so the repo alone reproduces the current effective config:
* export values win (added if missing, updated if changed),
* repo-only keys and empty-table declarations are never deleted,
* f32 serialization noise (0.74999998696148396 vs 0.75) is ignored,
* floats are written back clean (0.8400000000000001 -> 0.84),
* comments (tombi directives) are preserved across the rewrite.
Files are parsed with `tomli` and rewritten with `tomli_w`, then touched
files are re-formatted with `tombi` to match repo style. Note that `tomli_w`
renders every non-empty array on multiple lines and `tombi` preserves that
layout, so arrays in a folded file are reflowed from inline to multiline the
first time that file is touched.
After a successful fold the GUI override file is moved to
<state-dir>/settings.bak.d/<timestamp>.toml (the 3 most recent are kept) and
the running instance is reloaded.
Usage:
sync-noctalia.py # preview the fold; change nothing
sync-noctalia.py --apply # apply, backup settings.toml, reload
sync-noctalia.py --export-file <file> # read the export from F instead of noctalia
sync-noctalia.py --config-dir <dir> # target a different config dir
sync-noctalia.py --state-dir <dir> # state dir holding settings.toml
"""
import argparse
import math
import os
import re
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from subprocess import CalledProcessError
import tomli
import tomli_w
REPO = Path(__file__).resolve().parent.parent
DEFAULT_CONFIG_DIR = REPO / "modules" / "desktop" / "wm" / "niri" / "noctalia"
DEFAULT_STATE_DIR = Path.home() / ".local" / "state" / "noctalia"
KEEP_BACKUPS = 3
FLOAT_TOL = 1e-4
FLOAT_PRECISION = 6
def parse_export(export_file: Path | None) -> dict:
if export_file:
return tomli.loads(export_file.read_text())
try:
proc = subprocess.run(
["noctalia", "config", "export", "merged"],
capture_output=True,
text=True,
check=True,
)
except FileNotFoundError:
sys.exit("error: `noctalia` not found on PATH")
except CalledProcessError as err:
sys.exit(f"error: `noctalia config export merged` failed:\n{err.stderr}")
return tomli.loads(proc.stdout)
def values_differ(a, b) -> bool:
"""True if two values differ as config values (float-tolerant, recursive)."""
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return not math.isclose(float(a), float(b), rel_tol=0.0, abs_tol=FLOAT_TOL)
if isinstance(a, list) and isinstance(b, list):
return len(a) != len(b) or any(values_differ(x, y) for x, y in zip(a, b))
if isinstance(a, dict) and isinstance(b, dict):
return set(a) != set(b) or any(values_differ(a[k], b[k]) for k in a)
return a != b
def collect_changes(repo_sub, exp_sub, prefix, changes):
"""Recursively diff export subtree against repo subtree, appending
(path, old_value, new_value) tuples. New subtrees carry old_value=None
and a dict new_value."""
for k, v in exp_sub.items():
path = prefix + [k]
if isinstance(v, dict):
cur = repo_sub.get(k)
if isinstance(cur, dict):
collect_changes(cur, v, path, changes)
else:
changes.append((path, cur, v))
else:
cur = repo_sub.get(k)
if cur is None or values_differ(cur, v):
changes.append((path, cur, v))
def clean_value(v):
"""Round floats to FLOAT_PRECISION places, recursively.
tomli_w serialises floats with str(), which would keep f32 noise like
0.74999998696148396 written by a float slider; rounding keeps written
values clean without losing precision we care about.
"""
if isinstance(v, float):
return round(v, FLOAT_PRECISION)
if isinstance(v, list):
return [clean_value(x) for x in v]
if isinstance(v, dict):
return {k: clean_value(x) for k, x in v.items()}
return v
# Preview rendering (display only; file I/O goes through tomli_w)
def _dotted(segments) -> str:
"""Render segments as a TOML dotted path, quoting non-bare segments."""
out = []
for s in segments:
if not s or not all(c.isalnum() or c in "_-" for c in s):
out.append('"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"')
else:
out.append(s)
return ".".join(out)
def _clean_float(v: float) -> str:
s = f"{v:.6f}".rstrip("0").rstrip(".")
return s + ".0" if "." not in s else s
def serialize_value(v, indent: str = "") -> str:
if isinstance(v, bool):
return "true" if v else "false"
if isinstance(v, int):
return str(v)
if isinstance(v, float):
return _clean_float(v)
if isinstance(v, str):
return '"' + v.replace("\\", "\\\\").replace('"', '\\"') + '"'
if isinstance(v, list):
return "[" + ", ".join(serialize_value(e, indent) for e in v) + "]"
if isinstance(v, dict):
inner = ", ".join(
f"{_dotted([k])} = {serialize_value(x, '')}" for k, x in v.items()
)
return "{ " + inner + " }"
raise TypeError(f"unsupported TOML value: {v!r}")
def serialize_subtree(path, subtree) -> str:
"""Serialize a dict as a TOML section block: headers followed by keys."""
out = []
def walk(p, d):
out.append(f"[{_dotted(p)}]\n")
for k, v in d.items():
if not isinstance(v, dict):
out.append(f"{k} = {serialize_value(v)}\n")
for k, v in d.items():
if isinstance(v, dict):
walk(p + [k], v)
walk(path, subtree)
return "".join(out)
# Comment preservation
def extract_comment_blocks(text: str) -> list[tuple[list[str], str | None]]:
"""Contiguous comment lines, each paired with the table header that follows
them (or None for a trailing run). tomli_w drops comments when rewriting a
file, so they are re-anchored from the original text."""
lines = text.splitlines()
runs = []
i = 0
while i < len(lines):
if lines[i].lstrip().startswith("#"):
j = i
while j < len(lines) and lines[j].lstrip().startswith("#"):
j += 1
runs.append((i, j))
i = j
else:
i += 1
blocks = []
for start, end in runs:
anchor = None
for k in range(end, len(lines)):
t = lines[k].strip()
if t.startswith("[") and t.endswith("]"):
anchor = t
break
blocks.append((lines[start:end], anchor))
return blocks
def restore_comments(original_text: str, rendered_text: str) -> str:
"""Re-insert the original file's comments into tomli_w's output, before the
table header each comment preceded (any stragglers go at EOF)."""
blocks = extract_comment_blocks(original_text)
if not blocks:
return rendered_text
pending = list(blocks)
out = []
for line in rendered_text.splitlines():
stripped = line.strip()
if stripped.startswith("[") and stripped.endswith("]"):
for block, anchor in list(pending):
if anchor == stripped:
out.extend(block)
pending.remove((block, anchor))
out.append(line)
for block, _anchor in pending:
out.extend(block)
return "\n".join(out) + "\n"
# Dict-based edits
def apply_path(data: dict, path, value, *, as_table: bool, old):
"""Set ``path`` to ``value`` in ``data``, creating intermediate tables.
Leaf values are assigned in place (a new key appends at the end of its
table). Sub-tables are inserted at their sorted position among sibling
sub-tables, matching the repo's alphabetical `[section]` order; a scalar
being replaced by a table is dropped first.
"""
node = data
for seg in path[:-1]:
nxt = node.get(seg)
if not isinstance(nxt, dict):
nxt = {}
node[seg] = nxt
node = nxt
key = path[-1]
if as_table:
if old is not None:
node.pop(key, None)
items = list(node.items())
pos = len(items)
for i, (k, v) in enumerate(items):
if isinstance(v, dict) and k > key:
pos = i
break
items.insert(pos, (key, value))
node.clear()
node.update(items)
else:
node[key] = value
# Verification and state handling
def completeness_diffs(export, repo_after) -> list:
"""Leaf paths where repo_after still differs from the export."""
diffs = []
def walk(e, r, prefix):
for k, v in e.items():
p = prefix + [k]
if isinstance(v, dict):
if not isinstance(r.get(k), dict):
diffs.append((p, None, v))
else:
walk(v, r[k], p)
else:
cur = r.get(k)
if cur is None or values_differ(cur, v):
diffs.append((p, cur, v))
walk(export, repo_after, [])
return diffs
def run_validate(config_dir: Path) -> bool:
print(f" validate: noctalia config validate {config_dir}")
proc = subprocess.run(
["noctalia", "config", "validate", str(config_dir)],
capture_output=True,
text=True,
check=False,
)
out = proc.stdout + proc.stderr
for line in out.splitlines():
print(f" {line}")
if "WARN" in out:
print(
" note: confirm none of the warnings above were newly introduced by this fold"
)
return proc.returncode == 0
def run_tombi(files) -> None:
for f in files:
proc = subprocess.run(
["tombi", "format", str(f)], capture_output=True, text=True, check=False
)
if proc.returncode != 0:
print(f" tombi format {f.name}: FAILED\n{proc.stdout}{proc.stderr}")
def backup_settings(state_dir: Path, keep: int = KEEP_BACKUPS):
src = state_dir / "settings.toml"
if not src.exists():
return None
bak = state_dir / "settings.bak.d"
bak.mkdir(parents=True, exist_ok=True)
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
dest = bak / f"{ts}.toml"
n = 1
while dest.exists():
dest = bak / f"{ts}-{n}.toml"
n += 1
shutil.move(str(src), str(dest))
# Prune oldest backups, counting only timestamp-shaped files we own.
backups = sorted(
f for f in bak.glob("*.toml") if re.fullmatch(r"\d{8}-\d{6}(?:-\d+)?", f.stem)
)
for old in backups[:-keep]:
old.unlink()
return dest
def reload_instance():
proc = subprocess.run(
["noctalia", "msg", "config-reload"],
capture_output=True,
text=True,
timeout=15,
check=False,
)
if proc.returncode == 0:
print(" reload: noctalia msg config-reload")
else:
print(" reload: noctalia not running or reload failed")
def main():
ap = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
ap.add_argument(
"--apply", action="store_true", help="apply the fold (default: preview only)"
)
ap.add_argument(
"--config-dir", type=Path, default=DEFAULT_CONFIG_DIR, help=argparse.SUPPRESS
)
ap.add_argument(
"--state-dir", type=Path, default=DEFAULT_STATE_DIR, help=argparse.SUPPRESS
)
ap.add_argument("--export-file", type=Path, default=None, help=argparse.SUPPRESS)
args = ap.parse_args()
config_dir = args.config_dir.resolve()
state_dir = args.state_dir
if not config_dir.is_dir():
sys.exit(f"error: config dir not found: {config_dir}")
if not args.export_file:
live = Path(os.path.realpath(Path.home() / ".config" / "noctalia"))
if live != config_dir:
print(
f"warning: ~/.config/noctalia is not linked to {config_dir} (found {live});"
)
print(" the live export may describe a different config")
export = parse_export(args.export_file)
# Parse per-aspect files; map each top-level root to its owning file.
files = sorted(config_dir.glob("*.toml"))
texts = {}
parsed = {}
for f in files:
text = f.read_text()
texts[f] = text
parsed[f] = tomli.loads(text)
root_map = {}
for f, d in parsed.items():
for root in d:
root_map.setdefault(root, f)
# Collect every change the export implies relative to the repo.
changes = []
new_files = {}
for root, subtree in export.items():
if not isinstance(subtree, dict):
continue # do not fold top-level scalars
tgt = root_map.get(root)
if tgt is None:
new_files[root] = subtree
continue
cur = parsed[tgt].get(root)
collect_changes(cur if isinstance(cur, dict) else {}, subtree, [root], changes)
sets_by_file, subs_by_file = {}, {}
for path, old, val in changes:
f = root_map[path[0]]
if isinstance(val, dict): # new subtree
subs_by_file.setdefault(f, []).append((path, old, val))
else:
sets_by_file.setdefault(f, []).append((path, old, val))
if not changes and not new_files:
print("No changes to fold. Repo config already matches the export.")
return
# Preview
touched = set(sets_by_file) | set(subs_by_file)
for f in sorted(touched, key=str):
rel = os.path.relpath(f, REPO)
print(f"\n{rel}")
for path, old, val in sorted(
sets_by_file.get(f, []), key=lambda c: _dotted(c[0])
):
key = _dotted(path)
if old is None:
print(f" + {key} = {serialize_value(val)}")
else:
print(f" ~ {key}: {serialize_value(old)} -> {serialize_value(val)}")
for path, old, subtree in subs_by_file.get(f, []):
print(f" + [{_dotted(path)}]")
for line in serialize_subtree(path, subtree).splitlines():
if line.strip():
print(f" {line}")
for root in new_files:
print(
f"\n + new section root: {root} (new file {root.replace('_', '-')}.toml)"
)
if not args.apply:
total = len(changes) + len(new_files)
file_count = len(touched) + len(new_files)
print(
f"\n{total} change{'s' if total != 1 else ''} in {file_count} file(s)."
)
print("Run with --apply to fold into the repo.")
return
# Apply
for f in sorted(touched, key=str):
data = parsed[f]
for path, old, val in sets_by_file.get(f, []):
apply_path(data, path, clean_value(val), as_table=False, old=old)
for path, old, subtree in subs_by_file.get(f, []):
apply_path(data, path, clean_value(subtree), as_table=True, old=old)
f.write_text(restore_comments(texts[f], tomli_w.dumps(data)))
new_paths = []
for root, subtree in new_files.items():
p = config_dir / (root.replace("_", "-") + ".toml")
p.write_text(tomli_w.dumps(clean_value({root: subtree})))
new_paths.append(p)
files.append(p)
print(" formatting changed files with tombi...")
run_tombi(sorted(touched, key=str) + new_paths)
# Completeness: repo-as-loaded must now equal the export (within tolerance).
repo_after = {}
for f in files:
repo_after.update(tomli.loads(f.read_text()))
diffs = completeness_diffs(export, repo_after)
if diffs:
print("error: repo does not fully reproduce the export after applying:")
for p, old, val in diffs[:20]:
print(f" {_dotted(p)}: {serialize_value(old)} != {serialize_value(val)}")
sys.exit(1)
if not run_validate(config_dir):
sys.exit("error: validation failed after applying changes")
bak = backup_settings(state_dir)
if bak:
print(f" backed up {state_dir}/settings.toml -> {bak}")
reload_instance()
else:
# Should not reach this branch, since a fold implies GUI overrides exist.
print("warning: settings.toml does not exist. Nothing backuped")
total = len(changes) + len(new_files)
print(f"\nDone. {total} change{'s' if total != 1 else ''} folded.")
if __name__ == "__main__":
main()
+21 -5
View File
@@ -1,12 +1,28 @@
{ inputs, ... }:
{
imports = [ inputs.treefmt.flakeModule ];
perSystem.treefmt = {
{ inputs, ... }: {
imports = [
inputs.treefmt.flakeModule
];
perSystem.treefmt = { lib, pkgs, ... }: {
projectRootFile = "flake.nix";
programs = {
nixfmt.enable = true;
stylua.enable = true;
keep-sorted.enable = true;
stylua.enable = true;
ruff.enable = true;
};
settings.formatter.tombi = {
command = "${pkgs.bash}/bin/bash";
options = [
"-euc"
''
for file in "$@"; do
${lib.getExe pkgs.tombi} fmt $file
done
''
"--"
];
includes = [ "*.toml" ];
};
settings.excludes = [ "secrets/*" ];
};
}
+4 -9
View File
@@ -1,18 +1,13 @@
#:schema https://raw.githubusercontent.com/crate-ci/typos/refs/heads/master/config.schema.json
[files]
extend-exclude = [
"secrets"
]
extend-exclude = ["secrets"]
[default]
extend-ignore-identifiers-re = ["KEY_102ND", "VE_UE"]
[default.extend-words]
# noice.nvim
noice = "noice"
enew = "enew"
iif = "iif"
[default]
extend-ignore-identifiers-re = [
"KEY_102ND",
"VE_UE"
]