Compare commits
254 commits
Author | SHA1 | Date | |
---|---|---|---|
Geoffrey Frogeye | 84268f3c47 | ||
Geoffrey Frogeye | 22b844df2c | ||
Geoffrey Frogeye | 0d1c2f1975 | ||
Geoffrey Frogeye | a08d09328f | ||
Geoffrey Frogeye | 441177a263 | ||
Geoffrey Frogeye | cde7df57eb | ||
Geoffrey Frogeye | 1615af8730 | ||
Geoffrey Frogeye | 9389d1d284 | ||
Geoffrey Frogeye | a7e2b49bea | ||
Geoffrey Frogeye | d2dbb5bbde | ||
Geoffrey Frogeye | 64ab21f7dd | ||
Geoffrey Frogeye | c2068a30ff | ||
Geoffrey Frogeye | e305c6b1de | ||
Geoffrey Frogeye | 5804ff31ff | ||
Geoffrey Frogeye | 6618fbee9d | ||
Geoffrey Frogeye | dee90d9a96 | ||
Geoffrey Frogeye | 812e5acf6c | ||
Geoffrey Frogeye | 768b38b87f | ||
Geoffrey Frogeye | 6644e85c30 | ||
Geoffrey Frogeye | 1ae7d6b447 | ||
Geoffrey Frogeye | 01934563a5 | ||
Geoffrey Frogeye | 171232cb2e | ||
Geoffrey Frogeye | 36df032ecd | ||
Geoffrey Frogeye | a43209a902 | ||
Geoffrey Frogeye | 38ff39bc78 | ||
Geoffrey Frogeye | fc744fd73b | ||
Geoffrey Frogeye | f633761d54 | ||
Geoffrey Frogeye | ae61f0c6d4 | ||
Geoffrey Frogeye | 532b3628d3 | ||
Geoffrey Frogeye | 83c24f2fb2 | ||
Geoffrey Frogeye | 830552f8c3 | ||
Geoffrey Frogeye | 4d39ac0769 | ||
Geoffrey Frogeye | c375cb2e11 | ||
Geoffrey Frogeye | f81fd6bfd2 | ||
Geoffrey Frogeye | c7535d8ed8 | ||
Geoffrey Frogeye | 6690f3aa0d | ||
Geoffrey Frogeye | 7d60269b49 | ||
Geoffrey Frogeye | 139d3a3ea8 | ||
Geoffrey Frogeye | 86f9f75bd7 | ||
Geoffrey Frogeye | f011b376bb | ||
Geoffrey Frogeye | ce636f08ff | ||
Geoffrey Frogeye | 0fc6a51cb0 | ||
Geoffrey Frogeye | c90590640f | ||
Geoffrey Frogeye | 2329d67d16 | ||
Geoffrey Frogeye | f664b51c85 | ||
Geoffrey Frogeye | 63531c4bc6 | ||
Geoffrey Frogeye | 77ec4574f1 | ||
Geoffrey Frogeye | a6becdae70 | ||
Geoffrey Frogeye | bafb22c573 | ||
Geoffrey Frogeye | dd2e510473 | ||
Geoffrey Frogeye | ce516fffe9 | ||
Geoffrey Frogeye | 88e63aaf7f | ||
Geoffrey Frogeye | a6a1e32ade | ||
Geoffrey Frogeye | 7b9c4fb004 | ||
Geoffrey Frogeye | 81b1307609 | ||
Geoffrey Frogeye | 91e71bec07 | ||
Geoffrey Frogeye | ea1c390bc0 | ||
Geoffrey Frogeye | 498357fffc | ||
Geoffrey Frogeye | 91df3670f6 | ||
Geoffrey Frogeye | 8edb670486 | ||
Geoffrey Frogeye | b7d56a3118 | ||
Geoffrey Frogeye | 8909b2bfa3 | ||
Geoffrey Frogeye | ac43ef6548 | ||
Geoffrey Frogeye | 93d55c8c5c | ||
Geoffrey Frogeye | eab20b4339 | ||
Geoffrey Frogeye | a39118d439 | ||
Geoffrey Frogeye | 5462fa43fa | ||
Geoffrey Frogeye | 795ed034f8 | ||
Geoffrey Frogeye | 445c2b8a99 | ||
Geoffrey Frogeye | e09774c4ca | ||
Geoffrey Frogeye | a489830949 | ||
Geoffrey Frogeye | 42034eb5d8 | ||
Geoffrey Frogeye | d6d3df65df | ||
Geoffrey Frogeye | a3fcaf9d27 | ||
Geoffrey Frogeye | 2951280faa | ||
Geoffrey Frogeye | 0d047d3e46 | ||
Geoffrey Frogeye | c4058e8102 | ||
Geoffrey Frogeye | aa5847ec76 | ||
Geoffrey Frogeye | 6570e71eca | ||
Geoffrey Frogeye | 28ab3b0665 | ||
Geoffrey Frogeye | 10b48b22e1 | ||
Geoffrey Frogeye | c31f1ba8dd | ||
Geoffrey Frogeye | 865bffa641 | ||
Geoffrey Frogeye | d5917b1264 | ||
Geoffrey Frogeye | 17f0ba3370 | ||
Geoffrey Frogeye | 7b9d9053bf | ||
Geoffrey Frogeye | f72112f332 | ||
Geoffrey Frogeye | 92ea60bbc8 | ||
Geoffrey Frogeye | 96dea140be | ||
Geoffrey Frogeye | bc53468373 | ||
Geoffrey Frogeye | 5297f8478a | ||
Geoffrey Frogeye | 48bf80f1c0 | ||
Geoffrey Frogeye | 3479927d32 | ||
Geoffrey Frogeye | 71385d9ba9 | ||
Geoffrey Frogeye | fe33f30bce | ||
Geoffrey Frogeye | a95ae5f568 | ||
Geoffrey Frogeye | de187c6044 | ||
Geoffrey Frogeye | 2804086233 | ||
Geoffrey Frogeye | 88e0a1eb09 | ||
Geoffrey Frogeye | 995c115c90 | ||
Geoffrey Frogeye | 7c6e8adbed | ||
Geoffrey Frogeye | 536eee36ad | ||
Geoffrey Frogeye | 2a3624af09 | ||
Geoffrey Frogeye | 4ff4e0cc99 | ||
Geoffrey Frogeye | 6e8d8b43c2 | ||
Geoffrey Frogeye | 7c74c5e1d9 | ||
Geoffrey Frogeye | 552e1c1cf2 | ||
Geoffrey Frogeye | 056e3447e4 | ||
Geoffrey Frogeye | bd84dd7fd7 | ||
Geoffrey Frogeye | cc46352873 | ||
Geoffrey Frogeye | c770380328 | ||
Geoffrey Frogeye | a2e15e8c33 | ||
Geoffrey Frogeye | bdabf30728 | ||
Geoffrey Frogeye | 176be4f218 | ||
Geoffrey Frogeye | 82d5e8a466 | ||
Geoffrey Frogeye | 7a612754f6 | ||
Geoffrey Frogeye | 0663e3755b | ||
Geoffrey Frogeye | bd538785b8 | ||
Geoffrey Frogeye | c4bb02b16e | ||
Geoffrey Frogeye | e68be9e665 | ||
Geoffrey Frogeye | 836f8ee8b4 | ||
Geoffrey Frogeye | b0168f4354 | ||
Geoffrey Frogeye | 0e1d387069 | ||
Geoffrey Frogeye | 7b8ff04f5d | ||
Geoffrey Frogeye | d276581d94 | ||
Geoffrey Frogeye | 5924bd59c6 | ||
Geoffrey Frogeye | 173a231556 | ||
Geoffrey Frogeye | 98af492b75 | ||
Geoffrey Frogeye | 6e15aa2ea7 | ||
Geoffrey Frogeye | 86019601f8 | ||
Geoffrey Frogeye | 028cadb6ab | ||
Geoffrey Frogeye | 8005cbfbc1 | ||
Geoffrey Frogeye | b7d8797a6d | ||
Geoffrey Frogeye | 96ddd61320 | ||
Geoffrey Frogeye | cbc9a87f09 | ||
Geoffrey Frogeye | ab30bdf6a8 | ||
Geoffrey Frogeye | 067cfc3d7a | ||
Geoffrey Frogeye | 044318babc | ||
Geoffrey Frogeye | c319ee1394 | ||
Geoffrey Frogeye | a3999cc9b1 | ||
Geoffrey Frogeye | 1615abd814 | ||
Geoffrey Frogeye | 0c59a713da | ||
Geoffrey Frogeye | 4e68c3ccf7 | ||
Geoffrey Frogeye | 5148643a64 | ||
Geoffrey Frogeye | 4358f717d0 | ||
25c00be8fd | |||
fe468eebd7 | |||
7973e2ccd7 | |||
76a594ca9f | |||
Geoffrey Frogeye | 8d1d15a08e | ||
Geoffrey Frogeye | b02ec1c28c | ||
Geoffrey Frogeye | ce5a099899 | ||
Geoffrey Frogeye | 20dd333799 | ||
Geoffrey Frogeye | f04f8160db | ||
Geoffrey Frogeye | 35783ea086 | ||
Geoffrey Frogeye | 440b1e0563 | ||
Geoffrey Frogeye | a0d7e43a9d | ||
Geoffrey Frogeye | b3f1d95634 | ||
Geoffrey Frogeye | 82bafb3428 | ||
Geoffrey Frogeye | 2fa993ad2d | ||
Geoffrey Frogeye | 14f7199d65 | ||
Geoffrey Frogeye | c7c2c89f15 | ||
Geoffrey Frogeye | 2b76db290c | ||
Geoffrey Frogeye | ac0724d97a | ||
Geoffrey Frogeye | 5d4908d2e2 | ||
Geoffrey Frogeye | ee4e45905a | ||
Geoffrey Frogeye | 097d53807d | ||
Geoffrey Frogeye | 833320e3fa | ||
Geoffrey Frogeye | 448a154d74 | ||
Geoffrey Frogeye | 8476bbde12 | ||
Geoffrey Frogeye | bf803d18a6 | ||
Geoffrey Frogeye | 6e176fe61b | ||
Geoffrey Frogeye | f65f6853ee | ||
Geoffrey Frogeye | 972dcaae1f | ||
Geoffrey Frogeye | 659f6ae806 | ||
Geoffrey Frogeye | e5b034781d | ||
Geoffrey Frogeye | 11c1c8d9f1 | ||
Geoffrey Frogeye | 21aed8114f | ||
8e6203ce7d | |||
Geoffrey Frogeye | 16f5a0a9a5 | ||
Geoffrey Frogeye | f30abd991c | ||
Geoffrey Frogeye | c936d859c7 | ||
Geoffrey Frogeye | 5bba711d3c | ||
Geoffrey Frogeye | 9c6a2f69f0 | ||
Geoffrey Frogeye | 85cd61d206 | ||
Geoffrey Frogeye | ffd871299b | ||
Geoffrey Frogeye | ca2dc262b7 | ||
Geoffrey Frogeye | 59db464987 | ||
Geoffrey Frogeye | 0bb5981f3a | ||
5b3c887b41 | |||
Geoffrey Frogeye | dfc8d68495 | ||
Geoffrey Frogeye | 09b201ca24 | ||
Geoffrey Frogeye | 7cd77af9bf | ||
Geoffrey Frogeye | a57c6527ce | ||
Geoffrey Frogeye | 97a3e5f6e4 | ||
Geoffrey Frogeye | 55756e4ae7 | ||
Geoffrey Frogeye | eac22be095 | ||
Geoffrey Frogeye | fdf6725dc9 | ||
Geoffrey Frogeye | 46db2dd34f | ||
Geoffrey Frogeye | bac1813c77 | ||
Geoffrey Frogeye | e56514890d | ||
Geoffrey Frogeye | 30f1880f29 | ||
Geoffrey Frogeye | 770697f9f3 | ||
Geoffrey Frogeye | 65205a2fb8 | ||
Geoffrey Frogeye | 55641fe958 | ||
Geoffrey Frogeye | 6cee16924c | ||
Geoffrey Frogeye | 1dbfd6cf88 | ||
Geoffrey Frogeye | fbde2f5028 | ||
Geoffrey Frogeye | 43e7a5af46 | ||
Geoffrey Frogeye | 1b008c1ae8 | ||
Geoffrey Frogeye | c1d8bc65af | ||
Geoffrey Frogeye | 26e70acb2f | ||
Geoffrey Frogeye | e9a8d16ece | ||
Geoffrey Frogeye | e4c407fb28 | ||
Geoffrey Frogeye | d994dfb9fb | ||
Geoffrey Frogeye | 2ad4bee0f9 | ||
Geoffrey Frogeye | 4412180b3a | ||
Geoffrey Frogeye | 033f411060 | ||
Geoffrey Frogeye | f83806a307 | ||
Geoffrey Frogeye | bf796d9587 | ||
Geoffrey Frogeye | 6e4130fd26 | ||
Geoffrey Frogeye | d325eb2d27 | ||
Geoffrey Frogeye | aeccc22857 | ||
Geoffrey Frogeye | 8f370c5040 | ||
Geoffrey Frogeye | 83b38ddf61 | ||
Geoffrey Frogeye | 42bc007ed4 | ||
Geoffrey Frogeye | 881b22c9b2 | ||
Geoffrey Frogeye | 124df42fd8 | ||
Geoffrey Frogeye | 5360f8ff10 | ||
Geoffrey Frogeye | 4190299030 | ||
Geoffrey Frogeye | 66f3179d41 | ||
Geoffrey Frogeye | ecc6cb983d | ||
Geoffrey Frogeye | e0fb3fcb22 | ||
Geoffrey Frogeye | 597b50ebef | ||
Geoffrey Frogeye | 6d98d85642 | ||
Geoffrey Frogeye | e013bcfdba | ||
Geoffrey Frogeye | 25130195ec | ||
Geoffrey Frogeye | 7506f55468 | ||
Geoffrey Frogeye | 1abf3d503d | ||
Geoffrey Frogeye | c954f0df5f | ||
Geoffrey Frogeye | 3477528dd5 | ||
Geoffrey Frogeye | e2bb686d12 | ||
Geoffrey Frogeye | 74585ec4a7 | ||
Geoffrey Frogeye | 5b70c2f448 | ||
Geoffrey Frogeye | 241ec71350 | ||
Geoffrey Frogeye | 3755ab251d | ||
Geoffrey Frogeye | 0bc0aaa9bf | ||
Geoffrey Frogeye | c7d69cd100 | ||
e1c041368b | |||
Geoffrey Frogeye | 8b78cad60c | ||
Geoffrey Frogeye | becf0c961f | ||
Geoffrey Frogeye | 9362e78f87 | ||
Geoffrey Frogeye | f94e741948 | ||
Geoffrey Frogeye | ec1d120f12 |
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,5 +1,2 @@
|
||||||
*/hm
|
result
|
||||||
*/system
|
|
||||||
*/vm
|
|
||||||
*/vmWithBootLoader
|
|
||||||
*.qcow2
|
*.qcow2
|
||||||
|
|
|
@ -28,7 +28,6 @@ It is built on top of the Nix ecosystem
|
||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
They all have a `-h` flag.
|
They all have a `-h` flag.
|
||||||
Except `add_channels.sh`, which should be removed as soon as I migrate to Flakes.
|
|
||||||
|
|
||||||
## Extensions
|
## Extensions
|
||||||
|
|
||||||
|
|
10
abavorana/standin.nix
Normal file
10
abavorana/standin.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
frogeye = {
|
||||||
|
name = "abavorana";
|
||||||
|
storageSize = "big";
|
||||||
|
syncthing.name = "Abavorana";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# TODO Flakes
|
|
||||||
|
|
||||||
nix-channel --add https://nixos.org/channels/nixos-23.11 nixpkgs
|
|
||||||
nix-channel --add https://github.com/nix-community/home-manager/archive/release-23.11.tar.gz home-manager
|
|
||||||
nix-channel --add https://github.com/NixOS/nixos-hardware/archive/8772491ed75f150f02552c60694e1beff9f46013.tar.gz nixos-hardware
|
|
||||||
nix-channel --update
|
|
70
build_hm.sh
70
build_hm.sh
|
@ -1,70 +0,0 @@
|
||||||
#!/usr/bin/env nix-shell
|
|
||||||
#! nix-shell -i bash
|
|
||||||
#! nix-shell -p bash nix-output-monitor
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
function help {
|
|
||||||
echo "Usage: $0 [-h|-v|-b] profile"
|
|
||||||
echo "Build Home Manager configuration on the local machine."
|
|
||||||
echo
|
|
||||||
echo "Arguments:"
|
|
||||||
echo " profile: Home Manager profile to use"
|
|
||||||
echo
|
|
||||||
echo "Options:"
|
|
||||||
echo " -h: Display this help message."
|
|
||||||
}
|
|
||||||
|
|
||||||
while getopts "h" OPTION
|
|
||||||
do
|
|
||||||
case "$OPTION" in
|
|
||||||
h)
|
|
||||||
help
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
?)
|
|
||||||
help
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
shift "$(($OPTIND -1))"
|
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]
|
|
||||||
then
|
|
||||||
help
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
profile="$1"
|
|
||||||
|
|
||||||
profile_dir="${SCRIPT_DIR}/${profile}"
|
|
||||||
if [ ! -d "$profile_dir" ]
|
|
||||||
then
|
|
||||||
echo "Profile not found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
home_manager_config="${profile_dir}/hm.nix"
|
|
||||||
if [ ! -f "$home_manager_config" ]
|
|
||||||
then
|
|
||||||
echo "Home Manager configuration not found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
nom-build '<home-manager/home-manager/home-manager.nix>' --argstr confPath "${home_manager_config}" -o "${profile_dir}/hm"
|
|
||||||
|
|
||||||
set +x
|
|
||||||
|
|
||||||
echo
|
|
||||||
|
|
||||||
path="$(readlink -f "${profile_dir}/hm")"
|
|
||||||
|
|
||||||
echo "Manual installation instructions:"
|
|
||||||
echo "- Transfer $path and dependencies to the destination machine (somehow)"
|
|
||||||
echo "- Run $path/activate as the destination user"
|
|
||||||
echo "- Log into the user again to make sure everything is sourced"
|
|
||||||
echo "- Transfer necessary private keys (or use ssh -A for testing)"
|
|
||||||
echo "- Run git-sync-init"
|
|
||||||
echo "- Check that the system can build itself"
|
|
36
build_os.sh
36
build_os.sh
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i bash
|
#! nix-shell -i bash
|
||||||
#! nix-shell -p bash nix-output-monitor
|
#! nix-shell -p nix
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
function help {
|
function help {
|
||||||
echo "Usage: $0 [-h|-v|-b] profile"
|
echo "Usage: $0 [-h|-e|-b] [flake-uri#]name"
|
||||||
echo "Build NixOS configuration on the local machine."
|
echo "Build a NixOS configuration on the local machine."
|
||||||
echo
|
echo
|
||||||
echo "Arguments:"
|
echo "Arguments:"
|
||||||
echo " profile: OS/disk profile to use"
|
echo " profile: OS/disk profile to use"
|
||||||
|
@ -19,7 +19,7 @@ function help {
|
||||||
echo " -b: Build a virtual machine with boot loader."
|
echo " -b: Build a virtual machine with boot loader."
|
||||||
}
|
}
|
||||||
|
|
||||||
attr=system
|
arg=build
|
||||||
while getopts "hvb" OPTION
|
while getopts "hvb" OPTION
|
||||||
do
|
do
|
||||||
case "$OPTION" in
|
case "$OPTION" in
|
||||||
|
@ -28,10 +28,10 @@ do
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
v)
|
v)
|
||||||
attr=vm
|
arg=build-vm
|
||||||
;;
|
;;
|
||||||
b)
|
b)
|
||||||
attr=vmWithBootLoader
|
arg=build-vm-with-bootloader
|
||||||
;;
|
;;
|
||||||
?)
|
?)
|
||||||
help
|
help
|
||||||
|
@ -39,29 +39,35 @@ do
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
shift "$(($OPTIND -1))"
|
shift "$((OPTIND -1))"
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]
|
if [ "$#" -ne 1 ]
|
||||||
then
|
then
|
||||||
help
|
help
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
profile="$1"
|
|
||||||
|
|
||||||
profile_dir="${SCRIPT_DIR}/${profile}"
|
if [[ "$1" == *"#"* ]]
|
||||||
if [ ! -d "$profile_dir" ]
|
|
||||||
then
|
then
|
||||||
echo "Profile not found."
|
flake_uri="$(echo "$1" | cut -d'#' -f1)"
|
||||||
|
flake_uri=$( cd -- "$flake_uri" &> /dev/null && pwd )
|
||||||
|
name="$(echo "$1" | cut -d'#' -f2)"
|
||||||
|
else
|
||||||
|
flake_uri="$SCRIPT_DIR"
|
||||||
|
name="$1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
nixos_config="${profile_dir}/os.nix"
|
if [ ! -f "$flake_uri/flake.nix" ]
|
||||||
if [ ! -f "$nixos_config" ]
|
|
||||||
then
|
then
|
||||||
echo "NixOS configuration not found."
|
echo "Flake not found."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
flake="${flake_uri}#${name}"
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
nom-build '<nixpkgs/nixos>' -I "nixos-config=${nixos_config}" -A "$attr" -o "${profile_dir}/${attr}"
|
nix --extra-experimental-features "nix-command flakes" run "${SCRIPT_DIR}#nixos-rebuild" -- "$arg" --flake "$flake"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
# TODO Use update-local-flakes?
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{ id, name, passwordFile ? "/should_not_be_needed_in_this_context", ... }:
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
|
||||||
|
in
|
||||||
{
|
{
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
"${name}" = {
|
"${config.frogeye.name}" = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/${id}";
|
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
|
@ -25,7 +27,7 @@
|
||||||
size = "100%";
|
size = "100%";
|
||||||
content = {
|
content = {
|
||||||
type = "luks";
|
type = "luks";
|
||||||
name = "${name}";
|
name = "${config.frogeye.name}";
|
||||||
passwordFile = passwordFile;
|
passwordFile = passwordFile;
|
||||||
settings = {
|
settings = {
|
||||||
# Not having SSDs die fast is more important than crypto
|
# Not having SSDs die fast is more important than crypto
|
54
common/frogarized/default.nix
Normal file
54
common/frogarized/default.nix
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{ config, ... }:
|
||||||
|
let
|
||||||
|
# Use ./frogarized.py to generate
|
||||||
|
# Vendored to prevent IFDs
|
||||||
|
frogarized = rec {
|
||||||
|
common = {
|
||||||
|
author = "Geoffrey Frogeye (with work from Ethan Schoonover)";
|
||||||
|
base08 = "#e0332e";
|
||||||
|
base09 = "#cf4b15";
|
||||||
|
base0A = "#bb8801";
|
||||||
|
base0B = "#8d9800";
|
||||||
|
base0C = "#1fa198";
|
||||||
|
base0D = "#008dd1";
|
||||||
|
base0E = "#5c73c4";
|
||||||
|
base0F = "#d43982";
|
||||||
|
};
|
||||||
|
light = common // {
|
||||||
|
base00 = "#fff0f1";
|
||||||
|
base01 = "#fae2e3";
|
||||||
|
base02 = "#99a08d";
|
||||||
|
base03 = "#89947f";
|
||||||
|
base04 = "#677d64";
|
||||||
|
base05 = "#5a7058";
|
||||||
|
base06 = "#143718";
|
||||||
|
base07 = "#092c0e";
|
||||||
|
scheme = "Frogarized Light";
|
||||||
|
slug = "frogarized-light";
|
||||||
|
};
|
||||||
|
dark = common // {
|
||||||
|
base00 = "#092c0e";
|
||||||
|
base01 = "#143718";
|
||||||
|
base02 = "#5a7058";
|
||||||
|
base03 = "#677d64";
|
||||||
|
base04 = "#89947f";
|
||||||
|
base05 = "#99a08d";
|
||||||
|
base06 = "#fae2e3";
|
||||||
|
base07 = "#fff0f1";
|
||||||
|
scheme = "Frogarized Dark";
|
||||||
|
slug = "frogarized-dark";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
stylix = {
|
||||||
|
base16Scheme = frogarized.${config.stylix.polarity};
|
||||||
|
# On purpose also enable without a DE because stylix complains otherwise
|
||||||
|
image = builtins.fetchurl {
|
||||||
|
url = "https://get.wallhere.com/photo/sunlight-abstract-minimalism-green-simple-circle-light-leaf-wave-material-line-wing-computer-wallpaper-font-close-up-macro-photography-124350.png";
|
||||||
|
sha256 = "sha256:1zfq3f3v34i45mi72pkfqphm8kbhczsg260xjfl6dbydy91d7y93";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
112
common/frogarized/frogarized.py
Executable file
112
common/frogarized/frogarized.py
Executable file
|
@ -0,0 +1,112 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
import colorspacious
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Original values for the Solarized color scheme,
|
||||||
|
# created by Ethan Schoonover (https://ethanschoonover.com/solarized/)
|
||||||
|
SOLARIZED_LAB = np.array(
|
||||||
|
[
|
||||||
|
[15, -12, -12],
|
||||||
|
[20, -12, -12],
|
||||||
|
[45, -7, -7],
|
||||||
|
[50, -7, -7],
|
||||||
|
[60, -6, -3],
|
||||||
|
[65, -5, -2],
|
||||||
|
[92, -0, 10],
|
||||||
|
[97, 0, 10],
|
||||||
|
[50, 65, 45],
|
||||||
|
[50, 50, 55],
|
||||||
|
[60, 10, 65],
|
||||||
|
[60, -20, 65],
|
||||||
|
[60, -35, -5],
|
||||||
|
[55, -10, -45],
|
||||||
|
[50, 15, -45],
|
||||||
|
[50, 65, -5],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# I couldn't get a perfect translation of Solarized L*a*b values into sRGB,
|
||||||
|
# so here is upstream's translation for reference
|
||||||
|
SOLARIZED_RGB = np.array(
|
||||||
|
[
|
||||||
|
[0, 43, 54],
|
||||||
|
[7, 54, 66],
|
||||||
|
[88, 110, 117],
|
||||||
|
[101, 123, 131],
|
||||||
|
[131, 148, 150],
|
||||||
|
[147, 161, 161],
|
||||||
|
[238, 232, 213],
|
||||||
|
[253, 246, 227],
|
||||||
|
[220, 50, 47],
|
||||||
|
[203, 75, 22],
|
||||||
|
[181, 137, 0],
|
||||||
|
[133, 153, 0],
|
||||||
|
[42, 161, 152],
|
||||||
|
[38, 139, 210],
|
||||||
|
[108, 113, 196],
|
||||||
|
[211, 54, 130],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate a base16-theme based derived from Solarized"
|
||||||
|
)
|
||||||
|
parser.add_argument("--source", choices=["lab", "rgb"], default="lab")
|
||||||
|
parser.add_argument("--lightness_factor", type=float, default=1.0)
|
||||||
|
parser.add_argument("--chroma-factor", type=float, default=1.0)
|
||||||
|
parser.add_argument("--hue_shift", type=float, default=-75.0)
|
||||||
|
parser.add_argument("--polarity", choices=["dark", "light"], default="dark")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output", choices=["json", "truecolor"], default="truecolor"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Convert source to JCh color space
|
||||||
|
if args.source == "lab":
|
||||||
|
solarized_jch = colorspacious.cspace_convert(
|
||||||
|
SOLARIZED_LAB, "CIELab", "JCh"
|
||||||
|
)
|
||||||
|
elif args.source == "rgb":
|
||||||
|
solarized_jch = colorspacious.cspace_convert(
|
||||||
|
SOLARIZED_RGB, "sRGB255", "JCh"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build frogarized theme
|
||||||
|
jch_factor = [args.lightness_factor, args.chroma_factor, 1]
|
||||||
|
jch_shift = [0, 0, args.hue_shift]
|
||||||
|
frogarzied_jch = np.vstack(
|
||||||
|
[solarized_jch[:8] * jch_factor + jch_shift, solarized_jch[8:]]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert frogarized to RGB
|
||||||
|
frogarized_srgb = colorspacious.cspace_convert(
|
||||||
|
frogarzied_jch, "JCh", "sRGB255"
|
||||||
|
)
|
||||||
|
frogarized_rgb = np.uint8(np.rint(np.clip(frogarized_srgb, 0, 255)))
|
||||||
|
if args.polarity == "light":
|
||||||
|
frogarized_rgb = np.vstack([frogarized_rgb[7::-1], frogarized_rgb[8:]])
|
||||||
|
|
||||||
|
# Output
|
||||||
|
palette = dict()
|
||||||
|
for i in range(16):
|
||||||
|
rgb = frogarized_rgb[i]
|
||||||
|
r, g, b = rgb
|
||||||
|
hex = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
palette[f"base{i:02X}"] = hex
|
||||||
|
if args.output == "truecolor":
|
||||||
|
print(f"\033[48;2;{r};{g};{b}m{hex}\033[0m") # ]]
|
||||||
|
# treesitter is silly and will consider brackets in strings
|
||||||
|
# as indentation, hence the comment above
|
||||||
|
if args.output == "json":
|
||||||
|
scheme = palette.copy()
|
||||||
|
scheme.update(
|
||||||
|
{
|
||||||
|
"slug": f"frogarized-{args.polarity}",
|
||||||
|
"scheme": f"Frogarized {args.polarity.title()}",
|
||||||
|
"author": "Geoffrey Frogeye (with work from Ethan Schoonover)",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(json.dumps(scheme, indent=4))
|
2
common/update-local-flakes/default.nix
Normal file
2
common/update-local-flakes/default.nix
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
pkgs.writers.writePython3Bin "update-local-flakes" {} (builtins.readFile ./update-local-flakes.py)
|
3
common/update-local-flakes/overlay.nix
Normal file
3
common/update-local-flakes/overlay.nix
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
(self: super: {
|
||||||
|
update-local-flakes = super.callPackage ./. {};
|
||||||
|
})
|
62
common/update-local-flakes/update-local-flakes.py
Executable file
62
common/update-local-flakes/update-local-flakes.py
Executable file
|
@ -0,0 +1,62 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
GET_INPUTS_CMD = [
|
||||||
|
"nix-instantiate",
|
||||||
|
"--eval",
|
||||||
|
"--json", # This parser is stupid, better provide it with pre-eaten stuff
|
||||||
|
"--expr",
|
||||||
|
"builtins.fromJSON (builtins.toJSON (import ./flake.nix).inputs)",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def process_flake(flakeUri: str) -> None:
|
||||||
|
# get full path
|
||||||
|
flakeUri = os.path.normpath(flakeUri)
|
||||||
|
flakeFile = os.path.join(flakeUri, "flake.nix")
|
||||||
|
if not os.path.isfile(flakeFile):
|
||||||
|
raise FileNotFoundError(f"Flake not found: {flakeUri}")
|
||||||
|
# import dependencies
|
||||||
|
p = subprocess.run(GET_INPUTS_CMD, cwd=flakeUri, stdout=subprocess.PIPE)
|
||||||
|
deps = json.loads(p.stdout)
|
||||||
|
p.check_returncode()
|
||||||
|
# for each dependency
|
||||||
|
for dep_name, dep in deps.items():
|
||||||
|
dep_url = dep["url"]
|
||||||
|
# if not local path, continue
|
||||||
|
if not (
|
||||||
|
dep_url.startswith("path:")
|
||||||
|
or dep_url.startswith("git+file:")
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if dep.get("flake", True):
|
||||||
|
# get flake file corresponding
|
||||||
|
dep_path = dep_url.split(":")[1]
|
||||||
|
if not dep_path.startswith("/"):
|
||||||
|
dep_path = os.path.join(flakeUri, dep_path)
|
||||||
|
process_flake(dep_path)
|
||||||
|
# update lockfile
|
||||||
|
cmd = [
|
||||||
|
"nix",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"nix-command",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"flakes",
|
||||||
|
"flake",
|
||||||
|
"update",
|
||||||
|
dep_name,
|
||||||
|
]
|
||||||
|
p = subprocess.run(cmd, cwd=flakeUri)
|
||||||
|
p.check_returncode()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Recursively update lockfiles "
|
||||||
|
"of flakes located on the system"
|
||||||
|
)
|
||||||
|
parser.add_argument("flake", help="Starting flake", default="/")
|
||||||
|
args = parser.parse_args()
|
||||||
|
process_flake(args.flake)
|
12
cranberry/default.nix
Normal file
12
cranberry/default.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
frogeye.name = "cranberry";
|
||||||
|
disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/nvme-UMIS_RPJTJ128MEE1MWX_SS0L25188X3RC12121TP";
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
../common/disko/single_uefi_btrfs.nix
|
||||||
|
./hardware.nix
|
||||||
|
./features.nix
|
||||||
|
];
|
||||||
|
}
|
13
cranberry/features.nix
Normal file
13
cranberry/features.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
frogeye = {
|
||||||
|
desktop.xorg = true;
|
||||||
|
dev = {
|
||||||
|
c = true;
|
||||||
|
vm = true;
|
||||||
|
};
|
||||||
|
extra = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
45
cranberry/hardware.nix
Normal file
45
cranberry/hardware.nix
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{ pkgs, lib, config, nixos-hardware, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
boot = {
|
||||||
|
# From nixos-generate-config
|
||||||
|
initrd.availableKernelModules = [ "nvme" "xhci_pci" "usb_storage" "sd_mod" "sdhci_pci" ];
|
||||||
|
kernelModules = [ "kvm-amd" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Needed for Wi-Fi
|
||||||
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
|
||||||
|
frogeye.desktop = {
|
||||||
|
x11_screens = [ "eDP-1" ];
|
||||||
|
maxVideoHeight = 1080;
|
||||||
|
|
||||||
|
phasesCommands = {
|
||||||
|
jour = ''
|
||||||
|
echo 0 | sudo tee /sys/class/leds/chromeos::kbd_backlight/brightness &
|
||||||
|
${pkgs.brightnessctl}/bin/brightnessctl set 30% &
|
||||||
|
'';
|
||||||
|
crepuscule = ''
|
||||||
|
echo 1 | sudo tee /sys/class/leds/chromeos::kbd_backlight/brightness &
|
||||||
|
${pkgs.brightnessctl}/bin/brightnessctl set 10% &
|
||||||
|
'';
|
||||||
|
nuit = ''
|
||||||
|
echo 10 | sudo tee /sys/class/leds/chromeos::kbd_backlight/brightness &
|
||||||
|
${pkgs.brightnessctl}/bin/brightnessctl set 0% &
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Alt key swallowed the Meta one
|
||||||
|
home-manager.users.geoffrey = { ... }: {
|
||||||
|
xsession.windowManager.i3.config.modifier = "Mod1";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
nixos-hardware.nixosModules.common-cpu-amd
|
||||||
|
nixos-hardware.nixosModules.common-gpu-amd
|
||||||
|
nixos-hardware.nixosModules.common-pc-laptop
|
||||||
|
nixos-hardware.nixosModules.common-pc-ssd
|
||||||
|
];
|
||||||
|
}
|
|
@ -2,13 +2,13 @@
|
||||||
# MANU Snapper is not able to create the snapshot directory, so you'll need to do this after eventually running the backup script:
|
# MANU Snapper is not able to create the snapshot directory, so you'll need to do this after eventually running the backup script:
|
||||||
# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots
|
# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots
|
||||||
let
|
let
|
||||||
backup_subvolumes = [ "nixos" "home.rapido" ];
|
backup_subvolumes = [ "nixos" "home.rapido" "home.nixos" ];
|
||||||
backup_app = pkgs.writeShellApplication {
|
backup_app = pkgs.writeShellApplication {
|
||||||
name = "backup-subvolume";
|
name = "backup-subvolume";
|
||||||
runtimeInputs = with pkgs; [ coreutils btrfs-progs ];
|
runtimeInputs = with pkgs; [ coreutils btrfs-progs ];
|
||||||
text = builtins.readFile ./backup.sh;
|
text = builtins.readFile ./backup.sh;
|
||||||
};
|
};
|
||||||
snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" ];
|
snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" "home.nixos" ];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services =
|
services =
|
||||||
|
@ -28,11 +28,11 @@ in
|
||||||
# cleanup hourly snapshots after some time
|
# cleanup hourly snapshots after some time
|
||||||
TIMELINE_CLEANUP = true;
|
TIMELINE_CLEANUP = true;
|
||||||
TIMELINE_MIN_AGE = 1800;
|
TIMELINE_MIN_AGE = 1800;
|
||||||
TIMELINE_LIMIT_HOURLY = 24;
|
TIMELINE_LIMIT_HOURLY = "24";
|
||||||
TIMELINE_LIMIT_DAILY = 31;
|
TIMELINE_LIMIT_DAILY = "31";
|
||||||
TIMELINE_LIMIT_WEEKLY = 8;
|
TIMELINE_LIMIT_WEEKLY = "8";
|
||||||
TIMELINE_LIMIT_MONTHLY = 0;
|
TIMELINE_LIMIT_MONTHLY = "0";
|
||||||
TIMELINE_LIMIT_YEARLY = 0;
|
TIMELINE_LIMIT_YEARLY = "0";
|
||||||
|
|
||||||
# cleanup empty pre-post-pairs
|
# cleanup empty pre-post-pairs
|
||||||
EMPTY_PRE_POST_CLEANUP = true;
|
EMPTY_PRE_POST_CLEANUP = true;
|
||||||
|
|
72
curacao/co2meter/default.nix
Normal file
72
curacao/co2meter/default.nix
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
zytemp_mqtt_src = pkgs.fetchFromGitHub {
|
||||||
|
# owner = "patrislav1";
|
||||||
|
owner = "GeoffreyFrogeye";
|
||||||
|
repo = "zytemp_mqtt";
|
||||||
|
rev = "push-nurpouorqoyr"; # Humidity + availability support
|
||||||
|
sha256 = "sha256-nOhyBAgvjeQh9ys3cBJOVR67SDs96zBzxIRGpaq4yoA=";
|
||||||
|
};
|
||||||
|
zytemp_mqtt = pkgs.python3Packages.buildPythonPackage
|
||||||
|
rec {
|
||||||
|
name = "zytemp_mqtt";
|
||||||
|
src = zytemp_mqtt_src;
|
||||||
|
propagatedBuildInputs = with pkgs.python3Packages; [ hidapi paho-mqtt pyaml ];
|
||||||
|
};
|
||||||
|
usb_zytemp_udev = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "usb-zytemp-udev-rules";
|
||||||
|
version = "unstable-2023-05-24";
|
||||||
|
src = zytemp_mqtt_src;
|
||||||
|
|
||||||
|
dontConfigure = true;
|
||||||
|
dontBuild = true;
|
||||||
|
dontFixup = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/lib/udev/rules.d
|
||||||
|
cp udev/90-usb-zytemp-permissions.rules $out/lib/udev/rules.d/90-usb-zytemp.rules
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
mqtt_host = "192.168.7.53"; # Ludwig
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
environment.etc."zytempmqtt/config.yaml".text = lib.generators.toYAML { } {
|
||||||
|
decrypt = true;
|
||||||
|
mqtt_host = mqtt_host;
|
||||||
|
friendly_name = "Desk sensor";
|
||||||
|
};
|
||||||
|
services.udev.packages = [ usb_zytemp_udev ];
|
||||||
|
systemd = {
|
||||||
|
services.zytemp_mqtt = {
|
||||||
|
description = "Forward zyTemp CO2 sensor to MQTT";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${zytemp_mqtt}/bin/zytempmqtt";
|
||||||
|
|
||||||
|
# Hardening (hapazardeous)
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
DynamicUser = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = false;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [ "@system-service" "~@privileged" "~@resouces" ];
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
17
curacao/dedup/default.nix
Normal file
17
curacao/dedup/default.nix
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
services.beesd.filesystems = {
|
||||||
|
razmo = {
|
||||||
|
spec = "/mnt/razmo";
|
||||||
|
hashTableSizeMB = 512; # Recommended for 1 TiB, ×2 for compression, x2 for time
|
||||||
|
extraOptions = [ "--loadavg-target" "7.5" ];
|
||||||
|
};
|
||||||
|
rapido = {
|
||||||
|
spec = "/mnt/rapido";
|
||||||
|
hashTableSizeMB = 128; # 4 times smaller disk, 4 times smaller hashtable?
|
||||||
|
extraOptions = [ "--loadavg-target" "5" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
17
curacao/default.nix
Normal file
17
curacao/default.nix
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
frogeye.name = "curacao";
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
./backup
|
||||||
|
./co2meter
|
||||||
|
./dedup
|
||||||
|
./desk
|
||||||
|
./disko.nix
|
||||||
|
./features.nix
|
||||||
|
./hardware.nix
|
||||||
|
./homeautomation
|
||||||
|
./webcam
|
||||||
|
];
|
||||||
|
}
|
53
curacao/desk/default.nix
Normal file
53
curacao/desk/default.nix
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
desk_mqtt = pkgs.writers.writePython3 "desk_mqtt"
|
||||||
|
{
|
||||||
|
libraries = with pkgs.python3Packages; [ pyusb ha-mqtt-discoverable ];
|
||||||
|
}
|
||||||
|
(builtins.readFile ./desk_mqtt.py);
|
||||||
|
usb2lin06_udev = pkgs.writeTextFile {
|
||||||
|
name = "usb2lin06-udev-rules";
|
||||||
|
text = ''
|
||||||
|
SUBSYSTEM=="usb", ATTR{idVendor}=="12d3", ATTR{idProduct}=="0002", MODE="0666"
|
||||||
|
'';
|
||||||
|
destination = "/lib/udev/rules.d/90-usb2lin06.rules";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
services.udev.packages = [ usb2lin06_udev ];
|
||||||
|
systemd = {
|
||||||
|
services.desk_mqtt = {
|
||||||
|
description = "Control desk height via MQTT";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${desk_mqtt}";
|
||||||
|
RestartSec = 10;
|
||||||
|
Restart = "on-failure";
|
||||||
|
|
||||||
|
# Hardening (hapazardeous)
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
DynamicUser = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = false;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [ "@system-service" "~@privileged" "~@resouces" ];
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
374
curacao/desk/desk_mqtt.py
Executable file
374
curacao/desk/desk_mqtt.py
Executable file
|
@ -0,0 +1,374 @@
|
||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import ha_mqtt_discoverable
|
||||||
|
import ha_mqtt_discoverable.sensors
|
||||||
|
import paho.mqtt.client
|
||||||
|
import usb.core
|
||||||
|
import usb.util
|
||||||
|
|
||||||
|
|
||||||
|
class Desk:
|
||||||
|
"""
|
||||||
|
Controls my Linak desk, which is a CBD4P controller connected via USB2LIN06
|
||||||
|
This particular combination doesn't seem to report desk height,
|
||||||
|
so it is estimated from the physical controller that does work.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Source of data:
|
||||||
|
|
||||||
|
# https://github.com/UrbanskiDawid/usb2lin06-HID-in-linux-for-LINAK-Desk-Control-Cable
|
||||||
|
# https://github.com/monofox/python-linak-desk-control
|
||||||
|
# https://github.com/gryf/linak-ctrl
|
||||||
|
|
||||||
|
# Desk Control Basic Software
|
||||||
|
# https://www.linak-us.com/products/controls/desk-control-basic-software/
|
||||||
|
# Says it's connected but doesn't report height and buttons do nothing
|
||||||
|
# Expected, as manual says it only works with CBD4A or CBD6
|
||||||
|
# Decompiled with ILSpy (easy), doesn't offer much though
|
||||||
|
|
||||||
|
# CBD4+5 Configurator
|
||||||
|
# https://www.linak.nl/technische-ondersteuning/#/cbd4-cbd6s-configurator
|
||||||
|
# Connects, and settings can be changed.
|
||||||
|
# Don't think there's much that would help with our problem.
|
||||||
|
# Decompiled with Ghidra (hard), didn't go super far
|
||||||
|
|
||||||
|
VEND = 0x12D3
|
||||||
|
PROD = 0x0002
|
||||||
|
# Official apps use HID library, although only managed to barely make
|
||||||
|
# pyhidapi read manufacturer and product once after device reset
|
||||||
|
|
||||||
|
BUF_LEN = 64
|
||||||
|
MOVE_CMD_REPEAT_INTERVAL = 0.2 # s
|
||||||
|
STOP_CMD_INTERVAL = 1 # s
|
||||||
|
MAX_EST_INTERVAL = 10 # s
|
||||||
|
|
||||||
|
# Theoritical height values
|
||||||
|
VALUE_MIN = 0x0000
|
||||||
|
VALUE_MAX = 0x7FFE
|
||||||
|
VALUE_DOWN = 0x7FFF
|
||||||
|
VALUE_UP = 0x8000
|
||||||
|
VALUE_STOP = 0x8001
|
||||||
|
|
||||||
|
# Measured values
|
||||||
|
VALUE_BOT = 0x0001
|
||||||
|
VALUE_TOP = 0x1A50
|
||||||
|
HEIGHT_BOT = 68
|
||||||
|
HEIGHT_TOP = 135
|
||||||
|
FULL_RISE_TIME = 17.13 # s
|
||||||
|
FULL_FALL_TIME = 16.64 # s
|
||||||
|
|
||||||
|
# Computed values
|
||||||
|
HEIGHT_OFFSET = HEIGHT_BOT # cm
|
||||||
|
HEIGHT_MULT = VALUE_TOP / (HEIGHT_TOP - HEIGHT_BOT) # unit / cm
|
||||||
|
# Should be 100 in theory (1 unit = 0.1 mm)
|
||||||
|
FULL_TIME = (FULL_FALL_TIME + FULL_RISE_TIME) / 2 # s
|
||||||
|
SPEED_MARGIN = 0.9
|
||||||
|
# Better estimate a bit slower
|
||||||
|
SPEED = (VALUE_TOP - VALUE_BOT) / FULL_TIME * SPEED_MARGIN # unit / s
|
||||||
|
|
||||||
|
def _cmToUnit(self, height: float) -> int:
|
||||||
|
return round((height - self.HEIGHT_OFFSET) * self.HEIGHT_MULT)
|
||||||
|
|
||||||
|
def _unitToCm(self, height: int) -> float:
|
||||||
|
return height / self.HEIGHT_MULT + self.HEIGHT_OFFSET
|
||||||
|
|
||||||
|
def _get(self, typ: int, overflow_ok: bool = False) -> bytes:
|
||||||
|
# Magic numbers: get class interface, HID get report
|
||||||
|
raw = self._dev.ctrl_transfer(
|
||||||
|
0xA1, 0x01, 0x300 + typ, 0, self.BUF_LEN
|
||||||
|
).tobytes()
|
||||||
|
self.log.debug(f"Received {raw.hex()}")
|
||||||
|
assert raw[0] == typ
|
||||||
|
size = raw[1]
|
||||||
|
end = 2 + size
|
||||||
|
if not overflow_ok:
|
||||||
|
assert end < self.BUF_LEN
|
||||||
|
return raw[2:end]
|
||||||
|
# Non-implemented types:
|
||||||
|
# 1, 7: some kind of stream when the device isn't initialized?
|
||||||
|
# size reduces the faster you poll, increases when buttons are held
|
||||||
|
# 9: unknown, always report 0
|
||||||
|
|
||||||
|
def _set(self, typ: int, buf: bytes) -> None:
|
||||||
|
buf = bytes([typ]) + buf
|
||||||
|
# The official apps pad, not that it doesn't seem to work without
|
||||||
|
buf = buf + b"\x00" * (self.BUF_LEN - len(buf))
|
||||||
|
self.log.debug(f"Sending {buf.hex()}")
|
||||||
|
# Magic numbers: set class interface, HID set report
|
||||||
|
self._dev.ctrl_transfer(0x21, 0x09, 0x300 + typ, 0, buf)
|
||||||
|
# Non-implemented types:
|
||||||
|
# Some stuff < 10
|
||||||
|
|
||||||
|
def _reset_estimations(self) -> None:
|
||||||
|
self.est_value: None | int = None
|
||||||
|
self.est_value_bot = float(self.VALUE_BOT)
|
||||||
|
self.est_value_top = float(self.VALUE_TOP)
|
||||||
|
self.last_est: float = 0.0
|
||||||
|
|
||||||
|
def _initialize(self) -> None:
|
||||||
|
"""
|
||||||
|
Seems to take the USB2LIN06 out of "boot mode"
|
||||||
|
(name according to CBD4 Controller) which it is after reset.
|
||||||
|
Permits control and reading the report.
|
||||||
|
"""
|
||||||
|
buf = bytes([0x04, 0x00, 0xFB])
|
||||||
|
self._set(3, buf)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.log = logging.getLogger("Desk")
|
||||||
|
self._dev = usb.core.find(idVendor=Desk.VEND, idProduct=Desk.PROD)
|
||||||
|
if not self._dev:
|
||||||
|
raise ValueError(
|
||||||
|
f"Device {Desk.VEND}:" f"{Desk.PROD:04d} " f"not found!"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._dev.is_kernel_driver_active(0):
|
||||||
|
self._dev.detach_kernel_driver(0)
|
||||||
|
|
||||||
|
self._initialize()
|
||||||
|
self._reset_estimations()
|
||||||
|
self.last_destination = None
|
||||||
|
|
||||||
|
self.fetch_callback: typing.Callable[["Desk"], None] | None = None
|
||||||
|
|
||||||
|
def _get_report(self) -> bytes:
|
||||||
|
raw = self._get(4)
|
||||||
|
assert len(raw) == 0x38
|
||||||
|
return raw
|
||||||
|
|
||||||
|
def _update_estimations(self) -> None:
|
||||||
|
now = time.time()
|
||||||
|
delta_s = now - self.last_est
|
||||||
|
|
||||||
|
if delta_s > self.MAX_EST_INTERVAL:
|
||||||
|
# Attempt at fixing the issue of
|
||||||
|
# the service not working after the night
|
||||||
|
self._initialize()
|
||||||
|
self.log.warning(
|
||||||
|
"Too long without getting a report, "
|
||||||
|
"assuming the desk might be anywhere now."
|
||||||
|
)
|
||||||
|
self._reset_estimations()
|
||||||
|
else:
|
||||||
|
delta_u = delta_s * self.SPEED
|
||||||
|
|
||||||
|
if self.destination == self.VALUE_STOP:
|
||||||
|
pass
|
||||||
|
elif self.destination == self.VALUE_UP:
|
||||||
|
self.est_value_bot += delta_u
|
||||||
|
self.est_value_top += delta_u
|
||||||
|
elif self.destination == self.VALUE_DOWN:
|
||||||
|
self.est_value_bot -= delta_u
|
||||||
|
self.est_value_top -= delta_u
|
||||||
|
else:
|
||||||
|
|
||||||
|
def move_closer(start_val: float) -> float:
|
||||||
|
if start_val < self.destination:
|
||||||
|
end_val = start_val + delta_u
|
||||||
|
return min(end_val, self.destination)
|
||||||
|
else:
|
||||||
|
end_val = start_val - delta_u
|
||||||
|
return max(end_val, self.destination)
|
||||||
|
|
||||||
|
self.est_value_bot = move_closer(self.est_value_bot)
|
||||||
|
self.est_value_top = move_closer(self.est_value_top)
|
||||||
|
|
||||||
|
# Clamp
|
||||||
|
self.est_value_bot = max(self.VALUE_BOT, self.est_value_bot)
|
||||||
|
self.est_value_top = min(self.VALUE_TOP, self.est_value_top)
|
||||||
|
|
||||||
|
if self.est_value_top == self.est_value_bot:
|
||||||
|
if self.est_value is None:
|
||||||
|
self.log.info("Height estimation converged")
|
||||||
|
self.est_value = int(self.est_value_top)
|
||||||
|
|
||||||
|
self.last_est = now
|
||||||
|
|
||||||
|
def fetch(self) -> None:
|
||||||
|
for _ in range(3):
|
||||||
|
try:
|
||||||
|
raw = self._get_report()
|
||||||
|
break
|
||||||
|
except usb.USBError as e:
|
||||||
|
self.log.error(e)
|
||||||
|
else:
|
||||||
|
raw = self._get_report()
|
||||||
|
|
||||||
|
# Allegedly, from decompiling:
|
||||||
|
# https://www.linak-us.com/products/controls/desk-control-basic-software/
|
||||||
|
# Never reports anything in practice
|
||||||
|
self.value = struct.unpack("<H", raw[0:2])[0]
|
||||||
|
unk = struct.unpack("<H", raw[2:4])[0]
|
||||||
|
self.initalized = (unk & 0xF) != 0
|
||||||
|
|
||||||
|
# From observation. Reliable
|
||||||
|
self.destination = (struct.unpack("<H", raw[18:20])[0],)[0]
|
||||||
|
|
||||||
|
if self.destination != self.last_destination:
|
||||||
|
self.log.info(f"Destination changed to {self.destination:04x}")
|
||||||
|
self.last_destination = self.destination
|
||||||
|
|
||||||
|
self._update_estimations()
|
||||||
|
if self.fetch_callback is not None:
|
||||||
|
self.fetch_callback(self)
|
||||||
|
|
||||||
|
def _move(self, position: int) -> None:
|
||||||
|
buf = struct.pack("<H", position) * 4
|
||||||
|
self._set(5, buf)
|
||||||
|
|
||||||
|
def _move_to(self, position: int) -> None:
|
||||||
|
# Clamp
|
||||||
|
position = max(self.VALUE_BOT, position)
|
||||||
|
position = min(self.VALUE_TOP, position)
|
||||||
|
|
||||||
|
self.log.info(f"Start moving to {position:04x}")
|
||||||
|
self.fetch()
|
||||||
|
while self.est_value != position:
|
||||||
|
self._move(position)
|
||||||
|
time.sleep(self.MOVE_CMD_REPEAT_INTERVAL)
|
||||||
|
self.fetch()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def move_to(self, position: float) -> None:
|
||||||
|
"""
|
||||||
|
If any button is held during movement, the desk will stop moving,
|
||||||
|
yet this will think it's still moving, throwing off the estimates.
|
||||||
|
It's not a bug, it's a safety feature.
|
||||||
|
Also if you try to make it move when it's already moving,
|
||||||
|
it's going to keep moving while desyncing.
|
||||||
|
That one is a bug.
|
||||||
|
"""
|
||||||
|
# Would to stop for a while before reversing course, without being able
|
||||||
|
# to read the actual height it's just too annoying to implement
|
||||||
|
return self._move_to(self._cmToUnit(position))
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.log.info("Stop moving")
|
||||||
|
self._move(self.VALUE_STOP)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def get_height_bounds(self) -> tuple[float, float]:
|
||||||
|
return (
|
||||||
|
self._unitToCm(int(self.est_value_bot)),
|
||||||
|
self._unitToCm(int(self.est_value_top)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_height(self) -> float | None:
|
||||||
|
if self.est_value is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._unitToCm(self.est_value)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
desk = Desk()
|
||||||
|
serial = "000C-34E7"
|
||||||
|
|
||||||
|
# Configure the required parameters for the MQTT broker
|
||||||
|
mqtt_settings = ha_mqtt_discoverable.Settings.MQTT(host="192.168.7.53")
|
||||||
|
ndigits = 1
|
||||||
|
target_height: float | None = None
|
||||||
|
|
||||||
|
device_info = ha_mqtt_discoverable.DeviceInfo(
|
||||||
|
name="Desk",
|
||||||
|
identifiers=["Linak", serial],
|
||||||
|
manufacturer="Linak",
|
||||||
|
model="CBD4P",
|
||||||
|
suggested_area="Desk",
|
||||||
|
hw_version="77402",
|
||||||
|
sw_version="1.91",
|
||||||
|
serial_number=serial,
|
||||||
|
)
|
||||||
|
|
||||||
|
common_opts = {
|
||||||
|
"device": device_info,
|
||||||
|
"icon": "mdi:desk",
|
||||||
|
"unit_of_measurement": "cm",
|
||||||
|
"device_class": "distance",
|
||||||
|
"expire_after": 10,
|
||||||
|
}
|
||||||
|
# TODO Implement proper availability in hq-mqtt-discoverable
|
||||||
|
|
||||||
|
height_info = ha_mqtt_discoverable.sensors.NumberInfo(
|
||||||
|
name="Height ",
|
||||||
|
min=desk.HEIGHT_BOT,
|
||||||
|
max=desk.HEIGHT_TOP,
|
||||||
|
mode="slider",
|
||||||
|
step=10 ** (-ndigits),
|
||||||
|
unique_id="desk_height",
|
||||||
|
**common_opts,
|
||||||
|
)
|
||||||
|
height_settings = ha_mqtt_discoverable.Settings(
|
||||||
|
mqtt=mqtt_settings, entity=height_info
|
||||||
|
)
|
||||||
|
|
||||||
|
def height_callback(
|
||||||
|
client: paho.mqtt.client.Client,
|
||||||
|
user_data: None,
|
||||||
|
message: paho.mqtt.client.MQTTMessage,
|
||||||
|
) -> None:
|
||||||
|
global target_height
|
||||||
|
target_height = float(message.payload.decode())
|
||||||
|
log.info(f"Requested height to {target_height:.1f}")
|
||||||
|
|
||||||
|
height = ha_mqtt_discoverable.sensors.Number(
|
||||||
|
height_settings, height_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
height_max_info = ha_mqtt_discoverable.sensors.SensorInfo(
|
||||||
|
name="Estimated height max",
|
||||||
|
unique_id="desk_height_max",
|
||||||
|
entity_category="diagnostic",
|
||||||
|
**common_opts,
|
||||||
|
)
|
||||||
|
height_max_settings = ha_mqtt_discoverable.Settings(
|
||||||
|
mqtt=mqtt_settings, entity=height_max_info
|
||||||
|
)
|
||||||
|
height_max = ha_mqtt_discoverable.sensors.Sensor(height_max_settings)
|
||||||
|
|
||||||
|
height_min_info = ha_mqtt_discoverable.sensors.SensorInfo(
|
||||||
|
name="Estimated height min",
|
||||||
|
unique_id="desk_height_min",
|
||||||
|
entity_category="diagnostic",
|
||||||
|
**common_opts,
|
||||||
|
)
|
||||||
|
height_min_settings = ha_mqtt_discoverable.Settings(
|
||||||
|
mqtt=mqtt_settings, entity=height_min_info
|
||||||
|
)
|
||||||
|
height_min = ha_mqtt_discoverable.sensors.Sensor(height_min_settings)
|
||||||
|
|
||||||
|
def fetch_callback(desk: Desk) -> None:
|
||||||
|
log.debug("Received state, sending")
|
||||||
|
hcur = desk.get_height()
|
||||||
|
hmin, hmax = desk.get_height_bounds()
|
||||||
|
|
||||||
|
# If none this will set as unknown
|
||||||
|
# Also readings can be a bit outside the boundaries,
|
||||||
|
# so this skips verification
|
||||||
|
if isinstance(hcur, float):
|
||||||
|
hcur = round(hcur, ndigits=ndigits)
|
||||||
|
height._update_state(hcur)
|
||||||
|
|
||||||
|
height_max._update_state(round(hmax, ndigits=ndigits))
|
||||||
|
height_min._update_state(round(hmin, ndigits=ndigits))
|
||||||
|
|
||||||
|
desk.fetch_callback = fetch_callback
|
||||||
|
|
||||||
|
interval = 0.2
|
||||||
|
# Need to be rective to catch
|
||||||
|
while True:
|
||||||
|
if target_height:
|
||||||
|
temp_target_height = target_height
|
||||||
|
# Allows queuing of other instructions while moving
|
||||||
|
target_height = None
|
||||||
|
desk.move_to(temp_target_height)
|
||||||
|
else:
|
||||||
|
time.sleep(interval)
|
||||||
|
desk.fetch()
|
|
@ -1,29 +1,41 @@
|
||||||
{ passwordFile ? "/should_not_be_needed_in_this_context", ... }:
|
{ pkgs, lib, config, ... }:
|
||||||
# TODO Find a way to use keys in filesystem
|
# TODO Find a way to use keys in filesystem
|
||||||
# TODO Not relatime everywhere, thank you
|
# TODO Not relatime everywhere, thank you
|
||||||
# TODO Default options
|
# TODO Default options
|
||||||
let
|
let
|
||||||
btrfs_args_hdd = [
|
btrfs_args_ssd = [
|
||||||
"rw"
|
"rw"
|
||||||
"relatime"
|
"relatime"
|
||||||
"compress=zstd:3"
|
"compress=zstd:3"
|
||||||
"space_cache"
|
"space_cache"
|
||||||
|
"ssd"
|
||||||
];
|
];
|
||||||
btrfs_args_ssd = btrfs_args_hdd ++ [ "ssd" ];
|
passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
razmo = {
|
razmo = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/ata-ST1000LM048-2E7172_WKP8925H";
|
device = "/dev/disk/by-id/ata-SDLF1DAR-960G-1HA1_A027C1A3";
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
|
ESP = {
|
||||||
|
# Needs enough to store multiple kernel generations
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [
|
||||||
|
"defaults"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
swap = {
|
swap = {
|
||||||
priority = 10;
|
size = "8G";
|
||||||
start = "2048";
|
|
||||||
size = "6G";
|
|
||||||
content = {
|
content = {
|
||||||
type = "swap";
|
type = "swap";
|
||||||
randomEncryption = true;
|
randomEncryption = true;
|
||||||
|
@ -32,81 +44,29 @@ in
|
||||||
# hibernation image is saved. That's what I'm doing with Arch,
|
# hibernation image is saved. That's what I'm doing with Arch,
|
||||||
# but I'm setting resume=, should test if it actually works?
|
# but I'm setting resume=, should test if it actually works?
|
||||||
# Untranslated options from /etc/crypttab: swap,cipher=aes-xts-plain64,size=256
|
# Untranslated options from /etc/crypttab: swap,cipher=aes-xts-plain64,size=256
|
||||||
# Untranslated options from /etc/fstab: defaults,pri=100
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
nixosboot = {
|
luks = {
|
||||||
priority = 15;
|
size = "100%";
|
||||||
size = "2G";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
esp = {
|
|
||||||
priority = 20;
|
|
||||||
size = "128M";
|
|
||||||
type = "EF00"; # EFI system partition
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/efi";
|
|
||||||
mountOptions = [
|
|
||||||
"rw"
|
|
||||||
"relatime"
|
|
||||||
"fmask=0022"
|
|
||||||
"dmask=0022"
|
|
||||||
"codepage=437"
|
|
||||||
"iocharset=iso8859-1"
|
|
||||||
"shortname=mixed"
|
|
||||||
"utf8"
|
|
||||||
"errors=remount-ro"
|
|
||||||
"noauto"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
boot = {
|
|
||||||
priority = 30;
|
|
||||||
size = "128M";
|
|
||||||
content = {
|
|
||||||
type = "luks";
|
|
||||||
name = "boot";
|
|
||||||
extraFormatArgs = [ "--type luks1" ];
|
|
||||||
passwordFile = passwordFile;
|
|
||||||
settings = {
|
|
||||||
# keyFile = "/etc/keys/boot";
|
|
||||||
};
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext2";
|
|
||||||
mountpoint = "/mnt/old/boot";
|
|
||||||
mountOptions = [
|
|
||||||
"rw"
|
|
||||||
"relatime"
|
|
||||||
# "stripe=4" # For some reason doesn't work on NixOS
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
main = {
|
|
||||||
priority = 40;
|
|
||||||
content = {
|
content = {
|
||||||
type = "luks";
|
type = "luks";
|
||||||
name = "razmo";
|
name = "razmo";
|
||||||
passwordFile = passwordFile;
|
passwordFile = passwordFile;
|
||||||
settings = {
|
settings = {
|
||||||
# keyFile = "/etc/keys/razmo";
|
allowDiscards = true;
|
||||||
};
|
};
|
||||||
content = {
|
content = {
|
||||||
type = "btrfs";
|
type = "btrfs";
|
||||||
# extraArgs = [ "-f" ];
|
extraArgs = [ "-f" ];
|
||||||
mountpoint = "/mnt/razmo";
|
mountpoint = "/mnt/razmo";
|
||||||
mountOptions = btrfs_args_hdd;
|
|
||||||
subvolumes = {
|
subvolumes = {
|
||||||
"home.razmo" = {
|
"home.razmo" = {
|
||||||
mountpoint = "/home.heavy";
|
mountpoint = "/home.heavy";
|
||||||
mountOptions = btrfs_args_hdd;
|
mountOptions = [ "compress=zstd" "relatime" ];
|
||||||
|
};
|
||||||
|
"steam" = {
|
||||||
|
mountpoint = "/opt/steam.razmo";
|
||||||
|
mountOptions = [ "compress=zstd" "noatime" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -158,10 +118,6 @@ in
|
||||||
mountpoint = "/mnt/rapido";
|
mountpoint = "/mnt/rapido";
|
||||||
mountOptions = btrfs_args_ssd;
|
mountOptions = btrfs_args_ssd;
|
||||||
subvolumes = {
|
subvolumes = {
|
||||||
archlinux = {
|
|
||||||
mountpoint = "/mnt/old";
|
|
||||||
mountOptions = btrfs_args_ssd;
|
|
||||||
};
|
|
||||||
# Should be temporary, to make sure we can revert to Arch anytime
|
# Should be temporary, to make sure we can revert to Arch anytime
|
||||||
"home.nixos" = {
|
"home.nixos" = {
|
||||||
mountpoint = "/home";
|
mountpoint = "/home";
|
||||||
|
@ -187,4 +143,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
services.btrfs.autoScrub = {
|
||||||
|
enable = true;
|
||||||
|
fileSystems = [ "/mnt/razmo" "/mnt/rapido" ];
|
||||||
|
# TODO Should be generable from disko config, right?
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -1,17 +1,16 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
frogeye = {
|
frogeye = {
|
||||||
desktop.xorg = true;
|
desktop = {
|
||||||
|
xorg = true;
|
||||||
|
};
|
||||||
dev = {
|
dev = {
|
||||||
ansible = true;
|
|
||||||
c = true;
|
c = true;
|
||||||
docker = true;
|
docker = true;
|
||||||
fpga = true;
|
vm = true;
|
||||||
perl = true;
|
|
||||||
php = true;
|
|
||||||
python = true;
|
|
||||||
};
|
};
|
||||||
extra = true;
|
extra = true;
|
||||||
gaming = true;
|
gaming = true;
|
||||||
|
storageSize = "big";
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -1,11 +1,29 @@
|
||||||
{ lib, ... }:
|
{ pkgs, lib, nixos-hardware, unixpkgs, ... }:
|
||||||
|
let
|
||||||
|
displays = {
|
||||||
|
embedded = {
|
||||||
|
output = "eDP-1";
|
||||||
|
edid = "00ffffffffffff000dae381700000000011c01049526157802a155a556519d280b505400000001010101010101010101010101010101b43b804a71383440302035007dd61000001ac32f804a71383440302035007dd61000001a000000fe003059395747803137334843450a00000000000041319e001000000a010a2020004f";
|
||||||
|
};
|
||||||
|
deskLeft = {
|
||||||
|
output = "HDMI-1-3"; # Internal HDMI port
|
||||||
|
edid = "00ffffffffffff004c2d7b09333032302f160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d434230333533340a2020010702010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2";
|
||||||
|
};
|
||||||
|
deskRight = {
|
||||||
|
output = "DVI-I-2-1"; # DisplayLink
|
||||||
|
edid = "00ffffffffffff004c2d7b093330323020160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d433830303836350a2020011c02010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
config = {
|
||||||
<nixos-hardware/dell/g3/3779>
|
boot = {
|
||||||
];
|
# From nixos-generate-config
|
||||||
|
initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "sd_mod" "rtsx_usb_sdmmc" ];
|
||||||
|
kernelModules = [ "kvm-intel" ];
|
||||||
|
|
||||||
# UEFI works here, and variables can be touched
|
# UEFI works here, and variables can be touched
|
||||||
boot.loader = {
|
loader = {
|
||||||
efi.canTouchEfiVariables = lib.mkDefault true;
|
efi.canTouchEfiVariables = lib.mkDefault true;
|
||||||
grub = {
|
grub = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -14,4 +32,82 @@
|
||||||
# TODO Maybe we could? In case the HDD doesn't boot anymore?
|
# TODO Maybe we could? In case the HDD doesn't boot anymore?
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
# Also from nixos-generate-config
|
||||||
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
# TODO Do we really need that? Besides maybe microcode?
|
||||||
|
|
||||||
|
frogeye.desktop = {
|
||||||
|
x11_screens = [
|
||||||
|
displays.deskLeft.output
|
||||||
|
displays.deskRight.output
|
||||||
|
];
|
||||||
|
maxVideoHeight = 1440;
|
||||||
|
numlock = true;
|
||||||
|
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 &
|
||||||
|
'';
|
||||||
|
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 &
|
||||||
|
'';
|
||||||
|
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 &
|
||||||
|
'';
|
||||||
|
# TODO Display 2 doesn't work anymore?
|
||||||
|
};
|
||||||
|
};
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(self: super: {
|
||||||
|
displaylink = (import unixpkgs {
|
||||||
|
inherit (super) system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
}).displaylink;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
services = {
|
||||||
|
autorandr = {
|
||||||
|
profiles = {
|
||||||
|
portable = {
|
||||||
|
fingerprint.${displays.embedded.output} = displays.embedded.edid;
|
||||||
|
config.${displays.embedded.output} = { };
|
||||||
|
};
|
||||||
|
extOnly = {
|
||||||
|
fingerprint = {
|
||||||
|
${displays.embedded.output} = displays.embedded.edid;
|
||||||
|
${displays.deskLeft.output} = displays.deskLeft.edid;
|
||||||
|
${displays.deskRight.output} = displays.deskRight.edid;
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
${displays.embedded.output}.enable = false;
|
||||||
|
${displays.deskLeft.output} = {
|
||||||
|
primary = true;
|
||||||
|
mode = "1920x1200";
|
||||||
|
rate = "59.95";
|
||||||
|
position = "0x0";
|
||||||
|
};
|
||||||
|
${displays.deskRight.output} = {
|
||||||
|
mode = "1920x1200";
|
||||||
|
rate = "59.95";
|
||||||
|
position = "1920x0";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# TODO leftOnly and other things.Might want to abstract a few things first.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# Needs prefetched binary blobs, see https://nixos.wiki/wiki/Displaylink
|
||||||
|
xserver.videoDrivers = [ "displaylink" "modesetting" ];
|
||||||
|
# TODO See if nvidia and DL can work together.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
nixos-hardware.nixosModules.dell-g3-3779
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
14
curacao/homeautomation/default.nix
Normal file
14
curacao/homeautomation/default.nix
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
networking = {
|
||||||
|
# Allow mpd control from home assistant and phone
|
||||||
|
firewall.extraCommands = ''
|
||||||
|
iptables -A nixos-fw -p tcp -m tcp --dport 6600 -s 192.168.7.53 -j nixos-fw-accept
|
||||||
|
iptables -A nixos-fw -p tcp -m tcp --dport 6600 -s 192.168.7.92 -j nixos-fw-accept
|
||||||
|
'';
|
||||||
|
interfaces.enp3s0.wakeOnLan.enable = true;
|
||||||
|
};
|
||||||
|
services.tlp.settings.WOL_DISABLE = false;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
frogeye = {
|
|
||||||
desktop = {
|
|
||||||
xorg = true;
|
|
||||||
x11_screens = [ "HDMI-1-0" "eDP-1" ];
|
|
||||||
maxVideoHeight = 1440;
|
|
||||||
numlock = true;
|
|
||||||
phasesBrightness = {
|
|
||||||
enable = true;
|
|
||||||
jour = "40000";
|
|
||||||
crepuscule = "10000";
|
|
||||||
nuit = "1";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
dev = {
|
|
||||||
docker = true;
|
|
||||||
};
|
|
||||||
extra = true;
|
|
||||||
gaming = true;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../os
|
|
||||||
./options.nix
|
|
||||||
./hardware.nix
|
|
||||||
./dk.nix
|
|
||||||
./backup
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "curacao";
|
|
||||||
boot = {
|
|
||||||
initrd.luks.reusePassphrases = true;
|
|
||||||
loader = {
|
|
||||||
efi.efiSysMountPoint = "/efi";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
13
curacao/usb.nix
Normal file
13
curacao/usb.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/usb-Kingston_DataTraveler_3.0_E0D55EA57414F510489F0F1A-0:0";
|
||||||
|
frogeye.name = "curacao-usb";
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
../common/disko/single_uefi_btrfs.nix
|
||||||
|
./features.nix
|
||||||
|
./hardware.nix
|
||||||
|
];
|
||||||
|
}
|
14
curacao/webcam/default.nix
Normal file
14
curacao/webcam/default.nix
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
# TODO This should install cameractrls, but it seems like it's not easy to install.
|
||||||
|
# In the meantime, we install Flatpak and do:
|
||||||
|
# flatpak run hu.irl.cameractrls
|
||||||
|
services.flatpak.enable = true;
|
||||||
|
xdg.portal = {
|
||||||
|
config.common.default = "*";
|
||||||
|
enable = true;
|
||||||
|
extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../hm
|
|
||||||
../curacao/options.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
home.username = "gnix";
|
|
||||||
home.homeDirectory = "/home/gnix";
|
|
||||||
|
|
||||||
frogeye.desktop.nixGLIntel = true;
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
{ ... } @ args:
|
|
||||||
import ../dk/single_uefi_btrfs.nix (args // { id = "usb-Kingston_DataTraveler_3.0_E0D55EA57414F510489F0F1A-0:0"; name = "curacao_usb"; })
|
|
|
@ -1,22 +0,0 @@
|
||||||
{ pkgs, config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../os
|
|
||||||
../curacao/options.nix
|
|
||||||
../curacao/hardware.nix
|
|
||||||
./dk.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "curacao_usb";
|
|
||||||
|
|
||||||
# It's a removable drive, so no touching EFI vars
|
|
||||||
# (quite a lot of stuff to set for that!)
|
|
||||||
boot.loader = {
|
|
||||||
efi.canTouchEfiVariables = false;
|
|
||||||
grub = {
|
|
||||||
efiInstallAsRemovable = true;
|
|
||||||
device = "nodev";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Runs the command given in a Nix environment, and create it if it doesn't exist.
|
|
||||||
# Useful for environments where nix isn't installed / you do not have root access
|
|
||||||
|
|
||||||
# If you need a fresh slate:
|
|
||||||
# chmod +w .nix -R
|
|
||||||
# rm -rf .nix .nix-defexpr .nix-profile .config/nix .local/state/nix .local/share/nix .cache/nix
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
||||||
|
|
||||||
if [ ! -d /nix ]
|
|
||||||
then
|
|
||||||
# Doesn't support architectures other than x86_64
|
|
||||||
NIX_USER_CHROOT_URL=https://github.com/nix-community/nix-user-chroot/releases/download/1.2.2/nix-user-chroot-bin-1.2.2-x86_64-unknown-linux-musl
|
|
||||||
NIX_USER_CHROOT_SHA256SUM=e11aff604bb8d3ffd1d9c0c68cd636816d7eb8da540de18ee3a41ccad7ac0972
|
|
||||||
|
|
||||||
nix_user_chroot="$HOME/.local/bin/nix-user-chroot"
|
|
||||||
mkdir -p "$(dirname "$nix_user_chroot")"
|
|
||||||
|
|
||||||
nix_directory="$HOME/.nix"
|
|
||||||
mkdir -p "$nix_directory"
|
|
||||||
|
|
||||||
if [ ! -x "$nix_user_chroot" ] || ! echo "$NIX_USER_CHROOT_SHA256SUM $nix_user_chroot" | sha256sum --check --status
|
|
||||||
then
|
|
||||||
wget "$NIX_USER_CHROOT_URL" -O "$nix_user_chroot"
|
|
||||||
echo "$NIX_USER_CHROOT_SHA256SUM $nix_user_chroot" | sha256sum --check --status
|
|
||||||
chmod +x "$nix_user_chroot"
|
|
||||||
fi
|
|
||||||
exec "$nix_user_chroot" "$nix_directory" "$0" "$@"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
nix_profile_path="$HOME/.nix-profile/etc/profile.d/nix.sh"
|
|
||||||
|
|
||||||
if [ ! -f "$nix_profile_path" ]
|
|
||||||
then
|
|
||||||
NIX_INSTALLER_URL=https://releases.nixos.org/nix/nix-2.19.2/install
|
|
||||||
NIX_INSTALLER_SHA256SUM=435f0d7e11f7c7dffeeab0ec9cc55723f6d3c03352379d785633cf4ddb5caf90
|
|
||||||
|
|
||||||
nix_installer="$(mktemp)"
|
|
||||||
|
|
||||||
wget "$NIX_INSTALLER_URL" -O "$nix_installer"
|
|
||||||
echo "$NIX_INSTALLER_SHA256SUM $nix_installer" | sha256sum --check --status
|
|
||||||
chmod +x "$nix_installer"
|
|
||||||
|
|
||||||
"$nix_installer" --no-daemon --yes --no-channel-add --no-modify-profile
|
|
||||||
fi
|
|
||||||
|
|
||||||
. "$nix_profile_path"
|
|
||||||
|
|
||||||
"${SCRIPT_DIR}/add_channels.sh"
|
|
||||||
|
|
||||||
exec "$@"
|
|
718
flake.lock
Normal file
718
flake.lock
Normal file
|
@ -0,0 +1,718 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"base16": {
|
||||||
|
"inputs": {
|
||||||
|
"fromYaml": "fromYaml"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708890466,
|
||||||
|
"narHash": "sha256-LlrC09LoPi8OPYOGPXegD72v+//VapgAqhbOFS3i8sc=",
|
||||||
|
"owner": "SenchoPens",
|
||||||
|
"repo": "base16.nix",
|
||||||
|
"rev": "665b3c6748534eb766c777298721cece9453fdae",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "SenchoPens",
|
||||||
|
"repo": "base16.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-fish": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1622559957,
|
||||||
|
"narHash": "sha256-PebymhVYbL8trDVVXxCvZgc0S5VxI7I1Hv4RMSquTpA=",
|
||||||
|
"owner": "tomyun",
|
||||||
|
"repo": "base16-fish",
|
||||||
|
"rev": "2f6dd973a9075dabccd26f1cded09508180bf5fe",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tomyun",
|
||||||
|
"repo": "base16-fish",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-foot": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696725948,
|
||||||
|
"narHash": "sha256-65bz2bUL/yzZ1c8/GQASnoiGwaF8DczlxJtzik1c0AU=",
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-foot",
|
||||||
|
"rev": "eedbcfa30de0a4baa03e99f5e3ceb5535c2755ce",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-foot",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-helix": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696727917,
|
||||||
|
"narHash": "sha256-FVrbPk+NtMra0jtlC5oxyNchbm8FosmvXIatkRbYy1g=",
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-helix",
|
||||||
|
"rev": "dbe1480d99fe80f08df7970e471fac24c05f2ddb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-helix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-kitty": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1665001328,
|
||||||
|
"narHash": "sha256-aRaizTYPpuWEcvoYE9U+YRX+Wsc8+iG0guQJbvxEdJY=",
|
||||||
|
"owner": "kdrag0n",
|
||||||
|
"repo": "base16-kitty",
|
||||||
|
"rev": "06bb401fa9a0ffb84365905ffbb959ae5bf40805",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kdrag0n",
|
||||||
|
"repo": "base16-kitty",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-tmux": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696725902,
|
||||||
|
"narHash": "sha256-wDPg5elZPcQpu7Df0lI5O8Jv4A3T6jUQIVg63KDU+3Q=",
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-tmux",
|
||||||
|
"rev": "c02050bebb60dbb20cb433cd4d8ce668ecc11ba7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tinted-theming",
|
||||||
|
"repo": "base16-tmux",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base16-vim": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1663659192,
|
||||||
|
"narHash": "sha256-uJvaYYDMXvoo0fhBZUhN8WBXeJ87SRgof6GEK2efFT0=",
|
||||||
|
"owner": "chriskempson",
|
||||||
|
"repo": "base16-vim",
|
||||||
|
"rev": "3be3cd82cd31acfcab9a41bad853d9c68d30478d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "chriskempson",
|
||||||
|
"repo": "base16-vim",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1728330715,
|
||||||
|
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disko": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1730190761,
|
||||||
|
"narHash": "sha256-o5m5WzvY6cGIDupuOvjgNSS8AN6yP2iI9MtUC6q/uos=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"rev": "3979285062d6781525cded0f6c4ff92e71376b55",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "disko",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696426674,
|
||||||
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
|
"revCount": 57,
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat_2": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696426674,
|
||||||
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat_3": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1673956053,
|
||||||
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1727826117,
|
||||||
|
"narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1726560853,
|
||||||
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fromYaml": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689549921,
|
||||||
|
"narHash": "sha256-iX0pk/uB019TdBGlaJEWvBCfydT6sRq+eDcGPifVsCM=",
|
||||||
|
"owner": "SenchoPens",
|
||||||
|
"repo": "fromYaml",
|
||||||
|
"rev": "11fbbbfb32e3289d3c631e0134a23854e7865c84",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "SenchoPens",
|
||||||
|
"repo": "fromYaml",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"git-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat_2",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-stable": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729104314,
|
||||||
|
"narHash": "sha256-pZRZsq5oCdJt3upZIU4aslS9XwFJ+/nVtALHIciX/BI=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"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": {
|
||||||
|
"lastModified": 1713702291,
|
||||||
|
"narHash": "sha256-zYP1ehjtcV8fo+c+JFfkAqktZ384Y+y779fzmR9lQAU=",
|
||||||
|
"owner": "GNOME",
|
||||||
|
"repo": "gnome-shell",
|
||||||
|
"rev": "0d0aadf013f78a7f7f1dc984d0d812971864b934",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "GNOME",
|
||||||
|
"ref": "46.1",
|
||||||
|
"repo": "gnome-shell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1726989464,
|
||||||
|
"narHash": "sha256-Vl+WVTJwutXkimwGprnEtXc/s/s8sMuXzqXaspIGlwM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "2f23fa308a7c067e52dfcc30a0758f47043ec176",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "home-manager",
|
||||||
|
"ref": "release-24.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1726989464,
|
||||||
|
"narHash": "sha256-Vl+WVTJwutXkimwGprnEtXc/s/s8sMuXzqXaspIGlwM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "2f23fa308a7c067e52dfcc30a0758f47043ec176",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "release-24.05",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager_3": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"stylix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1714981474,
|
||||||
|
"narHash": "sha256-b3/U21CJjCjJKmA9WqUbZGZgCvospO3ArOUTgJugkOY=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "6ebe7be2e67be7b9b54d61ce5704f6fb466c536f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-darwin": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729826725,
|
||||||
|
"narHash": "sha256-w3WNlYxqWYsuzm/jgFPyhncduoDNjot28aC8j39TW0U=",
|
||||||
|
"owner": "lnl7",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"rev": "7840909b00fbd5a183008a6eb251ea307fe4a76e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "lnl7",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-formatter-pack": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nix-on-droid",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nmd": [
|
||||||
|
"nix-on-droid",
|
||||||
|
"nmd"
|
||||||
|
],
|
||||||
|
"nmt": "nmt"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705252799,
|
||||||
|
"narHash": "sha256-HgSTREh7VoXjGgNDwKQUYcYo13rPkltW7IitHrTPA5c=",
|
||||||
|
"owner": "Gerschtli",
|
||||||
|
"repo": "nix-formatter-pack",
|
||||||
|
"rev": "2de39dedd79aab14c01b9e2934842051a160ffa5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Gerschtli",
|
||||||
|
"repo": "nix-formatter-pack",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-on-droid": {
|
||||||
|
"inputs": {
|
||||||
|
"home-manager": [
|
||||||
|
"home-manager"
|
||||||
|
],
|
||||||
|
"nix-formatter-pack": "nix-formatter-pack",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-docs": "nixpkgs-docs",
|
||||||
|
"nixpkgs-for-bootstrap": "nixpkgs-for-bootstrap",
|
||||||
|
"nmd": "nmd"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1725658585,
|
||||||
|
"narHash": "sha256-P29z4Gt89n5ps1U7+qmIrj0BuRXGZQSIaOe2+tsPgfw=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nix-on-droid",
|
||||||
|
"rev": "5d88ff2519e4952f8d22472b52c531bb5f1635fc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nix-on-droid",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixos-hardware": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1730161780,
|
||||||
|
"narHash": "sha256-z5ILcmwMtiCoHTXS1KsQWqigO7HJO8sbyK7f7wn9F/E=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixos-hardware",
|
||||||
|
"rev": "07d15e8990d5d86a631641b4c429bc0a7400cfb8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixos-hardware",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1730137625,
|
||||||
|
"narHash": "sha256-9z8oOgFZiaguj+bbi3k4QhAD6JabWrnv7fscC/mt0KE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "64b80bfb316b57cdb8919a9110ef63393d74382a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-24.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-docs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705957679,
|
||||||
|
"narHash": "sha256-Q8LJaVZGJ9wo33wBafvZSzapYsjOaNjP/pOnSiKVGHY=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9a333eaa80901efe01df07eade2c16d183761fa3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "release-23.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-for-bootstrap": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1720244366,
|
||||||
|
"narHash": "sha256-WrDV0FPMVd2Sq9hkR5LNHudS3OSMmUrs90JUTN+MXpA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "49ee0e94463abada1de470c9c07bfc12b36dcf40",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "49ee0e94463abada1de470c9c07bfc12b36dcf40",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixvim": {
|
||||||
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
|
"home-manager": "home-manager_2",
|
||||||
|
"nix-darwin": "nix-darwin",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729945968,
|
||||||
|
"narHash": "sha256-4u+nbBSMuXWGCtXxUPPEflRm54+y/HLIbhIep9do8Ew=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixvim",
|
||||||
|
"rev": "c05ac01070425ed0797b1ff678dc690c333cea74",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "nixos-24.05",
|
||||||
|
"repo": "nixvim",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nmd": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nix-on-droid",
|
||||||
|
"nixpkgs-docs"
|
||||||
|
],
|
||||||
|
"scss-reset": "scss-reset"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705050560,
|
||||||
|
"narHash": "sha256-x3zzcdvhJpodsmdjqB4t5mkVW22V3wqHLOun0KRBzUI=",
|
||||||
|
"owner": "~rycee",
|
||||||
|
"repo": "nmd",
|
||||||
|
"rev": "66d9334933119c36f91a78d565c152a4fdc8d3d3",
|
||||||
|
"type": "sourcehut"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "~rycee",
|
||||||
|
"repo": "nmd",
|
||||||
|
"type": "sourcehut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nmt": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1648075362,
|
||||||
|
"narHash": "sha256-u36WgzoA84dMVsGXzml4wZ5ckGgfnvS0ryzo/3zn/Pc=",
|
||||||
|
"owner": "rycee",
|
||||||
|
"repo": "nmt",
|
||||||
|
"rev": "d83601002c99b78c89ea80e5e6ba21addcfe12ae",
|
||||||
|
"type": "gitlab"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rycee",
|
||||||
|
"repo": "nmt",
|
||||||
|
"type": "gitlab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nur": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1730300129,
|
||||||
|
"narHash": "sha256-QZm3ZsHn/75VsGg7ScPGfdByqBPFIQHmbpjT37iQp2g=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "NUR",
|
||||||
|
"rev": "656dcf946af3e368dd872fe525439518d8423080",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "NUR",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"disko": "disko",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"home-manager": "home-manager",
|
||||||
|
"nix-on-droid": "nix-on-droid",
|
||||||
|
"nixos-hardware": "nixos-hardware",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"nixvim": "nixvim",
|
||||||
|
"nur": "nur",
|
||||||
|
"stylix": "stylix",
|
||||||
|
"unixpkgs": "unixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scss-reset": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1631450058,
|
||||||
|
"narHash": "sha256-muDlZJPtXDIGevSEWkicPP0HQ6VtucbkMNygpGlBEUM=",
|
||||||
|
"owner": "andreymatin",
|
||||||
|
"repo": "scss-reset",
|
||||||
|
"rev": "0cf50e27a4e95e9bb5b1715eedf9c54dee1a5a91",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "andreymatin",
|
||||||
|
"repo": "scss-reset",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stylix": {
|
||||||
|
"inputs": {
|
||||||
|
"base16": "base16",
|
||||||
|
"base16-fish": "base16-fish",
|
||||||
|
"base16-foot": "base16-foot",
|
||||||
|
"base16-helix": "base16-helix",
|
||||||
|
"base16-kitty": "base16-kitty",
|
||||||
|
"base16-tmux": "base16-tmux",
|
||||||
|
"base16-vim": "base16-vim",
|
||||||
|
"flake-compat": "flake-compat_3",
|
||||||
|
"gnome-shell": "gnome-shell",
|
||||||
|
"home-manager": "home-manager_3",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1718122552,
|
||||||
|
"narHash": "sha256-A+dBkSwp8ssHKV/WyXb9uqIYrHBqHvtSedU24Lq9lqw=",
|
||||||
|
"owner": "danth",
|
||||||
|
"repo": "stylix",
|
||||||
|
"rev": "e59d2c1725b237c362e4a62f5722f5b268d566c7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "danth",
|
||||||
|
"ref": "release-24.05",
|
||||||
|
"repo": "stylix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729613947,
|
||||||
|
"narHash": "sha256-XGOvuIPW1XRfPgHtGYXd5MAmJzZtOuwlfKDgxX5KT3s=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "aac86347fb5063960eccb19493e0cadcdb4205ca",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1730298926,
|
||||||
|
"narHash": "sha256-ao1BYrrOB8SGdvOul6hGJYqp/QqEJTwZRViRXFvNnTQ=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "838f2f70e0e44d957009bf5a4fc0aa9c931b680e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "master",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
160
flake.nix
Normal file
160
flake.nix
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
{
|
||||||
|
description = "Geoffrey Frogeye's base configurations";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
# Packages
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-24.05";
|
||||||
|
unixpkgs.url = "nixpkgs/master";
|
||||||
|
# OS
|
||||||
|
disko = {
|
||||||
|
url = "disko";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixos-hardware.url = "nixos-hardware";
|
||||||
|
# NOD
|
||||||
|
nix-on-droid = {
|
||||||
|
url = "github:nix-community/nix-on-droid"; # No 24.05 yet
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.home-manager.follows = "home-manager";
|
||||||
|
};
|
||||||
|
# HM
|
||||||
|
home-manager = {
|
||||||
|
url = "home-manager/release-24.05";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
stylix = {
|
||||||
|
url = "github:danth/stylix/release-24.05";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixvim = {
|
||||||
|
url = "github:nix-community/nixvim/nixos-24.05";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nur.url = "github:nix-community/NUR";
|
||||||
|
# Local
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, disko, nix-on-droid, flake-utils, ... }@attrs:
|
||||||
|
# Machine independant outputs
|
||||||
|
let
|
||||||
|
nixpkgsConfig = {
|
||||||
|
config = {
|
||||||
|
allowUnfree = true;
|
||||||
|
};
|
||||||
|
overlays = [
|
||||||
|
(import ./common/update-local-flakes/overlay.nix)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
homeManagerConfig = {
|
||||||
|
sharedModules = [ self.homeManagerModules.dotfiles ];
|
||||||
|
extraSpecialArgs = attrs;
|
||||||
|
};
|
||||||
|
lib = {
|
||||||
|
nixosSystem = { system, modules ? [ ] }: nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
specialArgs = attrs;
|
||||||
|
modules = modules ++ [
|
||||||
|
self.nixosModules.dotfiles
|
||||||
|
{
|
||||||
|
nixpkgs = nixpkgsConfig;
|
||||||
|
home-manager = homeManagerConfig;
|
||||||
|
frogeye.toplevel = { _type = "override"; content = self; priority = 1000; };
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nixOnDroidConfiguration = { modules ? [ ] }: nix-on-droid.lib.nixOnDroidConfiguration {
|
||||||
|
pkgs = import nixpkgs (nixpkgsConfig // {
|
||||||
|
system = "aarch64-linux"; # nod doesn't support anything else
|
||||||
|
});
|
||||||
|
modules = modules ++ [
|
||||||
|
self.nixOnDroidModules.dotfiles
|
||||||
|
{
|
||||||
|
home-manager = homeManagerConfig;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
flakeTools = { self }: flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs (nixpkgsConfig // {
|
||||||
|
inherit system;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
apps = {
|
||||||
|
disko = {
|
||||||
|
type = "app";
|
||||||
|
program = "${disko.packages.${system}.default}/bin/disko";
|
||||||
|
};
|
||||||
|
nixos-install = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.nixos-install-tools}/bin/nixos-install";
|
||||||
|
};
|
||||||
|
nixos-rebuild = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.nixos-rebuild}/bin/nixos-rebuild";
|
||||||
|
};
|
||||||
|
repl = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.writeShellScript "vivarium-repl" ''
|
||||||
|
${pkgs.lix}/bin/nix repl --expr 'let flake = builtins.getFlake "${self}"; in flake // flake.nixosConfigurations // rec { pkgs = import ${nixpkgs} {}; lib = pkgs.lib; }'
|
||||||
|
''}";
|
||||||
|
};
|
||||||
|
# Available globally should this be needed in times of shenanigans
|
||||||
|
updateLocalFlakes = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.update-local-flakes}/bin/update-local-flakes";
|
||||||
|
};
|
||||||
|
nixosRebuild = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.writeShellScript "rebuild" ''${pkgs.writeShellApplication {
|
||||||
|
name = "rebuild";
|
||||||
|
runtimeInputs = with pkgs; [ nix-output-monitor nixos-rebuild ];
|
||||||
|
text = builtins.readFile ./os/rebuild.sh;
|
||||||
|
}}/bin/rebuild ${self} "$@"''}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Reusable configurations
|
||||||
|
inherit lib;
|
||||||
|
nixosModules.dotfiles.imports = [ ./os ];
|
||||||
|
nixOnDroidModules.dotfiles.imports = [ ./nod ];
|
||||||
|
homeManagerModules.dotfiles.imports = [ ./hm ];
|
||||||
|
# Actual configurations
|
||||||
|
nixosConfigurations.curacao = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [ ./curacao ];
|
||||||
|
};
|
||||||
|
nixosConfigurations.curacao-usb = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [ ./curacao/usb.nix ];
|
||||||
|
};
|
||||||
|
nixosConfigurations.pindakaas = lib.nixosSystem {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
modules = [ ./pindakaas ];
|
||||||
|
};
|
||||||
|
nixosConfigurations.pindakaas-sd = lib.nixosSystem {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
modules = [ ./pindakaas/sd.nix ];
|
||||||
|
};
|
||||||
|
nixosConfigurations.cranberry = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [ ./cranberry ];
|
||||||
|
};
|
||||||
|
nixOnDroidConfigurations.sprinkles = lib.nixOnDroidConfiguration { };
|
||||||
|
# Fake systems
|
||||||
|
nixosConfigurations.abavorana = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [ ./abavorana/standin.nix ];
|
||||||
|
};
|
||||||
|
nixosConfigurations.sprinkles = lib.nixosSystem {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
modules = [ ./sprinkles/standin.nix ];
|
||||||
|
};
|
||||||
|
# TODO devices/ or configs/ folders
|
||||||
|
} // (lib.flakeTools { inherit self; });
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
# full profile
|
|
||||||
|
|
||||||
Fake configuration that contains everything I could ever need,
|
|
||||||
used for debugging.
|
|
||||||
Can't build a full system due to not having a filesystem / bootloader configuration,
|
|
||||||
build as a VM (without bootloader).
|
|
10
full/os.nix
10
full/os.nix
|
@ -1,10 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../os
|
|
||||||
./options.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
# Create a different disk image depending on the architecture
|
|
||||||
networking.hostName = "${builtins.currentSystem}";
|
|
||||||
}
|
|
108
hm/accounts/default.nix
Normal file
108
hm/accounts/default.nix
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
{ pkgs, config, lib, ... }:
|
||||||
|
let
|
||||||
|
mkUserJs = with lib; prefs: extraPrefs: ''
|
||||||
|
// Generated by Geoffrey's dotfiles.
|
||||||
|
|
||||||
|
${concatStrings (mapAttrsToList (name: value: ''
|
||||||
|
user_pref("${name}", ${builtins.toJSON value});
|
||||||
|
'') prefs)}
|
||||||
|
${extraPrefs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
toThunderbirdCalendar = account:
|
||||||
|
let
|
||||||
|
id = builtins.hashString "sha256" account.name;
|
||||||
|
thunderbird = config.frogeye.accounts.calendar.accounts.${account.name};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"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}.name" = account.name;
|
||||||
|
"calendar.registry.${id}.readOnly" = thunderbird.readOnly;
|
||||||
|
"calendar.registry.${id}.refreshInterval" = builtins.toString thunderbird.refreshInterval;
|
||||||
|
"calendar.registry.${id}.suppressAlarms" = !thunderbird.showReminders; # TODO Check this actually corresponds
|
||||||
|
"calendar.registry.${id}.type" = account.remote.type; # TODO Check and validate supported types
|
||||||
|
"calendar.registry.${id}.uri" = account.remote.url;
|
||||||
|
"calendar.registry.${id}.username" = account.remote.userName;
|
||||||
|
# Unimplemented
|
||||||
|
"calendar.registry.${id}.notifications.times" = "";
|
||||||
|
# Unknown
|
||||||
|
# "calendar.registry.${id}.calendar-main-in-composite" = true;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
programs.aerc = {
|
||||||
|
enable = true;
|
||||||
|
extraConfig.general.unsafe-accounts-conf = true;
|
||||||
|
};
|
||||||
|
programs.thunderbird = {
|
||||||
|
enable = config.frogeye.desktop.xorg;
|
||||||
|
profiles.hm = {
|
||||||
|
isDefault = true;
|
||||||
|
withExternalGnupg = true;
|
||||||
|
extraConfig = mkUserJs
|
||||||
|
(lib.attrsets.mergeAttrsList (
|
||||||
|
# Add calendar config
|
||||||
|
(lib.mapAttrsToList (name: account: (toThunderbirdCalendar account)) config.accounts.calendar.accounts) ++
|
||||||
|
# Add config for every identity (kinda)
|
||||||
|
(lib.mapAttrsToList
|
||||||
|
(name: account: ({
|
||||||
|
# UPST Make signature be used in Thunderbird
|
||||||
|
"mail.identity.id_${builtins.hashString "sha256" account.address}.htmlSigText" = account.signature.text;
|
||||||
|
"mail.identity.id_${builtins.hashString "sha256" account.address}.compose_html" = false;
|
||||||
|
}))
|
||||||
|
config.accounts.email.accounts) ++
|
||||||
|
# General settings
|
||||||
|
[{
|
||||||
|
"mail.pane_config.dynamic" = 0;
|
||||||
|
"intl.date_time.pattern_override.date_short" = "yyyy-MM-dd";
|
||||||
|
}]
|
||||||
|
|
||||||
|
)) "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# UPST Thunderbird-specific options (should be named so), to be included in HM Thunderbird module
|
||||||
|
options = {
|
||||||
|
frogeye.accounts.calendar.accounts = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: {
|
||||||
|
# TODO Set defaults as Thunderbird sets it
|
||||||
|
options = {
|
||||||
|
color = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "#5277c3";
|
||||||
|
};
|
||||||
|
refreshInterval = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 0; # 0 = Manual
|
||||||
|
};
|
||||||
|
readOnly = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
showReminders = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
offlineSupport = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
email = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
# TODO Nullable
|
||||||
|
# TODO Ensure it actually matches an email identity
|
||||||
|
};
|
||||||
|
clientSideEmailScheduling = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
34
hm/brightness/default.nix
Normal file
34
hm/brightness/default.nix
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Light theme during the day, dark theme during the night (not automatic)
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
phases = [
|
||||||
|
{ command = "jour"; specialisation = null; }
|
||||||
|
{ command = "crepuscule"; specialisation = "dark"; }
|
||||||
|
{ command = "nuit"; specialisation = "dark"; }
|
||||||
|
];
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
home.packages = (map
|
||||||
|
(phase: (pkgs.writeShellScriptBin phase.command ''
|
||||||
|
switch="/nix/var/nix/profiles/system${lib.strings.optionalString (phase.specialisation != null) "/specialisation/${phase.specialisation}"}/bin/switch-to-configuration"
|
||||||
|
if [ -x "$switch" ]
|
||||||
|
then
|
||||||
|
sudo "$switch" test &
|
||||||
|
sudo "$switch" boot &
|
||||||
|
fi
|
||||||
|
${builtins.getAttr phase.command config.frogeye.desktop.phasesCommands}
|
||||||
|
wait
|
||||||
|
''))
|
||||||
|
phases) ++ (with pkgs; [
|
||||||
|
brightnessctl
|
||||||
|
]);
|
||||||
|
xsession.windowManager.i3.config.keybindings = {
|
||||||
|
XF86MonBrightnessUp = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
|
||||||
|
XF86MonBrightnessDown = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
|
||||||
|
"${mod}+F6" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 1%-";
|
||||||
|
"${mod}+F7" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +1%";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
483
hm/common.nix
483
hm/common.nix
|
@ -1,88 +1,20 @@
|
||||||
{ pkgs, config, lib, ... }:
|
{ pkgs, config, lib, ... }:
|
||||||
let
|
|
||||||
direnv = {
|
|
||||||
# Environment variables making programs stay out of $HOME, but also needing we create a directory for them
|
|
||||||
CARGOHOME = "${config.xdg.cacheHome}/cargo"; # There are config in there that we can version if one want
|
|
||||||
CCACHE_DIR = "${config.xdg.cacheHome}/ccache"; # The config file alone seems to be not enough
|
|
||||||
DASHT_DOCSETS_DIR = "${config.xdg.cacheHome}/dash_docsets";
|
|
||||||
GOPATH = "${config.xdg.cacheHome}/go";
|
|
||||||
GRADLE_USER_HOME = "${config.xdg.cacheHome}/gradle";
|
|
||||||
MIX_ARCHIVES = "${config.xdg.cacheHome}/mix/archives";
|
|
||||||
MONO_GAC_PREFIX = "${config.xdg.cacheHome}/mono";
|
|
||||||
npm_config_cache = "${config.xdg.cacheHome}/npm";
|
|
||||||
PARALLEL_HOME = "${config.xdg.cacheHome}/parallel";
|
|
||||||
TERMINFO = "${config.xdg.configHome}/terminfo";
|
|
||||||
WINEPREFIX = "${config.xdg.stateHome}/wineprefix/default";
|
|
||||||
YARN_CACHE_FOLDER = "${config.xdg.cacheHome}/yarn";
|
|
||||||
# TODO Some of that stuff is not really relavant any more
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
|
frogeye.hooks.lock = ''
|
||||||
nixpkgs.config.allowUnfree = true;
|
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
|
||||||
|
'';
|
||||||
programs =
|
programs = {
|
||||||
let
|
home-manager.enable = true;
|
||||||
commonRc = lib.strings.concatLines ([
|
bat = {
|
||||||
''
|
enable = true;
|
||||||
# Colored ls
|
config.style = "full";
|
||||||
# TODO Doesn't allow completion. Check out lsd instead
|
|
||||||
_colored_ls() {
|
|
||||||
${pkgs.coreutils}/bin/ls -lh --color=always $@ | ${pkgs.gawk}/bin/awk '
|
|
||||||
BEGIN {
|
|
||||||
FPAT = "([[:space:]]*[^[:space:]]+)";
|
|
||||||
OFS = "";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
$1 = "\033[36m" $1 "\033[0m";
|
|
||||||
$2 = "\033[31m" $2 "\033[0m";
|
|
||||||
$3 = "\033[32m" $3 "\033[0m";
|
|
||||||
$4 = "\033[32m" $4 "\033[0m";
|
|
||||||
$5 = "\033[31m" $5 "\033[0m";
|
|
||||||
$6 = "\033[34m" $6 "\033[0m";
|
|
||||||
$7 = "\033[34m" $7 "\033[0m";
|
|
||||||
print
|
|
||||||
}
|
|
||||||
'
|
|
||||||
}
|
|
||||||
alias ll="_colored_ls"
|
|
||||||
alias la="_colored_ls -a"
|
|
||||||
''
|
|
||||||
] ++ map (d: "mkdir -p ${d}") (builtins.attrValues direnv));
|
|
||||||
# TODO Those directory creations should probably done on home-manager activation
|
|
||||||
commonSessionVariables = {
|
|
||||||
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
|
|
||||||
# Less colors
|
|
||||||
LESS = "-R";
|
|
||||||
LESS_TERMCAP_mb = "$(echo $'\\E[1;31m')"; # begin blink
|
|
||||||
LESS_TERMCAP_md = "$(echo $'\\E[1;36m')"; # begin bold
|
|
||||||
LESS_TERMCAP_me = "$(echo $'\\E[0m')"; # reset bold/blink
|
|
||||||
LESS_TERMCAP_so = "$(echo $'\\E[01;44;33m')"; # begin reverse video
|
|
||||||
LESS_TERMCAP_se = "$(echo $'\\E[0m')"; # reset reverse video
|
|
||||||
LESS_TERMCAP_us = "$(echo $'\\E[1;32m')"; # begin underline
|
|
||||||
LESS_TERMCAP_ue = "$(echo $'\\E[0m')"; # reset underline
|
|
||||||
# Fzf
|
|
||||||
FZF_COMPLETION_OPTS = "${lib.strings.concatStringsSep " " config.programs.fzf.fileWidgetOptions}";
|
|
||||||
};
|
};
|
||||||
treatsHomeAsJunk = [
|
bash.shellAliases = {
|
||||||
# Programs that think $HOME is a reasonable place to put their junk
|
# Replacement commands
|
||||||
# and don't allow the user to change those questionable choices
|
# ls = "lsd"; # lsd is suuuper slow for large directories
|
||||||
"adb"
|
cat = "bat -pp";
|
||||||
"audacity"
|
|
||||||
"binwalk" # Should use .config according to the GitHub code though
|
|
||||||
"cabal" # TODO May have options but last time I tried it it crashed
|
|
||||||
"cmake"
|
|
||||||
"ddd"
|
|
||||||
"ghidra"
|
|
||||||
"itch"
|
|
||||||
"simplescreenrecorder" # Easy fix https://github.com/MaartenBaert/ssr/blob/1556ae456e833992fb6d39d40f7c7d7c337a4160/src/Main.cpp#L252
|
|
||||||
"vd"
|
|
||||||
"wpa_cli"
|
|
||||||
# TODO Maybe we can do something about node-gyp
|
|
||||||
];
|
|
||||||
commonShellAliases = {
|
|
||||||
# Completion for existing commands
|
# Completion for existing commands
|
||||||
ls = "ls -h --color=auto";
|
|
||||||
mkdir = "mkdir -v";
|
mkdir = "mkdir -v";
|
||||||
# cp = "cp -i"; # Disabled because conflicts with the ZSH/Bash one. This separation is confusing I swear.
|
# cp = "cp -i"; # Disabled because conflicts with the ZSH/Bash one. This separation is confusing I swear.
|
||||||
mv = "mv -iv";
|
mv = "mv -iv";
|
||||||
|
@ -91,7 +23,8 @@ in
|
||||||
ffmpeg = "ffmpeg -hide_banner";
|
ffmpeg = "ffmpeg -hide_banner";
|
||||||
ffprobe = "ffprobe -hide_banner";
|
ffprobe = "ffprobe -hide_banner";
|
||||||
ffplay = "ffplay -hide_banner";
|
ffplay = "ffplay -hide_banner";
|
||||||
# TODO Add ipython --no-confirm-exit --pdb
|
numbat = "numbat --intro-banner off";
|
||||||
|
insect = "numbat";
|
||||||
|
|
||||||
# Frequent mistakes
|
# Frequent mistakes
|
||||||
sl = "ls";
|
sl = "ls";
|
||||||
|
@ -101,20 +34,12 @@ in
|
||||||
please = "sudo";
|
please = "sudo";
|
||||||
|
|
||||||
# Shortcuts for commonly used commands
|
# Shortcuts for commonly used commands
|
||||||
# ll = "ls -l"; # Disabled because would overwrite the colored one
|
ll = "lsd -l";
|
||||||
# la = "ls -la"; # Eh maybe it's not that bad, but for now let's keep compatibility
|
la = "lsd -la";
|
||||||
s = "sudo -s -E";
|
s = "sudo -s -E";
|
||||||
|
|
||||||
|
|
||||||
# Give additional config to those programs, and not have them in my path
|
|
||||||
bower = "bower --config.storage.packages=${config.xdg.cacheHome}/bower/packages --config.storage.registry=${config.xdg.cacheHome}/bower/registry --config.storage.links=${config.xdg.cacheHome}/bower/links";
|
|
||||||
gdb = "gdb -x ${config.xdg.configHome}/gdbinit";
|
|
||||||
iftop = "iftop -c ${config.xdg.configHome}/iftoprc";
|
|
||||||
lmms = "lmms --config ${config.xdg.configHome}/lmmsrc.xml";
|
|
||||||
|
|
||||||
# Preference
|
# Preference
|
||||||
vi = "nvim";
|
|
||||||
vim = "nvim";
|
|
||||||
wol = "wakeonlan"; # TODO Really, isn't wol better? Also wtf Arch aliases to pass because neither is installed anyways x)
|
wol = "wakeonlan"; # TODO Really, isn't wol better? Also wtf Arch aliases to pass because neither is installed anyways x)
|
||||||
mutt = "neomutt";
|
mutt = "neomutt";
|
||||||
|
|
||||||
|
@ -130,54 +55,31 @@ in
|
||||||
|
|
||||||
# Imported from scripts
|
# Imported from scripts
|
||||||
rms = ''${pkgs.findutils}/bin/find . -name "*.sync-conflict-*" -delete''; # Remove syncthing conflict files
|
rms = ''${pkgs.findutils}/bin/find . -name "*.sync-conflict-*" -delete''; # Remove syncthing conflict files
|
||||||
pw = ''${pkgs.pwgen}/bin/pwgen 32 -y''; # Generate passwords. ln((26*2+10)**32)/ln(2) ≅ 190 bits of entropy
|
|
||||||
newestFile = ''${pkgs.findutils}/bin/find -type f -printf '%T+ %p\n' | sort | tail'';
|
newestFile = ''${pkgs.findutils}/bin/find -type f -printf '%T+ %p\n' | sort | tail'';
|
||||||
oldestFile = ''${pkgs.findutils}/bin/find -type f -printf '%T+ %p\n' | sort | head'';
|
oldestFile = ''${pkgs.findutils}/bin/find -type f -printf '%T+ %p\n' | sort | head'';
|
||||||
tracefiles = ''${pkgs.strace}/bin/strace -f -t -e trace=file'';
|
|
||||||
} // lib.attrsets.mergeAttrsList (map (p: { "${p}" = "HOME=${config.xdg.cacheHome}/junkhome ${p}"; }) treatsHomeAsJunk);
|
|
||||||
# TODO Maybe make nixpkg wrapper instead? So it also works from dmenu
|
|
||||||
# Could also accept my fate... Home-manager doesn't necessarily make it easy to put things out of the home directory
|
|
||||||
historySize = 100000;
|
|
||||||
historyFile = "${config.xdg.stateHome}/shell_history";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
|
|
||||||
home-manager.enable = true;
|
|
||||||
bash = {
|
|
||||||
enable = true;
|
|
||||||
bashrcExtra = lib.strings.concatLines [
|
|
||||||
commonRc
|
|
||||||
''
|
|
||||||
shopt -s expand_aliases
|
|
||||||
shopt -s histappend
|
|
||||||
''
|
|
||||||
];
|
|
||||||
sessionVariables = commonSessionVariables;
|
|
||||||
historySize = historySize;
|
|
||||||
historyFile = historyFile;
|
|
||||||
historyFileSize = historySize;
|
|
||||||
historyControl = [ "erasedups" "ignoredups" "ignorespace" ];
|
|
||||||
shellAliases = commonShellAliases // config.frogeye.shellAliases;
|
|
||||||
};
|
};
|
||||||
zsh = {
|
thefuck = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableAutosuggestions = true;
|
enableBashIntegration = true;
|
||||||
enableCompletion = true;
|
enableZshIntegration = true;
|
||||||
syntaxHighlighting.enable = true;
|
};
|
||||||
historySubstringSearch.enable = true;
|
lsd = {
|
||||||
initExtra = lib.strings.concatLines [
|
enable = true;
|
||||||
commonRc
|
settings = {
|
||||||
(builtins.readFile ./zshrc.sh)
|
size = "short";
|
||||||
];
|
};
|
||||||
defaultKeymap = "viins";
|
colors = {
|
||||||
history = {
|
# Base16 only, so it reuses the current theme.
|
||||||
size = historySize;
|
date = { day-old = 4; hour-old = 6; older = 5; };
|
||||||
save = historySize;
|
git-status = { conflicted = 14; default = 13; deleted = 1; ignored = 13; modified = 3; new-in-index = 2; new-in-workdir = 2; renamed = 4; typechange = 3; unmodified = 13; };
|
||||||
path = historyFile;
|
group = 6;
|
||||||
expireDuplicatesFirst = true;
|
inode = { invalid = 245; valid = 13; };
|
||||||
|
links = { invalid = 9; valid = 14; };
|
||||||
|
permission = { acl = 6; context = 14; exec = 1; exec-sticky = 5; no-access = 245; octal = 6; read = 2; write = 3; };
|
||||||
|
size = { large = 1; medium = 9; none = 11; small = 3; };
|
||||||
|
tree-edge = 13;
|
||||||
|
user = 2;
|
||||||
};
|
};
|
||||||
sessionVariables = commonSessionVariables;
|
|
||||||
shellAliases = commonShellAliases // config.frogeye.shellAliases;
|
|
||||||
};
|
};
|
||||||
dircolors = {
|
dircolors = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -185,101 +87,18 @@ in
|
||||||
enableZshIntegration = true;
|
enableZshIntegration = true;
|
||||||
# UPST This thing put stuff in .dircolors when it actually doesn't have to
|
# UPST This thing put stuff in .dircolors when it actually doesn't have to
|
||||||
};
|
};
|
||||||
powerline-go = {
|
git.enable = true;
|
||||||
enable = true;
|
gpg.enable = true;
|
||||||
modules = [ "user" "host" "venv" "cwd" "perms" "git" ];
|
|
||||||
modulesRight = [ "jobs" "exit" "duration" "load" ];
|
|
||||||
settings = {
|
|
||||||
colorize-hostname = true;
|
|
||||||
max-width = 25;
|
|
||||||
cwd-max-dir-size = 10;
|
|
||||||
duration = "$( test -n \"$__TIMER\" && echo $(( $EPOCHREALTIME - $\{__TIMER:-EPOCHREALTIME})) || echo 0 )";
|
|
||||||
# UPST Implement this properly in home-manager, would allow for bash support
|
|
||||||
};
|
|
||||||
extraUpdatePS1 = ''
|
|
||||||
unset __TIMER
|
|
||||||
echo -en "\033]0; $USER@$HOST $PWD\007"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
gpg = {
|
|
||||||
enable = true;
|
|
||||||
homedir = "${config.xdg.stateHome}/gnupg";
|
|
||||||
settings = {
|
|
||||||
# Remove fluff
|
|
||||||
no-greeting = true;
|
|
||||||
no-emit-version = true;
|
|
||||||
no-comments = true;
|
|
||||||
# Output format that I prefer
|
|
||||||
keyid-format = "0xlong";
|
|
||||||
# Show fingerprints
|
|
||||||
with-fingerprint = true;
|
|
||||||
# Make sure to show if key is invalid
|
|
||||||
# (should be default on most platform,
|
|
||||||
# but just to be sure)
|
|
||||||
list-options = "show-uid-validity";
|
|
||||||
verify-options = "show-uid-validity";
|
|
||||||
# Stronger algorithm (https://wiki.archlinux.org/title/GnuPG#Different_algorithm)
|
|
||||||
personal-digest-preferences = "SHA512";
|
|
||||||
cert-digest-algo = "SHA512";
|
|
||||||
default-preference-list = "SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed";
|
|
||||||
personal-cipher-preferences = "TWOFISH CAMELLIA256 AES 3DES";
|
|
||||||
};
|
|
||||||
publicKeys = [{
|
|
||||||
source = builtins.fetchurl {
|
|
||||||
url = "https://keys.openpgp.org/vks/v1/by-fingerprint/4FBA930D314A03215E2CDB0A8312C8CAC1BAC289";
|
|
||||||
sha256 = "sha256:10y9xqcy1vyk2p8baay14p3vwdnlwynk0fvfbika65hz2z8yw2cm";
|
|
||||||
};
|
|
||||||
trust = "ultimate";
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
fzf = {
|
fzf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableZshIntegration = true;
|
enableZshIntegration = true;
|
||||||
defaultOptions = [ "--height 40%" "--layout=default" ];
|
defaultOptions = [ "--height 40%" "--layout=default" ];
|
||||||
fileWidgetOptions = [ "--preview '[[ -d {} ]] && ${pkgs.coreutils}/bin/ls -l --color=always {} || [[ \$(${pkgs.file}/bin/file --mime {}) =~ binary ]] && ${pkgs.file}/bin/file --brief {} || (${pkgs.highlight}/bin/highlight -O ansi -l {} || coderay {} || rougify {} || ${pkgs.coreutils}/bin/cat {}) 2> /dev/null | head -500'" ];
|
fileWidgetOptions = [ "--preview '[[ -d {} ]] && ${pkgs.coreutils}/bin/ls -l --color=always {} || [[ \$(${pkgs.file}/bin/file --mime {}) =~ binary ]] && ${pkgs.file}/bin/file --brief {} || (${pkgs.highlight}/bin/highlight -O ansi -l {} || coderay {} || rougify {} || ${pkgs.coreutils}/bin/cat {}) 2> /dev/null | head -500'" ];
|
||||||
|
# TODO Above not working... not really used either?
|
||||||
# file and friends are not in PATH by default... so here we want aboslute paths, which means those won't get reloaded. Meh.
|
# file and friends are not in PATH by default... so here we want aboslute paths, which means those won't get reloaded. Meh.
|
||||||
};
|
};
|
||||||
# TODO highlight or bat
|
|
||||||
nix-index = {
|
|
||||||
enable = false; # TODO Index is impossible to generate, should use https://github.com/nix-community/nix-index-database
|
|
||||||
# but got no luck without flakes
|
|
||||||
enableZshIntegration = true;
|
|
||||||
};
|
|
||||||
less.enable = true;
|
less.enable = true;
|
||||||
git = {
|
nixvim.enable = true;
|
||||||
enable = true;
|
|
||||||
package = pkgs.gitFull;
|
|
||||||
aliases = {
|
|
||||||
"git" = "!exec git"; # In case I write one too many git
|
|
||||||
};
|
|
||||||
ignores = [
|
|
||||||
"*.swp"
|
|
||||||
"*.swo"
|
|
||||||
"*.ycm_extra_conf.py"
|
|
||||||
"tags"
|
|
||||||
".mypy_cache"
|
|
||||||
];
|
|
||||||
lfs.enable = true;
|
|
||||||
userEmail = lib.mkDefault "geoffrey@frogeye.fr";
|
|
||||||
userName = lib.mkDefault "Geoffrey Frogeye";
|
|
||||||
extraConfig = {
|
|
||||||
core = {
|
|
||||||
editor = "nvim";
|
|
||||||
};
|
|
||||||
push = {
|
|
||||||
default = "matching";
|
|
||||||
};
|
|
||||||
pull = {
|
|
||||||
ff = "only";
|
|
||||||
};
|
|
||||||
} // lib.optionalAttrs config.frogeye.desktop.xorg {
|
|
||||||
diff.tool = "meld";
|
|
||||||
difftool.prompt = false;
|
|
||||||
"difftool \"meld\"".cmd = "${pkgs.meld}/bin/meld \"$LOCAL\" \"$REMOTE\"";
|
|
||||||
# This escapes quotes, which isn't the case in the original, hoping this isn't an issue.
|
|
||||||
};
|
|
||||||
# TODO Delta syntax highlighter... and other cool-looking options?
|
|
||||||
};
|
|
||||||
readline = {
|
readline = {
|
||||||
enable = true;
|
enable = true;
|
||||||
variables = {
|
variables = {
|
||||||
|
@ -308,82 +127,8 @@ in
|
||||||
};
|
};
|
||||||
extraConfig = builtins.readFile ./inputrc;
|
extraConfig = builtins.readFile ./inputrc;
|
||||||
};
|
};
|
||||||
tmux =
|
tmux.enable = true;
|
||||||
let
|
|
||||||
themepack = pkgs.tmuxPlugins.mkTmuxPlugin
|
|
||||||
rec {
|
|
||||||
pluginName = "tmux-themepack";
|
|
||||||
version = "1.1.0";
|
|
||||||
rtpFilePath = "themepack.tmux";
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "jimeh";
|
|
||||||
repo = "tmux-themepack";
|
|
||||||
rev = "${version}";
|
|
||||||
sha256 = "f6y92kYsKDFanNx5ATx4BkaB/E7UrmyIHU/5Z01otQE=";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
enable = true;
|
|
||||||
mouse = false;
|
|
||||||
clock24 = true;
|
|
||||||
# TODO Vim mode?
|
|
||||||
plugins = with pkgs.tmuxPlugins; [
|
|
||||||
sensible
|
|
||||||
];
|
|
||||||
extraConfig = builtins.readFile ./tmux.conf + "source-file ${themepack}/share/tmux-plugins/tmux-themepack/powerline/default/green.tmuxtheme\n";
|
|
||||||
};
|
|
||||||
translate-shell.enable = true; # TODO Cool config?
|
translate-shell.enable = true; # TODO Cool config?
|
||||||
password-store.enable = true;
|
|
||||||
};
|
|
||||||
services = {
|
|
||||||
gpg-agent = {
|
|
||||||
enable = true; # TODO Consider not enabling it when not having any private key
|
|
||||||
enableBashIntegration = true;
|
|
||||||
enableZshIntegration = true;
|
|
||||||
pinentryFlavor = "gtk2"; # Falls back to curses when needed
|
|
||||||
};
|
|
||||||
# TODO Syncs a bit too often, also constantly asks for passphrase, which is annoying.
|
|
||||||
git-sync = {
|
|
||||||
enable = false;
|
|
||||||
repositories = {
|
|
||||||
dotfiles = {
|
|
||||||
path = "${config.xdg.configHome}/dotfiles";
|
|
||||||
uri = lib.mkDefault "https://git.frogeye.fr/geoffrey/dotfiles.git";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
xdg = {
|
|
||||||
configFile = {
|
|
||||||
"ccache.conf" = {
|
|
||||||
text = "ccache_dir = ${config.xdg.cacheHome}/ccache";
|
|
||||||
};
|
|
||||||
"gdbinit" = {
|
|
||||||
text = ''
|
|
||||||
define hook-quit
|
|
||||||
set confirm off
|
|
||||||
end
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"iftoprc" = {
|
|
||||||
text = ''
|
|
||||||
port-resolution: no
|
|
||||||
promiscuous: no
|
|
||||||
port-display: on
|
|
||||||
link-local: yes
|
|
||||||
use-bytes: yes
|
|
||||||
show-totals: yes
|
|
||||||
log-scale: yes
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"pythonstartup.py" = {
|
|
||||||
text = (builtins.readFile ./pythonstartup.py);
|
|
||||||
};
|
|
||||||
"screenrc" = {
|
|
||||||
text = (builtins.readFile ./screenrc);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
home = {
|
home = {
|
||||||
activation = {
|
activation = {
|
||||||
|
@ -395,137 +140,85 @@ in
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
stateVersion = "23.11";
|
stateVersion = "24.05";
|
||||||
language = {
|
|
||||||
base = "en_US.UTF-8";
|
|
||||||
# time = "en_DK.UTF-8"; # TODO Disabled because complaints during nixos-rebuild switch
|
|
||||||
};
|
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
# dotfiles dependencies
|
# Terminal utils
|
||||||
coreutils
|
coreutils
|
||||||
bash
|
moreutils
|
||||||
gnugrep
|
|
||||||
gnused
|
|
||||||
gnutar
|
|
||||||
openssl
|
|
||||||
wget
|
|
||||||
curl
|
|
||||||
python3Packages.pip
|
|
||||||
rename
|
rename
|
||||||
which
|
which
|
||||||
|
|
||||||
# shell
|
|
||||||
zsh-completions
|
|
||||||
nix-zsh-completions
|
|
||||||
zsh-history-substring-search
|
|
||||||
powerline-go
|
|
||||||
neofetch
|
|
||||||
|
|
||||||
# nix utils
|
|
||||||
nix-diff
|
|
||||||
nix-tree
|
|
||||||
nix-output-monitor
|
|
||||||
|
|
||||||
# terminal essentials
|
|
||||||
file
|
file
|
||||||
moreutils
|
cached-nix-shell # For scripts
|
||||||
man
|
|
||||||
|
# Pipe utils
|
||||||
|
gnugrep
|
||||||
|
gnused
|
||||||
|
gawk
|
||||||
|
|
||||||
|
# Extraction
|
||||||
|
gnutar
|
||||||
unzip
|
unzip
|
||||||
unrar
|
unrar
|
||||||
p7zip
|
p7zip
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
man
|
||||||
|
tldr
|
||||||
|
neofetch
|
||||||
|
|
||||||
# remote
|
# remote
|
||||||
|
wget
|
||||||
|
curl
|
||||||
|
openssl
|
||||||
openssh
|
openssh
|
||||||
rsync
|
rsync
|
||||||
borgbackup
|
borgbackup
|
||||||
|
sshfs
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
ncdu
|
ncdu
|
||||||
jdupes
|
jdupes
|
||||||
duperemove
|
duperemove
|
||||||
|
compsize
|
||||||
|
btdu
|
||||||
|
|
||||||
# local monitoring
|
# toolbox
|
||||||
htop
|
|
||||||
iotop
|
|
||||||
iftop
|
|
||||||
lsof
|
|
||||||
strace
|
|
||||||
pv
|
|
||||||
progress
|
|
||||||
speedtest-cli
|
|
||||||
|
|
||||||
# multimedia toolbox
|
|
||||||
sox
|
|
||||||
imagemagick
|
imagemagick
|
||||||
|
numbat
|
||||||
|
|
||||||
# password
|
# hardware
|
||||||
pwgen
|
pciutils
|
||||||
|
usbutils
|
||||||
|
dmidecode
|
||||||
|
lshw
|
||||||
|
labelle # Label printer
|
||||||
|
|
||||||
|
# Locker
|
||||||
(pkgs.writeShellApplication {
|
(pkgs.writeShellApplication {
|
||||||
name = "git-sync-init";
|
name = "lock";
|
||||||
# runtimeInputs = with pkgs; [ coreutils libnotify ];
|
text = ''
|
||||||
text = (lib.strings.concatLines
|
${config.frogeye.hooks.lock}
|
||||||
(map (r: ''[ -d "${r.path}" ] || ${pkgs.git}/bin/git clone "${r.uri}" "${r.path}"'')
|
|
||||||
(lib.attrsets.attrValues config.services.git-sync.repositories)
|
${pkgs.vlock}/bin/vlock --all
|
||||||
)
|
'';
|
||||||
);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Mail
|
|
||||||
isync
|
|
||||||
msmtp
|
|
||||||
notmuch
|
|
||||||
neomutt
|
|
||||||
lynx
|
|
||||||
|
|
||||||
# Organisation
|
|
||||||
vdirsyncer
|
|
||||||
khard
|
|
||||||
khal
|
|
||||||
todoman
|
|
||||||
|
|
||||||
# TODO Lots of redundancy with other way things are defined here
|
|
||||||
|
|
||||||
] ++ lib.optionals pkgs.stdenv.isx86_64 [
|
|
||||||
nodePackages.insect
|
|
||||||
# TODO Use whatever replaces insect, hopefully that works on aarch64
|
|
||||||
];
|
];
|
||||||
sessionVariables = {
|
sessionVariables = {
|
||||||
# Favourite commands
|
# Favourite commands
|
||||||
PAGER = "less";
|
|
||||||
EDITOR = "nvim";
|
|
||||||
|
|
||||||
# Extra config
|
# Extra config
|
||||||
BOOT9_PATH = "${config.xdg.dataHome}/citra-emu/sysdata/boot9.bin";
|
|
||||||
CCACHE_CONFIGPATH = "${config.xdg.configHome}/ccache.conf";
|
|
||||||
# INPUTRC = "${config.xdg.configHome}/inputrc"; # UPST Will use programs.readline, but doesn't allow path setting
|
# INPUTRC = "${config.xdg.configHome}/inputrc"; # UPST Will use programs.readline, but doesn't allow path setting
|
||||||
LESSHISTFILE = "${config.xdg.stateHome}/lesshst";
|
|
||||||
NODE_REPL_HISTORY = "${config.xdg.cacheHome}/node_repl_history";
|
|
||||||
PYTHONSTARTUP = "${config.xdg.configHome}/pythonstartup.py";
|
|
||||||
# TODO I think we're not using the urxvt daemon on purpose?
|
|
||||||
# TODO this should be desktop only, as a few things are too.
|
|
||||||
SCREENRC = "${config.xdg.configHome}/screenrc";
|
|
||||||
SQLITE_HISTFILE = "${config.xdg.stateHome}/sqlite_history";
|
SQLITE_HISTFILE = "${config.xdg.stateHome}/sqlite_history";
|
||||||
YARN_DISABLE_SELF_UPDATE_CHECK = "true"; # This also disable the creation of a ~/.yarnrc file
|
|
||||||
} // lib.optionalAttrs config.frogeye.desktop.xorg {
|
|
||||||
# Favourite commands
|
|
||||||
VISUAL = "nvim";
|
|
||||||
BROWSER = "${config.programs.qutebrowser.package}/bin/qutebrowser";
|
|
||||||
|
|
||||||
# Extra config
|
# Bash/ZSH only?
|
||||||
RXVT_SOCKET = "${config.xdg.stateHome}/urxvtd"; # Used to want -$HOME suffix, hopefullt this isn't needed
|
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
|
||||||
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
|
# Fzf
|
||||||
} // direnv;
|
FZF_COMPLETION_OPTS = "${lib.strings.concatStringsSep " " config.programs.fzf.fileWidgetOptions}";
|
||||||
# TODO Session variables only get reloaded on login I think.
|
};
|
||||||
sessionPath = [
|
sessionPath = [
|
||||||
"${config.home.homeDirectory}/.local/bin"
|
"${config.home.homeDirectory}/.local/bin"
|
||||||
"${config.home.sessionVariables.GOPATH}"
|
"${config.home.homeDirectory}/.config/dotfiles/hm/scripts" # Not Nix path otherwise it gets converted into store,
|
||||||
(builtins.toString ./scripts)
|
# and then every time you want to modify a script you have to rebuild and re-login...
|
||||||
];
|
];
|
||||||
file = {
|
|
||||||
".face" = { # TODO Doesn't show on NixOS. See https://wiki.archlinux.org/title/LightDM#Changing_your_avatar ?
|
|
||||||
source = pkgs.runCommand "face.png" { } "${pkgs.inkscape}/bin/inkscape ${./face.svg} -w 1024 -o $out";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# FIXME .config/home-manager/home.nix link. Using hostname?
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
../common/frogarized
|
||||||
../options.nix
|
../options.nix
|
||||||
|
./accounts
|
||||||
|
./brightness
|
||||||
./common.nix
|
./common.nix
|
||||||
./desktop.nix
|
./desktop
|
||||||
./dev.nix
|
./dev
|
||||||
./extra.nix
|
./extra
|
||||||
./gaming
|
./gaming
|
||||||
|
./git
|
||||||
|
./gpg
|
||||||
|
./homealone.nix
|
||||||
|
./monitoring
|
||||||
|
./nix
|
||||||
|
./pager
|
||||||
|
./password
|
||||||
|
./prompt
|
||||||
|
./rebuild
|
||||||
|
./shell
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./style.nix
|
./theme
|
||||||
./usernix
|
./tmux
|
||||||
./vim.nix
|
./vim
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
706
hm/desktop.nix
706
hm/desktop.nix
|
@ -1,706 +0,0 @@
|
||||||
{ pkgs, config, lib, ... }:
|
|
||||||
let
|
|
||||||
nixgl = import
|
|
||||||
(builtins.fetchGit {
|
|
||||||
url = "https://github.com/nix-community/nixGL";
|
|
||||||
rev = "489d6b095ab9d289fe11af0219a9ff00fe87c7c5";
|
|
||||||
})
|
|
||||||
{ };
|
|
||||||
nixGLIntelPrefix = "${nixgl.nixVulkanIntel}/bin/nixVulkanIntel ${nixgl.nixGLIntel}/bin/nixGLIntel ";
|
|
||||||
wmPrefix = "${lib.optionalString config.frogeye.desktop.nixGLIntel nixGLIntelPrefix}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./frobar
|
|
||||||
];
|
|
||||||
config = lib.mkIf config.frogeye.desktop.xorg {
|
|
||||||
frogeye.shellAliases = {
|
|
||||||
noise = ''${pkgs.sox}/bin/play -c 2 -n synth $'' + ''{1}noise'';
|
|
||||||
beep = ''${pkgs.sox}/bin/play -n synth sine E5 sine A4 remix 1-2 fade 0.5 1.2 0.5 2> /dev/null'';
|
|
||||||
|
|
||||||
# n = "$HOME/.config/i3/terminal & disown"; # Not used anymore since alacritty daemon mode doesn't preserve environment variables
|
|
||||||
x = "startx ${config.home.homeDirectory}/${config.xsession.scriptPath}; logout";
|
|
||||||
# TODO Is it possible to not start nvidia stuff on nixOS?
|
|
||||||
# nx = "nvidia-xrun ${config.xsession.scriptPath}; sudo systemctl start nvidia-xrun-pm; logout";
|
|
||||||
};
|
|
||||||
xsession = {
|
|
||||||
enable = true;
|
|
||||||
# Not using config.xdg.configHome because it needs to be $HOME-relative paths and path manipulation is hard
|
|
||||||
scriptPath = ".config/xsession";
|
|
||||||
profilePath = ".config/xprofile";
|
|
||||||
windowManager = {
|
|
||||||
command = lib.mkForce "${wmPrefix} ${config.xsession.windowManager.i3.package}/bin/i3";
|
|
||||||
i3 = {
|
|
||||||
enable = true;
|
|
||||||
config =
|
|
||||||
let
|
|
||||||
# lockColors = with config.lib.stylix.colors.withHashtag; { a = base00; b = base01; d = base00; }; # Black or White, depending on current theme
|
|
||||||
# lockColors = with config.lib.stylix.colors.withHashtag; { a = base0A; b = base0B; d = base00; }; # Green + Yellow
|
|
||||||
lockColors = { a = "#82a401"; b = "#466c01"; d = "#648901"; }; # Old
|
|
||||||
lockSvg = pkgs.writeText "lock.svg" "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 50 50\" height=\"50\" width=\"50\"><path fill=\"${lockColors.a}\" d=\"M0 50h50V0H0z\"/><path d=\"M0 0l50 50H25L0 25zm50 0v25L25 0z\" fill=\"${lockColors.b}\"/></svg>";
|
|
||||||
lockPng = pkgs.runCommand "lock.png" { } "${pkgs.imagemagick}/bin/convert ${lockSvg} $out";
|
|
||||||
locker = pkgs.writeShellScript "i3-locker"
|
|
||||||
''
|
|
||||||
# Remove SSH and GPG keys from keystores
|
|
||||||
${pkgs.openssh}/bin/ssh-add -D
|
|
||||||
echo RELOADAGENT | ${pkgs.gnupg}/bin/gpg-connect-agent
|
|
||||||
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
|
|
||||||
|
|
||||||
${pkgs.lightdm}/bin/dm-tool lock
|
|
||||||
# TODO Does that work for all DMs?
|
|
||||||
# TODO Might want to use i3lock on NixOS configs still?
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
if [ -d ${config.xdg.cacheHome}/lockpatterns ]
|
|
||||||
then
|
|
||||||
pattern=$(${pkgs.findutils} ${config.xdg.cacheHome}/lockpatterns | sort -R | head -1)
|
|
||||||
else
|
|
||||||
pattern=${lockPng}
|
|
||||||
fi
|
|
||||||
revert() {
|
|
||||||
${pkgs.xorg.xset}/bin/xset dpms 0 0 0
|
|
||||||
}
|
|
||||||
trap revert SIGHUP SIGINT SIGTERM
|
|
||||||
${pkgs.xorg.xset}/bin/xset dpms 5 5 5
|
|
||||||
${pkgs.i3lock}/bin/i3lock --nofork --color ${builtins.substring 1 6 lockColors.d} --image=$pattern --tiling --ignore-empty-password
|
|
||||||
revert
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
focus = "exec ${ pkgs.writeShellScript "i3-focus-window"
|
|
||||||
''
|
|
||||||
WINDOW=`${pkgs.xdotool}/bin/xdotool getwindowfocus`
|
|
||||||
eval `${pkgs.xdotool}/bin/xdotool getwindowgeometry --shell $WINDOW` # this brings in variables WIDTH and HEIGHT
|
|
||||||
TX=`${pkgs.coreutils}/bin/expr $WIDTH / 2`
|
|
||||||
TY=`${pkgs.coreutils}/bin/expr $HEIGHT / 2`
|
|
||||||
${pkgs.xdotool}/bin/xdotool mousemove -window $WINDOW $TX $TY
|
|
||||||
''
|
|
||||||
}";
|
|
||||||
mode_system = "[L] Vérouillage [E] Déconnexion [S] Veille [H] Hibernation [R] Redémarrage [P] Extinction";
|
|
||||||
mode_resize = "Resize";
|
|
||||||
mode_pres_main = "Presentation (main display)";
|
|
||||||
mode_pres_sec = "Presentation (secondary display)";
|
|
||||||
mode_screen = "Screen setup [A] Auto [L] Load [S] Save [R] Remove [D] Default";
|
|
||||||
mode_temp = "Temperature [R] Red [D] Dust storm [C] Campfire [O] Normal [A] All nighter [B] Blue";
|
|
||||||
fonts = config.stylix.fonts;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
modifier = "Mod4";
|
|
||||||
fonts = {
|
|
||||||
names = [ fonts.sansSerif.name ];
|
|
||||||
};
|
|
||||||
terminal = "alacritty";
|
|
||||||
colors = let ignore = "#ff00ff"; in
|
|
||||||
with config.lib.stylix.colors.withHashtag; lib.mkForce {
|
|
||||||
focused = { border = base0B; background = base0B; text = base00; indicator = base00; childBorder = base0B; };
|
|
||||||
focusedInactive = { border = base02; background = base02; text = base05; indicator = base02; childBorder = base02; };
|
|
||||||
unfocused = { border = base05; background = base04; text = base00; indicator = base04; childBorder = base00; };
|
|
||||||
urgent = { border = base0F; background = base08; text = base00; indicator = base08; childBorder = base0F; };
|
|
||||||
placeholder = { border = ignore; background = base00; text = base05; indicator = ignore; childBorder = base00; };
|
|
||||||
background = base07;
|
|
||||||
# I set the color of the active tab as the the background color of the terminal so they merge together.
|
|
||||||
};
|
|
||||||
focus.followMouse = false;
|
|
||||||
keybindings =
|
|
||||||
let
|
|
||||||
mod = config.xsession.windowManager.i3.config.modifier;
|
|
||||||
rofi = "exec --no-startup-id ${config.programs.rofi.package}/bin/rofi";
|
|
||||||
pactl = "exec ${pkgs.pulseaudio}/bin/pactl"; # TODO Use NixOS package if using NixOS
|
|
||||||
screenshots_dir = config.xdg.userDirs.extraConfig.XDG_SCREENSHOTS_DIR;
|
|
||||||
scrot = "${pkgs.scrot}/bin/scrot --exec '${pkgs.coreutils}/bin/mv $f ${screenshots_dir}/ && ${pkgs.optipng}/bin/optipng ${screenshots_dir}/$f'";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Compatibility layer for people coming from other backgrounds
|
|
||||||
"Mod1+Tab" = "${rofi} -modi window -show window";
|
|
||||||
"Mod1+F2" = "${rofi} -modi drun -show drun";
|
|
||||||
"Mod1+F4" = "kill";
|
|
||||||
# kill focused window
|
|
||||||
"${mod}+z" = "kill";
|
|
||||||
button2 = "kill";
|
|
||||||
# Rofi
|
|
||||||
"${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used";
|
|
||||||
# TODO Try autopass.cr
|
|
||||||
# 23.11 config.programs.rofi.pass.package
|
|
||||||
"${mod}+i" = "exec --no-startup-id ${pkgs.rofimoji}/bin/rofimoji";
|
|
||||||
"${mod}+plus" = "${rofi} -modi ssh -show ssh";
|
|
||||||
"${mod}+ù" = "${rofi} -modi ssh -show ssh -ssh-command '{terminal} -e {ssh-client} {host} -t \"sudo -s -E\"'";
|
|
||||||
# TODO In which keyboard layout?
|
|
||||||
"${mod}+Tab" = "${rofi} -modi window -show window";
|
|
||||||
# start program launcher
|
|
||||||
"${mod}+d" = "${rofi} -modi run -show run";
|
|
||||||
"${mod}+Shift+d" = "${rofi} -modi drun -show drun";
|
|
||||||
# Start Applications
|
|
||||||
"${mod}+Return" = "exec ${
|
|
||||||
pkgs.writeShellScript "terminal" "${config.programs.alacritty.package}/bin/alacritty msg create-window || exec ${config.programs.alacritty.package}/bin/alacritty -e zsh"
|
|
||||||
# -e zsh is for systems where I can't configure my user's shell
|
|
||||||
# TODO Is a shell script even required?
|
|
||||||
}";
|
|
||||||
"${mod}+Shift+Return" = "exec ${config.programs.urxvt.package}/bin/urxvt";
|
|
||||||
"${mod}+p" = "exec ${pkgs.xfce.thunar}/bin/thunar";
|
|
||||||
"${mod}+m" = "exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore --backend=webengine";
|
|
||||||
# TODO --backend not useful anymore
|
|
||||||
# Volume control
|
|
||||||
"XF86AudioRaiseVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ +5%";
|
|
||||||
"XF86AudioLowerVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ -5%";
|
|
||||||
"XF86AudioMute" = "${pactl} set-sink-mute @DEFAULT_SINK@ true";
|
|
||||||
"${mod}+F7" = "${pactl} suspend-sink @DEFAULT_SINK@ 1; ${pactl} suspend-sink @DEFAULT_SINK@ 0"; # Re-synchronize bluetooth headset
|
|
||||||
"${mod}+F11" = "exec ${pkgs.pavucontrol}/bin/pavucontrol";
|
|
||||||
"${mod}+F12" = "exec ${pkgs.pavucontrol}/bin/pavucontrol";
|
|
||||||
# TODO Find pacmixer?
|
|
||||||
# Media control
|
|
||||||
"XF86AudioPrev" = "exec ${pkgs.mpc-cli}/bin/mpc prev";
|
|
||||||
"XF86AudioPlay" = "exec ${pkgs.mpc-cli}/bin/mpc toggle";
|
|
||||||
"XF86AudioNext" = "exec ${pkgs.mpc-cli}/bin/mpc next";
|
|
||||||
# Backlight
|
|
||||||
"XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
|
|
||||||
"XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
|
|
||||||
# Misc
|
|
||||||
"${mod}+F10" = "exec ${ pkgs.writeShellScript "show-keyboard-layout"
|
|
||||||
''
|
|
||||||
layout=`${pkgs.xorg.setxkbmap}/bin/setxkbmap -query | ${pkgs.gnugrep}/bin/grep ^layout: | ${pkgs.gawk}/bin/awk '{ print $2 }'`
|
|
||||||
${pkgs.libgnomekbd}/bin/gkbd-keyboard-display -l $layout
|
|
||||||
''
|
|
||||||
}";
|
|
||||||
# Screenshots
|
|
||||||
"Print" = "exec ${scrot} --focused";
|
|
||||||
"${mod}+Print" = "exec ${scrot}";
|
|
||||||
"Ctrl+Print" = "exec ${pkgs.coreutils}/bin/sleep 1 && ${scrot} --select";
|
|
||||||
# TODO Try using bindsym --release instead of sleep
|
|
||||||
# change focus
|
|
||||||
"${mod}+h" = "focus left; ${focus}";
|
|
||||||
"${mod}+j" = "focus down; ${focus}";
|
|
||||||
"${mod}+k" = "focus up; ${focus}";
|
|
||||||
"${mod}+l" = "focus right; ${focus}";
|
|
||||||
# move focused window
|
|
||||||
"${mod}+Shift+h" = "move left; ${focus}";
|
|
||||||
"${mod}+Shift+j" = "move down; ${focus}";
|
|
||||||
"${mod}+Shift+k" = "move up; ${focus}";
|
|
||||||
"${mod}+Shift+l" = "move right; ${focus}";
|
|
||||||
# workspace back and forth (with/without active container)
|
|
||||||
"${mod}+b" = "workspace back_and_forth; ${focus}";
|
|
||||||
"${mod}+Shift+b" = "move container to workspace back_and_forth; workspace back_and_forth; ${focus}";
|
|
||||||
# Change container layout
|
|
||||||
"${mod}+g" = "split h; ${focus}";
|
|
||||||
"${mod}+v" = "split v; ${focus}";
|
|
||||||
"${mod}+f" = "fullscreen toggle; ${focus}";
|
|
||||||
"${mod}+s" = "layout stacking; ${focus}";
|
|
||||||
"${mod}+w" = "layout tabbed; ${focus}";
|
|
||||||
"${mod}+e" = "layout toggle split; ${focus}";
|
|
||||||
"${mod}+Shift+space" = "floating toggle; ${focus}";
|
|
||||||
# Focus container
|
|
||||||
"${mod}+space" = "focus mode_toggle; ${focus}";
|
|
||||||
"${mod}+a" = "focus parent; ${focus}";
|
|
||||||
"${mod}+q" = "focus child; ${focus}";
|
|
||||||
# Switch to workspace
|
|
||||||
"${mod}+1" = "workspace 1; ${focus}";
|
|
||||||
"${mod}+2" = "workspace 2; ${focus}";
|
|
||||||
"${mod}+3" = "workspace 3; ${focus}";
|
|
||||||
"${mod}+4" = "workspace 4; ${focus}";
|
|
||||||
"${mod}+5" = "workspace 5; ${focus}";
|
|
||||||
"${mod}+6" = "workspace 6; ${focus}";
|
|
||||||
"${mod}+7" = "workspace 7; ${focus}";
|
|
||||||
"${mod}+8" = "workspace 8; ${focus}";
|
|
||||||
"${mod}+9" = "workspace 9; ${focus}";
|
|
||||||
"${mod}+0" = "workspace 10; ${focus}";
|
|
||||||
# TODO Prevent repetitions, see workspace assignation for example
|
|
||||||
#navigate workspaces next / previous
|
|
||||||
"${mod}+Ctrl+h" = "workspace prev_on_output; ${focus}";
|
|
||||||
"${mod}+Ctrl+l" = "workspace next_on_output; ${focus}";
|
|
||||||
"${mod}+Ctrl+j" = "workspace prev; ${focus}";
|
|
||||||
"${mod}+Ctrl+k" = "workspace next; ${focus}";
|
|
||||||
# Move to workspace next / previous with focused container
|
|
||||||
"${mod}+Ctrl+Shift+h" = "move container to workspace prev_on_output; workspace prev_on_output; ${focus}";
|
|
||||||
"${mod}+Ctrl+Shift+l" = "move container to workspace next_on_output; workspace next_on_output; ${focus}";
|
|
||||||
"${mod}+Ctrl+Shift+j" = "move container to workspace prev; workspace prev; ${focus}";
|
|
||||||
"${mod}+Ctrl+Shift+k" = "move container to workspace next; workspace next; ${focus}";
|
|
||||||
# move focused container to workspace
|
|
||||||
"${mod}+ctrl+1" = "move container to workspace 1; ${focus}";
|
|
||||||
"${mod}+ctrl+2" = "move container to workspace 2; ${focus}";
|
|
||||||
"${mod}+ctrl+3" = "move container to workspace 3; ${focus}";
|
|
||||||
"${mod}+ctrl+4" = "move container to workspace 4; ${focus}";
|
|
||||||
"${mod}+ctrl+5" = "move container to workspace 5; ${focus}";
|
|
||||||
"${mod}+ctrl+6" = "move container to workspace 6; ${focus}";
|
|
||||||
"${mod}+ctrl+7" = "move container to workspace 7; ${focus}";
|
|
||||||
"${mod}+ctrl+8" = "move container to workspace 8; ${focus}";
|
|
||||||
"${mod}+ctrl+9" = "move container to workspace 9; ${focus}";
|
|
||||||
"${mod}+ctrl+0" = "move container to workspace 10; ${focus}";
|
|
||||||
# move to workspace with focused container
|
|
||||||
"${mod}+shift+1" = "move container to workspace 1; workspace 1; ${focus}";
|
|
||||||
"${mod}+shift+2" = "move container to workspace 2; workspace 2; ${focus}";
|
|
||||||
"${mod}+shift+3" = "move container to workspace 3; workspace 3; ${focus}";
|
|
||||||
"${mod}+shift+4" = "move container to workspace 4; workspace 4; ${focus}";
|
|
||||||
"${mod}+shift+5" = "move container to workspace 5; workspace 5; ${focus}";
|
|
||||||
"${mod}+shift+6" = "move container to workspace 6; workspace 6; ${focus}";
|
|
||||||
"${mod}+shift+7" = "move container to workspace 7; workspace 7; ${focus}";
|
|
||||||
"${mod}+shift+8" = "move container to workspace 8; workspace 8; ${focus}";
|
|
||||||
"${mod}+shift+9" = "move container to workspace 9; workspace 9; ${focus}";
|
|
||||||
"${mod}+shift+0" = "move container to workspace 10; workspace 10; ${focus}";
|
|
||||||
# move workspaces to screen (arrow keys)
|
|
||||||
"${mod}+ctrl+shift+Right" = "move workspace to output right; ${focus}";
|
|
||||||
"${mod}+ctrl+shift+Left" = "move workspace to output left; ${focus}";
|
|
||||||
"${mod}+Ctrl+Shift+Up" = "move workspace to output above; ${focus}";
|
|
||||||
"${mod}+Ctrl+Shift+Down" = "move workspace to output below; ${focus}";
|
|
||||||
# i3 control
|
|
||||||
"${mod}+Shift+c" = "reload";
|
|
||||||
"${mod}+Shift+r" = "restart";
|
|
||||||
"${mod}+Shift+e" = "exit";
|
|
||||||
# Screen off commands
|
|
||||||
"${mod}+F1" = "exec --no-startup-id ${pkgs.bash}/bin/sh -c \"${pkgs.coreutils}/bin/sleep .25 && ${pkgs.xorg.xset}/bin/xset dpms force off\"";
|
|
||||||
# TODO --release?
|
|
||||||
"${mod}+F4" = "exec --no-startup-id ${pkgs.xautolock}/bin/xautolock -disable";
|
|
||||||
"${mod}+F5" = "exec --no-startup-id ${pkgs.xautolock}/bin/xautolock -enable";
|
|
||||||
# Modes
|
|
||||||
"${mod}+Escape" = "mode ${mode_system}";
|
|
||||||
"${mod}+r" = "mode ${mode_resize}";
|
|
||||||
"${mod}+Shift+p" = "mode ${mode_pres_main}";
|
|
||||||
"${mod}+t" = "mode ${mode_screen}";
|
|
||||||
"${mod}+y" = "mode ${mode_temp}";
|
|
||||||
};
|
|
||||||
modes = let return_bindings = {
|
|
||||||
"Return" = "mode default";
|
|
||||||
"Escape" = "mode default";
|
|
||||||
}; in
|
|
||||||
{
|
|
||||||
"${mode_system}" = {
|
|
||||||
"l" = "exec --no-startup-id exec ${locker}, mode default";
|
|
||||||
"e" = "exit, mode default";
|
|
||||||
"s" = "exec --no-startup-id exec ${locker} & ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default";
|
|
||||||
"h" = "exec --no-startup-id exec ${locker} & ${pkgs.systemd}/bin/systemctl hibernate, mode default";
|
|
||||||
"r" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl reboot, mode default";
|
|
||||||
"p" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl poweroff -i, mode default";
|
|
||||||
} // return_bindings;
|
|
||||||
"${mode_resize}" = {
|
|
||||||
"h" = "resize shrink width 10 px or 10 ppt; ${focus}";
|
|
||||||
"j" = "resize grow height 10 px or 10 ppt; ${focus}";
|
|
||||||
"k" = "resize shrink height 10 px or 10 ppt; ${focus}";
|
|
||||||
"l" = "resize grow width 10 px or 10 ppt; ${focus}";
|
|
||||||
} // return_bindings;
|
|
||||||
"${mode_pres_main}" = {
|
|
||||||
"b" = "workspace 3, workspace 4, mode ${mode_pres_sec}";
|
|
||||||
"q" = "mode default";
|
|
||||||
"Return" = "mode default";
|
|
||||||
};
|
|
||||||
"${mode_pres_sec}" = {
|
|
||||||
"b" = "workspace 1, workspace 2, mode ${mode_pres_main}";
|
|
||||||
"q" = "mode default";
|
|
||||||
"Return" = "mode default";
|
|
||||||
};
|
|
||||||
"${mode_screen}" =
|
|
||||||
let
|
|
||||||
builtin_configs = [ "off" "common" "clone-largest" "horizontal" "vertical" "horizontal-reverse" "vertical-reverse" ];
|
|
||||||
autorandrmenu = { title, option, builtin ? false }: pkgs.writeShellScript "autorandrmenu"
|
|
||||||
''
|
|
||||||
shopt -s nullglob globstar
|
|
||||||
profiles="${if builtin then lib.strings.concatLines builtin_configs else ""}$(${pkgs.autorandr}/bin/autorandr | ${pkgs.gawk}/bin/awk '{ print $1 }')"
|
|
||||||
profile="$(echo "$profiles" | ${config.programs.rofi.package}/bin/rofi -dmenu -p "${title}")"
|
|
||||||
[[ -n "$profile" ]] || exit
|
|
||||||
${pkgs.autorandr}/bin/autorandr ${option} "$profile"
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
"a" = "exec ${pkgs.autorandr}/bin/autorandr --change --force, mode default";
|
|
||||||
"l" = "exec ${autorandrmenu {title="Load profile"; option="--load"; builtin = true;}}, mode default";
|
|
||||||
"s" = "exec ${autorandrmenu {title="Save profile"; option="--save";}}, mode default";
|
|
||||||
"r" = "exec ${autorandrmenu {title="Remove profile"; option="--remove";}}, mode default";
|
|
||||||
"d" = "exec ${autorandrmenu {title="Default profile"; option="--default"; builtin = true;}}, mode default";
|
|
||||||
} // return_bindings;
|
|
||||||
"${mode_temp}" = {
|
|
||||||
"r" = "exec ${pkgs.sct}/bin/sct 1000";
|
|
||||||
"d" = "exec ${pkgs.sct}/bin/sct 2000";
|
|
||||||
"c" = "exec ${pkgs.sct}/bin/sct 4500";
|
|
||||||
"o" = "exec ${pkgs.sct}/bin/sct";
|
|
||||||
"a" = "exec ${pkgs.sct}/bin/sct 8000";
|
|
||||||
"b" = "exec ${pkgs.sct}/bin/sct 10000";
|
|
||||||
} // return_bindings;
|
|
||||||
};
|
|
||||||
window = {
|
|
||||||
hideEdgeBorders = "both";
|
|
||||||
titlebar = false; # So that single-container screens are basically almost fullscreen
|
|
||||||
commands = [
|
|
||||||
# Open specific applications in floating mode
|
|
||||||
{ criteria = { class = "Firefox"; }; command = "layout tabbed"; } # Doesn't seem to work anymore
|
|
||||||
{ criteria = { class = "qutebrowser"; }; command = "layout tabbed"; }
|
|
||||||
{ criteria = { title = "^pdfpc.*"; window_role = "presenter"; }; command = "move to output left, fullscreen"; }
|
|
||||||
{ criteria = { title = "^pdfpc.*"; window_role = "presentation"; }; command = "move to output right, fullscreen"; }
|
|
||||||
# switch to workspace with urgent window automatically
|
|
||||||
{ criteria = { urgent = "latest"; }; command = "focus"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
floating = {
|
|
||||||
criteria = [
|
|
||||||
{ title = "pacmixer"; }
|
|
||||||
{ window_role = "pop-up"; }
|
|
||||||
{ window_role = "task_dialog"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
startup = [
|
|
||||||
# Lock screen after 10 minutes
|
|
||||||
{ notification = false; command = "${pkgs.xautolock}/bin/xautolock -time 10 -locker '${pkgs.xorg.xset}/bin/xset dpms force standby' -killtime 1 -killer ${locker}"; }
|
|
||||||
{
|
|
||||||
notification = false;
|
|
||||||
command = "${pkgs.writeShellApplication {
|
|
||||||
name = "batteryNotify";
|
|
||||||
runtimeInputs = with pkgs; [coreutils libnotify];
|
|
||||||
text = builtins.readFile ./batteryNotify.sh;
|
|
||||||
# TODO Use batsignal instead?
|
|
||||||
# TODO Only on computers with battery
|
|
||||||
}}/bin/batteryNotify";
|
|
||||||
}
|
|
||||||
# TODO There's a services.screen-locker.xautolock but not sure it can match the above command
|
|
||||||
];
|
|
||||||
workspaceLayout = "tabbed";
|
|
||||||
focus.mouseWarping = true; # i3 only supports warping to workspace, hence ${focus}
|
|
||||||
workspaceOutputAssign =
|
|
||||||
let
|
|
||||||
x11_screens = config.frogeye.desktop.x11_screens;
|
|
||||||
workspaces = map (i: { name = toString i; key = toString (lib.mod i 10); }) (lib.lists.range 1 10);
|
|
||||||
forEachWorkspace = f: map (w: f { w = w; workspace = ((builtins.elemAt workspaces w)); }) (lib.lists.range 0 ((builtins.length workspaces) - 1));
|
|
||||||
in
|
|
||||||
forEachWorkspace ({ w, workspace }: { output = builtins.elemAt x11_screens (lib.mod w (builtins.length x11_screens)); workspace = workspace.name; });
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
numlock.enable = config.frogeye.desktop.numlock;
|
|
||||||
};
|
|
||||||
|
|
||||||
programs = {
|
|
||||||
# Browser
|
|
||||||
qutebrowser = {
|
|
||||||
enable = true;
|
|
||||||
keyBindings = {
|
|
||||||
normal = {
|
|
||||||
# Match tab behaviour to i3. Not that I use them.
|
|
||||||
"H" = "tab-prev";
|
|
||||||
"J" = "back";
|
|
||||||
"K" = "forward";
|
|
||||||
"L" = "tab-next";
|
|
||||||
# "T" = null;
|
|
||||||
"af" = "spawn --userscript freshrss"; # TODO Broken?
|
|
||||||
"as" = "spawn --userscript shaarli"; # TODO I don't use shaarli anymore
|
|
||||||
# "d" = null;
|
|
||||||
"u" = "undo --window";
|
|
||||||
# TODO Unbind d and T (?)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
loadAutoconfig = true;
|
|
||||||
searchEngines = rec {
|
|
||||||
DEFAULT = ecosia;
|
|
||||||
alpinep = "https://pkgs.alpinelinux.org/packages?name={}&branch=edge";
|
|
||||||
ampwhat = "http://www.amp-what.com/unicode/search/{}";
|
|
||||||
arch = "https://wiki.archlinux.org/?search={}";
|
|
||||||
archp = "https://www.archlinux.org/packages/?q={}";
|
|
||||||
aur = "https://aur.archlinux.org/packages/?K={}";
|
|
||||||
aw = ampwhat;
|
|
||||||
ddg = duckduckgo;
|
|
||||||
dockerhub = "https://hub.docker.com/search/?isAutomated=0&isOfficial=0&page=1&pullCount=0&q={}&starCount=0";
|
|
||||||
duckduckgo = "https://duckduckgo.com/?q={}&ia=web";
|
|
||||||
ecosia = "https://www.ecosia.org/search?q={}";
|
|
||||||
gfr = "https://www.google.fr/search?hl=fr&q={}";
|
|
||||||
g = google;
|
|
||||||
gh = github;
|
|
||||||
gi = "http://images.google.com/search?q={}";
|
|
||||||
giphy = "https://giphy.com/search/{}";
|
|
||||||
github = "https://github.com/search?q={}";
|
|
||||||
google = "https://www.google.fr/search?q={}";
|
|
||||||
invidious = "https://invidious.frogeye.fr/search?q={}";
|
|
||||||
inv = invidious;
|
|
||||||
npm = "https://www.npmjs.com/search?q={}";
|
|
||||||
q = qwant;
|
|
||||||
qwant = "https://www.qwant.com/?t=web&q={}";
|
|
||||||
wolfram = "https://www.wolframalpha.com/input/?i={}";
|
|
||||||
youtube = "https://www.youtube.com/results?search_query={}";
|
|
||||||
yt = youtube;
|
|
||||||
};
|
|
||||||
settings = {
|
|
||||||
downloads.location.prompt = false;
|
|
||||||
tabs = {
|
|
||||||
show = "never";
|
|
||||||
tabs_are_windows = true;
|
|
||||||
};
|
|
||||||
url = rec {
|
|
||||||
open_base_url = true;
|
|
||||||
start_pages = lib.mkDefault "https://geoffrey.frogeye.fr/blank.html";
|
|
||||||
default_page = start_pages;
|
|
||||||
};
|
|
||||||
content = {
|
|
||||||
# I had this setting below, not sure if it did something special
|
|
||||||
# config.set("content.cookies.accept", "no-3rdparty", "chrome://*/*")
|
|
||||||
cookies.accept = "no-3rdparty";
|
|
||||||
prefers_reduced_motion = true;
|
|
||||||
headers.accept_language = "fr-FR, fr;q=0.9, en-GB;q=0.8, en-US;q=0.7, en;q=0.6";
|
|
||||||
tls.certificate_errors = "ask-block-thirdparty";
|
|
||||||
};
|
|
||||||
editor.command = [ "${pkgs.neovide}/bin/neovide" "--" "-f" "{file}" "-c" "normal {line}G{column0}l" ];
|
|
||||||
# TODO Doesn't work on Arch. Does it even load the right profile on Nix?
|
|
||||||
# TODO spellcheck.languages = ["fr-FR" "en-GB" "en-US"];
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Terminal
|
|
||||||
alacritty = {
|
|
||||||
# TODO Emojis. Or maybe they work on NixOS?
|
|
||||||
# Arch (working) shows this with alacritty -vvv:
|
|
||||||
# [TRACE] [crossfont] Got font path="/usr/share/fonts/twemoji/twemoji.ttf", index=0
|
|
||||||
# [DEBUG] [crossfont] Loaded Face Face { ft_face: Font Face: Regular, load_flags: MONOCHROME | TARGET_MONO | COLOR, render_mode: "Mono", lcd_filter: 1 }
|
|
||||||
# Nix (not working) shows this:
|
|
||||||
# [TRACE] [crossfont] Got font path="/nix/store/872g3w9vcr5nh93r0m83a3yzmpvd2qrj-home-manager-path/share/fonts/truetype/TwitterColorEmoji-SVGinOT.ttf", index=0
|
|
||||||
# [DEBUG] [crossfont] Loaded Face Face { ft_face: Font Face: Regular, load_flags: TARGET_LIGHT | COLOR, render_mode: "Lcd", lcd_filter: 1 }
|
|
||||||
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
bell = {
|
|
||||||
animation = "EaseOutExpo";
|
|
||||||
color = "#000000";
|
|
||||||
command = { program = "${pkgs.sox}/bin/play"; args = [ "-n" "synth" "sine" "C5" "sine" "E4" "remix" "1-2" "fade" "0.1" "0.2" "0.1" ]; };
|
|
||||||
duration = 100;
|
|
||||||
};
|
|
||||||
cursor = { vi_mode_style = "Underline"; };
|
|
||||||
env = {
|
|
||||||
WINIT_X11_SCALE_FACTOR = "1";
|
|
||||||
# Prevents Alacritty from resizing from one monitor to another.
|
|
||||||
# Might cause issue on HiDPI screens but we'll get there when we get there
|
|
||||||
};
|
|
||||||
hints = {
|
|
||||||
enabled = [
|
|
||||||
{
|
|
||||||
binding = { mods = "Control|Alt"; key = "F"; };
|
|
||||||
command = "${pkgs.xdg-utils}/bin/xdg-open";
|
|
||||||
mouse = { enabled = true; mods = "Control"; };
|
|
||||||
post_processing = true;
|
|
||||||
regex = "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\\\s{-}\\\\^⟨⟩`]+";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
key_bindings = [
|
|
||||||
{ mode = "~Search"; mods = "Alt|Control"; key = "Space"; action = "ToggleViMode"; }
|
|
||||||
{ mode = "Vi|~Search"; mods = "Control"; key = "K"; action = "ScrollHalfPageUp"; }
|
|
||||||
{ mode = "Vi|~Search"; mods = "Control"; key = "J"; action = "ScrollHalfPageDown"; }
|
|
||||||
{ mode = "~Vi"; mods = "Control|Alt"; key = "V"; action = "Paste"; }
|
|
||||||
{ mods = "Control|Alt"; key = "C"; action = "Copy"; }
|
|
||||||
{ mode = "~Search"; mods = "Control|Alt"; key = "F"; action = "SearchForward"; }
|
|
||||||
{ mode = "~Search"; mods = "Control|Alt"; key = "B"; action = "SearchBackward"; }
|
|
||||||
{ mode = "Vi|~Search"; mods = "Control|Alt"; key = "C"; action = "ClearSelection"; }
|
|
||||||
];
|
|
||||||
window = {
|
|
||||||
dynamic_padding = false;
|
|
||||||
dynamic_title = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# Backup terminal
|
|
||||||
urxvt = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.rxvt-unicode-emoji;
|
|
||||||
scroll = {
|
|
||||||
bar.enable = false;
|
|
||||||
};
|
|
||||||
iso14755 = false; # Disable Ctrl+Shift default bindings
|
|
||||||
keybindings = {
|
|
||||||
"Shift-Control-C" = "eval:selection_to_clipboard";
|
|
||||||
"Shift-Control-V" = "eval:paste_clipboard";
|
|
||||||
# TODO Not sure resizing works, Nix doesn't have the package (urxvt-resize-font-git on Arch)
|
|
||||||
"Control-KP_Subtract" = "resize-font:smaller";
|
|
||||||
"Control-KP_Add" = "resize-font:bigger";
|
|
||||||
};
|
|
||||||
extraConfig = {
|
|
||||||
"letterSpace" = 0;
|
|
||||||
"perl-ext-common" = "resize-font,bell-command,readline,selection";
|
|
||||||
"bell-command" = "${pkgs.sox}/bin/play -n synth sine C5 sine E4 remix 1-2 fade 0.1 0.2 0.1 &> /dev/null";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
rofi = {
|
|
||||||
# TODO This theme template, that was used for Arch, looks much better:
|
|
||||||
# https://gitlab.com/jordiorlando/base16-rofi/-/blob/master/templates/default.mustache
|
|
||||||
enable = true;
|
|
||||||
pass.enable = true;
|
|
||||||
extraConfig = {
|
|
||||||
lazy-grab = false;
|
|
||||||
matching = "regex";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
autorandr = {
|
|
||||||
enable = true;
|
|
||||||
hooks.postswitch = {
|
|
||||||
background = "${pkgs.feh}/bin/feh --no-fehbg --bg-fill ${config.stylix.image}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mpv = {
|
|
||||||
enable = true;
|
|
||||||
config = {
|
|
||||||
audio-display = false;
|
|
||||||
save-position-on-quit = true;
|
|
||||||
osc = false; # Required by thumbnail script
|
|
||||||
# Hardware acceleration (from https://nixos.wiki/wiki/Accelerated_Video_Playback#MPV)
|
|
||||||
hwdec = "auto-safe";
|
|
||||||
vo = "gpu";
|
|
||||||
profile = "gpu-hq";
|
|
||||||
};
|
|
||||||
scripts = with pkgs.mpvScripts; [ thumbnail ];
|
|
||||||
scriptOpts = {
|
|
||||||
mpv_thumbnail_script = {
|
|
||||||
autogenerate = false; # TODO It creates too many processes at once, crashing the system
|
|
||||||
cache_directory = "/tmp/mpv_thumbs_${config.home.username}";
|
|
||||||
mpv_hwdec = "auto-safe";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
xdg = {
|
|
||||||
mimeApps = {
|
|
||||||
enable = true;
|
|
||||||
defaultApplications = {
|
|
||||||
"text/html" = "org.qutebrowser.qutebrowser.desktop";
|
|
||||||
"x-scheme-handler/http" = "org.qutebrowser.qutebrowser.desktop";
|
|
||||||
"x-scheme-handler/https" = "org.qutebrowser.qutebrowser.desktop";
|
|
||||||
"x-scheme-handler/about" = "org.qutebrowser.qutebrowser.desktop";
|
|
||||||
"x-scheme-handler/unknown" = "org.qutebrowser.qutebrowser.desktop";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
userDirs = {
|
|
||||||
enable = true; # TODO Which ones do we want?
|
|
||||||
createDirectories = true;
|
|
||||||
# French, because then it there's a different initial for each, making navigation easier
|
|
||||||
desktop = null;
|
|
||||||
download = "${config.home.homeDirectory}/Téléchargements";
|
|
||||||
music = "${config.home.homeDirectory}/Musiques";
|
|
||||||
pictures = "${config.home.homeDirectory}/Images";
|
|
||||||
publicShare = null;
|
|
||||||
templates = null;
|
|
||||||
videos = "${config.home.homeDirectory}/Vidéos";
|
|
||||||
extraConfig = {
|
|
||||||
XDG_SCREENSHOTS_DIR = "${config.home.homeDirectory}/Screenshots";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
configFile = {
|
|
||||||
"pulse/client.conf" = {
|
|
||||||
text = ''cookie-file = .config/pulse/pulse-cookie'';
|
|
||||||
};
|
|
||||||
"rofimoji.rc" = {
|
|
||||||
text = ''
|
|
||||||
skin-tone = neutral
|
|
||||||
files = [emojis, math]
|
|
||||||
action = clipboard
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"vimpc/vimpcrc" = {
|
|
||||||
text = ''
|
|
||||||
map FF :browse<C-M>gg/
|
|
||||||
map à :set add next<C-M>a:set add end<C-M>
|
|
||||||
map @ :set add next<C-M>a:set add end<C-M>:next<C-M>
|
|
||||||
map ° D:browse<C-M>A:shuffle<C-M>:play<C-M>:playlist<C-M>
|
|
||||||
set songformat {%a - %b: %t}|{%f}$E$R $H[$H%l$H]$H
|
|
||||||
set libraryformat %n \| {%t}|{%f}$E$R $H[$H%l$H]$H
|
|
||||||
set ignorecase
|
|
||||||
set sort library
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services = {
|
|
||||||
blueman-applet.enable = true;
|
|
||||||
unclutter.enable = true;
|
|
||||||
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 = {
|
|
||||||
background = lib.mkForce base01;
|
|
||||||
foreground = lib.mkForce base03;
|
|
||||||
frame_color = lib.mkForce base05;
|
|
||||||
};
|
|
||||||
urgency_normal = {
|
|
||||||
background = lib.mkForce base02;
|
|
||||||
foreground = lib.mkForce base05;
|
|
||||||
frame_color = lib.mkForce base05;
|
|
||||||
};
|
|
||||||
urgency_critical = {
|
|
||||||
background = lib.mkForce base08;
|
|
||||||
foreground = lib.mkForce base06;
|
|
||||||
frame_color = lib.mkForce base05;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mpd = {
|
|
||||||
enable = true;
|
|
||||||
network = {
|
|
||||||
listenAddress = "0.0.0.0"; # So it can be controlled from home
|
|
||||||
# TODO ... and whoever is the Wi-Fi network I'm using, which, not great
|
|
||||||
startWhenNeeded = true;
|
|
||||||
};
|
|
||||||
extraConfig = ''
|
|
||||||
restore_paused "yes"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
autorandr.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
home = {
|
|
||||||
packages = with pkgs; [
|
|
||||||
pavucontrol # Because can't use Win+F1X on Pinebook 🙃
|
|
||||||
|
|
||||||
# remote
|
|
||||||
tigervnc
|
|
||||||
|
|
||||||
# music
|
|
||||||
mpc-cli
|
|
||||||
ashuffle
|
|
||||||
vimpc
|
|
||||||
|
|
||||||
# multimedia common
|
|
||||||
gimp
|
|
||||||
inkscape
|
|
||||||
libreoffice
|
|
||||||
|
|
||||||
# data management
|
|
||||||
freefilesync
|
|
||||||
|
|
||||||
# browsers
|
|
||||||
firefox
|
|
||||||
|
|
||||||
# fonts
|
|
||||||
dejavu_fonts
|
|
||||||
twemoji-color-font
|
|
||||||
gnome.gedit
|
|
||||||
feh
|
|
||||||
zbar
|
|
||||||
zathura
|
|
||||||
meld
|
|
||||||
python3Packages.magic
|
|
||||||
|
|
||||||
# x11-exclusive
|
|
||||||
numlockx
|
|
||||||
simplescreenrecorder
|
|
||||||
trayer
|
|
||||||
xclip
|
|
||||||
keynav
|
|
||||||
xorg.xinit
|
|
||||||
# TODO Make this clean. Service?
|
|
||||||
|
|
||||||
|
|
||||||
# organisation
|
|
||||||
pass
|
|
||||||
thunderbird
|
|
||||||
];
|
|
||||||
sessionVariables = {
|
|
||||||
MPD_PORT = "${toString config.services.mpd.network.port}";
|
|
||||||
ALSA_PLUGIN_DIR = "${pkgs.alsa-plugins}/lib/alsa-lib"; # Fixes an issue with sox (Cannot open shared library libasound_module_pcm_pulse.so)
|
|
||||||
# UPST Patch this upstream like: https://github.com/NixOS/nixpkgs/blob/216b111fb87091632d077898df647d1438fc2edb/pkgs/applications/audio/espeak-ng/default.nix#L84
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
40
hm/desktop/audio/default.nix
Normal file
40
hm/desktop/audio/default.nix
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
pactl = "exec ${pkgs.pulseaudio}/bin/pactl"; # TODO Use NixOS package if using NixOS
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
pwvucontrol # Because can't use Win+F1X on Pinebook 🙃
|
||||||
|
pavucontrol # Just in case
|
||||||
|
helvum
|
||||||
|
qpwgraph
|
||||||
|
sox
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
ALSA_PLUGIN_DIR = "${pkgs.alsa-plugins}/lib/alsa-lib"; # Fixes an issue with sox (Cannot open shared library libasound_module_pcm_pulse.so)
|
||||||
|
# UPST Patch this upstream like: https://github.com/NixOS/nixpkgs/blob/216b111fb87091632d077898df647d1438fc2edb/pkgs/applications/audio/espeak-ng/default.nix#L84
|
||||||
|
};
|
||||||
|
};
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
beep = ''${pkgs.sox}/bin/play -n synth sine E5 sine A4 remix 1-2 fade 0.5 1.2 0.5 2> /dev/null'';
|
||||||
|
noise = ''${pkgs.sox}/bin/play -c 2 -n synth $'' + ''{1}noise'';
|
||||||
|
};
|
||||||
|
xdg.configFile = {
|
||||||
|
"pulse/client.conf" = {
|
||||||
|
text = ''cookie-file = .config/pulse/pulse-cookie'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.keybindings =
|
||||||
|
{
|
||||||
|
"XF86AudioRaiseVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ +5%";
|
||||||
|
"XF86AudioLowerVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ -5%";
|
||||||
|
"XF86AudioMute" = "${pactl} set-sink-mute @DEFAULT_SINK@ true";
|
||||||
|
"${mod}+F8" = "${pactl} suspend-sink @DEFAULT_SINK@ 1; ${pactl} suspend-sink @DEFAULT_SINK@ 0"; # Re-synchronize bluetooth headset
|
||||||
|
"${mod}+F11" = "exec ${pkgs.pavucontrol}/bin/pwvucontrol";
|
||||||
|
# TODO Find pacmixer?
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
31
hm/desktop/autorandr/default.nix
Normal file
31
hm/desktop/autorandr/default.nix
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
builtin_configs = [ "off" "common" "clone-largest" "horizontal" "vertical" "horizontal-reverse" "vertical-reverse" ];
|
||||||
|
autorandrmenu = { title, option, builtin ? false }: pkgs.writeShellScript "autorandrmenu"
|
||||||
|
''
|
||||||
|
shopt -s nullglob globstar
|
||||||
|
profiles="${if builtin then lib.strings.concatLines builtin_configs else ""}$(${pkgs.autorandr}/bin/autorandr | ${pkgs.gawk}/bin/awk '{ print $1 }')"
|
||||||
|
profile="$(echo "$profiles" | ${config.programs.rofi.package}/bin/rofi -dmenu -p "${title}")"
|
||||||
|
[[ -n "$profile" ]] || exit
|
||||||
|
${pkgs.autorandr}/bin/autorandr ${option} "$profile"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
frogeye.desktop.i3.bindmodes = {
|
||||||
|
"Screen setup [A] Auto [L] Load [S] Save [R] Remove [D] Default" =
|
||||||
|
{
|
||||||
|
bindings = {
|
||||||
|
"a" = "exec ${pkgs.autorandr}/bin/autorandr --change --force, mode default";
|
||||||
|
"l" = "exec ${autorandrmenu {title="Load profile"; option="--load"; builtin = true;}}, mode default";
|
||||||
|
"s" = "exec ${autorandrmenu {title="Save profile"; option="--save";}}, mode default";
|
||||||
|
"r" = "exec ${autorandrmenu {title="Remove profile"; option="--remove";}}, mode default";
|
||||||
|
"d" = "exec ${autorandrmenu {title="Default profile"; option="--default"; builtin = true;}}, mode default";
|
||||||
|
};
|
||||||
|
mod_enter = "t";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
programs.autorandr.enable = true;
|
||||||
|
services.autorandr.enable = true;
|
||||||
|
};
|
||||||
|
}
|
9
hm/desktop/background/default.nix
Normal file
9
hm/desktop/background/default.nix
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{ pkgs, config, lib, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
# This correctly sets the background on some occasions, below does the rest
|
||||||
|
programs.autorandr.hooks.postswitch = {
|
||||||
|
background = "${pkgs.feh}/bin/feh --no-fehbg --bg-fill ${config.stylix.image}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
180
hm/desktop/browser/default.nix
Normal file
180
hm/desktop/browser/default.nix
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
{ pkgs, lib, config, nur, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
home.sessionVariables = {
|
||||||
|
BROWSER = "qutebrowser";
|
||||||
|
};
|
||||||
|
programs = {
|
||||||
|
firefox = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.firefox.override {
|
||||||
|
nativeMessagingHosts = [
|
||||||
|
pkgs.tridactyl-native
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
profiles.hm = {
|
||||||
|
extensions = with config.nur.repos.rycee.firefox-addons;
|
||||||
|
[
|
||||||
|
(buildFirefoxXpiAddon {
|
||||||
|
|
||||||
|
pname = "onetab";
|
||||||
|
version = "0.1.0";
|
||||||
|
addonId = "onetab@nated";
|
||||||
|
url = "https://addons.mozilla.org/firefox/downloads/file/4118712/one_tab_per_window-0.1.0.xpi";
|
||||||
|
sha256 = "sha256-64DeL2xgXpqz32LJWDx4jhS2Fvbld8re3z8fdwnNTw0=";
|
||||||
|
meta = with lib;
|
||||||
|
{
|
||||||
|
homepage = "https://git.sr.ht/~nated/onetab";
|
||||||
|
description = "When a new tab is opened, redirects it to a new window instead.";
|
||||||
|
license = licenses.unfree;
|
||||||
|
mozPermissions = [ "tabs" ];
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
tridactyl
|
||||||
|
ublock-origin
|
||||||
|
];
|
||||||
|
search = {
|
||||||
|
default = "DuckDuckGo";
|
||||||
|
engines = {
|
||||||
|
# TODO Harmonize with qutebrowser search engines
|
||||||
|
"Nix Packages" = {
|
||||||
|
urls = [
|
||||||
|
{
|
||||||
|
template = "https://search.nixos.org/packages";
|
||||||
|
params = [
|
||||||
|
{ name = "type"; value = "packages"; }
|
||||||
|
{ name = "query"; value = "{searchTerms}"; }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
|
||||||
|
definedAliases = [ "@np" ];
|
||||||
|
};
|
||||||
|
"NixOS Wiki" = {
|
||||||
|
urls = [{ template = "https://nixos.wiki/index.php?search={searchTerms}"; }];
|
||||||
|
iconUpdateURL = "https://nixos.wiki/favicon.png";
|
||||||
|
updateInterval = 24 * 60 * 60 * 1000; # every day
|
||||||
|
definedAliases = [ "@nw" ];
|
||||||
|
};
|
||||||
|
"Bing".metaData.hidden = true;
|
||||||
|
"Google".metaData.alias = "@g"; # builtin engines only support specifying one additional alias
|
||||||
|
};
|
||||||
|
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
|
||||||
|
"browser.search.region" = "GB";
|
||||||
|
"browser.search.isUS" = false;
|
||||||
|
"distribution.searchplugins.defaultLocale" = "en-GB";
|
||||||
|
"general.useragent.locale" = "en-GB";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
qutebrowser = {
|
||||||
|
enable = true;
|
||||||
|
keyBindings = {
|
||||||
|
normal = {
|
||||||
|
# Match tab behaviour to i3. Not that I use tabs.
|
||||||
|
"H" = "tab-prev";
|
||||||
|
"J" = "back";
|
||||||
|
"K" = "forward";
|
||||||
|
"L" = "tab-next";
|
||||||
|
# "T" = null;
|
||||||
|
"af" = "spawn --userscript freshrss"; # TODO Broken?
|
||||||
|
"as" = "spawn --userscript shaarli"; # TODO I don't use shaarli anymore
|
||||||
|
# "d" = null;
|
||||||
|
"u" = "undo --window";
|
||||||
|
# TODO Unbind d and T (?)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
loadAutoconfig = true;
|
||||||
|
searchEngines = rec {
|
||||||
|
DEFAULT = ecosia;
|
||||||
|
alpinep = "https://pkgs.alpinelinux.org/packages?name={}&branch=edge";
|
||||||
|
ampwhat = "http://www.amp-what.com/unicode/search/{}";
|
||||||
|
arch = "https://wiki.archlinux.org/?search={}";
|
||||||
|
archp = "https://www.archlinux.org/packages/?q={}";
|
||||||
|
aur = "https://aur.archlinux.org/packages/?K={}";
|
||||||
|
aw = ampwhat;
|
||||||
|
ddg = duckduckgo;
|
||||||
|
dockerhub = "https://hub.docker.com/search/?isAutomated=0&isOfficial=0&page=1&pullCount=0&q={}&starCount=0";
|
||||||
|
duckduckgo = "https://duckduckgo.com/?q={}&ia=web";
|
||||||
|
ecosia = "https://www.ecosia.org/search?q={}";
|
||||||
|
gfr = "https://www.google.fr/search?hl=fr&q={}";
|
||||||
|
g = google;
|
||||||
|
gh = github;
|
||||||
|
gi = "http://images.google.com/search?q={}";
|
||||||
|
giphy = "https://giphy.com/search/{}";
|
||||||
|
github = "https://github.com/search?q={}";
|
||||||
|
google = "https://www.google.fr/search?q={}";
|
||||||
|
hm = homemanager;
|
||||||
|
homemanager = "https://home-manager-options.extranix.com/?query={}&release=${config.home.version.release}";
|
||||||
|
invidious = "https://invidious.frogeye.fr/search?q={}";
|
||||||
|
inv = invidious;
|
||||||
|
nixos = "https://search.nixos.org/options?channel=${config.home.version.release}&query={}";
|
||||||
|
nixoswiki = "https://wiki.nixos.org/w/index.php?search={}";
|
||||||
|
nixpkgs = "https://search.nixos.org/packages?channel=${config.home.version.release}&query={}";
|
||||||
|
noogle = "https://noogle.dev/q?term={}";
|
||||||
|
npm = "https://www.npmjs.com/search?q={}";
|
||||||
|
nw = nixoswiki;
|
||||||
|
os = nixos;
|
||||||
|
pkgs = nixpkgs;
|
||||||
|
q = qwant;
|
||||||
|
qwant = "https://www.qwant.com/?t=web&q={}";
|
||||||
|
wolfram = "https://www.wolframalpha.com/input/?i={}";
|
||||||
|
youtube = "https://www.youtube.com/results?search_query={}";
|
||||||
|
yt = youtube;
|
||||||
|
};
|
||||||
|
settings = {
|
||||||
|
colors.webpage.darkmode.policy.images = "never"; # No inverting images in dark mode, is ugly
|
||||||
|
downloads.location.prompt = false;
|
||||||
|
tabs = {
|
||||||
|
show = "never";
|
||||||
|
tabs_are_windows = true;
|
||||||
|
};
|
||||||
|
url = rec {
|
||||||
|
open_base_url = true;
|
||||||
|
start_pages = lib.mkDefault "https://geoffrey.frogeye.fr/blank.html";
|
||||||
|
default_page = start_pages;
|
||||||
|
};
|
||||||
|
content = {
|
||||||
|
# I had this setting below, not sure if it did something special
|
||||||
|
# config.set("content.cookies.accept", "no-3rdparty", "chrome://*/*")
|
||||||
|
cookies.accept = "no-3rdparty";
|
||||||
|
prefers_reduced_motion = true;
|
||||||
|
headers.accept_language = "fr-FR, fr;q=0.9, en-GB;q=0.8, en-US;q=0.7, en;q=0.6";
|
||||||
|
tls.certificate_errors = "ask-block-thirdparty";
|
||||||
|
javascript.clipboard = "access"; # copy-paste is fine
|
||||||
|
};
|
||||||
|
editor.command = [ "${pkgs.neovide}/bin/neovide" "--" "-f" "{file}" "-c" "normal {line}G{column0}l" ];
|
||||||
|
# TODO Doesn't work on Arch. Does it even load the right profile on Nix?
|
||||||
|
# TODO spellcheck.languages = ["fr-FR" "en-GB" "en-US"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xdg = {
|
||||||
|
configFile."tridactyl/tridactylrc".source = ./tridactylrc; # TODO Improve that :)
|
||||||
|
mimeApps = {
|
||||||
|
enable = true;
|
||||||
|
defaultApplications = {
|
||||||
|
"text/html" = "org.qutebrowser.qutebrowser.desktop";
|
||||||
|
"x-scheme-handler/http" = "org.qutebrowser.qutebrowser.desktop";
|
||||||
|
"x-scheme-handler/https" = "org.qutebrowser.qutebrowser.desktop";
|
||||||
|
"x-scheme-handler/about" = "org.qutebrowser.qutebrowser.desktop";
|
||||||
|
"x-scheme-handler/unknown" = "org.qutebrowser.qutebrowser.desktop";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.keybindings = {
|
||||||
|
"${config.xsession.windowManager.i3.config.modifier}+m" = "exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
nur.hmModules.nur
|
||||||
|
];
|
||||||
|
}
|
|
@ -79,14 +79,14 @@ set searchurls.npm https://www.npmjs.com/search?q=%s
|
||||||
set searchurls.pypi https://pypi.org/search/?q=%s
|
set searchurls.pypi https://pypi.org/search/?q=%s
|
||||||
set searchurls.python https://docs.python.org/3/search.html?q=%s
|
set searchurls.python https://docs.python.org/3/search.html?q=%s
|
||||||
set searchurls.qwant https://www.qwant.com/?t=web&q=%s
|
set searchurls.qwant https://www.qwant.com/?t=web&q=%s
|
||||||
set searchurls.invidious https://invidious.drycat.fr/search?q=%s
|
set searchurls.invidious https://invidious.frogeye.fr/search?q=%s
|
||||||
set searchurls.id https://invidious.drycat.fr/search?q=%s
|
set searchurls.id https://invidious.drycat.fr/search?q=%s
|
||||||
set searchurls.wa https://www.wolframalpha.com/input/?i=%s
|
set searchurls.wa https://www.wolframalpha.com/input/?i=%s
|
||||||
set searchurls.yt https://www.youtube.com/results?search_query=%s
|
set searchurls.yt https://www.youtube.com/results?search_query=%s
|
||||||
|
|
||||||
" Firefox GUI
|
" Firefox GUI
|
||||||
" This can still be shown with F6!
|
" This can still be shown with F6!
|
||||||
" guiset_quiet gui none
|
guiset_quiet gui none
|
||||||
|
|
||||||
" Never autofocus
|
" Never autofocus
|
||||||
set allowautofocus false
|
set allowautofocus false
|
168
hm/desktop/default.nix
Normal file
168
hm/desktop/default.nix
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
{ pkgs, config, lib, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./audio
|
||||||
|
./autorandr
|
||||||
|
./background
|
||||||
|
./browser
|
||||||
|
./frobar/module.nix
|
||||||
|
./i3.nix
|
||||||
|
./lock
|
||||||
|
./mpd
|
||||||
|
./presentation
|
||||||
|
./redness
|
||||||
|
./screenshots
|
||||||
|
./terminal
|
||||||
|
];
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
xsession = {
|
||||||
|
enable = true;
|
||||||
|
# Not using config.xdg.configHome because it needs to be $HOME-relative paths and path manipulation is hard
|
||||||
|
scriptPath = ".config/xsession";
|
||||||
|
profilePath = ".config/xprofile";
|
||||||
|
windowManager = {
|
||||||
|
i3.enable = true;
|
||||||
|
};
|
||||||
|
numlock.enable = config.frogeye.desktop.numlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
programs = {
|
||||||
|
# Terminal
|
||||||
|
bash.shellAliases = {
|
||||||
|
x = "startx ${config.home.homeDirectory}/${config.xsession.scriptPath}; logout";
|
||||||
|
lmms = "lmms --config ${config.xdg.configHome}/lmmsrc.xml";
|
||||||
|
};
|
||||||
|
rofi = {
|
||||||
|
# TODO This theme template, that was used for Arch, looks much better:
|
||||||
|
# https://gitlab.com/jordiorlando/base16-rofi/-/blob/master/templates/default.mustache
|
||||||
|
enable = true;
|
||||||
|
pass.enable = true;
|
||||||
|
extraConfig = {
|
||||||
|
lazy-grab = false;
|
||||||
|
matching = "regex";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
mpv = {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
audio-display = false;
|
||||||
|
save-position-on-quit = true;
|
||||||
|
osc = false; # Required by thumbnail script
|
||||||
|
# Hardware acceleration (from https://nixos.wiki/wiki/Accelerated_Video_Playback#MPV, vo=gpu already default)
|
||||||
|
hwdec = "auto-safe";
|
||||||
|
profile = "gpu-hq";
|
||||||
|
};
|
||||||
|
scripts = with pkgs.mpvScripts; [ thumbnail mpris ];
|
||||||
|
scriptOpts = {
|
||||||
|
mpv_thumbnail_script = {
|
||||||
|
autogenerate = false; # TODO It creates too many processes at once, crashing the system
|
||||||
|
cache_directory = "/tmp/mpv_thumbs_${config.home.username}";
|
||||||
|
mpv_hwdec = "auto-safe";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg = {
|
||||||
|
userDirs =
|
||||||
|
let
|
||||||
|
wellKnownUserDirs = [ "desktop" "documents" "download" "music" "pictures" "publicShare" "templates" "videos" ];
|
||||||
|
wellKnownUserDirsNulled = builtins.listToAttrs (builtins.map (name: { inherit name; value = null; }) wellKnownUserDirs);
|
||||||
|
allFolders = builtins.attrValues config.frogeye.folders;
|
||||||
|
folders = builtins.filter (folder: folder.xdgUserDirVariable != null && folder.user == config.home.username) allFolders;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
enable = true;
|
||||||
|
createDirectories = true;
|
||||||
|
extraConfig = builtins.listToAttrs (builtins.map
|
||||||
|
(folder: {
|
||||||
|
name = folder.xdgUserDirVariable;
|
||||||
|
value = "${config.home.homeDirectory}/${folder.path}";
|
||||||
|
})
|
||||||
|
folders);
|
||||||
|
} // wellKnownUserDirsNulled; # Don't use defaults dirs
|
||||||
|
};
|
||||||
|
services = {
|
||||||
|
blueman-applet.enable = true;
|
||||||
|
unclutter.enable = true;
|
||||||
|
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 = {
|
||||||
|
background = lib.mkForce base01;
|
||||||
|
foreground = lib.mkForce base03;
|
||||||
|
frame_color = lib.mkForce base05;
|
||||||
|
};
|
||||||
|
urgency_normal = {
|
||||||
|
background = lib.mkForce base02;
|
||||||
|
foreground = lib.mkForce base05;
|
||||||
|
frame_color = lib.mkForce base05;
|
||||||
|
};
|
||||||
|
urgency_critical = {
|
||||||
|
background = lib.mkForce base08;
|
||||||
|
foreground = lib.mkForce base06;
|
||||||
|
frame_color = lib.mkForce base05;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
home = {
|
||||||
|
file = {
|
||||||
|
".face" = {
|
||||||
|
# TODO Only works on pindakaas? See https://wiki.archlinux.org/title/LightDM#Changing_your_avatar
|
||||||
|
source = pkgs.runCommand "face.png" { } "${pkgs.inkscape}/bin/inkscape ${./face.svg} -w 1024 -o $out";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
packages = with pkgs; [
|
||||||
|
# remote
|
||||||
|
tigervnc
|
||||||
|
|
||||||
|
# multimedia common
|
||||||
|
gimp
|
||||||
|
inkscape
|
||||||
|
libreoffice
|
||||||
|
|
||||||
|
# data management
|
||||||
|
freefilesync
|
||||||
|
|
||||||
|
# misc
|
||||||
|
gedit
|
||||||
|
xfce.thunar
|
||||||
|
nomacs
|
||||||
|
feh
|
||||||
|
zbar
|
||||||
|
evince
|
||||||
|
zathura
|
||||||
|
meld
|
||||||
|
python3Packages.magic
|
||||||
|
|
||||||
|
# x11-exclusive
|
||||||
|
simplescreenrecorder
|
||||||
|
trayer
|
||||||
|
xclip
|
||||||
|
keynav
|
||||||
|
xorg.xinit
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
759
hm/desktop/frobar/.dev/new.py
Normal file
759
hm/desktop/frobar/.dev/new.py
Normal file
|
@ -0,0 +1,759 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import enum
|
||||||
|
import ipaddress
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
import i3ipc
|
||||||
|
import i3ipc.aio
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
T = typing.TypeVar("T", bound="ComposableText")
|
||||||
|
P = typing.TypeVar("P", bound="ComposableText")
|
||||||
|
C = typing.TypeVar("C", bound="ComposableText")
|
||||||
|
Sortable = str | int
|
||||||
|
|
||||||
|
|
||||||
|
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 "{:3d}{}".format(int(num), unit)
|
||||||
|
else:
|
||||||
|
return "{:.1f}{}".format(num, unit)
|
||||||
|
num /= 1024
|
||||||
|
return "{:d}YiB".format(numi)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
def randomColor(seed: int | bytes | None = None) -> str:
|
||||||
|
if seed is not None:
|
||||||
|
random.seed(seed)
|
||||||
|
return "#" + "".join(f"{random.randint(0, 0xff):02x}" for _ in range(3))
|
||||||
|
|
||||||
|
|
||||||
|
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) -> None:
|
||||||
|
super().__init__(parent=parent, sortKey=sortKey)
|
||||||
|
self.parent: "Module"
|
||||||
|
|
||||||
|
self.color = randomColor()
|
||||||
|
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
|
||||||
|
|
||||||
|
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:
|
||||||
|
await asyncio.sleep(sleepTime)
|
||||||
|
else:
|
||||||
|
log.warning("Skipped an animation frame")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
if lastSection is None:
|
||||||
|
if self.alignment == Alignment.LEFT:
|
||||||
|
text += "%{B" + section.color + "}%{F-}"
|
||||||
|
else:
|
||||||
|
text += "%{B-}%{F" + section.color + "}%{R}%{F-}"
|
||||||
|
else:
|
||||||
|
if self.alignment == Alignment.RIGHT:
|
||||||
|
if lastSection.color == section.color:
|
||||||
|
text += ""
|
||||||
|
else:
|
||||||
|
text += "%{F" + section.color + "}%{R}"
|
||||||
|
else:
|
||||||
|
if lastSection.color == section.color:
|
||||||
|
text += ""
|
||||||
|
else:
|
||||||
|
text += "%{R}%{B" + section.color + "}"
|
||||||
|
text += "%{F-}"
|
||||||
|
text += section.getMarkup()
|
||||||
|
lastSection = section
|
||||||
|
if self.alignment != Alignment.RIGHT:
|
||||||
|
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) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.parent: None
|
||||||
|
self.children: typing.MutableSequence[Screen]
|
||||||
|
self.longRunningTasks: list[asyncio.Task] = list()
|
||||||
|
|
||||||
|
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()
|
||||||
|
for output in i3.get_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",
|
||||||
|
]
|
||||||
|
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[command]
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
class Provider:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.modules: list[Module] = list()
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
# Not a NotImplementedError, otherwise can't combine all classes
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MirrorProvider(Provider):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
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 = Section(parent=self.module)
|
||||||
|
|
||||||
|
|
||||||
|
class StaticProvider(SingleSectionProvider):
|
||||||
|
def __init__(self, text: str) -> None:
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await super().run()
|
||||||
|
self.section.setText(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class StatefulSection(Section):
|
||||||
|
|
||||||
|
def __init__(self, parent: Module, sortKey: Sortable = 0) -> None:
|
||||||
|
super().__init__(parent=parent, sortKey=sortKey)
|
||||||
|
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 SingleStatefulSectionProvider(MirrorProvider):
|
||||||
|
async def run(self) -> None:
|
||||||
|
await super().run()
|
||||||
|
self.section = StatefulSection(parent=self.module)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# Providers
|
||||||
|
|
||||||
|
|
||||||
|
class I3ModeProvider(SingleSectionProvider):
|
||||||
|
def on_mode(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
|
self.section.setText(None if e.change == "default" else e.change)
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await super().run()
|
||||||
|
i3 = await i3ipc.aio.Connection(auto_reconnect=True).connect()
|
||||||
|
i3.on(i3ipc.Event.MODE, self.on_mode)
|
||||||
|
await i3.main()
|
||||||
|
|
||||||
|
|
||||||
|
class I3WindowTitleProvider(SingleSectionProvider):
|
||||||
|
# TODO FEAT To make this available from start, we need to find the
|
||||||
|
# `focused=True` element following the `focus` array
|
||||||
|
def on_window(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
|
self.section.setText(e.container.name)
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await super().run()
|
||||||
|
i3 = await i3ipc.aio.Connection(auto_reconnect=True).connect()
|
||||||
|
i3.on(i3ipc.Event.WINDOW, self.on_window)
|
||||||
|
await i3.main()
|
||||||
|
|
||||||
|
|
||||||
|
class I3WorkspacesProvider(Provider):
|
||||||
|
# TODO Custom names
|
||||||
|
# TODO Colors
|
||||||
|
|
||||||
|
async def updateWorkspaces(self, i3: i3ipc.Connection) -> None:
|
||||||
|
"""
|
||||||
|
Since the i3 IPC interface cannot really tell you by events
|
||||||
|
when workspaces get invisible or not urgent anymore.
|
||||||
|
Relying on those exclusively would require reimplementing some of i3 logic.
|
||||||
|
Fetching all the workspaces on event looks ugly but is the most maintainable.
|
||||||
|
Times I tried to challenge this and failed: 2.
|
||||||
|
"""
|
||||||
|
workspaces = await i3.get_workspaces()
|
||||||
|
for workspace in workspaces:
|
||||||
|
module = self.modulesFromOutput[workspace.output]
|
||||||
|
if workspace.num in self.sections:
|
||||||
|
section = self.sections[workspace.num]
|
||||||
|
if section.parent != module:
|
||||||
|
section.unsetParent()
|
||||||
|
section.setParent(module)
|
||||||
|
else:
|
||||||
|
section = Section(parent=module, sortKey=workspace.num)
|
||||||
|
self.sections[workspace.num] = section
|
||||||
|
|
||||||
|
def generate_switch_workspace(num: int) -> typing.Callable:
|
||||||
|
def switch_workspace() -> None:
|
||||||
|
self.bar.taskGroup.create_task(
|
||||||
|
i3.command(f"workspace number {num}")
|
||||||
|
)
|
||||||
|
|
||||||
|
return switch_workspace
|
||||||
|
|
||||||
|
section.setAction(
|
||||||
|
Button.CLICK_LEFT, generate_switch_workspace(workspace.num)
|
||||||
|
)
|
||||||
|
name = workspace.name
|
||||||
|
if workspace.urgent:
|
||||||
|
name = f"{name} !"
|
||||||
|
elif workspace.focused:
|
||||||
|
name = f"{name} +"
|
||||||
|
elif workspace.visible:
|
||||||
|
name = f"{name} *"
|
||||||
|
section.setText(name)
|
||||||
|
workspacesNums = set(workspace.num for workspace in workspaces)
|
||||||
|
for num, section in self.sections.items():
|
||||||
|
if num not in workspacesNums:
|
||||||
|
# This should delete the Section but it turned out to be hard
|
||||||
|
section.setText(None)
|
||||||
|
|
||||||
|
def onWorkspaceChange(
|
||||||
|
self, i3: i3ipc.Connection, e: i3ipc.Event | None = None
|
||||||
|
) -> None:
|
||||||
|
# Cancelling the task doesn't seem to prevent performance double-events
|
||||||
|
self.bar.taskGroup.create_task(self.updateWorkspaces(i3))
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.sections: dict[int, Section] = dict()
|
||||||
|
self.modulesFromOutput: dict[str, Module] = dict()
|
||||||
|
self.bar: Bar
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
for module in self.modules:
|
||||||
|
screen = module.getFirstParentOfType(Screen)
|
||||||
|
output = screen.output
|
||||||
|
self.modulesFromOutput[output] = module
|
||||||
|
self.bar = module.bar
|
||||||
|
|
||||||
|
i3 = await i3ipc.aio.Connection(auto_reconnect=True).connect()
|
||||||
|
i3.on(i3ipc.Event.WORKSPACE, self.onWorkspaceChange)
|
||||||
|
self.onWorkspaceChange(i3)
|
||||||
|
await i3.main()
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProviderSection(StatefulSection):
|
||||||
|
def __init__(self, parent: Module, iface: str, provider: "NetworkProvider") -> None:
|
||||||
|
super().__init__(parent=parent, sortKey=iface)
|
||||||
|
self.iface = iface
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
|
self.ignore = False
|
||||||
|
self.icon = "?"
|
||||||
|
self.wifi = False
|
||||||
|
if iface == "lo":
|
||||||
|
self.ignore = True
|
||||||
|
elif iface.startswith("eth") or iface.startswith("enp"):
|
||||||
|
if "u" in iface:
|
||||||
|
self.icon = ""
|
||||||
|
else:
|
||||||
|
self.icon = ""
|
||||||
|
elif iface.startswith("wlan") or iface.startswith("wl"):
|
||||||
|
self.icon = ""
|
||||||
|
self.wifi = True
|
||||||
|
elif (
|
||||||
|
iface.startswith("tun") or iface.startswith("tap") or iface.startswith("wg")
|
||||||
|
):
|
||||||
|
self.icon = ""
|
||||||
|
elif iface.startswith("docker"):
|
||||||
|
self.icon = ""
|
||||||
|
elif iface.startswith("veth"):
|
||||||
|
self.icon = ""
|
||||||
|
elif iface.startswith("vboxnet"):
|
||||||
|
self.icon = ""
|
||||||
|
self.numberStates = 5 if self.wifi else 4
|
||||||
|
self.state = 1 if self.wifi else 0
|
||||||
|
|
||||||
|
self.setChangedState(self.update)
|
||||||
|
|
||||||
|
async def update(self) -> None:
|
||||||
|
if self.ignore or not self.provider.if_stats[self.iface].isup:
|
||||||
|
self.setText(None)
|
||||||
|
return
|
||||||
|
text = self.icon
|
||||||
|
|
||||||
|
state = self.state + (0 if self.wifi else 1) # SSID
|
||||||
|
if self.wifi and state >= 1:
|
||||||
|
cmd = ["iwgetid", self.iface, "--raw"]
|
||||||
|
proc = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await proc.communicate()
|
||||||
|
text += f" {stdout.decode().strip()}"
|
||||||
|
|
||||||
|
if state >= 2: # Address
|
||||||
|
for address in self.provider.if_addrs[self.iface]:
|
||||||
|
if address.family == socket.AF_INET:
|
||||||
|
net = ipaddress.IPv4Network(
|
||||||
|
(address.address, address.netmask), strict=False
|
||||||
|
)
|
||||||
|
text += f" {net.with_prefixlen}"
|
||||||
|
break
|
||||||
|
|
||||||
|
if state >= 3: # Speed
|
||||||
|
prevRecv = self.provider.prev_io_counters[self.iface].bytes_recv
|
||||||
|
recv = self.provider.io_counters[self.iface].bytes_recv
|
||||||
|
prevSent = self.provider.prev_io_counters[self.iface].bytes_sent
|
||||||
|
sent = self.provider.io_counters[self.iface].bytes_sent
|
||||||
|
dt = self.provider.time - self.provider.prev_time
|
||||||
|
|
||||||
|
recvDiff = (recv - prevRecv) / dt
|
||||||
|
sentDiff = (sent - prevSent) / dt
|
||||||
|
text += f" ↓{humanSize(recvDiff)}↑{humanSize(sentDiff)}"
|
||||||
|
|
||||||
|
if state >= 4: # Counter
|
||||||
|
text += f" ⇓{humanSize(recv)}⇑{humanSize(sent)}"
|
||||||
|
|
||||||
|
self.setText(text)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProvider(MirrorProvider, PeriodicProvider):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.sections: dict[str, NetworkProviderSection] = dict()
|
||||||
|
|
||||||
|
async def init(self) -> None:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
self.time = loop.time()
|
||||||
|
self.io_counters = psutil.net_io_counters(pernic=True)
|
||||||
|
|
||||||
|
async def loop(self) -> None:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
async with asyncio.TaskGroup() as tg:
|
||||||
|
self.prev_io_counters = self.io_counters
|
||||||
|
self.prev_time = self.time
|
||||||
|
# On-demand would only benefit if_addrs:
|
||||||
|
# stats are used to determine display,
|
||||||
|
# and we want to keep previous io_counters
|
||||||
|
# so displaying stats is ~instant.
|
||||||
|
self.time = loop.time()
|
||||||
|
self.if_stats = psutil.net_if_stats()
|
||||||
|
self.if_addrs = psutil.net_if_addrs()
|
||||||
|
self.io_counters = psutil.net_io_counters(pernic=True)
|
||||||
|
|
||||||
|
for iface in self.if_stats:
|
||||||
|
section = self.sections.get(iface)
|
||||||
|
if not section:
|
||||||
|
section = NetworkProviderSection(
|
||||||
|
parent=self.module, iface=iface, provider=self
|
||||||
|
)
|
||||||
|
self.sections[iface] = section
|
||||||
|
|
||||||
|
tg.create_task(section.update())
|
||||||
|
for iface, section in self.sections.items():
|
||||||
|
if iface not in self.if_stats:
|
||||||
|
section.setText(None)
|
||||||
|
|
||||||
|
async def onStateChange(self, section: StatefulSection) -> None:
|
||||||
|
assert isinstance(section, NetworkProviderSection)
|
||||||
|
await section.update()
|
||||||
|
|
||||||
|
|
||||||
|
class TimeProvider(PeriodicStatefulProvider):
|
||||||
|
FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"]
|
||||||
|
|
||||||
|
async def init(self) -> None:
|
||||||
|
self.section.state = 1
|
||||||
|
self.section.numberStates = len(self.FORMATS)
|
||||||
|
|
||||||
|
async def loop(self) -> None:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
format = self.FORMATS[self.section.state]
|
||||||
|
self.section.setText(now.strftime(format))
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
bar = Bar()
|
||||||
|
dualScreen = len(bar.children) > 1
|
||||||
|
|
||||||
|
bar.addProvider(I3ModeProvider(), alignment=Alignment.LEFT)
|
||||||
|
bar.addProvider(I3WorkspacesProvider(), alignment=Alignment.LEFT)
|
||||||
|
if dualScreen:
|
||||||
|
bar.addProvider(
|
||||||
|
I3WindowTitleProvider(), screenNum=0, alignment=Alignment.CENTER
|
||||||
|
)
|
||||||
|
bar.addProvider(
|
||||||
|
StaticProvider(text="mpris"),
|
||||||
|
screenNum=1 if dualScreen else None,
|
||||||
|
alignment=Alignment.CENTER,
|
||||||
|
)
|
||||||
|
|
||||||
|
bar.addProvider(StaticProvider("C L M T B"), alignment=Alignment.RIGHT)
|
||||||
|
bar.addProvider(
|
||||||
|
StaticProvider("pulse"),
|
||||||
|
screenNum=1 if dualScreen else None,
|
||||||
|
alignment=Alignment.RIGHT,
|
||||||
|
)
|
||||||
|
bar.addProvider(
|
||||||
|
NetworkProvider(),
|
||||||
|
screenNum=0 if dualScreen else None,
|
||||||
|
alignment=Alignment.RIGHT,
|
||||||
|
)
|
||||||
|
bar.addProvider(TimeProvider(), alignment=Alignment.RIGHT)
|
||||||
|
|
||||||
|
await bar.run()
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
32
hm/desktop/frobar/default.nix
Normal file
32
hm/desktop/frobar/default.nix
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { config = { }; overlays = [ ]; }, ... }:
|
||||||
|
let
|
||||||
|
lemonbar = (pkgs.lemonbar-xft.overrideAttrs (old: {
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "drscream";
|
||||||
|
repo = "lemonbar-xft";
|
||||||
|
rev = "a64a2a6a6d643f4d92f9d7600722710eebce7bdb";
|
||||||
|
sha256 = "sha256-T5FhEPIiDt/9paJwL9Sj84CBtA0YFi1hZz0+87Hd6jU=";
|
||||||
|
# https://github.com/drscream/lemonbar-xft/pull/2
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
in
|
||||||
|
# Tried using pyproject.nix but mpd2 dependency wouldn't resolve,
|
||||||
|
# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs.
|
||||||
|
pkgs.python3Packages.buildPythonApplication rec {
|
||||||
|
pname = "frobar";
|
||||||
|
version = "2.0";
|
||||||
|
|
||||||
|
propagatedBuildInputs = with pkgs.python3Packages; [
|
||||||
|
coloredlogs
|
||||||
|
notmuch
|
||||||
|
i3ipc
|
||||||
|
mpd2
|
||||||
|
psutil
|
||||||
|
pulsectl
|
||||||
|
pyinotify
|
||||||
|
];
|
||||||
|
nativeBuildInputs = [ lemonbar ] ++ (with pkgs; [ wirelesstools playerctl ]);
|
||||||
|
makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}" ];
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
}
|
77
hm/desktop/frobar/frobar/__init__.py
Normal file
77
hm/desktop/frobar/frobar/__init__.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
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 run() -> None:
|
||||||
|
Bar.init()
|
||||||
|
Updater.init()
|
||||||
|
|
||||||
|
# Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT)
|
||||||
|
# Bar.addSectionAll(fp.NetworkProvider(theme=2), BarGroupType.RIGHT)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO Middle
|
||||||
|
Bar.addSectionAll(fp.MprisProvider(theme=9), BarGroupType.LEFT)
|
||||||
|
# Bar.addSectionAll(fp.MpdProvider(theme=9), BarGroupType.LEFT)
|
||||||
|
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
|
||||||
|
|
||||||
|
# TODO Computer modes
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
# )
|
||||||
|
|
||||||
|
TIME_THEME = 4
|
||||||
|
Bar.addSectionAll(fp.TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
|
||||||
|
|
||||||
|
# Bar.run()
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3init
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
|
@ -7,11 +7,12 @@ import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import typing
|
||||||
|
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
import i3ipc
|
import i3ipc
|
||||||
|
|
||||||
from frobar.notbusy import notBusy
|
from frobar.common import notBusy
|
||||||
|
|
||||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
@ -29,6 +30,12 @@ log = logging.getLogger()
|
||||||
# TODO forceSize and changeText are different
|
# 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):
|
class BarGroupType(enum.Enum):
|
||||||
LEFT = 0
|
LEFT = 0
|
||||||
RIGHT = 1
|
RIGHT = 1
|
||||||
|
@ -40,6 +47,7 @@ class BarGroupType(enum.Enum):
|
||||||
class BarStdoutThread(threading.Thread):
|
class BarStdoutThread(threading.Thread):
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
while Bar.running:
|
while Bar.running:
|
||||||
|
assert Bar.process.stdout
|
||||||
handle = Bar.process.stdout.readline().strip()
|
handle = Bar.process.stdout.readline().strip()
|
||||||
if not len(handle):
|
if not len(handle):
|
||||||
Bar.stop()
|
Bar.stop()
|
||||||
|
@ -62,20 +70,31 @@ class Bar:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init() -> None:
|
def init() -> None:
|
||||||
Bar.running = True
|
Bar.running = True
|
||||||
|
Bar.everyone = set()
|
||||||
Section.init()
|
Section.init()
|
||||||
|
|
||||||
cmd = ["lemonbar", "-b", "-a", "64"]
|
cmd = [
|
||||||
|
"lemonbar",
|
||||||
|
"-b",
|
||||||
|
"-a",
|
||||||
|
"64",
|
||||||
|
"-F",
|
||||||
|
Section.FGCOLOR,
|
||||||
|
"-B",
|
||||||
|
Section.BGCOLOR,
|
||||||
|
]
|
||||||
for font in Bar.FONTS:
|
for font in Bar.FONTS:
|
||||||
cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)]
|
cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)]
|
||||||
Bar.process = subprocess.Popen(
|
Bar.process = subprocess.Popen(
|
||||||
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||||
)
|
)
|
||||||
Bar.stdoutThread = BarStdoutThread()
|
BarStdoutThread().start()
|
||||||
Bar.stdoutThread.start()
|
|
||||||
|
|
||||||
# Debug
|
i3 = i3ipc.Connection()
|
||||||
Bar(0)
|
for output in i3.get_outputs():
|
||||||
# Bar(1)
|
if not output.active:
|
||||||
|
continue
|
||||||
|
Bar(output.name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def stop() -> None:
|
def stop() -> None:
|
||||||
|
@ -90,29 +109,27 @@ class Bar:
|
||||||
Bar.forever()
|
Bar.forever()
|
||||||
i3 = i3ipc.Connection()
|
i3 = i3ipc.Connection()
|
||||||
|
|
||||||
def doStop(*args) -> None:
|
def doStop(*args: list) -> None:
|
||||||
Bar.stop()
|
Bar.stop()
|
||||||
print(88)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
i3.on("ipc_shutdown", doStop)
|
i3.on("ipc_shutdown", doStop)
|
||||||
i3.main()
|
i3.main()
|
||||||
except BaseException:
|
except BaseException:
|
||||||
print(93)
|
|
||||||
Bar.stop()
|
Bar.stop()
|
||||||
|
|
||||||
# Class globals
|
# Class globals
|
||||||
everyone = set()
|
everyone: set["Bar"]
|
||||||
string = ""
|
string = ""
|
||||||
process = None
|
process: subprocess.Popen
|
||||||
running = False
|
running = False
|
||||||
|
|
||||||
nextHandle = 0
|
nextHandle = 0
|
||||||
actionsF2H = dict()
|
actionsF2H: dict[Handle, bytes] = dict()
|
||||||
actionsH2F = dict()
|
actionsH2F: dict[bytes, Handle] = dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getFunctionHandle(function):
|
def getFunctionHandle(function: typing.Callable[[], None]) -> bytes:
|
||||||
assert callable(function)
|
assert callable(function)
|
||||||
if function in Bar.actionsF2H.keys():
|
if function in Bar.actionsF2H.keys():
|
||||||
return Bar.actionsF2H[function]
|
return Bar.actionsF2H[function]
|
||||||
|
@ -126,13 +143,12 @@ class Bar:
|
||||||
return handle
|
return handle
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def forever():
|
def forever() -> None:
|
||||||
Bar.process.wait()
|
Bar.process.wait()
|
||||||
Bar.stop()
|
Bar.stop()
|
||||||
|
|
||||||
def __init__(self, screen):
|
def __init__(self, output: str) -> None:
|
||||||
assert isinstance(screen, int)
|
self.output = output
|
||||||
self.screen = "%{S" + str(screen) + "}"
|
|
||||||
self.groups = dict()
|
self.groups = dict()
|
||||||
|
|
||||||
for groupType in BarGroupType:
|
for groupType in BarGroupType:
|
||||||
|
@ -140,36 +156,33 @@ class Bar:
|
||||||
self.groups[groupType] = group
|
self.groups[groupType] = group
|
||||||
|
|
||||||
self.childsChanged = False
|
self.childsChanged = False
|
||||||
|
Bar.everyone.add(self)
|
||||||
self.everyone.add(self)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def addSectionAll(section, group, screens=None):
|
def addSectionAll(
|
||||||
|
section: "Section", group: "BarGroupType"
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
.. note::
|
.. note::
|
||||||
Add the section before updating it for the first time.
|
Add the section before updating it for the first time.
|
||||||
"""
|
"""
|
||||||
assert isinstance(section, Section)
|
|
||||||
assert isinstance(group, BarGroupType)
|
|
||||||
# TODO screens selection
|
|
||||||
for bar in Bar.everyone:
|
for bar in Bar.everyone:
|
||||||
bar.addSection(section, group=group)
|
bar.addSection(section, group=group)
|
||||||
|
section.added()
|
||||||
|
|
||||||
def addSection(self, section, group):
|
def addSection(self, section: "Section", group: "BarGroupType") -> None:
|
||||||
assert isinstance(section, Section)
|
|
||||||
assert isinstance(group, BarGroupType)
|
|
||||||
self.groups[group].addSection(section)
|
self.groups[group].addSection(section)
|
||||||
|
|
||||||
def update(self):
|
def update(self) -> None:
|
||||||
if self.childsChanged:
|
if self.childsChanged:
|
||||||
self.string = self.screen
|
self.string = "%{Sn" + self.output + "}"
|
||||||
self.string += self.groups[BarGroupType.LEFT].string
|
self.string += self.groups[BarGroupType.LEFT].string
|
||||||
self.string += self.groups[BarGroupType.RIGHT].string
|
self.string += self.groups[BarGroupType.RIGHT].string
|
||||||
|
|
||||||
self.childsChanged = False
|
self.childsChanged = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def updateAll():
|
def updateAll() -> None:
|
||||||
if Bar.running:
|
if Bar.running:
|
||||||
Bar.string = ""
|
Bar.string = ""
|
||||||
for bar in Bar.everyone:
|
for bar in Bar.everyone:
|
||||||
|
@ -178,8 +191,10 @@ class Bar:
|
||||||
# Color for empty sections
|
# Color for empty sections
|
||||||
Bar.string += BarGroup.color(*Section.EMPTY)
|
Bar.string += BarGroup.color(*Section.EMPTY)
|
||||||
|
|
||||||
# print(Bar.string)
|
string = Bar.string + "\n"
|
||||||
Bar.process.stdin.write(bytes(Bar.string + "\n", "utf-8"))
|
# print(string)
|
||||||
|
assert Bar.process.stdin
|
||||||
|
Bar.process.stdin.write(string.encode())
|
||||||
Bar.process.stdin.flush()
|
Bar.process.stdin.flush()
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,18 +203,16 @@ class BarGroup:
|
||||||
One for each group of each bar
|
One for each group of each bar
|
||||||
"""
|
"""
|
||||||
|
|
||||||
everyone = set()
|
everyone: set["BarGroup"] = set()
|
||||||
|
|
||||||
def __init__(self, groupType, parent):
|
def __init__(self, groupType: BarGroupType, parent: Bar):
|
||||||
assert isinstance(groupType, BarGroupType)
|
|
||||||
assert isinstance(parent, Bar)
|
|
||||||
|
|
||||||
self.groupType = groupType
|
self.groupType = groupType
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
self.sections = list()
|
self.sections: list["Section"] = list()
|
||||||
self.string = ""
|
self.string = ""
|
||||||
self.parts = []
|
self.parts: list[Part] = []
|
||||||
|
|
||||||
#: One of the sections that had their theme or visibility changed
|
#: One of the sections that had their theme or visibility changed
|
||||||
self.childsThemeChanged = False
|
self.childsThemeChanged = False
|
||||||
|
@ -209,11 +222,11 @@ class BarGroup:
|
||||||
|
|
||||||
BarGroup.everyone.add(self)
|
BarGroup.everyone.add(self)
|
||||||
|
|
||||||
def addSection(self, section):
|
def addSection(self, section: "Section") -> None:
|
||||||
self.sections.append(section)
|
self.sections.append(section)
|
||||||
section.addParent(self)
|
section.addParent(self)
|
||||||
|
|
||||||
def addSectionAfter(self, sectionRef, section):
|
def addSectionAfter(self, sectionRef: "Section", section: "Section") -> None:
|
||||||
index = self.sections.index(sectionRef)
|
index = self.sections.index(sectionRef)
|
||||||
self.sections.insert(index + 1, section)
|
self.sections.insert(index + 1, section)
|
||||||
section.addParent(self)
|
section.addParent(self)
|
||||||
|
@ -221,20 +234,20 @@ class BarGroup:
|
||||||
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fgColor(color):
|
def fgColor(color: str) -> str:
|
||||||
return "%{F" + (color or "-") + "}"
|
return "%{F" + (color or "-") + "}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def bgColor(color):
|
def bgColor(color: str) -> str:
|
||||||
return "%{B" + (color or "-") + "}"
|
return "%{B" + (color or "-") + "}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def color(fg, bg):
|
def color(fg: str, bg: str) -> str:
|
||||||
return BarGroup.fgColor(fg) + BarGroup.bgColor(bg)
|
return BarGroup.fgColor(fg) + BarGroup.bgColor(bg)
|
||||||
|
|
||||||
def update(self):
|
def update(self) -> None:
|
||||||
if self.childsThemeChanged:
|
if self.childsThemeChanged:
|
||||||
parts = [BarGroup.ALIGNS[self.groupType]]
|
parts: list[Part] = [BarGroup.ALIGNS[self.groupType]]
|
||||||
|
|
||||||
secs = [sec for sec in self.sections if sec.visible]
|
secs = [sec for sec in self.sections if sec.visible]
|
||||||
lenS = len(secs)
|
lenS = len(secs)
|
||||||
|
@ -283,7 +296,7 @@ class BarGroup:
|
||||||
self.childsTextChanged = False
|
self.childsTextChanged = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def updateAll():
|
def updateAll() -> None:
|
||||||
for group in BarGroup.everyone:
|
for group in BarGroup.everyone:
|
||||||
group.update()
|
group.update()
|
||||||
Bar.updateAll()
|
Bar.updateAll()
|
||||||
|
@ -294,7 +307,7 @@ class SectionThread(threading.Thread):
|
||||||
ANIMATION_STOP = 0.001
|
ANIMATION_STOP = 0.001
|
||||||
ANIMATION_EVOLUTION = 0.9
|
ANIMATION_EVOLUTION = 0.9
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
while Section.somethingChanged.wait():
|
while Section.somethingChanged.wait():
|
||||||
notBusy.wait()
|
notBusy.wait()
|
||||||
Section.updateAll()
|
Section.updateAll()
|
||||||
|
@ -311,52 +324,54 @@ class SectionThread(threading.Thread):
|
||||||
animTime = self.ANIMATION_STOP
|
animTime = self.ANIMATION_STOP
|
||||||
|
|
||||||
|
|
||||||
|
Theme = tuple[str, str]
|
||||||
|
|
||||||
|
|
||||||
class Section:
|
class Section:
|
||||||
# TODO Update all of that to base16
|
# TODO Update all of that to base16
|
||||||
# COLORS = ['#272822', '#383830', '#49483e', '#75715e', '#a59f85', '#f8f8f2',
|
|
||||||
# '#f5f4f1', '#f9f8f5', '#f92672', '#fd971f', '#f4bf75', '#a6e22e',
|
|
||||||
# '#a1efe4', '#66d9ef', '#ae81ff', '#cc6633']
|
|
||||||
COLORS = [
|
COLORS = [
|
||||||
"#181818",
|
"#092c0e",
|
||||||
"#AB4642",
|
"#143718",
|
||||||
"#A1B56C",
|
"#5a7058",
|
||||||
"#F7CA88",
|
"#677d64",
|
||||||
"#7CAFC2",
|
"#89947f",
|
||||||
"#BA8BAF",
|
"#99a08d",
|
||||||
"#86C1B9",
|
"#fae2e3",
|
||||||
"#D8D8D8",
|
"#fff0f1",
|
||||||
"#585858",
|
"#e0332e",
|
||||||
"#AB4642",
|
"#cf4b15",
|
||||||
"#A1B56C",
|
"#bb8801",
|
||||||
"#F7CA88",
|
"#8d9800",
|
||||||
"#7CAFC2",
|
"#1fa198",
|
||||||
"#BA8BAF",
|
"#008dd1",
|
||||||
"#86C1B9",
|
"#5c73c4",
|
||||||
"#F8F8F8",
|
"#d43982",
|
||||||
]
|
]
|
||||||
FGCOLOR = "#F8F8F2"
|
FGCOLOR = "#fff0f1"
|
||||||
BGCOLOR = "#272822"
|
BGCOLOR = "#092c0e"
|
||||||
|
|
||||||
THEMES = list()
|
THEMES: list[Theme] = list()
|
||||||
EMPTY = (FGCOLOR, BGCOLOR)
|
EMPTY: Theme = (FGCOLOR, BGCOLOR)
|
||||||
|
|
||||||
ICON = None
|
ICON: str | None = None
|
||||||
PERSISTENT = False
|
PERSISTENT = False
|
||||||
|
|
||||||
#: Sections that do not have their destination size
|
#: Sections that do not have their destination size
|
||||||
sizeChanging = set()
|
sizeChanging: set["Section"] = set()
|
||||||
updateThread = SectionThread(daemon=True)
|
updateThread: threading.Thread = SectionThread(daemon=True)
|
||||||
somethingChanged = threading.Event()
|
somethingChanged = threading.Event()
|
||||||
lastChosenTheme = 0
|
lastChosenTheme = 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init() -> None:
|
||||||
for t in range(8, 16):
|
for t in range(8, 16):
|
||||||
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
|
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()
|
Section.updateThread.start()
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None) -> None:
|
||||||
#: Displayed section
|
#: Displayed section
|
||||||
#: Note: A section can be empty and displayed!
|
#: Note: A section can be empty and displayed!
|
||||||
self.visible = False
|
self.visible = False
|
||||||
|
@ -379,12 +394,12 @@ class Section:
|
||||||
self.dstSize = 0
|
self.dstSize = 0
|
||||||
|
|
||||||
#: Groups that have this section
|
#: Groups that have this section
|
||||||
self.parents = set()
|
self.parents: set[BarGroup] = set()
|
||||||
|
|
||||||
self.icon = self.ICON
|
self.icon = self.ICON
|
||||||
self.persistent = self.PERSISTENT
|
self.persistent = self.PERSISTENT
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
try:
|
try:
|
||||||
return "<{}><{}>{:01d}{}{:02d}/{:02d}".format(
|
return "<{}><{}>{:01d}{}{:02d}/{:02d}".format(
|
||||||
self.curText,
|
self.curText,
|
||||||
|
@ -394,26 +409,29 @@ class Section:
|
||||||
self.curSize,
|
self.curSize,
|
||||||
self.dstSize,
|
self.dstSize,
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return super().__str__()
|
return super().__str__()
|
||||||
|
|
||||||
def addParent(self, parent):
|
def addParent(self, parent: BarGroup) -> None:
|
||||||
self.parents.add(parent)
|
self.parents.add(parent)
|
||||||
|
|
||||||
def appendAfter(self, section):
|
def appendAfter(self, section: "Section") -> None:
|
||||||
assert len(self.parents)
|
assert len(self.parents)
|
||||||
for parent in self.parents:
|
for parent in self.parents:
|
||||||
parent.addSectionAfter(self, section)
|
parent.addSectionAfter(self, section)
|
||||||
|
|
||||||
def informParentsThemeChanged(self):
|
def added(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def informParentsThemeChanged(self) -> None:
|
||||||
for parent in self.parents:
|
for parent in self.parents:
|
||||||
parent.childsThemeChanged = True
|
parent.childsThemeChanged = True
|
||||||
|
|
||||||
def informParentsTextChanged(self):
|
def informParentsTextChanged(self) -> None:
|
||||||
for parent in self.parents:
|
for parent in self.parents:
|
||||||
parent.childsTextChanged = True
|
parent.childsTextChanged = True
|
||||||
|
|
||||||
def updateText(self, text):
|
def updateText(self, text: Element) -> None:
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
text = Text(text)
|
text = Text(text)
|
||||||
elif isinstance(text, Text) and not len(text.elements):
|
elif isinstance(text, Text) and not len(text.elements):
|
||||||
|
@ -440,14 +458,13 @@ class Section:
|
||||||
Section.sizeChanging.add(self)
|
Section.sizeChanging.add(self)
|
||||||
Section.somethingChanged.set()
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
def setDecorators(self, **kwargs):
|
def setDecorators(self, **kwargs: Handle) -> None:
|
||||||
self.dstText.setDecorators(**kwargs)
|
self.dstText.setDecorators(**kwargs)
|
||||||
self.curText = str(self.dstText)
|
self.curText = str(self.dstText)
|
||||||
self.informParentsTextChanged()
|
self.informParentsTextChanged()
|
||||||
Section.somethingChanged.set()
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
def updateTheme(self, theme):
|
def updateTheme(self, theme: int) -> None:
|
||||||
assert isinstance(theme, int)
|
|
||||||
assert theme < len(Section.THEMES)
|
assert theme < len(Section.THEMES)
|
||||||
if theme == self.theme:
|
if theme == self.theme:
|
||||||
return
|
return
|
||||||
|
@ -455,19 +472,18 @@ class Section:
|
||||||
self.informParentsThemeChanged()
|
self.informParentsThemeChanged()
|
||||||
Section.somethingChanged.set()
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
def updateVisibility(self, visibility):
|
def updateVisibility(self, visibility: bool) -> None:
|
||||||
assert isinstance(visibility, bool)
|
|
||||||
|
|
||||||
self.visible = visibility
|
self.visible = visibility
|
||||||
self.informParentsThemeChanged()
|
self.informParentsThemeChanged()
|
||||||
Section.somethingChanged.set()
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fit(text, size):
|
def fit(text: str, size: int) -> str:
|
||||||
t = len(text)
|
t = len(text)
|
||||||
return text[:size] if t >= size else text + [" "] * (size - t)
|
return text[:size] if t >= size else text + " " * (size - t)
|
||||||
|
|
||||||
def update(self):
|
def update(self) -> None:
|
||||||
# TODO Might profit of a better logic
|
# TODO Might profit of a better logic
|
||||||
if not self.visible:
|
if not self.visible:
|
||||||
self.updateVisibility(True)
|
self.updateVisibility(True)
|
||||||
|
@ -488,7 +504,7 @@ class Section:
|
||||||
self.informParentsTextChanged()
|
self.informParentsTextChanged()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def updateAll():
|
def updateAll() -> None:
|
||||||
"""
|
"""
|
||||||
Process all sections for text size changes
|
Process all sections for text size changes
|
||||||
"""
|
"""
|
||||||
|
@ -501,7 +517,7 @@ class Section:
|
||||||
Section.somethingChanged.clear()
|
Section.somethingChanged.clear()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ramp(p, ramp=" ▁▂▃▄▅▆▇█"):
|
def ramp(p: float, ramp: str = " ▁▂▃▄▅▆▇█") -> str:
|
||||||
if p > 1:
|
if p > 1:
|
||||||
return ramp[-1]
|
return ramp[-1]
|
||||||
elif p < 0:
|
elif p < 0:
|
||||||
|
@ -512,11 +528,11 @@ class Section:
|
||||||
|
|
||||||
class StatefulSection(Section):
|
class StatefulSection(Section):
|
||||||
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
|
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
|
||||||
NUMBER_STATES = None
|
NUMBER_STATES: int
|
||||||
DEFAULT_STATE = 0
|
DEFAULT_STATE = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, theme: int | None) -> None:
|
||||||
Section.__init__(self, *args, **kwargs)
|
Section.__init__(self, theme=theme)
|
||||||
self.state = self.DEFAULT_STATE
|
self.state = self.DEFAULT_STATE
|
||||||
if hasattr(self, "onChangeState"):
|
if hasattr(self, "onChangeState"):
|
||||||
self.onChangeState(self.state)
|
self.onChangeState(self.state)
|
||||||
|
@ -524,20 +540,22 @@ class StatefulSection(Section):
|
||||||
clickLeft=self.incrementState, clickRight=self.decrementState
|
clickLeft=self.incrementState, clickRight=self.decrementState
|
||||||
)
|
)
|
||||||
|
|
||||||
def incrementState(self):
|
def incrementState(self) -> None:
|
||||||
newState = min(self.state + 1, self.NUMBER_STATES - 1)
|
newState = min(self.state + 1, self.NUMBER_STATES - 1)
|
||||||
self.changeState(newState)
|
self.changeState(newState)
|
||||||
|
|
||||||
def decrementState(self):
|
def decrementState(self) -> None:
|
||||||
newState = max(self.state - 1, 0)
|
newState = max(self.state - 1, 0)
|
||||||
self.changeState(newState)
|
self.changeState(newState)
|
||||||
|
|
||||||
def changeState(self, state):
|
def changeState(self, state: int) -> None:
|
||||||
assert isinstance(state, int)
|
|
||||||
assert state < self.NUMBER_STATES
|
assert state < self.NUMBER_STATES
|
||||||
self.state = state
|
self.state = state
|
||||||
if hasattr(self, "onChangeState"):
|
if hasattr(self, "onChangeState"):
|
||||||
self.onChangeState(state)
|
self.onChangeState(state)
|
||||||
|
assert hasattr(
|
||||||
|
self, "refreshData"
|
||||||
|
), "StatefulSection should be paired with some Updater"
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
@ -548,10 +566,13 @@ class ColorCountsSection(StatefulSection):
|
||||||
NUMBER_STATES = 3
|
NUMBER_STATES = 3
|
||||||
COLORABLE_ICON = "?"
|
COLORABLE_ICON = "?"
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: None | int = None) -> None:
|
||||||
StatefulSection.__init__(self, theme=theme)
|
StatefulSection.__init__(self, theme=theme)
|
||||||
|
|
||||||
def fetcher(self):
|
def subfetcher(self) -> list[tuple[int, str]]:
|
||||||
|
raise NotImplementedError("Interface must be implemented")
|
||||||
|
|
||||||
|
def fetcher(self) -> typing.Union[None, "Text"]:
|
||||||
counts = self.subfetcher()
|
counts = self.subfetcher()
|
||||||
# Nothing
|
# Nothing
|
||||||
if not len(counts):
|
if not len(counts):
|
||||||
|
@ -566,67 +587,66 @@ class ColorCountsSection(StatefulSection):
|
||||||
# Icon + Total
|
# Icon + Total
|
||||||
elif self.state == 1 and len(counts) > 1:
|
elif self.state == 1 and len(counts) > 1:
|
||||||
total = sum([count for count, color in counts])
|
total = sum([count for count, color in counts])
|
||||||
return Text(self.COLORABLE_ICON, " ", total)
|
return Text(self.COLORABLE_ICON, " ", str(total))
|
||||||
# Icon + Counts
|
# Icon + Counts
|
||||||
else:
|
else:
|
||||||
text = Text(self.COLORABLE_ICON)
|
text = Text(self.COLORABLE_ICON)
|
||||||
for count, color in counts:
|
for count, color in counts:
|
||||||
text.append(" ", Text(count, fg=color))
|
text.append(" ", Text(str(count), fg=color))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class Text:
|
class Text:
|
||||||
def _setElements(self, elements):
|
def _setDecorators(self, decorators: dict[str, Decorator]) -> None:
|
||||||
# TODO OPTI Concatenate consecutrive string
|
|
||||||
self.elements = list(elements)
|
|
||||||
|
|
||||||
def _setDecorators(self, decorators):
|
|
||||||
# TODO OPTI Convert no decorator to strings
|
# TODO OPTI Convert no decorator to strings
|
||||||
self.decorators = decorators
|
self.decorators = decorators
|
||||||
self.prefix = None
|
self.prefix: str | None = None
|
||||||
self.suffix = None
|
self.suffix: str | None = None
|
||||||
|
|
||||||
|
def __init__(self, *args: Element, **kwargs: Decorator) -> None:
|
||||||
|
# TODO OPTI Concatenate consecutrive string
|
||||||
|
self.elements = list(args)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self._setElements(args)
|
|
||||||
self._setDecorators(kwargs)
|
self._setDecorators(kwargs)
|
||||||
self.section = None
|
self.section: Section
|
||||||
|
|
||||||
def append(self, *args):
|
def append(self, *args: Element) -> None:
|
||||||
self._setElements(self.elements + list(args))
|
self.elements += list(args)
|
||||||
|
|
||||||
def prepend(self, *args):
|
def prepend(self, *args: Element) -> None:
|
||||||
self._setElements(list(args) + self.elements)
|
self.elements = list(args) + self.elements
|
||||||
|
|
||||||
def setElements(self, *args):
|
def setElements(self, *args: Element) -> None:
|
||||||
self._setElements(args)
|
self.elements = list(args)
|
||||||
|
|
||||||
def setDecorators(self, **kwargs):
|
def setDecorators(self, **kwargs: Decorator) -> None:
|
||||||
self._setDecorators(kwargs)
|
self._setDecorators(kwargs)
|
||||||
|
|
||||||
def setSection(self, section):
|
def setSection(self, section: Section) -> None:
|
||||||
assert isinstance(section, Section)
|
|
||||||
self.section = section
|
self.section = section
|
||||||
for element in self.elements:
|
for element in self.elements:
|
||||||
if isinstance(element, Text):
|
if isinstance(element, Text):
|
||||||
element.setSection(section)
|
element.setSection(section)
|
||||||
|
|
||||||
def _genFixs(self):
|
def _genFixs(self) -> None:
|
||||||
if self.prefix is not None and self.suffix is not None:
|
if self.prefix is not None and self.suffix is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.prefix = ""
|
self.prefix = ""
|
||||||
self.suffix = ""
|
self.suffix = ""
|
||||||
|
|
||||||
def nest(prefix, 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.prefix = self.prefix + "%{" + prefix + "}"
|
||||||
self.suffix = "%{" + suffix + "}" + self.suffix
|
self.suffix = "%{" + suffix + "}" + self.suffix
|
||||||
|
|
||||||
def getColor(val):
|
def getColor(val: str) -> str:
|
||||||
# TODO Allow themes
|
# TODO Allow themes
|
||||||
assert isinstance(val, str) and len(val) == 7
|
assert len(val) == 7
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def button(number, function):
|
def button(number: str, function: Handle) -> None:
|
||||||
handle = Bar.getFunctionHandle(function)
|
handle = Bar.getFunctionHandle(function)
|
||||||
nest("A" + number + ":" + handle.decode() + ":", "A" + number)
|
nest("A" + number + ":" + handle.decode() + ":", "A" + number)
|
||||||
|
|
||||||
|
@ -635,25 +655,34 @@ class Text:
|
||||||
continue
|
continue
|
||||||
if key == "fg":
|
if key == "fg":
|
||||||
reset = self.section.THEMES[self.section.theme][0]
|
reset = self.section.THEMES[self.section.theme][0]
|
||||||
|
assert isinstance(val, str)
|
||||||
nest("F" + getColor(val), "F" + reset)
|
nest("F" + getColor(val), "F" + reset)
|
||||||
elif key == "bg":
|
elif key == "bg":
|
||||||
reset = self.section.THEMES[self.section.theme][1]
|
reset = self.section.THEMES[self.section.theme][1]
|
||||||
|
assert isinstance(val, str)
|
||||||
nest("B" + getColor(val), "B" + reset)
|
nest("B" + getColor(val), "B" + reset)
|
||||||
elif key == "clickLeft":
|
elif key == "clickLeft":
|
||||||
|
assert callable(val)
|
||||||
button("1", val)
|
button("1", val)
|
||||||
elif key == "clickMiddle":
|
elif key == "clickMiddle":
|
||||||
|
assert callable(val)
|
||||||
button("2", val)
|
button("2", val)
|
||||||
elif key == "clickRight":
|
elif key == "clickRight":
|
||||||
|
assert callable(val)
|
||||||
button("3", val)
|
button("3", val)
|
||||||
elif key == "scrollUp":
|
elif key == "scrollUp":
|
||||||
|
assert callable(val)
|
||||||
button("4", val)
|
button("4", val)
|
||||||
elif key == "scrollDown":
|
elif key == "scrollDown":
|
||||||
|
assert callable(val)
|
||||||
button("5", val)
|
button("5", val)
|
||||||
else:
|
else:
|
||||||
log.warn("Unkown decorator: {}".format(key))
|
log.warn("Unkown decorator: {}".format(key))
|
||||||
|
|
||||||
def _text(self, size=None, pad=False):
|
def _text(self, size: int | None = None, pad: bool = False) -> tuple[str, int]:
|
||||||
self._genFixs()
|
self._genFixs()
|
||||||
|
assert self.prefix is not None
|
||||||
|
assert self.suffix is not None
|
||||||
curString = self.prefix
|
curString = self.prefix
|
||||||
curSize = 0
|
curSize = 0
|
||||||
remSize = size
|
remSize = size
|
||||||
|
@ -679,7 +708,9 @@ class Text:
|
||||||
|
|
||||||
curString += self.suffix
|
curString += self.suffix
|
||||||
|
|
||||||
if pad and remSize > 0:
|
if pad:
|
||||||
|
assert remSize is not None
|
||||||
|
if remSize > 0:
|
||||||
curString += " " * remSize
|
curString += " " * remSize
|
||||||
curSize += remSize
|
curSize += remSize
|
||||||
|
|
||||||
|
@ -690,12 +721,14 @@ class Text:
|
||||||
assert size >= curSize
|
assert size >= curSize
|
||||||
return curString, curSize
|
return curString, curSize
|
||||||
|
|
||||||
def text(self, *args, **kwargs):
|
def text(self, size: int | None = None, pad: bool = False) -> str:
|
||||||
string, size = self._text(*args, **kwargs)
|
string, size = self._text(size=size, pad=pad)
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
self._genFixs()
|
self._genFixs()
|
||||||
|
assert self.prefix is not None
|
||||||
|
assert self.suffix is not None
|
||||||
curString = self.prefix
|
curString = self.prefix
|
||||||
for element in self.elements:
|
for element in self.elements:
|
||||||
if element is None:
|
if element is None:
|
||||||
|
@ -705,7 +738,7 @@ class Text:
|
||||||
curString += self.suffix
|
curString += self.suffix
|
||||||
return curString
|
return curString
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
curSize = 0
|
curSize = 0
|
||||||
for element in self.elements:
|
for element in self.elements:
|
||||||
if element is None:
|
if element is None:
|
||||||
|
@ -716,8 +749,8 @@ class Text:
|
||||||
curSize += len(str(element))
|
curSize += len(str(element))
|
||||||
return curSize
|
return curSize
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index: int) -> Element:
|
||||||
return self.elements[index]
|
return self.elements[index]
|
||||||
|
|
||||||
def __setitem__(self, index, data):
|
def __setitem__(self, index: int, data: Element) -> None:
|
||||||
self.elements[index] = data
|
self.elements[index] = data
|
|
@ -1,21 +1,27 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import enum
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
|
import i3ipc
|
||||||
import mpd
|
import mpd
|
||||||
import notmuch
|
import notmuch
|
||||||
import psutil
|
import psutil
|
||||||
import pulsectl
|
import pulsectl
|
||||||
|
|
||||||
from frobar.display import *
|
from frobar.display import (ColorCountsSection, Element, Section,
|
||||||
from frobar.updaters import *
|
StatefulSection, Text)
|
||||||
|
from frobar.updaters import (I3Updater, InotifyUpdater, MergedUpdater,
|
||||||
|
PeriodicUpdater, ThreadedUpdater, Updater)
|
||||||
|
|
||||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
@ -24,21 +30,22 @@ log = logging.getLogger()
|
||||||
# PulseaudioProvider and MpdProvider)
|
# PulseaudioProvider and MpdProvider)
|
||||||
|
|
||||||
|
|
||||||
def humanSize(num):
|
def humanSize(numi: int) -> str:
|
||||||
"""
|
"""
|
||||||
Returns a string of width 3+3
|
Returns a string of width 3+3
|
||||||
"""
|
"""
|
||||||
|
num = float(numi)
|
||||||
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
|
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
|
||||||
if abs(num) < 1000:
|
if abs(num) < 1000:
|
||||||
if num >= 10:
|
if num >= 10:
|
||||||
return "{:3d}{}".format(int(num), unit)
|
return "{:3d}{}".format(int(num), unit)
|
||||||
else:
|
else:
|
||||||
return "{:.1f}{}".format(num, unit)
|
return "{:.1f}{}".format(num, unit)
|
||||||
num /= 1024.0
|
num /= 1024
|
||||||
return "{:d}YiB".format(num)
|
return "{:d}YiB".format(numi)
|
||||||
|
|
||||||
|
|
||||||
def randomColor(seed=0):
|
def randomColor(seed: int | bytes = 0) -> str:
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
return "#{:02x}{:02x}{:02x}".format(*[random.randint(0, 255) for _ in range(3)])
|
return "#{:02x}{:02x}{:02x}".format(*[random.randint(0, 255) for _ in range(3)])
|
||||||
|
|
||||||
|
@ -48,11 +55,11 @@ class TimeProvider(StatefulSection, PeriodicUpdater):
|
||||||
NUMBER_STATES = len(FORMATS)
|
NUMBER_STATES = len(FORMATS)
|
||||||
DEFAULT_STATE = 1
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> str:
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
return now.strftime(self.FORMATS[self.state])
|
return now.strftime(self.FORMATS[self.state])
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
StatefulSection.__init__(self, theme)
|
StatefulSection.__init__(self, theme)
|
||||||
self.changeInterval(1) # TODO OPTI When state < 1
|
self.changeInterval(1) # TODO OPTI When state < 1
|
||||||
|
@ -66,10 +73,10 @@ class AlertLevel(enum.Enum):
|
||||||
|
|
||||||
class AlertingSection(StatefulSection):
|
class AlertingSection(StatefulSection):
|
||||||
# TODO EASE Correct settings for themes
|
# TODO EASE Correct settings for themes
|
||||||
THEMES = {AlertLevel.NORMAL: 2, AlertLevel.WARNING: 3, AlertLevel.DANGER: 1}
|
ALERT_THEMES = {AlertLevel.NORMAL: 3, AlertLevel.WARNING: 1, AlertLevel.DANGER: 0}
|
||||||
PERSISTENT = True
|
PERSISTENT = True
|
||||||
|
|
||||||
def getLevel(self, quantity):
|
def getLevel(self, quantity: float) -> AlertLevel:
|
||||||
if quantity > self.dangerThresold:
|
if quantity > self.dangerThresold:
|
||||||
return AlertLevel.DANGER
|
return AlertLevel.DANGER
|
||||||
elif quantity > self.warningThresold:
|
elif quantity > self.warningThresold:
|
||||||
|
@ -77,14 +84,14 @@ class AlertingSection(StatefulSection):
|
||||||
else:
|
else:
|
||||||
return AlertLevel.NORMAL
|
return AlertLevel.NORMAL
|
||||||
|
|
||||||
def updateLevel(self, quantity):
|
def updateLevel(self, quantity: float) -> None:
|
||||||
self.level = self.getLevel(quantity)
|
self.level = self.getLevel(quantity)
|
||||||
self.updateTheme(self.THEMES[self.level])
|
self.updateTheme(self.ALERT_THEMES[self.level])
|
||||||
if self.level == AlertLevel.NORMAL:
|
if self.level == AlertLevel.NORMAL:
|
||||||
return
|
return
|
||||||
# TODO Temporary update state
|
# TODO Temporary update state
|
||||||
|
|
||||||
def __init__(self, theme):
|
def __init__(self, theme: int | None = None):
|
||||||
StatefulSection.__init__(self, theme)
|
StatefulSection.__init__(self, theme)
|
||||||
self.dangerThresold = 0.90
|
self.dangerThresold = 0.90
|
||||||
self.warningThresold = 0.75
|
self.warningThresold = 0.75
|
||||||
|
@ -92,9 +99,9 @@ class AlertingSection(StatefulSection):
|
||||||
|
|
||||||
class CpuProvider(AlertingSection, PeriodicUpdater):
|
class CpuProvider(AlertingSection, PeriodicUpdater):
|
||||||
NUMBER_STATES = 3
|
NUMBER_STATES = 3
|
||||||
ICON = ""
|
ICON = ""
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
percent = psutil.cpu_percent(percpu=False)
|
percent = psutil.cpu_percent(percpu=False)
|
||||||
self.updateLevel(percent / 100)
|
self.updateLevel(percent / 100)
|
||||||
if self.state >= 2:
|
if self.state >= 2:
|
||||||
|
@ -102,22 +109,44 @@ class CpuProvider(AlertingSection, PeriodicUpdater):
|
||||||
return "".join([Section.ramp(p / 100) for p in percents])
|
return "".join([Section.ramp(p / 100) for p in percents])
|
||||||
elif self.state >= 1:
|
elif self.state >= 1:
|
||||||
return Section.ramp(percent / 100)
|
return Section.ramp(percent / 100)
|
||||||
|
return ""
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
AlertingSection.__init__(self, theme)
|
AlertingSection.__init__(self, theme)
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(1)
|
self.changeInterval(1)
|
||||||
|
|
||||||
|
|
||||||
|
class LoadProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
NUMBER_STATES = 3
|
||||||
|
ICON = ""
|
||||||
|
|
||||||
|
def fetcher(self) -> Element:
|
||||||
|
load = os.getloadavg()
|
||||||
|
self.updateLevel(load[0])
|
||||||
|
if self.state >= 2:
|
||||||
|
return " ".join(f"{load[i]:.2f}" for i in range(3))
|
||||||
|
elif self.state >= 1:
|
||||||
|
return f"{load[0]:.2f}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def __init__(self, theme: int | None = None):
|
||||||
|
AlertingSection.__init__(self, theme)
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(5)
|
||||||
|
self.warningThresold = 5
|
||||||
|
self.dangerThresold = 10
|
||||||
|
|
||||||
|
|
||||||
class RamProvider(AlertingSection, PeriodicUpdater):
|
class RamProvider(AlertingSection, PeriodicUpdater):
|
||||||
"""
|
"""
|
||||||
Shows free RAM
|
Shows free RAM
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NUMBER_STATES = 4
|
NUMBER_STATES = 4
|
||||||
ICON = ""
|
ICON = ""
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
mem = psutil.virtual_memory()
|
mem = psutil.virtual_memory()
|
||||||
freePerc = mem.percent / 100
|
freePerc = mem.percent / 100
|
||||||
self.updateLevel(freePerc)
|
self.updateLevel(freePerc)
|
||||||
|
@ -135,7 +164,7 @@ class RamProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
AlertingSection.__init__(self, theme)
|
AlertingSection.__init__(self, theme)
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(1)
|
self.changeInterval(1)
|
||||||
|
@ -144,23 +173,28 @@ class RamProvider(AlertingSection, PeriodicUpdater):
|
||||||
class TemperatureProvider(AlertingSection, PeriodicUpdater):
|
class TemperatureProvider(AlertingSection, PeriodicUpdater):
|
||||||
NUMBER_STATES = 2
|
NUMBER_STATES = 2
|
||||||
RAMP = ""
|
RAMP = ""
|
||||||
|
MAIN_TEMPS = ["coretemp", "amdgpu", "cpu_thermal"]
|
||||||
|
# For Intel, AMD and ARM respectively.
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
allTemp = psutil.sensors_temperatures()
|
allTemp = psutil.sensors_temperatures()
|
||||||
if "coretemp" not in allTemp:
|
for main in self.MAIN_TEMPS:
|
||||||
# TODO Opti Remove interval
|
if main in allTemp:
|
||||||
return ""
|
break
|
||||||
temp = allTemp["coretemp"][0]
|
else:
|
||||||
|
return "?"
|
||||||
|
temp = allTemp[main][0]
|
||||||
|
|
||||||
self.warningThresold = temp.high
|
self.warningThresold = temp.high or 90.0
|
||||||
self.dangerThresold = temp.critical
|
self.dangerThresold = temp.critical or 100.0
|
||||||
self.updateLevel(temp.current)
|
self.updateLevel(temp.current)
|
||||||
|
|
||||||
self.icon = Section.ramp(temp.current / temp.high, self.RAMP)
|
self.icon = Section.ramp(temp.current / self.warningThresold, self.RAMP)
|
||||||
if self.state >= 1:
|
if self.state >= 1:
|
||||||
return "{:.0f}°C".format(temp.current)
|
return "{:.0f}°C".format(temp.current)
|
||||||
|
return ""
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
AlertingSection.__init__(self, theme)
|
AlertingSection.__init__(self, theme)
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(5)
|
self.changeInterval(5)
|
||||||
|
@ -171,10 +205,9 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
||||||
NUMBER_STATES = 3
|
NUMBER_STATES = 3
|
||||||
RAMP = ""
|
RAMP = ""
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
bat = psutil.sensors_battery()
|
bat = psutil.sensors_battery()
|
||||||
if not bat:
|
if not bat:
|
||||||
self.icon = None
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.icon = ("" if bat.power_plugged else "") + Section.ramp(
|
self.icon = ("" if bat.power_plugged else "") + Section.ramp(
|
||||||
|
@ -184,7 +217,7 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
||||||
self.updateLevel(1 - bat.percent / 100)
|
self.updateLevel(1 - bat.percent / 100)
|
||||||
|
|
||||||
if self.state < 1:
|
if self.state < 1:
|
||||||
return
|
return ""
|
||||||
|
|
||||||
t = Text("{:.0f}%".format(bat.percent))
|
t = Text("{:.0f}%".format(bat.percent))
|
||||||
|
|
||||||
|
@ -196,17 +229,38 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
||||||
t.append(" ({:d}:{:02d})".format(h, m))
|
t.append(" ({:d}:{:02d})".format(h, m))
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
AlertingSection.__init__(self, theme)
|
AlertingSection.__init__(self, theme)
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(5)
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class XautolockProvider(Section, InotifyUpdater):
|
||||||
|
ICON = ""
|
||||||
|
|
||||||
|
def fetcher(self) -> str | None:
|
||||||
|
with open(self.path) as fd:
|
||||||
|
state = fd.read().strip()
|
||||||
|
if state == "enabled":
|
||||||
|
return None
|
||||||
|
elif state == "disabled":
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return "?"
|
||||||
|
|
||||||
|
def __init__(self, theme: int | None = None):
|
||||||
|
Section.__init__(self, theme=theme)
|
||||||
|
InotifyUpdater.__init__(self)
|
||||||
|
# TODO XDG
|
||||||
|
self.path = os.path.realpath(os.path.expanduser("~/.cache/xautolock"))
|
||||||
|
self.addPath(self.path)
|
||||||
|
|
||||||
|
|
||||||
class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
||||||
NUMBER_STATES = 3
|
NUMBER_STATES = 3
|
||||||
DEFAULT_STATE = 1
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
ThreadedUpdater.__init__(self)
|
ThreadedUpdater.__init__(self)
|
||||||
StatefulSection.__init__(self, theme)
|
StatefulSection.__init__(self, theme)
|
||||||
self.pulseEvents = pulsectl.Pulse("event-handler")
|
self.pulseEvents = pulsectl.Pulse("event-handler")
|
||||||
|
@ -216,15 +270,21 @@ class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
||||||
self.start()
|
self.start()
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
sinks = []
|
sinks = []
|
||||||
with pulsectl.Pulse("list-sinks") as pulse:
|
with pulsectl.Pulse("list-sinks") as pulse:
|
||||||
for sink in pulse.sink_list():
|
for sink in pulse.sink_list():
|
||||||
if sink.port_active.name == "analog-output-headphones":
|
if (
|
||||||
|
sink.port_active.name == "analog-output-headphones"
|
||||||
|
or sink.port_active.description == "Headphones"
|
||||||
|
):
|
||||||
icon = ""
|
icon = ""
|
||||||
elif sink.port_active.name == "analog-output-speaker":
|
elif (
|
||||||
|
sink.port_active.name == "analog-output-speaker"
|
||||||
|
or sink.port_active.description == "Speaker"
|
||||||
|
):
|
||||||
icon = "" if sink.mute else ""
|
icon = "" if sink.mute else ""
|
||||||
elif sink.port_active.name == "headset-output":
|
elif sink.port_active.name in ("headset-output", "headphone-output"):
|
||||||
icon = ""
|
icon = ""
|
||||||
else:
|
else:
|
||||||
icon = "?"
|
icon = "?"
|
||||||
|
@ -249,10 +309,10 @@ class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
||||||
|
|
||||||
return Text(*sinks)
|
return Text(*sinks)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self) -> None:
|
||||||
self.pulseEvents.event_listen()
|
self.pulseEvents.event_listen()
|
||||||
|
|
||||||
def handleEvent(self, ev):
|
def handleEvent(self, ev: pulsectl.PulseEventInfo) -> None:
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
@ -260,7 +320,7 @@ class NetworkProviderSection(StatefulSection, Updater):
|
||||||
NUMBER_STATES = 5
|
NUMBER_STATES = 5
|
||||||
DEFAULT_STATE = 1
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
def actType(self):
|
def actType(self) -> None:
|
||||||
self.ssid = None
|
self.ssid = None
|
||||||
if self.iface.startswith("eth") or self.iface.startswith("enp"):
|
if self.iface.startswith("eth") or self.iface.startswith("enp"):
|
||||||
if "u" in self.iface:
|
if "u" in self.iface:
|
||||||
|
@ -281,10 +341,10 @@ class NetworkProviderSection(StatefulSection, Updater):
|
||||||
self.icon = ""
|
self.icon = ""
|
||||||
elif self.iface.startswith("vboxnet"):
|
elif self.iface.startswith("vboxnet"):
|
||||||
self.icon = ""
|
self.icon = ""
|
||||||
else:
|
|
||||||
self.icon = "?"
|
|
||||||
|
|
||||||
def getAddresses(self):
|
def getAddresses(
|
||||||
|
self,
|
||||||
|
) -> tuple[psutil._common.snicaddr, psutil._common.snicaddr]:
|
||||||
ipv4 = None
|
ipv4 = None
|
||||||
ipv6 = None
|
ipv6 = None
|
||||||
for address in self.parent.addrs[self.iface]:
|
for address in self.parent.addrs[self.iface]:
|
||||||
|
@ -294,8 +354,8 @@ class NetworkProviderSection(StatefulSection, Updater):
|
||||||
ipv6 = address
|
ipv6 = address
|
||||||
return ipv4, ipv6
|
return ipv4, ipv6
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
self.icon = None
|
self.icon = "?"
|
||||||
self.persistent = False
|
self.persistent = False
|
||||||
if (
|
if (
|
||||||
self.iface not in self.parent.stats
|
self.iface not in self.parent.stats
|
||||||
|
@ -349,13 +409,13 @@ class NetworkProviderSection(StatefulSection, Updater):
|
||||||
|
|
||||||
return " ".join(text)
|
return " ".join(text)
|
||||||
|
|
||||||
def onChangeState(self, state):
|
def onChangeState(self, state: int) -> None:
|
||||||
self.showSsid = state >= 1
|
self.showSsid = state >= 1
|
||||||
self.showAddress = state >= 2
|
self.showAddress = state >= 2
|
||||||
self.showSpeed = state >= 3
|
self.showSpeed = state >= 3
|
||||||
self.showTransfer = state >= 4
|
self.showTransfer = state >= 4
|
||||||
|
|
||||||
def __init__(self, iface, parent):
|
def __init__(self, iface: str, parent: "NetworkProvider"):
|
||||||
Updater.__init__(self)
|
Updater.__init__(self)
|
||||||
StatefulSection.__init__(self, theme=parent.theme)
|
StatefulSection.__init__(self, theme=parent.theme)
|
||||||
self.iface = iface
|
self.iface = iface
|
||||||
|
@ -363,23 +423,23 @@ class NetworkProviderSection(StatefulSection, Updater):
|
||||||
|
|
||||||
|
|
||||||
class NetworkProvider(Section, PeriodicUpdater):
|
class NetworkProvider(Section, PeriodicUpdater):
|
||||||
def fetchData(self):
|
def fetchData(self) -> None:
|
||||||
self.prev = self.last
|
self.prev = self.last
|
||||||
self.prevIO = self.IO
|
self.prevIO = self.IO
|
||||||
|
|
||||||
self.stats = psutil.net_if_stats()
|
self.stats = psutil.net_if_stats()
|
||||||
self.addrs = psutil.net_if_addrs()
|
self.addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs()
|
||||||
self.IO = psutil.net_io_counters(pernic=True)
|
self.IO: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True)
|
||||||
self.ifaces = self.stats.keys()
|
self.ifaces = self.stats.keys()
|
||||||
|
|
||||||
self.last = time.perf_counter()
|
self.last: float = time.perf_counter()
|
||||||
self.dt = self.last - self.prev
|
self.dt = self.last - self.prev
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> None:
|
||||||
self.fetchData()
|
self.fetchData()
|
||||||
|
|
||||||
# Add missing sections
|
# Add missing sections
|
||||||
lastSection = self
|
lastSection: NetworkProvider | NetworkProviderSection = self
|
||||||
for iface in sorted(list(self.ifaces)):
|
for iface in sorted(list(self.ifaces)):
|
||||||
if iface not in self.sections.keys():
|
if iface not in self.sections.keys():
|
||||||
section = NetworkProviderSection(iface, self)
|
section = NetworkProviderSection(iface, self)
|
||||||
|
@ -395,15 +455,11 @@ class NetworkProvider(Section, PeriodicUpdater):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def addParent(self, parent):
|
def __init__(self, theme: int | None = None):
|
||||||
self.parents.add(parent)
|
|
||||||
self.refreshData()
|
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
Section.__init__(self, theme)
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
self.sections = dict()
|
self.sections: dict[str, NetworkProviderSection] = dict()
|
||||||
self.last = 0
|
self.last = 0
|
||||||
self.IO = dict()
|
self.IO = dict()
|
||||||
self.fetchData()
|
self.fetchData()
|
||||||
|
@ -415,7 +471,7 @@ class RfkillProvider(Section, PeriodicUpdater):
|
||||||
# toggled
|
# toggled
|
||||||
PATH = "/sys/class/rfkill"
|
PATH = "/sys/class/rfkill"
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
t = Text()
|
t = Text()
|
||||||
for device in os.listdir(self.PATH):
|
for device in os.listdir(self.PATH):
|
||||||
with open(os.path.join(self.PATH, device, "soft"), "rb") as f:
|
with open(os.path.join(self.PATH, device, "soft"), "rb") as f:
|
||||||
|
@ -429,7 +485,7 @@ class RfkillProvider(Section, PeriodicUpdater):
|
||||||
with open(os.path.join(self.PATH, device, "type"), "rb") as f:
|
with open(os.path.join(self.PATH, device, "type"), "rb") as f:
|
||||||
typ = f.read().strip()
|
typ = f.read().strip()
|
||||||
|
|
||||||
fg = (hardBlocked and "#CCCCCC") or (softBlocked and "#FF0000")
|
fg = (hardBlocked and "#CCCCCC") or (softBlocked and "#FF0000") or None
|
||||||
if typ == b"wlan":
|
if typ == b"wlan":
|
||||||
icon = ""
|
icon = ""
|
||||||
elif typ == b"bluetooth":
|
elif typ == b"bluetooth":
|
||||||
|
@ -440,14 +496,14 @@ class RfkillProvider(Section, PeriodicUpdater):
|
||||||
t.append(Text(icon, fg=fg))
|
t.append(Text(icon, fg=fg))
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
Section.__init__(self, theme)
|
Section.__init__(self, theme)
|
||||||
self.changeInterval(5)
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
class SshAgentProvider(PeriodicUpdater):
|
class SshAgentProvider(PeriodicUpdater):
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
cmd = ["ssh-add", "-l"]
|
cmd = ["ssh-add", "-l"]
|
||||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
|
@ -460,13 +516,13 @@ class SshAgentProvider(PeriodicUpdater):
|
||||||
text.append(Text("", fg=randomColor(seed=fingerprint)))
|
text.append(Text("", fg=randomColor(seed=fingerprint)))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(5)
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
class GpgAgentProvider(PeriodicUpdater):
|
class GpgAgentProvider(PeriodicUpdater):
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
cmd = ["gpg-connect-agent", "keyinfo --list", "/bye"]
|
cmd = ["gpg-connect-agent", "keyinfo --list", "/bye"]
|
||||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
# proc = subprocess.run(cmd)
|
# proc = subprocess.run(cmd)
|
||||||
|
@ -483,7 +539,7 @@ class GpgAgentProvider(PeriodicUpdater):
|
||||||
text.append(Text("", fg=randomColor(seed=keygrip)))
|
text.append(Text("", fg=randomColor(seed=keygrip)))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
PeriodicUpdater.__init__(self)
|
PeriodicUpdater.__init__(self)
|
||||||
self.changeInterval(5)
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
@ -492,7 +548,7 @@ class KeystoreProvider(Section, MergedUpdater):
|
||||||
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless
|
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless
|
||||||
ICON = ""
|
ICON = ""
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
MergedUpdater.__init__(self, SshAgentProvider(), GpgAgentProvider())
|
MergedUpdater.__init__(self, SshAgentProvider(), GpgAgentProvider())
|
||||||
Section.__init__(self, theme)
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
@ -500,24 +556,21 @@ class KeystoreProvider(Section, MergedUpdater):
|
||||||
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater):
|
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater):
|
||||||
COLORABLE_ICON = ""
|
COLORABLE_ICON = ""
|
||||||
|
|
||||||
def subfetcher(self):
|
def subfetcher(self) -> list[tuple[int, str]]:
|
||||||
db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY, path=self.dir)
|
db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY, path=self.dir)
|
||||||
counts = []
|
counts = []
|
||||||
for account in self.accounts:
|
for account in self.accounts:
|
||||||
queryStr = "folder:/{}/ and tag:unread".format(account)
|
queryStr = "folder:/{}/ and tag:unread".format(account)
|
||||||
query = notmuch.Query(db, queryStr)
|
query = notmuch.Query(db, queryStr)
|
||||||
nbMsgs = query.count_messages()
|
nbMsgs = query.count_messages()
|
||||||
if account == "frogeye":
|
|
||||||
global q
|
|
||||||
q = query
|
|
||||||
if nbMsgs < 1:
|
if nbMsgs < 1:
|
||||||
continue
|
continue
|
||||||
counts.append((nbMsgs, self.colors[account]))
|
counts.append((nbMsgs, self.colors[account]))
|
||||||
# db.close()
|
# db.close()
|
||||||
return counts
|
return counts
|
||||||
|
|
||||||
def __init__(self, dir="~/.mail/", theme=None):
|
def __init__(self, dir: str = "~/.mail/", theme: int | None = None):
|
||||||
PeriodicUpdater.__init__(self)
|
InotifyUpdater.__init__(self)
|
||||||
ColorCountsSection.__init__(self, theme)
|
ColorCountsSection.__init__(self, theme)
|
||||||
|
|
||||||
self.dir = os.path.realpath(os.path.expanduser(dir))
|
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||||
|
@ -543,7 +596,7 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||||
# TODO OPT Specific callback for specific directory
|
# TODO OPT Specific callback for specific directory
|
||||||
COLORABLE_ICON = ""
|
COLORABLE_ICON = ""
|
||||||
|
|
||||||
def updateCalendarList(self):
|
def updateCalendarList(self) -> None:
|
||||||
calendars = sorted(os.listdir(self.dir))
|
calendars = sorted(os.listdir(self.dir))
|
||||||
for calendar in calendars:
|
for calendar in calendars:
|
||||||
# If the calendar wasn't in the list
|
# If the calendar wasn't in the list
|
||||||
|
@ -559,9 +612,9 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||||
path = os.path.join(self.dir, calendar, "color")
|
path = os.path.join(self.dir, calendar, "color")
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
self.colors[calendar] = f.read().strip()
|
self.colors[calendar] = f.read().strip()
|
||||||
self.calendars = calendars
|
self.calendars: list[str] = calendars
|
||||||
|
|
||||||
def __init__(self, dir, theme=None):
|
def __init__(self, dir: str, theme: int | None = None):
|
||||||
"""
|
"""
|
||||||
:parm str dir: [main]path value in todoman.conf
|
:parm str dir: [main]path value in todoman.conf
|
||||||
"""
|
"""
|
||||||
|
@ -571,12 +624,12 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||||
assert os.path.isdir(self.dir)
|
assert os.path.isdir(self.dir)
|
||||||
|
|
||||||
self.calendars = []
|
self.calendars = []
|
||||||
self.colors = dict()
|
self.colors: dict[str, str] = dict()
|
||||||
self.names = dict()
|
self.names: dict[str, str] = dict()
|
||||||
self.updateCalendarList()
|
self.updateCalendarList()
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
|
||||||
def countUndone(self, calendar):
|
def countUndone(self, calendar: str | None) -> int:
|
||||||
cmd = ["todo", "--porcelain", "list"]
|
cmd = ["todo", "--porcelain", "list"]
|
||||||
if calendar:
|
if calendar:
|
||||||
cmd.append(self.names[calendar])
|
cmd.append(self.names[calendar])
|
||||||
|
@ -584,7 +637,7 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||||
data = json.loads(proc.stdout)
|
data = json.loads(proc.stdout)
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
def subfetcher(self):
|
def subfetcher(self) -> list[tuple[int, str]]:
|
||||||
counts = []
|
counts = []
|
||||||
|
|
||||||
# TODO This an ugly optimisation that cuts on features, but todoman
|
# TODO This an ugly optimisation that cuts on features, but todoman
|
||||||
|
@ -609,124 +662,107 @@ class I3WindowTitleProvider(Section, I3Updater):
|
||||||
# TODO FEAT To make this available from start, we need to find the
|
# TODO FEAT To make this available from start, we need to find the
|
||||||
# `focused=True` element following the `focus` array
|
# `focused=True` element following the `focus` array
|
||||||
# TODO Feat Make this output dependant if wanted
|
# TODO Feat Make this output dependant if wanted
|
||||||
def on_window(self, i3, e):
|
def on_window(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
self.updateText(e.container.name)
|
self.updateText(e.container.name)
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
I3Updater.__init__(self)
|
I3Updater.__init__(self)
|
||||||
Section.__init__(self, theme=theme)
|
Section.__init__(self, theme=theme)
|
||||||
self.on("window", self.on_window)
|
self.on("window", self.on_window)
|
||||||
|
|
||||||
|
|
||||||
class I3WorkspacesProviderSection(Section):
|
class I3WorkspacesProviderSection(Section):
|
||||||
def selectTheme(self):
|
def selectTheme(self) -> int:
|
||||||
if self.urgent:
|
if self.workspace.urgent:
|
||||||
return self.parent.themeUrgent
|
return self.parent.themeUrgent
|
||||||
elif self.focused:
|
elif self.workspace.focused:
|
||||||
return self.parent.themeFocus
|
return self.parent.themeFocus
|
||||||
|
elif self.workspace.visible:
|
||||||
|
return self.parent.themeVisible
|
||||||
else:
|
else:
|
||||||
return self.parent.themeNormal
|
return self.parent.themeNormal
|
||||||
|
|
||||||
# TODO On mode change the state (shown / hidden) gets overriden so every
|
# TODO On mode change the state (shown / hidden) gets overriden so every
|
||||||
# tab is shown
|
# tab is shown
|
||||||
|
|
||||||
def show(self):
|
def show(self) -> None:
|
||||||
self.updateTheme(self.selectTheme())
|
self.updateTheme(self.selectTheme())
|
||||||
self.updateText(self.fullName if self.focused else self.shortName)
|
self.updateText(
|
||||||
|
self.fullName if self.workspace.focused else self.workspace.name
|
||||||
def changeState(self, focused, urgent):
|
|
||||||
self.focused = focused
|
|
||||||
self.urgent = urgent
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
def setName(self, name):
|
|
||||||
self.shortName = name
|
|
||||||
self.fullName = (
|
|
||||||
self.parent.customNames[name] if name in self.parent.customNames else name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def switchTo(self):
|
def switchTo(self) -> None:
|
||||||
self.parent.i3.command("workspace {}".format(self.shortName))
|
self.parent.i3.command("workspace {}".format(self.workspace.name))
|
||||||
|
|
||||||
def __init__(self, name, parent):
|
def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None:
|
||||||
|
self.workspace = workspace
|
||||||
|
self.fullName: str = self.parent.customNames.get(workspace.name, workspace.name)
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def __init__(self, parent: "I3WorkspacesProvider"):
|
||||||
Section.__init__(self)
|
Section.__init__(self)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setName(name)
|
|
||||||
self.setDecorators(clickLeft=self.switchTo)
|
self.setDecorators(clickLeft=self.switchTo)
|
||||||
self.tempText = None
|
self.tempText: Element = None
|
||||||
|
|
||||||
def empty(self):
|
def empty(self) -> None:
|
||||||
self.updateTheme(self.parent.themeNormal)
|
self.updateTheme(self.parent.themeNormal)
|
||||||
self.updateText(None)
|
self.updateText(None)
|
||||||
|
|
||||||
def tempShow(self):
|
def tempShow(self) -> None:
|
||||||
self.updateText(self.tempText)
|
self.updateText(self.tempText)
|
||||||
|
|
||||||
def tempEmpty(self):
|
def tempEmpty(self) -> None:
|
||||||
self.tempText = self.dstText[1]
|
self.tempText = self.dstText[1]
|
||||||
self.updateText(None)
|
self.updateText(None)
|
||||||
|
|
||||||
|
|
||||||
class I3WorkspacesProvider(Section, I3Updater):
|
class I3WorkspacesProvider(Section, I3Updater):
|
||||||
# TODO FEAT Multi-screen
|
|
||||||
|
|
||||||
def initialPopulation(self, parent):
|
def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None:
|
||||||
"""
|
section: Section | None = None
|
||||||
Called on init
|
lastSectionOnOutput = self.modeSection
|
||||||
Can't reuse addWorkspace since i3.get_workspaces() gives dict and not
|
highestNumOnOutput = -1
|
||||||
ConObjects
|
for sect in self.sections.values():
|
||||||
"""
|
if sect.workspace.num == workspace.num:
|
||||||
workspaces = self.i3.get_workspaces()
|
section = sect
|
||||||
lastSection = self.modeSection
|
break
|
||||||
for workspace in workspaces:
|
elif (
|
||||||
# if parent.display != workspace["display"]:
|
sect.workspace.num > highestNumOnOutput
|
||||||
# continue
|
and sect.workspace.num < workspace.num
|
||||||
|
and sect.workspace.output == workspace.output
|
||||||
section = I3WorkspacesProviderSection(workspace.name, self)
|
):
|
||||||
section.focused = workspace.focused
|
lastSectionOnOutput = sect
|
||||||
section.urgent = workspace.urgent
|
highestNumOnOutput = sect.workspace.num
|
||||||
section.show()
|
|
||||||
parent.addSectionAfter(lastSection, section)
|
|
||||||
self.sections[workspace.num] = section
|
|
||||||
|
|
||||||
lastSection = section
|
|
||||||
|
|
||||||
def on_workspace_init(self, i3, e):
|
|
||||||
workspace = e.current
|
|
||||||
i = workspace.num
|
|
||||||
if i in self.sections:
|
|
||||||
section = self.sections[i]
|
|
||||||
else:
|
else:
|
||||||
# Find the section just before
|
section = I3WorkspacesProviderSection(self)
|
||||||
while i not in self.sections.keys() and i > 0:
|
|
||||||
i -= 1
|
|
||||||
prevSection = self.sections[i] if i != 0 else self.modeSection
|
|
||||||
|
|
||||||
section = I3WorkspacesProviderSection(workspace.name, self)
|
|
||||||
prevSection.appendAfter(section)
|
|
||||||
self.sections[workspace.num] = section
|
self.sections[workspace.num] = section
|
||||||
section.focused = workspace.focused
|
|
||||||
section.urgent = workspace.urgent
|
|
||||||
section.show()
|
|
||||||
|
|
||||||
def on_workspace_empty(self, i3, e):
|
for bargroup in self.parents:
|
||||||
|
if bargroup.parent.output == workspace.output:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
bargroup = list(self.parents)[0]
|
||||||
|
bargroup.addSectionAfter(lastSectionOnOutput, section)
|
||||||
|
section.updateWorkspace(workspace)
|
||||||
|
|
||||||
|
def updateWorkspaces(self) -> None:
|
||||||
|
workspaces = self.i3.get_workspaces()
|
||||||
|
for workspace in workspaces:
|
||||||
|
self.updateWorkspace(workspace)
|
||||||
|
|
||||||
|
def added(self) -> None:
|
||||||
|
super().added()
|
||||||
|
self.appendAfter(self.modeSection)
|
||||||
|
self.updateWorkspaces()
|
||||||
|
|
||||||
|
def on_workspace_change(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
|
self.updateWorkspaces()
|
||||||
|
|
||||||
|
def on_workspace_empty(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
self.sections[e.current.num].empty()
|
self.sections[e.current.num].empty()
|
||||||
|
|
||||||
def on_workspace_focus(self, i3, e):
|
def on_mode(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||||
self.sections[e.old.num].focused = False
|
|
||||||
self.sections[e.old.num].show()
|
|
||||||
self.sections[e.current.num].focused = True
|
|
||||||
self.sections[e.current.num].show()
|
|
||||||
|
|
||||||
def on_workspace_urgent(self, i3, e):
|
|
||||||
self.sections[e.current.num].urgent = e.current.urgent
|
|
||||||
self.sections[e.current.num].show()
|
|
||||||
|
|
||||||
def on_workspace_rename(self, i3, e):
|
|
||||||
self.sections[e.current.num].setName(e.name)
|
|
||||||
self.sections[e.current.num].show()
|
|
||||||
|
|
||||||
def on_mode(self, i3, e):
|
|
||||||
if e.change == "default":
|
if e.change == "default":
|
||||||
self.modeSection.updateText(None)
|
self.modeSection.updateText(None)
|
||||||
for section in self.sections.values():
|
for section in self.sections.values():
|
||||||
|
@ -737,41 +773,46 @@ class I3WorkspacesProvider(Section, I3Updater):
|
||||||
section.tempEmpty()
|
section.tempEmpty()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, theme=0, themeFocus=3, themeUrgent=1, themeMode=2, customNames=dict()
|
self,
|
||||||
|
theme: int = 0,
|
||||||
|
themeVisible: int = 4,
|
||||||
|
themeFocus: int = 3,
|
||||||
|
themeUrgent: int = 1,
|
||||||
|
themeMode: int = 2,
|
||||||
|
customNames: dict[str, str] = dict(),
|
||||||
):
|
):
|
||||||
I3Updater.__init__(self)
|
I3Updater.__init__(self)
|
||||||
Section.__init__(self)
|
Section.__init__(self)
|
||||||
self.themeNormal = theme
|
self.themeNormal = theme
|
||||||
self.themeFocus = themeFocus
|
self.themeFocus = themeFocus
|
||||||
self.themeUrgent = themeUrgent
|
self.themeUrgent = themeUrgent
|
||||||
|
self.themeVisible = themeVisible
|
||||||
self.customNames = customNames
|
self.customNames = customNames
|
||||||
|
|
||||||
self.sections = dict()
|
self.sections: dict[int, I3WorkspacesProviderSection] = dict()
|
||||||
self.on("workspace::init", self.on_workspace_init)
|
# The event object doesn't have the visible property,
|
||||||
self.on("workspace::focus", self.on_workspace_focus)
|
# so we have to fetch the list of workspaces anyways.
|
||||||
|
# This sacrifices a bit of performance for code simplicity.
|
||||||
|
self.on("workspace::init", self.on_workspace_change)
|
||||||
|
self.on("workspace::focus", self.on_workspace_change)
|
||||||
self.on("workspace::empty", self.on_workspace_empty)
|
self.on("workspace::empty", self.on_workspace_empty)
|
||||||
self.on("workspace::urgent", self.on_workspace_urgent)
|
self.on("workspace::urgent", self.on_workspace_change)
|
||||||
self.on("workspace::rename", self.on_workspace_rename)
|
self.on("workspace::rename", self.on_workspace_change)
|
||||||
# TODO Un-handled/tested: reload, rename, restored, move
|
# TODO Un-handled/tested: reload, rename, restored, move
|
||||||
|
|
||||||
self.on("mode", self.on_mode)
|
self.on("mode", self.on_mode)
|
||||||
self.modeSection = Section(theme=themeMode)
|
self.modeSection = Section(theme=themeMode)
|
||||||
|
|
||||||
def addParent(self, parent):
|
|
||||||
self.parents.add(parent)
|
|
||||||
parent.addSection(self.modeSection)
|
|
||||||
self.initialPopulation(parent)
|
|
||||||
|
|
||||||
|
|
||||||
class MpdProvider(Section, ThreadedUpdater):
|
class MpdProvider(Section, ThreadedUpdater):
|
||||||
# TODO FEAT More informations and controls
|
# TODO FEAT More informations and controls
|
||||||
|
|
||||||
MAX_LENGTH = 50
|
MAX_LENGTH = 50
|
||||||
|
|
||||||
def connect(self):
|
def connect(self) -> None:
|
||||||
self.mpd.connect("localhost", 6600)
|
self.mpd.connect("localhost", 6600)
|
||||||
|
|
||||||
def __init__(self, theme=None):
|
def __init__(self, theme: int | None = None):
|
||||||
ThreadedUpdater.__init__(self)
|
ThreadedUpdater.__init__(self)
|
||||||
Section.__init__(self, theme)
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
@ -780,7 +821,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
stat = self.mpd.status()
|
stat = self.mpd.status()
|
||||||
if not len(stat) or stat["state"] == "stop":
|
if not len(stat) or stat["state"] == "stop":
|
||||||
return None
|
return None
|
||||||
|
@ -791,7 +832,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
||||||
|
|
||||||
infos = []
|
infos = []
|
||||||
|
|
||||||
def tryAdd(field):
|
def tryAdd(field: str) -> None:
|
||||||
if field in cur:
|
if field in cur:
|
||||||
infos.append(cur[field])
|
infos.append(cur[field])
|
||||||
|
|
||||||
|
@ -805,7 +846,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
||||||
|
|
||||||
return " {}".format(infosStr)
|
return " {}".format(infosStr)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self) -> None:
|
||||||
try:
|
try:
|
||||||
self.mpd.idle("player")
|
self.mpd.idle("player")
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
@ -814,3 +855,104 @@ class MpdProvider(Section, ThreadedUpdater):
|
||||||
self.connect()
|
self.connect()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
class MprisProviderSection(Section, Updater):
|
||||||
|
def __init__(self, parent: "MprisProvider"):
|
||||||
|
Updater.__init__(self)
|
||||||
|
Section.__init__(self, theme=parent.theme)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
|
||||||
|
class MprisProvider(Section, ThreadedUpdater):
|
||||||
|
# TODO Controls (select player at least)
|
||||||
|
# TODO Use the Python native thing for it:
|
||||||
|
# https://github.com/altdesktop/playerctl?tab=readme-ov-file#using-the-library
|
||||||
|
# TODO Make it less sucky
|
||||||
|
|
||||||
|
SECTIONS = [
|
||||||
|
"{{ playerName }} {{ status }}",
|
||||||
|
"{{ album }}",
|
||||||
|
"{{ artist }}",
|
||||||
|
"{{ duration(position) }}/{{ duration(mpris:length) }}" " {{ title }}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# nf-fd icons don't work (UTF-16?)
|
||||||
|
SUBSTITUTIONS = {
|
||||||
|
"Playing": "",
|
||||||
|
"Paused": "",
|
||||||
|
"Stopped": "",
|
||||||
|
"mpd": "",
|
||||||
|
"firefox": "",
|
||||||
|
"chromium": "",
|
||||||
|
"mpv": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
ICONS = {
|
||||||
|
1: "",
|
||||||
|
2: "",
|
||||||
|
3: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_SECTION_LENGTH = 40
|
||||||
|
|
||||||
|
def __init__(self, theme: int | None = None):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
self.line = ""
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
self.sections: list[Section] = []
|
||||||
|
|
||||||
|
def fetcher(self) -> Element:
|
||||||
|
create = not len(self.sections)
|
||||||
|
populate = self.line
|
||||||
|
split = self.line.split("\t")
|
||||||
|
|
||||||
|
lastSection: Section = self
|
||||||
|
for i in range(len(self.SECTIONS)):
|
||||||
|
if create:
|
||||||
|
section = Section(theme=self.theme)
|
||||||
|
lastSection.appendAfter(section)
|
||||||
|
lastSection = section
|
||||||
|
self.sections.append(section)
|
||||||
|
else:
|
||||||
|
section = self.sections[i]
|
||||||
|
|
||||||
|
if populate:
|
||||||
|
text = split[i]
|
||||||
|
if i == 0:
|
||||||
|
for key, val in self.SUBSTITUTIONS.items():
|
||||||
|
text = text.replace(key, val)
|
||||||
|
if text:
|
||||||
|
if i in self.ICONS:
|
||||||
|
text = f"{self.ICONS[i]} {text}"
|
||||||
|
if len(text) > self.MAX_SECTION_LENGTH:
|
||||||
|
text = text[: self.MAX_SECTION_LENGTH - 1] + "…"
|
||||||
|
section.updateText(text)
|
||||||
|
else:
|
||||||
|
section.updateText(None)
|
||||||
|
else:
|
||||||
|
section.updateText(None)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def loop(self) -> None:
|
||||||
|
cmd = [
|
||||||
|
"playerctl",
|
||||||
|
"metadata",
|
||||||
|
"--format",
|
||||||
|
"\t".join(self.SECTIONS),
|
||||||
|
"--follow",
|
||||||
|
]
|
||||||
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||||
|
assert p.stdout
|
||||||
|
while p.poll() is None:
|
||||||
|
self.line = p.stdout.readline().decode().strip()
|
||||||
|
self.refreshData()
|
||||||
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||||
|
assert p.stdout
|
||||||
|
while p.poll() is None:
|
||||||
|
self.line = p.stdout.readline().decode().strip()
|
||||||
|
self.refreshData()
|
|
@ -4,6 +4,7 @@ import functools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -11,8 +12,8 @@ import coloredlogs
|
||||||
import i3ipc
|
import i3ipc
|
||||||
import pyinotify
|
import pyinotify
|
||||||
|
|
||||||
from frobar.display import Text
|
from frobar.common import notBusy
|
||||||
from frobar.notbusy import notBusy
|
from frobar.display import Element
|
||||||
|
|
||||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
@ -20,24 +21,23 @@ log = logging.getLogger()
|
||||||
# TODO Sync bar update with PeriodicUpdater updates
|
# TODO Sync bar update with PeriodicUpdater updates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Updater:
|
class Updater:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init() -> None:
|
||||||
PeriodicUpdater.init()
|
PeriodicUpdater.init()
|
||||||
InotifyUpdater.init()
|
InotifyUpdater.init()
|
||||||
notBusy.set()
|
notBusy.set()
|
||||||
|
|
||||||
def updateText(self, text):
|
def updateText(self, text: Element) -> None:
|
||||||
print(text)
|
print(text)
|
||||||
|
|
||||||
def fetcher(self):
|
def fetcher(self) -> Element:
|
||||||
return "{} refreshed".format(self)
|
return "{} refreshed".format(self)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
def refreshData(self):
|
def refreshData(self) -> None:
|
||||||
# TODO OPTI Maybe discard the refresh if there's already another one?
|
# TODO OPTI Maybe discard the refresh if there's already another one?
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
try:
|
try:
|
||||||
|
@ -50,7 +50,7 @@ class Updater:
|
||||||
|
|
||||||
|
|
||||||
class PeriodicUpdaterThread(threading.Thread):
|
class PeriodicUpdaterThread(threading.Thread):
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
# TODO Sync with system clock
|
# TODO Sync with system clock
|
||||||
counter = 0
|
counter = 0
|
||||||
while True:
|
while True:
|
||||||
|
@ -67,6 +67,7 @@ class PeriodicUpdaterThread(threading.Thread):
|
||||||
provider.refreshData()
|
provider.refreshData()
|
||||||
else:
|
else:
|
||||||
notBusy.clear()
|
notBusy.clear()
|
||||||
|
assert PeriodicUpdater.intervalStep is not None
|
||||||
counter += PeriodicUpdater.intervalStep
|
counter += PeriodicUpdater.intervalStep
|
||||||
counter = counter % PeriodicUpdater.intervalLoop
|
counter = counter % PeriodicUpdater.intervalLoop
|
||||||
for interval in PeriodicUpdater.intervals.keys():
|
for interval in PeriodicUpdater.intervals.keys():
|
||||||
|
@ -80,43 +81,42 @@ class PeriodicUpdater(Updater):
|
||||||
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
intervals = dict()
|
intervals: dict[int, set["PeriodicUpdater"]] = dict()
|
||||||
intervalStep = None
|
intervalStep: int | None = None
|
||||||
intervalLoop = None
|
intervalLoop: int
|
||||||
updateThread = PeriodicUpdaterThread(daemon=True)
|
updateThread: threading.Thread = PeriodicUpdaterThread(daemon=True)
|
||||||
intervalsChanged = threading.Event()
|
intervalsChanged = threading.Event()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def gcds(*args):
|
def gcds(*args: int) -> int:
|
||||||
return functools.reduce(math.gcd, args)
|
return functools.reduce(math.gcd, args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lcm(a, b):
|
def lcm(a: int, b: int) -> int:
|
||||||
"""Return lowest common multiple."""
|
"""Return lowest common multiple."""
|
||||||
return a * b // math.gcd(a, b)
|
return a * b // math.gcd(a, b)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lcms(*args):
|
def lcms(*args: int) -> int:
|
||||||
"""Return lowest common multiple."""
|
"""Return lowest common multiple."""
|
||||||
return functools.reduce(PeriodicUpdater.lcm, args)
|
return functools.reduce(PeriodicUpdater.lcm, args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def updateIntervals():
|
def updateIntervals() -> None:
|
||||||
intervalsList = list(PeriodicUpdater.intervals.keys())
|
intervalsList = list(PeriodicUpdater.intervals.keys())
|
||||||
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
||||||
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
||||||
PeriodicUpdater.intervalsChanged.set()
|
PeriodicUpdater.intervalsChanged.set()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init() -> None:
|
||||||
PeriodicUpdater.updateThread.start()
|
PeriodicUpdater.updateThread.start()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
Updater.__init__(self)
|
Updater.__init__(self)
|
||||||
self.interval = None
|
self.interval: int | None = None
|
||||||
|
|
||||||
def changeInterval(self, interval):
|
def changeInterval(self, interval: int) -> None:
|
||||||
assert isinstance(interval, int)
|
|
||||||
|
|
||||||
if self.interval is not None:
|
if self.interval is not None:
|
||||||
PeriodicUpdater.intervals[self.interval].remove(self)
|
PeriodicUpdater.intervals[self.interval].remove(self)
|
||||||
|
@ -131,12 +131,7 @@ class PeriodicUpdater(Updater):
|
||||||
|
|
||||||
|
|
||||||
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
||||||
def process_default(self, event):
|
def process_default(self, event: pyinotify.Event) -> None:
|
||||||
# DEBUG
|
|
||||||
# from pprint import pprint
|
|
||||||
# pprint(event.__dict__)
|
|
||||||
# return
|
|
||||||
|
|
||||||
assert event.path in InotifyUpdater.paths
|
assert event.path in InotifyUpdater.paths
|
||||||
|
|
||||||
if 0 in InotifyUpdater.paths[event.path]:
|
if 0 in InotifyUpdater.paths[event.path]:
|
||||||
|
@ -154,10 +149,10 @@ class InotifyUpdater(Updater):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
wm = pyinotify.WatchManager()
|
wm = pyinotify.WatchManager()
|
||||||
paths = dict()
|
paths: dict[str, dict[str | int, set["InotifyUpdater"]]] = dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init() -> None:
|
||||||
notifier = pyinotify.ThreadedNotifier(
|
notifier = pyinotify.ThreadedNotifier(
|
||||||
InotifyUpdater.wm, InotifyUpdaterEventHandler()
|
InotifyUpdater.wm, InotifyUpdaterEventHandler()
|
||||||
)
|
)
|
||||||
|
@ -166,14 +161,14 @@ class InotifyUpdater(Updater):
|
||||||
# TODO Mask for folders
|
# TODO Mask for folders
|
||||||
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
||||||
|
|
||||||
def addPath(self, path, refresh=True):
|
def addPath(self, path: str, refresh: bool = True) -> None:
|
||||||
path = os.path.realpath(os.path.expanduser(path))
|
path = os.path.realpath(os.path.expanduser(path))
|
||||||
|
|
||||||
# Detect if file or folder
|
# Detect if file or folder
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
self.dirpath = path
|
self.dirpath: str = path
|
||||||
# 0: Directory watcher
|
# 0: Directory watcher
|
||||||
self.filename = 0
|
self.filename: str | int = 0
|
||||||
elif os.path.isfile(path):
|
elif os.path.isfile(path):
|
||||||
self.dirpath = os.path.dirname(path)
|
self.dirpath = os.path.dirname(path)
|
||||||
self.filename = os.path.basename(path)
|
self.filename = os.path.basename(path)
|
||||||
|
@ -195,12 +190,12 @@ class InotifyUpdater(Updater):
|
||||||
|
|
||||||
|
|
||||||
class ThreadedUpdaterThread(threading.Thread):
|
class ThreadedUpdaterThread(threading.Thread):
|
||||||
def __init__(self, updater, *args, **kwargs):
|
def __init__(self, updater: "ThreadedUpdater") -> None:
|
||||||
self.updater = updater
|
self.updater = updater
|
||||||
threading.Thread.__init__(self, *args, **kwargs)
|
threading.Thread.__init__(self, daemon=True)
|
||||||
self.looping = True
|
self.looping = True
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
try:
|
try:
|
||||||
while self.looping:
|
while self.looping:
|
||||||
self.updater.loop()
|
self.updater.loop()
|
||||||
|
@ -215,57 +210,31 @@ class ThreadedUpdater(Updater):
|
||||||
Must implement loop(), and call start()
|
Must implement loop(), and call start()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
Updater.__init__(self)
|
Updater.__init__(self)
|
||||||
self.thread = ThreadedUpdaterThread(self, daemon=True)
|
self.thread = ThreadedUpdaterThread(self)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self) -> None:
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
|
|
||||||
class I3Updater(ThreadedUpdater):
|
class I3Updater(ThreadedUpdater):
|
||||||
# TODO OPTI One i3 connection for all
|
# TODO OPTI One i3 connection for all
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
ThreadedUpdater.__init__(self)
|
ThreadedUpdater.__init__(self)
|
||||||
self.i3 = i3ipc.Connection()
|
self.i3 = i3ipc.Connection()
|
||||||
|
self.on = self.i3.on
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def on(self, event, function):
|
def loop(self) -> None:
|
||||||
self.i3.on(event, function)
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
self.i3.main()
|
self.i3.main()
|
||||||
|
|
||||||
|
|
||||||
class MergedUpdater(Updater):
|
class MergedUpdater(Updater):
|
||||||
# TODO OPTI Do not update until end of periodic batch
|
def __init__(self, *args: Updater) -> None:
|
||||||
def fetcher(self):
|
raise NotImplementedError("Deprecated, as hacky and currently unused")
|
||||||
text = Text()
|
|
||||||
for updater in self.updaters:
|
|
||||||
text.append(self.texts[updater])
|
|
||||||
if not len(text):
|
|
||||||
return None
|
|
||||||
return text
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
Updater.__init__(self)
|
|
||||||
|
|
||||||
self.updaters = []
|
|
||||||
self.texts = dict()
|
|
||||||
|
|
||||||
for updater in args:
|
|
||||||
assert isinstance(updater, Updater)
|
|
||||||
|
|
||||||
def newUpdateText(updater, text):
|
|
||||||
self.texts[updater] = text
|
|
||||||
self.refreshData()
|
|
||||||
|
|
||||||
updater.updateText = newUpdateText.__get__(updater, Updater)
|
|
||||||
|
|
||||||
self.updaters.append(updater)
|
|
||||||
self.texts[updater] = ""
|
|
25
hm/desktop/frobar/module.nix
Normal file
25
hm/desktop/frobar/module.nix
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
xsession.windowManager.i3.config.bars = [ ];
|
||||||
|
programs.autorandr.hooks.postswitch = {
|
||||||
|
frobar = "${pkgs.systemd}/bin/systemctl --user restart frobar";
|
||||||
|
};
|
||||||
|
systemd.user.services.frobar = {
|
||||||
|
Unit = {
|
||||||
|
Description = "frobar";
|
||||||
|
After = [ "graphical-session-pre.target" ];
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
# Wait for i3 to start. Can't use ExecStartPre because otherwise it blocks graphical-session.target, and there's nothing i3/systemd
|
||||||
|
# TODO Do that better
|
||||||
|
ExecStart = ''${pkgs.bash}/bin/bash -c "while ! ${pkgs.i3}/bin/i3-msg; do ${pkgs.coreutils}/bin/sleep 1; done; ${pkgs.callPackage ./. {}}/bin/frobar"'';
|
||||||
|
};
|
||||||
|
|
||||||
|
Install = { WantedBy = [ "graphical-session.target" ]; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
# TODO Connection with i3 is lost on start sometimes, more often than with Arch?
|
218
hm/desktop/i3.nix
Normal file
218
hm/desktop/i3.nix
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
# FOCUS
|
||||||
|
focus = "exec ${ pkgs.writeShellScript "i3-focus-window" ''
|
||||||
|
WINDOW=`${pkgs.xdotool}/bin/xdotool getwindowfocus`
|
||||||
|
eval `${pkgs.xdotool}/bin/xdotool getwindowgeometry --shell $WINDOW` # this brings in variables WIDTH and HEIGHT
|
||||||
|
TX=`${pkgs.coreutils}/bin/expr $WIDTH / 2`
|
||||||
|
TY=`${pkgs.coreutils}/bin/expr $HEIGHT / 2`
|
||||||
|
${pkgs.xdotool}/bin/xdotool mousemove -window $WINDOW $TX $TY
|
||||||
|
''
|
||||||
|
}";
|
||||||
|
|
||||||
|
# CARDINALS
|
||||||
|
cardinals = [
|
||||||
|
{ vi = "h"; arrow = "Left"; container = "left"; workspace = "prev_on_output"; output = "left"; }
|
||||||
|
{ vi = "l"; arrow = "Right"; container = "right"; workspace = "next_on_output"; output = "right"; }
|
||||||
|
{ vi = "j"; arrow = "Down"; container = "down"; workspace = "prev"; output = "below"; }
|
||||||
|
{ vi = "k"; arrow = "Up"; container = "up"; workspace = "next"; output = "above"; }
|
||||||
|
];
|
||||||
|
forEachCardinal = f: map (c: f c) cardinals;
|
||||||
|
|
||||||
|
# WORKSPACES
|
||||||
|
workspaces_keys = lib.strings.stringToCharacters "1234567890";
|
||||||
|
workspaces = map
|
||||||
|
(i: {
|
||||||
|
id = i;
|
||||||
|
name = builtins.toString (i + 1);
|
||||||
|
key = builtins.elemAt workspaces_keys i;
|
||||||
|
})
|
||||||
|
(lib.lists.range 0 ((builtins.length workspaces_keys) - 1));
|
||||||
|
forEachWorkspace = f: map (w: f w) workspaces;
|
||||||
|
|
||||||
|
# MISC
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
rofi = "exec --no-startup-id ${config.programs.rofi.package}/bin/rofi";
|
||||||
|
modes = config.frogeye.desktop.i3.bindmodes;
|
||||||
|
x11_screens = config.frogeye.desktop.x11_screens;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.xsession.windowManager.i3.enable {
|
||||||
|
stylix.targets.i3.enable = false;
|
||||||
|
xdg.configFile = {
|
||||||
|
"rofimoji.rc" = {
|
||||||
|
text = ''
|
||||||
|
skin-tone = neutral
|
||||||
|
files = [emojis, math]
|
||||||
|
action = clipboard
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config = {
|
||||||
|
modifier = lib.mkDefault "Mod4";
|
||||||
|
fonts = {
|
||||||
|
names = [ config.stylix.fonts.sansSerif.name ];
|
||||||
|
};
|
||||||
|
terminal = "alacritty";
|
||||||
|
colors = let ignore = "#ff00ff"; in
|
||||||
|
with config.lib.stylix.colors.withHashtag; lib.mkForce {
|
||||||
|
focused = { border = base0B; background = base0B; text = base00; indicator = base00; childBorder = base0B; };
|
||||||
|
focusedInactive = { border = base02; background = base02; text = base05; indicator = base02; childBorder = base02; };
|
||||||
|
unfocused = { border = base05; background = base04; text = base00; indicator = base04; childBorder = base00; };
|
||||||
|
urgent = { border = base0F; background = base08; text = base00; indicator = base08; childBorder = base0F; };
|
||||||
|
placeholder = { border = ignore; background = base00; text = base05; indicator = ignore; childBorder = base00; };
|
||||||
|
background = base07;
|
||||||
|
# I set the color of the active tab as the the background color of the terminal so they merge together.
|
||||||
|
};
|
||||||
|
focus.followMouse = false;
|
||||||
|
keybindings =
|
||||||
|
{
|
||||||
|
# Compatibility layer for people coming from other backgrounds
|
||||||
|
"Mod1+Tab" = "${rofi} -modi window -show window";
|
||||||
|
"Mod1+F2" = "${rofi} -modi drun -show drun";
|
||||||
|
"Mod1+F4" = "kill";
|
||||||
|
# kill focused window
|
||||||
|
"${mod}+z" = "kill";
|
||||||
|
button2 = "kill";
|
||||||
|
# Rofi
|
||||||
|
"${mod}+i" = "exec --no-startup-id ${pkgs.rofimoji}/bin/rofimoji";
|
||||||
|
# start program launcher
|
||||||
|
"${mod}+d" = "${rofi} -modi run -show run";
|
||||||
|
"${mod}+Shift+d" = "${rofi} -modi drun -show drun";
|
||||||
|
# Start Applications
|
||||||
|
"${mod}+p" = "exec ${pkgs.xfce.thunar}/bin/thunar";
|
||||||
|
# Misc
|
||||||
|
"${mod}+F10" = "exec ${ pkgs.writeShellScript "show-keyboard-layout"
|
||||||
|
''
|
||||||
|
layout=`${pkgs.xorg.setxkbmap}/bin/setxkbmap -query | ${pkgs.gnugrep}/bin/grep ^layout: | ${pkgs.gawk}/bin/awk '{ print $2 }'`
|
||||||
|
${pkgs.libgnomekbd}/bin/gkbd-keyboard-display -l $layout
|
||||||
|
''
|
||||||
|
}";
|
||||||
|
# workspace back and forth (with/without active container)
|
||||||
|
"${mod}+b" = "workspace back_and_forth; ${focus}";
|
||||||
|
"${mod}+Shift+b" = "move container to workspace back_and_forth; workspace back_and_forth; ${focus}";
|
||||||
|
# Change container layout
|
||||||
|
"${mod}+g" = "split h; ${focus}";
|
||||||
|
"${mod}+v" = "split v; ${focus}";
|
||||||
|
"${mod}+f" = "fullscreen toggle; ${focus}";
|
||||||
|
"${mod}+s" = "layout stacking; ${focus}";
|
||||||
|
"${mod}+w" = "layout tabbed; ${focus}";
|
||||||
|
"${mod}+e" = "layout toggle split; ${focus}";
|
||||||
|
"${mod}+Shift+space" = "floating toggle; ${focus}";
|
||||||
|
# Focus container
|
||||||
|
"${mod}+space" = "focus mode_toggle; ${focus}";
|
||||||
|
"${mod}+a" = "focus parent; ${focus}";
|
||||||
|
"${mod}+q" = "focus child; ${focus}";
|
||||||
|
# i3 control
|
||||||
|
"${mod}+Shift+c" = "reload";
|
||||||
|
"${mod}+Shift+r" = "restart";
|
||||||
|
"${mod}+Shift+e" = "exit";
|
||||||
|
} // lib.mapAttrs' (k: v: lib.nameValuePair v.enter "mode ${v.name}") (lib.filterAttrs (k: v: v.enter != null) modes)
|
||||||
|
// lib.attrsets.mergeAttrsList (forEachCardinal (c: {
|
||||||
|
# change focus
|
||||||
|
"${mod}+${c.vi}" = "focus ${c.container}; ${focus}";
|
||||||
|
# move focused window
|
||||||
|
"${mod}+Shift+${c.vi}" = "move ${c.container}; ${focus}";
|
||||||
|
#navigate workspaces next / previous
|
||||||
|
"${mod}+Ctrl+${c.vi}" = "workspace ${c.workspace}; ${focus}";
|
||||||
|
# Move to workspace next / previous with focused container
|
||||||
|
"${mod}+Ctrl+Shift+${c.vi}" = "move container to workspace ${c.workspace}; workspace ${c.workspace}; ${focus}";
|
||||||
|
# move workspaces to screen (arrow keys)
|
||||||
|
"${mod}+Ctrl+Shift+${c.arrow}" = "move workspace to output ${c.output}; ${focus}";
|
||||||
|
})) // lib.attrsets.mergeAttrsList (forEachWorkspace (w: {
|
||||||
|
# Switch to workspace
|
||||||
|
"${mod}+${w.key}" = "workspace ${w.name}; ${focus}";
|
||||||
|
# move focused container to workspace
|
||||||
|
"${mod}+ctrl+${w.key}" = "move container to workspace ${w.name}; ${focus}";
|
||||||
|
# move to workspace with focused container
|
||||||
|
"${mod}+shift+${w.key}" = "move container to workspace ${w.name}; workspace ${w.name}; ${focus}";
|
||||||
|
}));
|
||||||
|
modes = lib.mapAttrs'
|
||||||
|
(k: v: lib.nameValuePair v.name (v.bindings // lib.optionalAttrs v.return_bindings {
|
||||||
|
"Return" = "mode default";
|
||||||
|
"Escape" = "mode default";
|
||||||
|
}))
|
||||||
|
modes;
|
||||||
|
window = {
|
||||||
|
hideEdgeBorders = "both";
|
||||||
|
titlebar = false; # So that single-container screens are basically almost fullscreen
|
||||||
|
commands = [
|
||||||
|
# switch to workspace with urgent window automatically
|
||||||
|
{ criteria = { urgent = "latest"; }; command = "focus"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
floating = {
|
||||||
|
criteria = [
|
||||||
|
{ window_role = "pop-up"; }
|
||||||
|
{ window_role = "task_dialog"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
startup = [
|
||||||
|
{
|
||||||
|
notification = false;
|
||||||
|
command = "${pkgs.writeShellApplication {
|
||||||
|
name = "batteryNotify";
|
||||||
|
runtimeInputs = with pkgs; [coreutils libnotify];
|
||||||
|
text = builtins.readFile ./batteryNotify.sh;
|
||||||
|
# TODO Use batsignal instead?
|
||||||
|
# TODO Only on computers with battery
|
||||||
|
}}/bin/batteryNotify";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
workspaceLayout = "tabbed";
|
||||||
|
focus.mouseWarping = true; # i3 only supports warping to workspace, hence ${focus}
|
||||||
|
workspaceOutputAssign =
|
||||||
|
forEachWorkspace (w: { output = builtins.elemAt x11_screens (lib.mod w.id (builtins.length x11_screens)); workspace = w.name; });
|
||||||
|
};
|
||||||
|
frogeye.desktop.i3.bindmodes = {
|
||||||
|
"Resize" = {
|
||||||
|
bindings = {
|
||||||
|
"h" = "resize shrink width 10 px or 10 ppt; ${focus}";
|
||||||
|
"j" = "resize grow height 10 px or 10 ppt; ${focus}";
|
||||||
|
"k" = "resize shrink height 10 px or 10 ppt; ${focus}";
|
||||||
|
"l" = "resize grow width 10 px or 10 ppt; ${focus}";
|
||||||
|
};
|
||||||
|
mod_enter = "r";
|
||||||
|
};
|
||||||
|
"[L] Vérouillage [E] Déconnexion [S] Veille [H] Hibernation [R] Redémarrage [P] Extinction" = {
|
||||||
|
bindings = {
|
||||||
|
"l" = "exec --no-startup-id exec xlock, mode default";
|
||||||
|
"e" = "exit, mode default";
|
||||||
|
"s" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default";
|
||||||
|
"h" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl hibernate, mode default";
|
||||||
|
"r" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl reboot, mode default";
|
||||||
|
"p" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl poweroff -i, mode default";
|
||||||
|
};
|
||||||
|
mod_enter = "Escape";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options = {
|
||||||
|
frogeye.desktop.i3.bindmodes = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
bindings = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
enter = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = "${mod}+${config.mod_enter}";
|
||||||
|
};
|
||||||
|
mod_enter = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
return_bindings = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
72
hm/desktop/lock/default.nix
Normal file
72
hm/desktop/lock/default.nix
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
# lockColors = with config.lib.stylix.colors.withHashtag; { a = base00; b = base01; d = base00; }; # Black or White, depending on current theme
|
||||||
|
# lockColors = with config.lib.stylix.colors.withHashtag; { a = base0A; b = base0B; d = base00; }; # Green + Yellow
|
||||||
|
lockColors = { a = "#82a401"; b = "#466c01"; d = "#648901"; }; # Old
|
||||||
|
lockSvg = pkgs.writeText "lock.svg" ''
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" height="50" width="50">
|
||||||
|
<path fill="${lockColors.a}" d="M0 50h50V0H0z"/>
|
||||||
|
<path d="M0 0l50 50H25L0 25zm50 0v25L25 0z" fill="${lockColors.b}"/>
|
||||||
|
</svg>
|
||||||
|
'';
|
||||||
|
lockPng = pkgs.runCommand "lock.png" { } "${pkgs.imagemagick}/bin/convert ${lockSvg} $out";
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
xautolockState = "${config.xdg.cacheHome}/xautolock";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
(pkgs.writeShellApplication {
|
||||||
|
name = "xlock";
|
||||||
|
text = ''
|
||||||
|
${config.frogeye.hooks.lock}
|
||||||
|
# TODO Reevaluate whether we want this or not
|
||||||
|
if ! ${pkgs.lightdm}/bin/dm-tool lock
|
||||||
|
then
|
||||||
|
if [ -d ${config.xdg.cacheHome}/lockpatterns ]
|
||||||
|
then
|
||||||
|
pattern=$(${pkgs.findutils} ${config.xdg.cacheHome}/lockpatterns | sort -R | head -1)
|
||||||
|
else
|
||||||
|
pattern=${lockPng}
|
||||||
|
fi
|
||||||
|
revert() {
|
||||||
|
${pkgs.xorg.xset}/bin/xset dpms 0 0 0
|
||||||
|
}
|
||||||
|
trap revert SIGHUP SIGINT SIGTERM
|
||||||
|
${pkgs.xorg.xset}/bin/xset dpms 5 5 5
|
||||||
|
${pkgs.i3lock}/bin/i3lock --nofork --color ${builtins.substring 1 6 lockColors.d} --image="$pattern" --tiling --ignore-empty-password
|
||||||
|
revert
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
|
xsession.windowManager.i3.config = {
|
||||||
|
keybindings = {
|
||||||
|
# Screen off commands
|
||||||
|
"${mod}+F1" = "--release exec --no-startup-id ${pkgs.xorg.xset}/bin/xset dpms force off";
|
||||||
|
# Toggle to save on buttons
|
||||||
|
# xautolock -toggle doesn't allow to read state.
|
||||||
|
# Writing into a file also allows frobar to display a lock icon
|
||||||
|
"${mod}+F5" = "exec --no-startup-id ${pkgs.writeShellScript "xautolock-toggle" ''
|
||||||
|
state="$(cat "${xautolockState}")"
|
||||||
|
if [ "$state" = "disabled" ]
|
||||||
|
then
|
||||||
|
${pkgs.xautolock}/bin/xautolock -enable
|
||||||
|
echo enabled > ${xautolockState}
|
||||||
|
else
|
||||||
|
${pkgs.xautolock}/bin/xautolock -disable
|
||||||
|
echo disabled > ${xautolockState}
|
||||||
|
fi
|
||||||
|
''}";
|
||||||
|
};
|
||||||
|
startup = [
|
||||||
|
# Stop screen after 10 minutes, 1 minutes after lock it
|
||||||
|
{ notification = false; command = "${pkgs.writeShellScript "xautolock-start" ''
|
||||||
|
echo enabled > ${xautolockState}
|
||||||
|
${pkgs.xautolock}/bin/xautolock -time 10 -locker '${pkgs.xorg.xset}/bin/xset dpms force standby' -killtime 1 -killer xlock
|
||||||
|
''}"; }
|
||||||
|
# services.screen-locker.xautolock is hardcoded to use systemd for -locker (doesn't even work...)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
63
hm/desktop/mpd/default.nix
Normal file
63
hm/desktop/mpd/default.nix
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
ashuffle
|
||||||
|
mpc-cli
|
||||||
|
vimpc
|
||||||
|
playerctl
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
MPD_PORT = "${toString config.services.mpd.network.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services = {
|
||||||
|
mpd = {
|
||||||
|
enable = true;
|
||||||
|
network = {
|
||||||
|
listenAddress = "0.0.0.0"; # Can be controlled remotely, determined with firewall
|
||||||
|
startWhenNeeded = true;
|
||||||
|
};
|
||||||
|
extraConfig = ''
|
||||||
|
restore_paused "yes"
|
||||||
|
audio_output {
|
||||||
|
type "pipewire"
|
||||||
|
name "PipeWire Sound Server"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
# UPST auto audio_output ?
|
||||||
|
musicDirectory = "${config.home.homeDirectory}/Musiques";
|
||||||
|
};
|
||||||
|
# Expose mpd to mpris
|
||||||
|
# mpd-mpris also exists but is MIT and make playerctld not pick up on play/pause events
|
||||||
|
mpdris2.enable = true;
|
||||||
|
# Allow control from headset
|
||||||
|
mpris-proxy.enable = true;
|
||||||
|
# Remember the last player
|
||||||
|
playerctld.enable = true;
|
||||||
|
};
|
||||||
|
xdg = {
|
||||||
|
configFile = {
|
||||||
|
"vimpc/vimpcrc" = {
|
||||||
|
text = ''
|
||||||
|
map FF :browse<C-M>gg/
|
||||||
|
map à :set add next<C-M>a:set add end<C-M>
|
||||||
|
map @ :set add next<C-M>a:set add end<C-M>:next<C-M>
|
||||||
|
map ° D:browse<C-M>A:shuffle<C-M>:play<C-M>:playlist<C-M>
|
||||||
|
set songformat {%a - %b: %t}|{%f}$E$R $H[$H%l$H]$H
|
||||||
|
set libraryformat %n \| {%t}|{%f}$E$R $H[$H%l$H]$H
|
||||||
|
set ignorecase
|
||||||
|
set sort library
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.keybindings =
|
||||||
|
{
|
||||||
|
"XF86AudioPrev" = "exec ${lib.getExe pkgs.playerctl} previous";
|
||||||
|
"XF86AudioPlay" = "exec ${lib.getExe pkgs.playerctl} play-pause";
|
||||||
|
"XF86AudioNext" = "exec ${lib.getExe pkgs.playerctl} next";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
39
hm/desktop/presentation/default.nix
Normal file
39
hm/desktop/presentation/default.nix
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Dual-screen presenting for slideshows and stuff.
|
||||||
|
# Not tested since Nix.
|
||||||
|
# Config mentions pdfpc, although the last thing I used was Impressive, even made patches to it.
|
||||||
|
# UPST Add Impressive to nixpkgs
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
mode_pres_main = "Presentation (main display)";
|
||||||
|
mode_pres_sec = "Presentation (secondary display)";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
frogeye.desktop.i3.bindmodes = {
|
||||||
|
"${mode_pres_main}" = {
|
||||||
|
mod_enter = "Shift+p";
|
||||||
|
bindings = {
|
||||||
|
"b" = "workspace 3, workspace 4, mode ${mode_pres_sec}";
|
||||||
|
"q" = "mode default";
|
||||||
|
"Return" = "mode default";
|
||||||
|
};
|
||||||
|
return_bindings = false;
|
||||||
|
};
|
||||||
|
"${mode_pres_sec}" = {
|
||||||
|
enter = null;
|
||||||
|
bindings = {
|
||||||
|
"b" = "workspace 1, workspace 2, mode ${mode_pres_main}";
|
||||||
|
"q" = "mode default";
|
||||||
|
"Return" = "mode default";
|
||||||
|
};
|
||||||
|
return_bindings = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.window.commands = [
|
||||||
|
# Open specific applications in floating mode
|
||||||
|
{ criteria = { title = "^pdfpc.*"; window_role = "presenter"; }; command = "move to output left, fullscreen"; }
|
||||||
|
{ criteria = { title = "^pdfpc.*"; window_role = "presentation"; }; command = "move to output right, fullscreen"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
28
hm/desktop/redness/default.nix
Normal file
28
hm/desktop/redness/default.nix
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
# UPST
|
||||||
|
sct = pkgs.sct.overrideAttrs
|
||||||
|
(old: {
|
||||||
|
patches = (old.patches or [ ]) ++ [
|
||||||
|
./sct_aarch64.patch
|
||||||
|
];
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
frogeye.desktop.i3.bindmodes = {
|
||||||
|
"Temperature [R] Red [D] Dust storm [C] Campfire [O] Normal [A] All nighter [B] Blue" = {
|
||||||
|
bindings = {
|
||||||
|
"r" = "exec ${sct}/bin/sct 1000";
|
||||||
|
"d" = "exec ${sct}/bin/sct 2000";
|
||||||
|
"c" = "exec ${sct}/bin/sct 4500";
|
||||||
|
"o" = "exec ${sct}/bin/sct";
|
||||||
|
"a" = "exec ${sct}/bin/sct 8000";
|
||||||
|
"b" = "exec ${sct}/bin/sct 10000";
|
||||||
|
};
|
||||||
|
mod_enter = "y";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
home.packages = [ sct ];
|
||||||
|
};
|
||||||
|
}
|
16
hm/desktop/screenshots/default.nix
Normal file
16
hm/desktop/screenshots/default.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
dir = config.xdg.userDirs.extraConfig.XDG_SCREENSHOTS_DIR;
|
||||||
|
scrot = "${pkgs.scrot}/bin/scrot --exec '${pkgs.coreutils}/bin/mv $f ${dir}/ && ${pkgs.optipng}/bin/optipng ${dir}/$f'";
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
frogeye.folders.screenshots.path = "Screenshots";
|
||||||
|
xsession.windowManager.i3.config.keybindings = {
|
||||||
|
"Print" = "exec ${scrot} --focused";
|
||||||
|
"${mod}+Print" = "exec ${scrot}";
|
||||||
|
"Ctrl+Print" = "--release exec ${scrot} --select";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
90
hm/desktop/terminal/default.nix
Normal file
90
hm/desktop/terminal/default.nix
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||||
|
home.sessionVariables = {
|
||||||
|
RXVT_SOCKET = "${config.xdg.stateHome}/urxvtd";
|
||||||
|
# We don't use urxvt deamon mode as we use it as a backup, but just in case, this helps keep it out of the home directory.
|
||||||
|
};
|
||||||
|
programs = {
|
||||||
|
alacritty = {
|
||||||
|
# TODO Emojis
|
||||||
|
# Arch (working) shows this with alacritty -vvv:
|
||||||
|
# [TRACE] [crossfont] Got font path="/usr/share/fonts/twemoji/twemoji.ttf", index=0
|
||||||
|
# [DEBUG] [crossfont] Loaded Face Face { ft_face: Font Face: Regular, load_flags: MONOCHROME | TARGET_MONO | COLOR, render_mode: "Mono", lcd_filter: 1 }
|
||||||
|
# Nix (not working) shows this:
|
||||||
|
# [TRACE] [crossfont] Got font path="/nix/store/872g3w9vcr5nh93r0m83a3yzmpvd2qrj-home-manager-path/share/fonts/truetype/TwitterColorEmoji-SVGinOT.ttf", index=0
|
||||||
|
# [DEBUG] [crossfont] Loaded Face Face { ft_face: Font Face: Regular, load_flags: TARGET_LIGHT | COLOR, render_mode: "Lcd", lcd_filter: 1 }
|
||||||
|
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
bell = {
|
||||||
|
animation = "EaseOutExpo";
|
||||||
|
color = "#000000";
|
||||||
|
command = { program = "${pkgs.sox}/bin/play"; args = [ "-n" "synth" "sine" "C5" "sine" "E4" "remix" "1-2" "fade" "0.1" "0.2" "0.1" ]; };
|
||||||
|
duration = 100;
|
||||||
|
};
|
||||||
|
cursor = { vi_mode_style = "Underline"; };
|
||||||
|
env = {
|
||||||
|
WINIT_X11_SCALE_FACTOR = "1";
|
||||||
|
# Prevents Alacritty from resizing from one monitor to another.
|
||||||
|
# Might cause issue on HiDPI screens but we'll get there when we get there
|
||||||
|
};
|
||||||
|
hints = {
|
||||||
|
enabled = [
|
||||||
|
{
|
||||||
|
binding = { mods = "Control|Alt"; key = "F"; };
|
||||||
|
command = "${pkgs.xdg-utils}/bin/xdg-open";
|
||||||
|
mouse = { enabled = true; mods = "Control"; };
|
||||||
|
post_processing = true;
|
||||||
|
regex = "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\\\s{-}\\\\^⟨⟩`]+";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
keyboard.bindings = [
|
||||||
|
{ mode = "~Search"; mods = "Alt|Control"; key = "Space"; action = "ToggleViMode"; }
|
||||||
|
{ mode = "Vi|~Search"; mods = "Control"; key = "K"; action = "ScrollHalfPageUp"; }
|
||||||
|
{ mode = "Vi|~Search"; mods = "Control"; key = "J"; action = "ScrollHalfPageDown"; }
|
||||||
|
{ mode = "~Vi"; mods = "Control|Alt"; key = "V"; action = "Paste"; }
|
||||||
|
{ mods = "Control|Alt"; key = "C"; action = "Copy"; }
|
||||||
|
{ mode = "~Search"; mods = "Control|Alt"; key = "F"; action = "SearchForward"; }
|
||||||
|
{ mode = "~Search"; mods = "Control|Alt"; key = "B"; action = "SearchBackward"; }
|
||||||
|
{ mode = "Vi|~Search"; mods = "Control|Alt"; key = "C"; action = "ClearSelection"; }
|
||||||
|
];
|
||||||
|
window = {
|
||||||
|
dynamic_padding = false;
|
||||||
|
dynamic_title = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# Backup terminal
|
||||||
|
urxvt = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.rxvt-unicode-emoji;
|
||||||
|
scroll = {
|
||||||
|
bar.enable = false;
|
||||||
|
};
|
||||||
|
iso14755 = false; # Disable Ctrl+Shift default bindings
|
||||||
|
keybindings = {
|
||||||
|
"Shift-Control-C" = "eval:selection_to_clipboard";
|
||||||
|
"Shift-Control-V" = "eval:paste_clipboard";
|
||||||
|
# TODO Not sure resizing works, Nix doesn't have the package (urxvt-resize-font-git on Arch)
|
||||||
|
"Control-KP_Subtract" = "resize-font:smaller";
|
||||||
|
"Control-KP_Add" = "resize-font:bigger";
|
||||||
|
};
|
||||||
|
extraConfig = {
|
||||||
|
"letterSpace" = 0;
|
||||||
|
"perl-ext-common" = "resize-font,bell-command,readline,selection";
|
||||||
|
"bell-command" = "${pkgs.sox}/bin/play -n synth sine C5 sine E4 remix 1-2 fade 0.1 0.2 0.1 &> /dev/null";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.keybindings = {
|
||||||
|
"${mod}+Return" = "exec ${config.programs.alacritty.package}/bin/alacritty msg create-window -e zsh || exec ${config.programs.alacritty.package}/bin/alacritty -e zsh";
|
||||||
|
# -e zsh is for systems where I can't configure my user's shell
|
||||||
|
"${mod}+Shift+Return" = "exec ${config.programs.urxvt.package}/bin/urxvt";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
65
hm/dev.nix
65
hm/dev.nix
|
@ -1,65 +0,0 @@
|
||||||
{ pkgs, config, ... }: {
|
|
||||||
# TODO Maybe should be per-directory dotenv
|
|
||||||
# Or not, for neovim
|
|
||||||
|
|
||||||
# Always on
|
|
||||||
home.packages = with pkgs; [
|
|
||||||
# Common
|
|
||||||
perf-tools
|
|
||||||
jq
|
|
||||||
yq
|
|
||||||
universal-ctags
|
|
||||||
highlight
|
|
||||||
|
|
||||||
# Network
|
|
||||||
socat
|
|
||||||
dig
|
|
||||||
whois
|
|
||||||
nmap
|
|
||||||
tcpdump
|
|
||||||
|
|
||||||
# nix
|
|
||||||
nix
|
|
||||||
|
|
||||||
# Always on (graphical)
|
|
||||||
] ++ lib.optionals config.frogeye.desktop.xorg [
|
|
||||||
# Common
|
|
||||||
zeal-qt6 # Offline documentation
|
|
||||||
|
|
||||||
# Network
|
|
||||||
wireshark-qt
|
|
||||||
|
|
||||||
# Ansible
|
|
||||||
] ++ lib.optionals config.frogeye.dev.ansible [
|
|
||||||
ansible
|
|
||||||
ansible-lint
|
|
||||||
|
|
||||||
# C/C++
|
|
||||||
] ++ lib.optionals config.frogeye.dev.c [
|
|
||||||
cmake
|
|
||||||
clang
|
|
||||||
ccache
|
|
||||||
gdb
|
|
||||||
|
|
||||||
# Docker
|
|
||||||
] ++ lib.optionals config.frogeye.dev.docker [
|
|
||||||
docker
|
|
||||||
docker-compose
|
|
||||||
|
|
||||||
# FPGA
|
|
||||||
] ++ lib.optionals config.frogeye.dev.fpga [
|
|
||||||
verilog
|
|
||||||
# ghdl # TODO Not on aarch64
|
|
||||||
|
|
||||||
# FPGA (graphical)
|
|
||||||
] ++ lib.optionals (config.frogeye.desktop.xorg && config.frogeye.dev.fpga) [
|
|
||||||
yosys
|
|
||||||
gtkwave
|
|
||||||
|
|
||||||
# Python
|
|
||||||
] ++ lib.optionals config.frogeye.dev.python [
|
|
||||||
python3Packages.ipython
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
50
hm/dev/c.nix
Normal file
50
hm/dev/c.nix
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.dev.c {
|
||||||
|
frogeye = {
|
||||||
|
direnv = {
|
||||||
|
CCACHE_DIR = "${config.xdg.cacheHome}/ccache"; # The config file alone seems to be not enough
|
||||||
|
};
|
||||||
|
junkhome = [
|
||||||
|
"binwalk" # Should use .config according to the GitHub code though
|
||||||
|
"cmake"
|
||||||
|
"ddd"
|
||||||
|
"ghidra"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
binwalk
|
||||||
|
ccache
|
||||||
|
clang
|
||||||
|
cmake
|
||||||
|
ddd
|
||||||
|
gdb
|
||||||
|
gnumake
|
||||||
|
valgrind
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
CCACHE_CONFIGPATH = "${config.xdg.configHome}/ccache.conf";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
gdb = "gdb -x ${config.xdg.configHome}/gdbinit";
|
||||||
|
};
|
||||||
|
programs.nixvim.plugins = {
|
||||||
|
dap.enable = true; # Debug Adapter Protocol client
|
||||||
|
lsp.servers.clangd.enable = true;
|
||||||
|
};
|
||||||
|
xdg.configFile = {
|
||||||
|
"ccache.conf" = {
|
||||||
|
text = "ccache_dir = ${config.xdg.cacheHome}/ccache";
|
||||||
|
};
|
||||||
|
gdbinit = {
|
||||||
|
text = ''
|
||||||
|
define hook-quit
|
||||||
|
set confirm off
|
||||||
|
end
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
86
hm/dev/common.nix
Normal file
86
hm/dev/common.nix
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
{ pkgs, config, ... }: {
|
||||||
|
# TODO Maybe should be per-directory dotenv
|
||||||
|
# Or not, for neovim
|
||||||
|
config = {
|
||||||
|
# Always on
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
# Common
|
||||||
|
perf-tools
|
||||||
|
jq
|
||||||
|
yq
|
||||||
|
universal-ctags
|
||||||
|
cloc
|
||||||
|
|
||||||
|
# Network
|
||||||
|
socat
|
||||||
|
dig
|
||||||
|
whois
|
||||||
|
nmap
|
||||||
|
tcpdump
|
||||||
|
mtr
|
||||||
|
traceroute
|
||||||
|
|
||||||
|
# nix
|
||||||
|
lix
|
||||||
|
nixpkgs-fmt
|
||||||
|
|
||||||
|
# Always on (graphical)
|
||||||
|
] ++ lib.optionals config.frogeye.desktop.xorg [
|
||||||
|
# Common
|
||||||
|
# zeal-qt6 # Offline documentation
|
||||||
|
sqlitebrowser
|
||||||
|
|
||||||
|
# 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) [
|
||||||
|
virt-manager
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.nixvim.plugins.lsp.servers = {
|
||||||
|
ansiblels.enable = config.frogeye.dev.ansible; # Ansible
|
||||||
|
bashls.enable = true; # Bash
|
||||||
|
jsonls.enable = true; # JSON
|
||||||
|
lua-ls.enable = true; # Lua (for Neovim debugging)
|
||||||
|
perlpls.enable = config.frogeye.dev.perl; # Perl
|
||||||
|
phpactor.enable = config.frogeye.dev.php; # PHP
|
||||||
|
# Nix
|
||||||
|
nil-ls = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
formatting.command = [ "nixpkgs-fmt" ];
|
||||||
|
nix.flake = {
|
||||||
|
autoArchive = true;
|
||||||
|
autoEvalInputs = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# TODO Something for SQL. sqls is deprecated, sqlls is not in Nixpkgs. Probably needs a DB connection configured anyways?
|
||||||
|
yamlls.enable = true; # YAML
|
||||||
|
# TODO Check out none-ls
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
10
hm/dev/default.nix
Normal file
10
hm/dev/default.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ pkgs, config, ... }: {
|
||||||
|
imports = [
|
||||||
|
./c.nix
|
||||||
|
./common.nix
|
||||||
|
./go.nix
|
||||||
|
./node.nix
|
||||||
|
./prose.nix
|
||||||
|
./python.nix
|
||||||
|
];
|
||||||
|
}
|
19
hm/dev/go.nix
Normal file
19
hm/dev/go.nix
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Untested post-nix
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.dev.go {
|
||||||
|
frogeye = {
|
||||||
|
direnv = {
|
||||||
|
GOPATH = "${config.xdg.cacheHome}/go";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
go
|
||||||
|
];
|
||||||
|
sessionPath = [
|
||||||
|
"${config.home.sessionVariables.GOPATH}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
21
hm/dev/node.nix
Normal file
21
hm/dev/node.nix
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Untested post-nix
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.dev.node {
|
||||||
|
frogeye = {
|
||||||
|
direnv = {
|
||||||
|
npm_config_cache = "${config.xdg.cacheHome}/npm";
|
||||||
|
YARN_CACHE_FOLDER = "${config.xdg.cacheHome}/yarn";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
home = {
|
||||||
|
sessionVariables = {
|
||||||
|
NODE_REPL_HISTORY = "${config.xdg.cacheHome}/node_repl_history";
|
||||||
|
YARN_DISABLE_SELF_UPDATE_CHECK = "true"; # This also disable the creation of a ~/.yarnrc file
|
||||||
|
};
|
||||||
|
};
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
bower = "bower --config.storage.packages=${config.xdg.cacheHome}/bower/packages --config.storage.registry=${config.xdg.cacheHome}/bower/registry --config.storage.links=${config.xdg.cacheHome}/bower/links";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
38
hm/dev/prose.nix
Normal file
38
hm/dev/prose.nix
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Prose is a programming language, fight me
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.dev.prose {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
hunspell
|
||||||
|
hunspellDicts.en_GB-ize
|
||||||
|
hunspellDicts.en_US
|
||||||
|
hunspellDicts.fr-moderne
|
||||||
|
hunspellDicts.nl_NL
|
||||||
|
# TODO libreoffice-extension-languagetool or libreoffice-extension-grammalecte-fr
|
||||||
|
];
|
||||||
|
};
|
||||||
|
programs.nixvim = {
|
||||||
|
autoCmd = [
|
||||||
|
# vim-easy-align: Align Markdown tables with |
|
||||||
|
{ event = "FileType"; pattern = "markdown"; command = "vmap <Bar> :EasyAlign*<Bar><Enter>"; }
|
||||||
|
];
|
||||||
|
extraPlugins = with pkgs.vimPlugins; lib.optionals config.programs.pandoc.enable [
|
||||||
|
vim-pandoc # Pandoc-specific stuff because there's no LSP for it
|
||||||
|
vim-pandoc-syntax
|
||||||
|
];
|
||||||
|
extraConfigVim = lib.optionalString config.programs.pandoc.enable ''
|
||||||
|
let g:pandoc#modules#disabled = ["folding"]
|
||||||
|
let g:pandoc#spell#enabled = 0
|
||||||
|
let g:pandoc#syntax#conceal#use = 0
|
||||||
|
'';
|
||||||
|
plugins.none-ls = {
|
||||||
|
enable = true;
|
||||||
|
sources = {
|
||||||
|
# LanguageTool
|
||||||
|
diagnostics.ltrs.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
54
hm/dev/python.nix
Normal file
54
hm/dev/python.nix
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.dev.python {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
python3
|
||||||
|
python3Packages.ipython
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
PYTHONSTARTUP = "${config.xdg.configHome}/pythonstartup.py";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
ipython = "ipython --no-confirm-exit --pdb";
|
||||||
|
};
|
||||||
|
programs.nixvim.plugins.lsp.servers.pylsp = {
|
||||||
|
# Python
|
||||||
|
enable = config.frogeye.dev.python;
|
||||||
|
settings.plugins = {
|
||||||
|
black.enabled = true;
|
||||||
|
flake8 = {
|
||||||
|
enabled = true;
|
||||||
|
maxLineLength = 88; # Compatibility with Black
|
||||||
|
};
|
||||||
|
isort.enabled = true;
|
||||||
|
mccabe.enabled = true;
|
||||||
|
pycodestyle = {
|
||||||
|
enabled = true;
|
||||||
|
maxLineLength = 88; # Compatibility with Black
|
||||||
|
};
|
||||||
|
pyflakes.enabled = true;
|
||||||
|
pylint.enabled = true;
|
||||||
|
pylsp_mypy = {
|
||||||
|
enabled = true;
|
||||||
|
overrides = [
|
||||||
|
"--cache-dir=${config.xdg.cacheHome}/mypy"
|
||||||
|
"--ignore-missing-imports"
|
||||||
|
"--disallow-untyped-defs"
|
||||||
|
"--disallow-untyped-calls"
|
||||||
|
"--disallow-incomplete-defs"
|
||||||
|
"--disallow-untyped-decorators"
|
||||||
|
true
|
||||||
|
];
|
||||||
|
};
|
||||||
|
# TODO Could add some, could also remove some
|
||||||
|
};
|
||||||
|
};
|
||||||
|
xdg.configFile = {
|
||||||
|
"pythonstartup.py" = {
|
||||||
|
text = (builtins.readFile ./pythonstartup.py);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
# Communication
|
# Communication
|
||||||
signal-desktop
|
signal-desktop
|
||||||
|
(pkgs.callPackage ./whisperx.nix {}) # Transcribe voice messages
|
||||||
|
|
||||||
# downloading
|
# downloading
|
||||||
# transmission TODO Collision if both transmissions are active?
|
# transmission TODO Collision if both transmissions are active?
|
||||||
|
@ -34,21 +35,20 @@
|
||||||
# TODO Convert existing LaTeX documents into using Nix build system
|
# TODO Convert existing LaTeX documents into using Nix build system
|
||||||
# texlive is big and not that much used, sooo
|
# texlive is big and not that much used, sooo
|
||||||
pdftk
|
pdftk
|
||||||
hunspell
|
pdfgrep
|
||||||
hunspellDicts.en_GB-ize
|
|
||||||
hunspellDicts.en_US
|
# Misc
|
||||||
hunspellDicts.fr-moderne
|
haskellPackages.dice
|
||||||
hunspellDicts.nl_NL
|
rustdesk-flutter
|
||||||
# TODO libreoffice-extension-languagetool or libreoffice-extension-grammalecte-fr
|
|
||||||
|
|
||||||
] ++ lib.optionals config.frogeye.desktop.xorg [
|
] ++ lib.optionals config.frogeye.desktop.xorg [
|
||||||
|
|
||||||
# multimedia editors
|
# multimedia editors
|
||||||
gimp
|
|
||||||
inkscape
|
|
||||||
darktable
|
darktable
|
||||||
puddletag
|
puddletag
|
||||||
audacity
|
audacity
|
||||||
|
xournalpp
|
||||||
|
krita
|
||||||
|
|
||||||
# downloading
|
# downloading
|
||||||
transmission-qt
|
transmission-qt
|
||||||
|
@ -64,8 +64,5 @@
|
||||||
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
|
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
|
||||||
blender
|
blender
|
||||||
]);
|
]);
|
||||||
services = {
|
|
||||||
syncthing.enable = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
37
hm/extra/whisperx.nix
Normal file
37
hm/extra/whisperx.nix
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
pkgs.python3Packages.buildPythonPackage {
|
||||||
|
pname = "whisperx";
|
||||||
|
version = "2024-08-19";
|
||||||
|
# pypi doesn't have the requirements.txt file, and it's required
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "m-bain";
|
||||||
|
repo = "whisperX";
|
||||||
|
rev = "9e3a9e0e38fcec1304e1784381059a0e2c670be5"; # git doesn't have tags
|
||||||
|
hash = "sha256-IVtn9fe/yi4+fbH57s9LoiREnMZ2nhEObp1a4R/7gHg=";
|
||||||
|
};
|
||||||
|
pyproject = true;
|
||||||
|
dependencies = [
|
||||||
|
pkgs.python3Packages.torch
|
||||||
|
pkgs.python3Packages.torchaudio
|
||||||
|
(pkgs.python3Packages.faster-whisper.overrideAttrs (old: {
|
||||||
|
# 1.0.2 is actually breaking APIs (requires hotwords argument)
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "SYSTRAN";
|
||||||
|
repo = "faster-whisper";
|
||||||
|
rev = "v1.0.0";
|
||||||
|
hash = "sha256-0fE8X1d6CgDrrHtRudksN/tIGRtBKMvoNwkSVyFNda4=";
|
||||||
|
};
|
||||||
|
}))
|
||||||
|
pkgs.python3Packages.transformers
|
||||||
|
pkgs.python3Packages.pyannote-audio # Not in the requirements.txt for some reason
|
||||||
|
pkgs.python3Packages.pandas
|
||||||
|
pkgs.python3Packages.nltk
|
||||||
|
];
|
||||||
|
build-system = [
|
||||||
|
pkgs.python3Packages.setuptools
|
||||||
|
];
|
||||||
|
pythonImportsCheck = [
|
||||||
|
"whisperx"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import typing
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
# CORE
|
|
||||||
|
|
||||||
|
|
||||||
class Notifier:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Section:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.text = b"(Loading)"
|
|
||||||
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.bar: "Bar"
|
|
||||||
self.section = Section()
|
|
||||||
self.sections = [self.section]
|
|
||||||
|
|
||||||
|
|
||||||
class Alignment:
|
|
||||||
def __init__(self, *modules: Module) -> None:
|
|
||||||
self.bar: "Bar"
|
|
||||||
self.modules = modules
|
|
||||||
for module in modules:
|
|
||||||
module.bar = self.bar
|
|
||||||
|
|
||||||
|
|
||||||
class Screen:
|
|
||||||
def __init__(self, left: Alignment = Alignment(), right: Alignment = Alignment()) -> None:
|
|
||||||
self.bar: "Bar"
|
|
||||||
self.left = left
|
|
||||||
self.left.bar = self.bar
|
|
||||||
self.right = right or Alignment()
|
|
||||||
self.right.bar = self.bar
|
|
||||||
|
|
||||||
|
|
||||||
class Bar:
|
|
||||||
def __init__(self, *screens: Screen) -> None:
|
|
||||||
self.screens = screens
|
|
||||||
for screen in screens:
|
|
||||||
screen.bar = self
|
|
||||||
self.process = subprocess.Popen(["lemonbar"], stdin=subprocess.PIPE)
|
|
||||||
|
|
||||||
def display(self) -> None:
|
|
||||||
string = b""
|
|
||||||
for s, screen in enumerate(self.screens):
|
|
||||||
string += b"%%{S%d}" % s
|
|
||||||
for control, alignment in [(b'%{l}', screen.left), (b'%{r}', screen.right)]:
|
|
||||||
string += control
|
|
||||||
for module in alignment.modules:
|
|
||||||
for section in module.sections:
|
|
||||||
string += b"<%b> |" % section.text
|
|
||||||
|
|
||||||
string += b"\n"
|
|
||||||
print(string)
|
|
||||||
assert self.process.stdin
|
|
||||||
self.process.stdin.write(string)
|
|
||||||
self.process.stdin.flush()
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
while True:
|
|
||||||
self.display()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
# REUSABLE
|
|
||||||
|
|
||||||
class ClockNotifier(Notifier):
|
|
||||||
def run(self) -> None:
|
|
||||||
while True:
|
|
||||||
def __init__(self, text: bytes):
|
|
||||||
super().__init__()
|
|
||||||
self.section.text = text
|
|
||||||
|
|
||||||
class StaticModule(Module):
|
|
||||||
def __init__(self, text: bytes):
|
|
||||||
super().__init__()
|
|
||||||
self.section.text = text
|
|
||||||
|
|
||||||
|
|
||||||
# USER
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
bar = Bar(
|
|
||||||
Screen(Alignment(StaticModule(b"A"))),
|
|
||||||
Screen(Alignment(StaticModule(b"B"))),
|
|
||||||
)
|
|
||||||
bar.run()
|
|
|
@ -1,199 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
|
||||||
Debugging script
|
|
||||||
"""
|
|
||||||
|
|
||||||
import i3ipc
|
|
||||||
import os
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
# import alsaaudio
|
|
||||||
from time import time
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
i3 = i3ipc.Connection()
|
|
||||||
lemonbar = subprocess.Popen(["lemonbar", "-b"], stdin=subprocess.PIPE)
|
|
||||||
|
|
||||||
# Utils
|
|
||||||
def upChart(p):
|
|
||||||
block = " ▁▂▃▄▅▆▇█"
|
|
||||||
return block[round(p * (len(block) - 1))]
|
|
||||||
|
|
||||||
|
|
||||||
def humanSizeOf(num, suffix="B"): # TODO Credit
|
|
||||||
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
|
||||||
if abs(num) < 1024.0:
|
|
||||||
return "%3.0f%2s%s" % (num, unit, suffix)
|
|
||||||
num /= 1024.0
|
|
||||||
return "%.0f%2s%s" % (num, "Yi", suffix)
|
|
||||||
|
|
||||||
|
|
||||||
# Values
|
|
||||||
mode = ""
|
|
||||||
container = i3.get_tree().find_focused()
|
|
||||||
workspaces = i3.get_workspaces()
|
|
||||||
outputs = i3.get_outputs()
|
|
||||||
|
|
||||||
username = os.environ["USER"]
|
|
||||||
hostname = os.environ["HOSTNAME"]
|
|
||||||
if "-" in hostname:
|
|
||||||
hostname = hostname.split("-")[-1]
|
|
||||||
|
|
||||||
oldNetIO = dict()
|
|
||||||
oldTime = time()
|
|
||||||
|
|
||||||
|
|
||||||
def update():
|
|
||||||
activeOutputs = sorted(
|
|
||||||
sorted(list(filter(lambda o: o.active, outputs)), key=lambda o: o.rect.y),
|
|
||||||
key=lambda o: o.rect.x,
|
|
||||||
)
|
|
||||||
z = ""
|
|
||||||
for aOutput in range(len(activeOutputs)):
|
|
||||||
output = activeOutputs[aOutput]
|
|
||||||
# Mode || Workspaces
|
|
||||||
t = []
|
|
||||||
if mode != "":
|
|
||||||
t.append(mode)
|
|
||||||
else:
|
|
||||||
t.append(
|
|
||||||
" ".join(
|
|
||||||
[
|
|
||||||
(w.name.upper() if w.focused else w.name)
|
|
||||||
for w in workspaces
|
|
||||||
if w.output == output.name
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Windows Title
|
|
||||||
# if container:
|
|
||||||
# t.append(container.name)
|
|
||||||
|
|
||||||
# CPU
|
|
||||||
t.append(
|
|
||||||
"C" + "".join([upChart(p / 100) for p in psutil.cpu_percent(percpu=True)])
|
|
||||||
)
|
|
||||||
|
|
||||||
# Memory
|
|
||||||
t.append(
|
|
||||||
"M"
|
|
||||||
+ str(round(psutil.virtual_memory().percent))
|
|
||||||
+ "% "
|
|
||||||
+ "S"
|
|
||||||
+ str(round(psutil.swap_memory().percent))
|
|
||||||
+ "%"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Disks
|
|
||||||
d = []
|
|
||||||
for disk in psutil.disk_partitions():
|
|
||||||
e = ""
|
|
||||||
if disk.device.startswith("/dev/sd"):
|
|
||||||
e += "S" + disk.device[-2:].upper()
|
|
||||||
elif disk.device.startswith("/dev/mmcblk"):
|
|
||||||
e += "M" + disk.device[-3] + disk.device[-1]
|
|
||||||
else:
|
|
||||||
e += "?"
|
|
||||||
e += " "
|
|
||||||
e += str(round(psutil.disk_usage(disk.mountpoint).percent)) + "%"
|
|
||||||
d.append(e)
|
|
||||||
t.append(" ".join(d))
|
|
||||||
|
|
||||||
# Network
|
|
||||||
netStats = psutil.net_if_stats()
|
|
||||||
netIO = psutil.net_io_counters(pernic=True)
|
|
||||||
net = []
|
|
||||||
for iface in filter(lambda i: i != "lo" and netStats[i].isup, netStats.keys()):
|
|
||||||
s = ""
|
|
||||||
if iface.startswith("eth"):
|
|
||||||
s += "E"
|
|
||||||
elif iface.startswith("wlan"):
|
|
||||||
s += "W"
|
|
||||||
else:
|
|
||||||
s += "?"
|
|
||||||
|
|
||||||
s += " "
|
|
||||||
now = time()
|
|
||||||
global oldNetIO, oldTime
|
|
||||||
|
|
||||||
sent = (
|
|
||||||
(oldNetIO[iface].bytes_sent if iface in oldNetIO else 0)
|
|
||||||
- (netIO[iface].bytes_sent if iface in netIO else 0)
|
|
||||||
) / (oldTime - now)
|
|
||||||
recv = (
|
|
||||||
(oldNetIO[iface].bytes_recv if iface in oldNetIO else 0)
|
|
||||||
- (netIO[iface].bytes_recv if iface in netIO else 0)
|
|
||||||
) / (oldTime - now)
|
|
||||||
s += (
|
|
||||||
"↓"
|
|
||||||
+ humanSizeOf(abs(recv), "B/s")
|
|
||||||
+ " ↑"
|
|
||||||
+ humanSizeOf(abs(sent), "B/s")
|
|
||||||
)
|
|
||||||
|
|
||||||
oldNetIO = netIO
|
|
||||||
oldTime = now
|
|
||||||
|
|
||||||
net.append(s)
|
|
||||||
t.append(" ".join(net))
|
|
||||||
|
|
||||||
# Battery
|
|
||||||
if os.path.isdir("/sys/class/power_supply/BAT0"):
|
|
||||||
with open("/sys/class/power_supply/BAT0/charge_now") as f:
|
|
||||||
charge_now = int(f.read())
|
|
||||||
with open("/sys/class/power_supply/BAT0/charge_full_design") as f:
|
|
||||||
charge_full = int(f.read())
|
|
||||||
t.append("B" + str(round(100 * charge_now / charge_full)) + "%")
|
|
||||||
|
|
||||||
# Volume
|
|
||||||
# t.append('V ' + str(alsaaudio.Mixer('Master').getvolume()[0]) + '%')
|
|
||||||
|
|
||||||
t.append(username + "@" + hostname)
|
|
||||||
|
|
||||||
# print(' - '.join(t))
|
|
||||||
# t = [output.name]
|
|
||||||
|
|
||||||
z += " - ".join(t) + "%{S" + str(aOutput + 1) + "}"
|
|
||||||
# lemonbar.stdin.write(bytes(' - '.join(t), 'utf-8'))
|
|
||||||
# lemonbar.stdin.write(bytes('%{S' + str(aOutput + 1) + '}', 'utf-8'))
|
|
||||||
|
|
||||||
lemonbar.stdin.write(bytes(z + "\n", "utf-8"))
|
|
||||||
lemonbar.stdin.flush()
|
|
||||||
|
|
||||||
|
|
||||||
# Event listeners
|
|
||||||
def on_mode(i3, e):
|
|
||||||
global mode
|
|
||||||
if e.change == "default":
|
|
||||||
mode = ""
|
|
||||||
else:
|
|
||||||
mode = e.change
|
|
||||||
update()
|
|
||||||
|
|
||||||
|
|
||||||
i3.on("mode", on_mode)
|
|
||||||
|
|
||||||
# def on_window_focus(i3, e):
|
|
||||||
# global container
|
|
||||||
# container = e.container
|
|
||||||
# update()
|
|
||||||
#
|
|
||||||
# i3.on("window::focus", on_window_focus)
|
|
||||||
|
|
||||||
|
|
||||||
def on_workspace_focus(i3, e):
|
|
||||||
global workspaces
|
|
||||||
workspaces = i3.get_workspaces()
|
|
||||||
update()
|
|
||||||
|
|
||||||
|
|
||||||
i3.on("workspace::focus", on_workspace_focus)
|
|
||||||
|
|
||||||
# Starting
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
|
|
||||||
i3.main()
|
|
|
@ -1,327 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
|
||||||
Beautiful script
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import multiprocessing
|
|
||||||
import i3ipc
|
|
||||||
import difflib
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
FONT = "DejaVuSansMono Nerd Font Mono"
|
|
||||||
|
|
||||||
# TODO Update to be in sync with base16
|
|
||||||
thm = [
|
|
||||||
"#002b36",
|
|
||||||
"#dc322f",
|
|
||||||
"#859900",
|
|
||||||
"#b58900",
|
|
||||||
"#268bd2",
|
|
||||||
"#6c71c4",
|
|
||||||
"#2aa198",
|
|
||||||
"#93a1a1",
|
|
||||||
"#657b83",
|
|
||||||
"#dc322f",
|
|
||||||
"#859900",
|
|
||||||
"#b58900",
|
|
||||||
"#268bd2",
|
|
||||||
"#6c71c4",
|
|
||||||
"#2aa198",
|
|
||||||
"#fdf6e3",
|
|
||||||
]
|
|
||||||
fg = "#93a1a1"
|
|
||||||
bg = "#002b36"
|
|
||||||
|
|
||||||
THEMES = {
|
|
||||||
"CENTER": (fg, bg),
|
|
||||||
"DEFAULT": (thm[0], thm[8]),
|
|
||||||
"1": (thm[0], thm[9]),
|
|
||||||
"2": (thm[0], thm[10]),
|
|
||||||
"3": (thm[0], thm[11]),
|
|
||||||
"4": (thm[0], thm[12]),
|
|
||||||
"5": (thm[0], thm[13]),
|
|
||||||
"6": (thm[0], thm[14]),
|
|
||||||
"7": (thm[0], thm[15]),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Utils
|
|
||||||
|
|
||||||
|
|
||||||
def fitText(text, size):
|
|
||||||
"""
|
|
||||||
Add spaces or cut a string to be `size` characters long
|
|
||||||
"""
|
|
||||||
if size > 0:
|
|
||||||
t = len(text)
|
|
||||||
if t >= size:
|
|
||||||
return text[:size]
|
|
||||||
else:
|
|
||||||
diff = size - t
|
|
||||||
return text + " " * diff
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def fgColor(theme):
|
|
||||||
global THEMES
|
|
||||||
return THEMES[theme][0]
|
|
||||||
|
|
||||||
|
|
||||||
def bgColor(theme):
|
|
||||||
global THEMES
|
|
||||||
return THEMES[theme][1]
|
|
||||||
|
|
||||||
|
|
||||||
class Section:
|
|
||||||
def __init__(self, theme="DEFAULT"):
|
|
||||||
self.text = ""
|
|
||||||
self.size = 0
|
|
||||||
self.toSize = 0
|
|
||||||
self.theme = theme
|
|
||||||
self.visible = False
|
|
||||||
self.name = ""
|
|
||||||
|
|
||||||
def update(self, text):
|
|
||||||
if text == "":
|
|
||||||
self.toSize = 0
|
|
||||||
else:
|
|
||||||
if len(text) < len(self.text):
|
|
||||||
self.text = text + self.text[len(text) :]
|
|
||||||
else:
|
|
||||||
self.text = text
|
|
||||||
self.toSize = len(text) + 3
|
|
||||||
|
|
||||||
def updateSize(self):
|
|
||||||
"""
|
|
||||||
Set the size for the next frame of animation
|
|
||||||
Return if another frame is needed
|
|
||||||
"""
|
|
||||||
if self.toSize > self.size:
|
|
||||||
self.size += 1
|
|
||||||
elif self.toSize < self.size:
|
|
||||||
self.size -= 1
|
|
||||||
self.visible = self.size
|
|
||||||
return self.toSize == self.size
|
|
||||||
|
|
||||||
def draw(self, left=True, nextTheme="DEFAULT"):
|
|
||||||
s = ""
|
|
||||||
if self.visible:
|
|
||||||
if not left:
|
|
||||||
if self.theme == nextTheme:
|
|
||||||
s += ""
|
|
||||||
else:
|
|
||||||
s += "%{F" + bgColor(self.theme) + "}"
|
|
||||||
s += "%{B" + bgColor(nextTheme) + "}"
|
|
||||||
s += ""
|
|
||||||
s += "%{F" + fgColor(self.theme) + "}"
|
|
||||||
s += "%{B" + bgColor(self.theme) + "}"
|
|
||||||
s += " " if self.size > 1 else ""
|
|
||||||
s += fitText(self.text, self.size - 3)
|
|
||||||
s += " " if self.size > 2 else ""
|
|
||||||
if left:
|
|
||||||
if self.theme == nextTheme:
|
|
||||||
s += ""
|
|
||||||
else:
|
|
||||||
s += "%{F" + bgColor(self.theme) + "}"
|
|
||||||
s += "%{B" + bgColor(nextTheme) + "}"
|
|
||||||
s += ""
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
# Section definition
|
|
||||||
sTime = Section("3")
|
|
||||||
|
|
||||||
hostname = os.environ["HOSTNAME"].split(".")[0]
|
|
||||||
sHost = Section("2")
|
|
||||||
sHost.update(
|
|
||||||
os.environ["USER"] + "@" + hostname.split("-")[-1] if "-" in hostname else hostname
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Groups definition
|
|
||||||
gLeft = []
|
|
||||||
gRight = [sTime, sHost]
|
|
||||||
|
|
||||||
# Bar handling
|
|
||||||
bar = subprocess.Popen(["lemonbar", "-f", FONT, "-b"], stdin=subprocess.PIPE)
|
|
||||||
|
|
||||||
|
|
||||||
def updateBar():
|
|
||||||
global timeLastUpdate, timeUpdate
|
|
||||||
global gLeft, gRight
|
|
||||||
global outputs
|
|
||||||
|
|
||||||
text = ""
|
|
||||||
for oi in range(len(outputs)):
|
|
||||||
output = outputs[oi]
|
|
||||||
gLeftFiltered = list(
|
|
||||||
filter(
|
|
||||||
lambda s: s.visible and (not s.output or s.output == output.name), gLeft
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tLeft = ""
|
|
||||||
l = len(gLeftFiltered)
|
|
||||||
for gi in range(l):
|
|
||||||
g = gLeftFiltered[gi]
|
|
||||||
# Next visible section for transition
|
|
||||||
nextTheme = gLeftFiltered[gi + 1].theme if gi + 1 < l else "CENTER"
|
|
||||||
tLeft = tLeft + g.draw(True, nextTheme)
|
|
||||||
|
|
||||||
tRight = ""
|
|
||||||
for gi in range(len(gRight)):
|
|
||||||
g = gRight[gi]
|
|
||||||
nextTheme = "CENTER"
|
|
||||||
for gn in gRight[gi + 1 :]:
|
|
||||||
if gn.visible:
|
|
||||||
nextTheme = gn.theme
|
|
||||||
break
|
|
||||||
tRight = g.draw(False, nextTheme) + tRight
|
|
||||||
text += (
|
|
||||||
"%{l}"
|
|
||||||
+ tLeft
|
|
||||||
+ "%{r}"
|
|
||||||
+ tRight
|
|
||||||
+ "%{B"
|
|
||||||
+ bgColor("CENTER")
|
|
||||||
+ "}"
|
|
||||||
+ "%{S"
|
|
||||||
+ str(oi + 1)
|
|
||||||
+ "}"
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.stdin.write(bytes(text + "\n", "utf-8"))
|
|
||||||
bar.stdin.flush()
|
|
||||||
|
|
||||||
|
|
||||||
# Values
|
|
||||||
i3 = i3ipc.Connection()
|
|
||||||
outputs = []
|
|
||||||
|
|
||||||
|
|
||||||
def on_output():
|
|
||||||
global outputs
|
|
||||||
outputs = sorted(
|
|
||||||
sorted(
|
|
||||||
list(filter(lambda o: o.active, i3.get_outputs())), key=lambda o: o.rect.y
|
|
||||||
),
|
|
||||||
key=lambda o: o.rect.x,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
on_output()
|
|
||||||
|
|
||||||
|
|
||||||
def on_workspace_focus():
|
|
||||||
global i3
|
|
||||||
global gLeft
|
|
||||||
workspaces = i3.get_workspaces()
|
|
||||||
wNames = [w.name for w in workspaces]
|
|
||||||
sNames = [s.name for s in gLeft]
|
|
||||||
|
|
||||||
newGLeft = []
|
|
||||||
|
|
||||||
def actuate(section, workspace):
|
|
||||||
if workspace:
|
|
||||||
section.name = workspace.name
|
|
||||||
section.output = workspace.output
|
|
||||||
if workspace.visible:
|
|
||||||
section.update(workspace.name)
|
|
||||||
else:
|
|
||||||
section.update(workspace.name.split(" ")[0])
|
|
||||||
|
|
||||||
if workspace.focused:
|
|
||||||
section.theme = "4"
|
|
||||||
elif workspace.urgent:
|
|
||||||
section.theme = "1"
|
|
||||||
else:
|
|
||||||
section.theme = "6"
|
|
||||||
else:
|
|
||||||
section.update("")
|
|
||||||
section.theme = "6"
|
|
||||||
|
|
||||||
for tag, i, j, k, l in difflib.SequenceMatcher(None, sNames, wNames).get_opcodes():
|
|
||||||
if tag == "equal": # If the workspaces didn't changed
|
|
||||||
for a in range(j - i):
|
|
||||||
workspace = workspaces[k + a]
|
|
||||||
section = gLeft[i + a]
|
|
||||||
actuate(section, workspace)
|
|
||||||
newGLeft.append(section)
|
|
||||||
if tag in ("delete", "replace"): # If the workspaces were removed
|
|
||||||
for section in gLeft[i:j]:
|
|
||||||
if section.visible:
|
|
||||||
actuate(section, None)
|
|
||||||
newGLeft.append(section)
|
|
||||||
else:
|
|
||||||
del section
|
|
||||||
if tag in ("insert", "replace"): # If the workspaces were removed
|
|
||||||
for workspace in workspaces[k:l]:
|
|
||||||
section = Section()
|
|
||||||
actuate(section, workspace)
|
|
||||||
newGLeft.append(section)
|
|
||||||
gLeft = newGLeft
|
|
||||||
|
|
||||||
updateBar()
|
|
||||||
|
|
||||||
|
|
||||||
on_workspace_focus()
|
|
||||||
|
|
||||||
|
|
||||||
def i3events(i3childPipe):
|
|
||||||
global i3
|
|
||||||
|
|
||||||
# Proxy functions
|
|
||||||
def on_workspace_focus(i3, e):
|
|
||||||
global i3childPipe
|
|
||||||
i3childPipe.send("on_workspace_focus")
|
|
||||||
|
|
||||||
i3.on("workspace::focus", on_workspace_focus)
|
|
||||||
|
|
||||||
def on_output(i3, e):
|
|
||||||
global i3childPipe
|
|
||||||
i3childPipe.send("on_output")
|
|
||||||
|
|
||||||
i3.on("output", on_output)
|
|
||||||
|
|
||||||
i3.main()
|
|
||||||
|
|
||||||
|
|
||||||
i3parentPipe, i3childPipe = multiprocessing.Pipe()
|
|
||||||
i3process = multiprocessing.Process(target=i3events, args=(i3childPipe,))
|
|
||||||
i3process.start()
|
|
||||||
|
|
||||||
|
|
||||||
def updateValues():
|
|
||||||
# Time
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
sTime.update(now.strftime("%x %X"))
|
|
||||||
|
|
||||||
|
|
||||||
def updateAnimation():
|
|
||||||
for s in set(gLeft + gRight):
|
|
||||||
s.updateSize()
|
|
||||||
updateBar()
|
|
||||||
|
|
||||||
|
|
||||||
lastUpdate = 0
|
|
||||||
while True:
|
|
||||||
now = time.time()
|
|
||||||
if i3parentPipe.poll():
|
|
||||||
msg = i3parentPipe.recv()
|
|
||||||
if msg == "on_workspace_focus":
|
|
||||||
on_workspace_focus()
|
|
||||||
elif msg == "on_output":
|
|
||||||
on_output()
|
|
||||||
# TODO Restart lemonbar
|
|
||||||
else:
|
|
||||||
print(msg)
|
|
||||||
updateAnimation()
|
|
||||||
if now >= lastUpdate + 1:
|
|
||||||
updateValues()
|
|
||||||
lastUpdate = now
|
|
||||||
|
|
||||||
time.sleep(0.05)
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import Xlib.display
|
|
||||||
|
|
||||||
dis = Xlib.display.Display()
|
|
||||||
|
|
||||||
nb = dis.screen_count()
|
|
||||||
|
|
||||||
for s in range(nb):
|
|
||||||
print(s)
|
|
|
@ -1,48 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> { config = { }; overlays = [ ]; }, ... }:
|
|
||||||
# Tried using pyproject.nix but mpd2 dependency wouldn't resolve,
|
|
||||||
# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs.
|
|
||||||
let
|
|
||||||
frobar = pkgs.python3Packages.buildPythonApplication {
|
|
||||||
pname = "frobar";
|
|
||||||
version = "2.0";
|
|
||||||
|
|
||||||
runtimeInputs = with pkgs; [ lemonbar-xft wirelesstools ];
|
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [
|
|
||||||
coloredlogs
|
|
||||||
notmuch
|
|
||||||
i3ipc
|
|
||||||
mpd2
|
|
||||||
psutil
|
|
||||||
pulsectl
|
|
||||||
pyinotify
|
|
||||||
];
|
|
||||||
makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath (with pkgs; [ lemonbar-xft wirelesstools ])}" ];
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
xsession.windowManager.i3.config.bars = [ ];
|
|
||||||
programs.autorandr.hooks.postswitch = {
|
|
||||||
frobar = "${pkgs.systemd}/bin/systemctl --user restart frobar";
|
|
||||||
};
|
|
||||||
systemd.user.services.frobar = {
|
|
||||||
Unit = {
|
|
||||||
Description = "frobar";
|
|
||||||
After = [ "graphical-session-pre.target" ];
|
|
||||||
PartOf = [ "graphical-session.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
Service = {
|
|
||||||
# Wait for i3 to start. Can't use ExecStartPre because otherwise it blocks graphical-session.target, and there's nothing i3/systemd
|
|
||||||
# TODO Do that better
|
|
||||||
ExecStart = ''${pkgs.bash}/bin/bash -c "while ! ${pkgs.i3}/bin/i3-msg; do ${pkgs.coreutils}/bin/sleep 1; done; ${frobar}/bin/frobar"'';
|
|
||||||
};
|
|
||||||
|
|
||||||
Install = { WantedBy = [ "graphical-session.target" ]; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
# TODO Connection with i3 is lost on start sometimes, more often than with Arch?
|
|
||||||
# TODO Restore ability to build frobar with nix-build
|
|
|
@ -1,64 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from frobar.providers import *
|
|
||||||
|
|
||||||
# TODO If multiple screen, expand the sections and share them
|
|
||||||
# TODO Graceful exit
|
|
||||||
|
|
||||||
def run():
|
|
||||||
Bar.init()
|
|
||||||
Updater.init()
|
|
||||||
|
|
||||||
WORKSPACE_THEME = 0
|
|
||||||
FOCUS_THEME = 3
|
|
||||||
URGENT_THEME = 1
|
|
||||||
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(
|
|
||||||
I3WorkspacesProvider(
|
|
||||||
theme=WORKSPACE_THEME,
|
|
||||||
themeFocus=FOCUS_THEME,
|
|
||||||
themeUrgent=URGENT_THEME,
|
|
||||||
themeMode=URGENT_THEME,
|
|
||||||
customNames=customNames,
|
|
||||||
),
|
|
||||||
BarGroupType.LEFT,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO Middle
|
|
||||||
Bar.addSectionAll(MpdProvider(theme=7), BarGroupType.LEFT)
|
|
||||||
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
|
|
||||||
|
|
||||||
# TODO Computer modes
|
|
||||||
|
|
||||||
SYSTEM_THEME = 2
|
|
||||||
DANGER_THEME = FOCUS_THEME
|
|
||||||
CRITICAL_THEME = URGENT_THEME
|
|
||||||
Bar.addSectionAll(CpuProvider(), BarGroupType.RIGHT)
|
|
||||||
Bar.addSectionAll(RamProvider(), BarGroupType.RIGHT)
|
|
||||||
Bar.addSectionAll(TemperatureProvider(), BarGroupType.RIGHT)
|
|
||||||
Bar.addSectionAll(BatteryProvider(), BarGroupType.RIGHT)
|
|
||||||
|
|
||||||
# Peripherals
|
|
||||||
PERIPHERAL_THEME = 5
|
|
||||||
NETWORK_THEME = 4
|
|
||||||
# TODO Disk space provider
|
|
||||||
# TODO Screen (connected, autorandr configuration, bbswitch) provider
|
|
||||||
Bar.addSectionAll(PulseaudioProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
|
|
||||||
Bar.addSectionAll(RfkillProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
|
|
||||||
Bar.addSectionAll(NetworkProvider(theme=NETWORK_THEME), BarGroupType.RIGHT)
|
|
||||||
|
|
||||||
# Personal
|
|
||||||
PERSONAL_THEME = 0
|
|
||||||
# Bar.addSectionAll(KeystoreProvider(theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
|
||||||
# Bar.addSectionAll(NotmuchUnreadProvider(dir='~/.mail/', theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
|
||||||
# Bar.addSectionAll(TodoProvider(dir='~/.vdirsyncer/currentCalendars/', theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
|
||||||
|
|
||||||
TIME_THEME = 6
|
|
||||||
Bar.addSectionAll(TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
|
|
||||||
|
|
||||||
# Bar.run()
|
|
|
@ -3,13 +3,28 @@
|
||||||
config = lib.mkIf config.frogeye.gaming {
|
config = lib.mkIf config.frogeye.gaming {
|
||||||
# Using config.nixpkgs.<something> creates an infinite recursion,
|
# Using config.nixpkgs.<something> creates an infinite recursion,
|
||||||
# but the above might not be correct in case of cross-compiling?
|
# but the above might not be correct in case of cross-compiling?
|
||||||
home.packages = with pkgs; [
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
# gaming
|
# gaming
|
||||||
yuzu-mainline
|
dolphin-emu
|
||||||
minecraft
|
ryujinx
|
||||||
|
prismlauncher
|
||||||
# TODO factorio
|
# TODO factorio
|
||||||
|
|
||||||
steam # Common pitfall: https://github.com/NixOS/nixpkgs/issues/86506#issuecomment-623746883
|
steam # Common pitfall: https://github.com/NixOS/nixpkgs/issues/86506#issuecomment-623746883
|
||||||
|
# itch # butler-15.21.0 is broken
|
||||||
|
(pkgs.python3Packages.ds4drv.overrideAttrs (old: {
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "TheDrHax";
|
||||||
|
repo = "ds4drv-cemuhook";
|
||||||
|
rev = "a58f63b70f8d8efa33e5e82a8888a1e08754aeed";
|
||||||
|
sha256 = "sha256-oMvHw5zeO0skoiqLU+EdjUabTvkipeBh+m8RHJcWZP8=";
|
||||||
|
};
|
||||||
|
}))
|
||||||
];
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
BOOT9_PATH = "${config.xdg.dataHome}/citra-emu/sysdata/boot9.bin";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
125
hm/git/default.nix
Normal file
125
hm/git/default.nix
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
{ pkgs, lib, config, unixpkgs, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.programs.git;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
home.packages = [
|
||||||
|
(pkgs.writeShellApplication {
|
||||||
|
name = "git-sync";
|
||||||
|
text = (lib.strings.concatLines
|
||||||
|
(map
|
||||||
|
(r: ''
|
||||||
|
echo "===== ${r.path}"
|
||||||
|
if [ ! -d "${r.path}" ]
|
||||||
|
then
|
||||||
|
${pkgs.git}/bin/git clone "${r.uri}" "${r.path}"
|
||||||
|
else
|
||||||
|
(
|
||||||
|
cd "${r.path}"
|
||||||
|
if [ -d .jj ]
|
||||||
|
then
|
||||||
|
${lib.getExe config.programs.jujutsu.package} git fetch
|
||||||
|
${lib.getExe config.programs.jujutsu.package} rebase -d main@origin
|
||||||
|
${lib.getExe config.programs.jujutsu.package} bookmark set main -r @-
|
||||||
|
${lib.getExe config.programs.jujutsu.package} git push
|
||||||
|
else
|
||||||
|
${pkgs.git}/bin/git --no-optional-locks diff --quiet || echo "Repository is dirty!"
|
||||||
|
${pkgs.git}/bin/git pull || true
|
||||||
|
# Only push if there's something to push. Also prevents from trying to push on repos where we don't have rights.
|
||||||
|
(${pkgs.git}/bin/git --no-optional-locks status --porcelain -b --ignore-submodules | grep ' \[ahead [0-9]\+\]' && ${pkgs.git}/bin/git push) || true
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
'')
|
||||||
|
(lib.attrsets.attrValues config.services.git-sync.repositories)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
];
|
||||||
|
programs = {
|
||||||
|
git = {
|
||||||
|
package = pkgs.gitFull;
|
||||||
|
aliases = {
|
||||||
|
"git" = "!exec git"; # In case I write one too many git
|
||||||
|
};
|
||||||
|
ignores = [
|
||||||
|
"*.swp"
|
||||||
|
"*.swo"
|
||||||
|
"*.ycm_extra_conf.py"
|
||||||
|
"tags"
|
||||||
|
".mypy_cache"
|
||||||
|
];
|
||||||
|
delta = {
|
||||||
|
enable = true;
|
||||||
|
options = {
|
||||||
|
line-numbers = true;
|
||||||
|
syntax-theme = "base16";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# Also tried difftastic, and while I like the default theme it's a bit
|
||||||
|
# less configurable
|
||||||
|
lfs.enable = true;
|
||||||
|
userEmail = lib.mkDefault "geoffrey@frogeye.fr";
|
||||||
|
userName = lib.mkDefault "Geoffrey Frogeye";
|
||||||
|
extraConfig = {
|
||||||
|
core = {
|
||||||
|
editor = "nvim";
|
||||||
|
};
|
||||||
|
push = {
|
||||||
|
default = "matching";
|
||||||
|
};
|
||||||
|
pull = {
|
||||||
|
ff = "only";
|
||||||
|
};
|
||||||
|
} // lib.optionalAttrs config.frogeye.desktop.xorg {
|
||||||
|
diff.tool = "meld";
|
||||||
|
difftool.prompt = false;
|
||||||
|
"difftool \"meld\"".cmd = "${pkgs.meld}/bin/meld \"$LOCAL\" \"$REMOTE\"";
|
||||||
|
# This escapes quotes, which isn't the case in the original, hoping this isn't an issue.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
jujutsu = {
|
||||||
|
enable = true;
|
||||||
|
package = (import unixpkgs {
|
||||||
|
inherit (pkgs) system;
|
||||||
|
}).jujutsu;
|
||||||
|
# Current version doesn't have the "none" signing backend
|
||||||
|
settings = {
|
||||||
|
git = {
|
||||||
|
auto-local-bookmark = true;
|
||||||
|
auto-local-branch = true;
|
||||||
|
};
|
||||||
|
user = {
|
||||||
|
email = cfg.userEmail;
|
||||||
|
name = cfg.userName;
|
||||||
|
};
|
||||||
|
ui = {
|
||||||
|
pager = "delta";
|
||||||
|
diff.format = "git";
|
||||||
|
diff-editor = "meld-3";
|
||||||
|
merge-editor = "meld";
|
||||||
|
};
|
||||||
|
signing = {
|
||||||
|
sign-all = true;
|
||||||
|
backend = "gpg";
|
||||||
|
inherit (cfg.signing) key;
|
||||||
|
backends.gpg.allow-expired-keys = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services = {
|
||||||
|
git-sync = {
|
||||||
|
enable = false; # The real thing syncs too quickly and asks for passphrase, which is annoying
|
||||||
|
# So for now it's just a way to park config which will be reused by git-sync-* commands
|
||||||
|
repositories = {
|
||||||
|
dotfiles = {
|
||||||
|
path = "${config.xdg.configHome}/dotfiles";
|
||||||
|
uri = lib.mkDefault "https://git.frogeye.fr/geoffrey/dotfiles.git";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
50
hm/gpg/default.nix
Normal file
50
hm/gpg/default.nix
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.programs.gpg.enable {
|
||||||
|
frogeye.hooks.lock = ''
|
||||||
|
echo RELOADAGENT | ${pkgs.gnupg}/bin/gpg-connect-agent
|
||||||
|
'';
|
||||||
|
programs.gpg = {
|
||||||
|
homedir = "${config.xdg.stateHome}/gnupg";
|
||||||
|
settings = {
|
||||||
|
# Remove fluff
|
||||||
|
no-greeting = true;
|
||||||
|
no-emit-version = true;
|
||||||
|
no-comments = true;
|
||||||
|
# Output format that I prefer
|
||||||
|
keyid-format = "0xlong";
|
||||||
|
# Show fingerprints
|
||||||
|
with-fingerprint = true;
|
||||||
|
# Make sure to show if key is invalid
|
||||||
|
# (should be default on most platform,
|
||||||
|
# but just to be sure)
|
||||||
|
list-options = "show-uid-validity";
|
||||||
|
verify-options = "show-uid-validity";
|
||||||
|
# Stronger algorithm (https://wiki.archlinux.org/title/GnuPG#Different_algorithm)
|
||||||
|
personal-digest-preferences = "SHA512";
|
||||||
|
cert-digest-algo = "SHA512";
|
||||||
|
default-preference-list = "SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed";
|
||||||
|
personal-cipher-preferences = "TWOFISH CAMELLIA256 AES 3DES";
|
||||||
|
};
|
||||||
|
publicKeys = [{
|
||||||
|
source = builtins.fetchurl {
|
||||||
|
url = "https://keys.openpgp.org/vks/v1/by-fingerprint/4FBA930D314A03215E2CDB0A8312C8CAC1BAC289";
|
||||||
|
sha256 = "sha256:10y9xqcy1vyk2p8baay14p3vwdnlwynk0fvfbika65hz2z8yw2cm";
|
||||||
|
};
|
||||||
|
trust = "ultimate";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
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.
|
||||||
|
defaultCacheTtl = 3600;
|
||||||
|
defaultCacheTtlSsh = defaultCacheTtl;
|
||||||
|
maxCacheTtl = 3*3600;
|
||||||
|
maxCacheTtlSsh = maxCacheTtl;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
61
hm/homealone.nix
Normal file
61
hm/homealone.nix
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{ lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
frogeye = {
|
||||||
|
# TODO Move to relevant config file. Rest can probably removed.
|
||||||
|
direnv = {
|
||||||
|
CARGOHOME = "${config.xdg.cacheHome}/cargo"; # There are config in there that we can version if one want
|
||||||
|
DASHT_DOCSETS_DIR = "${config.xdg.cacheHome}/dash_docsets";
|
||||||
|
GRADLE_USER_HOME = "${config.xdg.cacheHome}/gradle";
|
||||||
|
MIX_ARCHIVES = "${config.xdg.cacheHome}/mix/archives";
|
||||||
|
MONO_GAC_PREFIX = "${config.xdg.cacheHome}/mono";
|
||||||
|
PARALLEL_HOME = "${config.xdg.cacheHome}/parallel";
|
||||||
|
TERMINFO = "${config.xdg.configHome}/terminfo";
|
||||||
|
WINEPREFIX = "${config.xdg.stateHome}/wineprefix/default";
|
||||||
|
};
|
||||||
|
junkhome = [
|
||||||
|
"adb"
|
||||||
|
"audacity"
|
||||||
|
"cabal" # TODO May have options but last time I tried it it crashed
|
||||||
|
"itch"
|
||||||
|
"simplescreenrecorder" # Easy fix https://github.com/MaartenBaert/ssr/blob/1556ae456e833992fb6d39d40f7c7d7c337a4160/src/Main.cpp#L252
|
||||||
|
"vd"
|
||||||
|
"wpa_cli"
|
||||||
|
# TODO Maybe we can do something about node-gyp
|
||||||
|
];
|
||||||
|
};
|
||||||
|
home = {
|
||||||
|
activation.createDirenvFolders = lib.hm.dag.entryAfter [ "writeBoundary" ]
|
||||||
|
(lib.strings.concatLines (map (d: "mkdir -p ${d}") (
|
||||||
|
(builtins.attrValues config.frogeye.direnv) ++ [ "${config.xdg.cacheHome}/junkhome" ]
|
||||||
|
)));
|
||||||
|
|
||||||
|
sessionVariables = config.frogeye.direnv;
|
||||||
|
};
|
||||||
|
programs.bash.shellAliases = lib.attrsets.mergeAttrsList (map (p: { "${p}" = "HOME=${config.xdg.cacheHome}/junkhome ${p}"; }) config.frogeye.junkhome);
|
||||||
|
};
|
||||||
|
options.frogeye = {
|
||||||
|
direnv = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
DASHT_DOCSETS_DIR = "''${config.xdg.cacheHome}/dash_docsets";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Environment variables for which the value is a folder that will be automatically created.
|
||||||
|
Useful for keeping programs data out of $HOME for programs that won't create the directory themselves.
|
||||||
|
'';
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
};
|
||||||
|
junkhome = lib.mkOption {
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Program names that will be run with a different HOME so they don't clutter the real one.
|
||||||
|
Useful for programs that don't follow the XDG specification and tend to advertise themselves.
|
||||||
|
'';
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
};
|
||||||
|
# TODO Should make a nix package wrapper instead, so it also works from dmenu
|
||||||
|
};
|
||||||
|
}
|
35
hm/monitoring/default.nix
Normal file
35
hm/monitoring/default.nix
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
htop
|
||||||
|
iftop
|
||||||
|
iotop
|
||||||
|
lsof
|
||||||
|
progress
|
||||||
|
pv
|
||||||
|
speedtest-cli
|
||||||
|
strace
|
||||||
|
];
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
iftop = "iftop -c ${config.xdg.configHome}/iftoprc";
|
||||||
|
tracefiles = ''${pkgs.strace}/bin/strace -f -t -e trace=file'';
|
||||||
|
};
|
||||||
|
xdg = {
|
||||||
|
configFile = {
|
||||||
|
"iftoprc" = {
|
||||||
|
text = ''
|
||||||
|
port-resolution: no
|
||||||
|
promiscuous: no
|
||||||
|
port-display: on
|
||||||
|
link-local: yes
|
||||||
|
use-bytes: yes
|
||||||
|
show-totals: yes
|
||||||
|
log-scale: yes
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
24
hm/nix/default.nix
Normal file
24
hm/nix/default.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
nvd
|
||||||
|
nix-diff
|
||||||
|
nix-tree
|
||||||
|
nix-output-monitor
|
||||||
|
];
|
||||||
|
programs.nix-index = {
|
||||||
|
# For non-NixOS systems
|
||||||
|
enable = false; # TODO Index is impossible to generate, should use https://github.com/nix-community/nix-index-database
|
||||||
|
# but got no luck without flakes
|
||||||
|
enableZshIntegration = true;
|
||||||
|
};
|
||||||
|
nix = {
|
||||||
|
package = lib.mkDefault pkgs.lix;
|
||||||
|
settings = {
|
||||||
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
warn-dirty = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
18
hm/pager/default.nix
Normal file
18
hm/pager/default.nix
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
home = {
|
||||||
|
sessionVariables = {
|
||||||
|
LESSHISTFILE = "${config.xdg.stateHome}/lesshst";
|
||||||
|
LESS = "-R";
|
||||||
|
LESS_TERMCAP_mb = "$(echo $'\\E[1;31m')"; # begin blink
|
||||||
|
LESS_TERMCAP_md = "$(echo $'\\E[1;36m')"; # begin bold
|
||||||
|
LESS_TERMCAP_me = "$(echo $'\\E[0m')"; # reset bold/blink
|
||||||
|
LESS_TERMCAP_se = "$(echo $'\\E[0m')"; # reset reverse video
|
||||||
|
LESS_TERMCAP_so = "$(echo $'\\E[01;44;33m')"; # begin reverse video
|
||||||
|
LESS_TERMCAP_ue = "$(echo $'\\E[0m')"; # reset underline
|
||||||
|
LESS_TERMCAP_us = "$(echo $'\\E[1;32m')"; # begin underline
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
112
hm/password/default.nix
Normal file
112
hm/password/default.nix
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
mod = config.xsession.windowManager.i3.config.modifier;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
pwgen
|
||||||
|
(pkgs.writeShellApplication {
|
||||||
|
name = "install-passwords";
|
||||||
|
runtimeInputs = [ yq gawk moreutils ];
|
||||||
|
text = (lib.strings.concatLines (map
|
||||||
|
(file: ''
|
||||||
|
(
|
||||||
|
echo "===== Preparing to write ${file.path}"
|
||||||
|
temp="$(mktemp --tmpdir="${builtins.dirOf file.path}")"
|
||||||
|
cat "${file.template}" > "$temp"
|
||||||
|
'' + (lib.strings.concatLines (map
|
||||||
|
(password: (if password.selector == null then ''
|
||||||
|
echo "Reading ${password.path} for substituting ${password.variable}"
|
||||||
|
value="$(pass "${password.path}" | head -n1)"
|
||||||
|
'' else ''
|
||||||
|
echo "Reading ${password.path} -> ${password.selector} for substituting ${password.variable}"
|
||||||
|
value="$(pass "${password.path}" | tail -n +2 | yq -r '.${password.selector}')"
|
||||||
|
'') + ''
|
||||||
|
key="${password.variable}"
|
||||||
|
K="$key" V="$value" awk '{ gsub (ENVIRON["K"], ENVIRON["V"]); print }' "$temp" | sponge "$temp"
|
||||||
|
'')
|
||||||
|
(lib.attrsets.attrValues file.passwords))) + ''
|
||||||
|
echo "Moving the file in place"
|
||||||
|
chown "${file.owner}" "$temp"
|
||||||
|
chmod u=r "$temp"
|
||||||
|
mv -f "$temp" "${file.path}"
|
||||||
|
)
|
||||||
|
'')
|
||||||
|
config.frogeye.passwordFiles)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
];
|
||||||
|
programs = {
|
||||||
|
bash.shellAliases = {
|
||||||
|
pw = ''${pkgs.pwgen}/bin/pwgen 32 -y''; # Generate passwords. ln((26*2+10)**32)/ln(2) ≅ 190 bits of entropy
|
||||||
|
};
|
||||||
|
password-store.enable = true;
|
||||||
|
};
|
||||||
|
xsession.windowManager.i3.config.keybindings."${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used";
|
||||||
|
# TODO Try autopass.cr
|
||||||
|
};
|
||||||
|
options = {
|
||||||
|
frogeye.passwordFiles =
|
||||||
|
let
|
||||||
|
defaultvar = "@PASSWORD@";
|
||||||
|
pwtype = { name, ... }: {
|
||||||
|
options = {
|
||||||
|
variable = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "String in the template that will be substituted by the actual password";
|
||||||
|
};
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Path to the password store entry";
|
||||||
|
};
|
||||||
|
selector = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "If set, will parse the password metadata as YML and use selector (yq) instead of the password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
mainConfig = config;
|
||||||
|
in
|
||||||
|
lib.mkOption {
|
||||||
|
default = [ ];
|
||||||
|
type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
|
||||||
|
options = {
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Where to place the file.";
|
||||||
|
};
|
||||||
|
owner = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = mainConfig.home.username;
|
||||||
|
description = "Who will own the file.";
|
||||||
|
};
|
||||||
|
template = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = pkgs.writeTextFile {
|
||||||
|
name = "pwfile-template";
|
||||||
|
text = config.text;
|
||||||
|
};
|
||||||
|
description = "Path to the template used to make the file. Exclusive with `text`.";
|
||||||
|
};
|
||||||
|
text = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = defaultvar;
|
||||||
|
description = "Content of the template used to make the file. Exclusive with `template`.";
|
||||||
|
};
|
||||||
|
passwords = lib.mkOption {
|
||||||
|
default = lib.optionalAttrs (config.password != null) { ${defaultvar} = config.password; };
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule pwtype);
|
||||||
|
description = "Paths to passwords that will substitute the variables in the template. Exclusive with `password`";
|
||||||
|
};
|
||||||
|
password = lib.mkOption {
|
||||||
|
type = lib.types.submodule ({ ... }@args: pwtype (args // { name = defaultvar; }));
|
||||||
|
description = "Path to password that will substitute '@PASSWORD@' in the template. Exclusive with `passwords`.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
22
hm/prompt/default.nix
Normal file
22
hm/prompt/default.nix
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.programs.less.enable {
|
||||||
|
programs.powerline-go = {
|
||||||
|
enable = true;
|
||||||
|
modules = [ "user" "host" "venv" "cwd" "perms" "nix-shell" "git" ];
|
||||||
|
modulesRight = [ "jobs" "exit" "duration" ];
|
||||||
|
settings = {
|
||||||
|
colorize-hostname = true;
|
||||||
|
hostname-only-if-ssh = true;
|
||||||
|
max-width = 25;
|
||||||
|
cwd-max-dir-size = 10;
|
||||||
|
duration = "$( test -n \"$__TIMER\" && echo $(( $EPOCHREALTIME - $\{__TIMER:-EPOCHREALTIME})) || echo 0 )";
|
||||||
|
# UPST Implement this properly in home-manager, would allow for bash support
|
||||||
|
};
|
||||||
|
extraUpdatePS1 = ''
|
||||||
|
unset __TIMER
|
||||||
|
echo -en "\033]0; $USER@$HOST $PWD\007"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
36
hm/rebuild/default.nix
Normal file
36
hm/rebuild/default.nix
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{ pkgs, config, ... }:
|
||||||
|
{
|
||||||
|
home.packages = [
|
||||||
|
pkgs.update-local-flakes
|
||||||
|
(pkgs.writeShellApplication {
|
||||||
|
name = "rb";
|
||||||
|
text = ''
|
||||||
|
verb="switch"
|
||||||
|
if [ "$#" -ge 1 ]
|
||||||
|
then
|
||||||
|
verb="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
nixos_flake="$(readlink -f /etc/nixos)"
|
||||||
|
if [ -f "$nixos_flake/flake.nix" ]
|
||||||
|
then
|
||||||
|
update-local-flakes "$nixos_flake"
|
||||||
|
nix run "$nixos_flake#nixosRebuild" -- "$verb" "$@"
|
||||||
|
fi
|
||||||
|
# TODO Fix nix-on-droid and home-manager
|
||||||
|
# hm_flake="${config.xdg.configHome}/home-manager/flake.nix"
|
||||||
|
# if [ -f "$hm_flake" ]
|
||||||
|
# then
|
||||||
|
# update-local-flakes "$hm_flake"
|
||||||
|
# home-manager "$verb" "$@"
|
||||||
|
# fi
|
||||||
|
# nod_flake="${config.xdg.configHome}/nix-on-droid/flake.nix"
|
||||||
|
# if [ -f "$nod_flake" ]
|
||||||
|
# then
|
||||||
|
# update-local-flakes "$nod_flake"
|
||||||
|
# nix-on-droid "$verb" --flake "$(dirname "$nod_flake")" "$@"
|
||||||
|
# fi
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
265
hm/scripts/jlab
Executable file
265
hm/scripts/jlab
Executable file
|
@ -0,0 +1,265 @@
|
||||||
|
#!/usr/bin/env cached-nix-shell
|
||||||
|
#! nix-shell -i python3
|
||||||
|
#! nix-shell -p python3 python3Packages.pydantic
|
||||||
|
# vim: filetype=python
|
||||||
|
|
||||||
|
"""
|
||||||
|
glab wrapper for jujutsu,
|
||||||
|
with some convinience features.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
import typing_extensions
|
||||||
|
|
||||||
|
|
||||||
|
class GitLabMR(pydantic.BaseModel):
|
||||||
|
"""
|
||||||
|
Represents a GitLab Merge Request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
title: str
|
||||||
|
source_branch: str
|
||||||
|
target_branch: str
|
||||||
|
project_id: int
|
||||||
|
source_project_id: int
|
||||||
|
target_project_id: int
|
||||||
|
|
||||||
|
@pydantic.model_validator(mode="after")
|
||||||
|
def same_project(self) -> typing_extensions.Self:
|
||||||
|
if not (self.project_id == self.source_project_id == self.target_project_id):
|
||||||
|
raise NotImplementedError("Different project ids")
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def glab_get_mr(branch: str) -> GitLabMR:
|
||||||
|
"""
|
||||||
|
Get details about a GitLab MR.
|
||||||
|
"""
|
||||||
|
sp = subprocess.run(
|
||||||
|
["glab", "mr", "view", branch, "--output", "json"], stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
sp.check_returncode()
|
||||||
|
return GitLabMR.model_validate_json(sp.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuType:
|
||||||
|
"""
|
||||||
|
Utility to work with Template types.
|
||||||
|
https://martinvonz.github.io/jj/latest/templates/
|
||||||
|
"""
|
||||||
|
|
||||||
|
FIELD_SEPARATOR: typing.ClassVar[str] = "\0"
|
||||||
|
ESCAPED_SEPARATOR: typing.ClassVar[str] = r"\0"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def template(base: str, type_: typing.Type) -> str:
|
||||||
|
"""
|
||||||
|
Generates a --template string that is machine-parseable for a given type.
|
||||||
|
"""
|
||||||
|
if typing.get_origin(type_) == list:
|
||||||
|
# If we have a list, prepend it with the number of items
|
||||||
|
# so we know how many fields we should consume.
|
||||||
|
(subtype,) = typing.get_args(type_)
|
||||||
|
subtype = typing.cast(typing.Type, subtype)
|
||||||
|
return (
|
||||||
|
f'{base}.len()++"{JujutsuType.ESCAPED_SEPARATOR}"'
|
||||||
|
f'++{base}.map(|l| {JujutsuType.template("l", subtype)})'
|
||||||
|
)
|
||||||
|
elif issubclass(type_, JujutsuObject):
|
||||||
|
return type_.template(base)
|
||||||
|
else:
|
||||||
|
return f'{base}++"{JujutsuType.ESCAPED_SEPARATOR}"'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(stack: list[str], type_: typing.Type) -> typing.Any:
|
||||||
|
"""
|
||||||
|
Unserialize the result of a template to a given type.
|
||||||
|
Needs to be provided the template as a list splitted by the field separator.
|
||||||
|
It will consume the fields it needs.
|
||||||
|
"""
|
||||||
|
if typing.get_origin(type_) == list:
|
||||||
|
(subtype,) = typing.get_args(type_)
|
||||||
|
subtype = typing.cast(typing.Type, subtype)
|
||||||
|
len = int(stack.pop(0))
|
||||||
|
return [JujutsuType.parse(stack, subtype) for i in range(len)]
|
||||||
|
elif issubclass(type_, JujutsuObject):
|
||||||
|
return type_.parse(stack)
|
||||||
|
else:
|
||||||
|
return stack.pop(0)
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuObject(pydantic.BaseModel):
|
||||||
|
@classmethod
|
||||||
|
def template(cls, base: str) -> str:
|
||||||
|
temp = []
|
||||||
|
for k, v in cls.model_fields.items():
|
||||||
|
key = f"{base}.{k}()"
|
||||||
|
temp.append(JujutsuType.template(key, v.annotation))
|
||||||
|
return "++".join(temp)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, stack: list[str]) -> typing_extensions.Self:
|
||||||
|
ddict = dict()
|
||||||
|
for k, v in cls.model_fields.items():
|
||||||
|
ddict[k] = JujutsuType.parse(stack, v.annotation)
|
||||||
|
return cls(**ddict)
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuShortestIdPrefix(JujutsuObject):
|
||||||
|
prefix: str
|
||||||
|
rest: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full(self) -> str:
|
||||||
|
return self.prefix + self.rest
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuChangeId(JujutsuObject):
|
||||||
|
shortest: JujutsuShortestIdPrefix
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full(self) -> str:
|
||||||
|
return self.shortest.full
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuRefName(JujutsuObject):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class JujutsuCommit(JujutsuObject):
|
||||||
|
change_id: JujutsuChangeId
|
||||||
|
bookmarks: list[JujutsuRefName]
|
||||||
|
|
||||||
|
|
||||||
|
class Jujutsu:
|
||||||
|
"""
|
||||||
|
Represents a Jujutsu repo.
|
||||||
|
Since there's no need for multi-repo, this is just the one in the current directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, *args: str, **kwargs: typing.Any) -> subprocess.CompletedProcess:
|
||||||
|
cmd = ["jj"]
|
||||||
|
cmd.extend(args)
|
||||||
|
sp = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
sp.check_returncode()
|
||||||
|
return sp
|
||||||
|
|
||||||
|
def log(self, revset: str = "@") -> list[JujutsuCommit]:
|
||||||
|
cmd = [
|
||||||
|
"log",
|
||||||
|
"-r",
|
||||||
|
revset,
|
||||||
|
"--no-graph",
|
||||||
|
"-T",
|
||||||
|
JujutsuCommit.template("self"),
|
||||||
|
]
|
||||||
|
sp = self.run(*cmd, stdout=subprocess.PIPE)
|
||||||
|
stack = sp.stdout.decode().split(JujutsuType.FIELD_SEPARATOR)
|
||||||
|
assert stack[-1] == "", "No trailing NUL byte"
|
||||||
|
stack.pop()
|
||||||
|
commits = []
|
||||||
|
while len(stack):
|
||||||
|
commits.append(JujutsuCommit.parse(stack))
|
||||||
|
return commits
|
||||||
|
|
||||||
|
|
||||||
|
jj = Jujutsu()
|
||||||
|
|
||||||
|
|
||||||
|
def current_bookmark() -> JujutsuRefName | None:
|
||||||
|
"""
|
||||||
|
Replacement of git's current branch concept working with jj.
|
||||||
|
Needed for commodity features, such as not requiring to type the MR mumber / branch
|
||||||
|
for `glab mr`, or automatically advance the bookmark to the head before pushing.
|
||||||
|
"""
|
||||||
|
bookmarks = []
|
||||||
|
for commit in jj.log("reachable(@, trunk()..)"):
|
||||||
|
bookmarks.extend(commit.bookmarks)
|
||||||
|
|
||||||
|
if len(bookmarks) > 1:
|
||||||
|
raise NotImplementedError("Multiple bookmarks on trunk branch") # TODO
|
||||||
|
# If there's a split in the tree: TBD
|
||||||
|
# If there's no bookmark ahead: the bookmark behind
|
||||||
|
# If there's a bookmark ahead: that one
|
||||||
|
# (needs adjusting of push so it doesn't advance anything then)
|
||||||
|
|
||||||
|
if bookmarks:
|
||||||
|
return bookmarks[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def to_glab() -> None:
|
||||||
|
"""
|
||||||
|
Pass the remaining arguments to glab.
|
||||||
|
"""
|
||||||
|
sp = subprocess.run(["glab"] + sys.argv[1:])
|
||||||
|
sys.exit(sp.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) <= 1:
|
||||||
|
to_glab()
|
||||||
|
elif sys.argv[1] in ("merge", "mr"):
|
||||||
|
if len(sys.argv) <= 2:
|
||||||
|
to_glab()
|
||||||
|
elif sys.argv[2] == "checkout":
|
||||||
|
# Bypass the original checkout command so it doesn't run git commands.
|
||||||
|
# If there's no commit on the branch, add one with the MR title
|
||||||
|
# so jj has a current bookmark.
|
||||||
|
mr = glab_get_mr(sys.argv[3])
|
||||||
|
jj.run("git", "fetch")
|
||||||
|
if len(jj.log(f"{mr.source_branch} | {mr.target_branch}")) == 1:
|
||||||
|
title = re.sub(r"^(WIP|Draft): ", "", mr.title)
|
||||||
|
jj.run("new", mr.source_branch)
|
||||||
|
jj.run("describe", "-m", title)
|
||||||
|
jj.run("bookmark", "move", mr.source_branch)
|
||||||
|
else:
|
||||||
|
jj.run("edit", mr.source_branch)
|
||||||
|
elif sys.argv[2] in (
|
||||||
|
# If no MR number/branch is given, insert the current bookmark,
|
||||||
|
# as the current branch concept doesn't exist in jj
|
||||||
|
"approve",
|
||||||
|
"approvers",
|
||||||
|
"checkout",
|
||||||
|
"close",
|
||||||
|
"delete",
|
||||||
|
"diff",
|
||||||
|
"issues",
|
||||||
|
"merge",
|
||||||
|
"note",
|
||||||
|
"rebase",
|
||||||
|
"revoke",
|
||||||
|
"subscribe",
|
||||||
|
"todo",
|
||||||
|
"unsubscribe",
|
||||||
|
"update",
|
||||||
|
"view",
|
||||||
|
):
|
||||||
|
if len(sys.argv) <= 3 or sys.argv[3].startswith("-"):
|
||||||
|
bookmark = current_bookmark()
|
||||||
|
if bookmark:
|
||||||
|
sys.argv.insert(3, bookmark.name)
|
||||||
|
to_glab()
|
||||||
|
else:
|
||||||
|
to_glab()
|
||||||
|
elif sys.argv[1] == "push":
|
||||||
|
# Advance the current branch to the head and push
|
||||||
|
bookmark = current_bookmark()
|
||||||
|
if not bookmark:
|
||||||
|
raise RuntimeError("Couldn't find a current branch")
|
||||||
|
heads = jj.log("heads(@::)")
|
||||||
|
if len(heads) != 1:
|
||||||
|
raise RuntimeError("Multiple heads") # Or none if something goes horribly wrong
|
||||||
|
head = heads[0]
|
||||||
|
jj.run("bookmark", "set", bookmark.name, "-r", head.change_id.full)
|
||||||
|
jj.run("git", "push", "--bookmark", bookmark.name)
|
||||||
|
# TODO Sign https://github.com/martinvonz/jj/issues/4712
|
||||||
|
else:
|
||||||
|
to_glab()
|
||||||
|
|
||||||
|
# TODO Autocomplete
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i bash --pure
|
#! nix-shell -i bash --pure
|
||||||
#! nix-shell -p bash jq curl findutils coreutils
|
#! nix-shell -p bash jq curl cacert findutils coreutils
|
||||||
|
|
||||||
set -euo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
url="https://ip.frogeye.fr/json"
|
url="https://ip.frogeye.fr/json"
|
||||||
cachedir="$HOME/.cache/lip"
|
cachedir="$HOME/.cache/lip"
|
||||||
|
@ -16,7 +16,6 @@ then
|
||||||
jq_sel="$@"
|
jq_sel="$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ -n "$ip" ]
|
if [ -n "$ip" ]
|
||||||
then
|
then
|
||||||
cachefile="$cachedir/$ip"
|
cachefile="$cachedir/$ip"
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i bash --pure
|
#! nix-shell -i bash --pure
|
||||||
#! nix-shell -p bash coreutils imagemagick libjpeg optipng ffmpeg diffutils
|
#! nix-shell -p bash coreutils imagemagick libjpeg optipng ffmpeg diffutils
|
||||||
|
# vim: filetype=sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
# Optimizes everything the script can find in a folder,
|
# Optimizes everything the script can find in a folder,
|
||||||
# meaning it will compress files as much as possible,
|
# meaning it will compress files as much as possible,
|
||||||
|
@ -13,14 +15,13 @@
|
||||||
# TODO Lots of dupplicated code there
|
# TODO Lots of dupplicated code there
|
||||||
# TODO Maybe replace part with https://github.com/toy/image_optim?
|
# TODO Maybe replace part with https://github.com/toy/image_optim?
|
||||||
|
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
||||||
dir=${1:-$PWD}
|
dir=${1:-$PWD}
|
||||||
total=$(mktemp)
|
total=$(mktemp)
|
||||||
echo -n 0 > $total
|
echo -n 0 > "$total"
|
||||||
|
|
||||||
function showtotal {
|
function showtotal {
|
||||||
echo "Total saved: $(cat "$total") bytes"
|
echo "Total saved: $(cat "$total") bytes"
|
||||||
rm $total
|
rm "$total"
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +29,11 @@ trap showtotal SIGTERM SIGINT SIGFPE
|
||||||
|
|
||||||
function doReplace { # candidate original
|
function doReplace { # candidate original
|
||||||
mv "$c" "$o"
|
mv "$c" "$o"
|
||||||
saved=$(($os - $cs))
|
saved=$((os - cs))
|
||||||
perc=$((100 * $saved / $os))
|
perc=$((100 * saved / os))
|
||||||
echo "→ $os ⇒ $cs (saved $saved bytes, or ${perc}%)"
|
echo "→ $os ⇒ $cs (saved $saved bytes, or ${perc}%)"
|
||||||
newtotal=$(($(cat $total) + $saved))
|
newtotal=$(($(cat "$total") + saved))
|
||||||
echo -n $newtotal > $total
|
echo -n $newtotal > "$total"
|
||||||
}
|
}
|
||||||
|
|
||||||
function replace { # candidate original
|
function replace { # candidate original
|
||||||
|
@ -52,17 +53,17 @@ function replace { # candidate original
|
||||||
# Size verifications
|
# Size verifications
|
||||||
cs=$(wc -c "$c" | cut -d' ' -f1)
|
cs=$(wc -c "$c" | cut -d' ' -f1)
|
||||||
os=$(wc -c "$o" | cut -d' ' -f1)
|
os=$(wc -c "$o" | cut -d' ' -f1)
|
||||||
if [ $cs -le 0 ]; then
|
if [ "$cs" -le 0 ]; then
|
||||||
echo "→ Candidate is empty, skipping!"
|
echo "→ Candidate is empty, skipping!"
|
||||||
rm "$c"
|
rm "$c"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [ $cs -eq $os ]; then
|
if [ "$cs" -eq "$os" ]; then
|
||||||
echo "→ Candidate weight the same, skipping."
|
echo "→ Candidate weight the same, skipping."
|
||||||
rm "$c"
|
rm "$c"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [ $cs -gt $os ]; then
|
if [ "$cs" -gt "$os" ]; then
|
||||||
echo "→ Candidate is larger, skipping."
|
echo "→ Candidate is larger, skipping."
|
||||||
rm "$c"
|
rm "$c"
|
||||||
return
|
return
|
||||||
|
@ -71,76 +72,75 @@ function replace { # candidate original
|
||||||
doReplace "$c" "$o"
|
doReplace "$c" "$o"
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceImg { # candidate original
|
# function replaceImg { # candidate original
|
||||||
# With bitmap verification
|
# # With bitmap verification
|
||||||
|
#
|
||||||
c="$1"
|
# c="$1"
|
||||||
o="$2"
|
# o="$2"
|
||||||
|
#
|
||||||
# File verifications
|
# # File verifications
|
||||||
if [ ! -f "$o" ]; then
|
# if [ ! -f "$o" ]; then
|
||||||
echo "→ Original is inexistant, skipping!"
|
# echo "→ Original is inexistant, skipping!"
|
||||||
return
|
# return
|
||||||
fi
|
# fi
|
||||||
if [ ! -f "$c" ]; then
|
# if [ ! -f "$c" ]; then
|
||||||
echo "→ Candidate is inexistant, skipping!"
|
# echo "→ Candidate is inexistant, skipping!"
|
||||||
return
|
# return
|
||||||
fi
|
# fi
|
||||||
|
#
|
||||||
# Size verifications
|
# # Size verifications
|
||||||
cs=$(wc -c "$c" | cut -d' ' -f1)
|
# cs=$(wc -c "$c" | cut -d' ' -f1)
|
||||||
os=$(wc -c "$o" | cut -d' ' -f1)
|
# os=$(wc -c "$o" | cut -d' ' -f1)
|
||||||
if [ $cs -le 0 ]; then
|
# if [ $cs -le 0 ]; then
|
||||||
echo "→ Candidate is empty, skipping!"
|
# echo "→ Candidate is empty, skipping!"
|
||||||
rm "$c"
|
# rm "$c"
|
||||||
return
|
# return
|
||||||
fi
|
# fi
|
||||||
if [ $cs -eq $os ]; then
|
# if [ $cs -eq $os ]; then
|
||||||
echo "→ Candidate weight the same, skipping."
|
# echo "→ Candidate weight the same, skipping."
|
||||||
rm "$c"
|
# rm "$c"
|
||||||
return
|
# return
|
||||||
fi
|
# fi
|
||||||
if [ $cs -gt $os ]; then
|
# if [ $cs -gt $os ]; then
|
||||||
echo "→ Candidate is larger, skipping."
|
# echo "→ Candidate is larger, skipping."
|
||||||
rm "$c"
|
# rm "$c"
|
||||||
return
|
# return
|
||||||
fi
|
# fi
|
||||||
|
#
|
||||||
# Bitmap verification
|
# # Bitmap verification
|
||||||
ppmc="$(mktemp --suffix .ppm)"
|
# ppmc="$(mktemp --suffix .ppm)"
|
||||||
ppmo="$(mktemp --suffix .ppm)"
|
# ppmo="$(mktemp --suffix .ppm)"
|
||||||
convert "$c" "$ppmc"
|
# convert "$c" "$ppmc"
|
||||||
convert "$o" "$ppmo"
|
# convert "$o" "$ppmo"
|
||||||
|
#
|
||||||
if cmp --silent "$ppmo" "$ppmc"; then
|
# if cmp --silent "$ppmo" "$ppmc"; then
|
||||||
doReplace "$c" "$o"
|
# doReplace "$c" "$o"
|
||||||
else
|
# else
|
||||||
echo "→ Candidate don't have the same bit map as original, skipping!"
|
# echo "→ Candidate don't have the same bit map as original, skipping!"
|
||||||
fi
|
# fi
|
||||||
rm -f "$ppmc" "$ppmo" "$c"
|
# rm -f "$ppmc" "$ppmo" "$c"
|
||||||
|
#
|
||||||
}
|
# }
|
||||||
|
|
||||||
# JPEG (requires jpegtran)
|
# JPEG (requires jpegtran)
|
||||||
while read image
|
while read -r image
|
||||||
do
|
do
|
||||||
if [ -z "$image" ]; then continue; fi
|
if [ -z "$image" ]; then continue; fi
|
||||||
echo Processing $image
|
echo Processing "$image"
|
||||||
|
|
||||||
prog=$(mktemp --suffix .jpg)
|
prog=$(mktemp --suffix .jpg)
|
||||||
jpegtran -copy all -progressive "$image" > "$prog"
|
jpegtran -copy all -progressive "$image" > "$prog"
|
||||||
echo "→ Progressive done"
|
echo "→ Progressive done"
|
||||||
progs=$(wc -c "$prog" | cut -d' ' -f1)
|
|
||||||
replace "$prog" "$image"
|
replace "$prog" "$image"
|
||||||
|
|
||||||
|
|
||||||
done <<< "$(find "$dir/" -type f -iregex ".+.jpe?g$")"
|
done <<< "$(find "$dir/" -type f -iregex ".+.jpe?g$")"
|
||||||
|
|
||||||
# PNG (requires optipng)
|
# PNG (requires optipng)
|
||||||
while read image
|
while read -r image
|
||||||
do
|
do
|
||||||
if [ -z "$image" ]; then continue; fi
|
if [ -z "$image" ]; then continue; fi
|
||||||
echo Processing $image
|
echo Processing "$image"
|
||||||
|
|
||||||
temp=$(mktemp --suffix .png)
|
temp=$(mktemp --suffix .png)
|
||||||
cp "$image" "$temp"
|
cp "$image" "$temp"
|
||||||
|
@ -152,14 +152,13 @@ do
|
||||||
done <<< "$(find "$dir/" -type f -iname "*.png")"
|
done <<< "$(find "$dir/" -type f -iname "*.png")"
|
||||||
|
|
||||||
# FLAC (requires ffmpeg)
|
# FLAC (requires ffmpeg)
|
||||||
while read music
|
while read -r music
|
||||||
do
|
do
|
||||||
if [ -z "$music" ]; then continue; fi
|
if [ -z "$music" ]; then continue; fi
|
||||||
echo Processing $music
|
echo "Processing $music"
|
||||||
|
|
||||||
temp=$(mktemp --suffix .flac)
|
temp=$(mktemp --suffix .flac)
|
||||||
cp "$music" "$temp"
|
ffmpeg -nostdin -y -i "$music" -compression_level 8 "$temp"
|
||||||
ffmpeg -8 -o "$temp"
|
|
||||||
echo "→ Optimize done"
|
echo "→ Optimize done"
|
||||||
|
|
||||||
replace "$temp" "$music"
|
replace "$temp" "$music"
|
||||||
|
@ -187,6 +186,6 @@ done <<< "$(find "$dir/" -type f -iname "*.flac")"
|
||||||
# - I might want to keep editor data and/or ids for some of them
|
# - I might want to keep editor data and/or ids for some of them
|
||||||
# So rather use scour explicitely when needed
|
# So rather use scour explicitely when needed
|
||||||
|
|
||||||
${SCRIPT_DIR}/cleandev
|
cleandev
|
||||||
|
|
||||||
showtotal
|
showtotal
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i bash --pure
|
#! nix-shell -i bash
|
||||||
#! nix-shell -p bash pdftk inkscape gnused coreutils file
|
#! nix-shell -p bash pdftk inkscape gnused coreutils file
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
# Utility to write over a PDF file pages
|
# Utility to write over a PDF file pages
|
||||||
|
|
||||||
# TODO Inkscape vodoo: Put the original in its own layer and skip when merging
|
# TODO Inkscape vodoo: Put the original in its own layer and skip when merging
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i python3 --pure
|
#! nix-shell -i python3 --pure
|
||||||
#! nix-shell -p python3 python3Packages.coloredlogs python3Packages.r128gain
|
#! nix-shell -p python3 python3Packages.coloredlogs r128gain
|
||||||
|
|
||||||
# TODO r128gain is not maintainted anymore
|
# TODO r128gain is not maintainted anymore
|
||||||
|
# 24.05 rsgain replaces it, does the same job as I do with albums
|
||||||
|
|
||||||
# Normalisation is done at the default of each program,
|
# Normalisation is done at the default of each program,
|
||||||
# which is usually -89.0 dB
|
# which is usually -89.0 dB
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue