Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
d8ae0467c3 |
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
result
|
*/hm
|
||||||
|
*/system
|
||||||
|
*/vm
|
||||||
|
*/vmWithBootLoader
|
||||||
*.qcow2
|
*.qcow2
|
||||||
|
|
|
@ -28,6 +28,7 @@ It is built on top of the Nix ecosystem
|
||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
They all have a `-h` flag.
|
They all have a `-h` flag.
|
||||||
|
Except `add_channels.sh`, which should be removed as soon as I migrate to Flakes.
|
||||||
|
|
||||||
## Extensions
|
## Extensions
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
frogeye = {
|
|
||||||
name = "abavorana";
|
|
||||||
storageSize = "big";
|
|
||||||
syncthing.name = "Abavorana";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
8
add_channels.sh
Executable file
8
add_channels.sh
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/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
Executable file
70
build_hm.sh
Executable file
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#! nix-shell -i bash
|
||||||
|
#! nix-shell -p bash nix-output-monitor
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
function help {
|
||||||
|
echo "Usage: $0 [-h|-v|-b] profile"
|
||||||
|
echo "Build Home Manager configuration on the local machine."
|
||||||
|
echo
|
||||||
|
echo "Arguments:"
|
||||||
|
echo " profile: Home Manager profile to use"
|
||||||
|
echo
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h: Display this help message."
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts "h" OPTION
|
||||||
|
do
|
||||||
|
case "$OPTION" in
|
||||||
|
h)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
?)
|
||||||
|
help
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift "$(($OPTIND -1))"
|
||||||
|
|
||||||
|
if [ "$#" -ne 1 ]
|
||||||
|
then
|
||||||
|
help
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
profile="$1"
|
||||||
|
|
||||||
|
profile_dir="${SCRIPT_DIR}/${profile}"
|
||||||
|
if [ ! -d "$profile_dir" ]
|
||||||
|
then
|
||||||
|
echo "Profile not found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
home_manager_config="${profile_dir}/hm.nix"
|
||||||
|
if [ ! -f "$home_manager_config" ]
|
||||||
|
then
|
||||||
|
echo "Home Manager configuration not found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
nom-build '<home-manager/home-manager/home-manager.nix>' --argstr confPath "${home_manager_config}" -o "${profile_dir}/hm"
|
||||||
|
|
||||||
|
set +x
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
path="$(readlink -f "${profile_dir}/hm")"
|
||||||
|
|
||||||
|
echo "Manual installation instructions:"
|
||||||
|
echo "- Transfer $path and dependencies to the destination machine (somehow)"
|
||||||
|
echo "- Run $path/activate as the destination user"
|
||||||
|
echo "- Log into the user again to make sure everything is sourced"
|
||||||
|
echo "- Transfer necessary private keys (or use ssh -A for testing)"
|
||||||
|
echo "- Run git-sync-init"
|
||||||
|
echo "- Check that the system can build itself"
|
36
build_os.sh
36
build_os.sh
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#! nix-shell -i bash
|
#! nix-shell -i bash
|
||||||
#! nix-shell -p nix
|
#! nix-shell -p bash nix-output-monitor
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
function help {
|
function help {
|
||||||
echo "Usage: $0 [-h|-e|-b] [flake-uri#]name"
|
echo "Usage: $0 [-h|-v|-b] profile"
|
||||||
echo "Build a NixOS configuration on the local machine."
|
echo "Build NixOS configuration on the local machine."
|
||||||
echo
|
echo
|
||||||
echo "Arguments:"
|
echo "Arguments:"
|
||||||
echo " profile: OS/disk profile to use"
|
echo " profile: OS/disk profile to use"
|
||||||
|
@ -19,7 +19,7 @@ function help {
|
||||||
echo " -b: Build a virtual machine with boot loader."
|
echo " -b: Build a virtual machine with boot loader."
|
||||||
}
|
}
|
||||||
|
|
||||||
arg=build
|
attr=system
|
||||||
while getopts "hvb" OPTION
|
while getopts "hvb" OPTION
|
||||||
do
|
do
|
||||||
case "$OPTION" in
|
case "$OPTION" in
|
||||||
|
@ -28,10 +28,10 @@ do
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
v)
|
v)
|
||||||
arg=build-vm
|
attr=vm
|
||||||
;;
|
;;
|
||||||
b)
|
b)
|
||||||
arg=build-vm-with-bootloader
|
attr=vmWithBootLoader
|
||||||
;;
|
;;
|
||||||
?)
|
?)
|
||||||
help
|
help
|
||||||
|
@ -39,35 +39,29 @@ do
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
shift "$((OPTIND -1))"
|
shift "$(($OPTIND -1))"
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]
|
if [ "$#" -ne 1 ]
|
||||||
then
|
then
|
||||||
help
|
help
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
profile="$1"
|
||||||
|
|
||||||
if [[ "$1" == *"#"* ]]
|
profile_dir="${SCRIPT_DIR}/${profile}"
|
||||||
|
if [ ! -d "$profile_dir" ]
|
||||||
then
|
then
|
||||||
flake_uri="$(echo "$1" | cut -d'#' -f1)"
|
echo "Profile not found."
|
||||||
flake_uri=$( cd -- "$flake_uri" &> /dev/null && pwd )
|
|
||||||
name="$(echo "$1" | cut -d'#' -f2)"
|
|
||||||
else
|
|
||||||
flake_uri="$SCRIPT_DIR"
|
|
||||||
name="$1"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$flake_uri/flake.nix" ]
|
nixos_config="${profile_dir}/os.nix"
|
||||||
|
if [ ! -f "$nixos_config" ]
|
||||||
then
|
then
|
||||||
echo "Flake not found."
|
echo "NixOS configuration not found."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
flake="${flake_uri}#${name}"
|
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
nix --extra-experimental-features "nix-command flakes" run "${SCRIPT_DIR}#nixos-rebuild" -- "$arg" --flake "$flake"
|
nom-build '<nixpkgs/nixos>' -I "nixos-config=${nixos_config}" -A "$attr" -o "${profile_dir}/${attr}"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# TODO Use update-local-flakes?
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
{ 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
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))
|
|
|
@ -1,2 +0,0 @@
|
||||||
{ pkgs, ... }:
|
|
||||||
pkgs.writers.writePython3Bin "update-local-flakes" { } (builtins.readFile ./update-local-flakes.py)
|
|
|
@ -1,3 +0,0 @@
|
||||||
(self: super: {
|
|
||||||
update-local-flakes = super.callPackage ./. { };
|
|
||||||
})
|
|
|
@ -1,62 +0,0 @@
|
||||||
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)
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
frogeye = {
|
|
||||||
desktop.xorg = true;
|
|
||||||
dev = {
|
|
||||||
c = true;
|
|
||||||
vm = true;
|
|
||||||
};
|
|
||||||
extra = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
{
|
|
||||||
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" ];
|
|
||||||
# Times tpm2.target times out waiting for /dev/tpmrm0,
|
|
||||||
# it's fine after unlocking though
|
|
||||||
initrd.systemd.tpm2.enable = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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";
|
|
||||||
};
|
|
||||||
|
|
||||||
# 8 makes it run out of memory when rebuilding.
|
|
||||||
nix.settings.max-jobs = 1;
|
|
||||||
|
|
||||||
};
|
|
||||||
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,25 +2,13 @@
|
||||||
# MANU Snapper is not able to create the snapshot directory, so you'll need to do this after eventually running the backup script:
|
# MANU Snapper is not able to create the snapshot directory, so you'll need to do this after eventually running the backup script:
|
||||||
# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots
|
# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots
|
||||||
let
|
let
|
||||||
backup_subvolumes = [
|
backup_subvolumes = [ "nixos" "home.rapido" ];
|
||||||
"nixos"
|
|
||||||
"home.rapido"
|
|
||||||
"home.nixos"
|
|
||||||
];
|
|
||||||
backup_app = pkgs.writeShellApplication {
|
backup_app = pkgs.writeShellApplication {
|
||||||
name = "backup-subvolume";
|
name = "backup-subvolume";
|
||||||
runtimeInputs = with pkgs; [
|
runtimeInputs = with pkgs; [ coreutils btrfs-progs ];
|
||||||
coreutils
|
|
||||||
btrfs-progs
|
|
||||||
];
|
|
||||||
text = builtins.readFile ./backup.sh;
|
text = builtins.readFile ./backup.sh;
|
||||||
};
|
};
|
||||||
snapper_subvolumes = [
|
snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" ];
|
||||||
"nixos"
|
|
||||||
"home.rapido"
|
|
||||||
"home.razmo"
|
|
||||||
"home.nixos"
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services =
|
services =
|
||||||
|
@ -40,26 +28,21 @@ in
|
||||||
# cleanup hourly snapshots after some time
|
# cleanup hourly snapshots after some time
|
||||||
TIMELINE_CLEANUP = true;
|
TIMELINE_CLEANUP = true;
|
||||||
TIMELINE_MIN_AGE = 1800;
|
TIMELINE_MIN_AGE = 1800;
|
||||||
TIMELINE_LIMIT_HOURLY = "24";
|
TIMELINE_LIMIT_HOURLY = 24;
|
||||||
TIMELINE_LIMIT_DAILY = "31";
|
TIMELINE_LIMIT_DAILY = 31;
|
||||||
TIMELINE_LIMIT_WEEKLY = "8";
|
TIMELINE_LIMIT_WEEKLY = 8;
|
||||||
TIMELINE_LIMIT_MONTHLY = "0";
|
TIMELINE_LIMIT_MONTHLY = 0;
|
||||||
TIMELINE_LIMIT_YEARLY = "0";
|
TIMELINE_LIMIT_YEARLY = 0;
|
||||||
|
|
||||||
# cleanup empty pre-post-pairs
|
# cleanup empty pre-post-pairs
|
||||||
EMPTY_PRE_POST_CLEANUP = true;
|
EMPTY_PRE_POST_CLEANUP = true;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
snapper.configs = lib.attrsets.mergeAttrsList (
|
snapper.configs = lib.attrsets.mergeAttrsList (map (s: { "${s}" = default // { SUBVOLUME = "/mnt/razmo/${s}"; }; }) snapper_subvolumes);
|
||||||
map (s: {
|
|
||||||
"${s}" = default // {
|
|
||||||
SUBVOLUME = "/mnt/razmo/${s}";
|
|
||||||
};
|
|
||||||
}) snapper_subvolumes
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
systemd = {
|
systemd = {
|
||||||
services.bkp_rapido = {
|
services.bkp_rapido = {
|
||||||
description = "Make a snapshot of the SSD to the HDD";
|
description = "Make a snapshot of the SSD to the HDD";
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
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 {
|
|
||||||
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
|
|
||||||
sed -i 's|"usb"|"hidraw"|' $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"
|
|
||||||
"~@resources"
|
|
||||||
];
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
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"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
frogeye.name = "curacao";
|
|
||||||
};
|
|
||||||
imports = [
|
|
||||||
./backup
|
|
||||||
./co2meter
|
|
||||||
./dedup
|
|
||||||
./desk
|
|
||||||
./disko.nix
|
|
||||||
./features.nix
|
|
||||||
./hardware.nix
|
|
||||||
./homeautomation
|
|
||||||
./webcam
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
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"
|
|
||||||
"~@resources"
|
|
||||||
];
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,381 +0,0 @@
|
||||||
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.
|
|
||||||
# Tried to decompile 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()
|
|
||||||
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)
|
|
||||||
|
|
||||||
last_published_state = None
|
|
||||||
|
|
||||||
def fetch_callback(desk: Desk) -> None:
|
|
||||||
hcur = desk.get_height()
|
|
||||||
hmin, hmax = desk.get_height_bounds()
|
|
||||||
global last_published_state
|
|
||||||
|
|
||||||
state = hcur, hmin, hmax
|
|
||||||
if state == last_published_state:
|
|
||||||
return
|
|
||||||
last_published_state = state
|
|
||||||
|
|
||||||
# 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,46 +1,29 @@
|
||||||
{
|
{ passwordFile ? "/should_not_be_needed_in_this_context", ... }:
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
# TODO Find a way to use keys in filesystem
|
# TODO Find a way to use keys in filesystem
|
||||||
# TODO Not relatime everywhere, thank you
|
# TODO Not relatime everywhere, thank you
|
||||||
# TODO Default options
|
# TODO Default options
|
||||||
let
|
let
|
||||||
btrfs_args_ssd = [
|
btrfs_args_hdd = [
|
||||||
"rw"
|
"rw"
|
||||||
"relatime"
|
"relatime"
|
||||||
"compress=zstd:3"
|
"compress=zstd:3"
|
||||||
"space_cache"
|
"space_cache"
|
||||||
"ssd"
|
|
||||||
];
|
];
|
||||||
passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
|
btrfs_args_ssd = btrfs_args_hdd ++ [ "ssd" ];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
razmo = {
|
razmo = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/ata-SDLF1DAR-960G-1HA1_A027C1A3";
|
device = "/dev/disk/by-id/ata-ST1000LM048-2E7172_WKP8925H";
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
ESP = {
|
|
||||||
# Needs enough to store multiple kernel generations
|
|
||||||
size = "512M";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
swap = {
|
swap = {
|
||||||
size = "8G";
|
priority = 10;
|
||||||
|
start = "2048";
|
||||||
|
size = "6G";
|
||||||
content = {
|
content = {
|
||||||
type = "swap";
|
type = "swap";
|
||||||
randomEncryption = true;
|
randomEncryption = true;
|
||||||
|
@ -49,35 +32,81 @@ in
|
||||||
# hibernation image is saved. That's what I'm doing with Arch,
|
# hibernation image is saved. That's what I'm doing with Arch,
|
||||||
# but I'm setting resume=, should test if it actually works?
|
# but I'm setting resume=, should test if it actually works?
|
||||||
# Untranslated options from /etc/crypttab: swap,cipher=aes-xts-plain64,size=256
|
# Untranslated options from /etc/crypttab: swap,cipher=aes-xts-plain64,size=256
|
||||||
|
# Untranslated options from /etc/fstab: defaults,pri=100
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
luks = {
|
nixosboot = {
|
||||||
size = "100%";
|
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;
|
||||||
content = {
|
content = {
|
||||||
type = "luks";
|
type = "luks";
|
||||||
name = "razmo";
|
name = "razmo";
|
||||||
passwordFile = passwordFile;
|
passwordFile = passwordFile;
|
||||||
settings = {
|
settings = {
|
||||||
allowDiscards = true;
|
# keyFile = "/etc/keys/razmo";
|
||||||
};
|
};
|
||||||
content = {
|
content = {
|
||||||
type = "btrfs";
|
type = "btrfs";
|
||||||
extraArgs = [ "-f" ];
|
# extraArgs = [ "-f" ];
|
||||||
mountpoint = "/mnt/razmo";
|
mountpoint = "/mnt/razmo";
|
||||||
|
mountOptions = btrfs_args_hdd;
|
||||||
subvolumes = {
|
subvolumes = {
|
||||||
"home.razmo" = {
|
"home.razmo" = {
|
||||||
mountpoint = "/home.heavy";
|
mountpoint = "/home.heavy";
|
||||||
mountOptions = [
|
mountOptions = btrfs_args_hdd;
|
||||||
"compress=zstd"
|
|
||||||
"relatime"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
"steam" = {
|
|
||||||
mountpoint = "/opt/steam.razmo";
|
|
||||||
mountOptions = [
|
|
||||||
"compress=zstd"
|
|
||||||
"noatime"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -129,6 +158,10 @@ in
|
||||||
mountpoint = "/mnt/rapido";
|
mountpoint = "/mnt/rapido";
|
||||||
mountOptions = btrfs_args_ssd;
|
mountOptions = btrfs_args_ssd;
|
||||||
subvolumes = {
|
subvolumes = {
|
||||||
|
archlinux = {
|
||||||
|
mountpoint = "/mnt/old";
|
||||||
|
mountOptions = btrfs_args_ssd;
|
||||||
|
};
|
||||||
# Should be temporary, to make sure we can revert to Arch anytime
|
# Should be temporary, to make sure we can revert to Arch anytime
|
||||||
"home.nixos" = {
|
"home.nixos" = {
|
||||||
mountpoint = "/home";
|
mountpoint = "/home";
|
||||||
|
@ -154,12 +187,4 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
services.btrfs.autoScrub = {
|
|
||||||
enable = true;
|
|
||||||
fileSystems = [
|
|
||||||
"/mnt/razmo"
|
|
||||||
"/mnt/rapido"
|
|
||||||
];
|
|
||||||
# TODO Should be generable from disko config, right?
|
|
||||||
};
|
|
||||||
}
|
}
|
|
@ -1,123 +1,17 @@
|
||||||
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
nixos-hardware,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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
|
|
||||||
loader = {
|
|
||||||
efi.canTouchEfiVariables = lib.mkDefault true;
|
|
||||||
grub = {
|
|
||||||
enable = true;
|
|
||||||
efiSupport = true;
|
|
||||||
device = "nodev"; # Don't install on MBR
|
|
||||||
# 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?
|
|
||||||
|
|
||||||
# AnnePro 2
|
|
||||||
hardware.keyboard.qmk.enable = true;
|
|
||||||
|
|
||||||
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?
|
|
||||||
};
|
|
||||||
};
|
|
||||||
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 = [
|
imports = [
|
||||||
nixos-hardware.nixosModules.dell-g3-3779
|
<nixos-hardware/dell/g3/3779>
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# UEFI works here, and variables can be touched
|
||||||
|
boot.loader = {
|
||||||
|
efi.canTouchEfiVariables = lib.mkDefault true;
|
||||||
|
grub = {
|
||||||
|
enable = true;
|
||||||
|
efiSupport = true;
|
||||||
|
device = "nodev"; # Don't install on MBR
|
||||||
|
# TODO Maybe we could? In case the HDD doesn't boot anymore?
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
22
curacao/options.nix
Normal file
22
curacao/options.nix
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
18
curacao/os.nix
Normal file
18
curacao/os.nix
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../os
|
||||||
|
./options.nix
|
||||||
|
./hardware.nix
|
||||||
|
./dk.nix
|
||||||
|
./backup
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.hostName = "curacao";
|
||||||
|
boot = {
|
||||||
|
initrd.luks.reusePassphrases = true;
|
||||||
|
loader = {
|
||||||
|
efi.efiSysMountPoint = "/efi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
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 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
12
curacao_test/hm.nix
Normal file
12
curacao_test/hm.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../hm
|
||||||
|
../curacao/options.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
home.username = "gnix";
|
||||||
|
home.homeDirectory = "/home/gnix";
|
||||||
|
|
||||||
|
frogeye.desktop.nixGLIntel = true;
|
||||||
|
}
|
2
curacao_usb/dk.nix
Normal file
2
curacao_usb/dk.nix
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{ ... } @ args:
|
||||||
|
import ../dk/single_uefi_btrfs.nix (args // { id = "usb-Kingston_DataTraveler_3.0_E0D55EA57414F510489F0F1A-0:0"; name = "curacao_usb"; })
|
22
curacao_usb/os.nix
Normal file
22
curacao_usb/os.nix
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{ 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,17 +1,10 @@
|
||||||
{
|
{ id, name, passwordFile ? "/should_not_be_needed_in_this_context", ... }:
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
"${config.frogeye.name}" = {
|
"${name}" = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
|
device = "/dev/disk/by-id/${id}";
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
|
@ -32,7 +25,7 @@ in
|
||||||
size = "100%";
|
size = "100%";
|
||||||
content = {
|
content = {
|
||||||
type = "luks";
|
type = "luks";
|
||||||
name = "${config.frogeye.name}";
|
name = "${name}";
|
||||||
passwordFile = passwordFile;
|
passwordFile = passwordFile;
|
||||||
settings = {
|
settings = {
|
||||||
# Not having SSDs die fast is more important than crypto
|
# Not having SSDs die fast is more important than crypto
|
||||||
|
@ -46,24 +39,15 @@ in
|
||||||
subvolumes = {
|
subvolumes = {
|
||||||
"/nixos" = {
|
"/nixos" = {
|
||||||
mountpoint = "/";
|
mountpoint = "/";
|
||||||
mountOptions = [
|
mountOptions = [ "compress=zstd" "noatime" ];
|
||||||
"compress=zstd"
|
|
||||||
"noatime"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
"/home" = {
|
"/home" = {
|
||||||
mountpoint = "/home";
|
mountpoint = "/home";
|
||||||
mountOptions = [
|
mountOptions = [ "compress=zstd" "relatime" ];
|
||||||
"compress=zstd"
|
|
||||||
"relatime"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
"/nix" = {
|
"/nix" = {
|
||||||
mountpoint = "/nix";
|
mountpoint = "/nix";
|
||||||
mountOptions = [
|
mountOptions = [ "compress=zstd" "noatime" ];
|
||||||
"compress=zstd"
|
|
||||||
"noatime"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
# Maybe later
|
# Maybe later
|
||||||
# "/swap" = {
|
# "/swap" = {
|
55
ensure_nix.sh
Executable file
55
ensure_nix.sh
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/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 "$@"
|
962
flake.lock
962
flake.lock
|
@ -1,962 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"base16": {
|
|
||||||
"inputs": {
|
|
||||||
"fromYaml": "fromYaml"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1732200724,
|
|
||||||
"narHash": "sha256-+R1BH5wHhfnycySb7Sy5KbYEaTJZWm1h+LW1OtyhiTs=",
|
|
||||||
"owner": "SenchoPens",
|
|
||||||
"repo": "base16.nix",
|
|
||||||
"rev": "153d52373b0fb2d343592871009a286ec8837aec",
|
|
||||||
"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-helix": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1725860795,
|
|
||||||
"narHash": "sha256-Z2o8VBPW3I+KKTSfe25kskz0EUj7MpUh8u355Z1nVsU=",
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "base16-helix",
|
|
||||||
"rev": "7f795bf75d38e0eea9fed287264067ca187b88a9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "base16-helix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"base16-vim": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731949548,
|
|
||||||
"narHash": "sha256-XIDexXM66sSh5j/x70e054BnUsviibUShW7XhbDGhYo=",
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "base16-vim",
|
|
||||||
"rev": "61165b1632409bd55e530f3dbdd4477f011cadc6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "base16-vim",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1735644329,
|
|
||||||
"narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"disko": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737038063,
|
|
||||||
"narHash": "sha256-rMEuiK69MDhjz1JgbaeQ9mBDXMJ2/P8vmOYRbFndXsk=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "bf0abfde48f469c256f2b0f481c6281ff04a5db2",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "disko",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"firefox-gnome-theme": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1734969791,
|
|
||||||
"narHash": "sha256-A9PxLienMYJ/WUvqFie9qXrNC2MeRRYw7TG/q7DRjZg=",
|
|
||||||
"owner": "rafaelmardojai",
|
|
||||||
"repo": "firefox-gnome-theme",
|
|
||||||
"rev": "92f4890bd150fc9d97b61b3583680c0524a8cafe",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "rafaelmardojai",
|
|
||||||
"repo": "firefox-gnome-theme",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1736143030,
|
|
||||||
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-parts_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": [
|
|
||||||
"nur",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1733312601,
|
|
||||||
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_3": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": [
|
|
||||||
"stylix",
|
|
||||||
"systems"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fromYaml": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731966426,
|
|
||||||
"narHash": "sha256-lq95WydhbUTWig/JpqiB7oViTcHFP8Lv41IGtayokA8=",
|
|
||||||
"owner": "SenchoPens",
|
|
||||||
"repo": "fromYaml",
|
|
||||||
"rev": "106af9e2f715e2d828df706c386a685698f3223b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "SenchoPens",
|
|
||||||
"repo": "fromYaml",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"nixvim",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737043064,
|
|
||||||
"narHash": "sha256-I/OuxGwXwRi5gnFPsyCvVR+IfFstA+QXEpHu1hvsgD8=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "94ee657f6032d913fe0ef49adaa743804635b0bb",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks_2": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"stylix",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"gitignore": "gitignore_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"stylix",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-stable": [
|
|
||||||
"stylix",
|
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731363552,
|
|
||||||
"narHash": "sha256-vFta1uHnD29VUY4HJOO/D6p6rxyObnf+InnSMT4jlMU=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "cd1af27aa85026ac759d5d3fccf650abe7e1bbf0",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"stylix",
|
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1709087332,
|
|
||||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gnome-shell": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1732369855,
|
|
||||||
"narHash": "sha256-JhUWbcYPjHO3Xs3x9/Z9RuqXbcp5yhPluGjwsdE2GMg=",
|
|
||||||
"owner": "GNOME",
|
|
||||||
"repo": "gnome-shell",
|
|
||||||
"rev": "dadd58f630eeea41d645ee225a63f719390829dc",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "GNOME",
|
|
||||||
"ref": "47.2",
|
|
||||||
"repo": "gnome-shell",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1736373539,
|
|
||||||
"narHash": "sha256-dinzAqCjenWDxuy+MqUQq0I4zUSfaCvN9rzuCmgMZJY=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "bd65bc3cde04c16755955630b344bc9e35272c56",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "home-manager",
|
|
||||||
"ref": "release-24.11",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1736373539,
|
|
||||||
"narHash": "sha256-dinzAqCjenWDxuy+MqUQq0I4zUSfaCvN9rzuCmgMZJY=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "bd65bc3cde04c16755955630b344bc9e35272c56",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "release-24.11",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager_3": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"stylix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1733572789,
|
|
||||||
"narHash": "sha256-zjO6m5BqxXIyjrnUziAzk4+T4VleqjstNudSqWcpsHI=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "c7ffc9727d115e433fd884a62dc164b587ff651d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "release-24.11",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ixx": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": [
|
|
||||||
"nixvim",
|
|
||||||
"nuschtosSearch",
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nuschtosSearch",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1729958008,
|
|
||||||
"narHash": "sha256-EiOq8jF4Z/zQe0QYVc3+qSKxRK//CFHMB84aYrYGwEs=",
|
|
||||||
"owner": "NuschtOS",
|
|
||||||
"repo": "ixx",
|
|
||||||
"rev": "9fd01aad037f345350eab2cd45e1946cc66da4eb",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NuschtOS",
|
|
||||||
"ref": "v0.0.6",
|
|
||||||
"repo": "ixx",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-darwin": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1736820923,
|
|
||||||
"narHash": "sha256-SDuKLOWAh8VJRXlNWQn9QE99bjeEUAAbYXqrKGbsiyk=",
|
|
||||||
"owner": "lnl7",
|
|
||||||
"repo": "nix-darwin",
|
|
||||||
"rev": "944c2b181792ae7ae6b20c0df3f44879c11706c9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "lnl7",
|
|
||||||
"ref": "nix-darwin-24.11",
|
|
||||||
"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": 1737590910,
|
|
||||||
"narHash": "sha256-qM/y6Dtpu9Wmf5HqeZajQdn+cS0aljdYQQQnrvx+LJE=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixos-hardware",
|
|
||||||
"rev": "9368027715d8dde4b84c79c374948b5306fdd2db",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixos-hardware",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737569578,
|
|
||||||
"narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "47addd76727f42d351590c905d9d1905ca895b82",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"ref": "nixos-24.11",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737469691,
|
|
||||||
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"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"
|
|
||||||
],
|
|
||||||
"nuschtosSearch": "nuschtosSearch",
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737283156,
|
|
||||||
"narHash": "sha256-FyHmM6vvz+UxCrPZo/poIaZBZejLHVKkAH4cjtUxZDA=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixvim",
|
|
||||||
"rev": "abcbd250b8a2c7aab1f4b2b9e01598ee24b42337",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "nixos-24.11",
|
|
||||||
"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": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-parts": "flake-parts_2",
|
|
||||||
"nixpkgs": "nixpkgs_2",
|
|
||||||
"treefmt-nix": "treefmt-nix_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737643235,
|
|
||||||
"narHash": "sha256-wv3JCT3vfYUodDmBbRxtOxkWxMQ605K9viJ1AmZlU7I=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "NUR",
|
|
||||||
"rev": "2eb191a4ca7354482dca8a55ce4fa73b986f9617",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "NUR",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nuschtosSearch": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"ixx": "ixx",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1735854821,
|
|
||||||
"narHash": "sha256-Iv59gMDZajNfezTO0Fw6LHE7uKAShxbvMidmZREit7c=",
|
|
||||||
"owner": "NuschtOS",
|
|
||||||
"repo": "search",
|
|
||||||
"rev": "836908e3bddd837ae0f13e215dd48767aee355f0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NuschtOS",
|
|
||||||
"repo": "search",
|
|
||||||
"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-helix": "base16-helix",
|
|
||||||
"base16-vim": "base16-vim",
|
|
||||||
"firefox-gnome-theme": "firefox-gnome-theme",
|
|
||||||
"flake-compat": "flake-compat_2",
|
|
||||||
"flake-utils": "flake-utils_3",
|
|
||||||
"git-hooks": "git-hooks_2",
|
|
||||||
"gnome-shell": "gnome-shell",
|
|
||||||
"home-manager": "home-manager_3",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": "systems_3",
|
|
||||||
"tinted-foot": "tinted-foot",
|
|
||||||
"tinted-kitty": "tinted-kitty",
|
|
||||||
"tinted-tmux": "tinted-tmux"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737585583,
|
|
||||||
"narHash": "sha256-lU00TEdqCCWJNtMbR0l779xRJHtMf5FKCGKcsC+/Hr8=",
|
|
||||||
"owner": "danth",
|
|
||||||
"repo": "stylix",
|
|
||||||
"rev": "9409ae8a925aacc7ea9a794e0bde159b876572a3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "danth",
|
|
||||||
"ref": "release-24.11",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tinted-foot": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1726913040,
|
|
||||||
"narHash": "sha256-+eDZPkw7efMNUf3/Pv0EmsidqdwNJ1TaOum6k7lngDQ=",
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-foot",
|
|
||||||
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-foot",
|
|
||||||
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tinted-kitty": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1716423189,
|
|
||||||
"narHash": "sha256-2xF3sH7UIwegn+2gKzMpFi3pk5DlIlM18+vj17Uf82U=",
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-kitty",
|
|
||||||
"rev": "eb39e141db14baef052893285df9f266df041ff8",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-kitty",
|
|
||||||
"rev": "eb39e141db14baef052893285df9f266df041ff8",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tinted-tmux": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1729501581,
|
|
||||||
"narHash": "sha256-1ohEFMC23elnl39kxWnjzH1l2DFWWx4DhFNNYDTYt54=",
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-tmux",
|
|
||||||
"rev": "f0e7f7974a6441033eb0a172a0342e96722b4f14",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tinted-theming",
|
|
||||||
"repo": "tinted-tmux",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixvim",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737103437,
|
|
||||||
"narHash": "sha256-uPNWcYbhY2fjY3HOfRCR5jsfzdzemhfxLSxwjXYXqNc=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"rev": "d1ed3b385f8130e392870cfb1dbfaff8a63a1899",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nur",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1733222881,
|
|
||||||
"narHash": "sha256-JIPcz1PrpXUCbaccEnrcUS8jjEb/1vJbZz5KkobyFdM=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"rev": "49717b5af6f80172275d47a418c9719a31a78b53",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737644226,
|
|
||||||
"narHash": "sha256-75r2eJS7PKc+ZCdaIeF0PKQ9JM3w0YQvfzfsQfJq6EY=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "09b24484a773a6632f0234b10a33a300ceed73dc",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"ref": "master",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
227
flake.nix
227
flake.nix
|
@ -1,227 +0,0 @@
|
||||||
{
|
|
||||||
description = "Geoffrey Frogeye's base configurations";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
# Packages
|
|
||||||
nixpkgs.url = "nixpkgs/nixos-24.11";
|
|
||||||
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.11 yet
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
inputs.home-manager.follows = "home-manager";
|
|
||||||
};
|
|
||||||
# HM
|
|
||||||
home-manager = {
|
|
||||||
url = "home-manager/release-24.11";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
stylix = {
|
|
||||||
url = "github:danth/stylix/release-24.11";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
nixvim = {
|
|
||||||
url = "github:nix-community/nixvim/nixos-24.11";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
nur.url = "github:nix-community/NUR";
|
|
||||||
# Local
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs =
|
|
||||||
{
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
unixpkgs,
|
|
||||||
disko,
|
|
||||||
nix-on-droid,
|
|
||||||
flake-utils,
|
|
||||||
nur,
|
|
||||||
...
|
|
||||||
}@attrs:
|
|
||||||
# Machine independant outputs
|
|
||||||
let
|
|
||||||
nixpkgsConfig = {
|
|
||||||
config = {
|
|
||||||
allowUnfree = true;
|
|
||||||
# TODO Handpick exceptions
|
|
||||||
};
|
|
||||||
overlays = [
|
|
||||||
(import ./common/update-local-flakes/overlay.nix)
|
|
||||||
nur.overlays.default
|
|
||||||
(
|
|
||||||
# Cherry-pick packages from future
|
|
||||||
self: super:
|
|
||||||
let
|
|
||||||
upkgs = import unixpkgs { inherit (super) system; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
jjui = upkgs.jjui;
|
|
||||||
labelle = upkgs.labelle;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
homeManagerConfig = {
|
|
||||||
sharedModules = [ self.homeManagerModules.dotfiles ];
|
|
||||||
extraSpecialArgs = attrs;
|
|
||||||
};
|
|
||||||
lib = {
|
|
||||||
nixosSystem =
|
|
||||||
{
|
|
||||||
system,
|
|
||||||
modules ? [ ],
|
|
||||||
}:
|
|
||||||
nixpkgs.lib.nixosSystem {
|
|
||||||
inherit system;
|
|
||||||
specialArgs = attrs // {
|
|
||||||
upkgs = import unixpkgs {
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
modules = modules ++ [
|
|
||||||
self.nixosModules.dotfiles
|
|
||||||
# nur.modules.nixos.default
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
# We do an overlay here so nixos-rebuild and other use lix.
|
|
||||||
# We don't do an overlay for the whole system because lix is not binary compatible.
|
|
||||||
overlays = [
|
|
||||||
(self: super: { nix = super.lix; })
|
|
||||||
];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
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
|
|
||||||
jq
|
|
||||||
];
|
|
||||||
text = builtins.readFile ./os/rebuild.sh;
|
|
||||||
}
|
|
||||||
}/bin/rebuild ${self} "$@"''}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
formatter = pkgs.nixfmt-rfc-style;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
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.ludwig = lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [ ./ludwig/standin.nix ];
|
|
||||||
};
|
|
||||||
nixosConfigurations.sprinkles = lib.nixosSystem {
|
|
||||||
system = "aarch64-linux";
|
|
||||||
modules = [ ./sprinkles/standin.nix ];
|
|
||||||
};
|
|
||||||
# TODO devices/ or configs/ folders
|
|
||||||
}
|
|
||||||
// (lib.flakeTools { inherit self; });
|
|
||||||
}
|
|
6
full/README.md
Normal file
6
full/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# 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).
|
|
@ -1,16 +1,17 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
frogeye = {
|
frogeye = {
|
||||||
desktop = {
|
desktop.xorg = true;
|
||||||
xorg = true;
|
|
||||||
};
|
|
||||||
dev = {
|
dev = {
|
||||||
|
ansible = true;
|
||||||
c = true;
|
c = true;
|
||||||
docker = true;
|
docker = true;
|
||||||
vm = true;
|
fpga = true;
|
||||||
|
perl = true;
|
||||||
|
php = true;
|
||||||
|
python = true;
|
||||||
};
|
};
|
||||||
extra = true;
|
extra = true;
|
||||||
gaming = true;
|
gaming = true;
|
||||||
storageSize = "big";
|
|
||||||
};
|
};
|
||||||
}
|
}
|
10
full/os.nix
Normal file
10
full/os.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../os
|
||||||
|
./options.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
# Create a different disk image depending on the architecture
|
||||||
|
networking.hostName = "${builtins.currentSystem}";
|
||||||
|
}
|
|
@ -1,127 +0,0 @@
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
# 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%";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
688
hm/common.nix
688
hm/common.nix
|
@ -1,178 +1,389 @@
|
||||||
|
{ 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
|
||||||
{
|
{
|
||||||
pkgs,
|
|
||||||
config,
|
nixpkgs.config.allowUnfree = true;
|
||||||
lib,
|
|
||||||
...
|
programs =
|
||||||
}:
|
let
|
||||||
{
|
commonRc = lib.strings.concatLines ([
|
||||||
frogeye.hooks.lock = ''
|
''
|
||||||
${pkgs.coreutils}/bin/rm -rf "/tmp/cached_pass_$UID"
|
# Colored ls
|
||||||
'';
|
# TODO Doesn't allow completion. Check out lsd instead
|
||||||
programs = {
|
_colored_ls() {
|
||||||
home-manager.enable = true;
|
${pkgs.coreutils}/bin/ls -lh --color=always $@ | ${pkgs.gawk}/bin/awk '
|
||||||
bat = {
|
BEGIN {
|
||||||
enable = true;
|
FPAT = "([[:space:]]*[^[:space:]]+)";
|
||||||
config.style = "full";
|
OFS = "";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
$1 = "\033[36m" $1 "\033[0m";
|
||||||
|
$2 = "\033[31m" $2 "\033[0m";
|
||||||
|
$3 = "\033[32m" $3 "\033[0m";
|
||||||
|
$4 = "\033[32m" $4 "\033[0m";
|
||||||
|
$5 = "\033[31m" $5 "\033[0m";
|
||||||
|
$6 = "\033[34m" $6 "\033[0m";
|
||||||
|
$7 = "\033[34m" $7 "\033[0m";
|
||||||
|
print
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
alias ll="_colored_ls"
|
||||||
|
alias la="_colored_ls -a"
|
||||||
|
''
|
||||||
|
] ++ map (d: "mkdir -p ${d}") (builtins.attrValues direnv));
|
||||||
|
# TODO Those directory creations should probably done on home-manager activation
|
||||||
|
commonSessionVariables = {
|
||||||
|
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
|
||||||
|
# Less colors
|
||||||
|
LESS = "-R";
|
||||||
|
LESS_TERMCAP_mb = "$(echo $'\\E[1;31m')"; # begin blink
|
||||||
|
LESS_TERMCAP_md = "$(echo $'\\E[1;36m')"; # begin bold
|
||||||
|
LESS_TERMCAP_me = "$(echo $'\\E[0m')"; # reset bold/blink
|
||||||
|
LESS_TERMCAP_so = "$(echo $'\\E[01;44;33m')"; # begin reverse video
|
||||||
|
LESS_TERMCAP_se = "$(echo $'\\E[0m')"; # reset reverse video
|
||||||
|
LESS_TERMCAP_us = "$(echo $'\\E[1;32m')"; # begin underline
|
||||||
|
LESS_TERMCAP_ue = "$(echo $'\\E[0m')"; # reset underline
|
||||||
|
# Fzf
|
||||||
|
FZF_COMPLETION_OPTS = "${lib.strings.concatStringsSep " " config.programs.fzf.fileWidgetOptions}";
|
||||||
|
};
|
||||||
|
treatsHomeAsJunk = [
|
||||||
|
# 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 = {
|
||||||
|
# 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";
|
||||||
|
free = "free -h";
|
||||||
|
df = "df -h";
|
||||||
|
ffmpeg = "ffmpeg -hide_banner";
|
||||||
|
ffprobe = "ffprobe -hide_banner";
|
||||||
|
ffplay = "ffplay -hide_banner";
|
||||||
|
# TODO Add ipython --no-confirm-exit --pdb
|
||||||
|
|
||||||
|
# Frequent mistakes
|
||||||
|
sl = "ls";
|
||||||
|
al = "la";
|
||||||
|
mdkir = "mkdir";
|
||||||
|
systemclt = "systemctl";
|
||||||
|
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
|
||||||
|
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";
|
||||||
|
|
||||||
|
# Bash/Zsh only
|
||||||
|
cp = "cp -i --reflink=auto";
|
||||||
|
grep = "grep --color=auto";
|
||||||
|
dd = "dd status=progress";
|
||||||
|
rm = "rm -v --one-file-system";
|
||||||
|
# free = "free -m"; # Disabled because... no? Why?
|
||||||
|
diff = "diff --color=auto";
|
||||||
|
dmesg = "dmesg --ctime";
|
||||||
|
wget = "wget --hsts-file ${config.xdg.cacheHome}/wget-hsts";
|
||||||
|
|
||||||
|
# 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 = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
sessionVariables = commonSessionVariables;
|
||||||
|
shellAliases = commonShellAliases // config.frogeye.shellAliases;
|
||||||
|
};
|
||||||
|
dircolors = {
|
||||||
|
enable = true;
|
||||||
|
enableBashIntegration = true;
|
||||||
|
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";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
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'" ];
|
||||||
|
# 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?
|
||||||
|
};
|
||||||
|
readline = {
|
||||||
|
enable = true;
|
||||||
|
variables = {
|
||||||
|
"bell-style" = "none";
|
||||||
|
"colored-completion-prefix" = true;
|
||||||
|
"colored-stats" = true;
|
||||||
|
"completion-ignore-case" = true;
|
||||||
|
"completion-query-items" = 200;
|
||||||
|
"editing-mode" = "vi";
|
||||||
|
"history-preserve-point" = true;
|
||||||
|
"history-size" = 10000;
|
||||||
|
"horizontal-scroll-mode" = false;
|
||||||
|
"mark-directories" = true;
|
||||||
|
"mark-modified-lines" = false;
|
||||||
|
"mark-symlinked-directories" = true;
|
||||||
|
"match-hidden-files" = true;
|
||||||
|
"menu-complete-display-prefix" = true;
|
||||||
|
"page-completions" = true;
|
||||||
|
"print-completions-horizontally" = false;
|
||||||
|
"revert-all-at-newline" = false;
|
||||||
|
"show-all-if-ambiguous" = true;
|
||||||
|
"show-all-if-unmodified" = true;
|
||||||
|
"show-mode-in-prompt" = true;
|
||||||
|
"skip-completed-text" = true;
|
||||||
|
"visible-stats" = false;
|
||||||
|
};
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
translate-shell.enable = true; # TODO Cool config?
|
||||||
|
password-store.enable = true;
|
||||||
};
|
};
|
||||||
bash.shellAliases = {
|
services = {
|
||||||
# Replacement commands
|
gpg-agent = {
|
||||||
# ls = "lsd"; # lsd is suuuper slow for large directories
|
enable = true; # TODO Consider not enabling it when not having any private key
|
||||||
cat = "bat -pp";
|
|
||||||
|
|
||||||
# Completion for existing commands
|
|
||||||
mkdir = "mkdir -v";
|
|
||||||
# cp = "cp -i"; # Disabled because conflicts with the ZSH/Bash one. This separation is confusing I swear.
|
|
||||||
mv = "mv -iv";
|
|
||||||
free = "free -h";
|
|
||||||
df = "df -h";
|
|
||||||
ffmpeg = "ffmpeg -hide_banner";
|
|
||||||
ffprobe = "ffprobe -hide_banner";
|
|
||||||
ffplay = "ffplay -hide_banner";
|
|
||||||
numbat = "numbat --intro-banner off";
|
|
||||||
insect = "numbat";
|
|
||||||
|
|
||||||
# Frequent mistakes
|
|
||||||
sl = "ls";
|
|
||||||
al = "la";
|
|
||||||
mdkir = "mkdir";
|
|
||||||
systemclt = "systemctl";
|
|
||||||
please = "sudo";
|
|
||||||
|
|
||||||
# Shortcuts for commonly used commands
|
|
||||||
ll = "lsd -l";
|
|
||||||
la = "lsd -la";
|
|
||||||
s = "sudo -s -E";
|
|
||||||
|
|
||||||
# Preference
|
|
||||||
wol = "wakeonlan"; # TODO Really, isn't wol better? Also wtf Arch aliases to pass because neither is installed anyways x)
|
|
||||||
mutt = "neomutt";
|
|
||||||
|
|
||||||
# Bash/Zsh only
|
|
||||||
cp = "cp -i --reflink=auto";
|
|
||||||
grep = "grep --color=auto";
|
|
||||||
dd = "dd status=progress";
|
|
||||||
rm = "rm -v --one-file-system";
|
|
||||||
# free = "free -m"; # Disabled because... no? Why?
|
|
||||||
diff = "diff --color=auto";
|
|
||||||
dmesg = "dmesg --ctime";
|
|
||||||
wget = "wget --hsts-file ${config.xdg.cacheHome}/wget-hsts";
|
|
||||||
|
|
||||||
# Imported from scripts
|
|
||||||
rms = ''${pkgs.findutils}/bin/find . -name "*.sync-conflict-*" -delete''; # Remove syncthing conflict files
|
|
||||||
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'';
|
|
||||||
};
|
|
||||||
thefuck = {
|
|
||||||
enable = true;
|
|
||||||
enableBashIntegration = true;
|
enableBashIntegration = true;
|
||||||
enableZshIntegration = true;
|
enableZshIntegration = true;
|
||||||
|
pinentryFlavor = "gtk2"; # Falls back to curses when needed
|
||||||
};
|
};
|
||||||
lsd = {
|
# TODO Syncs a bit too often, also constantly asks for passphrase, which is annoying.
|
||||||
enable = true;
|
git-sync = {
|
||||||
settings = {
|
enable = false;
|
||||||
size = "short";
|
repositories = {
|
||||||
};
|
dotfiles = {
|
||||||
colors = {
|
path = "${config.xdg.configHome}/dotfiles";
|
||||||
# Base16 only, so it reuses the current theme.
|
uri = lib.mkDefault "https://git.frogeye.fr/geoffrey/dotfiles.git";
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
dircolors = {
|
};
|
||||||
enable = true;
|
xdg = {
|
||||||
enableBashIntegration = true;
|
configFile = {
|
||||||
enableZshIntegration = true;
|
"ccache.conf" = {
|
||||||
# UPST This thing put stuff in .dircolors when it actually doesn't have to
|
text = "ccache_dir = ${config.xdg.cacheHome}/ccache";
|
||||||
};
|
};
|
||||||
git.enable = true;
|
"gdbinit" = {
|
||||||
gpg.enable = true;
|
text = ''
|
||||||
fzf = {
|
define hook-quit
|
||||||
enable = true;
|
set confirm off
|
||||||
enableZshIntegration = true;
|
end
|
||||||
defaultOptions = [
|
'';
|
||||||
"--height 40%"
|
};
|
||||||
"--layout=default"
|
"iftoprc" = {
|
||||||
];
|
text = ''
|
||||||
fileWidgetOptions = [
|
port-resolution: no
|
||||||
"--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'"
|
promiscuous: no
|
||||||
];
|
port-display: on
|
||||||
# TODO Above not working... not really used either?
|
link-local: yes
|
||||||
# file and friends are not in PATH by default... so here we want aboslute paths, which means those won't get reloaded. Meh.
|
use-bytes: yes
|
||||||
};
|
show-totals: yes
|
||||||
less.enable = true;
|
log-scale: yes
|
||||||
nixvim.enable = true;
|
'';
|
||||||
readline = {
|
};
|
||||||
enable = true;
|
"pythonstartup.py" = {
|
||||||
variables = {
|
text = (builtins.readFile ./pythonstartup.py);
|
||||||
"bell-style" = "none";
|
};
|
||||||
"colored-completion-prefix" = true;
|
"screenrc" = {
|
||||||
"colored-stats" = true;
|
text = (builtins.readFile ./screenrc);
|
||||||
"completion-ignore-case" = true;
|
|
||||||
"completion-query-items" = 200;
|
|
||||||
"editing-mode" = "vi";
|
|
||||||
"history-preserve-point" = true;
|
|
||||||
"history-size" = 10000;
|
|
||||||
"horizontal-scroll-mode" = false;
|
|
||||||
"mark-directories" = true;
|
|
||||||
"mark-modified-lines" = false;
|
|
||||||
"mark-symlinked-directories" = true;
|
|
||||||
"match-hidden-files" = true;
|
|
||||||
"menu-complete-display-prefix" = true;
|
|
||||||
"page-completions" = true;
|
|
||||||
"print-completions-horizontally" = false;
|
|
||||||
"revert-all-at-newline" = false;
|
|
||||||
"show-all-if-ambiguous" = true;
|
|
||||||
"show-all-if-unmodified" = true;
|
|
||||||
"show-mode-in-prompt" = true;
|
|
||||||
"skip-completed-text" = true;
|
|
||||||
"visible-stats" = false;
|
|
||||||
};
|
};
|
||||||
extraConfig = builtins.readFile ./inputrc;
|
|
||||||
};
|
};
|
||||||
tmux.enable = true;
|
|
||||||
translate-shell.enable = true; # TODO Cool config?
|
|
||||||
};
|
};
|
||||||
home = {
|
home = {
|
||||||
activation = {
|
activation = {
|
||||||
|
@ -184,86 +395,137 @@
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
stateVersion = "24.05";
|
stateVersion = "23.11";
|
||||||
|
language = {
|
||||||
|
base = "en_US.UTF-8";
|
||||||
|
# time = "en_DK.UTF-8"; # TODO Disabled because complaints during nixos-rebuild switch
|
||||||
|
};
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
# Terminal utils
|
# dotfiles dependencies
|
||||||
coreutils-full
|
coreutils
|
||||||
moreutils
|
bash
|
||||||
rename
|
|
||||||
which
|
|
||||||
file
|
|
||||||
cached-nix-shell # For scripts
|
|
||||||
|
|
||||||
# Pipe utils
|
|
||||||
gnugrep
|
gnugrep
|
||||||
gnused
|
gnused
|
||||||
gawk
|
|
||||||
|
|
||||||
# Extraction
|
|
||||||
gnutar
|
gnutar
|
||||||
|
openssl
|
||||||
|
wget
|
||||||
|
curl
|
||||||
|
python3Packages.pip
|
||||||
|
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
|
||||||
unzip
|
unzip
|
||||||
unrar
|
unrar
|
||||||
p7zip
|
p7zip
|
||||||
|
|
||||||
# Documentation
|
|
||||||
man
|
|
||||||
tldr
|
|
||||||
neofetch
|
|
||||||
|
|
||||||
# remote
|
# remote
|
||||||
wget
|
|
||||||
curl
|
|
||||||
openssl
|
|
||||||
openssh
|
openssh
|
||||||
rsync
|
rsync
|
||||||
borgbackup
|
borgbackup
|
||||||
sshfs
|
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
ncdu
|
ncdu
|
||||||
jdupes
|
jdupes
|
||||||
duperemove
|
duperemove
|
||||||
compsize
|
|
||||||
btdu
|
|
||||||
|
|
||||||
# toolbox
|
# local monitoring
|
||||||
|
htop
|
||||||
|
iotop
|
||||||
|
iftop
|
||||||
|
lsof
|
||||||
|
strace
|
||||||
|
pv
|
||||||
|
progress
|
||||||
|
speedtest-cli
|
||||||
|
|
||||||
|
# multimedia toolbox
|
||||||
|
sox
|
||||||
imagemagick
|
imagemagick
|
||||||
numbat
|
|
||||||
bc
|
|
||||||
|
|
||||||
# hardware
|
# password
|
||||||
pciutils
|
pwgen
|
||||||
usbutils
|
|
||||||
dmidecode
|
|
||||||
lshw
|
|
||||||
labelle # Label printer
|
|
||||||
|
|
||||||
# Locker
|
|
||||||
(pkgs.writeShellApplication {
|
(pkgs.writeShellApplication {
|
||||||
name = "lock";
|
name = "git-sync-init";
|
||||||
text = ''
|
# runtimeInputs = with pkgs; [ coreutils libnotify ];
|
||||||
${config.frogeye.hooks.lock}
|
text = (lib.strings.concatLines
|
||||||
|
(map (r: ''[ -d "${r.path}" ] || ${pkgs.git}/bin/git clone "${r.uri}" "${r.path}"'')
|
||||||
${pkgs.vlock}/bin/vlock --all
|
(lib.attrsets.attrValues config.services.git-sync.repositories)
|
||||||
'';
|
)
|
||||||
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Mail
|
||||||
|
isync
|
||||||
|
msmtp
|
||||||
|
notmuch
|
||||||
|
neomutt
|
||||||
|
lynx
|
||||||
|
|
||||||
|
# Organisation
|
||||||
|
vdirsyncer
|
||||||
|
khard
|
||||||
|
khal
|
||||||
|
todoman
|
||||||
|
|
||||||
|
# TODO Lots of redundancy with other way things are defined here
|
||||||
|
|
||||||
|
] ++ lib.optionals pkgs.stdenv.isx86_64 [
|
||||||
|
nodePackages.insect
|
||||||
|
# TODO Use whatever replaces insect, hopefully that works on aarch64
|
||||||
];
|
];
|
||||||
sessionVariables = {
|
sessionVariables = {
|
||||||
# Favourite commands
|
# Favourite commands
|
||||||
|
PAGER = "less";
|
||||||
|
EDITOR = "nvim";
|
||||||
|
|
||||||
# Extra config
|
# Extra config
|
||||||
|
BOOT9_PATH = "${config.xdg.dataHome}/citra-emu/sysdata/boot9.bin";
|
||||||
|
CCACHE_CONFIGPATH = "${config.xdg.configHome}/ccache.conf";
|
||||||
# INPUTRC = "${config.xdg.configHome}/inputrc"; # UPST Will use programs.readline, but doesn't allow path setting
|
# INPUTRC = "${config.xdg.configHome}/inputrc"; # UPST Will use programs.readline, but doesn't allow path setting
|
||||||
|
LESSHISTFILE = "${config.xdg.stateHome}/lesshst";
|
||||||
|
NODE_REPL_HISTORY = "${config.xdg.cacheHome}/node_repl_history";
|
||||||
|
PYTHONSTARTUP = "${config.xdg.configHome}/pythonstartup.py";
|
||||||
|
# TODO I think we're not using the urxvt daemon on purpose?
|
||||||
|
# TODO this should be desktop only, as a few things are too.
|
||||||
|
SCREENRC = "${config.xdg.configHome}/screenrc";
|
||||||
SQLITE_HISTFILE = "${config.xdg.stateHome}/sqlite_history";
|
SQLITE_HISTFILE = "${config.xdg.stateHome}/sqlite_history";
|
||||||
|
YARN_DISABLE_SELF_UPDATE_CHECK = "true"; # This also disable the creation of a ~/.yarnrc file
|
||||||
|
} // lib.optionalAttrs config.frogeye.desktop.xorg {
|
||||||
|
# Favourite commands
|
||||||
|
VISUAL = "nvim";
|
||||||
|
BROWSER = "${config.programs.qutebrowser.package}/bin/qutebrowser";
|
||||||
|
|
||||||
# Bash/ZSH only?
|
# Extra config
|
||||||
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
|
RXVT_SOCKET = "${config.xdg.stateHome}/urxvtd"; # Used to want -$HOME suffix, hopefullt this isn't needed
|
||||||
# Fzf
|
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
|
||||||
FZF_COMPLETION_OPTS = "${lib.strings.concatStringsSep " " config.programs.fzf.fileWidgetOptions}";
|
} // direnv;
|
||||||
};
|
# TODO Session variables only get reloaded on login I think.
|
||||||
sessionPath = [
|
sessionPath = [
|
||||||
"${config.home.homeDirectory}/.local/bin"
|
"${config.home.homeDirectory}/.local/bin"
|
||||||
"${config.home.homeDirectory}/.config/dotfiles/hm/scripts" # Not Nix path otherwise it gets converted into store,
|
"${config.home.sessionVariables.GOPATH}"
|
||||||
# and then every time you want to modify a script you have to rebuild and re-login...
|
(builtins.toString ./scripts)
|
||||||
];
|
];
|
||||||
|
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,28 +1,15 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../common/frogarized
|
|
||||||
../options.nix
|
../options.nix
|
||||||
./accounts
|
|
||||||
./brightness
|
|
||||||
./common.nix
|
./common.nix
|
||||||
./desktop
|
./desktop.nix
|
||||||
./dev
|
./dev.nix
|
||||||
./extra
|
./extra.nix
|
||||||
./gaming
|
./gaming
|
||||||
./git
|
|
||||||
./gpg
|
|
||||||
./homealone.nix
|
|
||||||
./monitoring
|
|
||||||
./nix
|
|
||||||
./pager
|
|
||||||
./password
|
|
||||||
./prompt
|
|
||||||
./rebuild
|
|
||||||
./shell
|
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./theme
|
./style.nix
|
||||||
./tmux
|
./usernix
|
||||||
./vim
|
./vim.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
706
hm/desktop.nix
Normal file
706
hm/desktop.nix
Normal file
|
@ -0,0 +1,706 @@
|
||||||
|
{ 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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
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?
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
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}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./homepage.nix
|
|
||||||
];
|
|
||||||
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 pkgs.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 = {
|
|
||||||
"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.open_base_url = true;
|
|
||||||
content = {
|
|
||||||
# I had this setting below, not sure if it did something special
|
|
||||||
# config.set("content.cookies.accept", "no-3rdparty", "chrome://*/*")
|
|
||||||
cookies.accept = "no-3rdparty";
|
|
||||||
prefers_reduced_motion = true;
|
|
||||||
headers.accept_language = "en-GB,en;q=0.9";
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
html {
|
|
||||||
background-image: linear-gradient(#e6f0a3 0%, #d2e638 50%, #c3d825 51%, #dbf043 100%);
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font: 20px Helvetica, sans-serif;
|
|
||||||
padding: 2.5% 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
article {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 95%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
width: 110px;
|
|
||||||
height: 100px;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 15px 0;
|
|
||||||
margin: 0px 5px 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: top;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (min-width: 768px) {
|
|
||||||
nav {
|
|
||||||
margin-left: 110px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav .main {
|
|
||||||
position: absolute;
|
|
||||||
left: -130px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nav img {
|
|
||||||
margin: auto;
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 70%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover {
|
|
||||||
background: rgba(240, 240, 240, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:active {
|
|
||||||
background: rgba(220, 220, 220, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
nav a>.fa, nav a>.fa-stack{
|
|
||||||
width: 100%;
|
|
||||||
margin-top: .25em;
|
|
||||||
margin-bottom: .35em;
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a span {
|
|
||||||
display: block;
|
|
||||||
margin-top: .55em;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<title>Homepage</title>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width;minimum-scale=0.5,maximum-scale=1.0; user-scalable=1;" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{css}}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{fa_css}}"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<article>
|
|
||||||
<h1>Homepage</h1>
|
|
||||||
{{#sections}}
|
|
||||||
<h2>{{title}}</h2>
|
|
||||||
<nav style="color: {{color}};">
|
|
||||||
{{#image}}
|
|
||||||
<a href="{{url}}" class="main">
|
|
||||||
<img alt="Logo for {{title}}" src="{{image}}" />
|
|
||||||
</a>
|
|
||||||
{{/image}}
|
|
||||||
{{#links}}
|
|
||||||
<a href="{{url}}">
|
|
||||||
<i class="fa fa-{{icon}}" aria-label="Icon for {{name}} ({{icon}})"></i>
|
|
||||||
<span>{{name}}</span>
|
|
||||||
</a>
|
|
||||||
{{/links}}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{{/sections}}
|
|
||||||
</article>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,110 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
# TODO ForkAwesome is deprecated, find something else
|
|
||||||
fa = pkgs.fetchFromGitHub {
|
|
||||||
owner = "ForkAwesome";
|
|
||||||
repo = "Fork-Awesome";
|
|
||||||
rev = "1.2.0";
|
|
||||||
sha256 = "sha256-zG6/0dWjU7/y/oDZuSEv+54Mchng64LVyV8bluskYzc=";
|
|
||||||
};
|
|
||||||
data = config.frogeye.homepage // {
|
|
||||||
sections = builtins.attrValues config.frogeye.homepage.sections;
|
|
||||||
css = ./homepage.css;
|
|
||||||
fa_css = "${fa}/css/fork-awesome.min.css";
|
|
||||||
};
|
|
||||||
# Blatantly stolen from https://pablo.tools/blog/computers/nix-mustache-templates/
|
|
||||||
homepage = builtins.toString (
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
|
|
||||||
name = "homepage.html";
|
|
||||||
|
|
||||||
nativeBuildInpts = [ pkgs.mustache-go ];
|
|
||||||
|
|
||||||
passAsFile = [ "jsonData" ];
|
|
||||||
jsonData = builtins.toJSON data;
|
|
||||||
|
|
||||||
phases = [
|
|
||||||
"buildPhase"
|
|
||||||
"installPhase"
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
${pkgs.mustache-go}/bin/mustache $jsonDataPath ${./homepage.html.mustache} > homepage.html
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
cp homepage.html $out
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config.programs = {
|
|
||||||
firefox.profiles.hm.settings."browser.startup.homepage" = homepage;
|
|
||||||
qutebrowser.settings.url = {
|
|
||||||
start_pages = homepage;
|
|
||||||
default_page = homepage;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
options.frogeye.homepage = {
|
|
||||||
sections = lib.mkOption {
|
|
||||||
default = { };
|
|
||||||
description = "Folders used by users";
|
|
||||||
# Top-level so Syncthing can work for all users. Also there's no real home-manager syncthing module.
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ config, name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
title = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Section title";
|
|
||||||
};
|
|
||||||
color = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "#337ab7";
|
|
||||||
};
|
|
||||||
image = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.path;
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "about:blank";
|
|
||||||
};
|
|
||||||
links = lib.mkOption {
|
|
||||||
default = [ ];
|
|
||||||
type = lib.types.listOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Link";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "about:blank";
|
|
||||||
};
|
|
||||||
icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "question-circle";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
scrot
|
|
||||||
];
|
|
||||||
sessionVariables = {
|
|
||||||
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
{
|
|
||||||
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 = "3.0";
|
|
||||||
|
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [
|
|
||||||
i3ipc
|
|
||||||
psutil
|
|
||||||
pulsectl-asyncio
|
|
||||||
pygobject3
|
|
||||||
rich
|
|
||||||
];
|
|
||||||
nativeBuildInputs =
|
|
||||||
[ lemonbar ]
|
|
||||||
++ (with pkgs; [
|
|
||||||
wirelesstools
|
|
||||||
playerctl
|
|
||||||
]);
|
|
||||||
makeWrapperArgs = [
|
|
||||||
"--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}"
|
|
||||||
"--prefix GI_TYPELIB_PATH : ${GI_TYPELIB_PATH}"
|
|
||||||
];
|
|
||||||
|
|
||||||
GI_TYPELIB_PATH = pkgs.lib.makeSearchPath "lib/girepository-1.0" [
|
|
||||||
pkgs.glib.out
|
|
||||||
pkgs.playerctl
|
|
||||||
];
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
import rich.color
|
|
||||||
import rich.logging
|
|
||||||
import rich.terminal_theme
|
|
||||||
|
|
||||||
import frobar.common
|
|
||||||
import frobar.providers
|
|
||||||
from frobar.common import Alignment
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
# TODO Configurable
|
|
||||||
FROGARIZED = [
|
|
||||||
"#092c0e",
|
|
||||||
"#143718",
|
|
||||||
"#5a7058",
|
|
||||||
"#677d64",
|
|
||||||
"#89947f",
|
|
||||||
"#99a08d",
|
|
||||||
"#fae2e3",
|
|
||||||
"#fff0f1",
|
|
||||||
"#e0332e",
|
|
||||||
"#cf4b15",
|
|
||||||
"#bb8801",
|
|
||||||
"#8d9800",
|
|
||||||
"#1fa198",
|
|
||||||
"#008dd1",
|
|
||||||
"#5c73c4",
|
|
||||||
"#d43982",
|
|
||||||
]
|
|
||||||
# TODO Not super happy with the color management,
|
|
||||||
# while using an existing library is great, it's limited to ANSI colors
|
|
||||||
|
|
||||||
def base16_color(color: int) -> tuple[int, int, int]:
|
|
||||||
hexa = FROGARIZED[color]
|
|
||||||
return tuple(rich.color.parse_rgb_hex(hexa[1:]))
|
|
||||||
|
|
||||||
theme = rich.terminal_theme.TerminalTheme(
|
|
||||||
base16_color(0x0),
|
|
||||||
base16_color(0x0), # TODO should be 7, currently 0 so it's compatible with v2
|
|
||||||
[
|
|
||||||
base16_color(0x0), # black
|
|
||||||
base16_color(0x8), # red
|
|
||||||
base16_color(0xB), # green
|
|
||||||
base16_color(0xA), # yellow
|
|
||||||
base16_color(0xD), # blue
|
|
||||||
base16_color(0xE), # magenta
|
|
||||||
base16_color(0xC), # cyan
|
|
||||||
base16_color(0x5), # white
|
|
||||||
],
|
|
||||||
[
|
|
||||||
base16_color(0x3), # bright black
|
|
||||||
base16_color(0x8), # bright red
|
|
||||||
base16_color(0xB), # bright green
|
|
||||||
base16_color(0xA), # bright yellow
|
|
||||||
base16_color(0xD), # bright blue
|
|
||||||
base16_color(0xE), # bright magenta
|
|
||||||
base16_color(0xC), # bright cyan
|
|
||||||
base16_color(0x7), # bright white
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
bar = frobar.common.Bar(theme=theme)
|
|
||||||
dualScreen = len(bar.children) > 1
|
|
||||||
leftPreferred = 0 if dualScreen else None
|
|
||||||
rightPreferred = 1 if dualScreen else None
|
|
||||||
|
|
||||||
workspaces_suffixes = "▲■"
|
|
||||||
workspaces_names = dict(
|
|
||||||
(str(i + 1), f"{i+1} {c}") for i, c in enumerate(workspaces_suffixes)
|
|
||||||
)
|
|
||||||
|
|
||||||
color = rich.color.Color.parse
|
|
||||||
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.I3ModeProvider(color=color("red")), alignment=Alignment.LEFT
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.I3WorkspacesProvider(custom_names=workspaces_names),
|
|
||||||
alignment=Alignment.LEFT,
|
|
||||||
)
|
|
||||||
|
|
||||||
if dualScreen:
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.I3WindowTitleProvider(color=color("white")),
|
|
||||||
screenNum=0,
|
|
||||||
alignment=Alignment.CENTER,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.MprisProvider(color=color("bright_white")),
|
|
||||||
screenNum=rightPreferred,
|
|
||||||
alignment=Alignment.CENTER,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.common.SpacerProvider(),
|
|
||||||
alignment=Alignment.LEFT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.MprisProvider(color=color("bright_white")),
|
|
||||||
alignment=Alignment.LEFT,
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.CpuProvider(),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.LoadProvider(),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.RamProvider(),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.TemperatureProvider(),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.BatteryProvider(),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.PulseaudioProvider(color=color("magenta")),
|
|
||||||
screenNum=rightPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.NetworkProvider(color=color("blue")),
|
|
||||||
screenNum=leftPreferred,
|
|
||||||
alignment=Alignment.RIGHT,
|
|
||||||
)
|
|
||||||
bar.addProvider(
|
|
||||||
frobar.providers.TimeProvider(color=color("cyan")), alignment=Alignment.RIGHT
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.launch()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,629 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import collections
|
|
||||||
import datetime
|
|
||||||
import enum
|
|
||||||
import logging
|
|
||||||
import signal
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import gi
|
|
||||||
import gi.events
|
|
||||||
import gi.repository.GLib
|
|
||||||
import i3ipc
|
|
||||||
import i3ipc.aio
|
|
||||||
import rich.color
|
|
||||||
import rich.logging
|
|
||||||
import rich.terminal_theme
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level="DEBUG",
|
|
||||||
format="%(message)s",
|
|
||||||
datefmt="[%X]",
|
|
||||||
handlers=[rich.logging.RichHandler()],
|
|
||||||
)
|
|
||||||
log = logging.getLogger("frobar")
|
|
||||||
|
|
||||||
T = typing.TypeVar("T", bound="ComposableText")
|
|
||||||
P = typing.TypeVar("P", bound="ComposableText")
|
|
||||||
C = typing.TypeVar("C", bound="ComposableText")
|
|
||||||
Sortable = str | int
|
|
||||||
|
|
||||||
# Display utilities
|
|
||||||
|
|
||||||
|
|
||||||
def humanSize(numi: int) -> str:
|
|
||||||
"""
|
|
||||||
Returns a string of width 3+3
|
|
||||||
"""
|
|
||||||
num = float(numi)
|
|
||||||
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
|
|
||||||
if abs(num) < 1000:
|
|
||||||
if num >= 10:
|
|
||||||
return f"{int(num):3d}{unit}"
|
|
||||||
else:
|
|
||||||
return f"{num:.1f}{unit}"
|
|
||||||
num /= 1024
|
|
||||||
return f"{numi:d}YiB"
|
|
||||||
|
|
||||||
|
|
||||||
def ramp(p: float, states: str = " ▁▂▃▄▅▆▇█") -> str:
|
|
||||||
if p < 0:
|
|
||||||
return ""
|
|
||||||
d, m = divmod(p, 1.0)
|
|
||||||
text = states[-1] * int(d)
|
|
||||||
if m > 0:
|
|
||||||
text += states[round(m * (len(states) - 1))]
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def clip(text: str, length: int = 30) -> str:
|
|
||||||
if len(text) > length:
|
|
||||||
text = text[: length - 1] + "…"
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
class ComposableText(typing.Generic[P, C]):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
parent: typing.Optional[P] = None,
|
|
||||||
sortKey: Sortable = 0,
|
|
||||||
) -> None:
|
|
||||||
self.parent: typing.Optional[P] = None
|
|
||||||
self.children: typing.MutableSequence[C] = list()
|
|
||||||
self.sortKey = sortKey
|
|
||||||
if parent:
|
|
||||||
self.setParent(parent)
|
|
||||||
self.bar = self.getFirstParentOfType(Bar)
|
|
||||||
|
|
||||||
def setParent(self, parent: P) -> None:
|
|
||||||
assert self.parent is None
|
|
||||||
parent.children.append(self)
|
|
||||||
assert isinstance(parent.children, list)
|
|
||||||
parent.children.sort(key=lambda c: c.sortKey)
|
|
||||||
self.parent = parent
|
|
||||||
self.parent.updateMarkup()
|
|
||||||
|
|
||||||
def unsetParent(self) -> None:
|
|
||||||
assert self.parent
|
|
||||||
self.parent.children.remove(self)
|
|
||||||
self.parent.updateMarkup()
|
|
||||||
self.parent = None
|
|
||||||
|
|
||||||
def getFirstParentOfType(self, typ: typing.Type[T]) -> T:
|
|
||||||
parent = self
|
|
||||||
while not isinstance(parent, typ):
|
|
||||||
assert parent.parent, f"{self} doesn't have a parent of {typ}"
|
|
||||||
parent = parent.parent
|
|
||||||
return parent
|
|
||||||
|
|
||||||
def updateMarkup(self) -> None:
|
|
||||||
self.bar.refresh.set()
|
|
||||||
# TODO OPTI See if worth caching the output
|
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
|
||||||
raise NotImplementedError(f"{self} cannot generate markup")
|
|
||||||
|
|
||||||
def getMarkup(self) -> str:
|
|
||||||
return self.generateMarkup()
|
|
||||||
|
|
||||||
|
|
||||||
class Button(enum.Enum):
|
|
||||||
CLICK_LEFT = "1"
|
|
||||||
CLICK_MIDDLE = "2"
|
|
||||||
CLICK_RIGHT = "3"
|
|
||||||
SCROLL_UP = "4"
|
|
||||||
SCROLL_DOWN = "5"
|
|
||||||
|
|
||||||
|
|
||||||
class Section(ComposableText):
|
|
||||||
"""
|
|
||||||
Colorable block separated by chevrons
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
parent: "Module",
|
|
||||||
sortKey: Sortable = 0,
|
|
||||||
color: rich.color.Color = rich.color.Color.default(),
|
|
||||||
) -> None:
|
|
||||||
super().__init__(parent=parent, sortKey=sortKey)
|
|
||||||
self.parent: "Module"
|
|
||||||
self.color = color
|
|
||||||
|
|
||||||
self.desiredText: str | None = None
|
|
||||||
self.text = ""
|
|
||||||
self.targetSize = -1
|
|
||||||
self.size = -1
|
|
||||||
self.animationTask: asyncio.Task | None = None
|
|
||||||
self.actions: dict[Button, str] = dict()
|
|
||||||
|
|
||||||
def isHidden(self) -> bool:
|
|
||||||
return self.size < 0
|
|
||||||
|
|
||||||
# Geometric series, with a cap
|
|
||||||
ANIM_A = 0.025
|
|
||||||
ANIM_R = 0.9
|
|
||||||
ANIM_MIN = 0.001
|
|
||||||
|
|
||||||
async def animate(self) -> None:
|
|
||||||
increment = 1 if self.size < self.targetSize else -1
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
frameTime = loop.time()
|
|
||||||
animTime = self.ANIM_A
|
|
||||||
skipped = 0
|
|
||||||
|
|
||||||
while self.size != self.targetSize:
|
|
||||||
self.size += increment
|
|
||||||
self.updateMarkup()
|
|
||||||
|
|
||||||
animTime *= self.ANIM_R
|
|
||||||
animTime = max(self.ANIM_MIN, animTime)
|
|
||||||
frameTime += animTime
|
|
||||||
sleepTime = frameTime - loop.time()
|
|
||||||
|
|
||||||
# In case of stress, skip refreshing by not awaiting
|
|
||||||
if sleepTime > 0:
|
|
||||||
if skipped > 0:
|
|
||||||
log.warning(f"Skipped {skipped} animation frame(s)")
|
|
||||||
skipped = 0
|
|
||||||
await asyncio.sleep(sleepTime)
|
|
||||||
else:
|
|
||||||
skipped += 1
|
|
||||||
|
|
||||||
def setText(self, text: str | None) -> None:
|
|
||||||
# OPTI Don't redraw nor reset animation if setting the same text
|
|
||||||
if self.desiredText == text:
|
|
||||||
return
|
|
||||||
self.desiredText = text
|
|
||||||
if text is None:
|
|
||||||
self.text = ""
|
|
||||||
self.targetSize = -1
|
|
||||||
else:
|
|
||||||
self.text = f" {text} "
|
|
||||||
self.targetSize = len(self.text)
|
|
||||||
if self.animationTask:
|
|
||||||
self.animationTask.cancel()
|
|
||||||
# OPTI Skip the whole animation task if not required
|
|
||||||
if self.size == self.targetSize:
|
|
||||||
self.updateMarkup()
|
|
||||||
else:
|
|
||||||
self.animationTask = self.bar.taskGroup.create_task(self.animate())
|
|
||||||
|
|
||||||
def setAction(self, button: Button, callback: typing.Callable | None) -> None:
|
|
||||||
if button in self.actions:
|
|
||||||
command = self.actions[button]
|
|
||||||
self.bar.removeAction(command)
|
|
||||||
del self.actions[button]
|
|
||||||
if callback:
|
|
||||||
command = self.bar.addAction(callback)
|
|
||||||
self.actions[button] = command
|
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
|
||||||
assert not self.isHidden()
|
|
||||||
pad = max(0, self.size - len(self.text))
|
|
||||||
text = self.text[: self.size] + " " * pad
|
|
||||||
for button, command in self.actions.items():
|
|
||||||
text = "%{A" + button.value + ":" + command + ":}" + text + "%{A}"
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
class Module(ComposableText):
|
|
||||||
"""
|
|
||||||
Sections handled by a same updater
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, parent: "Side") -> None:
|
|
||||||
super().__init__(parent=parent)
|
|
||||||
self.parent: "Side"
|
|
||||||
self.children: typing.MutableSequence[Section]
|
|
||||||
|
|
||||||
self.mirroring: Module | None = None
|
|
||||||
self.mirrors: list[Module] = list()
|
|
||||||
|
|
||||||
def mirror(self, module: "Module") -> None:
|
|
||||||
self.mirroring = module
|
|
||||||
module.mirrors.append(self)
|
|
||||||
|
|
||||||
def getSections(self) -> typing.Sequence[Section]:
|
|
||||||
if self.mirroring:
|
|
||||||
return self.mirroring.children
|
|
||||||
else:
|
|
||||||
return self.children
|
|
||||||
|
|
||||||
def updateMarkup(self) -> None:
|
|
||||||
super().updateMarkup()
|
|
||||||
for mirror in self.mirrors:
|
|
||||||
mirror.updateMarkup()
|
|
||||||
|
|
||||||
|
|
||||||
class Alignment(enum.Enum):
|
|
||||||
LEFT = "l"
|
|
||||||
RIGHT = "r"
|
|
||||||
CENTER = "c"
|
|
||||||
|
|
||||||
|
|
||||||
class Side(ComposableText):
|
|
||||||
def __init__(self, parent: "Screen", alignment: Alignment) -> None:
|
|
||||||
super().__init__(parent=parent)
|
|
||||||
self.parent: Screen
|
|
||||||
self.children: typing.MutableSequence[Module] = []
|
|
||||||
|
|
||||||
self.alignment = alignment
|
|
||||||
self.bar = parent.getFirstParentOfType(Bar)
|
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
|
||||||
if not self.children:
|
|
||||||
return ""
|
|
||||||
text = "%{" + self.alignment.value + "}"
|
|
||||||
lastSection: Section | None = None
|
|
||||||
for module in self.children:
|
|
||||||
for section in module.getSections():
|
|
||||||
if section.isHidden():
|
|
||||||
continue
|
|
||||||
hexa = section.color.get_truecolor(theme=self.bar.theme).hex
|
|
||||||
if lastSection is None:
|
|
||||||
if self.alignment == Alignment.LEFT:
|
|
||||||
text += "%{B" + hexa + "}%{F-}"
|
|
||||||
else:
|
|
||||||
text += "%{B-}%{F" + hexa + "}%{R}%{F-}"
|
|
||||||
elif isinstance(lastSection, SpacerSection):
|
|
||||||
text += "%{B-}%{F" + hexa + "}%{R}%{F-}"
|
|
||||||
else:
|
|
||||||
if self.alignment == Alignment.RIGHT:
|
|
||||||
if lastSection.color == section.color:
|
|
||||||
text += ""
|
|
||||||
else:
|
|
||||||
text += "%{F" + hexa + "}%{R}"
|
|
||||||
else:
|
|
||||||
if lastSection.color == section.color:
|
|
||||||
text += ""
|
|
||||||
else:
|
|
||||||
text += "%{R}%{B" + hexa + "}"
|
|
||||||
text += "%{F-}"
|
|
||||||
text += section.getMarkup()
|
|
||||||
lastSection = section
|
|
||||||
if self.alignment != Alignment.RIGHT and lastSection:
|
|
||||||
text += "%{R}%{B-}"
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
class Screen(ComposableText):
|
|
||||||
def __init__(self, parent: "Bar", output: str) -> None:
|
|
||||||
super().__init__(parent=parent)
|
|
||||||
self.parent: "Bar"
|
|
||||||
self.children: typing.MutableSequence[Side]
|
|
||||||
|
|
||||||
self.output = output
|
|
||||||
|
|
||||||
for alignment in Alignment:
|
|
||||||
Side(parent=self, alignment=alignment)
|
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
|
||||||
return ("%{Sn" + self.output + "}") + "".join(
|
|
||||||
side.getMarkup() for side in self.children
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Bar(ComposableText):
|
|
||||||
"""
|
|
||||||
Top-level
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
theme: rich.terminal_theme.TerminalTheme = rich.terminal_theme.DEFAULT_TERMINAL_THEME,
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.parent: None
|
|
||||||
self.children: typing.MutableSequence[Screen]
|
|
||||||
self.longRunningTasks: list[asyncio.Task] = list()
|
|
||||||
self.theme = theme
|
|
||||||
|
|
||||||
self.refresh = asyncio.Event()
|
|
||||||
self.taskGroup = asyncio.TaskGroup()
|
|
||||||
self.providers: list["Provider"] = list()
|
|
||||||
self.actionIndex = 0
|
|
||||||
self.actions: dict[str, typing.Callable] = dict()
|
|
||||||
|
|
||||||
self.periodicProviderTask: typing.Coroutine | None = None
|
|
||||||
|
|
||||||
i3 = i3ipc.Connection()
|
|
||||||
outputs = i3.get_outputs()
|
|
||||||
outputs.sort(key=lambda output: output.rect.x)
|
|
||||||
for output in outputs:
|
|
||||||
if not output.active:
|
|
||||||
continue
|
|
||||||
Screen(parent=self, output=output.name)
|
|
||||||
|
|
||||||
def addLongRunningTask(self, coro: typing.Coroutine) -> None:
|
|
||||||
task = self.taskGroup.create_task(coro)
|
|
||||||
self.longRunningTasks.append(task)
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
cmd = [
|
|
||||||
"lemonbar",
|
|
||||||
"-b",
|
|
||||||
"-a",
|
|
||||||
"64",
|
|
||||||
"-f",
|
|
||||||
"DejaVuSansM Nerd Font:size=10",
|
|
||||||
"-F",
|
|
||||||
self.theme.foreground_color.hex,
|
|
||||||
"-B",
|
|
||||||
self.theme.background_color.hex,
|
|
||||||
]
|
|
||||||
proc = await asyncio.create_subprocess_exec(
|
|
||||||
*cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
|
|
||||||
)
|
|
||||||
|
|
||||||
async def refresher() -> None:
|
|
||||||
assert proc.stdin
|
|
||||||
while True:
|
|
||||||
await self.refresh.wait()
|
|
||||||
self.refresh.clear()
|
|
||||||
markup = self.getMarkup()
|
|
||||||
proc.stdin.write(markup.encode())
|
|
||||||
|
|
||||||
async def actionHandler() -> None:
|
|
||||||
assert proc.stdout
|
|
||||||
while True:
|
|
||||||
line = await proc.stdout.readline()
|
|
||||||
command = line.decode().strip()
|
|
||||||
callback = self.actions.get(command)
|
|
||||||
if callback is None:
|
|
||||||
# In some conditions on start it's empty
|
|
||||||
log.error(f"Unknown command: {command}")
|
|
||||||
return
|
|
||||||
callback()
|
|
||||||
|
|
||||||
async with self.taskGroup:
|
|
||||||
self.addLongRunningTask(refresher())
|
|
||||||
self.addLongRunningTask(actionHandler())
|
|
||||||
for provider in self.providers:
|
|
||||||
self.addLongRunningTask(provider.run())
|
|
||||||
|
|
||||||
def exit() -> None:
|
|
||||||
log.info("Terminating")
|
|
||||||
for task in self.longRunningTasks:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.add_signal_handler(signal.SIGINT, exit)
|
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
|
||||||
return "".join(screen.getMarkup() for screen in self.children) + "\n"
|
|
||||||
|
|
||||||
def addProvider(
|
|
||||||
self,
|
|
||||||
provider: "Provider",
|
|
||||||
alignment: Alignment = Alignment.LEFT,
|
|
||||||
screenNum: int | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
screenNum: the provider will be added on this screen if set, all otherwise
|
|
||||||
"""
|
|
||||||
modules = list()
|
|
||||||
for s, screen in enumerate(self.children):
|
|
||||||
if screenNum is None or s == screenNum:
|
|
||||||
side = next(filter(lambda s: s.alignment == alignment, screen.children))
|
|
||||||
module = Module(parent=side)
|
|
||||||
modules.append(module)
|
|
||||||
provider.modules = modules
|
|
||||||
if modules:
|
|
||||||
self.providers.append(provider)
|
|
||||||
|
|
||||||
def addAction(self, callback: typing.Callable) -> str:
|
|
||||||
command = f"{self.actionIndex:x}"
|
|
||||||
self.actions[command] = callback
|
|
||||||
self.actionIndex += 1
|
|
||||||
return command
|
|
||||||
|
|
||||||
def removeAction(self, command: str) -> None:
|
|
||||||
del self.actions[command]
|
|
||||||
|
|
||||||
def launch(self) -> None:
|
|
||||||
# Using GLib's event loop so we can run GLib's code
|
|
||||||
policy = gi.events.GLibEventLoopPolicy()
|
|
||||||
asyncio.set_event_loop_policy(policy)
|
|
||||||
loop = policy.get_event_loop()
|
|
||||||
loop.run_until_complete(self.run())
|
|
||||||
|
|
||||||
|
|
||||||
class Provider:
|
|
||||||
sectionType: type[Section] = Section
|
|
||||||
|
|
||||||
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
|
|
||||||
self.modules: list[Module] = list()
|
|
||||||
self.color = color
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
# Not a NotImplementedError, otherwise can't combine all classes
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MirrorProvider(Provider):
|
|
||||||
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
|
|
||||||
super().__init__(color=color)
|
|
||||||
self.module: Module
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
self.module = self.modules[0]
|
|
||||||
for module in self.modules[1:]:
|
|
||||||
module.mirror(self.module)
|
|
||||||
|
|
||||||
|
|
||||||
class SingleSectionProvider(MirrorProvider):
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
self.section = self.sectionType(parent=self.module, color=self.color)
|
|
||||||
|
|
||||||
|
|
||||||
class StaticProvider(SingleSectionProvider):
|
|
||||||
def __init__(
|
|
||||||
self, text: str, color: rich.color.Color = rich.color.Color.default()
|
|
||||||
) -> None:
|
|
||||||
super().__init__(color=color)
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
self.section.setText(self.text)
|
|
||||||
|
|
||||||
|
|
||||||
class SpacerSection(Section):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SpacerProvider(SingleSectionProvider):
|
|
||||||
sectionType = SpacerSection
|
|
||||||
|
|
||||||
def __init__(self, length: int = 5) -> None:
|
|
||||||
super().__init__(color=rich.color.Color.default())
|
|
||||||
self.length = length
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
assert isinstance(self.section, SpacerSection)
|
|
||||||
self.section.setText(" " * self.length)
|
|
||||||
|
|
||||||
|
|
||||||
class StatefulSection(Section):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
parent: Module,
|
|
||||||
sortKey: Sortable = 0,
|
|
||||||
color: rich.color.Color = rich.color.Color.default(),
|
|
||||||
) -> None:
|
|
||||||
super().__init__(parent=parent, sortKey=sortKey, color=color)
|
|
||||||
self.state = 0
|
|
||||||
self.numberStates: int
|
|
||||||
|
|
||||||
self.setAction(Button.CLICK_LEFT, self.incrementState)
|
|
||||||
self.setAction(Button.CLICK_RIGHT, self.decrementState)
|
|
||||||
|
|
||||||
def incrementState(self) -> None:
|
|
||||||
self.state += 1
|
|
||||||
self.changeState()
|
|
||||||
|
|
||||||
def decrementState(self) -> None:
|
|
||||||
self.state -= 1
|
|
||||||
self.changeState()
|
|
||||||
|
|
||||||
def setChangedState(self, callback: typing.Callable) -> None:
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
def changeState(self) -> None:
|
|
||||||
self.state %= self.numberStates
|
|
||||||
self.bar.taskGroup.create_task(self.callback())
|
|
||||||
|
|
||||||
|
|
||||||
class StatefulSectionProvider(Provider):
|
|
||||||
sectionType = StatefulSection
|
|
||||||
|
|
||||||
|
|
||||||
class SingleStatefulSectionProvider(StatefulSectionProvider, SingleSectionProvider):
|
|
||||||
section: StatefulSection
|
|
||||||
|
|
||||||
|
|
||||||
class MultiSectionsProvider(Provider):
|
|
||||||
|
|
||||||
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
|
|
||||||
super().__init__(color=color)
|
|
||||||
self.sectionKeys: dict[Module, dict[Sortable, Section]] = (
|
|
||||||
collections.defaultdict(dict)
|
|
||||||
)
|
|
||||||
self.updaters: dict[Section, typing.Callable] = dict()
|
|
||||||
|
|
||||||
async def getSectionUpdater(self, section: Section) -> typing.Callable:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def doNothing() -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def updateSections(self, sections: set[Sortable], module: Module) -> None:
|
|
||||||
moduleSections = self.sectionKeys[module]
|
|
||||||
async with asyncio.TaskGroup() as tg:
|
|
||||||
for sortKey in sections:
|
|
||||||
section = moduleSections.get(sortKey)
|
|
||||||
if not section:
|
|
||||||
section = self.sectionType(
|
|
||||||
parent=module, sortKey=sortKey, color=self.color
|
|
||||||
)
|
|
||||||
self.updaters[section] = await self.getSectionUpdater(section)
|
|
||||||
moduleSections[sortKey] = section
|
|
||||||
|
|
||||||
updater = self.updaters[section]
|
|
||||||
tg.create_task(updater())
|
|
||||||
|
|
||||||
missingKeys = set(moduleSections.keys()) - sections
|
|
||||||
for missingKey in missingKeys:
|
|
||||||
section = moduleSections.get(missingKey)
|
|
||||||
assert section
|
|
||||||
section.setText(None)
|
|
||||||
|
|
||||||
|
|
||||||
class PeriodicProvider(Provider):
|
|
||||||
async def init(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def task(cls, bar: Bar) -> None:
|
|
||||||
providers = list()
|
|
||||||
for provider in bar.providers:
|
|
||||||
if isinstance(provider, PeriodicProvider):
|
|
||||||
providers.append(provider)
|
|
||||||
await provider.init()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# TODO Block bar update during the periodic update of the loops
|
|
||||||
loops = [provider.loop() for provider in providers]
|
|
||||||
asyncio.gather(*loops)
|
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
# Hardcoded to 1 second... not sure if we want some more than that,
|
|
||||||
# and if the logic to check if a task should run would be a win
|
|
||||||
# compared to the task itself
|
|
||||||
remaining = 1 - now.microsecond / 1000000
|
|
||||||
await asyncio.sleep(remaining)
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
for module in self.modules:
|
|
||||||
bar = module.getFirstParentOfType(Bar)
|
|
||||||
assert bar
|
|
||||||
if not bar.periodicProviderTask:
|
|
||||||
bar.periodicProviderTask = PeriodicProvider.task(bar)
|
|
||||||
bar.addLongRunningTask(bar.periodicProviderTask)
|
|
||||||
|
|
||||||
|
|
||||||
class PeriodicStatefulProvider(SingleStatefulSectionProvider, PeriodicProvider):
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
self.section.setChangedState(self.loop)
|
|
||||||
|
|
||||||
|
|
||||||
class AlertingProvider(Provider):
|
|
||||||
COLOR_NORMAL = rich.color.Color.parse("green")
|
|
||||||
COLOR_WARNING = rich.color.Color.parse("yellow")
|
|
||||||
COLOR_DANGER = rich.color.Color.parse("red")
|
|
||||||
|
|
||||||
warningThreshold: float
|
|
||||||
dangerThreshold: float
|
|
||||||
|
|
||||||
def updateLevel(self, level: float) -> None:
|
|
||||||
if level > self.dangerThreshold:
|
|
||||||
color = self.COLOR_DANGER
|
|
||||||
elif level > self.warningThreshold:
|
|
||||||
color = self.COLOR_WARNING
|
|
||||||
else:
|
|
||||||
color = self.COLOR_NORMAL
|
|
||||||
for module in self.modules:
|
|
||||||
for section in module.getSections():
|
|
||||||
section.color = color
|
|
|
@ -1,624 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import collections
|
|
||||||
import datetime
|
|
||||||
import ipaddress
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import gi
|
|
||||||
import i3ipc
|
|
||||||
import i3ipc.aio
|
|
||||||
import psutil
|
|
||||||
import pulsectl
|
|
||||||
import pulsectl_asyncio
|
|
||||||
import rich.color
|
|
||||||
|
|
||||||
from frobar.common import (AlertingProvider, Button, MirrorProvider, Module,
|
|
||||||
MultiSectionsProvider, PeriodicProvider,
|
|
||||||
PeriodicStatefulProvider, Screen, Section,
|
|
||||||
SingleSectionProvider, StatefulSection,
|
|
||||||
StatefulSectionProvider, clip, humanSize, log, ramp)
|
|
||||||
|
|
||||||
gi.require_version("Playerctl", "2.0")
|
|
||||||
import gi.repository.Playerctl
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
# TODO Hide WorkspaceProvider when this is active
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
if e.container.name is None:
|
|
||||||
self.section.setText(None)
|
|
||||||
else:
|
|
||||||
self.section.setText(clip(e.container.name, 60))
|
|
||||||
|
|
||||||
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(MultiSectionsProvider):
|
|
||||||
COLOR_URGENT = rich.color.Color.parse("red")
|
|
||||||
COLOR_FOCUSED = rich.color.Color.parse("yellow")
|
|
||||||
# TODO Should be orange (not a terminal color)
|
|
||||||
COLOR_VISIBLE = rich.color.Color.parse("cyan")
|
|
||||||
COLOR_DEFAULT = rich.color.Color.parse("bright_black")
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
custom_names: dict[str, str] = {},
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.workspaces: dict[int, i3ipc.WorkspaceReply]
|
|
||||||
self.custom_names = custom_names
|
|
||||||
|
|
||||||
self.modulesFromOutput: dict[str, Module] = dict()
|
|
||||||
|
|
||||||
async def getSectionUpdater(self, section: Section) -> typing.Callable:
|
|
||||||
assert isinstance(section.sortKey, int)
|
|
||||||
num = section.sortKey
|
|
||||||
|
|
||||||
def switch_to_workspace() -> None:
|
|
||||||
self.bar.taskGroup.create_task(self.i3.command(f"workspace number {num}"))
|
|
||||||
|
|
||||||
section.setAction(Button.CLICK_LEFT, switch_to_workspace)
|
|
||||||
|
|
||||||
async def update() -> None:
|
|
||||||
workspace = self.workspaces.get(num)
|
|
||||||
if workspace is None:
|
|
||||||
log.warning(f"Can't find workspace {num}")
|
|
||||||
section.setText("X")
|
|
||||||
return
|
|
||||||
|
|
||||||
name = workspace.name
|
|
||||||
if workspace.urgent:
|
|
||||||
section.color = self.COLOR_URGENT
|
|
||||||
elif workspace.focused:
|
|
||||||
section.color = self.COLOR_FOCUSED
|
|
||||||
elif workspace.visible:
|
|
||||||
section.color = self.COLOR_VISIBLE
|
|
||||||
else:
|
|
||||||
section.color = self.COLOR_DEFAULT
|
|
||||||
if workspace.focused:
|
|
||||||
name = self.custom_names.get(name, name)
|
|
||||||
section.setText(name)
|
|
||||||
|
|
||||||
return update
|
|
||||||
|
|
||||||
async def updateWorkspaces(self) -> 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 self.i3.get_workspaces()
|
|
||||||
self.workspaces = dict()
|
|
||||||
modules = collections.defaultdict(set)
|
|
||||||
for workspace in workspaces:
|
|
||||||
self.workspaces[workspace.num] = workspace
|
|
||||||
module = self.modulesFromOutput[workspace.output]
|
|
||||||
modules[module].add(workspace.num)
|
|
||||||
|
|
||||||
await asyncio.gather(
|
|
||||||
*[self.updateSections(nums, module) for module, nums in modules.items()]
|
|
||||||
)
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
self.i3 = await i3ipc.aio.Connection(auto_reconnect=True).connect()
|
|
||||||
self.i3.on(i3ipc.Event.WORKSPACE, self.onWorkspaceChange)
|
|
||||||
self.onWorkspaceChange(self.i3)
|
|
||||||
await self.i3.main()
|
|
||||||
|
|
||||||
|
|
||||||
class MprisProvider(MirrorProvider):
|
|
||||||
|
|
||||||
STATUSES = {
|
|
||||||
gi.repository.Playerctl.PlaybackStatus.PLAYING: "",
|
|
||||||
gi.repository.Playerctl.PlaybackStatus.PAUSED: "",
|
|
||||||
gi.repository.Playerctl.PlaybackStatus.STOPPED: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
PROVIDERS = {
|
|
||||||
"mpd": "",
|
|
||||||
"firefox": "",
|
|
||||||
"chromium": "",
|
|
||||||
"mpv": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
self.status = self.sectionType(parent=self.module, color=self.color)
|
|
||||||
self.album = self.sectionType(parent=self.module, color=self.color)
|
|
||||||
self.artist = self.sectionType(parent=self.module, color=self.color)
|
|
||||||
self.title = self.sectionType(parent=self.module, color=self.color)
|
|
||||||
|
|
||||||
self.manager = gi.repository.Playerctl.PlayerManager()
|
|
||||||
self.manager.connect("name-appeared", self.on_name_appeared)
|
|
||||||
self.manager.connect("player-vanished", self.on_player_vanished)
|
|
||||||
|
|
||||||
self.playerctldName = gi.repository.Playerctl.PlayerName()
|
|
||||||
self.playerctldName.name = "playerctld"
|
|
||||||
self.playerctldName.source = gi.repository.Playerctl.Source.DBUS_SESSION
|
|
||||||
|
|
||||||
self.player: gi.repository.Playerctl.Player | None = None
|
|
||||||
self.playing = asyncio.Event()
|
|
||||||
|
|
||||||
for name in self.manager.props.player_names:
|
|
||||||
self.init_player(name)
|
|
||||||
|
|
||||||
self.updateSections()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Occasionally it will skip a second
|
|
||||||
# but haven't managed to reproduce with debug info
|
|
||||||
await self.playing.wait()
|
|
||||||
self.updateTitle()
|
|
||||||
if self.player:
|
|
||||||
pos = self.player.props.position
|
|
||||||
rem = 1 - (pos % 1000000) / 1000000
|
|
||||||
await asyncio.sleep(rem)
|
|
||||||
else:
|
|
||||||
self.playing.clear()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(
|
|
||||||
something: gi.overrides.GLib.Variant, key: str, default: typing.Any = None
|
|
||||||
) -> typing.Any:
|
|
||||||
if key in something.keys():
|
|
||||||
return something[key]
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def formatUs(ms: int) -> str:
|
|
||||||
if ms < 60 * 60 * 1000000:
|
|
||||||
return time.strftime("%M:%S", time.gmtime(ms // 1000000))
|
|
||||||
else:
|
|
||||||
return str(datetime.timedelta(microseconds=ms))
|
|
||||||
|
|
||||||
def findCurrentPlayer(self) -> None:
|
|
||||||
for name in [self.playerctldName] + self.manager.props.player_names:
|
|
||||||
# TODO Test what happens when playerctld is not available
|
|
||||||
self.player = gi.repository.Playerctl.Player.new_from_name(name)
|
|
||||||
if not self.player.props.can_play:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.player = None
|
|
||||||
|
|
||||||
def updateSections(self) -> None:
|
|
||||||
self.findCurrentPlayer()
|
|
||||||
|
|
||||||
if self.player is None:
|
|
||||||
self.status.setText(None)
|
|
||||||
self.album.setText(None)
|
|
||||||
self.artist.setText(None)
|
|
||||||
self.title.setText(None)
|
|
||||||
self.playing.clear()
|
|
||||||
return
|
|
||||||
|
|
||||||
player = self.player.props.player_name
|
|
||||||
player = self.PROVIDERS.get(player, player)
|
|
||||||
status = self.STATUSES.get(self.player.props.playback_status, "?")
|
|
||||||
self.status.setText(f"{player} {status}")
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.player.props.playback_status
|
|
||||||
== gi.repository.Playerctl.PlaybackStatus.PLAYING
|
|
||||||
):
|
|
||||||
self.playing.set()
|
|
||||||
else:
|
|
||||||
self.playing.clear()
|
|
||||||
|
|
||||||
metadata = self.player.props.metadata
|
|
||||||
|
|
||||||
album = self.get(metadata, "xesam:album")
|
|
||||||
if album:
|
|
||||||
self.album.setText(f" {clip(album)}")
|
|
||||||
else:
|
|
||||||
self.album.setText(None)
|
|
||||||
|
|
||||||
artists = self.get(metadata, "xesam:artist")
|
|
||||||
if artists:
|
|
||||||
artist = ", ".join(artists)
|
|
||||||
self.artist.setText(f" {clip(artist)}")
|
|
||||||
else:
|
|
||||||
self.artist.setText(None)
|
|
||||||
|
|
||||||
self.updateTitle()
|
|
||||||
|
|
||||||
def updateTitle(self) -> None:
|
|
||||||
if self.player is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
metadata = self.player.props.metadata
|
|
||||||
pos = self.player.props.position # In µs
|
|
||||||
text = f" {self.formatUs(pos)}"
|
|
||||||
dur = self.get(metadata, "mpris:length")
|
|
||||||
if dur:
|
|
||||||
text += f"/{self.formatUs(dur)}"
|
|
||||||
title = self.get(metadata, "xesam:title")
|
|
||||||
if title:
|
|
||||||
text += f" {clip(title)}"
|
|
||||||
self.title.setText(text)
|
|
||||||
|
|
||||||
def on_player_vanished(
|
|
||||||
self,
|
|
||||||
manager: gi.repository.Playerctl.PlayerManager,
|
|
||||||
player: gi.repository.Playerctl.Player,
|
|
||||||
) -> None:
|
|
||||||
self.updateSections()
|
|
||||||
|
|
||||||
def on_event(
|
|
||||||
self,
|
|
||||||
player: gi.repository.Playerctl.Player,
|
|
||||||
_: typing.Any,
|
|
||||||
manager: gi.repository.Playerctl.PlayerManager,
|
|
||||||
) -> None:
|
|
||||||
self.updateSections()
|
|
||||||
|
|
||||||
def init_player(self, name: gi.repository.Playerctl.PlayerName) -> None:
|
|
||||||
player = gi.repository.Playerctl.Player.new_from_name(name)
|
|
||||||
# All events will cause the active player to change,
|
|
||||||
# so we listen on all events, even if the display won't change
|
|
||||||
player.connect("playback-status", self.on_event, self.manager)
|
|
||||||
player.connect("loop-status", self.on_event, self.manager)
|
|
||||||
player.connect("shuffle", self.on_event, self.manager)
|
|
||||||
player.connect("metadata", self.on_event, self.manager)
|
|
||||||
player.connect("volume", self.on_event, self.manager)
|
|
||||||
player.connect("seeked", self.on_event, self.manager)
|
|
||||||
self.manager.manage_player(player)
|
|
||||||
|
|
||||||
def on_name_appeared(
|
|
||||||
self, manager: gi.repository.Playerctl.PlayerManager, name: str
|
|
||||||
) -> None:
|
|
||||||
self.init_player(name)
|
|
||||||
self.updateSections()
|
|
||||||
|
|
||||||
|
|
||||||
class CpuProvider(AlertingProvider, PeriodicStatefulProvider):
|
|
||||||
async def init(self) -> None:
|
|
||||||
self.section.numberStates = 3
|
|
||||||
self.warningThreshold = 75
|
|
||||||
self.dangerThreshold = 95
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
percent = psutil.cpu_percent(percpu=False)
|
|
||||||
self.updateLevel(percent)
|
|
||||||
|
|
||||||
text = ""
|
|
||||||
if self.section.state >= 2:
|
|
||||||
percents = psutil.cpu_percent(percpu=True)
|
|
||||||
text += " " + "".join([ramp(p / 100) for p in percents])
|
|
||||||
elif self.section.state >= 1:
|
|
||||||
text += " " + ramp(percent / 100)
|
|
||||||
self.section.setText(text)
|
|
||||||
|
|
||||||
|
|
||||||
class LoadProvider(AlertingProvider, PeriodicStatefulProvider):
|
|
||||||
async def init(self) -> None:
|
|
||||||
self.section.numberStates = 3
|
|
||||||
self.warningThreshold = 5
|
|
||||||
self.dangerThreshold = 10
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
load = os.getloadavg()
|
|
||||||
self.updateLevel(load[0])
|
|
||||||
|
|
||||||
text = ""
|
|
||||||
loads = 3 if self.section.state >= 2 else self.section.state
|
|
||||||
for load_index in range(loads):
|
|
||||||
text += f" {load[load_index]:.2f}"
|
|
||||||
self.section.setText(text)
|
|
||||||
|
|
||||||
|
|
||||||
class RamProvider(AlertingProvider, PeriodicStatefulProvider):
|
|
||||||
|
|
||||||
async def init(self) -> None:
|
|
||||||
self.section.numberStates = 4
|
|
||||||
self.warningThreshold = 75
|
|
||||||
self.dangerThreshold = 95
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
mem = psutil.virtual_memory()
|
|
||||||
self.updateLevel(mem.percent)
|
|
||||||
|
|
||||||
text = ""
|
|
||||||
if self.section.state >= 1:
|
|
||||||
text += " " + ramp(mem.percent / 100)
|
|
||||||
if self.section.state >= 2:
|
|
||||||
text += humanSize(mem.total - mem.available)
|
|
||||||
if self.section.state >= 3:
|
|
||||||
text += "/" + humanSize(mem.total)
|
|
||||||
self.section.setText(text)
|
|
||||||
|
|
||||||
|
|
||||||
class TemperatureProvider(AlertingProvider, PeriodicStatefulProvider):
|
|
||||||
RAMP = ""
|
|
||||||
MAIN_TEMPS = ["coretemp", "amdgpu", "cpu_thermal"]
|
|
||||||
# For Intel, AMD and ARM respectively.
|
|
||||||
|
|
||||||
main: str
|
|
||||||
|
|
||||||
async def init(self) -> None:
|
|
||||||
self.section.numberStates = 2
|
|
||||||
|
|
||||||
allTemp = psutil.sensors_temperatures()
|
|
||||||
for main in self.MAIN_TEMPS:
|
|
||||||
if main in allTemp:
|
|
||||||
self.main = main
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise IndexError("Could not find suitable temperature sensor")
|
|
||||||
|
|
||||||
temp = allTemp[self.main][0]
|
|
||||||
self.warningThreshold = temp.high or 90.0
|
|
||||||
self.dangerThreshold = temp.critical or 100.0
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
allTemp = psutil.sensors_temperatures()
|
|
||||||
temp = allTemp[self.main][0]
|
|
||||||
self.updateLevel(temp.current)
|
|
||||||
|
|
||||||
text = ramp(temp.current / self.warningThreshold, self.RAMP)
|
|
||||||
if self.section.state >= 1:
|
|
||||||
text += f" {temp.current:.0f}°C"
|
|
||||||
self.section.setText(text)
|
|
||||||
|
|
||||||
|
|
||||||
class BatteryProvider(AlertingProvider, PeriodicStatefulProvider):
|
|
||||||
# TODO Support ACPID for events
|
|
||||||
RAMP = ""
|
|
||||||
|
|
||||||
async def init(self) -> None:
|
|
||||||
self.section.numberStates = 3
|
|
||||||
# TODO 1 refresh rate is too quick
|
|
||||||
|
|
||||||
self.warningThreshold = 75
|
|
||||||
self.dangerThreshold = 95
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
bat = psutil.sensors_battery()
|
|
||||||
if not bat:
|
|
||||||
self.section.setText(None)
|
|
||||||
|
|
||||||
self.updateLevel(100 - bat.percent)
|
|
||||||
|
|
||||||
text = "" if bat.power_plugged else ""
|
|
||||||
text += ramp(bat.percent / 100, self.RAMP)
|
|
||||||
|
|
||||||
if self.section.state >= 1:
|
|
||||||
text += f" {bat.percent:.0f}%"
|
|
||||||
if self.section.state >= 2:
|
|
||||||
h = int(bat.secsleft / 3600)
|
|
||||||
m = int((bat.secsleft - h * 3600) / 60)
|
|
||||||
text += f" ({h:d}:{m:02d})"
|
|
||||||
|
|
||||||
self.section.setText(text)
|
|
||||||
|
|
||||||
|
|
||||||
class PulseaudioProvider(
|
|
||||||
MirrorProvider, StatefulSectionProvider, MultiSectionsProvider
|
|
||||||
):
|
|
||||||
async def getSectionUpdater(self, section: Section) -> typing.Callable:
|
|
||||||
assert isinstance(section, StatefulSection)
|
|
||||||
assert isinstance(section.sortKey, str)
|
|
||||||
|
|
||||||
sink = self.sinks[section.sortKey]
|
|
||||||
|
|
||||||
icon = "?"
|
|
||||||
|
|
||||||
if sink.port_active is None:
|
|
||||||
pass
|
|
||||||
elif (
|
|
||||||
sink.port_active.name == "analog-output-headphones"
|
|
||||||
or sink.port_active.description == "Headphones"
|
|
||||||
):
|
|
||||||
icon = ""
|
|
||||||
elif (
|
|
||||||
sink.port_active.name == "analog-output-speaker"
|
|
||||||
or sink.port_active.description == "Speaker"
|
|
||||||
):
|
|
||||||
icon = ""
|
|
||||||
elif sink.port_active.name in ("headset-output", "headphone-output"):
|
|
||||||
icon = ""
|
|
||||||
|
|
||||||
section.numberStates = 3
|
|
||||||
section.state = 1
|
|
||||||
|
|
||||||
# TODO Change volume with wheel
|
|
||||||
|
|
||||||
async def updater() -> None:
|
|
||||||
assert isinstance(section, StatefulSection)
|
|
||||||
text = icon
|
|
||||||
sink = self.sinks[section.sortKey]
|
|
||||||
|
|
||||||
async with pulsectl_asyncio.PulseAsync("frobar-get-volume") as pulse:
|
|
||||||
vol = await pulse.volume_get_all_chans(sink)
|
|
||||||
if section.state == 1:
|
|
||||||
text += f" {ramp(vol)}"
|
|
||||||
elif section.state == 2:
|
|
||||||
text += f" {vol:.0%}"
|
|
||||||
# TODO Show which is default
|
|
||||||
section.setText(text)
|
|
||||||
|
|
||||||
section.setChangedState(updater)
|
|
||||||
|
|
||||||
return updater
|
|
||||||
|
|
||||||
async def update(self) -> None:
|
|
||||||
async with pulsectl_asyncio.PulseAsync("frobar-list-sinks") as pulse:
|
|
||||||
self.sinks = dict((sink.name, sink) for sink in await pulse.sink_list())
|
|
||||||
await self.updateSections(set(self.sinks.keys()), self.module)
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
await super().run()
|
|
||||||
await self.update()
|
|
||||||
async with pulsectl_asyncio.PulseAsync("frobar-events-listener") as pulse:
|
|
||||||
async for event in pulse.subscribe_events(pulsectl.PulseEventMaskEnum.sink):
|
|
||||||
await self.update()
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProvider(
|
|
||||||
MirrorProvider, PeriodicProvider, StatefulSectionProvider, MultiSectionsProvider
|
|
||||||
):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
color: rich.color.Color = rich.color.Color.default(),
|
|
||||||
) -> None:
|
|
||||||
super().__init__(color=color)
|
|
||||||
|
|
||||||
async def init(self) -> None:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
self.time = loop.time()
|
|
||||||
self.io_counters = psutil.net_io_counters(pernic=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getIfaceAttributes(iface: str) -> tuple[bool, str, bool]:
|
|
||||||
relevant = True
|
|
||||||
icon = "?"
|
|
||||||
wifi = False
|
|
||||||
if iface == "lo":
|
|
||||||
relevant = False
|
|
||||||
elif iface.startswith("eth") or iface.startswith("enp"):
|
|
||||||
if "u" in iface:
|
|
||||||
icon = ""
|
|
||||||
else:
|
|
||||||
icon = ""
|
|
||||||
elif iface.startswith("wlan") or iface.startswith("wl"):
|
|
||||||
icon = ""
|
|
||||||
wifi = True
|
|
||||||
elif (
|
|
||||||
iface.startswith("tun") or iface.startswith("tap") or iface.startswith("wg")
|
|
||||||
):
|
|
||||||
icon = ""
|
|
||||||
|
|
||||||
elif iface.startswith("docker"):
|
|
||||||
icon = ""
|
|
||||||
elif iface.startswith("veth"):
|
|
||||||
icon = ""
|
|
||||||
elif iface.startswith("vboxnet"):
|
|
||||||
icon = ""
|
|
||||||
|
|
||||||
return relevant, icon, wifi
|
|
||||||
|
|
||||||
async def getSectionUpdater(self, section: Section) -> typing.Callable:
|
|
||||||
|
|
||||||
assert isinstance(section, StatefulSection)
|
|
||||||
assert isinstance(section.sortKey, str)
|
|
||||||
iface = section.sortKey
|
|
||||||
|
|
||||||
relevant, icon, wifi = self.getIfaceAttributes(iface)
|
|
||||||
|
|
||||||
if not relevant:
|
|
||||||
return self.doNothing
|
|
||||||
|
|
||||||
section.numberStates = 5 if wifi else 4
|
|
||||||
section.state = 1 if wifi else 0
|
|
||||||
|
|
||||||
async def update() -> None:
|
|
||||||
assert isinstance(section, StatefulSection)
|
|
||||||
|
|
||||||
if not self.if_stats[iface].isup:
|
|
||||||
section.setText(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
text = icon
|
|
||||||
|
|
||||||
state = section.state + (0 if wifi else 1)
|
|
||||||
if wifi and state >= 1: # SSID
|
|
||||||
cmd = ["iwgetid", 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.if_addrs[iface]:
|
|
||||||
if address.family == socket.AF_INET:
|
|
||||||
net = ipaddress.IPv4Network(
|
|
||||||
(address.address, address.netmask), strict=False
|
|
||||||
)
|
|
||||||
text += f" {address.address}/{net.prefixlen}"
|
|
||||||
break
|
|
||||||
|
|
||||||
if state >= 3: # Speed
|
|
||||||
prevRecv = self.prev_io_counters[iface].bytes_recv
|
|
||||||
recv = self.io_counters[iface].bytes_recv
|
|
||||||
prevSent = self.prev_io_counters[iface].bytes_sent
|
|
||||||
sent = self.io_counters[iface].bytes_sent
|
|
||||||
dt = self.time - self.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)}"
|
|
||||||
|
|
||||||
section.setText(text)
|
|
||||||
|
|
||||||
section.setChangedState(update)
|
|
||||||
|
|
||||||
return update
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
await self.updateSections(set(self.if_stats.keys()), self.module)
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
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?
|
|
|
@ -1,306 +0,0 @@
|
||||||
{
|
|
||||||
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;
|
|
||||||
services.picom.enable = true;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
{
|
|
||||||
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...)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
# 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";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
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 ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
{
|
|
||||||
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
Normal file
65
hm/dev.nix
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{ 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
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
55
hm/dev/c.nix
55
hm/dev/c.nix
|
@ -1,55 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
{ 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
|
|
||||||
nixfmt-rfc-style
|
|
||||||
|
|
||||||
# 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 = [ "nixfmt" ];
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{ pkgs, config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./c.nix
|
|
||||||
./common.nix
|
|
||||||
./go.nix
|
|
||||||
./node.nix
|
|
||||||
./prose.nix
|
|
||||||
./python.nix
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
# 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}"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
# 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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
{
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
71
hm/extra.nix
Normal file
71
hm/extra.nix
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
{
|
||||||
|
config = lib.mkIf config.frogeye.extra {
|
||||||
|
programs = {
|
||||||
|
pandoc.enable = true;
|
||||||
|
yt-dlp = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
format = "bestvideo[height<=${builtins.toString config.frogeye.desktop.maxVideoHeight}]+bestaudio/best";
|
||||||
|
sponsorblock-mark = "all";
|
||||||
|
sponsorblock-remove = "intro,outro,sponsor,selfpromo,preview,interaction,music_offtopic";
|
||||||
|
sub-langs = "en,fr";
|
||||||
|
write-auto-subs = true;
|
||||||
|
write-subs = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
home.packages = with pkgs; ([
|
||||||
|
# android tools
|
||||||
|
android-tools
|
||||||
|
|
||||||
|
# Communication
|
||||||
|
signal-desktop
|
||||||
|
|
||||||
|
# downloading
|
||||||
|
# transmission TODO Collision if both transmissions are active?
|
||||||
|
|
||||||
|
# Multimedia toolbox
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
# documents
|
||||||
|
visidata
|
||||||
|
# texlive.combined.scheme-full
|
||||||
|
# TODO Convert existing LaTeX documents into using Nix build system
|
||||||
|
# texlive is big and not that much used, sooo
|
||||||
|
pdftk
|
||||||
|
hunspell
|
||||||
|
hunspellDicts.en_GB-ize
|
||||||
|
hunspellDicts.en_US
|
||||||
|
hunspellDicts.fr-moderne
|
||||||
|
hunspellDicts.nl_NL
|
||||||
|
# TODO libreoffice-extension-languagetool or libreoffice-extension-grammalecte-fr
|
||||||
|
|
||||||
|
] ++ lib.optionals config.frogeye.desktop.xorg [
|
||||||
|
|
||||||
|
# multimedia editors
|
||||||
|
gimp
|
||||||
|
inkscape
|
||||||
|
darktable
|
||||||
|
puddletag
|
||||||
|
audacity
|
||||||
|
|
||||||
|
# downloading
|
||||||
|
transmission-qt
|
||||||
|
# wine only makes sense on x86_64
|
||||||
|
] ++ lib.optionals pkgs.stdenv.isx86_64 [
|
||||||
|
wine
|
||||||
|
# TODO wine-gecko wine-mono lib32-libpulse (?)
|
||||||
|
|
||||||
|
] ++ lib.optionals (!stdenv.isAarch64) [
|
||||||
|
# Musescore is broken on aarch64
|
||||||
|
musescore
|
||||||
|
# Blender 4.0.1 can't compile on aarch64
|
||||||
|
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
|
||||||
|
blender
|
||||||
|
]);
|
||||||
|
services = {
|
||||||
|
syncthing.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,80 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
config = lib.mkIf config.frogeye.extra {
|
|
||||||
programs = {
|
|
||||||
pandoc.enable = true;
|
|
||||||
yt-dlp = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
format = "bestvideo[height<=${builtins.toString config.frogeye.desktop.maxVideoHeight}]+bestaudio/best";
|
|
||||||
sponsorblock-mark = "all";
|
|
||||||
sponsorblock-remove = "intro,outro,sponsor,selfpromo,preview,interaction,music_offtopic";
|
|
||||||
sub-langs = "en,fr";
|
|
||||||
write-auto-subs = true;
|
|
||||||
write-subs = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
home.packages =
|
|
||||||
with pkgs;
|
|
||||||
(
|
|
||||||
[
|
|
||||||
# android tools
|
|
||||||
android-tools
|
|
||||||
|
|
||||||
# Communication
|
|
||||||
signal-desktop
|
|
||||||
(pkgs.callPackage ./whisperx.nix { }) # Transcribe voice messages
|
|
||||||
|
|
||||||
# downloading
|
|
||||||
# transmission TODO Collision if both transmissions are active?
|
|
||||||
|
|
||||||
# Multimedia toolbox
|
|
||||||
ffmpeg
|
|
||||||
|
|
||||||
# documents
|
|
||||||
visidata
|
|
||||||
# texlive.combined.scheme-full
|
|
||||||
# TODO Convert existing LaTeX documents into using Nix build system
|
|
||||||
# texlive is big and not that much used, sooo
|
|
||||||
pdftk
|
|
||||||
pdfgrep
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
haskellPackages.dice
|
|
||||||
rustdesk-flutter
|
|
||||||
|
|
||||||
]
|
|
||||||
++ lib.optionals config.frogeye.desktop.xorg [
|
|
||||||
|
|
||||||
# multimedia editors
|
|
||||||
darktable
|
|
||||||
puddletag
|
|
||||||
audacity
|
|
||||||
xournalpp
|
|
||||||
krita
|
|
||||||
|
|
||||||
# downloading
|
|
||||||
transmission_4-qt
|
|
||||||
# wine only makes sense on x86_64
|
|
||||||
]
|
|
||||||
++ lib.optionals pkgs.stdenv.isx86_64 [
|
|
||||||
wine
|
|
||||||
# TODO wine-gecko wine-mono lib32-libpulse (?)
|
|
||||||
|
|
||||||
]
|
|
||||||
++ lib.optionals (!stdenv.isAarch64) [
|
|
||||||
# Musescore is broken on aarch64
|
|
||||||
musescore
|
|
||||||
# Blender 4.0.1 can't compile on aarch64
|
|
||||||
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
|
|
||||||
blender
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
pkgs ? import <nixpkgs> { },
|
|
||||||
}:
|
|
||||||
pkgs.python3Packages.buildPythonPackage rec {
|
|
||||||
pname = "whisperx";
|
|
||||||
version = "3.2.0";
|
|
||||||
# pypi doesn't have the requirements.txt file, and it's required
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "m-bain";
|
|
||||||
repo = "whisperX";
|
|
||||||
rev = "v${version}"; # git doesn't have tags
|
|
||||||
hash = "sha256-JQvyR9JW8OiSRI0eywTyWB4VMXmu6cTpBekBWonoJa4=";
|
|
||||||
};
|
|
||||||
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
|
|
||||||
];
|
|
||||||
pythonRelaxDeps = true;
|
|
||||||
# torchaudio: 2.5.1a0 is >=2, despite dependency check saying otherwise
|
|
||||||
# ctranslate2: was pinned to 4.4.0 to fix some nvidia pinning issue or whatnot
|
|
||||||
# pyannote-audio: not sure what pins this to 3.1.1, but 3.3.1 works fine
|
|
||||||
# For some reason pyannote-audio doesn't get relaxed when listed in a pythonRelaxDeps array,
|
|
||||||
# hence why using true
|
|
||||||
build-system = [
|
|
||||||
pkgs.python3Packages.setuptools
|
|
||||||
];
|
|
||||||
pythonImportsCheck = [
|
|
||||||
"whisperx"
|
|
||||||
];
|
|
||||||
}
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
94
hm/frobar/.dev/barng.py
Executable file
94
hm/frobar/.dev/barng.py
Executable file
|
@ -0,0 +1,94 @@
|
||||||
|
#!/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()
|
199
hm/frobar/.dev/oldbar.py
Executable file
199
hm/frobar/.dev/oldbar.py
Executable file
|
@ -0,0 +1,199 @@
|
||||||
|
#!/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()
|
327
hm/frobar/.dev/pip.py
Executable file
327
hm/frobar/.dev/pip.py
Executable file
|
@ -0,0 +1,327 @@
|
||||||
|
#!/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)
|
10
hm/frobar/.dev/x.py
Executable file
10
hm/frobar/.dev/x.py
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import Xlib.display
|
||||||
|
|
||||||
|
dis = Xlib.display.Display()
|
||||||
|
|
||||||
|
nb = dis.screen_count()
|
||||||
|
|
||||||
|
for s in range(nb):
|
||||||
|
print(s)
|
48
hm/frobar/default.nix
Normal file
48
hm/frobar/default.nix
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{ 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
|
64
hm/frobar/frobar/__init__.py
Normal file
64
hm/frobar/frobar/__init__.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#!/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()
|
723
hm/frobar/frobar/display.py
Normal file
723
hm/frobar/frobar/display.py
Normal file
|
@ -0,0 +1,723 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
import i3ipc
|
||||||
|
|
||||||
|
from frobar.notbusy import notBusy
|
||||||
|
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Allow deletion of Bar, BarGroup and Section for screen changes
|
||||||
|
# IDEA Use i3 ipc events rather than relying on xrandr or Xlib (less portable
|
||||||
|
# but easier)
|
||||||
|
# TODO Optimize to use write() calls instead of string concatenation (writing
|
||||||
|
# BarGroup strings should be a good compromise)
|
||||||
|
# TODO Use bytes rather than strings
|
||||||
|
# TODO Use default colors of lemonbar sometimes
|
||||||
|
# TODO Adapt bar height with font height
|
||||||
|
# TODO OPTI Static text objects that update its parents if modified
|
||||||
|
# TODO forceSize and changeText are different
|
||||||
|
|
||||||
|
|
||||||
|
class BarGroupType(enum.Enum):
|
||||||
|
LEFT = 0
|
||||||
|
RIGHT = 1
|
||||||
|
# TODO Middle
|
||||||
|
# MID_LEFT = 2
|
||||||
|
# MID_RIGHT = 3
|
||||||
|
|
||||||
|
|
||||||
|
class BarStdoutThread(threading.Thread):
|
||||||
|
def run(self) -> None:
|
||||||
|
while Bar.running:
|
||||||
|
handle = Bar.process.stdout.readline().strip()
|
||||||
|
if not len(handle):
|
||||||
|
Bar.stop()
|
||||||
|
if handle not in Bar.actionsH2F:
|
||||||
|
log.error("Unknown action: {}".format(handle))
|
||||||
|
continue
|
||||||
|
function = Bar.actionsH2F[handle]
|
||||||
|
function()
|
||||||
|
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
"""
|
||||||
|
One bar for each screen
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
FONTS = ["DejaVuSansM Nerd Font"]
|
||||||
|
FONTSIZE = 10
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init() -> None:
|
||||||
|
Bar.running = True
|
||||||
|
Section.init()
|
||||||
|
|
||||||
|
cmd = ["lemonbar", "-b", "-a", "64"]
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
Bar(0)
|
||||||
|
# Bar(1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def stop() -> None:
|
||||||
|
Bar.running = False
|
||||||
|
Bar.process.kill()
|
||||||
|
|
||||||
|
# TODO This is not really the best way to do it I guess
|
||||||
|
os.killpg(os.getpid(), signal.SIGTERM)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run() -> None:
|
||||||
|
Bar.forever()
|
||||||
|
i3 = i3ipc.Connection()
|
||||||
|
|
||||||
|
def doStop(*args) -> None:
|
||||||
|
Bar.stop()
|
||||||
|
print(88)
|
||||||
|
|
||||||
|
try:
|
||||||
|
i3.on("ipc_shutdown", doStop)
|
||||||
|
i3.main()
|
||||||
|
except BaseException:
|
||||||
|
print(93)
|
||||||
|
Bar.stop()
|
||||||
|
|
||||||
|
# Class globals
|
||||||
|
everyone = set()
|
||||||
|
string = ""
|
||||||
|
process = None
|
||||||
|
running = False
|
||||||
|
|
||||||
|
nextHandle = 0
|
||||||
|
actionsF2H = dict()
|
||||||
|
actionsH2F = dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getFunctionHandle(function):
|
||||||
|
assert callable(function)
|
||||||
|
if function in Bar.actionsF2H.keys():
|
||||||
|
return Bar.actionsF2H[function]
|
||||||
|
|
||||||
|
handle = "{:x}".format(Bar.nextHandle).encode()
|
||||||
|
Bar.nextHandle += 1
|
||||||
|
|
||||||
|
Bar.actionsF2H[function] = handle
|
||||||
|
Bar.actionsH2F[handle] = function
|
||||||
|
|
||||||
|
return handle
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def forever():
|
||||||
|
Bar.process.wait()
|
||||||
|
Bar.stop()
|
||||||
|
|
||||||
|
def __init__(self, screen):
|
||||||
|
assert isinstance(screen, int)
|
||||||
|
self.screen = "%{S" + str(screen) + "}"
|
||||||
|
self.groups = dict()
|
||||||
|
|
||||||
|
for groupType in BarGroupType:
|
||||||
|
group = BarGroup(groupType, self)
|
||||||
|
self.groups[groupType] = group
|
||||||
|
|
||||||
|
self.childsChanged = False
|
||||||
|
|
||||||
|
self.everyone.add(self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def addSectionAll(section, group, screens=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)
|
||||||
|
|
||||||
|
def addSection(self, section, group):
|
||||||
|
assert isinstance(section, Section)
|
||||||
|
assert isinstance(group, BarGroupType)
|
||||||
|
self.groups[group].addSection(section)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.childsChanged:
|
||||||
|
self.string = self.screen
|
||||||
|
self.string += self.groups[BarGroupType.LEFT].string
|
||||||
|
self.string += self.groups[BarGroupType.RIGHT].string
|
||||||
|
|
||||||
|
self.childsChanged = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateAll():
|
||||||
|
if Bar.running:
|
||||||
|
Bar.string = ""
|
||||||
|
for bar in Bar.everyone:
|
||||||
|
bar.update()
|
||||||
|
Bar.string += bar.string
|
||||||
|
# Color for empty sections
|
||||||
|
Bar.string += BarGroup.color(*Section.EMPTY)
|
||||||
|
|
||||||
|
# print(Bar.string)
|
||||||
|
Bar.process.stdin.write(bytes(Bar.string + "\n", "utf-8"))
|
||||||
|
Bar.process.stdin.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class BarGroup:
|
||||||
|
"""
|
||||||
|
One for each group of each bar
|
||||||
|
"""
|
||||||
|
|
||||||
|
everyone = set()
|
||||||
|
|
||||||
|
def __init__(self, groupType, parent):
|
||||||
|
assert isinstance(groupType, BarGroupType)
|
||||||
|
assert isinstance(parent, Bar)
|
||||||
|
|
||||||
|
self.groupType = groupType
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.sections = list()
|
||||||
|
self.string = ""
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
#: One of the sections that had their theme or visibility changed
|
||||||
|
self.childsThemeChanged = False
|
||||||
|
|
||||||
|
#: One of the sections that had their text (maybe their size) changed
|
||||||
|
self.childsTextChanged = False
|
||||||
|
|
||||||
|
BarGroup.everyone.add(self)
|
||||||
|
|
||||||
|
def addSection(self, section):
|
||||||
|
self.sections.append(section)
|
||||||
|
section.addParent(self)
|
||||||
|
|
||||||
|
def addSectionAfter(self, sectionRef, section):
|
||||||
|
index = self.sections.index(sectionRef)
|
||||||
|
self.sections.insert(index + 1, section)
|
||||||
|
section.addParent(self)
|
||||||
|
|
||||||
|
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fgColor(color):
|
||||||
|
return "%{F" + (color or "-") + "}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bgColor(color):
|
||||||
|
return "%{B" + (color or "-") + "}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def color(fg, bg):
|
||||||
|
return BarGroup.fgColor(fg) + BarGroup.bgColor(bg)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.childsThemeChanged:
|
||||||
|
parts = [BarGroup.ALIGNS[self.groupType]]
|
||||||
|
|
||||||
|
secs = [sec for sec in self.sections if sec.visible]
|
||||||
|
lenS = len(secs)
|
||||||
|
for s in range(lenS):
|
||||||
|
sec = secs[s]
|
||||||
|
theme = Section.THEMES[sec.theme]
|
||||||
|
if self.groupType == BarGroupType.LEFT:
|
||||||
|
oSec = secs[s + 1] if s < lenS - 1 else None
|
||||||
|
else:
|
||||||
|
oSec = secs[s - 1] if s > 0 else None
|
||||||
|
oTheme = (
|
||||||
|
Section.THEMES[oSec.theme] if oSec is not None else Section.EMPTY
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.groupType == BarGroupType.LEFT:
|
||||||
|
if s == 0:
|
||||||
|
parts.append(BarGroup.bgColor(theme[1]))
|
||||||
|
parts.append(BarGroup.fgColor(theme[0]))
|
||||||
|
parts.append(sec)
|
||||||
|
if theme == oTheme:
|
||||||
|
parts.append("")
|
||||||
|
else:
|
||||||
|
parts.append(BarGroup.color(theme[1], oTheme[1]) + "")
|
||||||
|
else:
|
||||||
|
if theme is oTheme:
|
||||||
|
parts.append("")
|
||||||
|
else:
|
||||||
|
parts.append(BarGroup.fgColor(theme[1]) + "")
|
||||||
|
parts.append(BarGroup.color(*theme))
|
||||||
|
parts.append(sec)
|
||||||
|
|
||||||
|
# TODO OPTI Concatenate successive strings
|
||||||
|
self.parts = parts
|
||||||
|
|
||||||
|
if self.childsTextChanged or self.childsThemeChanged:
|
||||||
|
self.string = ""
|
||||||
|
for part in self.parts:
|
||||||
|
if isinstance(part, str):
|
||||||
|
self.string += part
|
||||||
|
elif isinstance(part, Section):
|
||||||
|
self.string += part.curText
|
||||||
|
|
||||||
|
self.parent.childsChanged = True
|
||||||
|
|
||||||
|
self.childsThemeChanged = False
|
||||||
|
self.childsTextChanged = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateAll():
|
||||||
|
for group in BarGroup.everyone:
|
||||||
|
group.update()
|
||||||
|
Bar.updateAll()
|
||||||
|
|
||||||
|
|
||||||
|
class SectionThread(threading.Thread):
|
||||||
|
ANIMATION_START = 0.025
|
||||||
|
ANIMATION_STOP = 0.001
|
||||||
|
ANIMATION_EVOLUTION = 0.9
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while Section.somethingChanged.wait():
|
||||||
|
notBusy.wait()
|
||||||
|
Section.updateAll()
|
||||||
|
animTime = self.ANIMATION_START
|
||||||
|
frameTime = time.perf_counter()
|
||||||
|
while len(Section.sizeChanging) > 0:
|
||||||
|
frameTime += animTime
|
||||||
|
curTime = time.perf_counter()
|
||||||
|
sleepTime = frameTime - curTime
|
||||||
|
time.sleep(sleepTime if sleepTime > 0 else 0)
|
||||||
|
Section.updateAll()
|
||||||
|
animTime *= self.ANIMATION_EVOLUTION
|
||||||
|
if animTime < self.ANIMATION_STOP:
|
||||||
|
animTime = self.ANIMATION_STOP
|
||||||
|
|
||||||
|
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
FGCOLOR = "#F8F8F2"
|
||||||
|
BGCOLOR = "#272822"
|
||||||
|
|
||||||
|
THEMES = list()
|
||||||
|
EMPTY = (FGCOLOR, BGCOLOR)
|
||||||
|
|
||||||
|
ICON = None
|
||||||
|
PERSISTENT = False
|
||||||
|
|
||||||
|
#: Sections that do not have their destination size
|
||||||
|
sizeChanging = set()
|
||||||
|
updateThread = SectionThread(daemon=True)
|
||||||
|
somethingChanged = threading.Event()
|
||||||
|
lastChosenTheme = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
for t in range(8, 16):
|
||||||
|
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
|
||||||
|
|
||||||
|
Section.updateThread.start()
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
#: Displayed section
|
||||||
|
#: Note: A section can be empty and displayed!
|
||||||
|
self.visible = False
|
||||||
|
|
||||||
|
if theme is None:
|
||||||
|
theme = Section.lastChosenTheme
|
||||||
|
Section.lastChosenTheme = (Section.lastChosenTheme + 1) % len(
|
||||||
|
Section.THEMES
|
||||||
|
)
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
|
#: Displayed text
|
||||||
|
self.curText = ""
|
||||||
|
#: Displayed text size
|
||||||
|
self.curSize = 0
|
||||||
|
|
||||||
|
#: Destination text
|
||||||
|
self.dstText = Text(" ", Text(), " ")
|
||||||
|
#: Destination size
|
||||||
|
self.dstSize = 0
|
||||||
|
|
||||||
|
#: Groups that have this section
|
||||||
|
self.parents = set()
|
||||||
|
|
||||||
|
self.icon = self.ICON
|
||||||
|
self.persistent = self.PERSISTENT
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
try:
|
||||||
|
return "<{}><{}>{:01d}{}{:02d}/{:02d}".format(
|
||||||
|
self.curText,
|
||||||
|
self.dstText,
|
||||||
|
self.theme,
|
||||||
|
"+" if self.visible else "-",
|
||||||
|
self.curSize,
|
||||||
|
self.dstSize,
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
return super().__str__()
|
||||||
|
|
||||||
|
def addParent(self, parent):
|
||||||
|
self.parents.add(parent)
|
||||||
|
|
||||||
|
def appendAfter(self, section):
|
||||||
|
assert len(self.parents)
|
||||||
|
for parent in self.parents:
|
||||||
|
parent.addSectionAfter(self, section)
|
||||||
|
|
||||||
|
def informParentsThemeChanged(self):
|
||||||
|
for parent in self.parents:
|
||||||
|
parent.childsThemeChanged = True
|
||||||
|
|
||||||
|
def informParentsTextChanged(self):
|
||||||
|
for parent in self.parents:
|
||||||
|
parent.childsTextChanged = True
|
||||||
|
|
||||||
|
def updateText(self, text):
|
||||||
|
if isinstance(text, str):
|
||||||
|
text = Text(text)
|
||||||
|
elif isinstance(text, Text) and not len(text.elements):
|
||||||
|
text = None
|
||||||
|
|
||||||
|
self.dstText[0] = (
|
||||||
|
None
|
||||||
|
if (text is None and not self.persistent)
|
||||||
|
else ((" " + self.icon + " ") if self.icon else " ")
|
||||||
|
)
|
||||||
|
self.dstText[1] = text
|
||||||
|
self.dstText[2] = (
|
||||||
|
" " if self.dstText[1] is not None and len(self.dstText[1]) else None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dstSize = len(self.dstText)
|
||||||
|
self.dstText.setSection(self)
|
||||||
|
|
||||||
|
if self.curSize == self.dstSize:
|
||||||
|
if self.dstSize > 0:
|
||||||
|
self.curText = str(self.dstText)
|
||||||
|
self.informParentsTextChanged()
|
||||||
|
else:
|
||||||
|
Section.sizeChanging.add(self)
|
||||||
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
|
def setDecorators(self, **kwargs):
|
||||||
|
self.dstText.setDecorators(**kwargs)
|
||||||
|
self.curText = str(self.dstText)
|
||||||
|
self.informParentsTextChanged()
|
||||||
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
|
def updateTheme(self, theme):
|
||||||
|
assert isinstance(theme, int)
|
||||||
|
assert theme < len(Section.THEMES)
|
||||||
|
if theme == self.theme:
|
||||||
|
return
|
||||||
|
self.theme = theme
|
||||||
|
self.informParentsThemeChanged()
|
||||||
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
|
def updateVisibility(self, visibility):
|
||||||
|
assert isinstance(visibility, bool)
|
||||||
|
|
||||||
|
self.visible = visibility
|
||||||
|
self.informParentsThemeChanged()
|
||||||
|
Section.somethingChanged.set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fit(text, size):
|
||||||
|
t = len(text)
|
||||||
|
return text[:size] if t >= size else text + [" "] * (size - t)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# TODO Might profit of a better logic
|
||||||
|
if not self.visible:
|
||||||
|
self.updateVisibility(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.dstSize > self.curSize:
|
||||||
|
self.curSize += 1
|
||||||
|
elif self.dstSize < self.curSize:
|
||||||
|
self.curSize -= 1
|
||||||
|
else:
|
||||||
|
# Visibility toggling must be done one step after curSize = 0
|
||||||
|
if self.dstSize == 0:
|
||||||
|
self.updateVisibility(False)
|
||||||
|
Section.sizeChanging.remove(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.curText = self.dstText.text(size=self.curSize, pad=True)
|
||||||
|
self.informParentsTextChanged()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateAll():
|
||||||
|
"""
|
||||||
|
Process all sections for text size changes
|
||||||
|
"""
|
||||||
|
|
||||||
|
for sizeChanging in Section.sizeChanging.copy():
|
||||||
|
sizeChanging.update()
|
||||||
|
|
||||||
|
BarGroup.updateAll()
|
||||||
|
|
||||||
|
Section.somethingChanged.clear()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ramp(p, ramp=" ▁▂▃▄▅▆▇█"):
|
||||||
|
if p > 1:
|
||||||
|
return ramp[-1]
|
||||||
|
elif p < 0:
|
||||||
|
return ramp[0]
|
||||||
|
else:
|
||||||
|
return ramp[round(p * (len(ramp) - 1))]
|
||||||
|
|
||||||
|
|
||||||
|
class StatefulSection(Section):
|
||||||
|
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
|
||||||
|
NUMBER_STATES = None
|
||||||
|
DEFAULT_STATE = 0
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Section.__init__(self, *args, **kwargs)
|
||||||
|
self.state = self.DEFAULT_STATE
|
||||||
|
if hasattr(self, "onChangeState"):
|
||||||
|
self.onChangeState(self.state)
|
||||||
|
self.setDecorators(
|
||||||
|
clickLeft=self.incrementState, clickRight=self.decrementState
|
||||||
|
)
|
||||||
|
|
||||||
|
def incrementState(self):
|
||||||
|
newState = min(self.state + 1, self.NUMBER_STATES - 1)
|
||||||
|
self.changeState(newState)
|
||||||
|
|
||||||
|
def decrementState(self):
|
||||||
|
newState = max(self.state - 1, 0)
|
||||||
|
self.changeState(newState)
|
||||||
|
|
||||||
|
def changeState(self, state):
|
||||||
|
assert isinstance(state, int)
|
||||||
|
assert state < self.NUMBER_STATES
|
||||||
|
self.state = state
|
||||||
|
if hasattr(self, "onChangeState"):
|
||||||
|
self.onChangeState(state)
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class ColorCountsSection(StatefulSection):
|
||||||
|
# TODO FEAT Blend colors when not expanded
|
||||||
|
# TODO FEAT Blend colors with importance of count
|
||||||
|
# TODO FEAT Allow icons instead of counts
|
||||||
|
NUMBER_STATES = 3
|
||||||
|
COLORABLE_ICON = "?"
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
StatefulSection.__init__(self, theme=theme)
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
counts = self.subfetcher()
|
||||||
|
# Nothing
|
||||||
|
if not len(counts):
|
||||||
|
return None
|
||||||
|
# Icon colored
|
||||||
|
elif self.state == 0 and len(counts) == 1:
|
||||||
|
count, color = counts[0]
|
||||||
|
return Text(self.COLORABLE_ICON, fg=color)
|
||||||
|
# Icon
|
||||||
|
elif self.state == 0 and len(counts) > 1:
|
||||||
|
return Text(self.COLORABLE_ICON)
|
||||||
|
# Icon + Total
|
||||||
|
elif self.state == 1 and len(counts) > 1:
|
||||||
|
total = sum([count for count, color in counts])
|
||||||
|
return Text(self.COLORABLE_ICON, " ", total)
|
||||||
|
# Icon + Counts
|
||||||
|
else:
|
||||||
|
text = Text(self.COLORABLE_ICON)
|
||||||
|
for count, color in counts:
|
||||||
|
text.append(" ", Text(count, fg=color))
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
class Text:
|
||||||
|
def _setElements(self, elements):
|
||||||
|
# TODO OPTI Concatenate consecutrive string
|
||||||
|
self.elements = list(elements)
|
||||||
|
|
||||||
|
def _setDecorators(self, decorators):
|
||||||
|
# TODO OPTI Convert no decorator to strings
|
||||||
|
self.decorators = decorators
|
||||||
|
self.prefix = None
|
||||||
|
self.suffix = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._setElements(args)
|
||||||
|
self._setDecorators(kwargs)
|
||||||
|
self.section = None
|
||||||
|
|
||||||
|
def append(self, *args):
|
||||||
|
self._setElements(self.elements + list(args))
|
||||||
|
|
||||||
|
def prepend(self, *args):
|
||||||
|
self._setElements(list(args) + self.elements)
|
||||||
|
|
||||||
|
def setElements(self, *args):
|
||||||
|
self._setElements(args)
|
||||||
|
|
||||||
|
def setDecorators(self, **kwargs):
|
||||||
|
self._setDecorators(kwargs)
|
||||||
|
|
||||||
|
def setSection(self, section):
|
||||||
|
assert isinstance(section, Section)
|
||||||
|
self.section = section
|
||||||
|
for element in self.elements:
|
||||||
|
if isinstance(element, Text):
|
||||||
|
element.setSection(section)
|
||||||
|
|
||||||
|
def _genFixs(self):
|
||||||
|
if self.prefix is not None and self.suffix is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.prefix = ""
|
||||||
|
self.suffix = ""
|
||||||
|
|
||||||
|
def nest(prefix, suffix):
|
||||||
|
self.prefix = self.prefix + "%{" + prefix + "}"
|
||||||
|
self.suffix = "%{" + suffix + "}" + self.suffix
|
||||||
|
|
||||||
|
def getColor(val):
|
||||||
|
# TODO Allow themes
|
||||||
|
assert isinstance(val, str) and len(val) == 7
|
||||||
|
return val
|
||||||
|
|
||||||
|
def button(number, function):
|
||||||
|
handle = Bar.getFunctionHandle(function)
|
||||||
|
nest("A" + number + ":" + handle.decode() + ":", "A" + number)
|
||||||
|
|
||||||
|
for key, val in self.decorators.items():
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
if key == "fg":
|
||||||
|
reset = self.section.THEMES[self.section.theme][0]
|
||||||
|
nest("F" + getColor(val), "F" + reset)
|
||||||
|
elif key == "bg":
|
||||||
|
reset = self.section.THEMES[self.section.theme][1]
|
||||||
|
nest("B" + getColor(val), "B" + reset)
|
||||||
|
elif key == "clickLeft":
|
||||||
|
button("1", val)
|
||||||
|
elif key == "clickMiddle":
|
||||||
|
button("2", val)
|
||||||
|
elif key == "clickRight":
|
||||||
|
button("3", val)
|
||||||
|
elif key == "scrollUp":
|
||||||
|
button("4", val)
|
||||||
|
elif key == "scrollDown":
|
||||||
|
button("5", val)
|
||||||
|
else:
|
||||||
|
log.warn("Unkown decorator: {}".format(key))
|
||||||
|
|
||||||
|
def _text(self, size=None, pad=False):
|
||||||
|
self._genFixs()
|
||||||
|
curString = self.prefix
|
||||||
|
curSize = 0
|
||||||
|
remSize = size
|
||||||
|
|
||||||
|
for element in self.elements:
|
||||||
|
if element is None:
|
||||||
|
continue
|
||||||
|
elif isinstance(element, Text):
|
||||||
|
newString, newSize = element._text(size=remSize)
|
||||||
|
else:
|
||||||
|
newString = str(element)
|
||||||
|
if remSize is not None:
|
||||||
|
newString = newString[:remSize]
|
||||||
|
newSize = len(newString)
|
||||||
|
|
||||||
|
curString += newString
|
||||||
|
curSize += newSize
|
||||||
|
|
||||||
|
if remSize is not None:
|
||||||
|
remSize -= newSize
|
||||||
|
if remSize <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
curString += self.suffix
|
||||||
|
|
||||||
|
if pad and remSize > 0:
|
||||||
|
curString += " " * remSize
|
||||||
|
curSize += remSize
|
||||||
|
|
||||||
|
if size is not None:
|
||||||
|
if pad:
|
||||||
|
assert size == curSize
|
||||||
|
else:
|
||||||
|
assert size >= curSize
|
||||||
|
return curString, curSize
|
||||||
|
|
||||||
|
def text(self, *args, **kwargs):
|
||||||
|
string, size = self._text(*args, **kwargs)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
self._genFixs()
|
||||||
|
curString = self.prefix
|
||||||
|
for element in self.elements:
|
||||||
|
if element is None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
curString += str(element)
|
||||||
|
curString += self.suffix
|
||||||
|
return curString
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
curSize = 0
|
||||||
|
for element in self.elements:
|
||||||
|
if element is None:
|
||||||
|
continue
|
||||||
|
elif isinstance(element, Text):
|
||||||
|
curSize += len(element)
|
||||||
|
else:
|
||||||
|
curSize += len(str(element))
|
||||||
|
return curSize
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self.elements[index]
|
||||||
|
|
||||||
|
def __setitem__(self, index, data):
|
||||||
|
self.elements[index] = data
|
5
hm/frobar/frobar/notbusy.py
Normal file
5
hm/frobar/frobar/notbusy.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
notBusy = threading.Event()
|
816
hm/frobar/frobar/providers.py
Normal file
816
hm/frobar/frobar/providers.py
Normal file
|
@ -0,0 +1,816 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
import mpd
|
||||||
|
import notmuch
|
||||||
|
import psutil
|
||||||
|
import pulsectl
|
||||||
|
|
||||||
|
from frobar.display import *
|
||||||
|
from frobar.updaters import *
|
||||||
|
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
# TODO Generator class (for I3WorkspacesProvider, NetworkProvider and later
|
||||||
|
# PulseaudioProvider and MpdProvider)
|
||||||
|
|
||||||
|
|
||||||
|
def humanSize(num):
|
||||||
|
"""
|
||||||
|
Returns a string of width 3+3
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def randomColor(seed=0):
|
||||||
|
random.seed(seed)
|
||||||
|
return "#{:02x}{:02x}{:02x}".format(*[random.randint(0, 255) for _ in range(3)])
|
||||||
|
|
||||||
|
|
||||||
|
class TimeProvider(StatefulSection, PeriodicUpdater):
|
||||||
|
FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"]
|
||||||
|
NUMBER_STATES = len(FORMATS)
|
||||||
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return now.strftime(self.FORMATS[self.state])
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
StatefulSection.__init__(self, theme)
|
||||||
|
self.changeInterval(1) # TODO OPTI When state < 1
|
||||||
|
|
||||||
|
|
||||||
|
class AlertLevel(enum.Enum):
|
||||||
|
NORMAL = 0
|
||||||
|
WARNING = 1
|
||||||
|
DANGER = 2
|
||||||
|
|
||||||
|
|
||||||
|
class AlertingSection(StatefulSection):
|
||||||
|
# TODO EASE Correct settings for themes
|
||||||
|
THEMES = {AlertLevel.NORMAL: 2, AlertLevel.WARNING: 3, AlertLevel.DANGER: 1}
|
||||||
|
PERSISTENT = True
|
||||||
|
|
||||||
|
def getLevel(self, quantity):
|
||||||
|
if quantity > self.dangerThresold:
|
||||||
|
return AlertLevel.DANGER
|
||||||
|
elif quantity > self.warningThresold:
|
||||||
|
return AlertLevel.WARNING
|
||||||
|
else:
|
||||||
|
return AlertLevel.NORMAL
|
||||||
|
|
||||||
|
def updateLevel(self, quantity):
|
||||||
|
self.level = self.getLevel(quantity)
|
||||||
|
self.updateTheme(self.THEMES[self.level])
|
||||||
|
if self.level == AlertLevel.NORMAL:
|
||||||
|
return
|
||||||
|
# TODO Temporary update state
|
||||||
|
|
||||||
|
def __init__(self, theme):
|
||||||
|
StatefulSection.__init__(self, theme)
|
||||||
|
self.dangerThresold = 0.90
|
||||||
|
self.warningThresold = 0.75
|
||||||
|
|
||||||
|
|
||||||
|
class CpuProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
NUMBER_STATES = 3
|
||||||
|
ICON = ""
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
percent = psutil.cpu_percent(percpu=False)
|
||||||
|
self.updateLevel(percent / 100)
|
||||||
|
if self.state >= 2:
|
||||||
|
percents = psutil.cpu_percent(percpu=True)
|
||||||
|
return "".join([Section.ramp(p / 100) for p in percents])
|
||||||
|
elif self.state >= 1:
|
||||||
|
return Section.ramp(percent / 100)
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
AlertingSection.__init__(self, theme)
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(1)
|
||||||
|
|
||||||
|
|
||||||
|
class RamProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
"""
|
||||||
|
Shows free RAM
|
||||||
|
"""
|
||||||
|
|
||||||
|
NUMBER_STATES = 4
|
||||||
|
ICON = ""
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
mem = psutil.virtual_memory()
|
||||||
|
freePerc = mem.percent / 100
|
||||||
|
self.updateLevel(freePerc)
|
||||||
|
|
||||||
|
if self.state < 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
text = Text(Section.ramp(freePerc))
|
||||||
|
if self.state >= 2:
|
||||||
|
freeStr = humanSize(mem.total - mem.available)
|
||||||
|
text.append(freeStr)
|
||||||
|
if self.state >= 3:
|
||||||
|
totalStr = humanSize(mem.total)
|
||||||
|
text.append("/", totalStr)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
AlertingSection.__init__(self, theme)
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(1)
|
||||||
|
|
||||||
|
|
||||||
|
class TemperatureProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
NUMBER_STATES = 2
|
||||||
|
RAMP = ""
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
allTemp = psutil.sensors_temperatures()
|
||||||
|
if "coretemp" not in allTemp:
|
||||||
|
# TODO Opti Remove interval
|
||||||
|
return ""
|
||||||
|
temp = allTemp["coretemp"][0]
|
||||||
|
|
||||||
|
self.warningThresold = temp.high
|
||||||
|
self.dangerThresold = temp.critical
|
||||||
|
self.updateLevel(temp.current)
|
||||||
|
|
||||||
|
self.icon = Section.ramp(temp.current / temp.high, self.RAMP)
|
||||||
|
if self.state >= 1:
|
||||||
|
return "{:.0f}°C".format(temp.current)
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
AlertingSection.__init__(self, theme)
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class BatteryProvider(AlertingSection, PeriodicUpdater):
|
||||||
|
# TODO Support ACPID for events
|
||||||
|
NUMBER_STATES = 3
|
||||||
|
RAMP = ""
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
bat = psutil.sensors_battery()
|
||||||
|
if not bat:
|
||||||
|
self.icon = None
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.icon = ("" if bat.power_plugged else "") + Section.ramp(
|
||||||
|
bat.percent / 100, self.RAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
self.updateLevel(1 - bat.percent / 100)
|
||||||
|
|
||||||
|
if self.state < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
t = Text("{:.0f}%".format(bat.percent))
|
||||||
|
|
||||||
|
if self.state < 2:
|
||||||
|
return t
|
||||||
|
|
||||||
|
h = int(bat.secsleft / 3600)
|
||||||
|
m = int((bat.secsleft - h * 3600) / 60)
|
||||||
|
t.append(" ({:d}:{:02d})".format(h, m))
|
||||||
|
return t
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
AlertingSection.__init__(self, theme)
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
||||||
|
NUMBER_STATES = 3
|
||||||
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
StatefulSection.__init__(self, theme)
|
||||||
|
self.pulseEvents = pulsectl.Pulse("event-handler")
|
||||||
|
|
||||||
|
self.pulseEvents.event_mask_set(pulsectl.PulseEventMaskEnum.sink)
|
||||||
|
self.pulseEvents.event_callback_set(self.handleEvent)
|
||||||
|
self.start()
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
sinks = []
|
||||||
|
with pulsectl.Pulse("list-sinks") as pulse:
|
||||||
|
for sink in pulse.sink_list():
|
||||||
|
if sink.port_active.name == "analog-output-headphones":
|
||||||
|
icon = ""
|
||||||
|
elif sink.port_active.name == "analog-output-speaker":
|
||||||
|
icon = "" if sink.mute else ""
|
||||||
|
elif sink.port_active.name == "headset-output":
|
||||||
|
icon = ""
|
||||||
|
else:
|
||||||
|
icon = "?"
|
||||||
|
vol = pulse.volume_get_all_chans(sink)
|
||||||
|
fg = (sink.mute and "#333333") or (vol > 1 and "#FF0000") or None
|
||||||
|
|
||||||
|
t = Text(icon, fg=fg)
|
||||||
|
sinks.append(t)
|
||||||
|
|
||||||
|
if self.state < 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.state < 2:
|
||||||
|
if not sink.mute:
|
||||||
|
ramp = " "
|
||||||
|
while vol >= 0:
|
||||||
|
ramp += self.ramp(vol if vol < 1 else 1)
|
||||||
|
vol -= 1
|
||||||
|
t.append(ramp)
|
||||||
|
else:
|
||||||
|
t.append(" {:2.0f}%".format(vol * 100))
|
||||||
|
|
||||||
|
return Text(*sinks)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.pulseEvents.event_listen()
|
||||||
|
|
||||||
|
def handleEvent(self, ev):
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProviderSection(StatefulSection, Updater):
|
||||||
|
NUMBER_STATES = 5
|
||||||
|
DEFAULT_STATE = 1
|
||||||
|
|
||||||
|
def actType(self):
|
||||||
|
self.ssid = None
|
||||||
|
if self.iface.startswith("eth") or self.iface.startswith("enp"):
|
||||||
|
if "u" in self.iface:
|
||||||
|
self.icon = ""
|
||||||
|
else:
|
||||||
|
self.icon = ""
|
||||||
|
elif self.iface.startswith("wlan") or self.iface.startswith("wl"):
|
||||||
|
self.icon = ""
|
||||||
|
if self.showSsid:
|
||||||
|
cmd = ["iwgetid", self.iface, "--raw"]
|
||||||
|
p = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
self.ssid = p.stdout.strip().decode()
|
||||||
|
elif self.iface.startswith("tun") or self.iface.startswith("tap"):
|
||||||
|
self.icon = ""
|
||||||
|
elif self.iface.startswith("docker"):
|
||||||
|
self.icon = ""
|
||||||
|
elif self.iface.startswith("veth"):
|
||||||
|
self.icon = ""
|
||||||
|
elif self.iface.startswith("vboxnet"):
|
||||||
|
self.icon = ""
|
||||||
|
else:
|
||||||
|
self.icon = "?"
|
||||||
|
|
||||||
|
def getAddresses(self):
|
||||||
|
ipv4 = None
|
||||||
|
ipv6 = None
|
||||||
|
for address in self.parent.addrs[self.iface]:
|
||||||
|
if address.family == socket.AF_INET:
|
||||||
|
ipv4 = address
|
||||||
|
elif address.family == socket.AF_INET6:
|
||||||
|
ipv6 = address
|
||||||
|
return ipv4, ipv6
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
self.icon = None
|
||||||
|
self.persistent = False
|
||||||
|
if (
|
||||||
|
self.iface not in self.parent.stats
|
||||||
|
or not self.parent.stats[self.iface].isup
|
||||||
|
or self.iface.startswith("lo")
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get addresses
|
||||||
|
ipv4, ipv6 = self.getAddresses()
|
||||||
|
if ipv4 is None and ipv6 is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
text = []
|
||||||
|
self.persistent = True
|
||||||
|
self.actType()
|
||||||
|
|
||||||
|
if self.showSsid and self.ssid:
|
||||||
|
text.append(self.ssid)
|
||||||
|
|
||||||
|
if self.showAddress:
|
||||||
|
if ipv4:
|
||||||
|
netStrFull = "{}/{}".format(ipv4.address, ipv4.netmask)
|
||||||
|
addr = ipaddress.IPv4Network(netStrFull, strict=False)
|
||||||
|
addrStr = "{}/{}".format(ipv4.address, addr.prefixlen)
|
||||||
|
text.append(addrStr)
|
||||||
|
# TODO IPV6
|
||||||
|
# if ipv6:
|
||||||
|
# text += ' ' + ipv6.address
|
||||||
|
|
||||||
|
if self.showSpeed:
|
||||||
|
recvDiff = (
|
||||||
|
self.parent.IO[self.iface].bytes_recv
|
||||||
|
- self.parent.prevIO[self.iface].bytes_recv
|
||||||
|
)
|
||||||
|
sentDiff = (
|
||||||
|
self.parent.IO[self.iface].bytes_sent
|
||||||
|
- self.parent.prevIO[self.iface].bytes_sent
|
||||||
|
)
|
||||||
|
recvDiff /= self.parent.dt
|
||||||
|
sentDiff /= self.parent.dt
|
||||||
|
text.append("↓{}↑{}".format(humanSize(recvDiff), humanSize(sentDiff)))
|
||||||
|
|
||||||
|
if self.showTransfer:
|
||||||
|
text.append(
|
||||||
|
"⇓{}⇑{}".format(
|
||||||
|
humanSize(self.parent.IO[self.iface].bytes_recv),
|
||||||
|
humanSize(self.parent.IO[self.iface].bytes_sent),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return " ".join(text)
|
||||||
|
|
||||||
|
def onChangeState(self, state):
|
||||||
|
self.showSsid = state >= 1
|
||||||
|
self.showAddress = state >= 2
|
||||||
|
self.showSpeed = state >= 3
|
||||||
|
self.showTransfer = state >= 4
|
||||||
|
|
||||||
|
def __init__(self, iface, parent):
|
||||||
|
Updater.__init__(self)
|
||||||
|
StatefulSection.__init__(self, theme=parent.theme)
|
||||||
|
self.iface = iface
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProvider(Section, PeriodicUpdater):
|
||||||
|
def fetchData(self):
|
||||||
|
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.ifaces = self.stats.keys()
|
||||||
|
|
||||||
|
self.last = time.perf_counter()
|
||||||
|
self.dt = self.last - self.prev
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
self.fetchData()
|
||||||
|
|
||||||
|
# Add missing sections
|
||||||
|
lastSection = self
|
||||||
|
for iface in sorted(list(self.ifaces)):
|
||||||
|
if iface not in self.sections.keys():
|
||||||
|
section = NetworkProviderSection(iface, self)
|
||||||
|
lastSection.appendAfter(section)
|
||||||
|
self.sections[iface] = section
|
||||||
|
else:
|
||||||
|
section = self.sections[iface]
|
||||||
|
lastSection = section
|
||||||
|
|
||||||
|
# Refresh section text
|
||||||
|
for section in self.sections.values():
|
||||||
|
section.refreshData()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def addParent(self, parent):
|
||||||
|
self.parents.add(parent)
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
self.sections = dict()
|
||||||
|
self.last = 0
|
||||||
|
self.IO = dict()
|
||||||
|
self.fetchData()
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class RfkillProvider(Section, PeriodicUpdater):
|
||||||
|
# TODO FEAT rfkill doesn't seem to indicate that the hardware switch is
|
||||||
|
# toggled
|
||||||
|
PATH = "/sys/class/rfkill"
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
t = Text()
|
||||||
|
for device in os.listdir(self.PATH):
|
||||||
|
with open(os.path.join(self.PATH, device, "soft"), "rb") as f:
|
||||||
|
softBlocked = f.read().strip() != b"0"
|
||||||
|
with open(os.path.join(self.PATH, device, "hard"), "rb") as f:
|
||||||
|
hardBlocked = f.read().strip() != b"0"
|
||||||
|
|
||||||
|
if not hardBlocked and not softBlocked:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(os.path.join(self.PATH, device, "type"), "rb") as f:
|
||||||
|
typ = f.read().strip()
|
||||||
|
|
||||||
|
fg = (hardBlocked and "#CCCCCC") or (softBlocked and "#FF0000")
|
||||||
|
if typ == b"wlan":
|
||||||
|
icon = ""
|
||||||
|
elif typ == b"bluetooth":
|
||||||
|
icon = ""
|
||||||
|
else:
|
||||||
|
icon = "?"
|
||||||
|
|
||||||
|
t.append(Text(icon, fg=fg))
|
||||||
|
return t
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
Section.__init__(self, theme)
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class SshAgentProvider(PeriodicUpdater):
|
||||||
|
def fetcher(self):
|
||||||
|
cmd = ["ssh-add", "-l"]
|
||||||
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return None
|
||||||
|
text = Text()
|
||||||
|
for line in proc.stdout.split(b"\n"):
|
||||||
|
if not len(line):
|
||||||
|
continue
|
||||||
|
fingerprint = line.split()[1]
|
||||||
|
text.append(Text("", fg=randomColor(seed=fingerprint)))
|
||||||
|
return text
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class GpgAgentProvider(PeriodicUpdater):
|
||||||
|
def fetcher(self):
|
||||||
|
cmd = ["gpg-connect-agent", "keyinfo --list", "/bye"]
|
||||||
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
# proc = subprocess.run(cmd)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return None
|
||||||
|
text = Text()
|
||||||
|
for line in proc.stdout.split(b"\n"):
|
||||||
|
if not len(line) or line == b"OK":
|
||||||
|
continue
|
||||||
|
spli = line.split()
|
||||||
|
if spli[6] != b"1":
|
||||||
|
continue
|
||||||
|
keygrip = spli[2]
|
||||||
|
text.append(Text("", fg=randomColor(seed=keygrip)))
|
||||||
|
return text
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoreProvider(Section, MergedUpdater):
|
||||||
|
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless
|
||||||
|
ICON = ""
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
MergedUpdater.__init__(self, SshAgentProvider(), GpgAgentProvider())
|
||||||
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater):
|
||||||
|
COLORABLE_ICON = ""
|
||||||
|
|
||||||
|
def subfetcher(self):
|
||||||
|
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)
|
||||||
|
ColorCountsSection.__init__(self, theme)
|
||||||
|
|
||||||
|
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||||
|
assert os.path.isdir(self.dir)
|
||||||
|
|
||||||
|
# Fetching account list
|
||||||
|
self.accounts = sorted(
|
||||||
|
[a for a in os.listdir(self.dir) if not a.startswith(".")]
|
||||||
|
)
|
||||||
|
# Fetching colors
|
||||||
|
self.colors = dict()
|
||||||
|
for account in self.accounts:
|
||||||
|
filename = os.path.join(self.dir, account, "color")
|
||||||
|
with open(filename, "r") as f:
|
||||||
|
color = f.read().strip()
|
||||||
|
self.colors[account] = color
|
||||||
|
|
||||||
|
self.addPath(os.path.join(self.dir, ".notmuch", "xapian"))
|
||||||
|
|
||||||
|
|
||||||
|
class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||||
|
# TODO OPT/UX Maybe we could get more data from the todoman python module
|
||||||
|
# TODO OPT Specific callback for specific directory
|
||||||
|
COLORABLE_ICON = ""
|
||||||
|
|
||||||
|
def updateCalendarList(self):
|
||||||
|
calendars = sorted(os.listdir(self.dir))
|
||||||
|
for calendar in calendars:
|
||||||
|
# If the calendar wasn't in the list
|
||||||
|
if calendar not in self.calendars:
|
||||||
|
self.addPath(os.path.join(self.dir, calendar), refresh=False)
|
||||||
|
|
||||||
|
# Fetching name
|
||||||
|
path = os.path.join(self.dir, calendar, "displayname")
|
||||||
|
with open(path, "r") as f:
|
||||||
|
self.names[calendar] = f.read().strip()
|
||||||
|
|
||||||
|
# Fetching color
|
||||||
|
path = os.path.join(self.dir, calendar, "color")
|
||||||
|
with open(path, "r") as f:
|
||||||
|
self.colors[calendar] = f.read().strip()
|
||||||
|
self.calendars = calendars
|
||||||
|
|
||||||
|
def __init__(self, dir, theme=None):
|
||||||
|
"""
|
||||||
|
:parm str dir: [main]path value in todoman.conf
|
||||||
|
"""
|
||||||
|
InotifyUpdater.__init__(self)
|
||||||
|
ColorCountsSection.__init__(self, theme=theme)
|
||||||
|
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||||
|
assert os.path.isdir(self.dir)
|
||||||
|
|
||||||
|
self.calendars = []
|
||||||
|
self.colors = dict()
|
||||||
|
self.names = dict()
|
||||||
|
self.updateCalendarList()
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
def countUndone(self, calendar):
|
||||||
|
cmd = ["todo", "--porcelain", "list"]
|
||||||
|
if calendar:
|
||||||
|
cmd.append(self.names[calendar])
|
||||||
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
data = json.loads(proc.stdout)
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def subfetcher(self):
|
||||||
|
counts = []
|
||||||
|
|
||||||
|
# TODO This an ugly optimisation that cuts on features, but todoman
|
||||||
|
# calls are very expensive so we keep that in the meanwhile
|
||||||
|
if self.state < 2:
|
||||||
|
c = self.countUndone(None)
|
||||||
|
if c > 0:
|
||||||
|
counts.append((c, "#00000"))
|
||||||
|
counts.append((0, "#FFFFF"))
|
||||||
|
return counts
|
||||||
|
# Optimisation ends here
|
||||||
|
|
||||||
|
for calendar in self.calendars:
|
||||||
|
c = self.countUndone(calendar)
|
||||||
|
if c <= 0:
|
||||||
|
continue
|
||||||
|
counts.append((c, self.colors[calendar]))
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.updateText(e.container.name)
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
I3Updater.__init__(self)
|
||||||
|
Section.__init__(self, theme=theme)
|
||||||
|
self.on("window", self.on_window)
|
||||||
|
|
||||||
|
|
||||||
|
class I3WorkspacesProviderSection(Section):
|
||||||
|
def selectTheme(self):
|
||||||
|
if self.urgent:
|
||||||
|
return self.parent.themeUrgent
|
||||||
|
elif self.focused:
|
||||||
|
return self.parent.themeFocus
|
||||||
|
else:
|
||||||
|
return self.parent.themeNormal
|
||||||
|
|
||||||
|
# TODO On mode change the state (shown / hidden) gets overriden so every
|
||||||
|
# tab is shown
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
def switchTo(self):
|
||||||
|
self.parent.i3.command("workspace {}".format(self.shortName))
|
||||||
|
|
||||||
|
def __init__(self, name, parent):
|
||||||
|
Section.__init__(self)
|
||||||
|
self.parent = parent
|
||||||
|
self.setName(name)
|
||||||
|
self.setDecorators(clickLeft=self.switchTo)
|
||||||
|
self.tempText = None
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
self.updateTheme(self.parent.themeNormal)
|
||||||
|
self.updateText(None)
|
||||||
|
|
||||||
|
def tempShow(self):
|
||||||
|
self.updateText(self.tempText)
|
||||||
|
|
||||||
|
def tempEmpty(self):
|
||||||
|
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]
|
||||||
|
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)
|
||||||
|
self.sections[workspace.num] = section
|
||||||
|
section.focused = workspace.focused
|
||||||
|
section.urgent = workspace.urgent
|
||||||
|
section.show()
|
||||||
|
|
||||||
|
def on_workspace_empty(self, i3, e):
|
||||||
|
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):
|
||||||
|
if e.change == "default":
|
||||||
|
self.modeSection.updateText(None)
|
||||||
|
for section in self.sections.values():
|
||||||
|
section.tempShow()
|
||||||
|
else:
|
||||||
|
self.modeSection.updateText(e.change)
|
||||||
|
for section in self.sections.values():
|
||||||
|
section.tempEmpty()
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, theme=0, themeFocus=3, themeUrgent=1, themeMode=2, customNames=dict()
|
||||||
|
):
|
||||||
|
I3Updater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
self.themeNormal = theme
|
||||||
|
self.themeFocus = themeFocus
|
||||||
|
self.themeUrgent = themeUrgent
|
||||||
|
self.customNames = customNames
|
||||||
|
|
||||||
|
self.sections = dict()
|
||||||
|
self.on("workspace::init", self.on_workspace_init)
|
||||||
|
self.on("workspace::focus", self.on_workspace_focus)
|
||||||
|
self.on("workspace::empty", self.on_workspace_empty)
|
||||||
|
self.on("workspace::urgent", self.on_workspace_urgent)
|
||||||
|
self.on("workspace::rename", self.on_workspace_rename)
|
||||||
|
# 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):
|
||||||
|
self.mpd.connect("localhost", 6600)
|
||||||
|
|
||||||
|
def __init__(self, theme=None):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
Section.__init__(self, theme)
|
||||||
|
|
||||||
|
self.mpd = mpd.MPDClient()
|
||||||
|
self.connect()
|
||||||
|
self.refreshData()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
stat = self.mpd.status()
|
||||||
|
if not len(stat) or stat["state"] == "stop":
|
||||||
|
return None
|
||||||
|
|
||||||
|
cur = self.mpd.currentsong()
|
||||||
|
if not len(cur):
|
||||||
|
return None
|
||||||
|
|
||||||
|
infos = []
|
||||||
|
|
||||||
|
def tryAdd(field):
|
||||||
|
if field in cur:
|
||||||
|
infos.append(cur[field])
|
||||||
|
|
||||||
|
tryAdd("title")
|
||||||
|
tryAdd("album")
|
||||||
|
tryAdd("artist")
|
||||||
|
|
||||||
|
infosStr = " - ".join(infos)
|
||||||
|
if len(infosStr) > MpdProvider.MAX_LENGTH:
|
||||||
|
infosStr = infosStr[: MpdProvider.MAX_LENGTH - 1] + "…"
|
||||||
|
|
||||||
|
return " {}".format(infosStr)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
try:
|
||||||
|
self.mpd.idle("player")
|
||||||
|
self.refreshData()
|
||||||
|
except mpd.base.ConnectionError as e:
|
||||||
|
log.warn(e, exc_info=True)
|
||||||
|
self.connect()
|
||||||
|
except BaseException as e:
|
||||||
|
log.error(e, exc_info=True)
|
271
hm/frobar/frobar/updaters.py
Normal file
271
hm/frobar/frobar/updaters.py
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
import i3ipc
|
||||||
|
import pyinotify
|
||||||
|
|
||||||
|
from frobar.display import Text
|
||||||
|
from frobar.notbusy import notBusy
|
||||||
|
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
# TODO Sync bar update with PeriodicUpdater updates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Updater:
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
PeriodicUpdater.init()
|
||||||
|
InotifyUpdater.init()
|
||||||
|
notBusy.set()
|
||||||
|
|
||||||
|
def updateText(self, text):
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
return "{} refreshed".format(self)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
def refreshData(self):
|
||||||
|
# TODO OPTI Maybe discard the refresh if there's already another one?
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
data = self.fetcher()
|
||||||
|
except BaseException as e:
|
||||||
|
log.error(e, exc_info=True)
|
||||||
|
data = ""
|
||||||
|
self.updateText(data)
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicUpdaterThread(threading.Thread):
|
||||||
|
def run(self):
|
||||||
|
# TODO Sync with system clock
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
notBusy.set()
|
||||||
|
if PeriodicUpdater.intervalsChanged.wait(
|
||||||
|
timeout=PeriodicUpdater.intervalStep
|
||||||
|
):
|
||||||
|
# ↑ sleeps here
|
||||||
|
notBusy.clear()
|
||||||
|
PeriodicUpdater.intervalsChanged.clear()
|
||||||
|
counter = 0
|
||||||
|
for providerList in PeriodicUpdater.intervals.copy().values():
|
||||||
|
for provider in providerList.copy():
|
||||||
|
provider.refreshData()
|
||||||
|
else:
|
||||||
|
notBusy.clear()
|
||||||
|
counter += PeriodicUpdater.intervalStep
|
||||||
|
counter = counter % PeriodicUpdater.intervalLoop
|
||||||
|
for interval in PeriodicUpdater.intervals.keys():
|
||||||
|
if counter % interval == 0:
|
||||||
|
for provider in PeriodicUpdater.intervals[interval]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||||
|
"""
|
||||||
|
|
||||||
|
intervals = dict()
|
||||||
|
intervalStep = None
|
||||||
|
intervalLoop = None
|
||||||
|
updateThread = PeriodicUpdaterThread(daemon=True)
|
||||||
|
intervalsChanged = threading.Event()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gcds(*args):
|
||||||
|
return functools.reduce(math.gcd, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lcm(a, b):
|
||||||
|
"""Return lowest common multiple."""
|
||||||
|
return a * b // math.gcd(a, b)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lcms(*args):
|
||||||
|
"""Return lowest common multiple."""
|
||||||
|
return functools.reduce(PeriodicUpdater.lcm, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateIntervals():
|
||||||
|
intervalsList = list(PeriodicUpdater.intervals.keys())
|
||||||
|
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
||||||
|
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
||||||
|
PeriodicUpdater.intervalsChanged.set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
PeriodicUpdater.updateThread.start()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Updater.__init__(self)
|
||||||
|
self.interval = None
|
||||||
|
|
||||||
|
def changeInterval(self, interval):
|
||||||
|
assert isinstance(interval, int)
|
||||||
|
|
||||||
|
if self.interval is not None:
|
||||||
|
PeriodicUpdater.intervals[self.interval].remove(self)
|
||||||
|
|
||||||
|
self.interval = interval
|
||||||
|
|
||||||
|
if interval not in PeriodicUpdater.intervals:
|
||||||
|
PeriodicUpdater.intervals[interval] = set()
|
||||||
|
PeriodicUpdater.intervals[interval].add(self)
|
||||||
|
|
||||||
|
PeriodicUpdater.updateIntervals()
|
||||||
|
|
||||||
|
|
||||||
|
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
||||||
|
def process_default(self, event):
|
||||||
|
# DEBUG
|
||||||
|
# from pprint import pprint
|
||||||
|
# pprint(event.__dict__)
|
||||||
|
# return
|
||||||
|
|
||||||
|
assert event.path in InotifyUpdater.paths
|
||||||
|
|
||||||
|
if 0 in InotifyUpdater.paths[event.path]:
|
||||||
|
for provider in InotifyUpdater.paths[event.path][0]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
if event.name in InotifyUpdater.paths[event.path]:
|
||||||
|
for provider in InotifyUpdater.paths[event.path][event.name]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class InotifyUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||||
|
"""
|
||||||
|
|
||||||
|
wm = pyinotify.WatchManager()
|
||||||
|
paths = dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
notifier = pyinotify.ThreadedNotifier(
|
||||||
|
InotifyUpdater.wm, InotifyUpdaterEventHandler()
|
||||||
|
)
|
||||||
|
notifier.start()
|
||||||
|
|
||||||
|
# TODO Mask for folders
|
||||||
|
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
||||||
|
|
||||||
|
def addPath(self, path, refresh=True):
|
||||||
|
path = os.path.realpath(os.path.expanduser(path))
|
||||||
|
|
||||||
|
# Detect if file or folder
|
||||||
|
if os.path.isdir(path):
|
||||||
|
self.dirpath = path
|
||||||
|
# 0: Directory watcher
|
||||||
|
self.filename = 0
|
||||||
|
elif os.path.isfile(path):
|
||||||
|
self.dirpath = os.path.dirname(path)
|
||||||
|
self.filename = os.path.basename(path)
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError("No such file or directory: '{}'".format(path))
|
||||||
|
|
||||||
|
# Register watch action
|
||||||
|
if self.dirpath not in InotifyUpdater.paths:
|
||||||
|
InotifyUpdater.paths[self.dirpath] = dict()
|
||||||
|
if self.filename not in InotifyUpdater.paths[self.dirpath]:
|
||||||
|
InotifyUpdater.paths[self.dirpath][self.filename] = set()
|
||||||
|
InotifyUpdater.paths[self.dirpath][self.filename].add(self)
|
||||||
|
|
||||||
|
# Add watch
|
||||||
|
InotifyUpdater.wm.add_watch(self.dirpath, InotifyUpdater.MASK)
|
||||||
|
|
||||||
|
if refresh:
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedUpdaterThread(threading.Thread):
|
||||||
|
def __init__(self, updater, *args, **kwargs):
|
||||||
|
self.updater = updater
|
||||||
|
threading.Thread.__init__(self, *args, **kwargs)
|
||||||
|
self.looping = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
while self.looping:
|
||||||
|
self.updater.loop()
|
||||||
|
except BaseException as e:
|
||||||
|
log.error("Error with {}".format(self.updater))
|
||||||
|
log.error(e, exc_info=True)
|
||||||
|
self.updater.updateText("")
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Must implement loop(), and call start()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Updater.__init__(self)
|
||||||
|
self.thread = ThreadedUpdaterThread(self, daemon=True)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.refreshData()
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
class I3Updater(ThreadedUpdater):
|
||||||
|
# TODO OPTI One i3 connection for all
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
self.i3 = i3ipc.Connection()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def on(self, event, function):
|
||||||
|
self.i3.on(event, function)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
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] = ""
|
|
@ -2,17 +2,19 @@ from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="frobar",
|
name="frobar",
|
||||||
version="3.0",
|
version="2.0",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
"coloredlogs",
|
||||||
|
"notmuch",
|
||||||
"i3ipc",
|
"i3ipc",
|
||||||
|
"python-mpd2",
|
||||||
"psutil",
|
"psutil",
|
||||||
"pulsectl-asyncio",
|
"pulsectl",
|
||||||
"pygobject3",
|
"pyinotify",
|
||||||
"rich",
|
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"frobar = frobar:main",
|
"frobar = frobar:run",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
|
@ -1,35 +1,15 @@
|
||||||
{
|
{ pkgs, lib, config, ... }:
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
config = lib.mkIf config.frogeye.gaming {
|
config = lib.mkIf config.frogeye.gaming {
|
||||||
# Using config.nixpkgs.<something> creates an infinite recursion,
|
# Using config.nixpkgs.<something> creates an infinite recursion,
|
||||||
# but the above might not be correct in case of cross-compiling?
|
# but the above might not be correct in case of cross-compiling?
|
||||||
home = {
|
home.packages = with pkgs; [
|
||||||
packages = with pkgs; [
|
# gaming
|
||||||
# gaming
|
yuzu-mainline
|
||||||
dolphin-emu
|
minecraft
|
||||||
ryujinx
|
# TODO factorio
|
||||||
prismlauncher
|
|
||||||
# TODO factorio
|
|
||||||
|
|
||||||
steam # Common pitfall: https://github.com/NixOS/nixpkgs/issues/86506#issuecomment-623746883
|
steam # Common pitfall: https://github.com/NixOS/nixpkgs/issues/86506#issuecomment-623746883
|
||||||
# itch # butler-15.21.0 is broken
|
];
|
||||||
(pkgs.python3Packages.ds4drv.overrideAttrs (old: {
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "TheDrHax";
|
|
||||||
repo = "ds4drv-cemuhook";
|
|
||||||
rev = "a58f63b70f8d8efa33e5e82a8888a1e08754aeed";
|
|
||||||
sha256 = "sha256-oMvHw5zeO0skoiqLU+EdjUabTvkipeBh+m8RHJcWZP8=";
|
|
||||||
};
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
sessionVariables = {
|
|
||||||
BOOT9_PATH = "${config.xdg.dataHome}/citra-emu/sysdata/boot9.bin";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.programs.git;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
home.packages = [
|
|
||||||
pkgs.jjui
|
|
||||||
pkgs.lazyjj
|
|
||||||
(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;
|
|
||||||
# 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
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 = [
|
|
||||||
{
|
|
||||||
# Always install my own public key
|
|
||||||
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;
|
|
||||||
# gnome3 is nicer, but requires gcr as a dbus package.
|
|
||||||
# Which is in my NixOS config, and on non-NixOS too.
|
|
||||||
# It will fall back to ncurses when running in non-graphics mode.
|
|
||||||
pinentryPackage = pkgs.pinentry-gnome3;
|
|
||||||
# If inactive, the key will be forgotten after this time
|
|
||||||
defaultCacheTtl = 3600;
|
|
||||||
defaultCacheTtlSsh = defaultCacheTtl;
|
|
||||||
# If active, the key will be forgotten adfter this time
|
|
||||||
maxCacheTtl = 3 * 3600;
|
|
||||||
maxCacheTtlSsh = maxCacheTtl;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
{ 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
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{ 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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
{
|
|
||||||
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`.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
programs.powerline-go = {
|
|
||||||
enable = true;
|
|
||||||
modules = [
|
|
||||||
"user"
|
|
||||||
"host"
|
|
||||||
"direnv"
|
|
||||||
"cwd"
|
|
||||||
"perms"
|
|
||||||
"nix-shell"
|
|
||||||
"venv"
|
|
||||||
"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"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
{ 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
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
263
hm/scripts/jlab
263
hm/scripts/jlab
|
@ -1,263 +0,0 @@
|
||||||
#!/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.
|
|
||||||
mr = glab_get_mr(sys.argv[3])
|
|
||||||
jj.run("git", "fetch")
|
|
||||||
jj.run("new", mr.source_branch)
|
|
||||||
# If there's no commit on the branch, add one with the MR title
|
|
||||||
# so jj has a current bookmark.
|
|
||||||
if len(jj.log(f"{mr.source_branch} | {mr.target_branch}")) == 1:
|
|
||||||
title = re.sub(r"^(WIP|Draft): ", "", mr.title)
|
|
||||||
jj.run("describe", "-m", title)
|
|
||||||
jj.run("bookmark", "move", 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(ancestors(@::) ~ empty())")
|
|
||||||
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
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue