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
|
||||
*/system
|
||||
*/vm
|
||||
*/vmWithBootLoader
|
||||
result
|
||||
*.qcow2
|
||||
|
|
|
@ -28,7 +28,6 @@ It is built on top of the Nix ecosystem
|
|||
## Scripts
|
||||
|
||||
They all have a `-h` flag.
|
||||
Except `add_channels.sh`, which should be removed as soon as I migrate to Flakes.
|
||||
|
||||
## 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
|
||||
#! nix-shell -i bash
|
||||
#! nix-shell -p bash nix-output-monitor
|
||||
#! nix-shell -p nix
|
||||
|
||||
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 NixOS configuration on the local machine."
|
||||
echo "Usage: $0 [-h|-e|-b] [flake-uri#]name"
|
||||
echo "Build a NixOS configuration on the local machine."
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " profile: OS/disk profile to use"
|
||||
|
@ -19,7 +19,7 @@ function help {
|
|||
echo " -b: Build a virtual machine with boot loader."
|
||||
}
|
||||
|
||||
attr=system
|
||||
arg=build
|
||||
while getopts "hvb" OPTION
|
||||
do
|
||||
case "$OPTION" in
|
||||
|
@ -28,10 +28,10 @@ do
|
|||
exit 0
|
||||
;;
|
||||
v)
|
||||
attr=vm
|
||||
arg=build-vm
|
||||
;;
|
||||
b)
|
||||
attr=vmWithBootLoader
|
||||
arg=build-vm-with-bootloader
|
||||
;;
|
||||
?)
|
||||
help
|
||||
|
@ -39,29 +39,35 @@ do
|
|||
;;
|
||||
esac
|
||||
done
|
||||
shift "$(($OPTIND -1))"
|
||||
shift "$((OPTIND -1))"
|
||||
|
||||
if [ "$#" -ne 1 ]
|
||||
then
|
||||
help
|
||||
exit 2
|
||||
fi
|
||||
profile="$1"
|
||||
|
||||
profile_dir="${SCRIPT_DIR}/${profile}"
|
||||
if [ ! -d "$profile_dir" ]
|
||||
if [[ "$1" == *"#"* ]]
|
||||
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
|
||||
|
||||
nixos_config="${profile_dir}/os.nix"
|
||||
if [ ! -f "$nixos_config" ]
|
||||
if [ ! -f "$flake_uri/flake.nix" ]
|
||||
then
|
||||
echo "NixOS configuration not found."
|
||||
echo "Flake not found."
|
||||
fi
|
||||
|
||||
flake="${flake_uri}#${name}"
|
||||
|
||||
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
|
||||
|
||||
# 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 = {
|
||||
disk = {
|
||||
"${name}" = {
|
||||
"${config.frogeye.name}" = {
|
||||
type = "disk";
|
||||
device = "/dev/disk/by-id/${id}";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
|
@ -25,7 +27,7 @@
|
|||
size = "100%";
|
||||
content = {
|
||||
type = "luks";
|
||||
name = "${name}";
|
||||
name = "${config.frogeye.name}";
|
||||
passwordFile = passwordFile;
|
||||
settings = {
|
||||
# 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:
|
||||
# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots
|
||||
let
|
||||
backup_subvolumes = [ "nixos" "home.rapido" ];
|
||||
backup_subvolumes = [ "nixos" "home.rapido" "home.nixos" ];
|
||||
backup_app = pkgs.writeShellApplication {
|
||||
name = "backup-subvolume";
|
||||
runtimeInputs = with pkgs; [ coreutils btrfs-progs ];
|
||||
text = builtins.readFile ./backup.sh;
|
||||
};
|
||||
snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" ];
|
||||
snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" "home.nixos" ];
|
||||
in
|
||||
{
|
||||
services =
|
||||
|
@ -28,11 +28,11 @@ in
|
|||
# cleanup hourly snapshots after some time
|
||||
TIMELINE_CLEANUP = true;
|
||||
TIMELINE_MIN_AGE = 1800;
|
||||
TIMELINE_LIMIT_HOURLY = 24;
|
||||
TIMELINE_LIMIT_DAILY = 31;
|
||||
TIMELINE_LIMIT_WEEKLY = 8;
|
||||
TIMELINE_LIMIT_MONTHLY = 0;
|
||||
TIMELINE_LIMIT_YEARLY = 0;
|
||||
TIMELINE_LIMIT_HOURLY = "24";
|
||||
TIMELINE_LIMIT_DAILY = "31";
|
||||
TIMELINE_LIMIT_WEEKLY = "8";
|
||||
TIMELINE_LIMIT_MONTHLY = "0";
|
||||
TIMELINE_LIMIT_YEARLY = "0";
|
||||
|
||||
# cleanup empty pre-post-pairs
|
||||
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 Not relatime everywhere, thank you
|
||||
# TODO Default options
|
||||
let
|
||||
btrfs_args_hdd = [
|
||||
btrfs_args_ssd = [
|
||||
"rw"
|
||||
"relatime"
|
||||
"compress=zstd:3"
|
||||
"space_cache"
|
||||
"ssd"
|
||||
];
|
||||
btrfs_args_ssd = btrfs_args_hdd ++ [ "ssd" ];
|
||||
passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
|
||||
in
|
||||
{
|
||||
disko.devices = {
|
||||
disk = {
|
||||
razmo = {
|
||||
type = "disk";
|
||||
device = "/dev/disk/by-id/ata-ST1000LM048-2E7172_WKP8925H";
|
||||
device = "/dev/disk/by-id/ata-SDLF1DAR-960G-1HA1_A027C1A3";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
ESP = {
|
||||
# Needs enough to store multiple kernel generations
|
||||
size = "512M";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
mountOptions = [
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
};
|
||||
swap = {
|
||||
priority = 10;
|
||||
start = "2048";
|
||||
size = "6G";
|
||||
size = "8G";
|
||||
content = {
|
||||
type = "swap";
|
||||
randomEncryption = true;
|
||||
|
@ -32,81 +44,29 @@ in
|
|||
# hibernation image is saved. That's what I'm doing with Arch,
|
||||
# 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/fstab: defaults,pri=100
|
||||
};
|
||||
};
|
||||
nixosboot = {
|
||||
priority = 15;
|
||||
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;
|
||||
luks = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "luks";
|
||||
name = "razmo";
|
||||
passwordFile = passwordFile;
|
||||
settings = {
|
||||
# keyFile = "/etc/keys/razmo";
|
||||
allowDiscards = true;
|
||||
};
|
||||
content = {
|
||||
type = "btrfs";
|
||||
# extraArgs = [ "-f" ];
|
||||
extraArgs = [ "-f" ];
|
||||
mountpoint = "/mnt/razmo";
|
||||
mountOptions = btrfs_args_hdd;
|
||||
subvolumes = {
|
||||
"home.razmo" = {
|
||||
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";
|
||||
mountOptions = btrfs_args_ssd;
|
||||
subvolumes = {
|
||||
archlinux = {
|
||||
mountpoint = "/mnt/old";
|
||||
mountOptions = btrfs_args_ssd;
|
||||
};
|
||||
# Should be temporary, to make sure we can revert to Arch anytime
|
||||
"home.nixos" = {
|
||||
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 = {
|
||||
desktop.xorg = true;
|
||||
desktop = {
|
||||
xorg = true;
|
||||
};
|
||||
dev = {
|
||||
ansible = true;
|
||||
c = true;
|
||||
docker = true;
|
||||
fpga = true;
|
||||
perl = true;
|
||||
php = true;
|
||||
python = true;
|
||||
vm = true;
|
||||
};
|
||||
extra = 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 = [
|
||||
<nixos-hardware/dell/g3/3779>
|
||||
];
|
||||
config = {
|
||||
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
|
||||
boot.loader = {
|
||||
loader = {
|
||||
efi.canTouchEfiVariables = lib.mkDefault true;
|
||||
grub = {
|
||||
enable = true;
|
||||
|
@ -14,4 +32,82 @@
|
|||
# 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, ... }:
|
||||
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
|
||||
{
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
programs =
|
||||
let
|
||||
commonRc = lib.strings.concatLines ([
|
||||
''
|
||||
# Colored ls
|
||||
# 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}";
|
||||
frogeye.hooks.lock = ''
|
||||
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
|
||||
'';
|
||||
programs = {
|
||||
home-manager.enable = true;
|
||||
bat = {
|
||||
enable = true;
|
||||
config.style = "full";
|
||||
};
|
||||
treatsHomeAsJunk = [
|
||||
# Programs that think $HOME is a reasonable place to put their junk
|
||||
# and don't allow the user to change those questionable choices
|
||||
"adb"
|
||||
"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 = {
|
||||
bash.shellAliases = {
|
||||
# Replacement commands
|
||||
# ls = "lsd"; # lsd is suuuper slow for large directories
|
||||
cat = "bat -pp";
|
||||
|
||||
# Completion for existing commands
|
||||
ls = "ls -h --color=auto";
|
||||
mkdir = "mkdir -v";
|
||||
# cp = "cp -i"; # Disabled because conflicts with the ZSH/Bash one. This separation is confusing I swear.
|
||||
mv = "mv -iv";
|
||||
|
@ -91,7 +23,8 @@ in
|
|||
ffmpeg = "ffmpeg -hide_banner";
|
||||
ffprobe = "ffprobe -hide_banner";
|
||||
ffplay = "ffplay -hide_banner";
|
||||
# TODO Add ipython --no-confirm-exit --pdb
|
||||
numbat = "numbat --intro-banner off";
|
||||
insect = "numbat";
|
||||
|
||||
# Frequent mistakes
|
||||
sl = "ls";
|
||||
|
@ -101,20 +34,12 @@ in
|
|||
please = "sudo";
|
||||
|
||||
# Shortcuts for commonly used commands
|
||||
# ll = "ls -l"; # Disabled because would overwrite the colored one
|
||||
# la = "ls -la"; # Eh maybe it's not that bad, but for now let's keep compatibility
|
||||
ll = "lsd -l";
|
||||
la = "lsd -la";
|
||||
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
|
||||
vi = "nvim";
|
||||
vim = "nvim";
|
||||
wol = "wakeonlan"; # TODO Really, isn't wol better? Also wtf Arch aliases to pass because neither is installed anyways x)
|
||||
mutt = "neomutt";
|
||||
|
||||
|
@ -130,54 +55,31 @@ in
|
|||
|
||||
# Imported from scripts
|
||||
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'';
|
||||
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;
|
||||
enableAutosuggestions = true;
|
||||
enableCompletion = true;
|
||||
syntaxHighlighting.enable = true;
|
||||
historySubstringSearch.enable = true;
|
||||
initExtra = lib.strings.concatLines [
|
||||
commonRc
|
||||
(builtins.readFile ./zshrc.sh)
|
||||
];
|
||||
defaultKeymap = "viins";
|
||||
history = {
|
||||
size = historySize;
|
||||
save = historySize;
|
||||
path = historyFile;
|
||||
expireDuplicatesFirst = true;
|
||||
enableBashIntegration = true;
|
||||
enableZshIntegration = true;
|
||||
};
|
||||
lsd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
size = "short";
|
||||
};
|
||||
colors = {
|
||||
# Base16 only, so it reuses the current theme.
|
||||
date = { day-old = 4; hour-old = 6; older = 5; };
|
||||
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; };
|
||||
group = 6;
|
||||
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 = {
|
||||
enable = true;
|
||||
|
@ -185,101 +87,18 @@ in
|
|||
enableZshIntegration = true;
|
||||
# UPST This thing put stuff in .dircolors when it actually doesn't have to
|
||||
};
|
||||
powerline-go = {
|
||||
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";
|
||||
}];
|
||||
};
|
||||
git.enable = true;
|
||||
gpg.enable = true;
|
||||
fzf = {
|
||||
enable = true;
|
||||
enableZshIntegration = true;
|
||||
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'" ];
|
||||
# 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.
|
||||
};
|
||||
# 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;
|
||||
git = {
|
||||
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?
|
||||
};
|
||||
nixvim.enable = true;
|
||||
readline = {
|
||||
enable = true;
|
||||
variables = {
|
||||
|
@ -308,82 +127,8 @@ in
|
|||
};
|
||||
extraConfig = builtins.readFile ./inputrc;
|
||||
};
|
||||
tmux =
|
||||
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";
|
||||
};
|
||||
tmux.enable = true;
|
||||
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 = {
|
||||
activation = {
|
||||
|
@ -395,137 +140,85 @@ in
|
|||
fi
|
||||
'';
|
||||
};
|
||||
stateVersion = "23.11";
|
||||
language = {
|
||||
base = "en_US.UTF-8";
|
||||
# time = "en_DK.UTF-8"; # TODO Disabled because complaints during nixos-rebuild switch
|
||||
};
|
||||
stateVersion = "24.05";
|
||||
packages = with pkgs; [
|
||||
# dotfiles dependencies
|
||||
# Terminal utils
|
||||
coreutils
|
||||
bash
|
||||
gnugrep
|
||||
gnused
|
||||
gnutar
|
||||
openssl
|
||||
wget
|
||||
curl
|
||||
python3Packages.pip
|
||||
moreutils
|
||||
rename
|
||||
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
|
||||
moreutils
|
||||
man
|
||||
cached-nix-shell # For scripts
|
||||
|
||||
# Pipe utils
|
||||
gnugrep
|
||||
gnused
|
||||
gawk
|
||||
|
||||
# Extraction
|
||||
gnutar
|
||||
unzip
|
||||
unrar
|
||||
p7zip
|
||||
|
||||
# Documentation
|
||||
man
|
||||
tldr
|
||||
neofetch
|
||||
|
||||
# remote
|
||||
wget
|
||||
curl
|
||||
openssl
|
||||
openssh
|
||||
rsync
|
||||
borgbackup
|
||||
sshfs
|
||||
|
||||
# cleanup
|
||||
ncdu
|
||||
jdupes
|
||||
duperemove
|
||||
compsize
|
||||
btdu
|
||||
|
||||
# local monitoring
|
||||
htop
|
||||
iotop
|
||||
iftop
|
||||
lsof
|
||||
strace
|
||||
pv
|
||||
progress
|
||||
speedtest-cli
|
||||
|
||||
# multimedia toolbox
|
||||
sox
|
||||
# toolbox
|
||||
imagemagick
|
||||
numbat
|
||||
|
||||
# password
|
||||
pwgen
|
||||
# hardware
|
||||
pciutils
|
||||
usbutils
|
||||
dmidecode
|
||||
lshw
|
||||
labelle # Label printer
|
||||
|
||||
# Locker
|
||||
(pkgs.writeShellApplication {
|
||||
name = "git-sync-init";
|
||||
# runtimeInputs = with pkgs; [ coreutils libnotify ];
|
||||
text = (lib.strings.concatLines
|
||||
(map (r: ''[ -d "${r.path}" ] || ${pkgs.git}/bin/git clone "${r.uri}" "${r.path}"'')
|
||||
(lib.attrsets.attrValues config.services.git-sync.repositories)
|
||||
)
|
||||
);
|
||||
name = "lock";
|
||||
text = ''
|
||||
${config.frogeye.hooks.lock}
|
||||
|
||||
${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 = {
|
||||
# Favourite commands
|
||||
PAGER = "less";
|
||||
EDITOR = "nvim";
|
||||
|
||||
# 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
|
||||
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";
|
||||
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
|
||||
RXVT_SOCKET = "${config.xdg.stateHome}/urxvtd"; # Used to want -$HOME suffix, hopefullt this isn't needed
|
||||
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
|
||||
} // direnv;
|
||||
# TODO Session variables only get reloaded on login I think.
|
||||
# Bash/ZSH only?
|
||||
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
|
||||
# Fzf
|
||||
FZF_COMPLETION_OPTS = "${lib.strings.concatStringsSep " " config.programs.fzf.fileWidgetOptions}";
|
||||
};
|
||||
sessionPath = [
|
||||
"${config.home.homeDirectory}/.local/bin"
|
||||
"${config.home.sessionVariables.GOPATH}"
|
||||
(builtins.toString ./scripts)
|
||||
"${config.home.homeDirectory}/.config/dotfiles/hm/scripts" # Not Nix path otherwise it gets converted into store,
|
||||
# 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 = [
|
||||
../common/frogarized
|
||||
../options.nix
|
||||
./accounts
|
||||
./brightness
|
||||
./common.nix
|
||||
./desktop.nix
|
||||
./dev.nix
|
||||
./extra.nix
|
||||
./desktop
|
||||
./dev
|
||||
./extra
|
||||
./gaming
|
||||
./git
|
||||
./gpg
|
||||
./homealone.nix
|
||||
./monitoring
|
||||
./nix
|
||||
./pager
|
||||
./password
|
||||
./prompt
|
||||
./rebuild
|
||||
./shell
|
||||
./ssh.nix
|
||||
./style.nix
|
||||
./usernix
|
||||
./vim.nix
|
||||
./theme
|
||||
./tmux
|
||||
./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.python https://docs.python.org/3/search.html?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.wa https://www.wolframalpha.com/input/?i=%s
|
||||
set searchurls.yt https://www.youtube.com/results?search_query=%s
|
||||
|
||||
" Firefox GUI
|
||||
" This can still be shown with F6!
|
||||
" guiset_quiet gui none
|
||||
guiset_quiet gui none
|
||||
|
||||
" Never autofocus
|
||||
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 logging
|
||||
|
@ -7,11 +7,12 @@ import signal
|
|||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
|
||||
import coloredlogs
|
||||
import i3ipc
|
||||
|
||||
from frobar.notbusy import notBusy
|
||||
from frobar.common import notBusy
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
@ -29,6 +30,12 @@ log = logging.getLogger()
|
|||
# TODO forceSize and changeText are different
|
||||
|
||||
|
||||
Handle = typing.Callable[[], None]
|
||||
Decorator = Handle | str | None
|
||||
Element: typing.TypeAlias = typing.Union[str, "Text", None]
|
||||
Part: typing.TypeAlias = typing.Union[str, "Text", "Section"]
|
||||
|
||||
|
||||
class BarGroupType(enum.Enum):
|
||||
LEFT = 0
|
||||
RIGHT = 1
|
||||
|
@ -40,6 +47,7 @@ class BarGroupType(enum.Enum):
|
|||
class BarStdoutThread(threading.Thread):
|
||||
def run(self) -> None:
|
||||
while Bar.running:
|
||||
assert Bar.process.stdout
|
||||
handle = Bar.process.stdout.readline().strip()
|
||||
if not len(handle):
|
||||
Bar.stop()
|
||||
|
@ -62,20 +70,31 @@ class Bar:
|
|||
@staticmethod
|
||||
def init() -> None:
|
||||
Bar.running = True
|
||||
Bar.everyone = set()
|
||||
Section.init()
|
||||
|
||||
cmd = ["lemonbar", "-b", "-a", "64"]
|
||||
cmd = [
|
||||
"lemonbar",
|
||||
"-b",
|
||||
"-a",
|
||||
"64",
|
||||
"-F",
|
||||
Section.FGCOLOR,
|
||||
"-B",
|
||||
Section.BGCOLOR,
|
||||
]
|
||||
for font in Bar.FONTS:
|
||||
cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)]
|
||||
Bar.process = subprocess.Popen(
|
||||
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||
)
|
||||
Bar.stdoutThread = BarStdoutThread()
|
||||
Bar.stdoutThread.start()
|
||||
BarStdoutThread().start()
|
||||
|
||||
# Debug
|
||||
Bar(0)
|
||||
# Bar(1)
|
||||
i3 = i3ipc.Connection()
|
||||
for output in i3.get_outputs():
|
||||
if not output.active:
|
||||
continue
|
||||
Bar(output.name)
|
||||
|
||||
@staticmethod
|
||||
def stop() -> None:
|
||||
|
@ -90,29 +109,27 @@ class Bar:
|
|||
Bar.forever()
|
||||
i3 = i3ipc.Connection()
|
||||
|
||||
def doStop(*args) -> None:
|
||||
def doStop(*args: list) -> None:
|
||||
Bar.stop()
|
||||
print(88)
|
||||
|
||||
try:
|
||||
i3.on("ipc_shutdown", doStop)
|
||||
i3.main()
|
||||
except BaseException:
|
||||
print(93)
|
||||
Bar.stop()
|
||||
|
||||
# Class globals
|
||||
everyone = set()
|
||||
everyone: set["Bar"]
|
||||
string = ""
|
||||
process = None
|
||||
process: subprocess.Popen
|
||||
running = False
|
||||
|
||||
nextHandle = 0
|
||||
actionsF2H = dict()
|
||||
actionsH2F = dict()
|
||||
actionsF2H: dict[Handle, bytes] = dict()
|
||||
actionsH2F: dict[bytes, Handle] = dict()
|
||||
|
||||
@staticmethod
|
||||
def getFunctionHandle(function):
|
||||
def getFunctionHandle(function: typing.Callable[[], None]) -> bytes:
|
||||
assert callable(function)
|
||||
if function in Bar.actionsF2H.keys():
|
||||
return Bar.actionsF2H[function]
|
||||
|
@ -126,13 +143,12 @@ class Bar:
|
|||
return handle
|
||||
|
||||
@staticmethod
|
||||
def forever():
|
||||
def forever() -> None:
|
||||
Bar.process.wait()
|
||||
Bar.stop()
|
||||
|
||||
def __init__(self, screen):
|
||||
assert isinstance(screen, int)
|
||||
self.screen = "%{S" + str(screen) + "}"
|
||||
def __init__(self, output: str) -> None:
|
||||
self.output = output
|
||||
self.groups = dict()
|
||||
|
||||
for groupType in BarGroupType:
|
||||
|
@ -140,36 +156,33 @@ class Bar:
|
|||
self.groups[groupType] = group
|
||||
|
||||
self.childsChanged = False
|
||||
|
||||
self.everyone.add(self)
|
||||
Bar.everyone.add(self)
|
||||
|
||||
@staticmethod
|
||||
def addSectionAll(section, group, screens=None):
|
||||
def addSectionAll(
|
||||
section: "Section", group: "BarGroupType"
|
||||
) -> None:
|
||||
"""
|
||||
.. note::
|
||||
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:
|
||||
bar.addSection(section, group=group)
|
||||
section.added()
|
||||
|
||||
def addSection(self, section, group):
|
||||
assert isinstance(section, Section)
|
||||
assert isinstance(group, BarGroupType)
|
||||
def addSection(self, section: "Section", group: "BarGroupType") -> None:
|
||||
self.groups[group].addSection(section)
|
||||
|
||||
def update(self):
|
||||
def update(self) -> None:
|
||||
if self.childsChanged:
|
||||
self.string = self.screen
|
||||
self.string = "%{Sn" + self.output + "}"
|
||||
self.string += self.groups[BarGroupType.LEFT].string
|
||||
self.string += self.groups[BarGroupType.RIGHT].string
|
||||
|
||||
self.childsChanged = False
|
||||
|
||||
@staticmethod
|
||||
def updateAll():
|
||||
def updateAll() -> None:
|
||||
if Bar.running:
|
||||
Bar.string = ""
|
||||
for bar in Bar.everyone:
|
||||
|
@ -178,8 +191,10 @@ class Bar:
|
|||
# Color for empty sections
|
||||
Bar.string += BarGroup.color(*Section.EMPTY)
|
||||
|
||||
# print(Bar.string)
|
||||
Bar.process.stdin.write(bytes(Bar.string + "\n", "utf-8"))
|
||||
string = Bar.string + "\n"
|
||||
# print(string)
|
||||
assert Bar.process.stdin
|
||||
Bar.process.stdin.write(string.encode())
|
||||
Bar.process.stdin.flush()
|
||||
|
||||
|
||||
|
@ -188,18 +203,16 @@ class BarGroup:
|
|||
One for each group of each bar
|
||||
"""
|
||||
|
||||
everyone = set()
|
||||
everyone: set["BarGroup"] = set()
|
||||
|
||||
def __init__(self, groupType, parent):
|
||||
assert isinstance(groupType, BarGroupType)
|
||||
assert isinstance(parent, Bar)
|
||||
def __init__(self, groupType: BarGroupType, parent: Bar):
|
||||
|
||||
self.groupType = groupType
|
||||
self.parent = parent
|
||||
|
||||
self.sections = list()
|
||||
self.sections: list["Section"] = list()
|
||||
self.string = ""
|
||||
self.parts = []
|
||||
self.parts: list[Part] = []
|
||||
|
||||
#: One of the sections that had their theme or visibility changed
|
||||
self.childsThemeChanged = False
|
||||
|
@ -209,11 +222,11 @@ class BarGroup:
|
|||
|
||||
BarGroup.everyone.add(self)
|
||||
|
||||
def addSection(self, section):
|
||||
def addSection(self, section: "Section") -> None:
|
||||
self.sections.append(section)
|
||||
section.addParent(self)
|
||||
|
||||
def addSectionAfter(self, sectionRef, section):
|
||||
def addSectionAfter(self, sectionRef: "Section", section: "Section") -> None:
|
||||
index = self.sections.index(sectionRef)
|
||||
self.sections.insert(index + 1, section)
|
||||
section.addParent(self)
|
||||
|
@ -221,20 +234,20 @@ class BarGroup:
|
|||
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
||||
|
||||
@staticmethod
|
||||
def fgColor(color):
|
||||
def fgColor(color: str) -> str:
|
||||
return "%{F" + (color or "-") + "}"
|
||||
|
||||
@staticmethod
|
||||
def bgColor(color):
|
||||
def bgColor(color: str) -> str:
|
||||
return "%{B" + (color or "-") + "}"
|
||||
|
||||
@staticmethod
|
||||
def color(fg, bg):
|
||||
def color(fg: str, bg: str) -> str:
|
||||
return BarGroup.fgColor(fg) + BarGroup.bgColor(bg)
|
||||
|
||||
def update(self):
|
||||
def update(self) -> None:
|
||||
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]
|
||||
lenS = len(secs)
|
||||
|
@ -283,7 +296,7 @@ class BarGroup:
|
|||
self.childsTextChanged = False
|
||||
|
||||
@staticmethod
|
||||
def updateAll():
|
||||
def updateAll() -> None:
|
||||
for group in BarGroup.everyone:
|
||||
group.update()
|
||||
Bar.updateAll()
|
||||
|
@ -294,7 +307,7 @@ class SectionThread(threading.Thread):
|
|||
ANIMATION_STOP = 0.001
|
||||
ANIMATION_EVOLUTION = 0.9
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
while Section.somethingChanged.wait():
|
||||
notBusy.wait()
|
||||
Section.updateAll()
|
||||
|
@ -311,52 +324,54 @@ class SectionThread(threading.Thread):
|
|||
animTime = self.ANIMATION_STOP
|
||||
|
||||
|
||||
Theme = tuple[str, str]
|
||||
|
||||
|
||||
class Section:
|
||||
# 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 = [
|
||||
"#181818",
|
||||
"#AB4642",
|
||||
"#A1B56C",
|
||||
"#F7CA88",
|
||||
"#7CAFC2",
|
||||
"#BA8BAF",
|
||||
"#86C1B9",
|
||||
"#D8D8D8",
|
||||
"#585858",
|
||||
"#AB4642",
|
||||
"#A1B56C",
|
||||
"#F7CA88",
|
||||
"#7CAFC2",
|
||||
"#BA8BAF",
|
||||
"#86C1B9",
|
||||
"#F8F8F8",
|
||||
"#092c0e",
|
||||
"#143718",
|
||||
"#5a7058",
|
||||
"#677d64",
|
||||
"#89947f",
|
||||
"#99a08d",
|
||||
"#fae2e3",
|
||||
"#fff0f1",
|
||||
"#e0332e",
|
||||
"#cf4b15",
|
||||
"#bb8801",
|
||||
"#8d9800",
|
||||
"#1fa198",
|
||||
"#008dd1",
|
||||
"#5c73c4",
|
||||
"#d43982",
|
||||
]
|
||||
FGCOLOR = "#F8F8F2"
|
||||
BGCOLOR = "#272822"
|
||||
FGCOLOR = "#fff0f1"
|
||||
BGCOLOR = "#092c0e"
|
||||
|
||||
THEMES = list()
|
||||
EMPTY = (FGCOLOR, BGCOLOR)
|
||||
THEMES: list[Theme] = list()
|
||||
EMPTY: Theme = (FGCOLOR, BGCOLOR)
|
||||
|
||||
ICON = None
|
||||
ICON: str | None = None
|
||||
PERSISTENT = False
|
||||
|
||||
#: Sections that do not have their destination size
|
||||
sizeChanging = set()
|
||||
updateThread = SectionThread(daemon=True)
|
||||
sizeChanging: set["Section"] = set()
|
||||
updateThread: threading.Thread = SectionThread(daemon=True)
|
||||
somethingChanged = threading.Event()
|
||||
lastChosenTheme = 0
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
def init() -> None:
|
||||
for t in range(8, 16):
|
||||
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
|
||||
Section.THEMES.append((Section.COLORS[0], Section.COLORS[3]))
|
||||
Section.THEMES.append((Section.COLORS[0], Section.COLORS[6]))
|
||||
|
||||
Section.updateThread.start()
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None) -> None:
|
||||
#: Displayed section
|
||||
#: Note: A section can be empty and displayed!
|
||||
self.visible = False
|
||||
|
@ -379,12 +394,12 @@ class Section:
|
|||
self.dstSize = 0
|
||||
|
||||
#: Groups that have this section
|
||||
self.parents = set()
|
||||
self.parents: set[BarGroup] = set()
|
||||
|
||||
self.icon = self.ICON
|
||||
self.persistent = self.PERSISTENT
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
try:
|
||||
return "<{}><{}>{:01d}{}{:02d}/{:02d}".format(
|
||||
self.curText,
|
||||
|
@ -394,26 +409,29 @@ class Section:
|
|||
self.curSize,
|
||||
self.dstSize,
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return super().__str__()
|
||||
|
||||
def addParent(self, parent):
|
||||
def addParent(self, parent: BarGroup) -> None:
|
||||
self.parents.add(parent)
|
||||
|
||||
def appendAfter(self, section):
|
||||
def appendAfter(self, section: "Section") -> None:
|
||||
assert len(self.parents)
|
||||
for parent in self.parents:
|
||||
parent.addSectionAfter(self, section)
|
||||
|
||||
def informParentsThemeChanged(self):
|
||||
def added(self) -> None:
|
||||
pass
|
||||
|
||||
def informParentsThemeChanged(self) -> None:
|
||||
for parent in self.parents:
|
||||
parent.childsThemeChanged = True
|
||||
|
||||
def informParentsTextChanged(self):
|
||||
def informParentsTextChanged(self) -> None:
|
||||
for parent in self.parents:
|
||||
parent.childsTextChanged = True
|
||||
|
||||
def updateText(self, text):
|
||||
def updateText(self, text: Element) -> None:
|
||||
if isinstance(text, str):
|
||||
text = Text(text)
|
||||
elif isinstance(text, Text) and not len(text.elements):
|
||||
|
@ -440,14 +458,13 @@ class Section:
|
|||
Section.sizeChanging.add(self)
|
||||
Section.somethingChanged.set()
|
||||
|
||||
def setDecorators(self, **kwargs):
|
||||
def setDecorators(self, **kwargs: Handle) -> None:
|
||||
self.dstText.setDecorators(**kwargs)
|
||||
self.curText = str(self.dstText)
|
||||
self.informParentsTextChanged()
|
||||
Section.somethingChanged.set()
|
||||
|
||||
def updateTheme(self, theme):
|
||||
assert isinstance(theme, int)
|
||||
def updateTheme(self, theme: int) -> None:
|
||||
assert theme < len(Section.THEMES)
|
||||
if theme == self.theme:
|
||||
return
|
||||
|
@ -455,19 +472,18 @@ class Section:
|
|||
self.informParentsThemeChanged()
|
||||
Section.somethingChanged.set()
|
||||
|
||||
def updateVisibility(self, visibility):
|
||||
assert isinstance(visibility, bool)
|
||||
def updateVisibility(self, visibility: bool) -> None:
|
||||
|
||||
self.visible = visibility
|
||||
self.informParentsThemeChanged()
|
||||
Section.somethingChanged.set()
|
||||
|
||||
@staticmethod
|
||||
def fit(text, size):
|
||||
def fit(text: str, size: int) -> str:
|
||||
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
|
||||
if not self.visible:
|
||||
self.updateVisibility(True)
|
||||
|
@ -488,7 +504,7 @@ class Section:
|
|||
self.informParentsTextChanged()
|
||||
|
||||
@staticmethod
|
||||
def updateAll():
|
||||
def updateAll() -> None:
|
||||
"""
|
||||
Process all sections for text size changes
|
||||
"""
|
||||
|
@ -501,7 +517,7 @@ class Section:
|
|||
Section.somethingChanged.clear()
|
||||
|
||||
@staticmethod
|
||||
def ramp(p, ramp=" ▁▂▃▄▅▆▇█"):
|
||||
def ramp(p: float, ramp: str = " ▁▂▃▄▅▆▇█") -> str:
|
||||
if p > 1:
|
||||
return ramp[-1]
|
||||
elif p < 0:
|
||||
|
@ -512,11 +528,11 @@ class Section:
|
|||
|
||||
class StatefulSection(Section):
|
||||
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
|
||||
NUMBER_STATES = None
|
||||
NUMBER_STATES: int
|
||||
DEFAULT_STATE = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Section.__init__(self, *args, **kwargs)
|
||||
def __init__(self, theme: int | None) -> None:
|
||||
Section.__init__(self, theme=theme)
|
||||
self.state = self.DEFAULT_STATE
|
||||
if hasattr(self, "onChangeState"):
|
||||
self.onChangeState(self.state)
|
||||
|
@ -524,20 +540,22 @@ class StatefulSection(Section):
|
|||
clickLeft=self.incrementState, clickRight=self.decrementState
|
||||
)
|
||||
|
||||
def incrementState(self):
|
||||
def incrementState(self) -> None:
|
||||
newState = min(self.state + 1, self.NUMBER_STATES - 1)
|
||||
self.changeState(newState)
|
||||
|
||||
def decrementState(self):
|
||||
def decrementState(self) -> None:
|
||||
newState = max(self.state - 1, 0)
|
||||
self.changeState(newState)
|
||||
|
||||
def changeState(self, state):
|
||||
assert isinstance(state, int)
|
||||
def changeState(self, state: int) -> None:
|
||||
assert state < self.NUMBER_STATES
|
||||
self.state = state
|
||||
if hasattr(self, "onChangeState"):
|
||||
self.onChangeState(state)
|
||||
assert hasattr(
|
||||
self, "refreshData"
|
||||
), "StatefulSection should be paired with some Updater"
|
||||
self.refreshData()
|
||||
|
||||
|
||||
|
@ -548,10 +566,13 @@ class ColorCountsSection(StatefulSection):
|
|||
NUMBER_STATES = 3
|
||||
COLORABLE_ICON = "?"
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: None | int = None) -> None:
|
||||
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()
|
||||
# Nothing
|
||||
if not len(counts):
|
||||
|
@ -566,67 +587,66 @@ class ColorCountsSection(StatefulSection):
|
|||
# Icon + Total
|
||||
elif self.state == 1 and len(counts) > 1:
|
||||
total = sum([count for count, color in counts])
|
||||
return Text(self.COLORABLE_ICON, " ", total)
|
||||
return Text(self.COLORABLE_ICON, " ", str(total))
|
||||
# Icon + Counts
|
||||
else:
|
||||
text = Text(self.COLORABLE_ICON)
|
||||
for count, color in counts:
|
||||
text.append(" ", Text(count, fg=color))
|
||||
text.append(" ", Text(str(count), fg=color))
|
||||
return text
|
||||
|
||||
|
||||
class Text:
|
||||
def _setElements(self, elements):
|
||||
# TODO OPTI Concatenate consecutrive string
|
||||
self.elements = list(elements)
|
||||
|
||||
def _setDecorators(self, decorators):
|
||||
def _setDecorators(self, decorators: dict[str, Decorator]) -> None:
|
||||
# TODO OPTI Convert no decorator to strings
|
||||
self.decorators = decorators
|
||||
self.prefix = None
|
||||
self.suffix = None
|
||||
self.prefix: str | None = None
|
||||
self.suffix: str | None = None
|
||||
|
||||
def __init__(self, *args: Element, **kwargs: Decorator) -> None:
|
||||
# TODO OPTI Concatenate consecutrive string
|
||||
self.elements = list(args)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._setElements(args)
|
||||
self._setDecorators(kwargs)
|
||||
self.section = None
|
||||
self.section: Section
|
||||
|
||||
def append(self, *args):
|
||||
self._setElements(self.elements + list(args))
|
||||
def append(self, *args: Element) -> None:
|
||||
self.elements += list(args)
|
||||
|
||||
def prepend(self, *args):
|
||||
self._setElements(list(args) + self.elements)
|
||||
def prepend(self, *args: Element) -> None:
|
||||
self.elements = list(args) + self.elements
|
||||
|
||||
def setElements(self, *args):
|
||||
self._setElements(args)
|
||||
def setElements(self, *args: Element) -> None:
|
||||
self.elements = list(args)
|
||||
|
||||
def setDecorators(self, **kwargs):
|
||||
def setDecorators(self, **kwargs: Decorator) -> None:
|
||||
self._setDecorators(kwargs)
|
||||
|
||||
def setSection(self, section):
|
||||
assert isinstance(section, Section)
|
||||
def setSection(self, section: Section) -> None:
|
||||
self.section = section
|
||||
for element in self.elements:
|
||||
if isinstance(element, Text):
|
||||
element.setSection(section)
|
||||
|
||||
def _genFixs(self):
|
||||
def _genFixs(self) -> None:
|
||||
if self.prefix is not None and self.suffix is not None:
|
||||
return
|
||||
|
||||
self.prefix = ""
|
||||
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.suffix = "%{" + suffix + "}" + self.suffix
|
||||
|
||||
def getColor(val):
|
||||
def getColor(val: str) -> str:
|
||||
# TODO Allow themes
|
||||
assert isinstance(val, str) and len(val) == 7
|
||||
assert len(val) == 7
|
||||
return val
|
||||
|
||||
def button(number, function):
|
||||
def button(number: str, function: Handle) -> None:
|
||||
handle = Bar.getFunctionHandle(function)
|
||||
nest("A" + number + ":" + handle.decode() + ":", "A" + number)
|
||||
|
||||
|
@ -635,25 +655,34 @@ class Text:
|
|||
continue
|
||||
if key == "fg":
|
||||
reset = self.section.THEMES[self.section.theme][0]
|
||||
assert isinstance(val, str)
|
||||
nest("F" + getColor(val), "F" + reset)
|
||||
elif key == "bg":
|
||||
reset = self.section.THEMES[self.section.theme][1]
|
||||
assert isinstance(val, str)
|
||||
nest("B" + getColor(val), "B" + reset)
|
||||
elif key == "clickLeft":
|
||||
assert callable(val)
|
||||
button("1", val)
|
||||
elif key == "clickMiddle":
|
||||
assert callable(val)
|
||||
button("2", val)
|
||||
elif key == "clickRight":
|
||||
assert callable(val)
|
||||
button("3", val)
|
||||
elif key == "scrollUp":
|
||||
assert callable(val)
|
||||
button("4", val)
|
||||
elif key == "scrollDown":
|
||||
assert callable(val)
|
||||
button("5", val)
|
||||
else:
|
||||
log.warn("Unkown decorator: {}".format(key))
|
||||
|
||||
def _text(self, size=None, pad=False):
|
||||
def _text(self, size: int | None = None, pad: bool = False) -> tuple[str, int]:
|
||||
self._genFixs()
|
||||
assert self.prefix is not None
|
||||
assert self.suffix is not None
|
||||
curString = self.prefix
|
||||
curSize = 0
|
||||
remSize = size
|
||||
|
@ -679,7 +708,9 @@ class Text:
|
|||
|
||||
curString += self.suffix
|
||||
|
||||
if pad and remSize > 0:
|
||||
if pad:
|
||||
assert remSize is not None
|
||||
if remSize > 0:
|
||||
curString += " " * remSize
|
||||
curSize += remSize
|
||||
|
||||
|
@ -690,12 +721,14 @@ class Text:
|
|||
assert size >= curSize
|
||||
return curString, curSize
|
||||
|
||||
def text(self, *args, **kwargs):
|
||||
string, size = self._text(*args, **kwargs)
|
||||
def text(self, size: int | None = None, pad: bool = False) -> str:
|
||||
string, size = self._text(size=size, pad=pad)
|
||||
return string
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
self._genFixs()
|
||||
assert self.prefix is not None
|
||||
assert self.suffix is not None
|
||||
curString = self.prefix
|
||||
for element in self.elements:
|
||||
if element is None:
|
||||
|
@ -705,7 +738,7 @@ class Text:
|
|||
curString += self.suffix
|
||||
return curString
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
curSize = 0
|
||||
for element in self.elements:
|
||||
if element is None:
|
||||
|
@ -716,8 +749,8 @@ class Text:
|
|||
curSize += len(str(element))
|
||||
return curSize
|
||||
|
||||
def __getitem__(self, index):
|
||||
def __getitem__(self, index: int) -> Element:
|
||||
return self.elements[index]
|
||||
|
||||
def __setitem__(self, index, data):
|
||||
def __setitem__(self, index: int, data: Element) -> None:
|
||||
self.elements[index] = data
|
|
@ -1,21 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import datetime
|
||||
import enum
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import coloredlogs
|
||||
import i3ipc
|
||||
import mpd
|
||||
import notmuch
|
||||
import psutil
|
||||
import pulsectl
|
||||
|
||||
from frobar.display import *
|
||||
from frobar.updaters import *
|
||||
from frobar.display import (ColorCountsSection, Element, Section,
|
||||
StatefulSection, Text)
|
||||
from frobar.updaters import (I3Updater, InotifyUpdater, MergedUpdater,
|
||||
PeriodicUpdater, ThreadedUpdater, Updater)
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
@ -24,21 +30,22 @@ log = logging.getLogger()
|
|||
# PulseaudioProvider and MpdProvider)
|
||||
|
||||
|
||||
def humanSize(num):
|
||||
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.0
|
||||
return "{:d}YiB".format(num)
|
||||
num /= 1024
|
||||
return "{:d}YiB".format(numi)
|
||||
|
||||
|
||||
def randomColor(seed=0):
|
||||
def randomColor(seed: int | bytes = 0) -> str:
|
||||
random.seed(seed)
|
||||
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)
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> str:
|
||||
now = datetime.datetime.now()
|
||||
return now.strftime(self.FORMATS[self.state])
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.changeInterval(1) # TODO OPTI When state < 1
|
||||
|
@ -66,10 +73,10 @@ class AlertLevel(enum.Enum):
|
|||
|
||||
class AlertingSection(StatefulSection):
|
||||
# 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
|
||||
|
||||
def getLevel(self, quantity):
|
||||
def getLevel(self, quantity: float) -> AlertLevel:
|
||||
if quantity > self.dangerThresold:
|
||||
return AlertLevel.DANGER
|
||||
elif quantity > self.warningThresold:
|
||||
|
@ -77,14 +84,14 @@ class AlertingSection(StatefulSection):
|
|||
else:
|
||||
return AlertLevel.NORMAL
|
||||
|
||||
def updateLevel(self, quantity):
|
||||
def updateLevel(self, quantity: float) -> None:
|
||||
self.level = self.getLevel(quantity)
|
||||
self.updateTheme(self.THEMES[self.level])
|
||||
self.updateTheme(self.ALERT_THEMES[self.level])
|
||||
if self.level == AlertLevel.NORMAL:
|
||||
return
|
||||
# TODO Temporary update state
|
||||
|
||||
def __init__(self, theme):
|
||||
def __init__(self, theme: int | None = None):
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.dangerThresold = 0.90
|
||||
self.warningThresold = 0.75
|
||||
|
@ -92,9 +99,9 @@ class AlertingSection(StatefulSection):
|
|||
|
||||
class CpuProvider(AlertingSection, PeriodicUpdater):
|
||||
NUMBER_STATES = 3
|
||||
ICON = ""
|
||||
ICON = ""
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
percent = psutil.cpu_percent(percpu=False)
|
||||
self.updateLevel(percent / 100)
|
||||
if self.state >= 2:
|
||||
|
@ -102,22 +109,44 @@ class CpuProvider(AlertingSection, PeriodicUpdater):
|
|||
return "".join([Section.ramp(p / 100) for p in percents])
|
||||
elif self.state >= 1:
|
||||
return Section.ramp(percent / 100)
|
||||
return ""
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
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):
|
||||
"""
|
||||
Shows free RAM
|
||||
"""
|
||||
|
||||
NUMBER_STATES = 4
|
||||
ICON = ""
|
||||
ICON = ""
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
mem = psutil.virtual_memory()
|
||||
freePerc = mem.percent / 100
|
||||
self.updateLevel(freePerc)
|
||||
|
@ -135,7 +164,7 @@ class RamProvider(AlertingSection, PeriodicUpdater):
|
|||
|
||||
return text
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(1)
|
||||
|
@ -144,23 +173,28 @@ class RamProvider(AlertingSection, PeriodicUpdater):
|
|||
class TemperatureProvider(AlertingSection, PeriodicUpdater):
|
||||
NUMBER_STATES = 2
|
||||
RAMP = ""
|
||||
MAIN_TEMPS = ["coretemp", "amdgpu", "cpu_thermal"]
|
||||
# For Intel, AMD and ARM respectively.
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
allTemp = psutil.sensors_temperatures()
|
||||
if "coretemp" not in allTemp:
|
||||
# TODO Opti Remove interval
|
||||
return ""
|
||||
temp = allTemp["coretemp"][0]
|
||||
for main in self.MAIN_TEMPS:
|
||||
if main in allTemp:
|
||||
break
|
||||
else:
|
||||
return "?"
|
||||
temp = allTemp[main][0]
|
||||
|
||||
self.warningThresold = temp.high
|
||||
self.dangerThresold = temp.critical
|
||||
self.warningThresold = temp.high or 90.0
|
||||
self.dangerThresold = temp.critical or 100.0
|
||||
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:
|
||||
return "{:.0f}°C".format(temp.current)
|
||||
return ""
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
@ -171,10 +205,9 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
|||
NUMBER_STATES = 3
|
||||
RAMP = ""
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
bat = psutil.sensors_battery()
|
||||
if not bat:
|
||||
self.icon = None
|
||||
return None
|
||||
|
||||
self.icon = ("" if bat.power_plugged else "") + Section.ramp(
|
||||
|
@ -184,7 +217,7 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
|||
self.updateLevel(1 - bat.percent / 100)
|
||||
|
||||
if self.state < 1:
|
||||
return
|
||||
return ""
|
||||
|
||||
t = Text("{:.0f}%".format(bat.percent))
|
||||
|
||||
|
@ -196,17 +229,38 @@ class BatteryProvider(AlertingSection, PeriodicUpdater):
|
|||
t.append(" ({:d}:{:02d})".format(h, m))
|
||||
return t
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
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):
|
||||
NUMBER_STATES = 3
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
ThreadedUpdater.__init__(self)
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.pulseEvents = pulsectl.Pulse("event-handler")
|
||||
|
@ -216,15 +270,21 @@ class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
|||
self.start()
|
||||
self.refreshData()
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
sinks = []
|
||||
with pulsectl.Pulse("list-sinks") as pulse:
|
||||
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 = ""
|
||||
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 ""
|
||||
elif sink.port_active.name == "headset-output":
|
||||
elif sink.port_active.name in ("headset-output", "headphone-output"):
|
||||
icon = ""
|
||||
else:
|
||||
icon = "?"
|
||||
|
@ -249,10 +309,10 @@ class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
|||
|
||||
return Text(*sinks)
|
||||
|
||||
def loop(self):
|
||||
def loop(self) -> None:
|
||||
self.pulseEvents.event_listen()
|
||||
|
||||
def handleEvent(self, ev):
|
||||
def handleEvent(self, ev: pulsectl.PulseEventInfo) -> None:
|
||||
self.refreshData()
|
||||
|
||||
|
||||
|
@ -260,7 +320,7 @@ class NetworkProviderSection(StatefulSection, Updater):
|
|||
NUMBER_STATES = 5
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def actType(self):
|
||||
def actType(self) -> None:
|
||||
self.ssid = None
|
||||
if self.iface.startswith("eth") or self.iface.startswith("enp"):
|
||||
if "u" in self.iface:
|
||||
|
@ -281,10 +341,10 @@ class NetworkProviderSection(StatefulSection, Updater):
|
|||
self.icon = ""
|
||||
elif self.iface.startswith("vboxnet"):
|
||||
self.icon = ""
|
||||
else:
|
||||
self.icon = "?"
|
||||
|
||||
def getAddresses(self):
|
||||
def getAddresses(
|
||||
self,
|
||||
) -> tuple[psutil._common.snicaddr, psutil._common.snicaddr]:
|
||||
ipv4 = None
|
||||
ipv6 = None
|
||||
for address in self.parent.addrs[self.iface]:
|
||||
|
@ -294,8 +354,8 @@ class NetworkProviderSection(StatefulSection, Updater):
|
|||
ipv6 = address
|
||||
return ipv4, ipv6
|
||||
|
||||
def fetcher(self):
|
||||
self.icon = None
|
||||
def fetcher(self) -> Element:
|
||||
self.icon = "?"
|
||||
self.persistent = False
|
||||
if (
|
||||
self.iface not in self.parent.stats
|
||||
|
@ -349,13 +409,13 @@ class NetworkProviderSection(StatefulSection, Updater):
|
|||
|
||||
return " ".join(text)
|
||||
|
||||
def onChangeState(self, state):
|
||||
def onChangeState(self, state: int) -> None:
|
||||
self.showSsid = state >= 1
|
||||
self.showAddress = state >= 2
|
||||
self.showSpeed = state >= 3
|
||||
self.showTransfer = state >= 4
|
||||
|
||||
def __init__(self, iface, parent):
|
||||
def __init__(self, iface: str, parent: "NetworkProvider"):
|
||||
Updater.__init__(self)
|
||||
StatefulSection.__init__(self, theme=parent.theme)
|
||||
self.iface = iface
|
||||
|
@ -363,23 +423,23 @@ class NetworkProviderSection(StatefulSection, Updater):
|
|||
|
||||
|
||||
class NetworkProvider(Section, PeriodicUpdater):
|
||||
def fetchData(self):
|
||||
def fetchData(self) -> None:
|
||||
self.prev = self.last
|
||||
self.prevIO = self.IO
|
||||
|
||||
self.stats = psutil.net_if_stats()
|
||||
self.addrs = psutil.net_if_addrs()
|
||||
self.IO = psutil.net_io_counters(pernic=True)
|
||||
self.addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs()
|
||||
self.IO: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True)
|
||||
self.ifaces = self.stats.keys()
|
||||
|
||||
self.last = time.perf_counter()
|
||||
self.last: float = time.perf_counter()
|
||||
self.dt = self.last - self.prev
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> None:
|
||||
self.fetchData()
|
||||
|
||||
# Add missing sections
|
||||
lastSection = self
|
||||
lastSection: NetworkProvider | NetworkProviderSection = self
|
||||
for iface in sorted(list(self.ifaces)):
|
||||
if iface not in self.sections.keys():
|
||||
section = NetworkProviderSection(iface, self)
|
||||
|
@ -395,15 +455,11 @@ class NetworkProvider(Section, PeriodicUpdater):
|
|||
|
||||
return None
|
||||
|
||||
def addParent(self, parent):
|
||||
self.parents.add(parent)
|
||||
self.refreshData()
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
|
||||
self.sections = dict()
|
||||
self.sections: dict[str, NetworkProviderSection] = dict()
|
||||
self.last = 0
|
||||
self.IO = dict()
|
||||
self.fetchData()
|
||||
|
@ -415,7 +471,7 @@ class RfkillProvider(Section, PeriodicUpdater):
|
|||
# toggled
|
||||
PATH = "/sys/class/rfkill"
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
t = Text()
|
||||
for device in os.listdir(self.PATH):
|
||||
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:
|
||||
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":
|
||||
icon = ""
|
||||
elif typ == b"bluetooth":
|
||||
|
@ -440,14 +496,14 @@ class RfkillProvider(Section, PeriodicUpdater):
|
|||
t.append(Text(icon, fg=fg))
|
||||
return t
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class SshAgentProvider(PeriodicUpdater):
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
cmd = ["ssh-add", "-l"]
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
if proc.returncode != 0:
|
||||
|
@ -460,13 +516,13 @@ class SshAgentProvider(PeriodicUpdater):
|
|||
text.append(Text("", fg=randomColor(seed=fingerprint)))
|
||||
return text
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class GpgAgentProvider(PeriodicUpdater):
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
cmd = ["gpg-connect-agent", "keyinfo --list", "/bye"]
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
# proc = subprocess.run(cmd)
|
||||
|
@ -483,7 +539,7 @@ class GpgAgentProvider(PeriodicUpdater):
|
|||
text.append(Text("", fg=randomColor(seed=keygrip)))
|
||||
return text
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
@ -492,7 +548,7 @@ class KeystoreProvider(Section, MergedUpdater):
|
|||
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless
|
||||
ICON = ""
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
MergedUpdater.__init__(self, SshAgentProvider(), GpgAgentProvider())
|
||||
Section.__init__(self, theme)
|
||||
|
||||
|
@ -500,24 +556,21 @@ class KeystoreProvider(Section, MergedUpdater):
|
|||
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater):
|
||||
COLORABLE_ICON = ""
|
||||
|
||||
def subfetcher(self):
|
||||
def subfetcher(self) -> list[tuple[int, str]]:
|
||||
db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY, path=self.dir)
|
||||
counts = []
|
||||
for account in self.accounts:
|
||||
queryStr = "folder:/{}/ and tag:unread".format(account)
|
||||
query = notmuch.Query(db, queryStr)
|
||||
nbMsgs = query.count_messages()
|
||||
if account == "frogeye":
|
||||
global q
|
||||
q = query
|
||||
if nbMsgs < 1:
|
||||
continue
|
||||
counts.append((nbMsgs, self.colors[account]))
|
||||
# db.close()
|
||||
return counts
|
||||
|
||||
def __init__(self, dir="~/.mail/", theme=None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
def __init__(self, dir: str = "~/.mail/", theme: int | None = None):
|
||||
InotifyUpdater.__init__(self)
|
||||
ColorCountsSection.__init__(self, theme)
|
||||
|
||||
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||
|
@ -543,7 +596,7 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
|||
# TODO OPT Specific callback for specific directory
|
||||
COLORABLE_ICON = ""
|
||||
|
||||
def updateCalendarList(self):
|
||||
def updateCalendarList(self) -> None:
|
||||
calendars = sorted(os.listdir(self.dir))
|
||||
for calendar in calendars:
|
||||
# If the calendar wasn't in the list
|
||||
|
@ -559,9 +612,9 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
|||
path = os.path.join(self.dir, calendar, "color")
|
||||
with open(path, "r") as f:
|
||||
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
|
||||
"""
|
||||
|
@ -571,12 +624,12 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
|||
assert os.path.isdir(self.dir)
|
||||
|
||||
self.calendars = []
|
||||
self.colors = dict()
|
||||
self.names = dict()
|
||||
self.colors: dict[str, str] = dict()
|
||||
self.names: dict[str, str] = dict()
|
||||
self.updateCalendarList()
|
||||
self.refreshData()
|
||||
|
||||
def countUndone(self, calendar):
|
||||
def countUndone(self, calendar: str | None) -> int:
|
||||
cmd = ["todo", "--porcelain", "list"]
|
||||
if calendar:
|
||||
cmd.append(self.names[calendar])
|
||||
|
@ -584,7 +637,7 @@ class TodoProvider(ColorCountsSection, InotifyUpdater):
|
|||
data = json.loads(proc.stdout)
|
||||
return len(data)
|
||||
|
||||
def subfetcher(self):
|
||||
def subfetcher(self) -> list[tuple[int, str]]:
|
||||
counts = []
|
||||
|
||||
# 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
|
||||
# `focused=True` element following the `focus` array
|
||||
# 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)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
I3Updater.__init__(self)
|
||||
Section.__init__(self, theme=theme)
|
||||
self.on("window", self.on_window)
|
||||
|
||||
|
||||
class I3WorkspacesProviderSection(Section):
|
||||
def selectTheme(self):
|
||||
if self.urgent:
|
||||
def selectTheme(self) -> int:
|
||||
if self.workspace.urgent:
|
||||
return self.parent.themeUrgent
|
||||
elif self.focused:
|
||||
elif self.workspace.focused:
|
||||
return self.parent.themeFocus
|
||||
elif self.workspace.visible:
|
||||
return self.parent.themeVisible
|
||||
else:
|
||||
return self.parent.themeNormal
|
||||
|
||||
# TODO On mode change the state (shown / hidden) gets overriden so every
|
||||
# tab is shown
|
||||
|
||||
def show(self):
|
||||
def show(self) -> None:
|
||||
self.updateTheme(self.selectTheme())
|
||||
self.updateText(self.fullName if self.focused else self.shortName)
|
||||
|
||||
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
|
||||
self.updateText(
|
||||
self.fullName if self.workspace.focused else self.workspace.name
|
||||
)
|
||||
|
||||
def switchTo(self):
|
||||
self.parent.i3.command("workspace {}".format(self.shortName))
|
||||
def switchTo(self) -> None:
|
||||
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)
|
||||
self.parent = parent
|
||||
self.setName(name)
|
||||
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.updateText(None)
|
||||
|
||||
def tempShow(self):
|
||||
def tempShow(self) -> None:
|
||||
self.updateText(self.tempText)
|
||||
|
||||
def tempEmpty(self):
|
||||
def tempEmpty(self) -> None:
|
||||
self.tempText = self.dstText[1]
|
||||
self.updateText(None)
|
||||
|
||||
|
||||
class I3WorkspacesProvider(Section, I3Updater):
|
||||
# TODO FEAT Multi-screen
|
||||
|
||||
def initialPopulation(self, parent):
|
||||
"""
|
||||
Called on init
|
||||
Can't reuse addWorkspace since i3.get_workspaces() gives dict and not
|
||||
ConObjects
|
||||
"""
|
||||
workspaces = self.i3.get_workspaces()
|
||||
lastSection = self.modeSection
|
||||
for workspace in workspaces:
|
||||
# if parent.display != workspace["display"]:
|
||||
# continue
|
||||
|
||||
section = I3WorkspacesProviderSection(workspace.name, self)
|
||||
section.focused = workspace.focused
|
||||
section.urgent = workspace.urgent
|
||||
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]
|
||||
def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None:
|
||||
section: Section | None = None
|
||||
lastSectionOnOutput = self.modeSection
|
||||
highestNumOnOutput = -1
|
||||
for sect in self.sections.values():
|
||||
if sect.workspace.num == workspace.num:
|
||||
section = sect
|
||||
break
|
||||
elif (
|
||||
sect.workspace.num > highestNumOnOutput
|
||||
and sect.workspace.num < workspace.num
|
||||
and sect.workspace.output == workspace.output
|
||||
):
|
||||
lastSectionOnOutput = sect
|
||||
highestNumOnOutput = sect.workspace.num
|
||||
else:
|
||||
# Find the section just before
|
||||
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)
|
||||
section = I3WorkspacesProviderSection(self)
|
||||
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()
|
||||
|
||||
def on_workspace_focus(self, i3, e):
|
||||
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):
|
||||
def on_mode(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
|
||||
if e.change == "default":
|
||||
self.modeSection.updateText(None)
|
||||
for section in self.sections.values():
|
||||
|
@ -737,41 +773,46 @@ class I3WorkspacesProvider(Section, I3Updater):
|
|||
section.tempEmpty()
|
||||
|
||||
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)
|
||||
Section.__init__(self)
|
||||
self.themeNormal = theme
|
||||
self.themeFocus = themeFocus
|
||||
self.themeUrgent = themeUrgent
|
||||
self.themeVisible = themeVisible
|
||||
self.customNames = customNames
|
||||
|
||||
self.sections = dict()
|
||||
self.on("workspace::init", self.on_workspace_init)
|
||||
self.on("workspace::focus", self.on_workspace_focus)
|
||||
self.sections: dict[int, I3WorkspacesProviderSection] = dict()
|
||||
# The event object doesn't have the visible property,
|
||||
# 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::urgent", self.on_workspace_urgent)
|
||||
self.on("workspace::rename", self.on_workspace_rename)
|
||||
self.on("workspace::urgent", self.on_workspace_change)
|
||||
self.on("workspace::rename", self.on_workspace_change)
|
||||
# TODO Un-handled/tested: reload, rename, restored, move
|
||||
|
||||
self.on("mode", self.on_mode)
|
||||
self.modeSection = Section(theme=themeMode)
|
||||
|
||||
def addParent(self, parent):
|
||||
self.parents.add(parent)
|
||||
parent.addSection(self.modeSection)
|
||||
self.initialPopulation(parent)
|
||||
|
||||
|
||||
class MpdProvider(Section, ThreadedUpdater):
|
||||
# TODO FEAT More informations and controls
|
||||
|
||||
MAX_LENGTH = 50
|
||||
|
||||
def connect(self):
|
||||
def connect(self) -> None:
|
||||
self.mpd.connect("localhost", 6600)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
def __init__(self, theme: int | None = None):
|
||||
ThreadedUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
|
||||
|
@ -780,7 +821,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
|||
self.refreshData()
|
||||
self.start()
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
stat = self.mpd.status()
|
||||
if not len(stat) or stat["state"] == "stop":
|
||||
return None
|
||||
|
@ -791,7 +832,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
|||
|
||||
infos = []
|
||||
|
||||
def tryAdd(field):
|
||||
def tryAdd(field: str) -> None:
|
||||
if field in cur:
|
||||
infos.append(cur[field])
|
||||
|
||||
|
@ -805,7 +846,7 @@ class MpdProvider(Section, ThreadedUpdater):
|
|||
|
||||
return " {}".format(infosStr)
|
||||
|
||||
def loop(self):
|
||||
def loop(self) -> None:
|
||||
try:
|
||||
self.mpd.idle("player")
|
||||
self.refreshData()
|
||||
|
@ -814,3 +855,104 @@ class MpdProvider(Section, ThreadedUpdater):
|
|||
self.connect()
|
||||
except BaseException as e:
|
||||
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 math
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
@ -11,8 +12,8 @@ import coloredlogs
|
|||
import i3ipc
|
||||
import pyinotify
|
||||
|
||||
from frobar.display import Text
|
||||
from frobar.notbusy import notBusy
|
||||
from frobar.common import notBusy
|
||||
from frobar.display import Element
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
@ -20,24 +21,23 @@ log = logging.getLogger()
|
|||
# TODO Sync bar update with PeriodicUpdater updates
|
||||
|
||||
|
||||
|
||||
class Updater:
|
||||
@staticmethod
|
||||
def init():
|
||||
def init() -> None:
|
||||
PeriodicUpdater.init()
|
||||
InotifyUpdater.init()
|
||||
notBusy.set()
|
||||
|
||||
def updateText(self, text):
|
||||
def updateText(self, text: Element) -> None:
|
||||
print(text)
|
||||
|
||||
def fetcher(self):
|
||||
def fetcher(self) -> Element:
|
||||
return "{} refreshed".format(self)
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def refreshData(self):
|
||||
def refreshData(self) -> None:
|
||||
# TODO OPTI Maybe discard the refresh if there's already another one?
|
||||
self.lock.acquire()
|
||||
try:
|
||||
|
@ -50,7 +50,7 @@ class Updater:
|
|||
|
||||
|
||||
class PeriodicUpdaterThread(threading.Thread):
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
# TODO Sync with system clock
|
||||
counter = 0
|
||||
while True:
|
||||
|
@ -67,6 +67,7 @@ class PeriodicUpdaterThread(threading.Thread):
|
|||
provider.refreshData()
|
||||
else:
|
||||
notBusy.clear()
|
||||
assert PeriodicUpdater.intervalStep is not None
|
||||
counter += PeriodicUpdater.intervalStep
|
||||
counter = counter % PeriodicUpdater.intervalLoop
|
||||
for interval in PeriodicUpdater.intervals.keys():
|
||||
|
@ -80,43 +81,42 @@ class PeriodicUpdater(Updater):
|
|||
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||
"""
|
||||
|
||||
intervals = dict()
|
||||
intervalStep = None
|
||||
intervalLoop = None
|
||||
updateThread = PeriodicUpdaterThread(daemon=True)
|
||||
intervals: dict[int, set["PeriodicUpdater"]] = dict()
|
||||
intervalStep: int | None = None
|
||||
intervalLoop: int
|
||||
updateThread: threading.Thread = PeriodicUpdaterThread(daemon=True)
|
||||
intervalsChanged = threading.Event()
|
||||
|
||||
@staticmethod
|
||||
def gcds(*args):
|
||||
def gcds(*args: int) -> int:
|
||||
return functools.reduce(math.gcd, args)
|
||||
|
||||
@staticmethod
|
||||
def lcm(a, b):
|
||||
def lcm(a: int, b: int) -> int:
|
||||
"""Return lowest common multiple."""
|
||||
return a * b // math.gcd(a, b)
|
||||
|
||||
@staticmethod
|
||||
def lcms(*args):
|
||||
def lcms(*args: int) -> int:
|
||||
"""Return lowest common multiple."""
|
||||
return functools.reduce(PeriodicUpdater.lcm, args)
|
||||
|
||||
@staticmethod
|
||||
def updateIntervals():
|
||||
def updateIntervals() -> None:
|
||||
intervalsList = list(PeriodicUpdater.intervals.keys())
|
||||
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
||||
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
||||
PeriodicUpdater.intervalsChanged.set()
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
def init() -> None:
|
||||
PeriodicUpdater.updateThread.start()
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
Updater.__init__(self)
|
||||
self.interval = None
|
||||
self.interval: int | None = None
|
||||
|
||||
def changeInterval(self, interval):
|
||||
assert isinstance(interval, int)
|
||||
def changeInterval(self, interval: int) -> None:
|
||||
|
||||
if self.interval is not None:
|
||||
PeriodicUpdater.intervals[self.interval].remove(self)
|
||||
|
@ -131,12 +131,7 @@ class PeriodicUpdater(Updater):
|
|||
|
||||
|
||||
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
||||
def process_default(self, event):
|
||||
# DEBUG
|
||||
# from pprint import pprint
|
||||
# pprint(event.__dict__)
|
||||
# return
|
||||
|
||||
def process_default(self, event: pyinotify.Event) -> None:
|
||||
assert event.path in InotifyUpdater.paths
|
||||
|
||||
if 0 in InotifyUpdater.paths[event.path]:
|
||||
|
@ -154,10 +149,10 @@ class InotifyUpdater(Updater):
|
|||
"""
|
||||
|
||||
wm = pyinotify.WatchManager()
|
||||
paths = dict()
|
||||
paths: dict[str, dict[str | int, set["InotifyUpdater"]]] = dict()
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
def init() -> None:
|
||||
notifier = pyinotify.ThreadedNotifier(
|
||||
InotifyUpdater.wm, InotifyUpdaterEventHandler()
|
||||
)
|
||||
|
@ -166,14 +161,14 @@ class InotifyUpdater(Updater):
|
|||
# TODO Mask for folders
|
||||
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))
|
||||
|
||||
# Detect if file or folder
|
||||
if os.path.isdir(path):
|
||||
self.dirpath = path
|
||||
self.dirpath: str = path
|
||||
# 0: Directory watcher
|
||||
self.filename = 0
|
||||
self.filename: str | int = 0
|
||||
elif os.path.isfile(path):
|
||||
self.dirpath = os.path.dirname(path)
|
||||
self.filename = os.path.basename(path)
|
||||
|
@ -195,12 +190,12 @@ class InotifyUpdater(Updater):
|
|||
|
||||
|
||||
class ThreadedUpdaterThread(threading.Thread):
|
||||
def __init__(self, updater, *args, **kwargs):
|
||||
def __init__(self, updater: "ThreadedUpdater") -> None:
|
||||
self.updater = updater
|
||||
threading.Thread.__init__(self, *args, **kwargs)
|
||||
threading.Thread.__init__(self, daemon=True)
|
||||
self.looping = True
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
try:
|
||||
while self.looping:
|
||||
self.updater.loop()
|
||||
|
@ -215,57 +210,31 @@ class ThreadedUpdater(Updater):
|
|||
Must implement loop(), and call start()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
Updater.__init__(self)
|
||||
self.thread = ThreadedUpdaterThread(self, daemon=True)
|
||||
self.thread = ThreadedUpdaterThread(self)
|
||||
|
||||
def loop(self):
|
||||
def loop(self) -> None:
|
||||
self.refreshData()
|
||||
time.sleep(10)
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
self.thread.start()
|
||||
|
||||
|
||||
class I3Updater(ThreadedUpdater):
|
||||
# TODO OPTI One i3 connection for all
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
ThreadedUpdater.__init__(self)
|
||||
self.i3 = i3ipc.Connection()
|
||||
self.on = self.i3.on
|
||||
self.start()
|
||||
|
||||
def on(self, event, function):
|
||||
self.i3.on(event, function)
|
||||
|
||||
def loop(self):
|
||||
def loop(self) -> None:
|
||||
self.i3.main()
|
||||
|
||||
|
||||
class MergedUpdater(Updater):
|
||||
# TODO OPTI Do not update until end of periodic batch
|
||||
def fetcher(self):
|
||||
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] = ""
|
||||
def __init__(self, *args: Updater) -> None:
|
||||
raise NotImplementedError("Deprecated, as hacky and currently unused")
|
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
|
||||
signal-desktop
|
||||
(pkgs.callPackage ./whisperx.nix {}) # Transcribe voice messages
|
||||
|
||||
# downloading
|
||||
# transmission TODO Collision if both transmissions are active?
|
||||
|
@ -34,21 +35,20 @@
|
|||
# TODO Convert existing LaTeX documents into using Nix build system
|
||||
# texlive is big and not that much used, sooo
|
||||
pdftk
|
||||
hunspell
|
||||
hunspellDicts.en_GB-ize
|
||||
hunspellDicts.en_US
|
||||
hunspellDicts.fr-moderne
|
||||
hunspellDicts.nl_NL
|
||||
# TODO libreoffice-extension-languagetool or libreoffice-extension-grammalecte-fr
|
||||
pdfgrep
|
||||
|
||||
# Misc
|
||||
haskellPackages.dice
|
||||
rustdesk-flutter
|
||||
|
||||
] ++ lib.optionals config.frogeye.desktop.xorg [
|
||||
|
||||
# multimedia editors
|
||||
gimp
|
||||
inkscape
|
||||
darktable
|
||||
puddletag
|
||||
audacity
|
||||
xournalpp
|
||||
krita
|
||||
|
||||
# downloading
|
||||
transmission-qt
|
||||
|
@ -64,8 +64,5 @@
|
|||
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
|
||||
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 {
|
||||
# Using config.nixpkgs.<something> creates an infinite recursion,
|
||||
# but the above might not be correct in case of cross-compiling?
|
||||
home.packages = with pkgs; [
|
||||
home = {
|
||||
packages = with pkgs; [
|
||||
# gaming
|
||||
yuzu-mainline
|
||||
minecraft
|
||||
dolphin-emu
|
||||
ryujinx
|
||||
prismlauncher
|
||||
# TODO factorio
|
||||
|
||||
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
|
||||
#! 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"
|
||||
cachedir="$HOME/.cache/lip"
|
||||
|
@ -16,7 +16,6 @@ then
|
|||
jq_sel="$@"
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$ip" ]
|
||||
then
|
||||
cachefile="$cachedir/$ip"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#! nix-shell -i bash --pure
|
||||
#! 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,
|
||||
# meaning it will compress files as much as possible,
|
||||
|
@ -13,14 +15,13 @@
|
|||
# TODO Lots of dupplicated code there
|
||||
# TODO Maybe replace part with https://github.com/toy/image_optim?
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
dir=${1:-$PWD}
|
||||
total=$(mktemp)
|
||||
echo -n 0 > $total
|
||||
echo -n 0 > "$total"
|
||||
|
||||
function showtotal {
|
||||
echo "Total saved: $(cat "$total") bytes"
|
||||
rm $total
|
||||
rm "$total"
|
||||
exit
|
||||
}
|
||||
|
||||
|
@ -28,11 +29,11 @@ trap showtotal SIGTERM SIGINT SIGFPE
|
|||
|
||||
function doReplace { # candidate original
|
||||
mv "$c" "$o"
|
||||
saved=$(($os - $cs))
|
||||
perc=$((100 * $saved / $os))
|
||||
saved=$((os - cs))
|
||||
perc=$((100 * saved / os))
|
||||
echo "→ $os ⇒ $cs (saved $saved bytes, or ${perc}%)"
|
||||
newtotal=$(($(cat $total) + $saved))
|
||||
echo -n $newtotal > $total
|
||||
newtotal=$(($(cat "$total") + saved))
|
||||
echo -n $newtotal > "$total"
|
||||
}
|
||||
|
||||
function replace { # candidate original
|
||||
|
@ -52,17 +53,17 @@ function replace { # candidate original
|
|||
# Size verifications
|
||||
cs=$(wc -c "$c" | 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!"
|
||||
rm "$c"
|
||||
return
|
||||
fi
|
||||
if [ $cs -eq $os ]; then
|
||||
if [ "$cs" -eq "$os" ]; then
|
||||
echo "→ Candidate weight the same, skipping."
|
||||
rm "$c"
|
||||
return
|
||||
fi
|
||||
if [ $cs -gt $os ]; then
|
||||
if [ "$cs" -gt "$os" ]; then
|
||||
echo "→ Candidate is larger, skipping."
|
||||
rm "$c"
|
||||
return
|
||||
|
@ -71,76 +72,75 @@ function replace { # candidate original
|
|||
doReplace "$c" "$o"
|
||||
}
|
||||
|
||||
function replaceImg { # candidate original
|
||||
# With bitmap verification
|
||||
|
||||
c="$1"
|
||||
o="$2"
|
||||
|
||||
# File verifications
|
||||
if [ ! -f "$o" ]; then
|
||||
echo "→ Original is inexistant, skipping!"
|
||||
return
|
||||
fi
|
||||
if [ ! -f "$c" ]; then
|
||||
echo "→ Candidate is inexistant, skipping!"
|
||||
return
|
||||
fi
|
||||
|
||||
# Size verifications
|
||||
cs=$(wc -c "$c" | cut -d' ' -f1)
|
||||
os=$(wc -c "$o" | cut -d' ' -f1)
|
||||
if [ $cs -le 0 ]; then
|
||||
echo "→ Candidate is empty, skipping!"
|
||||
rm "$c"
|
||||
return
|
||||
fi
|
||||
if [ $cs -eq $os ]; then
|
||||
echo "→ Candidate weight the same, skipping."
|
||||
rm "$c"
|
||||
return
|
||||
fi
|
||||
if [ $cs -gt $os ]; then
|
||||
echo "→ Candidate is larger, skipping."
|
||||
rm "$c"
|
||||
return
|
||||
fi
|
||||
|
||||
# Bitmap verification
|
||||
ppmc="$(mktemp --suffix .ppm)"
|
||||
ppmo="$(mktemp --suffix .ppm)"
|
||||
convert "$c" "$ppmc"
|
||||
convert "$o" "$ppmo"
|
||||
|
||||
if cmp --silent "$ppmo" "$ppmc"; then
|
||||
doReplace "$c" "$o"
|
||||
else
|
||||
echo "→ Candidate don't have the same bit map as original, skipping!"
|
||||
fi
|
||||
rm -f "$ppmc" "$ppmo" "$c"
|
||||
|
||||
}
|
||||
# function replaceImg { # candidate original
|
||||
# # With bitmap verification
|
||||
#
|
||||
# c="$1"
|
||||
# o="$2"
|
||||
#
|
||||
# # File verifications
|
||||
# if [ ! -f "$o" ]; then
|
||||
# echo "→ Original is inexistant, skipping!"
|
||||
# return
|
||||
# fi
|
||||
# if [ ! -f "$c" ]; then
|
||||
# echo "→ Candidate is inexistant, skipping!"
|
||||
# return
|
||||
# fi
|
||||
#
|
||||
# # Size verifications
|
||||
# cs=$(wc -c "$c" | cut -d' ' -f1)
|
||||
# os=$(wc -c "$o" | cut -d' ' -f1)
|
||||
# if [ $cs -le 0 ]; then
|
||||
# echo "→ Candidate is empty, skipping!"
|
||||
# rm "$c"
|
||||
# return
|
||||
# fi
|
||||
# if [ $cs -eq $os ]; then
|
||||
# echo "→ Candidate weight the same, skipping."
|
||||
# rm "$c"
|
||||
# return
|
||||
# fi
|
||||
# if [ $cs -gt $os ]; then
|
||||
# echo "→ Candidate is larger, skipping."
|
||||
# rm "$c"
|
||||
# return
|
||||
# fi
|
||||
#
|
||||
# # Bitmap verification
|
||||
# ppmc="$(mktemp --suffix .ppm)"
|
||||
# ppmo="$(mktemp --suffix .ppm)"
|
||||
# convert "$c" "$ppmc"
|
||||
# convert "$o" "$ppmo"
|
||||
#
|
||||
# if cmp --silent "$ppmo" "$ppmc"; then
|
||||
# doReplace "$c" "$o"
|
||||
# else
|
||||
# echo "→ Candidate don't have the same bit map as original, skipping!"
|
||||
# fi
|
||||
# rm -f "$ppmc" "$ppmo" "$c"
|
||||
#
|
||||
# }
|
||||
|
||||
# JPEG (requires jpegtran)
|
||||
while read image
|
||||
while read -r image
|
||||
do
|
||||
if [ -z "$image" ]; then continue; fi
|
||||
echo Processing $image
|
||||
echo Processing "$image"
|
||||
|
||||
prog=$(mktemp --suffix .jpg)
|
||||
jpegtran -copy all -progressive "$image" > "$prog"
|
||||
echo "→ Progressive done"
|
||||
progs=$(wc -c "$prog" | cut -d' ' -f1)
|
||||
replace "$prog" "$image"
|
||||
|
||||
|
||||
done <<< "$(find "$dir/" -type f -iregex ".+.jpe?g$")"
|
||||
|
||||
# PNG (requires optipng)
|
||||
while read image
|
||||
while read -r image
|
||||
do
|
||||
if [ -z "$image" ]; then continue; fi
|
||||
echo Processing $image
|
||||
echo Processing "$image"
|
||||
|
||||
temp=$(mktemp --suffix .png)
|
||||
cp "$image" "$temp"
|
||||
|
@ -152,14 +152,13 @@ do
|
|||
done <<< "$(find "$dir/" -type f -iname "*.png")"
|
||||
|
||||
# FLAC (requires ffmpeg)
|
||||
while read music
|
||||
while read -r music
|
||||
do
|
||||
if [ -z "$music" ]; then continue; fi
|
||||
echo Processing $music
|
||||
echo "Processing $music"
|
||||
|
||||
temp=$(mktemp --suffix .flac)
|
||||
cp "$music" "$temp"
|
||||
ffmpeg -8 -o "$temp"
|
||||
ffmpeg -nostdin -y -i "$music" -compression_level 8 "$temp"
|
||||
echo "→ Optimize done"
|
||||
|
||||
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
|
||||
# So rather use scour explicitely when needed
|
||||
|
||||
${SCRIPT_DIR}/cleandev
|
||||
cleandev
|
||||
|
||||
showtotal
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#! nix-shell -i bash --pure
|
||||
#! nix-shell -i bash
|
||||
#! nix-shell -p bash pdftk inkscape gnused coreutils file
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# Utility to write over a PDF file pages
|
||||
|
||||
# TODO Inkscape vodoo: Put the original in its own layer and skip when merging
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#! 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
|
||||
# 24.05 rsgain replaces it, does the same job as I do with albums
|
||||
|
||||
# Normalisation is done at the default of each program,
|
||||
# which is usually -89.0 dB
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue