nix: Make nix the root

Which means now I'll have to think about real prefixes in commit names.
This commit is contained in:
Geoffrey Frogeye 2023-11-26 23:58:22 +01:00
parent 550eed06e0
commit ee178b7d57
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8
190 changed files with 5 additions and 6 deletions

53
hm/batteryNotify.sh Executable file
View file

@ -0,0 +1,53 @@
BATT="/sys/class/power_supply/BAT0"
LOW=10
CRIT=3
LASTSTATE="$HOME/.cache/batteryState"
function setState() { # state [...notify-send arguments]
state="$1"
last="$(cat "$LASTSTATE" 2> /dev/null)"
shift
echo "Battery state: $state"
if [ "$state" != "$last" ]
then
notify-send "$@"
echo "$state" > "$LASTSTATE"
fi
}
function computeState() {
acpiStatus="$(cat "$BATT/status")"
acpiCapacity="$(cat "$BATT/capacity")"
if [ "$acpiStatus" == "Discharging" ]
then
if [ "$acpiCapacity" -le $CRIT ]
then
setState "CRIT" -u critical -i battery-caution "Battery level is critical" "$acpiCapacity %"
elif [ "$acpiCapacity" -le $LOW ]
then
setState "LOW" -u critical -i battery-low "Battery level is low" "$acpiCapacity %"
else
setState "DISCHARGING" -i battery-good "Battery is discharging" "$acpiCapacity %"
fi
elif [ "$acpiStatus" == "Charging" ]
then
setState "CHARGING" -u normal -i battery-good-charging "Battery is charging" "$acpiCapacity %"
elif [ "$acpiStatus" == "Full" ]
then
setState "FULL" -u low -i battery-full-charged "Battery is full" "$acpiCapacity %"
fi
}
if [ "$1" == "-d" ]
then
while true
do
computeState
sleep 10
done
else
computeState
fi

514
hm/common.nix Normal file
View file

@ -0,0 +1,514 @@
{ 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
{
programs =
let
commonRc = lib.strings.concatLines ([
''
# Colored ls
# TODO Doesn't allow completion. Check out lsd instead
_colored_ls() {
\ls -lh --color=always $@ | awk '
BEGIN {
FPAT = "([[:space:]]*[^[:space:]]+)";
OFS = "";
}
{
$1 = "\033[36m" $1 "\033[0m";
$2 = "\033[31m" $2 "\033[0m";
$3 = "\033[32m" $3 "\033[0m";
$4 = "\033[32m" $4 "\033[0m";
$5 = "\033[31m" $5 "\033[0m";
$6 = "\033[34m" $6 "\033[0m";
$7 = "\033[34m" $7 "\033[0m";
print
}
'
}
alias ll="_colored_ls"
alias la="_colored_ls -a"
''
] ++ map (d: "mkdir -p ${d}") (builtins.attrValues direnv));
# TODO Those directory creations should probably done on home-manager activation
commonSessionVariables = {
TIME_STYLE = "+%Y-%m-%d %H:%M:%S";
# Less colors
LESS = "-R";
LESS_TERMCAP_mb = "$'\E[1;31m'"; # begin blink
LESS_TERMCAP_md = "$'\E[1;36m'"; # begin bold
LESS_TERMCAP_me = "$'\E[0m'"; # reset bold/blink
LESS_TERMCAP_so = "$'\E[01;44;33m'"; # begin reverse video
LESS_TERMCAP_se = "$'\E[0m'"; # reset reverse video
LESS_TERMCAP_us = "$'\E[1;32m'"; # begin underline
LESS_TERMCAP_ue = "$'\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";
# n = "$HOME/.config/i3/terminal & disown"; # Not used anymore since alacritty daemon mode doesn't preserve environment variables
x = "startx ${config.xdg.configHome}/xinitrc; logout";
nx = "nvidia-xrun ${config.xdg.configHome}/xinitrc; sudo systemctl start nvidia-xrun-pm; logout";
# TODO Put in display.nix
# Was also thinking of not storing the config in .config and use nix-store instead,
# but maybe it's a bad idea as home-manager switch doesn't replace aliases in running shells
# FIXME Is it still relevant with NixOS?
# 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.optionalAttrs config.frogeye.desktop.xorg {
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'';
# FIXME ALSA lib dlmisc.c:337:(snd_dlobj_cache_get0) Cannot open shared library libasound_module_pcm_pulse.so (/nix/store/9b06fxbvm07iy9f9dvi5vk2iy9pk8hyz-alsa-lib-1.2.8/lib/alsa-lib/libasound_module_pcm_pulse.so: cannot open shared object file: No such file or directory)
} // 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.cacheHome}/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;
};
zsh = {
enable = true;
enableAutosuggestions = true;
enableCompletion = true;
enableSyntaxHighlighting = true;
# syntaxHighlighting.enable = true; # 23.11 syntax
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;
};
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'';
};
# neovim = {
# enable = true;
# defaultEditor = true;
# vimAlias = true;
# viAlias = true;
# vimdiffAlias = true;
# };
# FIXME Still want this despite using nixvim
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 = true;
enableZshIntegration = true;
};
less.enable = true;
git = {
enable = true;
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;
signing = {
key = "0x8312C8CAC1BAC289"; # FIXME Only in extension
# TODO signByDefault?
};
userEmail = "geoffrey@frogeye.fr";
userName = "Geoffrey Frogeye Preud'homme";
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";
};
};
services = {
gpg-agent = {
enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
enableSshSupport = true;
pinentryFlavor = "gtk2"; # Falls back to curses when needed
sshKeys = [ "72A5F2913026776593947CF00DFF330E820E731D" ]; # TODO This seems to prevent other keys from being added?
# FIXME For g extension, as base is not supposed to have private keys
};
};
xdg = {
configFile = {
"ccache.conf" = {
text = "ccache_dir = ${config.xdg.cacheHome}/ccache";
};
"gdbinit" = {
text = ''
define hook-quit
set confirm off
end
'';
};
"iftoprc" = {
text = ''
port-resolution: no
promiscuous: no
port-display: on
link-local: yes
use-bytes: yes
show-totals: yes
log-scale: yes
'';
};
"pythonstartup.py" = {
text = (builtins.readFile ./pythonstartup.py);
};
"screenrc" = {
text = (builtins.readFile ./screenrc);
};
};
};
home = {
stateVersion = "23.05";
language = {
base = "en_US.UTF-8";
# time = "en_DK.UTF-8"; # FIXME Disabled because complaints during nixos-rebuild switch
};
packages = with pkgs; [
# dotfiles dependencies
coreutils
bash
gnugrep
gnused
gnutar
openssl
wget
curl
python3Packages.pip
rename
# shell
zsh-completions
nix-zsh-completions
zsh-history-substring-search
powerline-go
# terminal essentials
moreutils
man
# nodePackages.insect # FIXME Don't install on aarch64
# TODO Use whatever replaces insect
translate-shell
unzip
unrar
p7zip
# remote
openssh
rsync
borgbackup
# cleanup
ncdu
jdupes
duperemove
# local monitoring
htop
iotop
iftop
lsof
strace
pv
progress
speedtest-cli
# multimedia toolbox
sox
imagemagick
# password
pass
pwgen
# Mail
isync
msmtp
notmuch
neomutt
lynx
# Organisation
vdirsyncer
khard
khal
todoman
syncthing
# TODO Lots of redundancy with other way things are defined here
];
sessionVariables = {
# Favourite commands
PAGER = "${pkgs.coreutils}/bin/less";
EDITOR = "${pkgs.neovim}/bin/nvim";
# Extra config
BOOT9_PATH = "${config.xdg.dataHome}/citra-emu/sysdata/boot9.bin";
CCACHE_CONFIGPATH = "${config.xdg.configHome}/ccache.conf";
# INPUTRC = "${config.xdg.configHome}/inputrc"; # UPST Will use programs.readline, but doesn't allow path setting
LESSHISTFILE = "${config.xdg.stateHome}/lesshst";
NODE_REPL_HISTORY = "${config.xdg.cacheHome}/node_repl_history";
PYTHONSTARTUP = "${config.xdg.configHome}/pythonstartup.py";
# TODO I think we're not using the urxvt daemon on purpose?
# TODO this should be desktop only, as a few things are too.
SCREENRC = "${config.xdg.configHome}/screenrc";
SQLITE_HISTFILE = "${config.xdg.stateHome}/sqlite_history";
YARN_DISABLE_SELF_UPDATE_CHECK = "true"; # This also disable the creation of a ~/.yarnrc file
} // lib.optionalAttrs config.frogeye.desktop.xorg {
# Favourite commands
VISUAL = "${pkgs.neovim}/bin/nvim";
BROWSER = "${config.programs.qutebrowser.package}/bin/qutebrowser";
# Extra config
RXVT_SOCKET = "${config.xdg.stateHome}/urxvtd"; # Used to want -$HOME suffix, hopefullt this isn't needed
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs
} // direnv;
# TODO Session variables only get reloaded on login I think.
sessionPath = [
"${config.home.homeDirectory}/.local/bin"
"${config.home.sessionVariables.GOPATH}"
"${config.frogeye.dotfiles.path}/scripts"
];
file = {
".face" = {
source = pkgs.runCommand "face.png" { } "${pkgs.inkscape}/bin/inkscape ${./face.svg} -w 1024 -o $out";
};
};
};
}

656
hm/desktop.nix Normal file
View file

@ -0,0 +1,656 @@
{ pkgs, config, lib, ... }:
{
config = lib.mkIf config.frogeye.desktop.xorg {
xsession = {
enable = true;
windowManager = {
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.d}\"/></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?
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";
in
{
modifier = "Mod1"; # FIXME Mod1 for VM, Mod4 for not VM
terminal = "alacritty";
bars = []; # Using frobar
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
scrot = "${pkgs.scrot}/bin/scrot --exec '${pkgs.coreutils}/bin/mv $f ~/Screenshots/ && ${pkgs.optipng}/bin/optipng ~/Screenshots/$f'";
# TODO nix path and create directory?
in
{
# Compatibility layer for people coming from other backgrounds
# "Mod1+Tab" = "${rofi} -modi window -show window"; # FIXME When no longer using a VM
"Mod1+F2" = "${rofi} -modi drun -show drun";
# "Mod1+F4" = "kill"; # FIXME When no longer using a VM
# kill focused window
"${mod}+z" = "kill";
button2 = "kill";
# Rofi
"${mod}+c" = "exec --no-startup-id ${pkgs.rofi-pass}/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";
# 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}"; # FIXME Doesn't work at least in the VM
"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";
# FIXME Not on aarch64
} // 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 = [ "HDMI-1-0" "eDP1" ]; # FIXME Per computer thing
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 = true; # FIXME Only on computers with a separate one
};
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; # FIXME Salvage stuff from autoconfig.yml
searchEngines = rec {
DEFAULT = ecosia;
ampwhat = "http://www.amp-what.com/unicode/search/{}";
aw = ampwhat;
ddg = duckduckgo;
duckduckgo = "https://duckduckgo.com/?q={}&ia=web";
ecosia = "https://www.ecosia.org/search?q={}";
github = "https://github.com/search?q={}";
google = "https://www.google.fr/search?q={}";
g = google;
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 = {
open_base_url = true;
start_pages = "https://geoffrey.frogeye.fr/blank.html";
};
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;
};
};
};
# Terminal
alacritty = {
# FIXME Emojis
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"; };
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 = {
enable = true;
pass.enable = true;
extraConfig = {
lazy-grab = false;
matching = "regex";
};
};
autorandr = {
enable = true;
hooks.postswitch =
let
frobar = (pkgs.callPackage (import ./frobar) { });
in
{
"background" = "${pkgs.feh}/bin/feh --no-fehbg --bg-fill ${config.stylix.image}";
"frobar" = "${pkgs.i3}/bin/i3-msg exec ${frobar}/bin/frobar_launcher";
};
};
mpv = {
enable = true;
config = {
audio-display = false;
save-position-on-quit = true;
osc = false; # # Required by thumbnail script
};
scripts = with pkgs.mpvScripts; [ thumbnail ];
scriptOpts = {
mpv_thumbnail_script = {
cache_directory = "/tmp/mpv_thumbs_${config.home.username}";
};
};
};
};
xdg = {
mimeApps = {
enable = true;
associations.added = {
"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'';
};
"xinitrc" =
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
{
source = pkgs.writeShellScript "xinitrc" ''
${pkgs.xorg.xrdb}/bin/xrdb ${config.xresources.path}
${wmPrefix}${config.xsession.windowManager.command}
'';
};
"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 = {
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
port = 8601; # FIXME Chose a different one for testing, should revert
startWhenNeeded = true;
};
extraConfig = ''
restore_paused "yes"
'';
};
autorandr.enable = true;
};
home = {
packages = with pkgs; [
# 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
yubikey-touch-detector
# x11-exclusive
numlockx
simplescreenrecorder
trayer
xclip
keynav
xorg.xinit
xorg.xbacklight
# TODO Make this clean. Service?
# organisation
pass
thunderbird
];
sessionVariables = {
MPD_PORT = "${toString config.services.mpd.network.port}";
};
};
};
}

66
hm/dev.nix Normal file
View file

@ -0,0 +1,66 @@
{ pkgs, config, ... }: {
# TODO Maybe should be per-directory dotenv
# Or not, for neovim
# Always on
home.packages = with pkgs; [
# Common
perf-tools
git
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
];
}

57
hm/extra.nix Normal file
View file

@ -0,0 +1,57 @@
{ pkgs, lib, config, ... }:
{
config = lib.mkIf config.frogeye.extra {
programs = {
pandoc.enable = true;
};
home.packages = with pkgs; ([
# android tools
android-tools
# downloading
yt-dlp
megatools
# transmission TODO Collision if both transmissions are active?
# Multimedia toolbox
ffmpeg
# documents
visidata
texlive.combined.scheme-full
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
blender
puddletag
musescore
audacity
# downloading
transmission-qt
# FIXME Below not on aarch64
# wine
wine
# TODO wine-gecko wine-mono lib32-libpulse (?)
# gaming
steam
yuzu-mainline
minecraft
# TODO factorio
]);
};
}

192
hm/face.svg Normal file
View file

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg2"
xml:space="preserve"
width="500"
height="500"
viewBox="0 0 500 500.00002"
enable-background="new"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><linearGradient
id="Gradient_1"
gradientUnits="userSpaceOnUse"
x1="559.79999"
y1="0.0125"
x2="559.79999"
y2="834.88751"
spreadMethod="pad"><stop
offset="0%"
stop-color="#CCE474"
id="stop2" /><stop
offset="21.96078431372549%"
stop-color="#CBE372"
id="stop4-3" /><stop
offset="29.80392156862745%"
stop-color="#C8E26B"
id="stop6-6" /><stop
offset="35.68627450980392%"
stop-color="#C3DE60"
id="stop8" /><stop
offset="40%"
stop-color="#BBD94F"
id="stop10" /><stop
offset="43.92156862745098%"
stop-color="#B1D339"
id="stop12" /><stop
offset="47.450980392156865%"
stop-color="#A4CB1E"
id="stop14" /><stop
offset="49.80392156862745%"
stop-color="#99C405"
id="stop16" /><stop
offset="54.11764705882353%"
stop-color="#A2CC18"
id="stop18-7" /><stop
offset="59.21568627450981%"
stop-color="#AAD329"
id="stop20-5" /><stop
offset="65.49019607843137%"
stop-color="#B0D834"
id="stop22" /><stop
offset="74.90196078431373%"
stop-color="#B3DB3B"
id="stop24-3" /><stop
offset="100%"
stop-color="#B4DC3D"
id="stop26" /></linearGradient><linearGradient
xlink:href="#Gradient_1"
id="linearGradient15891"
gradientUnits="userSpaceOnUse"
x1="559.79999"
y1="0.0125"
x2="559.79999"
y2="834.88751"
spreadMethod="pad"
gradientTransform="translate(-2962.3062,128.19426)" /><filter
style="color-interpolation-filters:sRGB"
id="filter1"
x="0"
y="0"
width="1.0052697"
height="1.0081699"><feComponentTransfer
id="feComponentTransfer1"><feFuncR
id="feFuncR1"
type="discrete"
tableValues="1" /><feFuncG
id="feFuncG1"
type="discrete"
tableValues="1" /><feFuncB
id="feFuncB1"
type="discrete"
tableValues="1" /><feFuncA
id="feFuncA1"
type="identity"
tableValues="1" /></feComponentTransfer><feOffset
dx="2.4500000000000002"
dy="2.4500000000000002"
id="feOffset1"
result="result1" /><feBlend
mode="normal"
id="feBlend1"
in2="result1"
in="SourceGraphic" /></filter></defs><g
transform="translate(559.2858,-691.3027)"
style="display:inline"
id="g61596"><g
style="display:inline"
transform="matrix(0.44656812,0,0,0.59890998,763.58571,614.52588)"
id="g38-5"><g
id="g36"><g
id="use34"><path
style="fill:url(#linearGradient15891);stroke:none"
id="path15887"
d="m -1842.6562,963.04426 v -834.85 h -1119.65 v 834.85 z" /></g></g></g></g><g
transform="translate(559.2858,-691.3027)"
style="display:inline;filter:url(#filter1)"
id="g16328"><g
transform="matrix(0.57741006,0,0,-0.57741006,-278.83618,862.3163)"
id="g16254"><path
id="path16252"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -2.947,15.294 -22.128,72.379 -143.132,56.208 21.015,2.281 100.452,4.552 139.645,-83.752 -9.586,20.819 -37.7,69.634 -93.652,78.608 -71.51,11.466 -109.919,-12.516 -136.312,-22.286 -26.394,-9.772 -106.737,-56.449 -132.625,-65.167 -14.994,-5.05 -44.693,-18.094 -68.81,-38.721 16.273,14.494 39.126,29.005 71.287,42.604 52.334,22.129 161.277,95.281 248.44,95.186 C -31.643,62.588 -4.91,28.398 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-109.48077,846.84748)"
id="g16262"><path
id="path16260"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -7.293,-7.944 -11.633,-18.825 -10.715,-29.12 1.233,-13.799 13.076,-25.783 27.153,-25.487 7.211,0.153 13.912,3.428 18.067,8.137 13.956,15.822 -13.806,41.482 -22.333,11.651 -0.174,-0.603 -0.086,-1.489 0.197,-1.977 0.28,-0.488 0.649,-0.394 0.822,0.208 7.996,27.984 33.536,3.09 19.366,-8.968 -3.861,-3.283 -9.493,-5.549 -15.237,-5.999 -12.719,-0.989 -24.767,8.662 -26.664,21.394 -1.507,10.111 3.291,20.939 11.032,28.634 26.329,26.17 64.196,-2.748 57.46,-35.583 -4.432,-21.636 -21.984,-31.551 -41.565,-35.563 -3.841,-0.787 -7.749,-1.345 -11.638,-1.718 -3.883,-0.371 -7.767,-0.561 -11.561,-0.61 -0.023,0.003 -0.045,0.004 -0.068,0.003 h -0.004 v -0.004 c -0.518,-0.007 -0.21,-2.813 0.173,-2.809 h 0.006 l 0.008,0.001 v -0.006 l 0.006,0.001 0.037,0.005 c 3.808,0.051 7.701,0.241 11.603,0.614 3.928,0.375 7.855,0.935 11.69,1.72 19.902,4.081 37.779,14.33 42.407,36.89 C 67.324,-4.044 28.104,30.604 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-478.09959,997.90223)"
id="g16266"><path
id="path16264"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 2.649,73.746 -37.597,74.47 V 63.625 l 28.439,2.892 c -1.126,-3.375 -2.694,-6.71 -4.7,-10 -2.011,-3.296 -4.178,-6.35 -6.506,-9.161 -2.333,-2.812 -4.698,-5.342 -7.111,-7.59 -2.409,-2.248 -4.578,-4.098 -6.507,-5.542 -0.962,-0.802 -2.734,-1.768 -5.301,-2.893 -2.572,-1.122 -5.626,-1.566 -9.158,-1.327 -3.536,0.243 -7.35,1.49 -11.448,3.736 -4.096,2.254 -8.235,6.267 -12.412,12.05 -6.589,8.355 -11.687,16.749 -15.303,25.187 -3.615,8.434 -6.067,16.626 -7.35,24.582 -1.288,7.952 -1.566,15.543 -0.843,22.773 0.722,7.229 2.206,13.936 4.458,20.123 2.248,6.183 5.02,11.647 8.314,16.388 3.292,4.737 6.868,8.634 10.724,11.689 4.339,5.46 8.835,9.437 13.496,11.929 4.659,2.489 9.318,3.935 13.978,4.338 4.658,0.399 9.317,0.038 13.978,-1.085 4.658,-1.125 9.159,-2.651 13.495,-4.578 6.269,-2.733 10.443,-6.428 12.533,-11.086 2.087,-4.662 2.893,-9.361 2.411,-14.099 -0.484,-4.741 -1.808,-9.157 -3.976,-13.255 -0.426,-0.8 7.949,-2.648 9.279,0.242 2.248,4.898 3.855,10.08 4.819,15.543 0.482,3.534 -0.283,7.51 -2.29,11.931 -2.011,4.416 -5.022,8.593 -9.037,12.531 -4.018,3.935 -8.88,7.23 -14.58,9.881 -5.705,2.652 -12.014,4.015 -18.919,4.097 -6.91,0.079 -14.218,-1.567 -21.931,-4.94 -7.711,-3.374 -15.586,-9.08 -23.618,-17.111 -7.874,-8.036 -13.617,-17.231 -17.232,-27.595 -3.615,-10.362 -5.543,-20.929 -5.783,-31.691 -0.242,-10.765 1.001,-21.369 3.735,-31.813 2.73,-10.445 6.465,-19.802 11.206,-28.075 4.738,-8.277 10.322,-15.142 16.75,-20.605 6.424,-5.461 13.255,-8.596 20.485,-9.401 7.067,-0.802 13.092,-0.558 18.074,0.722 4.98,1.288 9.196,3.296 12.655,6.025 3.451,2.738 6.385,5.99 8.794,9.763 2.412,3.772 4.58,7.832 6.509,12.17 0.321,0.641 0.362,-0.806 0.118,-4.338 C -7.591,42.496 -7.916,37.996 -8.314,32.535 -8.717,27.076 -9.158,21.25 -9.641,15.064 -10.123,8.879 -10.446,3.537 -10.605,-0.963 l -16.387,-2.17 v -8.435 l 40.248,4.34 v 9.399 z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-476.29293,884.2114)"
id="g16270"><path
id="path16268"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 88.931 v -24.1 h -6.99 l 0.723,14.219 -26.992,0.964 c -3.375,0.158 -6.711,0.279 -10.002,0.361 -3.294,0.08 -6.229,0.2 -8.795,0.362 -2.573,0.158 -4.663,0.279 -6.266,0.362 -1.607,0.078 -2.493,0.12 -2.651,0.12 -0.161,0 -0.241,-1.167 -0.241,-3.495 0,-2.331 0.038,-5.422 0.121,-9.278 0.079,-3.856 0.158,-8.236 0.239,-13.135 0.081,-4.903 0.2,-9.922 0.362,-15.062 0.32,-12.05 0.724,-25.629 1.206,-40.73 l 15.182,0.242 -0.482,13.497 7.473,0.722 2.889,-32.295 -11.086,-0.481 v 9.64 l -13.495,-0.964 1.205,-54.466 53.504,2.409 2.409,17.835 h 7.469 l -2.893,-30.124 -90.372,-1.205 v 6.505 l 18.555,4.58 L 18.559,-7.472 0,-6.266 Z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-398.22755,907.86829)"
id="g16274"><path
id="path16272"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -8.035,-13.013 -12.892,-26.592 -14.578,-40.728 -1.689,-14.141 0.361,-28.521 6.142,-43.139 2.734,-6.748 6.426,-12.212 11.089,-16.387 4.657,-4.182 9.88,-6.87 15.666,-8.075 5.783,-1.205 11.808,-0.805 18.073,1.205 6.266,2.008 12.293,5.823 18.075,11.447 6.266,6.104 10.564,13.334 12.893,21.69 2.328,8.354 3.213,17.072 2.651,26.149 -0.565,9.076 -2.33,17.992 -5.302,26.752 -2.973,8.754 -6.627,16.587 -10.964,23.497 -6.108,9.64 -11.568,16.184 -16.389,19.641 -4.82,3.453 -9.362,4.617 -13.616,3.494 C 19.481,24.421 15.462,21.488 11.688,16.75 7.912,12.01 4.015,6.424 0,0 m 25.307,-118.331 c -5.626,-0.321 -10.725,-0.12 -15.305,0.604 -4.577,0.722 -8.797,2.567 -12.652,5.541 -3.855,2.972 -7.352,7.473 -10.484,13.497 -3.133,6.025 -6.066,14.255 -8.796,24.702 -1.927,6.906 -2.771,14.581 -2.529,23.017 0.24,8.434 1.241,16.869 3.012,25.304 1.766,8.435 4.174,16.629 7.23,24.582 3.049,7.954 6.542,14.98 10.485,21.087 3.934,6.105 8.15,11.003 12.65,14.702 4.497,3.694 9.075,5.543 13.738,5.543 6.905,0 13.256,-2.132 19.037,-6.387 5.786,-4.259 10.927,-9.843 15.424,-16.749 4.497,-6.91 8.235,-14.664 11.206,-23.257 2.974,-8.596 5.099,-17.231 6.387,-25.907 2.088,-13.82 2.851,-25.547 2.292,-35.186 -0.565,-9.639 -2.049,-17.634 -4.459,-23.979 -2.411,-6.349 -5.507,-11.29 -9.28,-14.822 -3.777,-3.537 -7.872,-6.188 -12.29,-7.953 -4.42,-1.769 -8.837,-2.891 -13.255,-3.375 -4.42,-0.482 -8.557,-0.804 -12.411,-0.964" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-308.19442,972.57512)"
id="g16278"><path
id="path16276"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 13.495,0.963 0.482,139.54 -16.388,-4.579 0.483,7.471 c 5.782,2.085 12.773,4.537 20.965,7.35 8.195,2.809 16.667,5.219 25.427,7.23 8.755,2.007 17.351,3.292 25.786,3.855 8.435,0.562 15.786,-0.281 22.053,-2.529 2.409,-0.644 4.134,-2.091 5.181,-4.339 1.041,-2.251 1.362,-4.62 0.966,-7.109 -0.404,-2.493 -1.449,-4.699 -3.134,-6.628 -1.688,-1.927 -3.976,-2.891 -6.87,-2.891 -3.215,0 -5.784,0.519 -7.712,1.566 -1.927,1.043 -3.215,2.328 -3.854,3.856 -0.646,1.525 -0.723,3.254 -0.243,5.181 0.482,1.929 1.447,3.774 2.892,5.544 -7.07,-0.324 -13.617,-1.006 -19.642,-2.049 -6.026,-1.047 -11.447,-2.169 -16.267,-3.374 -4.82,-1.205 -8.918,-2.372 -12.293,-3.495 -3.372,-1.126 -6.023,-2.01 -7.953,-2.65 l 0.483,-70.373 20.003,1.206 0.722,11.086 h 7.231 c 0.16,0 -0.479,-10.446 -1.928,-31.331 H 43.378 L 43.86,63.864 23.617,63.141 22.411,0.963 36.873,3.132 36.391,-6.99 0,-8.677 Z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-219.83209,884.2114)"
id="g16282"><path
id="path16280"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 49.885 l 10.47,-9.084 -4.684,0.167 c -3.375,0.158 -6.71,0.279 -10.002,0.361 -3.295,0.08 -6.23,0.2 -8.796,0.362 -2.573,0.158 -4.664,0.279 -6.266,0.362 -1.607,0.078 -2.493,0.12 -2.652,0.12 -0.159,0 -0.24,-1.167 -0.24,-3.495 0,-2.331 0.038,-5.422 0.122,-9.278 0.076,-3.856 0.155,-8.236 0.238,-13.135 0.081,-4.903 0.2,-9.922 0.361,-15.062 0.321,-12.05 0.723,-25.629 1.206,-40.73 l 15.183,0.242 -0.482,13.497 7.471,0.722 2.891,-32.295 -11.085,-0.481 v 9.64 l -13.496,-0.964 1.205,-54.466 53.503,2.409 2.409,17.835 h 7.471 l -2.89,-30.124 -90.376,-1.205 v 6.505 l 18.556,4.58 L 18.558,-7.472 0,-6.266 Z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-159.16119,886.15987)"
id="g16286"><path
id="path16284"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 29.401,8.676 0.483,-9.639 -7.712,-2.411 22.894,-59.526 21.933,54.225 -8.678,2.169 v 5.061 l 30.126,-1.688 -2.652,-9.398 -10.603,1.686 -26.028,-61.454 0.723,-94.954 h 13.496 v -7.953 l -33.982,0.962 v 7.715 l 9.158,-0.724 0.482,97.605 L 12.29,-4.337 3.132,-6.266 Z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-184.16345,869.72649)"
id="g16290"><path
id="path16288"
style="fill:none;stroke:#000000;stroke-width:5.647;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 0,0 c 1.893,-1.663 16.513,-5.652 12.795,-16.931 7.208,6.008 9.155,15.147 18.24,21.18 -2.542,-6.545 -0.932,-20.71 -9.036,-24.252 11.145,-1.657 12.241,4.784 27.421,1.396 -7.338,-5.168 -15.408,-8.757 -24.21,-10.77 8.244,-3.75 11.209,-12.681 13.916,-20.437 -5.719,4.094 -19.832,6.022 -21.26,14.763 1.331,-4.41 1.125,-6.078 -0.617,-5.001 3.59,-8.32 -0.205,-16.921 -3.404,-24.493 -2.058,8.77 -8.713,18.589 -6.417,28.009 -2.685,-8.641 -11.243,-12.684 -18.585,-16.313 5.516,10.125 5.512,15.098 12.813,23.587 -8.9,-1.635 -16.451,4.071 -23.071,8.895 7.009,-0.121 19.761,6.331 25.879,-0.055 C 0.182,-14.278 -1.308,-7.472 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-305.49907,981.53571)"
id="g16294"><path
id="path16292"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 4.755,-1.007 9.469,-2.193 14.142,-3.511 18.184,-5.283 36.631,-12.659 50.298,-26.651 13.577,-13.793 18.948,-35.559 11.741,-53.607 -3.405,-8.812 -11.209,-15.85 -20.306,-18.239 -4.448,-1.356 -9.171,-2.15 -13.873,-1.612 -4.68,0.483 -9.287,2.654 -12.342,6.294 -6.202,7.472 -5.775,18.259 -2.063,26.611 3.757,8.577 11.085,15.316 19.656,18.967 13.895,5.885 29.805,4.483 43.718,-0.444 14.039,-5.036 26.726,-13.3 37.762,-23.199 11.003,-9.955 20.529,-21.65 27.404,-34.873 3.433,-6.603 6.135,-13.624 7.679,-20.959 1.556,-7.374 1.301,-15.12 -0.717,-22.381 -3.793,-14.55 -15.627,-27.205 -30.63,-30.362 -15.067,-3.389 -29.951,1.978 -43.591,7.065 -13.71,5.469 -27.918,9.745 -42.643,11.184 -7.147,0.483 -15.263,0.852 -21.09,-3.774 -2.829,-2.373 -3.799,-6.282 -3.476,-9.914 0.293,-3.682 1.477,-7.252 2.897,-10.672 -1.924,4.724 -3.719,9.79 -2.941,14.925 0.672,5.35 5.812,8.763 10.638,9.733 12.215,2.447 24.69,-0.243 36.711,-2.968 12.161,-3.008 23.569,-8.292 35.511,-11.566 11.773,-3.388 24.812,-4.314 35.771,1.339 10.95,5.406 18.599,16.416 21.09,28.382 2.791,12.091 -0.594,24.631 -5.799,35.877 -5.281,11.297 -12.677,21.57 -21.175,30.663 -16.995,17.892 -39.089,32.508 -63.608,34.736 -8.876,0.628 -18.068,-0.611 -26.043,-4.697 -7.959,-3.998 -14.476,-10.99 -17.419,-19.42 -2.984,-8.196 -2.421,-18.538 4.076,-24.726 6.573,-6.263 16.747,-5.526 24.808,-2.541 8.456,2.659 15.134,9.372 18.084,17.577 3.065,8.171 3.194,17.283 1.317,25.756 -0.948,4.243 -2.427,8.376 -4.499,12.153 -2.107,3.701 -4.737,7.398 -7.56,10.558 -5.797,6.443 -13.188,11.342 -20.911,15.385 -16.578,8.404 -35.09,13.086 -53.696,16.241 -18.649,3.069 -37.696,4.515 -56.594,4.735 -18.966,0.245 -38.159,0.008 -56.93,3.326 -9.339,1.681 -18.627,4.286 -26.987,8.839 -8.35,4.489 -15.53,11.324 -19.688,19.679 -1.085,2.044 -3.255,7.268 -4.193,12.679 -1.099,5.378 -1.149,10.774 -1.314,12.744 0.548,-8.169 2.38,-16.405 6.301,-23.647 3.86,-7.274 9.967,-13.184 17.134,-17.276 14.518,-8.204 31.899,-10.19 48.906,-11.124 C -87.282,6.4 -69.918,7.266 -52.275,6.453 -34.79,5.628 -17.177,3.708 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-359.11142,1021.4165)"
id="g16298"><path
id="path16296"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 3.397,-1.412 6.911,-2.492 10.495,-3.112 13.817,-2.814 28.983,4.758 36.493,16.6 3.674,5.884 5.109,13.63 2.693,20.016 -2.589,6.435 -9.718,10.167 -16.77,10.812 C 26.011,44.98 18.542,43.211 13.303,38.429 10.719,36.058 8.704,32.941 8.271,29.442 7.827,25.944 8.977,22.371 10.779,19.267 14.497,13.021 20.21,8.135 26.107,3.844 c 5.962,-4.275 12.381,-7.919 19.004,-11.087 5.305,-2.511 10.732,-4.793 16.311,-6.534 5.52,-1.641 11.504,-2.247 17.196,-3.632 11.493,-2.481 22.909,-5.584 33.808,-10.138 10.873,-4.554 21.226,-10.576 30.164,-18.351 8.97,-7.728 16.514,-17.027 22.62,-27.121 13.462,-19.712 16.185,-45.646 9.357,-68.252 -3.434,-11.303 -9.591,-22.087 -18.893,-29.615 -9.24,-7.611 -21.479,-10.945 -33.257,-10.132 8.222,-0.491 16.481,1.343 23.432,4.909 8.62,4.291 15.564,11.512 20.362,19.887 4.839,8.399 7.685,17.926 8.945,27.625 1.221,9.714 0.868,19.697 -1.296,29.29 -1.977,9.663 -6.729,18.435 -11.973,26.911 -10.449,16.917 -25.416,30.976 -43.197,39.774 -17.714,9.06 -37.179,13.361 -56.267,17.515 -13.645,4.57 -26.488,11.216 -38.145,19.552 -5.706,4.275 -11.3,9.056 -14.891,15.437 -1.74,3.166 -2.795,6.927 -2.143,10.646 0.644,3.72 2.849,6.944 5.537,9.429 5.525,4.988 13.191,7.011 20.497,6.485 C 40.482,45.91 48.312,42.526 51.883,35.432 55.271,28.342 54.19,20.204 50.949,13.501 47.602,6.759 42.062,1.368 35.701,-2.395 28.977,-6.342 20.977,-8.77 12.849,-7.928 4.939,-7.2 -2.636,-4.423 -9.455,-0.626 c -13.7,7.705 -24.68,19.052 -34.056,31.104 -18.53,24.412 -31.581,52.29 -39.022,80.488 -1.085,3.501 -3.356,17.301 -3.598,20.435 2.238,-12.869 6.646,-25.442 11.748,-37.818 C -69.21,81.217 -63.119,69.065 -56.134,57.36 -49.122,45.684 -41.241,34.39 -32.056,24.283 -22.903,14.246 -12.326,5.164 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-496.15832,937.16902)"
id="g16302"><path
id="path16300"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 0,0 c -0.977,-2.42 -2.724,-3.975 -5.139,-3.018 -2.419,0.957 -3.087,3.466 -2.009,7.53 2.051,6.483 8.06,10.28 14.171,15.72 C 9.03,12.7 11.041,5.17 8.991,-1.316 8.786,-4.605 5.396,-6.064 3.751,-5.978 1.336,-5.021 0.564,-4.154 0,0 m -15.43,17.332 c -3.287,0.177 -4.829,1.911 -4.729,3.555 -0.669,2.508 1.951,4.84 4.466,5.531 6.68,1.284 13.925,-1.586 21.172,-4.454 C -0.632,16.525 -5.969,10.22 -11.775,9.71 c -3.389,-1.463 -6.577,0.359 -7.25,2.872 -0.668,2.506 0.204,3.284 3.595,4.75 m 8.001,22.651 c -1.44,3.377 -2.109,5.888 -0.364,7.443 1.749,1.551 4.934,-0.271 7.246,-2.87 3.858,-4.332 5.868,-11.865 7.002,-20.171 -8.118,2.091 -16.238,4.184 -20.096,8.515 -2.313,2.601 -2.882,6.753 -1.135,8.307 1.746,1.555 4.161,0.598 7.347,-1.224 m 23.428,5.321 c 0.976,2.422 2.724,3.973 5.137,3.018 2.412,-0.956 3.084,-3.466 2.01,-7.53 C 21.094,34.307 15.083,30.512 8.974,25.071 6.963,32.602 4.955,40.135 7.006,46.618 c 0.205,3.289 3.591,4.75 5.236,4.664 2.415,-0.96 3.19,-1.825 3.757,-5.978 M 31.426,27.971 c 3.284,-0.177 4.831,-1.909 4.729,-3.554 0.667,-2.509 -1.95,-4.841 -4.466,-5.529 -6.68,-1.285 -13.927,1.583 -21.173,4.453 6.111,5.438 11.45,11.744 17.257,12.253 3.389,1.463 6.574,-0.36 7.245,-2.87 0.67,-2.509 -0.204,-3.287 -3.592,-4.753 M 23.424,5.32 c 1.439,-3.378 2.113,-5.885 0.368,-7.442 -1.747,-1.552 -4.935,0.27 -7.251,2.87 C 12.684,5.08 10.676,12.612 9.542,20.921 17.659,18.827 25.779,16.736 29.637,12.403 31.95,9.804 32.52,5.649 30.773,4.094 29.027,2.54 26.612,3.499 23.424,5.32" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-235.13854,898.12704)"
id="g16306"><path
id="path16304"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -4.821,2.569 -9.923,4.496 -15.305,5.784 -5.383,1.283 -10.404,2.169 -15.062,2.652 -5.303,0.639 -10.602,0.801 -15.905,0.48 l 1.445,-77.119 c 7.068,0.483 13.817,1.284 20.246,2.41 5.301,0.965 10.602,2.369 15.904,4.217 5.301,1.846 9.236,4.218 11.809,7.11 2.567,2.893 4.737,6.868 6.508,11.929 1.765,5.062 2.73,10.323 2.892,15.786 0.157,5.461 -0.686,10.642 -2.531,15.544 C 8.152,-6.307 4.82,-2.572 0,0 m -66.276,-128.212 11.087,-0.482 -2.65,137.37 h -13.016 l -0.962,9.881 c 17.027,0.964 32.373,0.482 46.031,-1.447 5.782,-0.963 11.481,-2.25 17.109,-3.855 5.62,-1.608 10.685,-3.777 15.184,-6.507 4.495,-2.734 8.232,-6.108 11.208,-10.122 2.969,-4.018 4.614,-8.838 4.936,-14.461 0.484,-7.711 0.244,-14.459 -0.721,-20.242 -0.965,-5.785 -2.33,-10.767 -4.099,-14.943 -1.769,-4.18 -3.936,-7.633 -6.503,-10.363 -2.574,-2.734 -5.146,-4.982 -7.713,-6.747 -6.428,-4.181 -13.738,-6.188 -21.931,-6.025 l 54.463,-73.505 h 11.812 l 1.686,-8.918 -33.018,-2.17 -0.479,6.991 10.602,1.204 -57.117,72.781 -14.46,-1.927 v -46.995 l 11.086,0.965 v -8.918 l -32.052,0.482 z" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-290.17403,883.09827)"
id="g16322"><path
id="path16320"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -1.046,2.248 -2.772,3.694 -5.182,4.338 -6.264,2.248 -13.616,3.092 -22.048,2.53 -8.436,-0.564 -17.033,-1.849 -25.791,-3.855 -8.756,-2.012 -17.23,-4.421 -25.422,-7.23 -8.195,-2.813 -15.183,-5.265 -20.969,-7.351 l -0.482,-7.471 16.388,4.579 -0.481,-139.54 -13.496,-0.962 v -8.677 l 36.39,1.686 0.483,10.122 -14.461,-2.169 1.205,62.179 0.242,9.399 -0.481,70.372 c 1.926,0.64 4.577,1.525 7.953,2.65 3.372,1.123 7.469,2.291 12.289,3.496 4.819,1.205 10.245,2.327 16.266,3.373 6.026,1.043 12.573,1.725 19.643,2.049 -1.445,-1.77 -2.41,-3.614 -2.891,-5.543 -0.481,-1.928 -0.405,-3.657 0.241,-5.182 0.639,-1.528 1.927,-2.812 3.854,-3.855 1.929,-1.047 4.498,-1.567 7.714,-1.567 2.892,0 5.181,0.964 6.869,2.892 1.688,1.928 2.729,4.135 3.132,6.628 C 1.365,-4.62 1.045,-2.252 0,0" /></g><g
transform="matrix(0.57741006,0,0,-0.57741006,-165.14449,937.08189)"
id="g16326"><path
id="path16324"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C 22.756,-46.462 103.04,-47.09 103.04,-47.09 13.691,-47.872 0,0 0,0 m -99.337,-33.74 -0.013,-0.428 c -0.282,-11.702 -7.741,-19.501 -17.459,-20.42 -9.699,-0.923 -17.428,5.868 -17.126,18.404 0.305,12.534 8.49,24.57 18.356,32.178 8.289,-7.974 16.495,-19.297 16.242,-29.734 m -193.03,35.08 c 17.189,9.15 49.441,15.952 73.138,17.77 37.651,3.162 78.074,-3.446 99.464,-21.019 -11.731,-8.605 -24.16,-22.297 -24.482,-35.679 -0.333,-13.793 13.964,-22.856 27.331,-21.592 12.154,1.156 23.305,11.385 23.64,25.162 0.283,11.723 -9.111,23.742 -18.603,32.026 12.931,8.741 31.863,15.95 56.18,18.254 20.639,1.955 39.915,-2.89 45.18,-10.74 2.945,-3.475 1.061,-6.145 2.888,-5.974 l 0.67,0.205 c 1.55,-2.236 4.476,-7.174 7.834,-16.512 4.984,-13.875 27.627,-36.002 78.85,-33.898 0,0 20.259,-1.476 28.273,2.091 0,0 38.35,0.905 46.58,6.699 0,0 -24.95,-7.212 -52.941,6.203 -27.987,13.417 -51.337,41.257 -89.088,39.938 0,0 -18.556,-0.639 -23.477,7.48 l 0.252,-0.946 c -8.283,6.583 -25.672,11.105 -44.92,9.637 -23.692,-2.245 -46.914,-10.287 -59.863,-19.431 -22.557,19.123 -64.225,25.19 -103.672,22.28 -24.898,-1.928 -57.771,-8.386 -78.594,-18.288 z" /></g></g></svg>

After

Width:  |  Height:  |  Size: 22 KiB

94
hm/frobar/.dev/barng.py Executable file
View 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
View 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
View 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
View 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)

3
hm/frobar/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist/*
frobar.egg-info/*
__pycache__

37
hm/frobar/default.nix Normal file
View file

@ -0,0 +1,37 @@
{ 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";
propagatedBuildInputs = with pkgs.python3Packages; [
coloredlogs
notmuch
i3ipc
mpd2
psutil
pulsectl
pyinotify
];
src = ./.;
};
frobar_launcher = pkgs.writeShellApplication
{
name = "frobar_launcher";
runtimeInputs = with pkgs; [ lemonbar-xft wirelesstools ];
text = ''
pidfile=$XDG_RUNTIME_DIR/frobar/$DISPLAY.pid
${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "$pidfile")"
([ -f "$pidfile" ] && ${pkgs.procps}/bin/kill "$(<"$pidfile")") || true
${frobar}/bin/frobar & disown
echo $! > "$pidfile"
'';
};
in
frobar_launcher
# FIXME Doesn't kill the old bar
# FIXME Not using Nerdfont
# TODO Connection with i3 is lost sometimes, more often than with Arch?

View 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
View 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 = ["DejaVuSansMono Nerd Font Mono"]
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

View file

@ -0,0 +1,5 @@
#!/usr/bin/env python3
import threading
notBusy = threading.Event()

View 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)

View 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] = ""

20
hm/frobar/setup.py Normal file
View file

@ -0,0 +1,20 @@
from setuptools import setup
setup(
name="frobar",
version="2.0",
install_requires=[
"coloredlogs",
"notmuch",
"i3ipc",
"python-mpd2",
"psutil",
"pulsectl",
"pyinotify",
],
entry_points={
"console_scripts": [
"frobar = frobar:run",
]
},
)

26
hm/inputrc Normal file
View file

@ -0,0 +1,26 @@
$if mode=vi
# these are for vi-command mode
set keymap vi-command
"k": history-search-backward
"j": history-search-forward
"\e[A": history-search-backward
"\e[B": history-search-forward
Control-l: clear-screen
# these are for vi-insert mode
set keymap vi-insert
"jk": vi-movement-mode
"\e[A": history-search-backward
"\e[B": history-search-forward
Control-l: clear-screen
# Switch between thin cursor and thicc block depending on vi mode
$if term=linux
set vi-ins-mode-string \1\e[?0c\2
set vi-cmd-mode-string \1\e[?8c\2
$else
set vi-ins-mode-string \1\e[6 q\2
set vi-cmd-mode-string \1\e[2 q\2
$endif
$endif

12
hm/loader.nix Normal file
View file

@ -0,0 +1,12 @@
{ ... }:
{
imports = [
../options.nix
./common.nix
./desktop.nix
./dev.nix
./extra.nix
./style.nix
./vim.nix
];
}

47
hm/pythonstartup.py Normal file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env python3
import sys
import os
# From https://github.com/python/cpython/blob/v3.7.0b5/Lib/site.py#L436
# Changing the history file
def register_readline() -> None:
import atexit
try:
import readline
import rlcompleter
except ImportError:
return
# Reading the initialization (config) file may not be enough to set a
# completion key, so we set one first and then read the file.
readline_doc = getattr(readline, '__doc__', '')
if readline_doc is not None and 'libedit' in readline_doc:
readline.parse_and_bind('bind ^I rl_complete')
else:
readline.parse_and_bind('tab: complete')
try:
readline.read_init_file()
except OSError:
# An OSError here could have many causes, but the most likely one
# is that there's no .inputrc file (or .editrc file in the case of
# Mac OS X + libedit) in the expected location. In that case, we
# want to ignore the exception.
pass
if readline.get_current_history_length() == 0:
# If no history was loaded, default to .python_history.
# The guard is necessary to avoid doubling history size at
# each interpreter exit when readline was already configured
# through a PYTHONSTARTUP hook, see:
# http://bugs.python.org/issue5845#msg198636
history = os.path.join(os.path.expanduser('~'),
'.cache/python_history')
try:
readline.read_history_file(history)
except OSError:
pass
atexit.register(readline.write_history_file, history)
sys.__interactivehook__ = register_readline

9
hm/screenrc Normal file
View file

@ -0,0 +1,9 @@
term xterm-256color
hardstatus off
hardstatus alwayslastline
hardstatus alwayslastline '%{= G}[ %{G}%H %{g}][%= %{= w}%?%-Lw%?%{= R}%n*%f %t%?%{= R}(%u)%?%{= w}%+Lw%?%= %{= g}][ %{y}Load: %l %{g}][%{B}%Y-%m-%d %{W}%c:%s %{g}]'
startup_message off
termcapinfo xterm* ti@:te@
vbell off
altscreen on
windowlist string "%4n %h%=%f"

47
hm/style.nix Normal file
View file

@ -0,0 +1,47 @@
{ pkgs, config, ... }:
let
stylix = builtins.fetchGit {
url = "https://github.com/danth/stylix.git";
ref = "release-23.05";
};
in
{
imports = [ (import stylix).homeManagerModules.stylix ];
stylix = {
# FIXME Changeable at runtime
base16Scheme = "${pkgs.base16-schemes}/share/themes/solarized-dark.yaml";
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";
};
# The background is set on some occasions, autorandr + feh do the rest
fonts = {
sizes = {
applications = 10;
terminal = 10;
};
# FIXME Somehow this seems smaller than Arch on the left screen, yet bigger on the right...
monospace = {
package = pkgs.nerdfonts.override {
fonts = [ "DejaVuSansMono" ]; # Choose from https://github.com/NixOS/nixpkgs/blob/6ba3207643fd27ffa25a172911e3d6825814d155/pkgs/data/fonts/nerdfonts/shas.nix
};
name = "DejaVuSansM Nerd Font";
};
};
targets = {
vim.enable = false; # FIXME Not compatible with nixvim for now (there's a MR)
i3.enable = false; # I prefer my own styles
tmux.enable = false; # Using another theme
};
};
# Fix https://nix-community.github.io/home-manager/index.html#_why_do_i_get_an_error_message_about_literal_ca_desrt_dconf_literal_or_literal_dconf_service_literal
# home.packages = [ pkgs.dconf ];
dconf.enable = false; # Otherwise standalone home-manager complains it can't find /etc/dbus-1/session.conf on Arch.
# Symlinking it to /usr/share/dbus-1/session.conf goes further but not much.
}

17
hm/tmux.conf Normal file
View file

@ -0,0 +1,17 @@
# switch windows alt+number
bind-key -n M-1 select-window -t 1
bind-key -n M-2 select-window -t 2
bind-key -n M-3 select-window -t 3
bind-key -n M-4 select-window -t 4
bind-key -n M-5 select-window -t 5
bind-key -n M-6 select-window -t 6
bind-key -n M-7 select-window -t 7
bind-key -n M-8 select-window -t 8
bind-key -n M-9 select-window -t 9
# https://superuser.com/a/1007721
bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e; send-keys -M'"
# Inform tmux that alacritty supports RGB
# (because for some reason terminfo doesn't?)
set -ga terminal-overrides ',alacritty*:RGB'

408
hm/vim.nix Normal file
View file

@ -0,0 +1,408 @@
{ pkgs, lib, config, ... }:
let
nixvim = import (builtins.fetchGit {
url = "https://github.com/nix-community/nixvim";
ref = "nixos-23.05";
});
vim-shot-f = pkgs.vimUtils.buildVimPluginFrom2Nix {
pname = "vim-shot-f";
version = "2016-02-05";
src = pkgs.fetchFromGitHub {
owner = "deris";
repo = "vim-shot-f";
rev = "eea71d2a1038aa87fe175de9150b39dc155e5e7f";
sha256 = "iAPvIs/lhW+w5kFTZKaY97D/kfCGtqKrJVFvZ8cHu+c=";
};
meta.homepage = "https://github.com/deris/vim-shot-f";
};
quick-scope = pkgs.vimUtils.buildVimPluginFrom2Nix rec {
pname = "quick-scope";
version = "2.6.1";
src = pkgs.fetchFromGitHub {
owner = "unblevable";
repo = "quick-scope";
rev = "v${version}";
sha256 = "TcA4jZIdnQd06V+JrXGiCMr0Yhm9gB6OMiTSdzMt/Qw=";
};
meta.homepage = "https://github.com/unblevable/quick-scope";
};
in
{
imports = [
nixvim.homeManagerModules.nixvim
];
programs.nixvim = {
enable = true;
options = {
# From https://www.hillelwayne.com/post/intermediate-vim/
title = true;
number = true;
relativenumber = true;
scrolloff = 10;
lazyredraw = true; # Do not redraw screen in the middle of a macro. Makes them complete faster.
cursorcolumn = true;
ignorecase = true;
smartcase = true;
gdefault = true;
tabstop = 4;
shiftwidth = 4;
expandtab = true;
splitbelow = true;
visualbell = true;
updatetime = 250;
undofile = true;
# From http://stackoverflow.com/a/5004785/2766106
list = true;
listchars = "tab:,trail:·,extends:,precedes:,nbsp:_";
showbreak = "";
wildmode = "longest:full,full";
wildmenu = true;
};
globals = {
netrw_fastbrowse = 0; # Close the file explorer once you select a file
};
colorschemes.base16 = {
# FIXME Dynamic... or use stylix
enable = true;
colorscheme = "solarized-light";
};
plugins = {
# Catches attention when cursor changed position
# TODO Unmapped, do I still want to use it?
specs = {
enable = true;
min_jump = 5;
fader = { builtin = "pulse_fader"; };
resizer = { builtin = "shrink_resizer"; };
};
# Tabline
barbar.enable = true;
# Go to whatever
telescope = {
enable = true;
keymaps = {
gF = "find_files";
gf = "git_files";
gB = "buffers";
gl = "current_buffer_fuzzy_find";
gL = "live_grep";
gT = "tags";
gt = "treesitter";
gm = "marks";
gh = "oldfiles";
gH = "command_history";
gS = "search_history";
gC = "commands";
gr = "lsp_references";
# ga = "lsp_code_actions";
# ge = "lsp_document_diagnostics";
# gE = "lsp_workspace_diagnostics";
# FIXME Above makes nvim crash on startup, action is not provided
gd = "lsp_definitions";
gs = "lsp_document_symbols";
};
defaults = {
vimgrep_arguments = [
"${pkgs.ripgrep}/bin/rg"
"--color=never"
"--no-heading"
"--with-filename"
"--line-number"
"--column"
"--smart-case"
];
};
extensions.fzf-native = {
enable = true;
caseMode = "smart_case";
fuzzy = true;
overrideFileSorter = true;
overrideGenericSorter = true;
};
};
# Surrounding pairs
surround.enable = true; # Change surrounding pairs (e.g. brackets, quotes)
# Language Server
lsp = {
enable = true;
keymaps = {
silent = true;
diagnostic = {
"<Space>e" = "open_float";
"[e" = "goto_prev";
"]e" = "goto_next";
};
lspBuf = {
"gD" = "declaration";
"K" = "hover";
"gi" = "implementation";
"<C-S-k>" = "signature_help";
"<space>wa" = "add_workspace_folder";
"<space>wr" = "remove_workspace_folder";
# "<space>wl" = "list_workspace_folder";
# TODO Full thing was function() print(vim.inspect(vim.lsp.buf.list_workspace_folder())) end but not sure I'm ever really using this
# Also makes nvim crash like this, so uncommented
"<space>D" = "type_definition";
"<space>rn" = "rename";
"<space>f" = "format";
# TODO Full thing was function() vim.lsp.buf.format { async = true } end, so async while this isn't
# Maybe replace this with lsp-format?
};
};
servers = {
# FIXME ansiblels
bashls.enable = true; # Bash
jsonls.enable = true; # JSON
lua-ls.enable = true; # Lua (for Neovim debugging)
# FIXME perlls
pylsp = lib.mkIf config.frogeye.dev.python {
# Python
enable = true;
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"
];
};
# FIXME Somehow no warning is shown
# TODO Could add some, could also remove some
};
};
# FIXME phpactor. Only from 23.11
# phpactor.enable = true; # PHP
rnix-lsp.enable = true; # Nix
# FIXME sqlls
yamlls.enable = true; # YAML
# TODO Check out none-ls
};
# FIXME vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') as onAttach... is it needed?
# FIXME flags = { debounce_text_changes = 150 } is the default value ok? Maybe it was actually ignored
};
nvim-lightbulb = {
# Shows a lightbulb whenever a codeAction is available under the cursor
enable = true;
autocmd.enabled = true;
};
lspkind.enable = true; # Add icons to LSP completions
# Treesitter
treesitter = {
# Allows for better syntax highlighting
enable = true;
incrementalSelection = {
enable = true;
};
# indent = true; # Not very working last time I tried apparently
};
# TODO Investigate https://github.com/nvim-treesitter/nvim-treesitter-textobjects
indent-blankline.enable = true; # 23.11 integrate with rainbow-delimiters and use more of the options
undotree.enable = true; # Navigate edition history
# Git
fugitive.enable = true; # Git basics
gitsigns.enable = true; # Show changed lines in the gutter
# Language-specific
# dap.enable = true; # Debug Adapter Protocol client # 23.11
nvim-colorizer.enable = true; # Display colors of color-codes
};
extraPlugins = with pkgs.vimPlugins; [
nvim-scrollview # Scroll bar
# Status line
feline-nvim # Customizable status line.
# TODO Abandonned. Maybe use lualine?
# Search/replace
vim-abolish # Regex for words, with case in mind
vim-easy-align # Aligning lines around a certain character
# Surrounding pairs
targets-vim # Better interaction with surrounding pairs
# f/F mode
vim-shot-f # Highlight relevant characters for f/F/t/T modes
quick-scope # Highlight relevant characters for f/F modes one per word but always
# Registers
registers-nvim # Show register content when pressing "
# TODO Doesn't work. Didn't work on Arch either
# Tags
vim-gutentags # Generate tags
symbols-outline-nvim # Show a symbol panel on the right
# TODO Fails on startup. Same on Arch. Config issue?
# Language server
lsp_signature-nvim # Show argument definition when typing a function
# Treesitter
nvim-ts-rainbow # Randomly color parenthesis pairs
# 23.11: Replace with plugins.rainbow-delimiters
# Snippets
vim-vsnip
vim-vsnip-integ
friendly-snippets
# Auto-completion
nvim-compe
# TODO Archived. Maybe use nvim-cmp and plugins instead?
# Git
fugitive-gitlab-vim # Open files in GitLab
# TODO Connect it!
# Language-specific
tcomment_vim # Language-aware (un)commenting
] ++ lib.optionals config.frogeye.extra [
vim-LanguageTool # Check grammar for human languages
] ++ lib.optionals config.programs.pandoc.enable [
vim-pandoc # Pandoc-specific stuff because there's no LSP for it
vim-pandoc-syntax
] ++ lib.optionals config.frogeye.dev.c [
nvim-dap # Debug Adapter Protocol client
] ++ lib.optionals config.frogeye.dev.ansible [
ansible-vim # FIXME See if it doesn't require to do ./UltiSnips/generate.sh
];
extraConfigLua = lib.strings.concatMapStringsSep "\n" (f: builtins.readFile f) [
./vim/feline.lua
./vim/symbols-outline-nvim.lua
./vim/lsp_signature-nvim.lua
./vim/nvim-ts-rainbow.lua
];
extraConfigVim = ''
" GENERAL
" Avoid showing message extra message when using completion
set shortmess+=c
command Reload source $MYVIMRC
" PLUGINS
" vim-gutentags
let g:gutentags_cache_dir = expand('~/.cache/nvim/tags')
" nvim-compe
" Copy-pasted because I couldn't be bothered
set completeopt=menuone,noselect
inoremap <silent><expr> <C-Space> compe#complete()
inoremap <silent><expr> <CR> compe#confirm('<CR>')
inoremap <silent><expr> <C-e> compe#close('<C-e>')
inoremap <silent><expr> <C-f> compe#scroll({ 'delta': +4 })
inoremap <silent><expr> <C-d> compe#scroll({ 'delta': -4 })
'' + lib.optionalString config.frogeye.extra ''
" languagetool
let g:languagetool_cmd = "${pkgs.languagetool}/bin/languagetool-commandline"
" TODO Doesn't work
'' + lib.optionalString config.programs.pandoc.enable ''
" vim-pandox
let g:pandoc#modules#disabled = ["folding"]
let g:pandoc#spell#enabled = 0
let g:pandoc#syntax#conceal#use = 0
'';
autoCmd = [
# Turn off relativenumber only for insert mode
{ event = "InsertEnter *"; command = "set norelativenumber"; }
{ event = "InsertLeave *"; command = "set relativenumber"; }
# Additional extensions
{ event = "BufNewFile,BufRead"; pattern = "*.jinja"; command = "set filetype=jinja2"; } # TODO Probably GH-specific?
# vim-easy-align: Align Markdown tables
{ event = "FileType markdown"; command = "vmap <Bar> :EasyAlign*<Bar><Enter>"; }
];
userCommands = {
# Reload = { command = "source $MYVIRMC"; };
# TODO Is not working, options is set to nil even though it shouldn't
};
# 23.11: Use keymaps, seems better
maps = {
# GENERAL
# Allow saving of files as sudo when I forgot to start vim using sudo.
# From https://stackoverflow.com/a/7078429
command."w!!" = { action = "w !sudo tee > /dev/null %"; };
insert."jk" = { action = "<Esc>"; };
visual."<Enter>" = { action = "<Esc>"; };
normal."<Enter>" = { action = "o<Esc>"; };
# normal."<C-H>" = { action = ":bp<CR>"; };
# normal."<C-L>" = { action = ":bn<CR>"; };
normal."<C-K>" = { action = "kkkkkkkkkkkkkkkkkkkkk"; };
normal."<C-J>" = { action = "jjjjjjjjjjjjjjjjjjjjj"; };
# \s to replace globally the word under the cursor
normal."<Leader>s" = { action = ":%s/\\<<C-r><C-w>\\>/"; };
# PLUGINS
# barbar
normal."<C-H>" = { action = "<Cmd>BufferPrevious<CR>"; silent = true; };
normal."<C-L>" = { action = "<Cmd>BufferNext<CR>"; silent = true; };
# TODO https://www.reddit.com/r/neovim/comments/mbj8m5/how_to_setup_ctrlshiftkey_mappings_in_neovim_and/
normal."<Space><C-H>" = { action = "<Cmd>BufferMovePrevious<CR>"; silent = true; };
normal."<Space><C-L>" = { action = "<Cmd>BufferMoveNext<CR>"; silent = true; };
# TODO gotos don't work
normal."<C-1>" = { action = "<Cmd>BufferGoto 1<CR>"; silent = true; };
normal."<C-2>" = { action = "<Cmd>BufferGoto 2<CR>"; silent = true; };
normal."<C-3>" = { action = "<Cmd>BufferGoto 3<CR>"; silent = true; };
normal."<C-4>" = { action = "<Cmd>BufferGoto 4<CR>"; silent = true; };
normal."<C-5>" = { action = "<Cmd>BufferGoto 5<CR>"; silent = true; };
normal."<C-6>" = { action = "<Cmd>BufferGoto 6<CR>"; silent = true; };
normal."<C-7>" = { action = "<Cmd>BufferGoto 7<CR>"; silent = true; };
normal."<C-8>" = { action = "<Cmd>BufferGoto 8<CR>"; silent = true; };
normal."<C-9>" = { action = "<Cmd>BufferGoto 9<CR>"; silent = true; };
normal."<C-0>" = { action = "<Cmd>BufferLast<CR>"; silent = true; };
normal."gb" = { action = "<Cmd>BufferPick<CR>"; silent = true; };
# TODO Other useful options?
# symbols-outline-nvim
normal."<Space>s" = { action = "<Cmd>SymbolsOutline<CR>"; silent = true; };
# undotree
normal."<Space>u" = { action = "<Cmd>UndotreeToggle<CR>"; silent = true; };
};
};
}

167
hm/vim/feline.lua Normal file
View file

@ -0,0 +1,167 @@
vim.cmd([[
set noshowmode
set laststatus=2
]])
-- local base16_colors = require('base16-colorscheme').colors
-- FIXME Color setting doesn't work, see ./feline_test.vim for a reproducible use case
-- that works on Arch but not on Nix (with Plug stuff removed)
local vi_mode_utils = require('feline.providers.vi_mode')
local lsp = require('feline.providers.lsp')
require('feline').setup({
-- default_bg = 'base01',
-- default_fg = 'base04',
-- theme = {
-- base00 = base16_colors.base00,
-- base01 = base16_colors.base01,
-- base02 = base16_colors.base02,
-- base03 = base16_colors.base03,
-- base04 = base16_colors.base04,
-- base05 = base16_colors.base05,
-- base06 = base16_colors.base06,
-- base07 = base16_colors.base07,
-- base08 = base16_colors.base08,
-- base09 = base16_colors.base09,
-- base0A = base16_colors.base0A,
-- base0B = base16_colors.base0B,
-- base0C = base16_colors.base0C,
-- base0D = base16_colors.base0D,
-- base0E = base16_colors.base0E,
-- base0F = base16_colors.base0F,
-- },
components = {
active = {
{
{
provider = function() return string.format(' %d ', vim.fn.line('$')) end,
-- If you can, make it depend on the actual bar size
left_sep = {
{str = 'block', fg = 'base05'}
},
hl = {
fg = 'base01',
bg = 'base04',
},
},
{
provider = 'vi_mode',
hl = function()
return {
name = vi_mode_utils.get_mode_highlight_name(),
bg = vi_mode_utils.get_mode_color(),
fg = 'white',
style = 'bold',
}
end,
left_sep = {''},
right_sep = {''},
},
{
provider='',
hl = function()
return {
bg = vi_mode_utils.get_mode_color(),
fg = (vim.bo.modified and 'base09') or 'base0D',
}
end,
},
{
provider = 'file_info',
type = 'relative',
hl = function()
return {
fg = 'base06',
bg = (vim.bo.modified and 'base09') or 'base0D',
style = 'bold',
}
end,
left_sep = {''},
right_sep = {''},
},
{
provider='',
hl = function()
return {
bg = 'base02',
fg = (vim.bo.modified and 'base09') or 'base0D',
}
end,
},
{
provider = 'position',
hl = { fg = 'base05', bg = 'base02' },
right_sep = {''},
},
-- If it miraculously became easy to do, add LSP position here
{
provider='',
hl = { bg = 'base01', fg = 'base02' },
},
},
{
{
provider='',
hl = { bg = 'base03', fg = 'base01', },
},
{
provider='z',
enabled = function() return next(vim.lsp.buf_get_clients()) == nil end,
hl = { bg = 'base03', fg = 'base01', },
left_sep = '',
},
{
provider = 'diagnostic_errors',
enabled = function() return lsp.diagnostics_exist(vim.diagnostic.severity.ERROR) end,
hl = { fg = 'red', bg = 'base03', },
left_sep = '',
},
{
provider = 'diagnostic_warnings',
enabled = function() return lsp.diagnostics_exist(vim.diagnostic.severity.WARN) end,
hl = { fg = 'yellow', bg = 'base03', },
left_sep = '',
},
{
provider = 'diagnostic_hints',
enabled = function() return lsp.diagnostics_exist(vim.diagnostic.severity.HINT) end,
hl = { fg = 'cyan', bg = 'base03', },
left_sep = '',
},
{
provider = 'diagnostic_info',
enabled = function() return lsp.diagnostics_exist(vim.diagnostic.severity.INFO) end,
hl = { fg = 'skyblue', bg = 'base03', },
left_sep = '',
},
{
provider='█',
hl = { bg = 'base02', fg = 'base03', },
},
{
provider = 'git_diff_added',
hl = { fg = 'green', bg = 'base02', },
left_sep = '',
enabled = function() return vim.b.gitsigns_status_dict end,
},
{
provider = 'git_diff_changed',
hl = { fg = 'orange', bg = 'base02', },
left_sep = '',
enabled = function() return vim.b.gitsigns_status_dict end,
},
{
provider = 'git_diff_removed',
hl = { fg = 'red', bg = 'base02', },
left_sep = '',
enabled = function() return vim.b.gitsigns_status_dict end,
},
{
provider = 'git_branch',
hl = { fg = 'base05', bg = 'base02', style = 'bold', },
right_sep = '',
left_sep = '',
enabled = function() return vim.b.gitsigns_status_dict end,
},
}
},
}
})

9
hm/vim/feline_test.vim Normal file
View file

@ -0,0 +1,9 @@
source ~/.config/vim/plug.vim
call plug#begin('~/.cache/nvim/plugged')
Plug 'RRethy/nvim-base16'
call plug#end()
colorscheme base16-solarized-dark
lua << EOF
a = require('base16-colorscheme').colors.base00
EOF

View file

@ -0,0 +1,3 @@
require'lsp_signature'.on_attach({
hint_enable = false,
})

72
hm/vim/nvim-compe.lua Normal file
View file

@ -0,0 +1,72 @@
-- Default recommended config
require'compe'.setup {
enabled = true;
autocomplete = true;
debug = false;
min_length = 1;
preselect = 'enable';
throttle_time = 80;
source_timeout = 200;
resolve_timeout = 800;
incomplete_delay = 400;
max_abbr_width = 100;
max_kind_width = 100;
max_menu_width = 100;
documentation = {
border = { '', '' ,'', ' ', '', '', '', ' ' }, -- the border option is the same as `|help nvim_open_win|`
winhighlight = "NormalFloat:CompeDocumentation,FloatBorder:CompeDocumentationBorder",
max_width = 120,
min_width = 60,
max_height = math.floor(vim.o.lines * 0.3),
min_height = 1,
};
source = {
path = true;
buffer = true;
calc = true;
nvim_lsp = true;
vsnip = true;
};
}
-- Vim instructions were here
local t = function(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
local check_back_space = function()
local col = vim.fn.col('.') - 1
return col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') ~= nil
end
-- Use (s-)tab to:
--- move to prev/next item in completion menuone
--- jump to prev/next snippet's placeholder
_G.tab_complete = function()
if vim.fn.pumvisible() == 1 then
return t "<C-n>"
elseif vim.fn['vsnip#available'](1) == 1 then
return t "<Plug>(vsnip-expand-or-jump)"
elseif check_back_space() then
return t "<Tab>"
else
return vim.fn['compe#complete']()
end
end
_G.s_tab_complete = function()
if vim.fn.pumvisible() == 1 then
return t "<C-p>"
elseif vim.fn['vsnip#jumpable'](-1) == 1 then
return t "<Plug>(vsnip-jump-prev)"
else
-- If <S-Tab> is not working in your terminal, change it to <C-h>
return t "<S-Tab>"
end
end
vim.api.nvim_set_keymap("i", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("i", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})

View file

@ -0,0 +1,7 @@
require'nvim-treesitter.configs'.setup {
rainbow = {
enable = true,
extended_mode = true, -- Highlight also non-parentheses delimiters, boolean or table: lang -> boolean
max_file_lines = 1000, -- Do not enable for files with more than 1000 lines, int
}
}

View file

@ -0,0 +1,19 @@
vim.g.symbols_outline = {
highlight_hovered_item = true,
show_guides = true,
auto_preview = true,
position = 'right',
show_numbers = false,
show_relative_numbers = false,
show_symbol_details = true,
keymaps = {
close = "<Esc>",
goto_location = "<Cr>",
focus_location = "o",
hover_symbol = "<C-space>",
rename_symbol = "r",
code_actions = "a",
},
lsp_blacklist = {},
}
-- TODO Should be hierarchical, doesn't seem to be :/

112
hm/zshrc.sh Normal file
View file

@ -0,0 +1,112 @@
#
# ZSH aliases and customizations
# Am not sure what everything does in there...
#
# TODO Learn `setopt extendedglob`
# powerline-go duration support
zmodload zsh/datetime
function preexec() {
__TIMER=$EPOCHREALTIME
}
# Cursor based on mode
if [ $TERM = "linux" ]; then
_LINE_CURSOR="\e[?0c"
_BLOCK_CURSOR="\e[?8c"
else
_LINE_CURSOR="\e[6 q"
_BLOCK_CURSOR="\e[2 q"
fi
function zle-keymap-select zle-line-init
{
case $KEYMAP in
vicmd) print -n -- "$_BLOCK_CURSOR";;
viins|main) print -n -- "$_LINE_CURSOR";;
esac
}
function zle-line-finish
{
print -n -- "$_BLOCK_CURSOR"
}
zle -N zle-line-init
zle -N zle-line-finish
zle -N zle-keymap-select
# Also return to normal mode from jk shortcut
bindkey 'jk' vi-cmd-mode
# Edit command line
autoload edit-command-line
zle -N edit-command-line
bindkey -M vicmd v edit-command-line
# Additional history-substring-search bindings for vi cmd mode
# TODO Doesn't work, as home-manager loads history-substring at the very end of the file
# bindkey -M vicmd 'k' history-substring-search-up
# bindkey -M vicmd 'j' history-substring-search-down
# Autocompletion
autoload -Uz promptinit
promptinit
# Color common prefix
zstyle -e ':completion:*:default' list-colors 'reply=("${PREFIX:+=(#bi)($PREFIX:t)(?)*==35=00}:${(s.:.)LS_COLORS}")'
setopt GLOBDOTS # Complete hidden files
setopt NO_BEEP # that annoying beep goes away
# setopt NO_LIST_BEEP # beeping is only turned off for ambiguous completions
#
setopt AUTO_LIST # when the completion is ambiguous you get a list without having to type ^D
# setopt BASH_AUTO_LIST # the list only happens the second time you hit tab on an ambiguous completion
# setopt LIST_AMBIGUOUS # this is modified so that nothing is listed if there is an unambiguous prefix or suffix to be inserted --- this can be combined with BASH_AUTO_LIST, so that where both are applicable you need to hit tab three times for a listing
unsetopt REC_EXACT # if the string on the command line exactly matches one of the possible completions, it is accepted, even if there is another completion (i.e. that string with something else added) that also matches
unsetopt MENU_COMPLETE # one completion is always inserted completely, then when you hit TAB it changes to the next, and so on until you get back to where you started
unsetopt AUTO_MENU # you only get the menu behaviour when you hit TAB again on the ambiguous completion.
unsetopt AUTO_REMOVE_SLASH
# Help
# TODO ~~Doesn't work (how ironic)~~ Works but it's just man?
autoload -Uz run-help
unalias run-help
alias help=run-help
autoload -Uz run-help-git
autoload -Uz run-help-ip
autoload -Uz run-help-openssl
autoload -Uz run-help-p4
autoload -Uz run-help-sudo
autoload -Uz run-help-svk
autoload -Uz run-help-svn
# Dir stack
DIRSTACKFILE="$HOME/.cache/zsh_dirstack"
if [[ -f $DIRSTACKFILE ]] && [[ $#dirstack -eq 0 ]]; then
dirstack=( ${(f)"$(< $DIRSTACKFILE)"} )
# [[ -d $dirstack[1] ]] && cd $dirstack[1]
fi
chpwd() {
print -l $PWD ${(u)dirstack} >$DIRSTACKFILE
}
DIRSTACKSIZE=20
setopt AUTO_PUSHD PUSHD_SILENT PUSHD_TO_HOME
setopt PUSHD_IGNORE_DUPS # Remove duplicate entries
setopt PUSHD_MINUS # This reverts the +/- operators.
# History
# From https://unix.stackexchange.com/a/273863
setopt BANG_HIST # Treat the '!' character specially during expansion.
setopt INC_APPEND_HISTORY # Write to the history file immediately, not when the shell exits.
setopt HIST_IGNORE_ALL_DUPS # Delete old recorded entry if new entry is a duplicate.
setopt HIST_FIND_NO_DUPS # Do not display a line previously found.
setopt HIST_SAVE_NO_DUPS # Don't write duplicate entries in the history file.
setopt HIST_REDUCE_BLANKS # Remove superfluous blanks before recording entry.
unsetopt HIST_VERIFY # Don't execute immediately upon history expansion.
unsetopt HIST_BEEP # Beep when accessing nonexistent history.