Compare commits
No commits in common. "main" and "push-kmvskqxkozop" have entirely different histories.
main
...
push-kmvsk
abavorana
common/disko
cranberry
curacao
flake.lockflake.nixhm
accounts
common.nixdesktop
dev
extra
git
gpg
homealone.nixmonitoring
nix
prompt
scripts
shell
ssh.nixtheme
vim
nod/common
options.nixos
pindakaas
|
@ -2,9 +2,9 @@
|
|||
{
|
||||
config = {
|
||||
frogeye = {
|
||||
name = "morton";
|
||||
name = "abavorana";
|
||||
storageSize = "big";
|
||||
syncthing.name = "Morton";
|
||||
syncthing.name = "Abavorana";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
frogeye = {
|
||||
desktop.xorg = true;
|
||||
dev = {
|
||||
"3d" = true;
|
||||
c = true;
|
||||
vm = true;
|
||||
};
|
||||
extra = true;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
nixos-hardware,
|
||||
...
|
||||
}:
|
||||
|
@ -24,7 +26,7 @@
|
|||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
frogeye.desktop = {
|
||||
x11_screens = [ "DP-2" "eDP-1" ];
|
||||
x11_screens = [ "eDP-1" ];
|
||||
maxVideoHeight = 1080;
|
||||
|
||||
phasesCommands = {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
xorg = true;
|
||||
};
|
||||
dev = {
|
||||
"3d" = true;
|
||||
c = true;
|
||||
docker = true;
|
||||
vm = true;
|
||||
|
|
|
@ -49,9 +49,6 @@ 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
|
||||
|
@ -62,18 +59,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?
|
||||
};
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
250
flake.lock
250
flake.lock
|
@ -37,11 +37,11 @@
|
|||
"base16-helix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1736852337,
|
||||
"narHash": "sha256-esD42YdgLlEh7koBrSqcT7p2fsMctPAcGl/+2sYJa2o=",
|
||||
"lastModified": 1725860795,
|
||||
"narHash": "sha256-Z2o8VBPW3I+KKTSfe25kskz0EUj7MpUh8u355Z1nVsU=",
|
||||
"owner": "tinted-theming",
|
||||
"repo": "base16-helix",
|
||||
"rev": "03860521c40b0b9c04818f2218d9cc9efc21e7a5",
|
||||
"rev": "7f795bf75d38e0eea9fed287264067ca187b88a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -53,17 +53,16 @@
|
|||
"base16-vim": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1732806396,
|
||||
"narHash": "sha256-e0bpPySdJf0F68Ndanwm+KWHgQiZ0s7liLhvJSWDNsA=",
|
||||
"lastModified": 1731949548,
|
||||
"narHash": "sha256-XIDexXM66sSh5j/x70e054BnUsviibUShW7XhbDGhYo=",
|
||||
"owner": "tinted-theming",
|
||||
"repo": "base16-vim",
|
||||
"rev": "577fe8125d74ff456cf942c733a85d769afe58b7",
|
||||
"rev": "61165b1632409bd55e530f3dbdd4477f011cadc6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tinted-theming",
|
||||
"repo": "base16-vim",
|
||||
"rev": "577fe8125d74ff456cf942c733a85d769afe58b7",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
|
@ -75,11 +74,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1741473158,
|
||||
"narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
|
||||
"lastModified": 1728330715,
|
||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
|
||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -95,11 +94,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743598667,
|
||||
"narHash": "sha256-ViE7NoFWytYO2uJONTAX35eGsvTYXNHjWALeHAg8OQY=",
|
||||
"lastModified": 1735048446,
|
||||
"narHash": "sha256-Tc35Y8H+krA6rZeOIczsaGAtobSSBPqR32AfNTeHDRc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "329d3d7e8bc63dd30c39e14e6076db590a6eabe6",
|
||||
"rev": "3a4de9fa3a78ba7b7170dda6bd8b4cdab87c0b21",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -107,30 +106,14 @@
|
|||
"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": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"revCount": 69,
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"revCount": 57,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
|
||||
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
|
@ -140,11 +123,11 @@
|
|||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -161,11 +144,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743550720,
|
||||
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||
"lastModified": 1733312601,
|
||||
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -278,40 +261,18 @@
|
|||
"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"
|
||||
],
|
||||
"gitignore": "gitignore_2",
|
||||
"nixpkgs": [
|
||||
"stylix",
|
||||
"nixpkgs-stable": [
|
||||
"nixvim",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742649964,
|
||||
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
|
||||
"lastModified": 1734425854,
|
||||
"narHash": "sha256-nzE5UbJ41aPEKf8R2ZFYtLkqPmF7EIUbNEdHMBLg0Ig=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
|
||||
"rev": "0ddd26d0925f618c3a5d85a4fa5eb1e23a09491d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -342,28 +303,6 @@
|
|||
"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": {
|
||||
|
@ -388,11 +327,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743808813,
|
||||
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
|
||||
"lastModified": 1734366194,
|
||||
"narHash": "sha256-vykpJ1xsdkv0j8WOVXrRFHUAdp9NXHpxdnn1F4pYgSw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
|
||||
"rev": "80b0fdf483c5d1cb75aaad909bd390d48673857f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -409,11 +348,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743808813,
|
||||
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
|
||||
"lastModified": 1734366194,
|
||||
"narHash": "sha256-vykpJ1xsdkv0j8WOVXrRFHUAdp9NXHpxdnn1F4pYgSw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
|
||||
"rev": "80b0fdf483c5d1cb75aaad909bd390d48673857f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -431,11 +370,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743808813,
|
||||
"narHash": "sha256-2lDQBOmlz9ggPxcS7/GvcVdzXMIiT+PpMao6FbLJSr0=",
|
||||
"lastModified": 1733572789,
|
||||
"narHash": "sha256-zjO6m5BqxXIyjrnUziAzk4+T4VleqjstNudSqWcpsHI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "a9f8b3db211b4609ddd83683f9db89796c7f6ac6",
|
||||
"rev": "c7ffc9727d115e433fd884a62dc164b587ff651d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -473,6 +412,38 @@
|
|||
"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": [
|
||||
|
@ -481,16 +452,15 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743127615,
|
||||
"narHash": "sha256-+sMGqywrSr50BGMLMeY789mSrzjkoxZiu61eWjYS/8o=",
|
||||
"lastModified": 1733570843,
|
||||
"narHash": "sha256-sQJAxY1TYWD1UyibN/FnN97paTFuwBw3Vp3DNCyKsMk=",
|
||||
"owner": "lnl7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "fc843893cecc1838a59713ee3e50e9e7edc6207c",
|
||||
"rev": "a35b08d09efda83625bef267eb24347b446c80b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "lnl7",
|
||||
"ref": "nix-darwin-24.11",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -550,11 +520,11 @@
|
|||
},
|
||||
"nixos-hardware": {
|
||||
"locked": {
|
||||
"lastModified": 1743420942,
|
||||
"narHash": "sha256-b/exDDQSLmENZZgbAEI3qi9yHkuXAXCPbormD8CSJXo=",
|
||||
"lastModified": 1734954597,
|
||||
"narHash": "sha256-QIhd8/0x30gEv8XEE1iAnrdMlKuQ0EzthfDR7Hwl+fk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "de6fc5551121c59c01e2a3d45b277a6d05077bc4",
|
||||
"rev": "def1d472c832d77885f174089b0d34854b007198",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -564,11 +534,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1743813633,
|
||||
"narHash": "sha256-BgkBz4NpV6Kg8XF7cmHDHRVGZYnKbvG0Y4p+jElwxaM=",
|
||||
"lastModified": 1734991663,
|
||||
"narHash": "sha256-8T660guvdaOD+2/Cj970bWlQwAyZLKrrbkhYOFcY1YE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7819a0d29d1dd2bc331bec4b327f0776359b1fa6",
|
||||
"rev": "6c90912761c43e22b6fb000025ab96dd31c971ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -611,11 +581,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1743827369,
|
||||
"narHash": "sha256-rpqepOZ8Eo1zg+KJeWoq1HAOgoMCDloqv5r2EAa9TSA=",
|
||||
"lastModified": 1734649271,
|
||||
"narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "42a1c966be226125b48c384171c44c651c236c22",
|
||||
"rev": "d70bd19e0a38ad4790d3913bf08fcbfc9eeca507",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -640,11 +610,11 @@
|
|||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743856924,
|
||||
"narHash": "sha256-CgCbUGd9y639PfcuzA0TrA6O5N1ICl+mB95+qTG52+E=",
|
||||
"lastModified": 1734784342,
|
||||
"narHash": "sha256-uap4LcvjpTz5WTgDfQYtL3QCpGmtee7DuD5mB8AIiLw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "d209a04d349febe85c777078ca2eeea5e8bbc8a1",
|
||||
"rev": "334947672f1eb05488e69657b9c412230bd658b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -699,11 +669,11 @@
|
|||
"treefmt-nix": "treefmt-nix_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743941131,
|
||||
"narHash": "sha256-8nTzPXoAQConOnOqG2ZxB8nuUvrGkPuNuqVao9ZNXcI=",
|
||||
"lastModified": 1735130532,
|
||||
"narHash": "sha256-efntkb+ydFSI2kvLn6SURQEp4KnThRGZ2eeJHiKL93o=",
|
||||
"owner": "nix-community",
|
||||
"repo": "NUR",
|
||||
"rev": "aa39cbcf5d0bec9b2b3205ae82baa7aac4d6042c",
|
||||
"rev": "b9f4b07220fa430240dba1825cdac9d673dedf55",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -722,11 +692,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743683223,
|
||||
"narHash": "sha256-LdXtHFvhEC3S64dphap1pkkzwjErbW65eH1VRerCUT0=",
|
||||
"lastModified": 1733773348,
|
||||
"narHash": "sha256-Y47y+LesOCkJaLvj+dI/Oa6FAKj/T9sKVKDXLNsViPw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "56a49ffef2908dad1e9a8adef1f18802bc760962",
|
||||
"rev": "3051be7f403bff1d1d380e4612f0c70675b44fc9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -740,13 +710,14 @@
|
|||
"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",
|
||||
"unixpkgs": "unixpkgs"
|
||||
"stylix": "stylix"
|
||||
}
|
||||
},
|
||||
"scss-reset": {
|
||||
|
@ -771,10 +742,8 @@
|
|||
"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": [
|
||||
|
@ -786,11 +755,11 @@
|
|||
"tinted-tmux": "tinted-tmux"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743892916,
|
||||
"narHash": "sha256-RWvfosAHobUiGMhWIS915WF4TsrQYDXv1gJk59TLAdU=",
|
||||
"lastModified": 1734110444,
|
||||
"narHash": "sha256-fp1iV2JldCSvz+7ODzXYUkQ+H7zyiWw5E0MQ4ILC4vw=",
|
||||
"owner": "danth",
|
||||
"repo": "stylix",
|
||||
"rev": "aebfec1998ebbc087de0104e4a4cec99ec1e3f7a",
|
||||
"rev": "9015d5d0d5d100f849129c43d257b827d300b089",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -882,11 +851,11 @@
|
|||
"tinted-tmux": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1743296873,
|
||||
"narHash": "sha256-8IQulrb1OBSxMwdKijO9fB70ON//V32dpK9Uioy7FzY=",
|
||||
"lastModified": 1729501581,
|
||||
"narHash": "sha256-1ohEFMC23elnl39kxWnjzH1l2DFWWx4DhFNNYDTYt54=",
|
||||
"owner": "tinted-theming",
|
||||
"repo": "tinted-tmux",
|
||||
"rev": "af5152c8d7546dfb4ff6df94080bf5ff54f64e3a",
|
||||
"rev": "f0e7f7974a6441033eb0a172a0342e96722b4f14",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -903,11 +872,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743748085,
|
||||
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=",
|
||||
"lastModified": 1734704479,
|
||||
"narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d",
|
||||
"rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -936,21 +905,6 @@
|
|||
"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",
|
||||
|
|
29
flake.nix
29
flake.nix
|
@ -4,7 +4,8 @@
|
|||
inputs = {
|
||||
# Packages
|
||||
nixpkgs.url = "nixpkgs/nixos-24.11";
|
||||
unixpkgs.url = "nixpkgs/master";
|
||||
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
|
||||
# OS
|
||||
disko = {
|
||||
url = "disko";
|
||||
|
@ -39,7 +40,6 @@
|
|||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
unixpkgs,
|
||||
disko,
|
||||
nix-on-droid,
|
||||
flake-utils,
|
||||
|
@ -56,19 +56,6 @@
|
|||
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 = {
|
||||
|
@ -83,11 +70,7 @@
|
|||
}:
|
||||
nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
specialArgs = attrs // {
|
||||
upkgs = import unixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
};
|
||||
specialArgs = attrs;
|
||||
modules = modules ++ [
|
||||
self.nixosModules.dotfiles
|
||||
# nur.modules.nixos.default
|
||||
|
@ -133,7 +116,6 @@
|
|||
# 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)
|
||||
];
|
||||
}
|
||||
);
|
||||
|
@ -171,7 +153,6 @@
|
|||
runtimeInputs = with pkgs; [
|
||||
nix-output-monitor
|
||||
nixos-rebuild
|
||||
jq
|
||||
];
|
||||
text = builtins.readFile ./os/rebuild.sh;
|
||||
}
|
||||
|
@ -212,9 +193,9 @@
|
|||
};
|
||||
nixOnDroidConfigurations.sprinkles = lib.nixOnDroidConfiguration { };
|
||||
# Fake systems
|
||||
nixosConfigurations.morton = lib.nixosSystem {
|
||||
nixosConfigurations.abavorana = lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [ ./morton/standin.nix ];
|
||||
modules = [ ./abavorana/standin.nix ];
|
||||
};
|
||||
nixosConfigurations.sprinkles = lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
|
@ -27,8 +28,7 @@ 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,10 +79,6 @@ 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 {
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
{
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
{
|
||||
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 = "/";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
|
@ -2,8 +2,12 @@
|
|||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
labellenixpkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
labellepkgs = import labellenixpkgs { inherit (pkgs) system; };
|
||||
in
|
||||
{
|
||||
frogeye.hooks.lock = ''
|
||||
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
|
||||
|
@ -175,10 +179,19 @@
|
|||
translate-shell.enable = true; # TODO Cool config?
|
||||
};
|
||||
home = {
|
||||
stateVersion = "24.11";
|
||||
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";
|
||||
packages = with pkgs; [
|
||||
# Terminal utils
|
||||
coreutils-full
|
||||
coreutils
|
||||
moreutils
|
||||
rename
|
||||
which
|
||||
|
@ -220,14 +233,13 @@
|
|||
# toolbox
|
||||
imagemagick
|
||||
numbat
|
||||
bc
|
||||
|
||||
# hardware
|
||||
pciutils
|
||||
usbutils
|
||||
dmidecode
|
||||
lshw
|
||||
labelle # Label printer
|
||||
labellepkgs.labelle # Label printer
|
||||
|
||||
# Locker
|
||||
(pkgs.writeShellApplication {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
./homepage.nix
|
||||
];
|
||||
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||
home.sessionVariables = {
|
||||
BROWSER = "qutebrowser";
|
||||
|
@ -24,6 +21,7 @@
|
|||
profiles.hm = {
|
||||
extensions = with pkgs.nur.repos.rycee.firefox-addons; [
|
||||
(buildFirefoxXpiAddon {
|
||||
|
||||
pname = "onetab";
|
||||
version = "0.1.0";
|
||||
addonId = "onetab@nated";
|
||||
|
@ -75,6 +73,7 @@
|
|||
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
|
||||
|
@ -147,13 +146,17 @@
|
|||
show = "never";
|
||||
tabs_are_windows = true;
|
||||
};
|
||||
url.open_base_url = true;
|
||||
url = rec {
|
||||
open_base_url = true;
|
||||
start_pages = lib.mkDefault "https://geoffrey.frogeye.fr/blank.html";
|
||||
default_page = start_pages;
|
||||
};
|
||||
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 = "en-GB,en;q=0.9";
|
||||
headers.accept_language = "fr-FR, fr;q=0.9, en-GB;q=0.8, en-US;q=0.7, en;q=0.6";
|
||||
tls.certificate_errors = "ask-block-thirdparty";
|
||||
javascript.clipboard = "access"; # copy-paste is fine
|
||||
};
|
||||
|
@ -184,8 +187,7 @@
|
|||
};
|
||||
};
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<!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>
|
|
@ -1,110 +0,0 @@
|
|||
{
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
|
@ -113,12 +113,19 @@
|
|||
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 = {
|
||||
|
@ -138,7 +145,6 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
keynav.enable = true;
|
||||
};
|
||||
|
||||
home = {
|
||||
|
@ -172,14 +178,13 @@
|
|||
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
|
||||
|
|
1058
hm/desktop/frobar/.dev/new.py
Normal file
1058
hm/desktop/frobar/.dev/new.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -22,13 +22,17 @@ in
|
|||
# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs.
|
||||
pkgs.python3Packages.buildPythonApplication rec {
|
||||
pname = "frobar";
|
||||
version = "3.0";
|
||||
version = "2.0";
|
||||
|
||||
propagatedBuildInputs = with pkgs.python3Packages; [
|
||||
coloredlogs # old only
|
||||
i3ipc
|
||||
mpd2
|
||||
notmuch
|
||||
psutil
|
||||
pulsectl # old only
|
||||
pulsectl-asyncio
|
||||
pygobject3
|
||||
pyinotify
|
||||
rich
|
||||
];
|
||||
nativeBuildInputs =
|
||||
|
@ -37,15 +41,7 @@ pkgs.python3Packages.buildPythonApplication rec {
|
|||
wirelesstools
|
||||
playerctl
|
||||
]);
|
||||
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
|
||||
];
|
||||
makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}" ];
|
||||
|
||||
src = ./.;
|
||||
}
|
||||
|
|
|
@ -1,146 +1,77 @@
|
|||
import rich.color
|
||||
import rich.logging
|
||||
import rich.terminal_theme
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import frobar.common
|
||||
import frobar.providers
|
||||
from frobar.common import Alignment
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
def run() -> None:
|
||||
Bar.init()
|
||||
Updater.init()
|
||||
|
||||
def base16_color(color: int) -> tuple[int, int, int]:
|
||||
hexa = FROGARIZED[color]
|
||||
return tuple(rich.color.parse_rgb_hex(hexa[1:]))
|
||||
# Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT)
|
||||
# Bar.addSectionAll(fp.NetworkProvider(theme=2), BarGroupType.RIGHT)
|
||||
|
||||
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
|
||||
],
|
||||
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,
|
||||
)
|
||||
|
||||
bar = frobar.common.Bar(theme=theme)
|
||||
dualScreen = len(bar.children) > 1
|
||||
leftPreferred = 0 if dualScreen else None
|
||||
rightPreferred = 1 if dualScreen else None
|
||||
# TODO Middle
|
||||
Bar.addSectionAll(fp.MprisProvider(theme=9), BarGroupType.LEFT)
|
||||
# Bar.addSectionAll(fp.MpdProvider(theme=9), BarGroupType.LEFT)
|
||||
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
|
||||
|
||||
workspaces_suffixes = "▲■"
|
||||
workspaces_names = dict(
|
||||
(str(i + 1), f"{i+1} {c}") for i, c in enumerate(workspaces_suffixes)
|
||||
)
|
||||
# TODO Computer modes
|
||||
|
||||
color = rich.color.Color.parse
|
||||
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)
|
||||
|
||||
bar.addProvider(
|
||||
frobar.providers.I3ModeProvider(color=color("red")), alignment=Alignment.LEFT
|
||||
)
|
||||
bar.addProvider(
|
||||
frobar.providers.I3WorkspacesProvider(custom_names=workspaces_names),
|
||||
alignment=Alignment.LEFT,
|
||||
)
|
||||
# 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)
|
||||
|
||||
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,
|
||||
)
|
||||
# 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,
|
||||
# )
|
||||
|
||||
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
|
||||
)
|
||||
TIME_THEME = 4
|
||||
Bar.addSectionAll(fp.TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
|
||||
|
||||
bar.launch()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# Bar.run()
|
||||
|
|
|
@ -1,629 +1,5 @@
|
|||
import asyncio
|
||||
import collections
|
||||
import datetime
|
||||
import enum
|
||||
import logging
|
||||
import signal
|
||||
import typing
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
import gi.events
|
||||
import gi.repository.GLib
|
||||
import i3ipc
|
||||
import i3ipc.aio
|
||||
import rich.color
|
||||
import rich.logging
|
||||
import rich.terminal_theme
|
||||
import threading
|
||||
|
||||
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
|
||||
notBusy = threading.Event()
|
||||
|
|
756
hm/desktop/frobar/frobar/display.py
Normal file
756
hm/desktop/frobar/frobar/display.py
Normal file
|
@ -0,0 +1,756 @@
|
|||
#!/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
240
hm/desktop/frobar/frobar/updaters.py
Normal file
240
hm/desktop/frobar/frobar/updaters.py
Normal file
|
@ -0,0 +1,240 @@
|
|||
#!/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")
|
|
@ -2,17 +2,19 @@ from setuptools import setup
|
|||
|
||||
setup(
|
||||
name="frobar",
|
||||
version="3.0",
|
||||
version="2.0",
|
||||
install_requires=[
|
||||
"coloredlogs",
|
||||
"notmuch",
|
||||
"i3ipc",
|
||||
"python-mpd2",
|
||||
"psutil",
|
||||
"pulsectl-asyncio",
|
||||
"pygobject3",
|
||||
"rich",
|
||||
"pulsectl",
|
||||
"pyinotify",
|
||||
],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"frobar = frobar:main",
|
||||
"frobar = frobar:run",
|
||||
]
|
||||
},
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ let
|
|||
in
|
||||
{
|
||||
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||
home.packages = [
|
||||
home.packages = with pkgs; [
|
||||
(pkgs.writeShellApplication {
|
||||
name = "xlock";
|
||||
text = ''
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Config mentions pdfpc, although the last thing I used was Impressive, even made patches to it.
|
||||
# UPST Add Impressive to nixpkgs
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
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=";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -37,12 +37,33 @@
|
|||
# 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) [
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
{
|
||||
...
|
||||
}:
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./3d.nix
|
||||
./c.nix
|
||||
./common.nix
|
||||
./go.nix
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Untested post-nix
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
sub-langs = "en,fr";
|
||||
write-auto-subs = true;
|
||||
write-subs = true;
|
||||
mark-watched = true; # Give something to creators, maybe
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -40,24 +39,41 @@
|
|||
|
||||
# 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 (?)
|
||||
|
||||
# Misc
|
||||
rustdesk-flutter
|
||||
]
|
||||
++ 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
|
||||
]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
jjuinixpkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.programs.git;
|
||||
jjuipkgs = import jjuinixpkgs { inherit (pkgs) system; };
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
home.packages = [
|
||||
pkgs.jjui
|
||||
jjuipkgs.jjui
|
||||
pkgs.lazyjj
|
||||
(pkgs.writeShellApplication {
|
||||
name = "git-sync";
|
||||
|
@ -105,7 +107,7 @@ in
|
|||
diff-editor = "meld-3";
|
||||
merge-editor = "meld";
|
||||
};
|
||||
signing = lib.mkIf (!builtins.isNull cfg.signing) {
|
||||
signing = {
|
||||
sign-all = true;
|
||||
backend = "gpg";
|
||||
inherit (cfg.signing) key;
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
};
|
||||
publicKeys = [
|
||||
{
|
||||
# Always install my own public key
|
||||
source = builtins.fetchurl {
|
||||
url = "https://keys.openpgp.org/vks/v1/by-fingerprint/4FBA930D314A03215E2CDB0A8312C8CAC1BAC289";
|
||||
sha256 = "sha256:10y9xqcy1vyk2p8baay14p3vwdnlwynk0fvfbika65hz2z8yw2cm";
|
||||
|
@ -45,14 +44,12 @@
|
|||
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;
|
||||
};
|
||||
|
|
|
@ -61,6 +61,6 @@
|
|||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
};
|
||||
# TODO Should make a nix package wrapper instead, so it also works from rofi
|
||||
# TODO Should make a nix package wrapper instead, so it also works from dmenu
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
config = {
|
||||
config = lib.mkIf config.programs.less.enable {
|
||||
programs.powerline-go = {
|
||||
enable = true;
|
||||
modules = [
|
||||
"user"
|
||||
"host"
|
||||
"direnv"
|
||||
"venv"
|
||||
"cwd"
|
||||
"perms"
|
||||
"nix-shell"
|
||||
"venv"
|
||||
"git"
|
||||
];
|
||||
modulesRight = [
|
||||
|
|
|
@ -58,4 +58,15 @@ then
|
|||
|
||||
fi
|
||||
|
||||
ssh -t "$@" "$(cat "${CACHE_DIR}/cmd")"
|
||||
# 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")"
|
||||
|
|
75
hm/scripts/replayGain
Executable file
75
hm/scripts/replayGain
Executable file
|
@ -0,0 +1,75 @@
|
|||
#!/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
Normal file → Executable file
0
hm/scripts/updateCompressedMusic
Normal file → Executable file
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
@ -39,23 +40,10 @@ 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
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# 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" ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
@ -10,7 +12,7 @@
|
|||
programs.ssh = {
|
||||
enable = true;
|
||||
controlMaster = "auto";
|
||||
controlPersist = "60s"; # Enough to cache Ansible stuff, not too long so I don't have remember which shenanigans I did with my last connection
|
||||
controlPersist = "60s"; # TODO Default is 10minutes... makes more sense no?
|
||||
# Ping the server frequently enough so it doesn't think we left (non-spoofable)
|
||||
serverAliveInterval = 30;
|
||||
matchBlocks."*" = {
|
||||
|
@ -18,7 +20,8 @@
|
|||
# as it is kinda a security concern
|
||||
forwardAgent = false;
|
||||
# Restrict terminal features (servers don't necessarily have the terminfo for my cutting edge terminal)
|
||||
setEnv.TERM = "xterm-256color";
|
||||
sendEnv = [ "!TERM" ];
|
||||
# TODO Why not TERM=xterm-256color?
|
||||
extraOptions = {
|
||||
# Check SSHFP records
|
||||
VerifyHostKeyDNS = "yes";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
stylix,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
@ -30,7 +32,7 @@ in
|
|||
{
|
||||
config = {
|
||||
programs.nixvim = {
|
||||
extraPlugins = [
|
||||
extraPlugins = with pkgs.vimPlugins; [
|
||||
# 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
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
system.stateVersion = "24.11";
|
||||
system.stateVersion = "24.05";
|
||||
terminal.font = "${
|
||||
pkgs.nerdfonts.override {
|
||||
fonts = [ "DejaVuSansMono" ];
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
@ -43,21 +44,15 @@
|
|||
];
|
||||
|
||||
i18n = {
|
||||
defaultLocale = "nl_NL.UTF-8";
|
||||
defaultLocale = "en_GB.UTF-8";
|
||||
extraLocaleSettings = {
|
||||
LC_TIME = "en_DK.UTF-8"; # Use YYYY-MM-DD, Thunderbird & qutebrowser use own settings
|
||||
LC_TIME = "en_DK.UTF-8"; # Should give YYYY-MM-DD but doesn't work for browser & Thunderbird :(
|
||||
};
|
||||
};
|
||||
|
||||
nix = {
|
||||
gc = {
|
||||
automatic = true;
|
||||
persistent = true;
|
||||
options = "--delete-older-than 14d";
|
||||
};
|
||||
package = pkgs.lix;
|
||||
settings = {
|
||||
auto-optimise-store = true;
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
|
@ -79,12 +74,15 @@
|
|||
};
|
||||
|
||||
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.
|
||||
|
@ -100,6 +98,6 @@
|
|||
# TODO Hibernation?
|
||||
|
||||
# Use defaults from
|
||||
system.stateVersion = "24.11";
|
||||
system.stateVersion = "24.05";
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Need nvidia proprietary drivers to work
|
||||
{
|
||||
pkgs,
|
||||
nixpkgs,
|
||||
config,
|
||||
lib,
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
./battery.nix
|
||||
./boot
|
||||
./ccc
|
||||
./common.nix
|
||||
./cuda
|
||||
./common.nix
|
||||
./desktop
|
||||
./dev
|
||||
disko.nixosModules.disko
|
||||
./dns
|
||||
./gaming
|
||||
./geoffrey.nix
|
||||
./password
|
||||
|
@ -19,7 +18,6 @@
|
|||
./remote-builds
|
||||
./style
|
||||
./syncthing
|
||||
./time
|
||||
./wireless
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,26 +4,25 @@
|
|||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
setupScript = "${
|
||||
pkgs.writeShellApplication {
|
||||
name = "greeter-setup-script";
|
||||
runtimeInputs = [ pkgs.autorandr ];
|
||||
text = ''
|
||||
autorandr --change
|
||||
'';
|
||||
}
|
||||
}/bin/greeter-setup-script";
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (builtins.length config.frogeye.desktop.x11_screens > 1) {
|
||||
config = {
|
||||
services = {
|
||||
autorandr.enable = true;
|
||||
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}
|
||||
'';
|
||||
xserver.displayManager.lightdm.extraConfig = ''
|
||||
[Seat:*]
|
||||
display-setup-script = ${setupScript}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
...
|
||||
}:
|
||||
{
|
||||
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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
{ 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" ];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,78 +4,42 @@ 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
|
||||
error "Action should be one of: build, test, boot, switch, confirm"
|
||||
echo "Action should be one of: build, test, boot, switch, confirm"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
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
|
||||
# Build, looking nice
|
||||
tmpdir="$(mktemp -d)"
|
||||
# sudo so the eval cache is shared with nixos-rebuild
|
||||
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)
|
||||
sudo nom build "$self#nixosConfigurations.$HOSTNAME.config.system.build.toplevel" -o "$tmpdir/toplevel" "$@"
|
||||
toplevel="$(readlink -f "$tmpdir/toplevel")"
|
||||
rm -rf "$tmpdir"
|
||||
|
||||
info "Building"
|
||||
sudo nom build "$derivation^*" --no-link "$@"
|
||||
|
||||
info "Showing diff"
|
||||
# Show diff
|
||||
nvd diff "$(readlink -f /nix/var/nix/profiles/system)" "$toplevel"
|
||||
|
||||
info "Figuring current specialisation"
|
||||
systemLink=
|
||||
currentSystem="$(readlink -f /run/current-system)"
|
||||
while read -r system
|
||||
do
|
||||
if [ "$(readlink -f "$system")" = "$currentSystem" ]
|
||||
then
|
||||
systemLink="$system"
|
||||
break
|
||||
fi
|
||||
done <<< "$(ls -d /nix/var/nix/profiles/system-*-link{,/specialisation/*})"
|
||||
|
||||
# Figure out specialisation
|
||||
specialisationArgs=()
|
||||
if [ -n "$systemLink" ]
|
||||
then
|
||||
specialisation=$(echo "$systemLink" | cut -d/ -f8)
|
||||
if [ -n "$specialisation" ]
|
||||
currentSystem="$(readlink -f /run/current-system)"
|
||||
while read -r specialisation
|
||||
do
|
||||
if [ "$(readlink -f "/nix/var/nix/profiles/system/specialisation/$specialisation")" = "$currentSystem" ]
|
||||
then
|
||||
specialisationArgs=("--specialisation" "$specialisation")
|
||||
fi
|
||||
else
|
||||
warn "Could not find link for $currentSystem, will switch to non-specialized system"
|
||||
fi
|
||||
|
||||
|
||||
done <<< "$(ls /nix/var/nix/profiles/system/specialisation)"
|
||||
|
||||
# Apply
|
||||
confirm="n"
|
||||
if [ "$verb" = "confirm" ]
|
||||
then
|
||||
warn "Apply configuration? [y/N]"
|
||||
echo "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
|
||||
|
@ -83,11 +47,10 @@ fi
|
|||
# Set as boot
|
||||
if [ "$verb" = "confirm" ]
|
||||
then
|
||||
warn "Set configuration as boot? [y/N]"
|
||||
echo "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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
|
@ -7,55 +8,66 @@ 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
|
||||
# TODO Proper configuration option instead of pile of defs and hacks
|
||||
# MANU ssh-keygen -y -f /etc/ssh/ssh_host_ed25519_key | base64 -w0
|
||||
vivariumBuilders = [
|
||||
{
|
||||
hostName = "morton.frogeye.fr";
|
||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEetvIp4ZrP+ofXNDypnrLxdU034SBYg7fx9FxClDJA3";
|
||||
hostName = "abavorana.frogeye.fr";
|
||||
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU5iNzcrS01tRHI0MVhZdmZITXQvK3NHMkJCSEIzYUl4M045WDNVejhFaUogZ2VvZmZyZXlAY3VyYWNhbwo=";
|
||||
supportedFeatures = [
|
||||
"nixos-test"
|
||||
"benchmark"
|
||||
"big-parallel"
|
||||
"kvm"
|
||||
];
|
||||
maxJobs = 12; # 8 cores, 16 with hyperthreading, trying not to overload the thing
|
||||
maxJobs = 8;
|
||||
}
|
||||
{
|
||||
hostName = "ludwig.clowncar.frogeye.fr";
|
||||
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU41SXZhMzNXeGplN095cHVEUHBSakFNMTlvRUtEVDRiYlpUTm82V1FLZTAgZ2VvZmZyZXlAY3VyYWNhbwo=";
|
||||
maxJobs = 4;
|
||||
}
|
||||
];
|
||||
# MANU pass vivarium/lemmy/remote-builds/cache | nix key convert-secret-to-public | cat
|
||||
publicKeys = [
|
||||
"morton.frogeye.fr:rSjbCZ4mgXkb+ENKI7sk/KIbftlQzCTQA7pWkdfS2r4="
|
||||
"abavorana.frogeye.fr:rcKZ9gwaIQLcst/vbhbF7meUQD5sveT2QQN4a+Zo1BM="
|
||||
"ludwig.clowncar.frogeye.fr:jTlN0fCOLU49M3LQw5j/u++Gmwrsv3m9RGs0slSg6r0="
|
||||
];
|
||||
in
|
||||
{
|
||||
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)
|
||||
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
|
||||
'';
|
||||
};
|
||||
nix = {
|
||||
buildMachines = builtins.map (
|
||||
vivariumBuilder:
|
||||
lib.attrsets.filterAttrs (k: v: k != "publicKey") (vivariumBuilderDefault // vivariumBuilder)
|
||||
vivariumBuilder: vivariumBuilderDefault // vivariumBuilder
|
||||
) vivariumBuilders;
|
||||
distributedBuilds = true;
|
||||
distributedBuilds = false;
|
||||
settings = {
|
||||
builders-use-substitutes = true;
|
||||
trusted-public-keys = publicKeys;
|
||||
substituters = builtins.map (
|
||||
trusted-substituters = builtins.map (
|
||||
builder: "${builder.protocol}://${builder.sshUser}@${builder.hostName}"
|
||||
) config.nix.buildMachines;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
stylix,
|
||||
...
|
||||
|
|
|
@ -24,9 +24,7 @@ let
|
|||
);
|
||||
allDevices = nixosDevices;
|
||||
syncingDevices = builtins.filter (device: device.syncthing.id != null) allDevices;
|
||||
peerDevices = builtins.filter (
|
||||
device: device.syncthing.id != config.frogeye.syncthing.id
|
||||
) syncingDevices;
|
||||
peerDevices = builtins.filter (device: device.name != config.frogeye.name) syncingDevices;
|
||||
|
||||
# Can't use the module's folders enable option, as it still requests things somehow
|
||||
allFolders = builtins.attrValues config.frogeye.folders;
|
||||
|
@ -36,58 +34,12 @@ 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;
|
||||
|
@ -111,6 +63,8 @@ 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
|
||||
);
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
@ -53,6 +54,15 @@ 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 = ''
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
nixos-hardware,
|
||||
...
|
||||
}:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
|
Loading…
Reference in a new issue