Compare commits

...

63 commits

Author SHA1 Message Date
Geoffrey Frogeye 984ef44672
I thought I fixed you 2025-04-17 15:27:13 +02:00
Geoffrey Frogeye 1d9269126e
frobar: Attempt to fix frobar not showing up on startup 2025-04-17 15:24:36 +02:00
Geoffrey Frogeye 534e246cec
Some SSH stuff 2025-04-17 15:19:48 +02:00
Geoffrey Frogeye 2ce896491b
No mo abavorana 2025-04-06 22:43:01 +02:00
Geoffrey Frogeye 1fb1498148
Upgrade 2025-04-06 22:42:15 +02:00
Geoffrey Frogeye 10deec06e1
Bye suckless stuff 2025-04-03 20:33:23 +02:00
Geoffrey Frogeye b50a21e0de
Auto-cleanup
Managed to clean 10 GiB even with beesd enabled, and it's still going!
Reconsidering beesd but also meh, it hasn't been a pain lately.
2025-03-31 00:08:33 +02:00
Geoffrey Frogeye 68bf84cd0e
Ludwig not syncing anymore 2025-03-26 01:03:05 +01:00
Geoffrey Frogeye 8ac05e9d37
Not not 2025-03-25 14:47:15 +01:00
Geoffrey Frogeye 636f1bd617
Build with morton! 2025-03-25 10:15:33 +01:00
Geoffrey Frogeye 5ca5007eb5
No mo clowncar 2025-03-22 23:33:16 +01:00
Geoffrey Frogeye 8951a4fb43
Add morton 2025-03-15 04:42:23 +01:00
Geoffrey Frogeye 1ed4500cea
nextcloud: Added 2025-03-02 00:01:20 +01:00
Geoffrey Frogeye 58f32985b5
Add netrc 2025-02-27 21:12:47 +01:00
Geoffrey Frogeye 6e979e317e
Less VirtualBox 2025-02-26 01:47:16 +01:00
Geoffrey Frogeye cb21bf4cd9
3d: Use openscad-unstable
From more than 3 seconds to less than half a second to just load
BOSL2/std.scad
2025-02-25 21:35:22 +01:00
Geoffrey Frogeye e9e6265c41
Remove unused stuff
Evaluation on cranberry is sure does not leave much memory.
2025-02-25 17:06:14 +01:00
Geoffrey Frogeye dc52303eb7
Allow evaluating here 2025-02-25 16:13:10 +01:00
Geoffrey Frogeye 3dc42a7756
Fix update-local-flakes app 2025-02-25 15:44:14 +01:00
Geoffrey Frogeye 0478f00a58
OpenSCAD libraries 2025-02-25 14:56:57 +01:00
Geoffrey Frogeye dfa4e65377
CAAAD 2025-02-25 00:24:34 +01:00
Geoffrey Frogeye 75de3cc274
Monies 2025-02-23 21:59:09 +01:00
Geoffrey Frogeye 778521804a
Bluenav 2025-02-23 19:25:50 +01:00
Geoffrey Frogeye 6c3572eb43
Finally upadte stateVersion to 24.11
Only changes docker's live-restore to false, so 🤷
2025-02-22 23:00:32 +01:00
Geoffrey Frogeye 8b9ebc2dc3
Update 2025-02-22 22:25:40 +01:00
Geoffrey Frogeye ea37f3d67b
Forgot to remove this file 2025-02-14 04:24:25 +01:00
Geoffrey Frogeye de17a48de3
Friendship ended with systemd-resolved. Now dnsmasq is my new best friend.
Welp, it was short-lived.
2025-02-14 04:14:28 +01:00
Geoffrey Frogeye 78027a7f48
Moulti DNS 2025-02-14 03:15:36 +01:00
Geoffrey Frogeye 0229cab124
Show time
Ok no actually I meant Fix time but Show time sounds good so that's
officially the commit's name now.
2025-02-13 13:39:39 +01:00
Geoffrey Frogeye 13eeeabc92
Add more utils 2025-02-13 13:38:54 +01:00
Geoffrey Frogeye 49b8dd0b5e
homepage: Allow an URL for categories 2025-02-02 23:24:15 +01:00
Geoffrey Frogeye 7ca1e0576b
Music handlers moved to server 2025-02-01 01:38:37 +01:00
Geoffrey Frogeye e26fa6a011
Local homepage
Basically the same thing as currently online, except templated, and
trimmed from useless stuff, but needs modernization.
2025-01-31 00:05:27 +01:00
Geoffrey Frogeye 8d07123713
Keyboardz 2025-01-30 21:22:26 +01:00
Geoffrey Frogeye 684d9b4d8b
frobar: More corner cases 2025-01-28 19:07:52 +01:00
Geoffrey Frogeye aceb44293f
syncthing: Support remote declarative sync 2025-01-27 00:15:28 +01:00
Geoffrey Frogeye 460ab8938f
frobar: Workaround missing workspace
Not sure what causes it, not interested in figuring out right now.
2025-01-26 17:18:40 +01:00
Geoffrey Frogeye 3335f90de4
Fix specialisation detection
I think
2025-01-26 17:18:28 +01:00
Geoffrey Frogeye 8fd6a0d2d8
Misc 2025-01-23 20:17:28 +01:00
Geoffrey Frogeye f3e7b29a98
Upgrade 2025-01-23 18:57:51 +01:00
Geoffrey Frogeye bc9fefe8ee
Add earlyoom 2025-01-23 18:46:31 +01:00
Geoffrey Frogeye e113b70357
rebuild: Fix 2025-01-23 18:46:13 +01:00
Geoffrey Frogeye cce96e063d
rebuild: Fix display 2025-01-23 18:19:49 +01:00
Geoffrey Frogeye cd4536c53b
Rebuild: Separate evaluation 2025-01-23 18:17:09 +01:00
Geoffrey Frogeye fafc4d45b7
remote-builds: Fix host key verification 2025-01-23 17:34:20 +01:00
Geoffrey Frogeye 875ea5d137
deploy: Colorize steps 2025-01-23 17:31:48 +01:00
Geoffrey Frogeye 50abb95e4c
Re-enable remote builds 2025-01-23 15:34:21 +01:00
Geoffrey Frogeye 6b662477c4
Shell goodness
Also potential for RCE, yay?
2025-01-21 23:43:01 +01:00
Geoffrey Frogeye 6d6a991fa5
Switch up the languages 2025-01-21 00:21:00 +01:00
Geoffrey Frogeye e6656923e0
frobar: Fix previous commit
Yeah I definitely did testing 🙃
2025-01-16 19:28:38 +01:00
Geoffrey Frogeye fa11c76d46
frobar: Sort screens by x position 2025-01-16 18:18:24 +01:00
Geoffrey Frogeye 04a08faa7f
frobar: Fix ramp doubles on 1.0 2025-01-15 16:32:06 +01:00
Geoffrey Frogeye c4fdd50a61
New ability get: Scanning! 2025-01-15 16:22:48 +01:00
Geoffrey Frogeye 08f1049cc1
Bit more comments 2025-01-13 12:37:37 +01:00
Geoffrey Frogeye 3ccb0abfe5
frobar: Handle PulseAudio sinks without active port
No idea in which conditions those happen, but apparently they do happen.
2025-01-11 15:01:50 +01:00
Geoffrey Frogeye d6e95da9dc
frobar: Fix empty container name 2025-01-10 22:18:15 +01:00
Geoffrey Frogeye 9adfcd2377
frobar: Now version 3! 2025-01-10 19:09:15 +01:00
Geoffrey Frogeye 42d3d1b3a6
frobar-ng: Fix last few issues 2025-01-10 18:35:10 +01:00
Geoffrey Frogeye 24dd070bd9
frobar-ng: Fix mpris issues 2025-01-10 17:46:37 +01:00
Geoffrey Frogeye 9916cb0170
frobar-ng: Fix Glib preventing closing 2025-01-10 15:19:51 +01:00
Geoffrey Frogeye f22b42a711
frobar-ng: Broken mpris! 2025-01-10 00:17:06 +01:00
Geoffrey Frogeye ffd402f57c
frobar-ng: Sections factory! 2025-01-06 23:08:52 +01:00
Geoffrey Frogeye ecf831d4ac
frobar-ng: Improve animation 2025-01-06 00:58:05 +01:00
77 changed files with 2286 additions and 3486 deletions

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -4,8 +4,7 @@
frogeye = {
desktop.xorg = true;
dev = {
c = true;
vm = true;
"3d" = true;
};
extra = true;
};

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
nixos-hardware,
...
}:
@ -26,7 +24,7 @@
hardware.enableRedistributableFirmware = true;
frogeye.desktop = {
x11_screens = [ "eDP-1" ];
x11_screens = [ "DP-2" "eDP-1" ];
maxVideoHeight = 1080;
phasesCommands = {

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -5,6 +5,7 @@
xorg = true;
};
dev = {
"3d" = true;
c = true;
docker = true;
vm = true;

View file

@ -49,6 +49,9 @@ in
hardware.enableRedistributableFirmware = true;
# TODO Do we really need that? Besides maybe microcode?
# AnnePro 2
hardware.keyboard.qmk.enable = true;
frogeye.desktop = {
x11_screens = [
displays.deskLeft.output
@ -59,18 +62,18 @@ in
phasesCommands = {
jour = ''
${pkgs.brightnessctl}/bin/brightnessctl set 40000 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 20 -d 1 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 20 -d 2 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 20 -d 1 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 20 -d 2 &
'';
crepuscule = ''
${pkgs.brightnessctl}/bin/brightnessctl set 10000 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 10 -d 1 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 10 -d 2 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 10 -d 1 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 10 -d 2 &
'';
nuit = ''
${pkgs.brightnessctl}/bin/brightnessctl set 1 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 0 -d 1 &
${pkgs.ddcutil}/bin/ddcutil setvcp 10 0 -d 2 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 0 -d 1 &
# ${pkgs.ddcutil}/bin/ddcutil setvcp 10 0 -d 2 &
'';
# TODO Display 2 doesn't work anymore?
};

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -37,11 +37,11 @@
"base16-helix": {
"flake": false,
"locked": {
"lastModified": 1725860795,
"narHash": "sha256-Z2o8VBPW3I+KKTSfe25kskz0EUj7MpUh8u355Z1nVsU=",
"lastModified": 1736852337,
"narHash": "sha256-esD42YdgLlEh7koBrSqcT7p2fsMctPAcGl/+2sYJa2o=",
"owner": "tinted-theming",
"repo": "base16-helix",
"rev": "7f795bf75d38e0eea9fed287264067ca187b88a9",
"rev": "03860521c40b0b9c04818f2218d9cc9efc21e7a5",
"type": "github"
},
"original": {
@ -53,16 +53,17 @@
"base16-vim": {
"flake": false,
"locked": {
"lastModified": 1731949548,
"narHash": "sha256-XIDexXM66sSh5j/x70e054BnUsviibUShW7XhbDGhYo=",
"lastModified": 1732806396,
"narHash": "sha256-e0bpPySdJf0F68Ndanwm+KWHgQiZ0s7liLhvJSWDNsA=",
"owner": "tinted-theming",
"repo": "base16-vim",
"rev": "61165b1632409bd55e530f3dbdd4477f011cadc6",
"rev": "577fe8125d74ff456cf942c733a85d769afe58b7",
"type": "github"
},
"original": {
"owner": "tinted-theming",
"repo": "base16-vim",
"rev": "577fe8125d74ff456cf942c733a85d769afe58b7",
"type": "github"
}
},
@ -74,11 +75,11 @@
]
},
"locked": {
"lastModified": 1728330715,
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
"lastModified": 1741473158,
"narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
"owner": "numtide",
"repo": "devshell",
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
"rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
"type": "github"
},
"original": {
@ -94,11 +95,11 @@
]
},
"locked": {
"lastModified": 1735048446,
"narHash": "sha256-Tc35Y8H+krA6rZeOIczsaGAtobSSBPqR32AfNTeHDRc=",
"lastModified": 1743598667,
"narHash": "sha256-ViE7NoFWytYO2uJONTAX35eGsvTYXNHjWALeHAg8OQY=",
"owner": "nix-community",
"repo": "disko",
"rev": "3a4de9fa3a78ba7b7170dda6bd8b4cdab87c0b21",
"rev": "329d3d7e8bc63dd30c39e14e6076db590a6eabe6",
"type": "github"
},
"original": {
@ -106,14 +107,30 @@
"type": "indirect"
}
},
"firefox-gnome-theme": {
"flake": false,
"locked": {
"lastModified": 1743774811,
"narHash": "sha256-oiHLDHXq7ymsMVYSg92dD1OLnKLQoU/Gf2F1GoONLCE=",
"owner": "rafaelmardojai",
"repo": "firefox-gnome-theme",
"rev": "df53a7a31872faf5ca53dd0730038a62ec63ca9e",
"type": "github"
},
"original": {
"owner": "rafaelmardojai",
"repo": "firefox-gnome-theme",
"type": "github"
}
},
"flake-compat": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
@ -123,11 +140,11 @@
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@ -144,11 +161,11 @@
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@ -261,18 +278,40 @@
"nixpkgs": [
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1742649964,
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"git-hooks_2": {
"inputs": {
"flake-compat": [
"stylix",
"flake-compat"
],
"nixpkgs-stable": [
"nixvim",
"gitignore": "gitignore_2",
"nixpkgs": [
"stylix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1734425854,
"narHash": "sha256-nzE5UbJ41aPEKf8R2ZFYtLkqPmF7EIUbNEdHMBLg0Ig=",
"lastModified": 1742649964,
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "0ddd26d0925f618c3a5d85a4fa5eb1e23a09491d",
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
"type": "github"
},
"original": {
@ -303,6 +342,28 @@
"type": "github"
}
},
"gitignore_2": {
"inputs": {
"nixpkgs": [
"stylix",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gnome-shell": {
"flake": false,
"locked": {
@ -327,11 +388,11 @@
]
},
"locked": {
"lastModified": 1734366194,
"narHash": "sha256-vykpJ1xsdkv0j8WOVXrRFHUAdp9NXHpxdnn1F4pYgSw=",
"lastModified": 1743808813,
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "80b0fdf483c5d1cb75aaad909bd390d48673857f",
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
"type": "github"
},
"original": {
@ -348,11 +409,11 @@
]
},
"locked": {
"lastModified": 1734366194,
"narHash": "sha256-vykpJ1xsdkv0j8WOVXrRFHUAdp9NXHpxdnn1F4pYgSw=",
"lastModified": 1743808813,
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "80b0fdf483c5d1cb75aaad909bd390d48673857f",
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
"type": "github"
},
"original": {
@ -370,11 +431,11 @@
]
},
"locked": {
"lastModified": 1733572789,
"narHash": "sha256-zjO6m5BqxXIyjrnUziAzk4+T4VleqjstNudSqWcpsHI=",
"lastModified": 1743808813,
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "c7ffc9727d115e433fd884a62dc164b587ff651d",
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
"type": "github"
},
"original": {
@ -412,38 +473,6 @@
"type": "github"
}
},
"jjuinixpkgs": {
"locked": {
"lastModified": 1734688116,
"narHash": "sha256-Ex3o8880p+yZ9915s46/4XtnN4jS6tqp2TlfGR1+l1w=",
"owner": "Adda0",
"repo": "nixpkgs",
"rev": "c951613d3cb3d61b14c890238017d0685ff359f9",
"type": "github"
},
"original": {
"owner": "Adda0",
"ref": "jjui",
"repo": "nixpkgs",
"type": "github"
}
},
"labellenixpkgs": {
"locked": {
"lastModified": 1733305049,
"narHash": "sha256-j3L36nA0PTjVA6gtMVILBhrBSMxuhevlDW9Nfws1oII=",
"owner": "FabianRig",
"repo": "nixpkgs",
"rev": "88ac05665bc6a85aabe78070b99fd23ad1675409",
"type": "github"
},
"original": {
"owner": "FabianRig",
"ref": "update-labelle-1.3.2",
"repo": "nixpkgs",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
@ -452,15 +481,16 @@
]
},
"locked": {
"lastModified": 1733570843,
"narHash": "sha256-sQJAxY1TYWD1UyibN/FnN97paTFuwBw3Vp3DNCyKsMk=",
"lastModified": 1743127615,
"narHash": "sha256-+sMGqywrSr50BGMLMeY789mSrzjkoxZiu61eWjYS/8o=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "a35b08d09efda83625bef267eb24347b446c80b8",
"rev": "fc843893cecc1838a59713ee3e50e9e7edc6207c",
"type": "github"
},
"original": {
"owner": "lnl7",
"ref": "nix-darwin-24.11",
"repo": "nix-darwin",
"type": "github"
}
@ -520,11 +550,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1734954597,
"narHash": "sha256-QIhd8/0x30gEv8XEE1iAnrdMlKuQ0EzthfDR7Hwl+fk=",
"lastModified": 1743420942,
"narHash": "sha256-b/exDDQSLmENZZgbAEI3qi9yHkuXAXCPbormD8CSJXo=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "def1d472c832d77885f174089b0d34854b007198",
"rev": "de6fc5551121c59c01e2a3d45b277a6d05077bc4",
"type": "github"
},
"original": {
@ -534,11 +564,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1734991663,
"narHash": "sha256-8T660guvdaOD+2/Cj970bWlQwAyZLKrrbkhYOFcY1YE=",
"lastModified": 1743813633,
"narHash": "sha256-BgkBz4NpV6Kg8XF7cmHDHRVGZYnKbvG0Y4p+jElwxaM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6c90912761c43e22b6fb000025ab96dd31c971ff",
"rev": "7819a0d29d1dd2bc331bec4b327f0776359b1fa6",
"type": "github"
},
"original": {
@ -581,11 +611,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1734649271,
"narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=",
"lastModified": 1743827369,
"narHash": "sha256-rpqepOZ8Eo1zg+KJeWoq1HAOgoMCDloqv5r2EAa9TSA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d70bd19e0a38ad4790d3913bf08fcbfc9eeca507",
"rev": "42a1c966be226125b48c384171c44c651c236c22",
"type": "github"
},
"original": {
@ -610,11 +640,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1734784342,
"narHash": "sha256-uap4LcvjpTz5WTgDfQYtL3QCpGmtee7DuD5mB8AIiLw=",
"lastModified": 1743856924,
"narHash": "sha256-CgCbUGd9y639PfcuzA0TrA6O5N1ICl+mB95+qTG52+E=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "334947672f1eb05488e69657b9c412230bd658b4",
"rev": "d209a04d349febe85c777078ca2eeea5e8bbc8a1",
"type": "github"
},
"original": {
@ -669,11 +699,11 @@
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
"lastModified": 1735130532,
"narHash": "sha256-efntkb+ydFSI2kvLn6SURQEp4KnThRGZ2eeJHiKL93o=",
"lastModified": 1743941131,
"narHash": "sha256-8nTzPXoAQConOnOqG2ZxB8nuUvrGkPuNuqVao9ZNXcI=",
"owner": "nix-community",
"repo": "NUR",
"rev": "b9f4b07220fa430240dba1825cdac9d673dedf55",
"rev": "aa39cbcf5d0bec9b2b3205ae82baa7aac4d6042c",
"type": "github"
},
"original": {
@ -692,11 +722,11 @@
]
},
"locked": {
"lastModified": 1733773348,
"narHash": "sha256-Y47y+LesOCkJaLvj+dI/Oa6FAKj/T9sKVKDXLNsViPw=",
"lastModified": 1743683223,
"narHash": "sha256-LdXtHFvhEC3S64dphap1pkkzwjErbW65eH1VRerCUT0=",
"owner": "NuschtOS",
"repo": "search",
"rev": "3051be7f403bff1d1d380e4612f0c70675b44fc9",
"rev": "56a49ffef2908dad1e9a8adef1f18802bc760962",
"type": "github"
},
"original": {
@ -710,14 +740,13 @@
"disko": "disko",
"flake-utils": "flake-utils",
"home-manager": "home-manager",
"jjuinixpkgs": "jjuinixpkgs",
"labellenixpkgs": "labellenixpkgs",
"nix-on-droid": "nix-on-droid",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs",
"nixvim": "nixvim",
"nur": "nur",
"stylix": "stylix"
"stylix": "stylix",
"unixpkgs": "unixpkgs"
}
},
"scss-reset": {
@ -742,8 +771,10 @@
"base16-fish": "base16-fish",
"base16-helix": "base16-helix",
"base16-vim": "base16-vim",
"firefox-gnome-theme": "firefox-gnome-theme",
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_3",
"git-hooks": "git-hooks_2",
"gnome-shell": "gnome-shell",
"home-manager": "home-manager_3",
"nixpkgs": [
@ -755,11 +786,11 @@
"tinted-tmux": "tinted-tmux"
},
"locked": {
"lastModified": 1734110444,
"narHash": "sha256-fp1iV2JldCSvz+7ODzXYUkQ+H7zyiWw5E0MQ4ILC4vw=",
"lastModified": 1743892916,
"narHash": "sha256-RWvfosAHobUiGMhWIS915WF4TsrQYDXv1gJk59TLAdU=",
"owner": "danth",
"repo": "stylix",
"rev": "9015d5d0d5d100f849129c43d257b827d300b089",
"rev": "aebfec1998ebbc087de0104e4a4cec99ec1e3f7a",
"type": "github"
},
"original": {
@ -851,11 +882,11 @@
"tinted-tmux": {
"flake": false,
"locked": {
"lastModified": 1729501581,
"narHash": "sha256-1ohEFMC23elnl39kxWnjzH1l2DFWWx4DhFNNYDTYt54=",
"lastModified": 1743296873,
"narHash": "sha256-8IQulrb1OBSxMwdKijO9fB70ON//V32dpK9Uioy7FzY=",
"owner": "tinted-theming",
"repo": "tinted-tmux",
"rev": "f0e7f7974a6441033eb0a172a0342e96722b4f14",
"rev": "af5152c8d7546dfb4ff6df94080bf5ff54f64e3a",
"type": "github"
},
"original": {
@ -872,11 +903,11 @@
]
},
"locked": {
"lastModified": 1734704479,
"narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=",
"lastModified": 1743748085,
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f",
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d",
"type": "github"
},
"original": {
@ -905,6 +936,21 @@
"repo": "treefmt-nix",
"type": "github"
}
},
"unixpkgs": {
"locked": {
"lastModified": 1743942655,
"narHash": "sha256-EtFQJXP5L2S8IgGx/AsCACYdzwf0sGiriyxN1BH2f6Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6e0ed24a84eeaf3f5b94bd35e2c2d2de5ea8fede",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "master",
"type": "indirect"
}
}
},
"root": "root",

View file

@ -4,8 +4,7 @@
inputs = {
# Packages
nixpkgs.url = "nixpkgs/nixos-24.11";
jjuinixpkgs.url = "github:Adda0/nixpkgs/jjui"; # Testing PR
labellenixpkgs.url = "github:FabianRig/nixpkgs/update-labelle-1.3.2"; # Current 24.11 version doesn't match dependencies
unixpkgs.url = "nixpkgs/master";
# OS
disko = {
url = "disko";
@ -40,6 +39,7 @@
{
self,
nixpkgs,
unixpkgs,
disko,
nix-on-droid,
flake-utils,
@ -56,6 +56,19 @@
overlays = [
(import ./common/update-local-flakes/overlay.nix)
nur.overlays.default
(
# Cherry-pick packages from future
self: super:
let
upkgs = import unixpkgs { inherit (super) system; };
in
{
jjui = upkgs.jjui;
labelle = upkgs.labelle;
orca-slicer = upkgs.orca-slicer; # Not prebuilt in 24.11 for some reason
nextcloud-client = upkgs.nextcloud-client; # Need https://github.com/nextcloud/desktop/pull/7714
}
)
];
};
homeManagerConfig = {
@ -70,7 +83,11 @@
}:
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = attrs;
specialArgs = attrs // {
upkgs = import unixpkgs {
inherit system;
};
};
modules = modules ++ [
self.nixosModules.dotfiles
# nur.modules.nixos.default
@ -116,6 +133,7 @@
# We don't do an overlay for the whole system because lix is not binary compatible.
overlays = [
(self: super: { nix = super.lix; })
(import ./common/update-local-flakes/overlay.nix)
];
}
);
@ -153,6 +171,7 @@
runtimeInputs = with pkgs; [
nix-output-monitor
nixos-rebuild
jq
];
text = builtins.readFile ./os/rebuild.sh;
}
@ -193,9 +212,9 @@
};
nixOnDroidConfigurations.sprinkles = lib.nixOnDroidConfiguration { };
# Fake systems
nixosConfigurations.abavorana = lib.nixosSystem {
nixosConfigurations.morton = lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./abavorana/standin.nix ];
modules = [ ./morton/standin.nix ];
};
nixosConfigurations.sprinkles = lib.nixosSystem {
system = "aarch64-linux";

View file

@ -1,5 +1,4 @@
{
pkgs,
config,
lib,
...
@ -28,7 +27,8 @@ let
"calendar.registry.${id}.cache.enabled" = thunderbird.offlineSupport; # TODO Check this actually corresponds
"calendar.registry.${id}.color" = thunderbird.color;
"calendar.registry.${id}.forceEmailScheduling" = thunderbird.clientSideEmailScheduling;
"calendar.registry.${id}.imip.identity.key" = "id_${builtins.hashString "sha256" thunderbird.email}";
"calendar.registry.${id}.imip.identity.key" =
"id_${builtins.hashString "sha256" thunderbird.email}";
"calendar.registry.${id}.name" = account.name;
"calendar.registry.${id}.readOnly" = thunderbird.readOnly;
"calendar.registry.${id}.refreshInterval" = builtins.toString thunderbird.refreshInterval;
@ -79,6 +79,10 @@ in
};
};
};
imports = [
./netrc.nix
./nextcloud.nix
];
# UPST Thunderbird-specific options (should be named so), to be included in HM Thunderbird module
options = {
frogeye.accounts.calendar.accounts = lib.mkOption {

83
hm/accounts/netrc.nix Normal file
View file

@ -0,0 +1,83 @@
{
lib,
config,
...
}:
# Does not implement everything .netrc allows
# (starting with: changing the .netrc position)
# but neither do clients anyways.
let
cfg = config.frogeye.netrc;
in
{
config = {
frogeye.passwordFiles = [
{
path = "${config.home.homeDirectory}/.netrc";
text = lib.trivial.pipe cfg [
builtins.attrValues
(builtins.map (
n: "machine ${n.machine} login @${n.machine}_login@ password @${n.machine}_password@"
))
lib.strings.concatLines
];
passwords = lib.trivial.pipe cfg [
builtins.attrValues
(builtins.map (n: [
{
name = "@${n.machine}_login@";
value = {
inherit (n) path;
selector = n.login;
};
}
{
name = "@${n.machine}_password@";
value = {
inherit (n) path;
selector = n.password;
};
}
]))
lib.lists.flatten
builtins.listToAttrs
];
}
];
};
options = {
frogeye.netrc = lib.mkOption {
default = { };
description = "Entries to add to .netrc";
type = lib.types.attrsOf (
lib.types.submodule (
{ config, name, ... }:
{
options = {
machine = lib.mkOption {
type = lib.types.str;
default = name;
readOnly = true;
internal = true;
};
login = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "email";
description = "Password selector that will be used as the login field";
};
password = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Password selector that will be used as the password field";
};
path = lib.mkOption {
type = lib.types.str;
description = "Path to the password store entry";
};
};
}
)
);
};
};
}

109
hm/accounts/nextcloud.nix Normal file
View file

@ -0,0 +1,109 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.frogeye.nextcloud;
in
{
config = {
systemd.user = lib.mkIf (builtins.length cfg > 0) {
services.nextcloud_sync = {
Service = {
Type = "oneshot";
ExecStart = lib.getExe (
pkgs.writeShellApplication {
name = "nextcloud_sync";
runtimeInputs = with pkgs; [
nextcloud-client
];
text =
''
sync() {
url="$1"
remote="$2"
local="$3"
echo "Syncing $url $remote to $local"
# Adding to Syncthing ignores (so it's not double-synced)
dir="$local"
while [ "$dir" != "/" ]
do
if [ -d "$dir/.stfolder" ]
then
if [ ! -f "$dir/.stignore" ]
then
touch "$dir/.stignore"
fi
rule="''${local#"$dir/"}/**"
if ! grep -qFx "$rule" "$dir/.stignore"
then
echo "$rule" >> "$dir/.stignore"
fi
fi
dir="$(dirname "$dir")"
done
# Actual syncing
mkdir -p "$local"
nextcloudcmd -n -h --path "$remote" "$local" "$url"
}
''
+ (lib.trivial.pipe cfg [
(builtins.map (n: ''
sync ${
lib.strings.escapeShellArgs [
n.url
n.remote
"${config.home.homeDirectory}/${n.local}"
]
}
''))
lib.strings.concatLines
]);
}
);
};
};
timers.nextcloud_sync = {
Timer = {
OnCalendar = "*:1/5";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
};
};
options = {
frogeye.nextcloud = lib.mkOption {
default = [ ];
description = "Sync Nextcloud folders. Uses netrc for authentication";
type = lib.types.listOf (
lib.types.submodule (
{ config, ... }:
{
options = {
url = lib.mkOption {
type = lib.types.str;
description = "URL of the Nextcloud instance";
};
local = lib.mkOption {
type = lib.types.str;
description = "Local path (relative to home) to sync";
};
remote = lib.mkOption {
type = lib.types.str;
description = "Remote path to sync";
default = "/";
};
};
}
)
);
};
};
}

View file

@ -2,12 +2,8 @@
pkgs,
config,
lib,
labellenixpkgs,
...
}:
let
labellepkgs = import labellenixpkgs { inherit (pkgs) system; };
in
{
frogeye.hooks.lock = ''
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
@ -179,19 +175,10 @@ in
translate-shell.enable = true; # TODO Cool config?
};
home = {
activation = {
# Prevent Virtualbox from creating a "VirtualBox VMs" folder in $HOME
setVirtualboxSettings = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if command -v VBoxManage > /dev/null
then
VBoxManage setproperty machinefolder ${config.xdg.dataHome}/virtualbox
fi
'';
};
stateVersion = "24.05";
stateVersion = "24.11";
packages = with pkgs; [
# Terminal utils
coreutils
coreutils-full
moreutils
rename
which
@ -233,13 +220,14 @@ in
# toolbox
imagemagick
numbat
bc
# hardware
pciutils
usbutils
dmidecode
lshw
labellepkgs.labelle # Label printer
labelle # Label printer
# Locker
(pkgs.writeShellApplication {

View file

@ -1,7 +1,6 @@
{
pkgs,
config,
lib,
...
}:
{

View file

@ -5,6 +5,9 @@
...
}:
{
imports = [
./homepage.nix
];
config = lib.mkIf config.frogeye.desktop.xorg {
home.sessionVariables = {
BROWSER = "qutebrowser";
@ -21,7 +24,6 @@
profiles.hm = {
extensions = with pkgs.nur.repos.rycee.firefox-addons; [
(buildFirefoxXpiAddon {
pname = "onetab";
version = "0.1.0";
addonId = "onetab@nated";
@ -73,7 +75,6 @@
force = true;
};
settings = {
"browser.startup.homepage" = "https://geoffrey.frogeye.fr/home.php";
"signon.rememberSignons" = false; # Don't save passwords
"browser.newtabpage.enabled" = false; # Best would be homepage but not possible without extension?
# Europe please
@ -146,17 +147,13 @@
show = "never";
tabs_are_windows = true;
};
url = rec {
open_base_url = true;
start_pages = lib.mkDefault "https://geoffrey.frogeye.fr/blank.html";
default_page = start_pages;
};
url.open_base_url = true;
content = {
# I had this setting below, not sure if it did something special
# config.set("content.cookies.accept", "no-3rdparty", "chrome://*/*")
cookies.accept = "no-3rdparty";
prefers_reduced_motion = true;
headers.accept_language = "fr-FR, fr;q=0.9, en-GB;q=0.8, en-US;q=0.7, en;q=0.6";
headers.accept_language = "en-GB,en;q=0.9";
tls.certificate_errors = "ask-block-thirdparty";
javascript.clipboard = "access"; # copy-paste is fine
};
@ -187,7 +184,8 @@
};
};
xsession.windowManager.i3.config.keybindings = {
"${config.xsession.windowManager.i3.config.modifier}+m" = "exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore";
"${config.xsession.windowManager.i3.config.modifier}+m" =
"exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore";
};
};
}

View file

@ -0,0 +1,86 @@
html {
background-image: linear-gradient(#e6f0a3 0%, #d2e638 50%, #c3d825 51%, #dbf043 100%);
min-height: 100%;
}
body {
font: 20px Helvetica, sans-serif;
padding: 2.5% 0;
}
article {
margin: 0 auto;
max-width: 95%;
}
h1, h2 {
display: none;
}
nav a {
background: rgba(255, 255, 255, 0.8);
width: 110px;
height: 100px;
display: inline-block;
padding: 15px 0;
margin: 0px 5px 10px;
border: 1px solid #ddd;
border-radius: 5px;
text-align: center;
vertical-align: top;
position: relative;
}
@media only screen and (min-width: 768px) {
nav {
margin-left: 110px;
position: relative;
}
nav .main {
position: absolute;
left: -130px;
}
}
nav img {
margin: auto;
max-width: 90%;
max-height: 70%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
a {
text-decoration: none;
color: inherit;
}
nav a:hover {
background: rgba(240, 240, 240, 0.8);
}
nav a:active {
background: rgba(220, 220, 220, 0.8);
}
nav a>.fa, nav a>.fa-stack{
width: 100%;
margin-top: .25em;
margin-bottom: .35em;
font-size: 32px;
}
nav a span {
display: block;
margin-top: .55em;
font-weight: 400;
line-height: 1.1;
}

View file

@ -0,0 +1,32 @@
<!doctype html>
<html lang="fr">
<head>
<title>Homepage</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width;minimum-scale=0.5,maximum-scale=1.0; user-scalable=1;" />
<link rel="stylesheet" type="text/css" href="{{css}}"/>
<link rel="stylesheet" type="text/css" href="{{fa_css}}"/>
</head>
<body>
<article>
<h1>Homepage</h1>
{{#sections}}
<h2>{{title}}</h2>
<nav style="color: {{color}};">
{{#image}}
<a href="{{url}}" class="main">
<img alt="Logo for {{title}}" src="{{image}}" />
</a>
{{/image}}
{{#links}}
<a href="{{url}}">
<i class="fa fa-{{icon}}" aria-label="Icon for {{name}} ({{icon}})"></i>
<span>{{name}}</span>
</a>
{{/links}}
</nav>
{{/sections}}
</article>
</body>
</html>

View file

@ -0,0 +1,110 @@
{
pkgs,
lib,
config,
...
}:
let
# TODO ForkAwesome is deprecated, find something else
fa = pkgs.fetchFromGitHub {
owner = "ForkAwesome";
repo = "Fork-Awesome";
rev = "1.2.0";
sha256 = "sha256-zG6/0dWjU7/y/oDZuSEv+54Mchng64LVyV8bluskYzc=";
};
data = config.frogeye.homepage // {
sections = builtins.attrValues config.frogeye.homepage.sections;
css = ./homepage.css;
fa_css = "${fa}/css/fork-awesome.min.css";
};
# Blatantly stolen from https://pablo.tools/blog/computers/nix-mustache-templates/
homepage = builtins.toString (
pkgs.stdenv.mkDerivation {
name = "homepage.html";
nativeBuildInpts = [ pkgs.mustache-go ];
passAsFile = [ "jsonData" ];
jsonData = builtins.toJSON data;
phases = [
"buildPhase"
"installPhase"
];
buildPhase = ''
${pkgs.mustache-go}/bin/mustache $jsonDataPath ${./homepage.html.mustache} > homepage.html
'';
installPhase = ''
cp homepage.html $out
'';
}
);
in
{
config.programs = {
firefox.profiles.hm.settings."browser.startup.homepage" = homepage;
qutebrowser.settings.url = {
start_pages = homepage;
default_page = homepage;
};
};
options.frogeye.homepage = {
sections = lib.mkOption {
default = { };
description = "Folders used by users";
# Top-level so Syncthing can work for all users. Also there's no real home-manager syncthing module.
type = lib.types.attrsOf (
lib.types.submodule (
{ config, name, ... }:
{
options = {
title = lib.mkOption {
type = lib.types.str;
default = "Section title";
};
color = lib.mkOption {
type = lib.types.str;
default = "#337ab7";
};
image = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
url = lib.mkOption {
type = lib.types.str;
default = "about:blank";
};
links = lib.mkOption {
default = [ ];
type = lib.types.listOf (
lib.types.submodule (
{ config, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = "Link";
};
url = lib.mkOption {
type = lib.types.str;
default = "about:blank";
};
icon = lib.mkOption {
type = lib.types.str;
default = "question-circle";
};
};
}
)
);
};
};
}
)
);
};
};
}

View file

@ -113,19 +113,12 @@
dunst = {
enable = true;
settings =
# TODO Change dmenu for rofi, so we can use context
with config.lib.stylix.colors.withHashtag; {
global = {
separator_color = lib.mkForce base05;
idle_threshold = 120;
markup = "full";
max_icon_size = 48;
# TODO Those shortcuts don't seem to work, maybe try:
# > define shortcuts inside your window manager and bind them to dunstctl(1) commands
close_all = "ctrl+mod4+n";
close = "mod4+n";
context = "mod1+mod4+n";
history = "shift+mod4+n";
};
urgency_low = {
@ -145,6 +138,7 @@
};
};
};
keynav.enable = true;
};
home = {
@ -178,13 +172,14 @@
zathura
meld
python3Packages.magic
bluetuith
# x11-exclusive
simplescreenrecorder
trayer
xclip
keynav
xorg.xinit
scrot
];
sessionVariables = {
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs

File diff suppressed because it is too large Load diff

View file

@ -22,17 +22,13 @@ in
# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs.
pkgs.python3Packages.buildPythonApplication rec {
pname = "frobar";
version = "2.0";
version = "3.0";
propagatedBuildInputs = with pkgs.python3Packages; [
coloredlogs # old only
i3ipc
mpd2
notmuch
psutil
pulsectl # old only
pulsectl-asyncio
pyinotify
pygobject3
rich
];
nativeBuildInputs =
@ -41,7 +37,15 @@ pkgs.python3Packages.buildPythonApplication rec {
wirelesstools
playerctl
]);
makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}" ];
makeWrapperArgs = [
"--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}"
"--prefix GI_TYPELIB_PATH : ${GI_TYPELIB_PATH}"
];
GI_TYPELIB_PATH = pkgs.lib.makeSearchPath "lib/girepository-1.0" [
pkgs.glib.out
pkgs.playerctl
];
src = ./.;
}

View file

@ -1,77 +1,146 @@
#!/usr/bin/env python3
import rich.color
import rich.logging
import rich.terminal_theme
from frobar import providers as fp
from frobar.display import Bar, BarGroupType
from frobar.updaters import Updater
# TODO If multiple screen, expand the sections and share them
# TODO Graceful exit
import frobar.common
import frobar.providers
from frobar.common import Alignment
def run() -> None:
Bar.init()
Updater.init()
def main() -> None:
# TODO Configurable
FROGARIZED = [
"#092c0e",
"#143718",
"#5a7058",
"#677d64",
"#89947f",
"#99a08d",
"#fae2e3",
"#fff0f1",
"#e0332e",
"#cf4b15",
"#bb8801",
"#8d9800",
"#1fa198",
"#008dd1",
"#5c73c4",
"#d43982",
]
# TODO Not super happy with the color management,
# while using an existing library is great, it's limited to ANSI colors
# Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT)
# Bar.addSectionAll(fp.NetworkProvider(theme=2), BarGroupType.RIGHT)
def base16_color(color: int) -> tuple[int, int, int]:
hexa = FROGARIZED[color]
return tuple(rich.color.parse_rgb_hex(hexa[1:]))
WORKSPACE_THEME = 8
FOCUS_THEME = 2
URGENT_THEME = 0
CUSTOM_SUFFIXES = "▲■"
customNames = dict()
for i in range(len(CUSTOM_SUFFIXES)):
short = str(i + 1)
full = short + " " + CUSTOM_SUFFIXES[i]
customNames[short] = full
Bar.addSectionAll(
fp.I3WorkspacesProvider(
theme=WORKSPACE_THEME,
themeFocus=FOCUS_THEME,
themeUrgent=URGENT_THEME,
themeMode=URGENT_THEME,
customNames=customNames,
),
BarGroupType.LEFT,
theme = rich.terminal_theme.TerminalTheme(
base16_color(0x0),
base16_color(0x0), # TODO should be 7, currently 0 so it's compatible with v2
[
base16_color(0x0), # black
base16_color(0x8), # red
base16_color(0xB), # green
base16_color(0xA), # yellow
base16_color(0xD), # blue
base16_color(0xE), # magenta
base16_color(0xC), # cyan
base16_color(0x5), # white
],
[
base16_color(0x3), # bright black
base16_color(0x8), # bright red
base16_color(0xB), # bright green
base16_color(0xA), # bright yellow
base16_color(0xD), # bright blue
base16_color(0xE), # bright magenta
base16_color(0xC), # bright cyan
base16_color(0x7), # bright white
],
)
# TODO Middle
Bar.addSectionAll(fp.MprisProvider(theme=9), BarGroupType.LEFT)
# Bar.addSectionAll(fp.MpdProvider(theme=9), BarGroupType.LEFT)
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
bar = frobar.common.Bar(theme=theme)
dualScreen = len(bar.children) > 1
leftPreferred = 0 if dualScreen else None
rightPreferred = 1 if dualScreen else None
# TODO Computer modes
workspaces_suffixes = "▲■"
workspaces_names = dict(
(str(i + 1), f"{i+1} {c}") for i, c in enumerate(workspaces_suffixes)
)
Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(fp.LoadProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(fp.RamProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(fp.TemperatureProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(fp.BatteryProvider(), BarGroupType.RIGHT)
color = rich.color.Color.parse
# Peripherals
PERIPHERAL_THEME = 6
NETWORK_THEME = 5
# TODO Disk space provider
# TODO Screen (connected, autorandr configuration, bbswitch) provider
Bar.addSectionAll(fp.XautolockProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
Bar.addSectionAll(fp.PulseaudioProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
Bar.addSectionAll(fp.RfkillProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
Bar.addSectionAll(fp.NetworkProvider(theme=NETWORK_THEME), BarGroupType.RIGHT)
bar.addProvider(
frobar.providers.I3ModeProvider(color=color("red")), alignment=Alignment.LEFT
)
bar.addProvider(
frobar.providers.I3WorkspacesProvider(custom_names=workspaces_names),
alignment=Alignment.LEFT,
)
# Personal
# PERSONAL_THEME = 7
# Bar.addSectionAll(fp.KeystoreProvider(theme=PERSONAL_THEME), BarGroupType.RIGHT)
# Bar.addSectionAll(
# fp.NotmuchUnreadProvider(dir="~/.mail/", theme=PERSONAL_THEME),
# BarGroupType.RIGHT,
# )
# Bar.addSectionAll(
# fp.TodoProvider(dir="~/.vdirsyncer/currentCalendars/", theme=PERSONAL_THEME),
# BarGroupType.RIGHT,
# )
if dualScreen:
bar.addProvider(
frobar.providers.I3WindowTitleProvider(color=color("white")),
screenNum=0,
alignment=Alignment.CENTER,
)
bar.addProvider(
frobar.providers.MprisProvider(color=color("bright_white")),
screenNum=rightPreferred,
alignment=Alignment.CENTER,
)
else:
bar.addProvider(
frobar.common.SpacerProvider(),
alignment=Alignment.LEFT,
)
bar.addProvider(
frobar.providers.MprisProvider(color=color("bright_white")),
alignment=Alignment.LEFT,
)
TIME_THEME = 4
Bar.addSectionAll(fp.TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
bar.addProvider(
frobar.providers.CpuProvider(),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.LoadProvider(),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.RamProvider(),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.TemperatureProvider(),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.BatteryProvider(),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.PulseaudioProvider(color=color("magenta")),
screenNum=rightPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.NetworkProvider(color=color("blue")),
screenNum=leftPreferred,
alignment=Alignment.RIGHT,
)
bar.addProvider(
frobar.providers.TimeProvider(color=color("cyan")), alignment=Alignment.RIGHT
)
# Bar.run()
bar.launch()
if __name__ == "__main__":
main()

View file

@ -1,5 +1,629 @@
#!/usr/bin/env python3
import asyncio
import collections
import datetime
import enum
import logging
import signal
import typing
import threading
import gi
import gi.events
import gi.repository.GLib
import i3ipc
import i3ipc.aio
import rich.color
import rich.logging
import rich.terminal_theme
notBusy = threading.Event()
logging.basicConfig(
level="DEBUG",
format="%(message)s",
datefmt="[%X]",
handlers=[rich.logging.RichHandler()],
)
log = logging.getLogger("frobar")
T = typing.TypeVar("T", bound="ComposableText")
P = typing.TypeVar("P", bound="ComposableText")
C = typing.TypeVar("C", bound="ComposableText")
Sortable = str | int
# Display utilities
def humanSize(numi: int) -> str:
"""
Returns a string of width 3+3
"""
num = float(numi)
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
if abs(num) < 1000:
if num >= 10:
return f"{int(num):3d}{unit}"
else:
return f"{num:.1f}{unit}"
num /= 1024
return f"{numi:d}YiB"
def ramp(p: float, states: str = " ▁▂▃▄▅▆▇█") -> str:
if p < 0:
return ""
d, m = divmod(p, 1.0)
text = states[-1] * int(d)
if m > 0:
text += states[round(m * (len(states) - 1))]
return text
def clip(text: str, length: int = 30) -> str:
if len(text) > length:
text = text[: length - 1] + ""
return text
class ComposableText(typing.Generic[P, C]):
def __init__(
self,
parent: typing.Optional[P] = None,
sortKey: Sortable = 0,
) -> None:
self.parent: typing.Optional[P] = None
self.children: typing.MutableSequence[C] = list()
self.sortKey = sortKey
if parent:
self.setParent(parent)
self.bar = self.getFirstParentOfType(Bar)
def setParent(self, parent: P) -> None:
assert self.parent is None
parent.children.append(self)
assert isinstance(parent.children, list)
parent.children.sort(key=lambda c: c.sortKey)
self.parent = parent
self.parent.updateMarkup()
def unsetParent(self) -> None:
assert self.parent
self.parent.children.remove(self)
self.parent.updateMarkup()
self.parent = None
def getFirstParentOfType(self, typ: typing.Type[T]) -> T:
parent = self
while not isinstance(parent, typ):
assert parent.parent, f"{self} doesn't have a parent of {typ}"
parent = parent.parent
return parent
def updateMarkup(self) -> None:
self.bar.refresh.set()
# TODO OPTI See if worth caching the output
def generateMarkup(self) -> str:
raise NotImplementedError(f"{self} cannot generate markup")
def getMarkup(self) -> str:
return self.generateMarkup()
class Button(enum.Enum):
CLICK_LEFT = "1"
CLICK_MIDDLE = "2"
CLICK_RIGHT = "3"
SCROLL_UP = "4"
SCROLL_DOWN = "5"
class Section(ComposableText):
"""
Colorable block separated by chevrons
"""
def __init__(
self,
parent: "Module",
sortKey: Sortable = 0,
color: rich.color.Color = rich.color.Color.default(),
) -> None:
super().__init__(parent=parent, sortKey=sortKey)
self.parent: "Module"
self.color = color
self.desiredText: str | None = None
self.text = ""
self.targetSize = -1
self.size = -1
self.animationTask: asyncio.Task | None = None
self.actions: dict[Button, str] = dict()
def isHidden(self) -> bool:
return self.size < 0
# Geometric series, with a cap
ANIM_A = 0.025
ANIM_R = 0.9
ANIM_MIN = 0.001
async def animate(self) -> None:
increment = 1 if self.size < self.targetSize else -1
loop = asyncio.get_running_loop()
frameTime = loop.time()
animTime = self.ANIM_A
skipped = 0
while self.size != self.targetSize:
self.size += increment
self.updateMarkup()
animTime *= self.ANIM_R
animTime = max(self.ANIM_MIN, animTime)
frameTime += animTime
sleepTime = frameTime - loop.time()
# In case of stress, skip refreshing by not awaiting
if sleepTime > 0:
if skipped > 0:
log.warning(f"Skipped {skipped} animation frame(s)")
skipped = 0
await asyncio.sleep(sleepTime)
else:
skipped += 1
def setText(self, text: str | None) -> None:
# OPTI Don't redraw nor reset animation if setting the same text
if self.desiredText == text:
return
self.desiredText = text
if text is None:
self.text = ""
self.targetSize = -1
else:
self.text = f" {text} "
self.targetSize = len(self.text)
if self.animationTask:
self.animationTask.cancel()
# OPTI Skip the whole animation task if not required
if self.size == self.targetSize:
self.updateMarkup()
else:
self.animationTask = self.bar.taskGroup.create_task(self.animate())
def setAction(self, button: Button, callback: typing.Callable | None) -> None:
if button in self.actions:
command = self.actions[button]
self.bar.removeAction(command)
del self.actions[button]
if callback:
command = self.bar.addAction(callback)
self.actions[button] = command
def generateMarkup(self) -> str:
assert not self.isHidden()
pad = max(0, self.size - len(self.text))
text = self.text[: self.size] + " " * pad
for button, command in self.actions.items():
text = "%{A" + button.value + ":" + command + ":}" + text + "%{A}"
return text
class Module(ComposableText):
"""
Sections handled by a same updater
"""
def __init__(self, parent: "Side") -> None:
super().__init__(parent=parent)
self.parent: "Side"
self.children: typing.MutableSequence[Section]
self.mirroring: Module | None = None
self.mirrors: list[Module] = list()
def mirror(self, module: "Module") -> None:
self.mirroring = module
module.mirrors.append(self)
def getSections(self) -> typing.Sequence[Section]:
if self.mirroring:
return self.mirroring.children
else:
return self.children
def updateMarkup(self) -> None:
super().updateMarkup()
for mirror in self.mirrors:
mirror.updateMarkup()
class Alignment(enum.Enum):
LEFT = "l"
RIGHT = "r"
CENTER = "c"
class Side(ComposableText):
def __init__(self, parent: "Screen", alignment: Alignment) -> None:
super().__init__(parent=parent)
self.parent: Screen
self.children: typing.MutableSequence[Module] = []
self.alignment = alignment
self.bar = parent.getFirstParentOfType(Bar)
def generateMarkup(self) -> str:
if not self.children:
return ""
text = "%{" + self.alignment.value + "}"
lastSection: Section | None = None
for module in self.children:
for section in module.getSections():
if section.isHidden():
continue
hexa = section.color.get_truecolor(theme=self.bar.theme).hex
if lastSection is None:
if self.alignment == Alignment.LEFT:
text += "%{B" + hexa + "}%{F-}"
else:
text += "%{B-}%{F" + hexa + "}%{R}%{F-}"
elif isinstance(lastSection, SpacerSection):
text += "%{B-}%{F" + hexa + "}%{R}%{F-}"
else:
if self.alignment == Alignment.RIGHT:
if lastSection.color == section.color:
text += ""
else:
text += "%{F" + hexa + "}%{R}"
else:
if lastSection.color == section.color:
text += ""
else:
text += "%{R}%{B" + hexa + "}"
text += "%{F-}"
text += section.getMarkup()
lastSection = section
if self.alignment != Alignment.RIGHT and lastSection:
text += "%{R}%{B-}"
return text
class Screen(ComposableText):
def __init__(self, parent: "Bar", output: str) -> None:
super().__init__(parent=parent)
self.parent: "Bar"
self.children: typing.MutableSequence[Side]
self.output = output
for alignment in Alignment:
Side(parent=self, alignment=alignment)
def generateMarkup(self) -> str:
return ("%{Sn" + self.output + "}") + "".join(
side.getMarkup() for side in self.children
)
class Bar(ComposableText):
"""
Top-level
"""
def __init__(
self,
theme: rich.terminal_theme.TerminalTheme = rich.terminal_theme.DEFAULT_TERMINAL_THEME,
) -> None:
super().__init__()
self.parent: None
self.children: typing.MutableSequence[Screen]
self.longRunningTasks: list[asyncio.Task] = list()
self.theme = theme
self.refresh = asyncio.Event()
self.taskGroup = asyncio.TaskGroup()
self.providers: list["Provider"] = list()
self.actionIndex = 0
self.actions: dict[str, typing.Callable] = dict()
self.periodicProviderTask: typing.Coroutine | None = None
i3 = i3ipc.Connection()
outputs = i3.get_outputs()
outputs.sort(key=lambda output: output.rect.x)
for output in outputs:
if not output.active:
continue
Screen(parent=self, output=output.name)
def addLongRunningTask(self, coro: typing.Coroutine) -> None:
task = self.taskGroup.create_task(coro)
self.longRunningTasks.append(task)
async def run(self) -> None:
cmd = [
"lemonbar",
"-b",
"-a",
"64",
"-f",
"DejaVuSansM Nerd Font:size=10",
"-F",
self.theme.foreground_color.hex,
"-B",
self.theme.background_color.hex,
]
proc = await asyncio.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
)
async def refresher() -> None:
assert proc.stdin
while True:
await self.refresh.wait()
self.refresh.clear()
markup = self.getMarkup()
proc.stdin.write(markup.encode())
async def actionHandler() -> None:
assert proc.stdout
while True:
line = await proc.stdout.readline()
command = line.decode().strip()
callback = self.actions.get(command)
if callback is None:
# In some conditions on start it's empty
log.error(f"Unknown command: {command}")
return
callback()
async with self.taskGroup:
self.addLongRunningTask(refresher())
self.addLongRunningTask(actionHandler())
for provider in self.providers:
self.addLongRunningTask(provider.run())
def exit() -> None:
log.info("Terminating")
for task in self.longRunningTasks:
task.cancel()
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, exit)
def generateMarkup(self) -> str:
return "".join(screen.getMarkup() for screen in self.children) + "\n"
def addProvider(
self,
provider: "Provider",
alignment: Alignment = Alignment.LEFT,
screenNum: int | None = None,
) -> None:
"""
screenNum: the provider will be added on this screen if set, all otherwise
"""
modules = list()
for s, screen in enumerate(self.children):
if screenNum is None or s == screenNum:
side = next(filter(lambda s: s.alignment == alignment, screen.children))
module = Module(parent=side)
modules.append(module)
provider.modules = modules
if modules:
self.providers.append(provider)
def addAction(self, callback: typing.Callable) -> str:
command = f"{self.actionIndex:x}"
self.actions[command] = callback
self.actionIndex += 1
return command
def removeAction(self, command: str) -> None:
del self.actions[command]
def launch(self) -> None:
# Using GLib's event loop so we can run GLib's code
policy = gi.events.GLibEventLoopPolicy()
asyncio.set_event_loop_policy(policy)
loop = policy.get_event_loop()
loop.run_until_complete(self.run())
class Provider:
sectionType: type[Section] = Section
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
self.modules: list[Module] = list()
self.color = color
async def run(self) -> None:
# Not a NotImplementedError, otherwise can't combine all classes
pass
class MirrorProvider(Provider):
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
super().__init__(color=color)
self.module: Module
async def run(self) -> None:
await super().run()
self.module = self.modules[0]
for module in self.modules[1:]:
module.mirror(self.module)
class SingleSectionProvider(MirrorProvider):
async def run(self) -> None:
await super().run()
self.section = self.sectionType(parent=self.module, color=self.color)
class StaticProvider(SingleSectionProvider):
def __init__(
self, text: str, color: rich.color.Color = rich.color.Color.default()
) -> None:
super().__init__(color=color)
self.text = text
async def run(self) -> None:
await super().run()
self.section.setText(self.text)
class SpacerSection(Section):
pass
class SpacerProvider(SingleSectionProvider):
sectionType = SpacerSection
def __init__(self, length: int = 5) -> None:
super().__init__(color=rich.color.Color.default())
self.length = length
async def run(self) -> None:
await super().run()
assert isinstance(self.section, SpacerSection)
self.section.setText(" " * self.length)
class StatefulSection(Section):
def __init__(
self,
parent: Module,
sortKey: Sortable = 0,
color: rich.color.Color = rich.color.Color.default(),
) -> None:
super().__init__(parent=parent, sortKey=sortKey, color=color)
self.state = 0
self.numberStates: int
self.setAction(Button.CLICK_LEFT, self.incrementState)
self.setAction(Button.CLICK_RIGHT, self.decrementState)
def incrementState(self) -> None:
self.state += 1
self.changeState()
def decrementState(self) -> None:
self.state -= 1
self.changeState()
def setChangedState(self, callback: typing.Callable) -> None:
self.callback = callback
def changeState(self) -> None:
self.state %= self.numberStates
self.bar.taskGroup.create_task(self.callback())
class StatefulSectionProvider(Provider):
sectionType = StatefulSection
class SingleStatefulSectionProvider(StatefulSectionProvider, SingleSectionProvider):
section: StatefulSection
class MultiSectionsProvider(Provider):
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
super().__init__(color=color)
self.sectionKeys: dict[Module, dict[Sortable, Section]] = (
collections.defaultdict(dict)
)
self.updaters: dict[Section, typing.Callable] = dict()
async def getSectionUpdater(self, section: Section) -> typing.Callable:
raise NotImplementedError()
@staticmethod
async def doNothing() -> None:
pass
async def updateSections(self, sections: set[Sortable], module: Module) -> None:
moduleSections = self.sectionKeys[module]
async with asyncio.TaskGroup() as tg:
for sortKey in sections:
section = moduleSections.get(sortKey)
if not section:
section = self.sectionType(
parent=module, sortKey=sortKey, color=self.color
)
self.updaters[section] = await self.getSectionUpdater(section)
moduleSections[sortKey] = section
updater = self.updaters[section]
tg.create_task(updater())
missingKeys = set(moduleSections.keys()) - sections
for missingKey in missingKeys:
section = moduleSections.get(missingKey)
assert section
section.setText(None)
class PeriodicProvider(Provider):
async def init(self) -> None:
pass
async def loop(self) -> None:
raise NotImplementedError()
@classmethod
async def task(cls, bar: Bar) -> None:
providers = list()
for provider in bar.providers:
if isinstance(provider, PeriodicProvider):
providers.append(provider)
await provider.init()
while True:
# TODO Block bar update during the periodic update of the loops
loops = [provider.loop() for provider in providers]
asyncio.gather(*loops)
now = datetime.datetime.now()
# Hardcoded to 1 second... not sure if we want some more than that,
# and if the logic to check if a task should run would be a win
# compared to the task itself
remaining = 1 - now.microsecond / 1000000
await asyncio.sleep(remaining)
async def run(self) -> None:
await super().run()
for module in self.modules:
bar = module.getFirstParentOfType(Bar)
assert bar
if not bar.periodicProviderTask:
bar.periodicProviderTask = PeriodicProvider.task(bar)
bar.addLongRunningTask(bar.periodicProviderTask)
class PeriodicStatefulProvider(SingleStatefulSectionProvider, PeriodicProvider):
async def run(self) -> None:
await super().run()
self.section.setChangedState(self.loop)
class AlertingProvider(Provider):
COLOR_NORMAL = rich.color.Color.parse("green")
COLOR_WARNING = rich.color.Color.parse("yellow")
COLOR_DANGER = rich.color.Color.parse("red")
warningThreshold: float
dangerThreshold: float
def updateLevel(self, level: float) -> None:
if level > self.dangerThreshold:
color = self.COLOR_DANGER
elif level > self.warningThreshold:
color = self.COLOR_WARNING
else:
color = self.COLOR_NORMAL
for module in self.modules:
for section in module.getSections():
section.color = color

View file

@ -1,756 +0,0 @@
#!/usr/bin/env python3init
import enum
import logging
import os
import signal
import subprocess
import threading
import time
import typing
import coloredlogs
import i3ipc
from frobar.common import notBusy
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger()
# TODO Allow deletion of Bar, BarGroup and Section for screen changes
# IDEA Use i3 ipc events rather than relying on xrandr or Xlib (less portable
# but easier)
# TODO Optimize to use write() calls instead of string concatenation (writing
# BarGroup strings should be a good compromise)
# TODO Use bytes rather than strings
# TODO Use default colors of lemonbar sometimes
# TODO Adapt bar height with font height
# TODO OPTI Static text objects that update its parents if modified
# TODO forceSize and changeText are different
Handle = typing.Callable[[], None]
Decorator = Handle | str | None
Element: typing.TypeAlias = typing.Union[str, "Text", None]
Part: typing.TypeAlias = typing.Union[str, "Text", "Section"]
class BarGroupType(enum.Enum):
LEFT = 0
RIGHT = 1
# TODO Middle
# MID_LEFT = 2
# MID_RIGHT = 3
class BarStdoutThread(threading.Thread):
def run(self) -> None:
while Bar.running:
assert Bar.process.stdout
handle = Bar.process.stdout.readline().strip()
if not len(handle):
Bar.stop()
if handle not in Bar.actionsH2F:
log.error("Unknown action: {}".format(handle))
continue
function = Bar.actionsH2F[handle]
function()
class Bar:
"""
One bar for each screen
"""
# Constants
FONTS = ["DejaVuSansM Nerd Font"]
FONTSIZE = 10
@staticmethod
def init() -> None:
Bar.running = True
Bar.everyone = set()
Section.init()
cmd = [
"lemonbar",
"-b",
"-a",
"64",
"-F",
Section.FGCOLOR,
"-B",
Section.BGCOLOR,
]
for font in Bar.FONTS:
cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)]
Bar.process = subprocess.Popen(
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
BarStdoutThread().start()
i3 = i3ipc.Connection()
for output in i3.get_outputs():
if not output.active:
continue
Bar(output.name)
@staticmethod
def stop() -> None:
Bar.running = False
Bar.process.kill()
# TODO This is not really the best way to do it I guess
os.killpg(os.getpid(), signal.SIGTERM)
@staticmethod
def run() -> None:
Bar.forever()
i3 = i3ipc.Connection()
def doStop(*args: list) -> None:
Bar.stop()
try:
i3.on("ipc_shutdown", doStop)
i3.main()
except BaseException:
Bar.stop()
# Class globals
everyone: set["Bar"]
string = ""
process: subprocess.Popen
running = False
nextHandle = 0
actionsF2H: dict[Handle, bytes] = dict()
actionsH2F: dict[bytes, Handle] = dict()
@staticmethod
def getFunctionHandle(function: typing.Callable[[], None]) -> bytes:
assert callable(function)
if function in Bar.actionsF2H.keys():
return Bar.actionsF2H[function]
handle = "{:x}".format(Bar.nextHandle).encode()
Bar.nextHandle += 1
Bar.actionsF2H[function] = handle
Bar.actionsH2F[handle] = function
return handle
@staticmethod
def forever() -> None:
Bar.process.wait()
Bar.stop()
def __init__(self, output: str) -> None:
self.output = output
self.groups = dict()
for groupType in BarGroupType:
group = BarGroup(groupType, self)
self.groups[groupType] = group
self.childsChanged = False
Bar.everyone.add(self)
@staticmethod
def addSectionAll(
section: "Section", group: "BarGroupType"
) -> None:
"""
.. note::
Add the section before updating it for the first time.
"""
for bar in Bar.everyone:
bar.addSection(section, group=group)
section.added()
def addSection(self, section: "Section", group: "BarGroupType") -> None:
self.groups[group].addSection(section)
def update(self) -> None:
if self.childsChanged:
self.string = "%{Sn" + self.output + "}"
self.string += self.groups[BarGroupType.LEFT].string
self.string += self.groups[BarGroupType.RIGHT].string
self.childsChanged = False
@staticmethod
def updateAll() -> None:
if Bar.running:
Bar.string = ""
for bar in Bar.everyone:
bar.update()
Bar.string += bar.string
# Color for empty sections
Bar.string += BarGroup.color(*Section.EMPTY)
string = Bar.string + "\n"
# print(string)
assert Bar.process.stdin
Bar.process.stdin.write(string.encode())
Bar.process.stdin.flush()
class BarGroup:
"""
One for each group of each bar
"""
everyone: set["BarGroup"] = set()
def __init__(self, groupType: BarGroupType, parent: Bar):
self.groupType = groupType
self.parent = parent
self.sections: list["Section"] = list()
self.string = ""
self.parts: list[Part] = []
#: One of the sections that had their theme or visibility changed
self.childsThemeChanged = False
#: One of the sections that had their text (maybe their size) changed
self.childsTextChanged = False
BarGroup.everyone.add(self)
def addSection(self, section: "Section") -> None:
self.sections.append(section)
section.addParent(self)
def addSectionAfter(self, sectionRef: "Section", section: "Section") -> None:
index = self.sections.index(sectionRef)
self.sections.insert(index + 1, section)
section.addParent(self)
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
@staticmethod
def fgColor(color: str) -> str:
return "%{F" + (color or "-") + "}"
@staticmethod
def bgColor(color: str) -> str:
return "%{B" + (color or "-") + "}"
@staticmethod
def color(fg: str, bg: str) -> str:
return BarGroup.fgColor(fg) + BarGroup.bgColor(bg)
def update(self) -> None:
if self.childsThemeChanged:
parts: list[Part] = [BarGroup.ALIGNS[self.groupType]]
secs = [sec for sec in self.sections if sec.visible]
lenS = len(secs)
for s in range(lenS):
sec = secs[s]
theme = Section.THEMES[sec.theme]
if self.groupType == BarGroupType.LEFT:
oSec = secs[s + 1] if s < lenS - 1 else None
else:
oSec = secs[s - 1] if s > 0 else None
oTheme = (
Section.THEMES[oSec.theme] if oSec is not None else Section.EMPTY
)
if self.groupType == BarGroupType.LEFT:
if s == 0:
parts.append(BarGroup.bgColor(theme[1]))
parts.append(BarGroup.fgColor(theme[0]))
parts.append(sec)
if theme == oTheme:
parts.append("")
else:
parts.append(BarGroup.color(theme[1], oTheme[1]) + "")
else:
if theme is oTheme:
parts.append("")
else:
parts.append(BarGroup.fgColor(theme[1]) + "")
parts.append(BarGroup.color(*theme))
parts.append(sec)
# TODO OPTI Concatenate successive strings
self.parts = parts
if self.childsTextChanged or self.childsThemeChanged:
self.string = ""
for part in self.parts:
if isinstance(part, str):
self.string += part
elif isinstance(part, Section):
self.string += part.curText
self.parent.childsChanged = True
self.childsThemeChanged = False
self.childsTextChanged = False
@staticmethod
def updateAll() -> None:
for group in BarGroup.everyone:
group.update()
Bar.updateAll()
class SectionThread(threading.Thread):
ANIMATION_START = 0.025
ANIMATION_STOP = 0.001
ANIMATION_EVOLUTION = 0.9
def run(self) -> None:
while Section.somethingChanged.wait():
notBusy.wait()
Section.updateAll()
animTime = self.ANIMATION_START
frameTime = time.perf_counter()
while len(Section.sizeChanging) > 0:
frameTime += animTime
curTime = time.perf_counter()
sleepTime = frameTime - curTime
time.sleep(sleepTime if sleepTime > 0 else 0)
Section.updateAll()
animTime *= self.ANIMATION_EVOLUTION
if animTime < self.ANIMATION_STOP:
animTime = self.ANIMATION_STOP
Theme = tuple[str, str]
class Section:
# TODO Update all of that to base16
COLORS = [
"#092c0e",
"#143718",
"#5a7058",
"#677d64",
"#89947f",
"#99a08d",
"#fae2e3",
"#fff0f1",
"#e0332e",
"#cf4b15",
"#bb8801",
"#8d9800",
"#1fa198",
"#008dd1",
"#5c73c4",
"#d43982",
]
FGCOLOR = "#fff0f1"
BGCOLOR = "#092c0e"
THEMES: list[Theme] = list()
EMPTY: Theme = (FGCOLOR, BGCOLOR)
ICON: str | None = None
PERSISTENT = False
#: Sections that do not have their destination size
sizeChanging: set["Section"] = set()
updateThread: threading.Thread = SectionThread(daemon=True)
somethingChanged = threading.Event()
lastChosenTheme = 0
@staticmethod
def init() -> None:
for t in range(8, 16):
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
Section.THEMES.append((Section.COLORS[0], Section.COLORS[3]))
Section.THEMES.append((Section.COLORS[0], Section.COLORS[6]))
Section.updateThread.start()
def __init__(self, theme: int | None = None) -> None:
#: Displayed section
#: Note: A section can be empty and displayed!
self.visible = False
if theme is None:
theme = Section.lastChosenTheme
Section.lastChosenTheme = (Section.lastChosenTheme + 1) % len(
Section.THEMES
)
self.theme = theme
#: Displayed text
self.curText = ""
#: Displayed text size
self.curSize = 0
#: Destination text
self.dstText = Text(" ", Text(), " ")
#: Destination size
self.dstSize = 0
#: Groups that have this section
self.parents: set[BarGroup] = set()
self.icon = self.ICON
self.persistent = self.PERSISTENT
def __str__(self) -> str:
try:
return "<{}><{}>{:01d}{}{:02d}/{:02d}".format(
self.curText,
self.dstText,
self.theme,
"+" if self.visible else "-",
self.curSize,
self.dstSize,
)
except Exception:
return super().__str__()
def addParent(self, parent: BarGroup) -> None:
self.parents.add(parent)
def appendAfter(self, section: "Section") -> None:
assert len(self.parents)
for parent in self.parents:
parent.addSectionAfter(self, section)
def added(self) -> None:
pass
def informParentsThemeChanged(self) -> None:
for parent in self.parents:
parent.childsThemeChanged = True
def informParentsTextChanged(self) -> None:
for parent in self.parents:
parent.childsTextChanged = True
def updateText(self, text: Element) -> None:
if isinstance(text, str):
text = Text(text)
elif isinstance(text, Text) and not len(text.elements):
text = None
self.dstText[0] = (
None
if (text is None and not self.persistent)
else ((" " + self.icon + " ") if self.icon else " ")
)
self.dstText[1] = text
self.dstText[2] = (
" " if self.dstText[1] is not None and len(self.dstText[1]) else None
)
self.dstSize = len(self.dstText)
self.dstText.setSection(self)
if self.curSize == self.dstSize:
if self.dstSize > 0:
self.curText = str(self.dstText)
self.informParentsTextChanged()
else:
Section.sizeChanging.add(self)
Section.somethingChanged.set()
def setDecorators(self, **kwargs: Handle) -> None:
self.dstText.setDecorators(**kwargs)
self.curText = str(self.dstText)
self.informParentsTextChanged()
Section.somethingChanged.set()
def updateTheme(self, theme: int) -> None:
assert theme < len(Section.THEMES)
if theme == self.theme:
return
self.theme = theme
self.informParentsThemeChanged()
Section.somethingChanged.set()
def updateVisibility(self, visibility: bool) -> None:
self.visible = visibility
self.informParentsThemeChanged()
Section.somethingChanged.set()
@staticmethod
def fit(text: str, size: int) -> str:
t = len(text)
return text[:size] if t >= size else text + " " * (size - t)
def update(self) -> None:
# TODO Might profit of a better logic
if not self.visible:
self.updateVisibility(True)
return
if self.dstSize > self.curSize:
self.curSize += 1
elif self.dstSize < self.curSize:
self.curSize -= 1
else:
# Visibility toggling must be done one step after curSize = 0
if self.dstSize == 0:
self.updateVisibility(False)
Section.sizeChanging.remove(self)
return
self.curText = self.dstText.text(size=self.curSize, pad=True)
self.informParentsTextChanged()
@staticmethod
def updateAll() -> None:
"""
Process all sections for text size changes
"""
for sizeChanging in Section.sizeChanging.copy():
sizeChanging.update()
BarGroup.updateAll()
Section.somethingChanged.clear()
@staticmethod
def ramp(p: float, ramp: str = " ▁▂▃▄▅▆▇█") -> str:
if p > 1:
return ramp[-1]
elif p < 0:
return ramp[0]
else:
return ramp[round(p * (len(ramp) - 1))]
class StatefulSection(Section):
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
NUMBER_STATES: int
DEFAULT_STATE = 0
def __init__(self, theme: int | None) -> None:
Section.__init__(self, theme=theme)
self.state = self.DEFAULT_STATE
if hasattr(self, "onChangeState"):
self.onChangeState(self.state)
self.setDecorators(
clickLeft=self.incrementState, clickRight=self.decrementState
)
def incrementState(self) -> None:
newState = min(self.state + 1, self.NUMBER_STATES - 1)
self.changeState(newState)
def decrementState(self) -> None:
newState = max(self.state - 1, 0)
self.changeState(newState)
def changeState(self, state: int) -> None:
assert state < self.NUMBER_STATES
self.state = state
if hasattr(self, "onChangeState"):
self.onChangeState(state)
assert hasattr(
self, "refreshData"
), "StatefulSection should be paired with some Updater"
self.refreshData()
class ColorCountsSection(StatefulSection):
# TODO FEAT Blend colors when not expanded
# TODO FEAT Blend colors with importance of count
# TODO FEAT Allow icons instead of counts
NUMBER_STATES = 3
COLORABLE_ICON = "?"
def __init__(self, theme: None | int = None) -> None:
StatefulSection.__init__(self, theme=theme)
def subfetcher(self) -> list[tuple[int, str]]:
raise NotImplementedError("Interface must be implemented")
def fetcher(self) -> typing.Union[None, "Text"]:
counts = self.subfetcher()
# Nothing
if not len(counts):
return None
# Icon colored
elif self.state == 0 and len(counts) == 1:
count, color = counts[0]
return Text(self.COLORABLE_ICON, fg=color)
# Icon
elif self.state == 0 and len(counts) > 1:
return Text(self.COLORABLE_ICON)
# Icon + Total
elif self.state == 1 and len(counts) > 1:
total = sum([count for count, color in counts])
return Text(self.COLORABLE_ICON, " ", str(total))
# Icon + Counts
else:
text = Text(self.COLORABLE_ICON)
for count, color in counts:
text.append(" ", Text(str(count), fg=color))
return text
class Text:
def _setDecorators(self, decorators: dict[str, Decorator]) -> None:
# TODO OPTI Convert no decorator to strings
self.decorators = decorators
self.prefix: str | None = None
self.suffix: str | None = None
def __init__(self, *args: Element, **kwargs: Decorator) -> None:
# TODO OPTI Concatenate consecutrive string
self.elements = list(args)
self._setDecorators(kwargs)
self.section: Section
def append(self, *args: Element) -> None:
self.elements += list(args)
def prepend(self, *args: Element) -> None:
self.elements = list(args) + self.elements
def setElements(self, *args: Element) -> None:
self.elements = list(args)
def setDecorators(self, **kwargs: Decorator) -> None:
self._setDecorators(kwargs)
def setSection(self, section: Section) -> None:
self.section = section
for element in self.elements:
if isinstance(element, Text):
element.setSection(section)
def _genFixs(self) -> None:
if self.prefix is not None and self.suffix is not None:
return
self.prefix = ""
self.suffix = ""
def nest(prefix: str, suffix: str) -> None:
assert self.prefix is not None
assert self.suffix is not None
self.prefix = self.prefix + "%{" + prefix + "}"
self.suffix = "%{" + suffix + "}" + self.suffix
def getColor(val: str) -> str:
# TODO Allow themes
assert len(val) == 7
return val
def button(number: str, function: Handle) -> None:
handle = Bar.getFunctionHandle(function)
nest("A" + number + ":" + handle.decode() + ":", "A" + number)
for key, val in self.decorators.items():
if val is None:
continue
if key == "fg":
reset = self.section.THEMES[self.section.theme][0]
assert isinstance(val, str)
nest("F" + getColor(val), "F" + reset)
elif key == "bg":
reset = self.section.THEMES[self.section.theme][1]
assert isinstance(val, str)
nest("B" + getColor(val), "B" + reset)
elif key == "clickLeft":
assert callable(val)
button("1", val)
elif key == "clickMiddle":
assert callable(val)
button("2", val)
elif key == "clickRight":
assert callable(val)
button("3", val)
elif key == "scrollUp":
assert callable(val)
button("4", val)
elif key == "scrollDown":
assert callable(val)
button("5", val)
else:
log.warn("Unkown decorator: {}".format(key))
def _text(self, size: int | None = None, pad: bool = False) -> tuple[str, int]:
self._genFixs()
assert self.prefix is not None
assert self.suffix is not None
curString = self.prefix
curSize = 0
remSize = size
for element in self.elements:
if element is None:
continue
elif isinstance(element, Text):
newString, newSize = element._text(size=remSize)
else:
newString = str(element)
if remSize is not None:
newString = newString[:remSize]
newSize = len(newString)
curString += newString
curSize += newSize
if remSize is not None:
remSize -= newSize
if remSize <= 0:
break
curString += self.suffix
if pad:
assert remSize is not None
if remSize > 0:
curString += " " * remSize
curSize += remSize
if size is not None:
if pad:
assert size == curSize
else:
assert size >= curSize
return curString, curSize
def text(self, size: int | None = None, pad: bool = False) -> str:
string, size = self._text(size=size, pad=pad)
return string
def __str__(self) -> str:
self._genFixs()
assert self.prefix is not None
assert self.suffix is not None
curString = self.prefix
for element in self.elements:
if element is None:
continue
else:
curString += str(element)
curString += self.suffix
return curString
def __len__(self) -> int:
curSize = 0
for element in self.elements:
if element is None:
continue
elif isinstance(element, Text):
curSize += len(element)
else:
curSize += len(str(element))
return curSize
def __getitem__(self, index: int) -> Element:
return self.elements[index]
def __setitem__(self, index: int, data: Element) -> None:
self.elements[index] = data

File diff suppressed because it is too large Load diff

View file

@ -1,240 +0,0 @@
#!/usr/bin/env python3
import functools
import logging
import math
import os
import subprocess
import threading
import time
import coloredlogs
import i3ipc
import pyinotify
from frobar.common import notBusy
from frobar.display import Element
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger()
# TODO Sync bar update with PeriodicUpdater updates
class Updater:
@staticmethod
def init() -> None:
PeriodicUpdater.init()
InotifyUpdater.init()
notBusy.set()
def updateText(self, text: Element) -> None:
print(text)
def fetcher(self) -> Element:
return "{} refreshed".format(self)
def __init__(self) -> None:
self.lock = threading.Lock()
def refreshData(self) -> None:
# TODO OPTI Maybe discard the refresh if there's already another one?
self.lock.acquire()
try:
data = self.fetcher()
except BaseException as e:
log.error(e, exc_info=True)
data = ""
self.updateText(data)
self.lock.release()
class PeriodicUpdaterThread(threading.Thread):
def run(self) -> None:
# TODO Sync with system clock
counter = 0
while True:
notBusy.set()
if PeriodicUpdater.intervalsChanged.wait(
timeout=PeriodicUpdater.intervalStep
):
# ↑ sleeps here
notBusy.clear()
PeriodicUpdater.intervalsChanged.clear()
counter = 0
for providerList in PeriodicUpdater.intervals.copy().values():
for provider in providerList.copy():
provider.refreshData()
else:
notBusy.clear()
assert PeriodicUpdater.intervalStep is not None
counter += PeriodicUpdater.intervalStep
counter = counter % PeriodicUpdater.intervalLoop
for interval in PeriodicUpdater.intervals.keys():
if counter % interval == 0:
for provider in PeriodicUpdater.intervals[interval]:
provider.refreshData()
class PeriodicUpdater(Updater):
"""
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
"""
intervals: dict[int, set["PeriodicUpdater"]] = dict()
intervalStep: int | None = None
intervalLoop: int
updateThread: threading.Thread = PeriodicUpdaterThread(daemon=True)
intervalsChanged = threading.Event()
@staticmethod
def gcds(*args: int) -> int:
return functools.reduce(math.gcd, args)
@staticmethod
def lcm(a: int, b: int) -> int:
"""Return lowest common multiple."""
return a * b // math.gcd(a, b)
@staticmethod
def lcms(*args: int) -> int:
"""Return lowest common multiple."""
return functools.reduce(PeriodicUpdater.lcm, args)
@staticmethod
def updateIntervals() -> None:
intervalsList = list(PeriodicUpdater.intervals.keys())
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
PeriodicUpdater.intervalsChanged.set()
@staticmethod
def init() -> None:
PeriodicUpdater.updateThread.start()
def __init__(self) -> None:
Updater.__init__(self)
self.interval: int | None = None
def changeInterval(self, interval: int) -> None:
if self.interval is not None:
PeriodicUpdater.intervals[self.interval].remove(self)
self.interval = interval
if interval not in PeriodicUpdater.intervals:
PeriodicUpdater.intervals[interval] = set()
PeriodicUpdater.intervals[interval].add(self)
PeriodicUpdater.updateIntervals()
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
def process_default(self, event: pyinotify.Event) -> None:
assert event.path in InotifyUpdater.paths
if 0 in InotifyUpdater.paths[event.path]:
for provider in InotifyUpdater.paths[event.path][0]:
provider.refreshData()
if event.name in InotifyUpdater.paths[event.path]:
for provider in InotifyUpdater.paths[event.path][event.name]:
provider.refreshData()
class InotifyUpdater(Updater):
"""
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
"""
wm = pyinotify.WatchManager()
paths: dict[str, dict[str | int, set["InotifyUpdater"]]] = dict()
@staticmethod
def init() -> None:
notifier = pyinotify.ThreadedNotifier(
InotifyUpdater.wm, InotifyUpdaterEventHandler()
)
notifier.start()
# TODO Mask for folders
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
def addPath(self, path: str, refresh: bool = True) -> None:
path = os.path.realpath(os.path.expanduser(path))
# Detect if file or folder
if os.path.isdir(path):
self.dirpath: str = path
# 0: Directory watcher
self.filename: str | int = 0
elif os.path.isfile(path):
self.dirpath = os.path.dirname(path)
self.filename = os.path.basename(path)
else:
raise FileNotFoundError("No such file or directory: '{}'".format(path))
# Register watch action
if self.dirpath not in InotifyUpdater.paths:
InotifyUpdater.paths[self.dirpath] = dict()
if self.filename not in InotifyUpdater.paths[self.dirpath]:
InotifyUpdater.paths[self.dirpath][self.filename] = set()
InotifyUpdater.paths[self.dirpath][self.filename].add(self)
# Add watch
InotifyUpdater.wm.add_watch(self.dirpath, InotifyUpdater.MASK)
if refresh:
self.refreshData()
class ThreadedUpdaterThread(threading.Thread):
def __init__(self, updater: "ThreadedUpdater") -> None:
self.updater = updater
threading.Thread.__init__(self, daemon=True)
self.looping = True
def run(self) -> None:
try:
while self.looping:
self.updater.loop()
except BaseException as e:
log.error("Error with {}".format(self.updater))
log.error(e, exc_info=True)
self.updater.updateText("")
class ThreadedUpdater(Updater):
"""
Must implement loop(), and call start()
"""
def __init__(self) -> None:
Updater.__init__(self)
self.thread = ThreadedUpdaterThread(self)
def loop(self) -> None:
self.refreshData()
time.sleep(10)
def start(self) -> None:
self.thread.start()
class I3Updater(ThreadedUpdater):
# TODO OPTI One i3 connection for all
def __init__(self) -> None:
ThreadedUpdater.__init__(self)
self.i3 = i3ipc.Connection()
self.on = self.i3.on
self.start()
def loop(self) -> None:
self.i3.main()
class MergedUpdater(Updater):
def __init__(self, *args: Updater) -> None:
raise NotImplementedError("Deprecated, as hacky and currently unused")

View file

@ -2,19 +2,17 @@ from setuptools import setup
setup(
name="frobar",
version="2.0",
version="3.0",
install_requires=[
"coloredlogs",
"notmuch",
"i3ipc",
"python-mpd2",
"psutil",
"pulsectl",
"pyinotify",
"pulsectl-asyncio",
"pygobject3",
"rich",
],
entry_points={
"console_scripts": [
"frobar = frobar:run",
"frobar = frobar:main",
]
},
)

View file

@ -24,7 +24,7 @@ let
in
{
config = lib.mkIf config.frogeye.desktop.xorg {
home.packages = with pkgs; [
home.packages = [
(pkgs.writeShellApplication {
name = "xlock";
text = ''

View file

@ -3,7 +3,6 @@
# Config mentions pdfpc, although the last thing I used was Impressive, even made patches to it.
# UPST Add Impressive to nixpkgs
{
pkgs,
lib,
config,
...

35
hm/dev/3d.nix Normal file
View file

@ -0,0 +1,35 @@
{
pkgs,
lib,
config,
...
}:
{
config = lib.mkIf config.frogeye.dev."3d" {
home = {
packages = with pkgs; [
blender
openscad-unstable # 2022+ parses files much faster
orca-slicer
];
};
programs.nixvim.plugins = {
# openscad.enable = true; # Doesn't do anything besides annoying popups
lsp.servers.openscad_lsp.enable = true;
};
xdg.dataFile = {
"OpenSCAD/libraries/BOSL2".source = pkgs.fetchFromGitHub {
owner = "BelfrySCAD";
repo = "BOSL2";
rev = "ff7e8b8611022b1ce58ea2a1e076028d3d7d40ff"; # no tags, no release
hash = "sha256-esKgXyKLudDfWT2pxOjltZsQ9N6Whlf4zhxd071COzQ=";
};
"OpenSCAD/libraries/MCAD".source = pkgs.fetchFromGitHub {
owner = "openscad";
repo = "MCAD";
rev = "bd0a7ba3f042bfbced5ca1894b236cea08904e26"; # no tags, no release
hash = "sha256-rnrapCe5BkdibbCYVyGZi0l1/8DZxoDnulK37fwZbqo=";
};
};
};
}

View file

@ -37,33 +37,12 @@
# Network
wireshark-qt
# Ansible
]
++ lib.optionals config.frogeye.dev.ansible [
ansible
ansible-lint
# Docker
]
++ lib.optionals config.frogeye.dev.docker [
docker
docker-compose
# FPGA
]
++ lib.optionals config.frogeye.dev.fpga [
verilog
]
++ lib.optionals (config.frogeye.dev.fpga && pkgs.stdenv.isx86_64) [
ghdl
# FPGA (graphical)
]
++ lib.optionals (config.frogeye.desktop.xorg && config.frogeye.dev.fpga) [
yosys
gtkwave
# VM (graphical)
]
++ lib.optionals (config.frogeye.desktop.xorg && config.frogeye.dev.vm) [

View file

@ -1,6 +1,9 @@
{ pkgs, config, ... }:
{
...
}:
{
imports = [
./3d.nix
./c.nix
./common.nix
./go.nix

View file

@ -1,6 +1,5 @@
# Untested post-nix
{
pkgs,
lib,
config,
...

View file

@ -17,6 +17,7 @@
sub-langs = "en,fr";
write-auto-subs = true;
write-subs = true;
mark-watched = true; # Give something to creators, maybe
};
};
};
@ -39,41 +40,24 @@
# documents
visidata
# texlive.combined.scheme-full
# TODO Convert existing LaTeX documents into using Nix build system
# texlive is big and not that much used, sooo
pdftk
pdfgrep
# Misc
haskellPackages.dice
rustdesk-flutter
]
++ lib.optionals config.frogeye.desktop.xorg [
# multimedia editors
darktable
puddletag
audacity
xournalpp
krita
# downloading
transmission_4-qt
# wine only makes sense on x86_64
]
++ lib.optionals pkgs.stdenv.isx86_64 [
wine
# TODO wine-gecko wine-mono lib32-libpulse (?)
]
++ lib.optionals (!stdenv.isAarch64) [
# Musescore is broken on aarch64
musescore
# Blender 4.0.1 can't compile on aarch64
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
blender
# Misc
rustdesk-flutter
]
);
};

View file

@ -2,17 +2,15 @@
pkgs,
lib,
config,
jjuinixpkgs,
...
}:
let
cfg = config.programs.git;
jjuipkgs = import jjuinixpkgs { inherit (pkgs) system; };
in
{
config = lib.mkIf cfg.enable {
home.packages = [
jjuipkgs.jjui
pkgs.jjui
pkgs.lazyjj
(pkgs.writeShellApplication {
name = "git-sync";
@ -107,7 +105,7 @@ in
diff-editor = "meld-3";
merge-editor = "meld";
};
signing = {
signing = lib.mkIf (!builtins.isNull cfg.signing) {
sign-all = true;
backend = "gpg";
inherit (cfg.signing) key;

View file

@ -33,6 +33,7 @@
};
publicKeys = [
{
# Always install my own public key
source = builtins.fetchurl {
url = "https://keys.openpgp.org/vks/v1/by-fingerprint/4FBA930D314A03215E2CDB0A8312C8CAC1BAC289";
sha256 = "sha256:10y9xqcy1vyk2p8baay14p3vwdnlwynk0fvfbika65hz2z8yw2cm";
@ -44,12 +45,14 @@
services.gpg-agent = rec {
enableBashIntegration = true;
enableZshIntegration = true;
pinentryPackage = pkgs.pinentry-gnome3;
# gnome3 is nicer, but requires gcr as a dbus package.
# Which is in my NixOS config, and on non-NixOS too.
# It will fall back to ncurses when running in non-graphics mode.
pinentryPackage = pkgs.pinentry-gnome3;
# If inactive, the key will be forgotten after this time
defaultCacheTtl = 3600;
defaultCacheTtlSsh = defaultCacheTtl;
# If active, the key will be forgotten adfter this time
maxCacheTtl = 3 * 3600;
maxCacheTtlSsh = maxCacheTtl;
};

View file

@ -61,6 +61,6 @@
'';
type = lib.types.listOf lib.types.str;
};
# TODO Should make a nix package wrapper instead, so it also works from dmenu
# TODO Should make a nix package wrapper instead, so it also works from rofi
};
}

View file

@ -1,6 +1,5 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -1,7 +1,6 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,20 +1,18 @@
{
pkgs,
lib,
config,
...
}:
{
config = lib.mkIf config.programs.less.enable {
config = {
programs.powerline-go = {
enable = true;
modules = [
"user"
"host"
"venv"
"direnv"
"cwd"
"perms"
"nix-shell"
"venv"
"git"
];
modulesRight = [

View file

@ -58,15 +58,4 @@ then
fi
# To keep until https://github.com/openssh/openssh-portable/commit/f64f8c00d158acc1359b8a096835849b23aa2e86
# is merged
function _ssh {
if [ "${TERM}" = "alacritty" ]
then
TERM=xterm-256color ssh "$@"
else
ssh "$@"
fi
}
alias ssh='_ssh'
_ssh -t "$@" "$(cat "${CACHE_DIR}/cmd")"
ssh -t "$@" "$(cat "${CACHE_DIR}/cmd")"

View file

@ -1,75 +0,0 @@
#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.coloredlogs r128gain
# TODO r128gain is not maintainted anymore
# rsgain replaces it, does the same job as I do with albums
# Normalisation is done at the default of each program,
# which is usually -89.0 dB
# TODO The simplifications/fixes I've done makes it consider
# multi-discs albums as multiple albums
import logging
import os
import sys
import typing
import coloredlogs
import r128gain
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger()
# TODO Remove debug
# Constants
FORCE = "-f" in sys.argv
if FORCE:
sys.argv.remove("-f")
if len(sys.argv) >= 2:
SOURCE_FOLDER = os.path.realpath(sys.argv[1])
else:
SOURCE_FOLDER = os.path.join(os.path.expanduser("~"), "Musiques")
def isMusic(f: str) -> bool:
ext = os.path.splitext(f)[1][1:].lower()
return ext in r128gain.AUDIO_EXTENSIONS
# Get album paths
log.info("Listing albums and tracks")
albums = list()
singleFiles = list()
for root, dirs, files in os.walk(SOURCE_FOLDER):
folder_has_music = False
for f in files:
if isMusic(f):
folder_has_music = True
fullPath = os.path.join(root, f)
singleFiles.append(fullPath)
if folder_has_music:
albums.append(root)
# log.info("Processing single files")
# r128gain.process(singleFiles, album_gain=False,
# skip_tagged=not FORCE, report=True)
for album in albums:
albumName = os.path.relpath(album, SOURCE_FOLDER)
log.info("Processing album {}".format(albumName))
musicFiles = list()
for f in os.listdir(album):
if isMusic(f):
fullPath = os.path.join(album, f)
musicFiles.append(fullPath)
if not musicFiles:
continue
r128gain.process(musicFiles, album_gain=True, skip_tagged=not FORCE, report=True)
print("==============================")

0
hm/scripts/updateCompressedMusic Executable file → Normal file
View file

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,6 +1,5 @@
{
pkgs,
lib,
config,
...
}:
@ -40,10 +39,23 @@ in
expireDuplicatesFirst = true;
};
shellAliases = cfg.shellAliases;
plugins = [
{
name = "zsh-nix-shell";
file = "nix-shell.plugin.zsh";
src = pkgs.fetchFromGitHub {
owner = "chisui";
repo = "zsh-nix-shell";
rev = "v0.8.0";
sha256 = "1lzrn0n4fxfcgg65v0qhnj7wnybybqzs4adz7xsrkgmcsr0ii8b7";
};
}
];
};
};
};
imports = [
./atuin.nix
./direnv.nix
];
}

17
hm/shell/direnv.nix Normal file
View file

@ -0,0 +1,17 @@
# Allow switching to a specific environment when going into a directory
{
...
}:
{
config = {
programs = {
direnv = {
enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
git.ignores = [ ".envrc" ".direnv" ];
};
};
}

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
...
}:
{
@ -12,7 +10,7 @@
programs.ssh = {
enable = true;
controlMaster = "auto";
controlPersist = "60s"; # TODO Default is 10minutes... makes more sense no?
controlPersist = "60s"; # Enough to cache Ansible stuff, not too long so I don't have remember which shenanigans I did with my last connection
# Ping the server frequently enough so it doesn't think we left (non-spoofable)
serverAliveInterval = 30;
matchBlocks."*" = {
@ -20,8 +18,7 @@
# as it is kinda a security concern
forwardAgent = false;
# Restrict terminal features (servers don't necessarily have the terminfo for my cutting edge terminal)
sendEnv = [ "!TERM" ];
# TODO Why not TERM=xterm-256color?
setEnv.TERM = "xterm-256color";
extraOptions = {
# Check SSHFP records
VerifyHostKeyDNS = "yes";

View file

@ -1,7 +1,6 @@
{
pkgs,
config,
lib,
stylix,
...
}:

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
...
}:
let
@ -32,7 +30,7 @@ in
{
config = {
programs.nixvim = {
extraPlugins = with pkgs.vimPlugins; [
extraPlugins = [
# f/F mode
vim-shot-f # Highlight relevant characters for f/F/t/T modes
quick-scope # Highlight relevant characters for f/F modes one per word but always

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,7 +1,5 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -2,9 +2,9 @@
{
config = {
frogeye = {
name = "abavorana";
name = "morton";
storageSize = "big";
syncthing.name = "Abavorana";
syncthing.name = "Morton";
};
};
}

View file

@ -15,7 +15,7 @@
};
};
};
system.stateVersion = "24.05";
system.stateVersion = "24.11";
terminal.font = "${
pkgs.nerdfonts.override {
fonts = [ "DejaVuSansMono" ];

View file

@ -64,10 +64,10 @@ in
};
};
dev = {
"3d" = lib.mkEnableOption "3D (printing) / CAD stuff";
ansible = lib.mkEnableOption "Ansible dev stuff";
c = lib.mkEnableOption "C/C++ dev stuff";
docker = lib.mkEnableOption "Docker dev stuff";
fpga = lib.mkEnableOption "FPGA dev stuff";
go = lib.mkEnableOption "Go dev stuff";
node = lib.mkEnableOption "NodeJS dev stuff";
perl = lib.mkEnableOption "Perl dev stuff";

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -1,6 +1,5 @@
{
pkgs,
lib,
config,
...
}:
@ -44,15 +43,21 @@
];
i18n = {
defaultLocale = "en_GB.UTF-8";
defaultLocale = "nl_NL.UTF-8";
extraLocaleSettings = {
LC_TIME = "en_DK.UTF-8"; # Should give YYYY-MM-DD but doesn't work for browser & Thunderbird :(
LC_TIME = "en_DK.UTF-8"; # Use YYYY-MM-DD, Thunderbird & qutebrowser use own settings
};
};
nix = {
gc = {
automatic = true;
persistent = true;
options = "--delete-older-than 14d";
};
package = pkgs.lix;
settings = {
auto-optimise-store = true;
experimental-features = [
"nix-command"
"flakes"
@ -74,15 +79,12 @@
};
services = {
# More aggressive OoM killer so we never hang
earlyoom.enable = true;
# Enable the OpenSSH daemon
openssh.enable = true;
# Time sychronisation
chrony = {
enable = true;
servers = map (n: "${toString n}.europe.pool.ntp.org") (lib.lists.range 0 3);
};
# Prevent power button from shutting down the computer.
# On Pinebook it's too easy to hit,
# on others I sometimes turn it off when unsuspending.
@ -98,6 +100,6 @@
# TODO Hibernation?
# Use defaults from
system.stateVersion = "24.05";
system.stateVersion = "24.11";
}

View file

@ -1,6 +1,5 @@
# Need nvidia proprietary drivers to work
{
pkgs,
nixpkgs,
config,
lib,

View file

@ -6,11 +6,12 @@
./battery.nix
./boot
./ccc
./cuda
./common.nix
./cuda
./desktop
./dev
disko.nixosModules.disko
./dns
./gaming
./geoffrey.nix
./password
@ -18,6 +19,7 @@
./remote-builds
./style
./syncthing
./time
./wireless
];
}

View file

@ -4,25 +4,26 @@
config,
...
}:
let
setupScript = "${
pkgs.writeShellApplication {
name = "greeter-setup-script";
runtimeInputs = [ pkgs.autorandr ];
text = ''
autorandr --change
'';
}
}/bin/greeter-setup-script";
in
{
config = {
config = lib.mkIf (builtins.length config.frogeye.desktop.x11_screens > 1) {
services = {
autorandr.enable = true;
xserver.displayManager.lightdm.extraConfig = ''
[Seat:*]
display-setup-script = ${setupScript}
'';
xserver.displayManager.lightdm.extraConfig =
let
setupScript = "${
pkgs.writeShellApplication {
name = "greeter-setup-script";
runtimeInputs = [ pkgs.autorandr ];
text = ''
autorandr --change
'';
}
}/bin/greeter-setup-script";
in
''
[Seat:*]
display-setup-script = ${setupScript}
'';
};
};
}

13
os/dns/default.nix Normal file
View file

@ -0,0 +1,13 @@
{
...
}:
{
config = {
services.dnsmasq = {
# We want to be able to have two VPNs active at once.
# Not an issue for routing, but we need local DNS with conditional forwarding.
enable = true;
resolveLocalQueries = true;
};
};
}

View file

@ -1,10 +1,12 @@
{ lib, config, ... }:
{
config = lib.mkIf config.services.printing.enable {
hardware.sane.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
users.users.geoffrey.extraGroups = [ "lp" "scanner" ];
};
}

View file

@ -4,42 +4,78 @@ verb="$2"
shift
shift
# Helpers
error() {
echo -e '\033[1;31m'"$*"'\033[0m'
}
warn() {
echo -e '\033[1;33m'"$*"'\033[0m'
}
info() {
echo -e '\033[1;34m'"$*"'\033[0m'
}
debug() {
[ ! -v DEBUG ] || echo -e '\033[1;35m'"$*"'\033[0m'
}
if [ "$verb" != "build" ] && [ "$verb" != "test" ] && [ "$verb" != "boot" ] && [ "$verb" != "switch" ] && [ "$verb" != "confirm" ]
then
echo "Action should be one of: build, test, boot, switch, confirm"
error "Action should be one of: build, test, boot, switch, confirm"
exit 2
fi
# Build, looking nice
tmpdir="$(mktemp -d)"
info "Evaluating"
# Evaluating can take a lot of memory, and Nix doesn't free it until the program ends,
# which can be limiting on memory-constrained devices. Hence the build step is separate.
# Drawback: it will query info about missing paths twice
# nix eval doesn't use the eval cache, so we do a nix build --dry-run
# sudo so the eval cache is shared with nixos-rebuild
sudo nom build "$self#nixosConfigurations.$HOSTNAME.config.system.build.toplevel" -o "$tmpdir/toplevel" "$@"
toplevel="$(readlink -f "$tmpdir/toplevel")"
rm -rf "$tmpdir"
json=$(time sudo nix build "$self#nixosConfigurations.$HOSTNAME.config.system.build.toplevel" --dry-run --json )
toplevel=$(echo "$json" | jq '.[0].outputs.out' -r)
derivation=$(echo "$json" | jq '.[0].drvPath' -r)
# Show diff
info "Building"
sudo nom build "$derivation^*" --no-link "$@"
info "Showing diff"
nvd diff "$(readlink -f /nix/var/nix/profiles/system)" "$toplevel"
# Figure out specialisation
specialisationArgs=()
info "Figuring current specialisation"
systemLink=
currentSystem="$(readlink -f /run/current-system)"
while read -r specialisation
while read -r system
do
if [ "$(readlink -f "/nix/var/nix/profiles/system/specialisation/$specialisation")" = "$currentSystem" ]
if [ "$(readlink -f "$system")" = "$currentSystem" ]
then
systemLink="$system"
break
fi
done <<< "$(ls -d /nix/var/nix/profiles/system-*-link{,/specialisation/*})"
specialisationArgs=()
if [ -n "$systemLink" ]
then
specialisation=$(echo "$systemLink" | cut -d/ -f8)
if [ -n "$specialisation" ]
then
specialisationArgs=("--specialisation" "$specialisation")
fi
done <<< "$(ls /nix/var/nix/profiles/system/specialisation)"
else
warn "Could not find link for $currentSystem, will switch to non-specialized system"
fi
# Apply
confirm="n"
if [ "$verb" = "confirm" ]
then
echo "Apply configuration? [y/N]"
warn "Apply configuration? [y/N]"
read -r confirm
fi
if [ "$verb" = "test" ] || [ "$verb" = "switch" ] || [ "$confirm" = "y" ]
then
info "Applying"
"$toplevel/bin/update-password-store"
sudo nixos-rebuild --flake "$self#$HOSTNAME" test "${specialisationArgs[@]}" "$@"
fi
@ -47,10 +83,11 @@ fi
# Set as boot
if [ "$verb" = "confirm" ]
then
echo "Set configuration as boot? [y/N]"
warn "Set configuration as boot? [y/N]"
read -r confirm
fi
if [ "$verb" = "boot" ] || [ "$verb" = "switch" ] || [ "$confirm" = "y" ]
then
info "Setting as boot"
sudo nixos-rebuild --flake "$self#$HOSTNAME" boot "$@"
fi

View file

@ -1,5 +1,4 @@
{
pkgs,
lib,
config,
...
@ -8,66 +7,55 @@ let
vivariumBuilderDefault = {
systems = [
"x86_64-linux"
"aarch64-linux"
];
protocol = "ssh-ng";
sshUser = "nixremote";
# sshKey doesn't work
};
# MANU ssh-keygen -y -f /etc/ssh/ssh_host_ed25519_key | base64 -w0
# MANU ssh-keygen -y -f /etc/ssh/ssh_host_ed25519_key
# TODO Proper configuration option instead of pile of defs and hacks
vivariumBuilders = [
{
hostName = "abavorana.frogeye.fr";
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU5iNzcrS01tRHI0MVhZdmZITXQvK3NHMkJCSEIzYUl4M045WDNVejhFaUogZ2VvZmZyZXlAY3VyYWNhbwo=";
hostName = "morton.frogeye.fr";
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEetvIp4ZrP+ofXNDypnrLxdU034SBYg7fx9FxClDJA3";
supportedFeatures = [
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
];
maxJobs = 8;
}
{
hostName = "ludwig.clowncar.frogeye.fr";
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU41SXZhMzNXeGplN095cHVEUHBSakFNMTlvRUtEVDRiYlpUTm82V1FLZTAgZ2VvZmZyZXlAY3VyYWNhbwo=";
maxJobs = 4;
maxJobs = 12; # 8 cores, 16 with hyperthreading, trying not to overload the thing
}
];
# MANU pass vivarium/lemmy/remote-builds/cache | nix key convert-secret-to-public | cat
publicKeys = [
"abavorana.frogeye.fr:rcKZ9gwaIQLcst/vbhbF7meUQD5sveT2QQN4a+Zo1BM="
"ludwig.clowncar.frogeye.fr:jTlN0fCOLU49M3LQw5j/u++Gmwrsv3m9RGs0slSg6r0="
"morton.frogeye.fr:rSjbCZ4mgXkb+ENKI7sk/KIbftlQzCTQA7pWkdfS2r4="
];
in
{
config = {
system.activationScripts.remote = {
supportsDryActivation = true;
text = ''
mkdir -p /root/.ssh
cat ${
pkgs.writeText "root-ssh-config" (
lib.strings.concatLines (
builtins.map (builder: ''
Host ${builder.hostName}
ControlMaster auto
ControlPath ~/.ssh/master-%r@%n:%p
ControlPersist 60s
'') vivariumBuilders
)
)
} > /root/.ssh/config
'';
};
programs.ssh.knownHosts = lib.trivial.pipe vivariumBuilders [
(builtins.map (builder: {
name = builder.hostName;
value.publicKey = builder.publicKey;
}))
builtins.listToAttrs
];
# Currently using port 22 only because:
# - Morton has to use it for git
# - Hopefully allowed on some firewalls
# - Thought you couldn't set SSH config
# You might be able to set SSH config with porgrams.ssh, although I only tried creating a /root/.ssh/config file
# (which does not work, unless logged in as root. host keys from root are used regardless of the user, though)
nix = {
buildMachines = builtins.map (
vivariumBuilder: vivariumBuilderDefault // vivariumBuilder
vivariumBuilder:
lib.attrsets.filterAttrs (k: v: k != "publicKey") (vivariumBuilderDefault // vivariumBuilder)
) vivariumBuilders;
distributedBuilds = false;
distributedBuilds = true;
settings = {
builders-use-substitutes = true;
trusted-public-keys = publicKeys;
trusted-substituters = builtins.map (
substituters = builtins.map (
builder: "${builder.protocol}://${builder.sshUser}@${builder.hostName}"
) config.nix.buildMachines;
};

View file

@ -1,6 +1,5 @@
{
pkgs,
lib,
config,
stylix,
...

View file

@ -24,7 +24,9 @@ let
);
allDevices = nixosDevices;
syncingDevices = builtins.filter (device: device.syncthing.id != null) allDevices;
peerDevices = builtins.filter (device: device.name != config.frogeye.name) syncingDevices;
peerDevices = builtins.filter (
device: device.syncthing.id != config.frogeye.syncthing.id
) syncingDevices;
# Can't use the module's folders enable option, as it still requests things somehow
allFolders = builtins.attrValues config.frogeye.folders;
@ -34,12 +36,58 @@ let
folder: device:
(lib.hasAttrByPath [ folder.name ] device.folders)
&& device.folders.${folder.name}.syncthing.enable;
folderDeviceEntry = folder: device: { deviceID = device.syncthing.id; };
enable = (builtins.length syncedFolders) > 0;
in
{
config = {
# Allow to export configuration to other systems
system.build.syncthingConfig = {
folders = lib.trivial.pipe syncedFolders [
(builtins.map (folder: {
name = folder.name;
value = folder;
}))
builtins.listToAttrs
(lib.attrsets.mapAttrs (
folderName: folder:
(lib.attrsets.filterAttrs (
k: v:
builtins.elem k [
"label"
"path"
"syncthing"
"user"
]
))
folder
))
];
devices = lib.trivial.pipe syncingDevices [
(builtins.map (device: {
name = device.name;
value = device;
}))
builtins.listToAttrs
(lib.attrsets.mapAttrs (
deviceName: device:
{
folders = lib.trivial.pipe device.folders [
(lib.attrsets.filterAttrs (folderName: folder: folder.syncthing.enable))
(lib.attrsets.mapAttrs (folderName: folder: { syncthing.enable = true; }))
];
}
//
(lib.attrsets.filterAttrs (
k: v:
builtins.elem k [
"syncthing"
]
))
device
))
];
};
services.${service} = {
inherit enable;
openDefaultPorts = true;
@ -63,8 +111,6 @@ in
value = {
label = "${capitalizeFirstLetter folder.user} ${folder.label}";
path = "${config.users.users.${folder.user}.home}/${folder.path}";
# Despite further in the code indicating this is possible, it is, actually not
# devices = builtins.map (folderDeviceEntry folder) (builtins.filter (folderShouldSyncWith folder) peerDevices);
devices = builtins.map (device: device.name) (
builtins.filter (folderShouldSyncWith folder) peerDevices
);

47
os/time/default.nix Normal file
View file

@ -0,0 +1,47 @@
{
lib,
config,
...
}:
{
config = {
# Apparently better than reference implementation
services.chrony.enable = true;
networking = {
# Using community provided service
timeServers = map (n: "${toString n}.europe.pool.ntp.org") (lib.lists.range 0 3);
# Only try to sync time when we have internet connection
dhcpcd.runHook = ''
if $if_up
then
/run/wrappers/bin/sudo ${config.services.chrony.package}/bin/chronyc online
elif $if_down
then
/run/wrappers/bin/sudo ${config.services.chrony.package}/bin/chronyc offline
fi
'';
};
# Allow dhcpcd to control chrony
security.sudo.extraRules = [
{
users = [ "dhcpcd" ];
commands =
builtins.map
(arg: {
command = "${config.services.chrony.package}/bin/chronyc ${arg}";
options = [ "NOPASSWD" ];
})
[
"online"
"offline"
];
}
];
systemd.services.dhcpcd.serviceConfig.NoNewPrivileges = false;
};
}

View file

@ -1,7 +1,6 @@
{
pkgs,
lib,
config,
...
}:
let
@ -54,15 +53,6 @@ in
];
# wireless support via wpa_supplicant
networking = {
# Tell the time synchronisation service when we got/lost the connection
dhcpcd.runHook = ''
if $if_up; then
${config.services.chrony.package}/bin/chronyc online
elif $if_down; then
${config.services.chrony.package}/bin/chronyc offline
fi
'';
wireless = {
enable = true;
extraConfig = ''

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}:

View file

@ -1,7 +1,4 @@
{
pkgs,
lib,
config,
...
}:
{

View file

@ -1,7 +1,6 @@
{
pkgs,
lib,
config,
nixos-hardware,
...
}:

View file

@ -1,6 +1,4 @@
{
pkgs,
lib,
config,
...
}: