nix: Make nix the root
Which means now I'll have to think about real prefixes in commit names.
This commit is contained in:
parent
550eed06e0
commit
ee178b7d57
190 changed files with 5 additions and 6 deletions
53
hm/batteryNotify.sh
Executable file
53
hm/batteryNotify.sh
Executable 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
514
hm/common.nix
Normal 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
656
hm/desktop.nix
Normal 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
66
hm/dev.nix
Normal 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
57
hm/extra.nix
Normal 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
192
hm/face.svg
Normal 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
94
hm/frobar/.dev/barng.py
Executable file
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import typing
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
# CORE
|
||||
|
||||
|
||||
class Notifier:
|
||||
pass
|
||||
|
||||
|
||||
class Section:
|
||||
def __init__(self) -> None:
|
||||
self.text = b"(Loading)"
|
||||
|
||||
|
||||
class Module:
|
||||
def __init__(self) -> None:
|
||||
self.bar: "Bar"
|
||||
self.section = Section()
|
||||
self.sections = [self.section]
|
||||
|
||||
|
||||
class Alignment:
|
||||
def __init__(self, *modules: Module) -> None:
|
||||
self.bar: "Bar"
|
||||
self.modules = modules
|
||||
for module in modules:
|
||||
module.bar = self.bar
|
||||
|
||||
|
||||
class Screen:
|
||||
def __init__(self, left: Alignment = Alignment(), right: Alignment = Alignment()) -> None:
|
||||
self.bar: "Bar"
|
||||
self.left = left
|
||||
self.left.bar = self.bar
|
||||
self.right = right or Alignment()
|
||||
self.right.bar = self.bar
|
||||
|
||||
|
||||
class Bar:
|
||||
def __init__(self, *screens: Screen) -> None:
|
||||
self.screens = screens
|
||||
for screen in screens:
|
||||
screen.bar = self
|
||||
self.process = subprocess.Popen(["lemonbar"], stdin=subprocess.PIPE)
|
||||
|
||||
def display(self) -> None:
|
||||
string = b""
|
||||
for s, screen in enumerate(self.screens):
|
||||
string += b"%%{S%d}" % s
|
||||
for control, alignment in [(b'%{l}', screen.left), (b'%{r}', screen.right)]:
|
||||
string += control
|
||||
for module in alignment.modules:
|
||||
for section in module.sections:
|
||||
string += b"<%b> |" % section.text
|
||||
|
||||
string += b"\n"
|
||||
print(string)
|
||||
assert self.process.stdin
|
||||
self.process.stdin.write(string)
|
||||
self.process.stdin.flush()
|
||||
|
||||
def run(self) -> None:
|
||||
while True:
|
||||
self.display()
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
# REUSABLE
|
||||
|
||||
class ClockNotifier(Notifier):
|
||||
def run(self) -> None:
|
||||
while True:
|
||||
def __init__(self, text: bytes):
|
||||
super().__init__()
|
||||
self.section.text = text
|
||||
|
||||
class StaticModule(Module):
|
||||
def __init__(self, text: bytes):
|
||||
super().__init__()
|
||||
self.section.text = text
|
||||
|
||||
|
||||
# USER
|
||||
|
||||
if __name__ == "__main__":
|
||||
bar = Bar(
|
||||
Screen(Alignment(StaticModule(b"A"))),
|
||||
Screen(Alignment(StaticModule(b"B"))),
|
||||
)
|
||||
bar.run()
|
199
hm/frobar/.dev/oldbar.py
Executable file
199
hm/frobar/.dev/oldbar.py
Executable file
|
@ -0,0 +1,199 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Debugging script
|
||||
"""
|
||||
|
||||
import i3ipc
|
||||
import os
|
||||
import psutil
|
||||
|
||||
# import alsaaudio
|
||||
from time import time
|
||||
import subprocess
|
||||
|
||||
i3 = i3ipc.Connection()
|
||||
lemonbar = subprocess.Popen(["lemonbar", "-b"], stdin=subprocess.PIPE)
|
||||
|
||||
# Utils
|
||||
def upChart(p):
|
||||
block = " ▁▂▃▄▅▆▇█"
|
||||
return block[round(p * (len(block) - 1))]
|
||||
|
||||
|
||||
def humanSizeOf(num, suffix="B"): # TODO Credit
|
||||
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
||||
if abs(num) < 1024.0:
|
||||
return "%3.0f%2s%s" % (num, unit, suffix)
|
||||
num /= 1024.0
|
||||
return "%.0f%2s%s" % (num, "Yi", suffix)
|
||||
|
||||
|
||||
# Values
|
||||
mode = ""
|
||||
container = i3.get_tree().find_focused()
|
||||
workspaces = i3.get_workspaces()
|
||||
outputs = i3.get_outputs()
|
||||
|
||||
username = os.environ["USER"]
|
||||
hostname = os.environ["HOSTNAME"]
|
||||
if "-" in hostname:
|
||||
hostname = hostname.split("-")[-1]
|
||||
|
||||
oldNetIO = dict()
|
||||
oldTime = time()
|
||||
|
||||
|
||||
def update():
|
||||
activeOutputs = sorted(
|
||||
sorted(list(filter(lambda o: o.active, outputs)), key=lambda o: o.rect.y),
|
||||
key=lambda o: o.rect.x,
|
||||
)
|
||||
z = ""
|
||||
for aOutput in range(len(activeOutputs)):
|
||||
output = activeOutputs[aOutput]
|
||||
# Mode || Workspaces
|
||||
t = []
|
||||
if mode != "":
|
||||
t.append(mode)
|
||||
else:
|
||||
t.append(
|
||||
" ".join(
|
||||
[
|
||||
(w.name.upper() if w.focused else w.name)
|
||||
for w in workspaces
|
||||
if w.output == output.name
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# Windows Title
|
||||
# if container:
|
||||
# t.append(container.name)
|
||||
|
||||
# CPU
|
||||
t.append(
|
||||
"C" + "".join([upChart(p / 100) for p in psutil.cpu_percent(percpu=True)])
|
||||
)
|
||||
|
||||
# Memory
|
||||
t.append(
|
||||
"M"
|
||||
+ str(round(psutil.virtual_memory().percent))
|
||||
+ "% "
|
||||
+ "S"
|
||||
+ str(round(psutil.swap_memory().percent))
|
||||
+ "%"
|
||||
)
|
||||
|
||||
# Disks
|
||||
d = []
|
||||
for disk in psutil.disk_partitions():
|
||||
e = ""
|
||||
if disk.device.startswith("/dev/sd"):
|
||||
e += "S" + disk.device[-2:].upper()
|
||||
elif disk.device.startswith("/dev/mmcblk"):
|
||||
e += "M" + disk.device[-3] + disk.device[-1]
|
||||
else:
|
||||
e += "?"
|
||||
e += " "
|
||||
e += str(round(psutil.disk_usage(disk.mountpoint).percent)) + "%"
|
||||
d.append(e)
|
||||
t.append(" ".join(d))
|
||||
|
||||
# Network
|
||||
netStats = psutil.net_if_stats()
|
||||
netIO = psutil.net_io_counters(pernic=True)
|
||||
net = []
|
||||
for iface in filter(lambda i: i != "lo" and netStats[i].isup, netStats.keys()):
|
||||
s = ""
|
||||
if iface.startswith("eth"):
|
||||
s += "E"
|
||||
elif iface.startswith("wlan"):
|
||||
s += "W"
|
||||
else:
|
||||
s += "?"
|
||||
|
||||
s += " "
|
||||
now = time()
|
||||
global oldNetIO, oldTime
|
||||
|
||||
sent = (
|
||||
(oldNetIO[iface].bytes_sent if iface in oldNetIO else 0)
|
||||
- (netIO[iface].bytes_sent if iface in netIO else 0)
|
||||
) / (oldTime - now)
|
||||
recv = (
|
||||
(oldNetIO[iface].bytes_recv if iface in oldNetIO else 0)
|
||||
- (netIO[iface].bytes_recv if iface in netIO else 0)
|
||||
) / (oldTime - now)
|
||||
s += (
|
||||
"↓"
|
||||
+ humanSizeOf(abs(recv), "B/s")
|
||||
+ " ↑"
|
||||
+ humanSizeOf(abs(sent), "B/s")
|
||||
)
|
||||
|
||||
oldNetIO = netIO
|
||||
oldTime = now
|
||||
|
||||
net.append(s)
|
||||
t.append(" ".join(net))
|
||||
|
||||
# Battery
|
||||
if os.path.isdir("/sys/class/power_supply/BAT0"):
|
||||
with open("/sys/class/power_supply/BAT0/charge_now") as f:
|
||||
charge_now = int(f.read())
|
||||
with open("/sys/class/power_supply/BAT0/charge_full_design") as f:
|
||||
charge_full = int(f.read())
|
||||
t.append("B" + str(round(100 * charge_now / charge_full)) + "%")
|
||||
|
||||
# Volume
|
||||
# t.append('V ' + str(alsaaudio.Mixer('Master').getvolume()[0]) + '%')
|
||||
|
||||
t.append(username + "@" + hostname)
|
||||
|
||||
# print(' - '.join(t))
|
||||
# t = [output.name]
|
||||
|
||||
z += " - ".join(t) + "%{S" + str(aOutput + 1) + "}"
|
||||
# lemonbar.stdin.write(bytes(' - '.join(t), 'utf-8'))
|
||||
# lemonbar.stdin.write(bytes('%{S' + str(aOutput + 1) + '}', 'utf-8'))
|
||||
|
||||
lemonbar.stdin.write(bytes(z + "\n", "utf-8"))
|
||||
lemonbar.stdin.flush()
|
||||
|
||||
|
||||
# Event listeners
|
||||
def on_mode(i3, e):
|
||||
global mode
|
||||
if e.change == "default":
|
||||
mode = ""
|
||||
else:
|
||||
mode = e.change
|
||||
update()
|
||||
|
||||
|
||||
i3.on("mode", on_mode)
|
||||
|
||||
# def on_window_focus(i3, e):
|
||||
# global container
|
||||
# container = e.container
|
||||
# update()
|
||||
#
|
||||
# i3.on("window::focus", on_window_focus)
|
||||
|
||||
|
||||
def on_workspace_focus(i3, e):
|
||||
global workspaces
|
||||
workspaces = i3.get_workspaces()
|
||||
update()
|
||||
|
||||
|
||||
i3.on("workspace::focus", on_workspace_focus)
|
||||
|
||||
# Starting
|
||||
|
||||
update()
|
||||
|
||||
|
||||
i3.main()
|
327
hm/frobar/.dev/pip.py
Executable file
327
hm/frobar/.dev/pip.py
Executable file
|
@ -0,0 +1,327 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Beautiful script
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import datetime
|
||||
import os
|
||||
import multiprocessing
|
||||
import i3ipc
|
||||
import difflib
|
||||
|
||||
# Constants
|
||||
FONT = "DejaVuSansMono Nerd Font Mono"
|
||||
|
||||
# TODO Update to be in sync with base16
|
||||
thm = [
|
||||
"#002b36",
|
||||
"#dc322f",
|
||||
"#859900",
|
||||
"#b58900",
|
||||
"#268bd2",
|
||||
"#6c71c4",
|
||||
"#2aa198",
|
||||
"#93a1a1",
|
||||
"#657b83",
|
||||
"#dc322f",
|
||||
"#859900",
|
||||
"#b58900",
|
||||
"#268bd2",
|
||||
"#6c71c4",
|
||||
"#2aa198",
|
||||
"#fdf6e3",
|
||||
]
|
||||
fg = "#93a1a1"
|
||||
bg = "#002b36"
|
||||
|
||||
THEMES = {
|
||||
"CENTER": (fg, bg),
|
||||
"DEFAULT": (thm[0], thm[8]),
|
||||
"1": (thm[0], thm[9]),
|
||||
"2": (thm[0], thm[10]),
|
||||
"3": (thm[0], thm[11]),
|
||||
"4": (thm[0], thm[12]),
|
||||
"5": (thm[0], thm[13]),
|
||||
"6": (thm[0], thm[14]),
|
||||
"7": (thm[0], thm[15]),
|
||||
}
|
||||
|
||||
# Utils
|
||||
|
||||
|
||||
def fitText(text, size):
|
||||
"""
|
||||
Add spaces or cut a string to be `size` characters long
|
||||
"""
|
||||
if size > 0:
|
||||
t = len(text)
|
||||
if t >= size:
|
||||
return text[:size]
|
||||
else:
|
||||
diff = size - t
|
||||
return text + " " * diff
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def fgColor(theme):
|
||||
global THEMES
|
||||
return THEMES[theme][0]
|
||||
|
||||
|
||||
def bgColor(theme):
|
||||
global THEMES
|
||||
return THEMES[theme][1]
|
||||
|
||||
|
||||
class Section:
|
||||
def __init__(self, theme="DEFAULT"):
|
||||
self.text = ""
|
||||
self.size = 0
|
||||
self.toSize = 0
|
||||
self.theme = theme
|
||||
self.visible = False
|
||||
self.name = ""
|
||||
|
||||
def update(self, text):
|
||||
if text == "":
|
||||
self.toSize = 0
|
||||
else:
|
||||
if len(text) < len(self.text):
|
||||
self.text = text + self.text[len(text) :]
|
||||
else:
|
||||
self.text = text
|
||||
self.toSize = len(text) + 3
|
||||
|
||||
def updateSize(self):
|
||||
"""
|
||||
Set the size for the next frame of animation
|
||||
Return if another frame is needed
|
||||
"""
|
||||
if self.toSize > self.size:
|
||||
self.size += 1
|
||||
elif self.toSize < self.size:
|
||||
self.size -= 1
|
||||
self.visible = self.size
|
||||
return self.toSize == self.size
|
||||
|
||||
def draw(self, left=True, nextTheme="DEFAULT"):
|
||||
s = ""
|
||||
if self.visible:
|
||||
if not left:
|
||||
if self.theme == nextTheme:
|
||||
s += ""
|
||||
else:
|
||||
s += "%{F" + bgColor(self.theme) + "}"
|
||||
s += "%{B" + bgColor(nextTheme) + "}"
|
||||
s += ""
|
||||
s += "%{F" + fgColor(self.theme) + "}"
|
||||
s += "%{B" + bgColor(self.theme) + "}"
|
||||
s += " " if self.size > 1 else ""
|
||||
s += fitText(self.text, self.size - 3)
|
||||
s += " " if self.size > 2 else ""
|
||||
if left:
|
||||
if self.theme == nextTheme:
|
||||
s += ""
|
||||
else:
|
||||
s += "%{F" + bgColor(self.theme) + "}"
|
||||
s += "%{B" + bgColor(nextTheme) + "}"
|
||||
s += ""
|
||||
return s
|
||||
|
||||
|
||||
# Section definition
|
||||
sTime = Section("3")
|
||||
|
||||
hostname = os.environ["HOSTNAME"].split(".")[0]
|
||||
sHost = Section("2")
|
||||
sHost.update(
|
||||
os.environ["USER"] + "@" + hostname.split("-")[-1] if "-" in hostname else hostname
|
||||
)
|
||||
|
||||
|
||||
# Groups definition
|
||||
gLeft = []
|
||||
gRight = [sTime, sHost]
|
||||
|
||||
# Bar handling
|
||||
bar = subprocess.Popen(["lemonbar", "-f", FONT, "-b"], stdin=subprocess.PIPE)
|
||||
|
||||
|
||||
def updateBar():
|
||||
global timeLastUpdate, timeUpdate
|
||||
global gLeft, gRight
|
||||
global outputs
|
||||
|
||||
text = ""
|
||||
for oi in range(len(outputs)):
|
||||
output = outputs[oi]
|
||||
gLeftFiltered = list(
|
||||
filter(
|
||||
lambda s: s.visible and (not s.output or s.output == output.name), gLeft
|
||||
)
|
||||
)
|
||||
tLeft = ""
|
||||
l = len(gLeftFiltered)
|
||||
for gi in range(l):
|
||||
g = gLeftFiltered[gi]
|
||||
# Next visible section for transition
|
||||
nextTheme = gLeftFiltered[gi + 1].theme if gi + 1 < l else "CENTER"
|
||||
tLeft = tLeft + g.draw(True, nextTheme)
|
||||
|
||||
tRight = ""
|
||||
for gi in range(len(gRight)):
|
||||
g = gRight[gi]
|
||||
nextTheme = "CENTER"
|
||||
for gn in gRight[gi + 1 :]:
|
||||
if gn.visible:
|
||||
nextTheme = gn.theme
|
||||
break
|
||||
tRight = g.draw(False, nextTheme) + tRight
|
||||
text += (
|
||||
"%{l}"
|
||||
+ tLeft
|
||||
+ "%{r}"
|
||||
+ tRight
|
||||
+ "%{B"
|
||||
+ bgColor("CENTER")
|
||||
+ "}"
|
||||
+ "%{S"
|
||||
+ str(oi + 1)
|
||||
+ "}"
|
||||
)
|
||||
|
||||
bar.stdin.write(bytes(text + "\n", "utf-8"))
|
||||
bar.stdin.flush()
|
||||
|
||||
|
||||
# Values
|
||||
i3 = i3ipc.Connection()
|
||||
outputs = []
|
||||
|
||||
|
||||
def on_output():
|
||||
global outputs
|
||||
outputs = sorted(
|
||||
sorted(
|
||||
list(filter(lambda o: o.active, i3.get_outputs())), key=lambda o: o.rect.y
|
||||
),
|
||||
key=lambda o: o.rect.x,
|
||||
)
|
||||
|
||||
|
||||
on_output()
|
||||
|
||||
|
||||
def on_workspace_focus():
|
||||
global i3
|
||||
global gLeft
|
||||
workspaces = i3.get_workspaces()
|
||||
wNames = [w.name for w in workspaces]
|
||||
sNames = [s.name for s in gLeft]
|
||||
|
||||
newGLeft = []
|
||||
|
||||
def actuate(section, workspace):
|
||||
if workspace:
|
||||
section.name = workspace.name
|
||||
section.output = workspace.output
|
||||
if workspace.visible:
|
||||
section.update(workspace.name)
|
||||
else:
|
||||
section.update(workspace.name.split(" ")[0])
|
||||
|
||||
if workspace.focused:
|
||||
section.theme = "4"
|
||||
elif workspace.urgent:
|
||||
section.theme = "1"
|
||||
else:
|
||||
section.theme = "6"
|
||||
else:
|
||||
section.update("")
|
||||
section.theme = "6"
|
||||
|
||||
for tag, i, j, k, l in difflib.SequenceMatcher(None, sNames, wNames).get_opcodes():
|
||||
if tag == "equal": # If the workspaces didn't changed
|
||||
for a in range(j - i):
|
||||
workspace = workspaces[k + a]
|
||||
section = gLeft[i + a]
|
||||
actuate(section, workspace)
|
||||
newGLeft.append(section)
|
||||
if tag in ("delete", "replace"): # If the workspaces were removed
|
||||
for section in gLeft[i:j]:
|
||||
if section.visible:
|
||||
actuate(section, None)
|
||||
newGLeft.append(section)
|
||||
else:
|
||||
del section
|
||||
if tag in ("insert", "replace"): # If the workspaces were removed
|
||||
for workspace in workspaces[k:l]:
|
||||
section = Section()
|
||||
actuate(section, workspace)
|
||||
newGLeft.append(section)
|
||||
gLeft = newGLeft
|
||||
|
||||
updateBar()
|
||||
|
||||
|
||||
on_workspace_focus()
|
||||
|
||||
|
||||
def i3events(i3childPipe):
|
||||
global i3
|
||||
|
||||
# Proxy functions
|
||||
def on_workspace_focus(i3, e):
|
||||
global i3childPipe
|
||||
i3childPipe.send("on_workspace_focus")
|
||||
|
||||
i3.on("workspace::focus", on_workspace_focus)
|
||||
|
||||
def on_output(i3, e):
|
||||
global i3childPipe
|
||||
i3childPipe.send("on_output")
|
||||
|
||||
i3.on("output", on_output)
|
||||
|
||||
i3.main()
|
||||
|
||||
|
||||
i3parentPipe, i3childPipe = multiprocessing.Pipe()
|
||||
i3process = multiprocessing.Process(target=i3events, args=(i3childPipe,))
|
||||
i3process.start()
|
||||
|
||||
|
||||
def updateValues():
|
||||
# Time
|
||||
now = datetime.datetime.now()
|
||||
sTime.update(now.strftime("%x %X"))
|
||||
|
||||
|
||||
def updateAnimation():
|
||||
for s in set(gLeft + gRight):
|
||||
s.updateSize()
|
||||
updateBar()
|
||||
|
||||
|
||||
lastUpdate = 0
|
||||
while True:
|
||||
now = time.time()
|
||||
if i3parentPipe.poll():
|
||||
msg = i3parentPipe.recv()
|
||||
if msg == "on_workspace_focus":
|
||||
on_workspace_focus()
|
||||
elif msg == "on_output":
|
||||
on_output()
|
||||
# TODO Restart lemonbar
|
||||
else:
|
||||
print(msg)
|
||||
updateAnimation()
|
||||
if now >= lastUpdate + 1:
|
||||
updateValues()
|
||||
lastUpdate = now
|
||||
|
||||
time.sleep(0.05)
|
10
hm/frobar/.dev/x.py
Executable file
10
hm/frobar/.dev/x.py
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import Xlib.display
|
||||
|
||||
dis = Xlib.display.Display()
|
||||
|
||||
nb = dis.screen_count()
|
||||
|
||||
for s in range(nb):
|
||||
print(s)
|
3
hm/frobar/.gitignore
vendored
Normal file
3
hm/frobar/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
dist/*
|
||||
frobar.egg-info/*
|
||||
__pycache__
|
37
hm/frobar/default.nix
Normal file
37
hm/frobar/default.nix
Normal 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?
|
64
hm/frobar/frobar/__init__.py
Normal file
64
hm/frobar/frobar/__init__.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python3
|
||||
from frobar.providers import *
|
||||
|
||||
# TODO If multiple screen, expand the sections and share them
|
||||
# TODO Graceful exit
|
||||
|
||||
def run():
|
||||
Bar.init()
|
||||
Updater.init()
|
||||
|
||||
WORKSPACE_THEME = 0
|
||||
FOCUS_THEME = 3
|
||||
URGENT_THEME = 1
|
||||
CUSTOM_SUFFIXES = "▲■"
|
||||
|
||||
customNames = dict()
|
||||
for i in range(len(CUSTOM_SUFFIXES)):
|
||||
short = str(i + 1)
|
||||
full = short + " " + CUSTOM_SUFFIXES[i]
|
||||
customNames[short] = full
|
||||
Bar.addSectionAll(
|
||||
I3WorkspacesProvider(
|
||||
theme=WORKSPACE_THEME,
|
||||
themeFocus=FOCUS_THEME,
|
||||
themeUrgent=URGENT_THEME,
|
||||
themeMode=URGENT_THEME,
|
||||
customNames=customNames,
|
||||
),
|
||||
BarGroupType.LEFT,
|
||||
)
|
||||
|
||||
# TODO Middle
|
||||
Bar.addSectionAll(MpdProvider(theme=7), BarGroupType.LEFT)
|
||||
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
|
||||
|
||||
# TODO Computer modes
|
||||
|
||||
SYSTEM_THEME = 2
|
||||
DANGER_THEME = FOCUS_THEME
|
||||
CRITICAL_THEME = URGENT_THEME
|
||||
Bar.addSectionAll(CpuProvider(), BarGroupType.RIGHT)
|
||||
Bar.addSectionAll(RamProvider(), BarGroupType.RIGHT)
|
||||
Bar.addSectionAll(TemperatureProvider(), BarGroupType.RIGHT)
|
||||
Bar.addSectionAll(BatteryProvider(), BarGroupType.RIGHT)
|
||||
|
||||
# Peripherals
|
||||
PERIPHERAL_THEME = 5
|
||||
NETWORK_THEME = 4
|
||||
# TODO Disk space provider
|
||||
# TODO Screen (connected, autorandr configuration, bbswitch) provider
|
||||
Bar.addSectionAll(PulseaudioProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
|
||||
Bar.addSectionAll(RfkillProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
|
||||
Bar.addSectionAll(NetworkProvider(theme=NETWORK_THEME), BarGroupType.RIGHT)
|
||||
|
||||
# Personal
|
||||
PERSONAL_THEME = 0
|
||||
# Bar.addSectionAll(KeystoreProvider(theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
||||
# Bar.addSectionAll(NotmuchUnreadProvider(dir='~/.mail/', theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
||||
# Bar.addSectionAll(TodoProvider(dir='~/.vdirsyncer/currentCalendars/', theme=PERSONAL_THEME), BarGroupType.RIGHT)
|
||||
|
||||
TIME_THEME = 6
|
||||
Bar.addSectionAll(TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
|
||||
|
||||
# Bar.run()
|
723
hm/frobar/frobar/display.py
Normal file
723
hm/frobar/frobar/display.py
Normal file
|
@ -0,0 +1,723 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
import coloredlogs
|
||||
import i3ipc
|
||||
|
||||
from frobar.notbusy import notBusy
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
# TODO Allow deletion of Bar, BarGroup and Section for screen changes
|
||||
# IDEA Use i3 ipc events rather than relying on xrandr or Xlib (less portable
|
||||
# but easier)
|
||||
# TODO Optimize to use write() calls instead of string concatenation (writing
|
||||
# BarGroup strings should be a good compromise)
|
||||
# TODO Use bytes rather than strings
|
||||
# TODO Use default colors of lemonbar sometimes
|
||||
# TODO Adapt bar height with font height
|
||||
# TODO OPTI Static text objects that update its parents if modified
|
||||
# TODO forceSize and changeText are different
|
||||
|
||||
|
||||
class BarGroupType(enum.Enum):
|
||||
LEFT = 0
|
||||
RIGHT = 1
|
||||
# TODO Middle
|
||||
# MID_LEFT = 2
|
||||
# MID_RIGHT = 3
|
||||
|
||||
|
||||
class BarStdoutThread(threading.Thread):
|
||||
def run(self) -> None:
|
||||
while Bar.running:
|
||||
handle = Bar.process.stdout.readline().strip()
|
||||
if not len(handle):
|
||||
Bar.stop()
|
||||
if handle not in Bar.actionsH2F:
|
||||
log.error("Unknown action: {}".format(handle))
|
||||
continue
|
||||
function = Bar.actionsH2F[handle]
|
||||
function()
|
||||
|
||||
|
||||
class Bar:
|
||||
"""
|
||||
One bar for each screen
|
||||
"""
|
||||
|
||||
# Constants
|
||||
FONTS = ["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
|
5
hm/frobar/frobar/notbusy.py
Normal file
5
hm/frobar/frobar/notbusy.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import threading
|
||||
|
||||
notBusy = threading.Event()
|
816
hm/frobar/frobar/providers.py
Normal file
816
hm/frobar/frobar/providers.py
Normal file
|
@ -0,0 +1,816 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import datetime
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
import coloredlogs
|
||||
import mpd
|
||||
import notmuch
|
||||
import psutil
|
||||
import pulsectl
|
||||
|
||||
from frobar.display import *
|
||||
from frobar.updaters import *
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
||||
# TODO Generator class (for I3WorkspacesProvider, NetworkProvider and later
|
||||
# PulseaudioProvider and MpdProvider)
|
||||
|
||||
|
||||
def humanSize(num):
|
||||
"""
|
||||
Returns a string of width 3+3
|
||||
"""
|
||||
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
|
||||
if abs(num) < 1000:
|
||||
if num >= 10:
|
||||
return "{:3d}{}".format(int(num), unit)
|
||||
else:
|
||||
return "{:.1f}{}".format(num, unit)
|
||||
num /= 1024.0
|
||||
return "{:d}YiB".format(num)
|
||||
|
||||
|
||||
def randomColor(seed=0):
|
||||
random.seed(seed)
|
||||
return "#{:02x}{:02x}{:02x}".format(*[random.randint(0, 255) for _ in range(3)])
|
||||
|
||||
|
||||
class TimeProvider(StatefulSection, PeriodicUpdater):
|
||||
FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"]
|
||||
NUMBER_STATES = len(FORMATS)
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def fetcher(self):
|
||||
now = datetime.datetime.now()
|
||||
return now.strftime(self.FORMATS[self.state])
|
||||
|
||||
def __init__(self, theme=None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.changeInterval(1) # TODO OPTI When state < 1
|
||||
|
||||
|
||||
class AlertLevel(enum.Enum):
|
||||
NORMAL = 0
|
||||
WARNING = 1
|
||||
DANGER = 2
|
||||
|
||||
|
||||
class AlertingSection(StatefulSection):
|
||||
# TODO EASE Correct settings for themes
|
||||
THEMES = {AlertLevel.NORMAL: 2, AlertLevel.WARNING: 3, AlertLevel.DANGER: 1}
|
||||
PERSISTENT = True
|
||||
|
||||
def getLevel(self, quantity):
|
||||
if quantity > self.dangerThresold:
|
||||
return AlertLevel.DANGER
|
||||
elif quantity > self.warningThresold:
|
||||
return AlertLevel.WARNING
|
||||
else:
|
||||
return AlertLevel.NORMAL
|
||||
|
||||
def updateLevel(self, quantity):
|
||||
self.level = self.getLevel(quantity)
|
||||
self.updateTheme(self.THEMES[self.level])
|
||||
if self.level == AlertLevel.NORMAL:
|
||||
return
|
||||
# TODO Temporary update state
|
||||
|
||||
def __init__(self, theme):
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.dangerThresold = 0.90
|
||||
self.warningThresold = 0.75
|
||||
|
||||
|
||||
class CpuProvider(AlertingSection, PeriodicUpdater):
|
||||
NUMBER_STATES = 3
|
||||
ICON = ""
|
||||
|
||||
def fetcher(self):
|
||||
percent = psutil.cpu_percent(percpu=False)
|
||||
self.updateLevel(percent / 100)
|
||||
if self.state >= 2:
|
||||
percents = psutil.cpu_percent(percpu=True)
|
||||
return "".join([Section.ramp(p / 100) for p in percents])
|
||||
elif self.state >= 1:
|
||||
return Section.ramp(percent / 100)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(1)
|
||||
|
||||
|
||||
class RamProvider(AlertingSection, PeriodicUpdater):
|
||||
"""
|
||||
Shows free RAM
|
||||
"""
|
||||
|
||||
NUMBER_STATES = 4
|
||||
ICON = ""
|
||||
|
||||
def fetcher(self):
|
||||
mem = psutil.virtual_memory()
|
||||
freePerc = mem.percent / 100
|
||||
self.updateLevel(freePerc)
|
||||
|
||||
if self.state < 1:
|
||||
return None
|
||||
|
||||
text = Text(Section.ramp(freePerc))
|
||||
if self.state >= 2:
|
||||
freeStr = humanSize(mem.total - mem.available)
|
||||
text.append(freeStr)
|
||||
if self.state >= 3:
|
||||
totalStr = humanSize(mem.total)
|
||||
text.append("/", totalStr)
|
||||
|
||||
return text
|
||||
|
||||
def __init__(self, theme=None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(1)
|
||||
|
||||
|
||||
class TemperatureProvider(AlertingSection, PeriodicUpdater):
|
||||
NUMBER_STATES = 2
|
||||
RAMP = ""
|
||||
|
||||
def fetcher(self):
|
||||
allTemp = psutil.sensors_temperatures()
|
||||
if "coretemp" not in allTemp:
|
||||
# TODO Opti Remove interval
|
||||
return ""
|
||||
temp = allTemp["coretemp"][0]
|
||||
|
||||
self.warningThresold = temp.high
|
||||
self.dangerThresold = temp.critical
|
||||
self.updateLevel(temp.current)
|
||||
|
||||
self.icon = Section.ramp(temp.current / temp.high, self.RAMP)
|
||||
if self.state >= 1:
|
||||
return "{:.0f}°C".format(temp.current)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class BatteryProvider(AlertingSection, PeriodicUpdater):
|
||||
# TODO Support ACPID for events
|
||||
NUMBER_STATES = 3
|
||||
RAMP = ""
|
||||
|
||||
def fetcher(self):
|
||||
bat = psutil.sensors_battery()
|
||||
if not bat:
|
||||
self.icon = None
|
||||
return None
|
||||
|
||||
self.icon = ("" if bat.power_plugged else "") + Section.ramp(
|
||||
bat.percent / 100, self.RAMP
|
||||
)
|
||||
|
||||
self.updateLevel(1 - bat.percent / 100)
|
||||
|
||||
if self.state < 1:
|
||||
return
|
||||
|
||||
t = Text("{:.0f}%".format(bat.percent))
|
||||
|
||||
if self.state < 2:
|
||||
return t
|
||||
|
||||
h = int(bat.secsleft / 3600)
|
||||
m = int((bat.secsleft - h * 3600) / 60)
|
||||
t.append(" ({:d}:{:02d})".format(h, m))
|
||||
return t
|
||||
|
||||
def __init__(self, theme=None):
|
||||
AlertingSection.__init__(self, theme)
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class PulseaudioProvider(StatefulSection, ThreadedUpdater):
|
||||
NUMBER_STATES = 3
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def __init__(self, theme=None):
|
||||
ThreadedUpdater.__init__(self)
|
||||
StatefulSection.__init__(self, theme)
|
||||
self.pulseEvents = pulsectl.Pulse("event-handler")
|
||||
|
||||
self.pulseEvents.event_mask_set(pulsectl.PulseEventMaskEnum.sink)
|
||||
self.pulseEvents.event_callback_set(self.handleEvent)
|
||||
self.start()
|
||||
self.refreshData()
|
||||
|
||||
def fetcher(self):
|
||||
sinks = []
|
||||
with pulsectl.Pulse("list-sinks") as pulse:
|
||||
for sink in pulse.sink_list():
|
||||
if sink.port_active.name == "analog-output-headphones":
|
||||
icon = ""
|
||||
elif sink.port_active.name == "analog-output-speaker":
|
||||
icon = "" if sink.mute else ""
|
||||
elif sink.port_active.name == "headset-output":
|
||||
icon = ""
|
||||
else:
|
||||
icon = "?"
|
||||
vol = pulse.volume_get_all_chans(sink)
|
||||
fg = (sink.mute and "#333333") or (vol > 1 and "#FF0000") or None
|
||||
|
||||
t = Text(icon, fg=fg)
|
||||
sinks.append(t)
|
||||
|
||||
if self.state < 1:
|
||||
continue
|
||||
|
||||
if self.state < 2:
|
||||
if not sink.mute:
|
||||
ramp = " "
|
||||
while vol >= 0:
|
||||
ramp += self.ramp(vol if vol < 1 else 1)
|
||||
vol -= 1
|
||||
t.append(ramp)
|
||||
else:
|
||||
t.append(" {:2.0f}%".format(vol * 100))
|
||||
|
||||
return Text(*sinks)
|
||||
|
||||
def loop(self):
|
||||
self.pulseEvents.event_listen()
|
||||
|
||||
def handleEvent(self, ev):
|
||||
self.refreshData()
|
||||
|
||||
|
||||
class NetworkProviderSection(StatefulSection, Updater):
|
||||
NUMBER_STATES = 5
|
||||
DEFAULT_STATE = 1
|
||||
|
||||
def actType(self):
|
||||
self.ssid = None
|
||||
if self.iface.startswith("eth") or self.iface.startswith("enp"):
|
||||
if "u" in self.iface:
|
||||
self.icon = ""
|
||||
else:
|
||||
self.icon = ""
|
||||
elif self.iface.startswith("wlan") or self.iface.startswith("wl"):
|
||||
self.icon = ""
|
||||
if self.showSsid:
|
||||
cmd = ["iwgetid", self.iface, "--raw"]
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
self.ssid = p.stdout.strip().decode()
|
||||
elif self.iface.startswith("tun") or self.iface.startswith("tap"):
|
||||
self.icon = ""
|
||||
elif self.iface.startswith("docker"):
|
||||
self.icon = ""
|
||||
elif self.iface.startswith("veth"):
|
||||
self.icon = ""
|
||||
elif self.iface.startswith("vboxnet"):
|
||||
self.icon = ""
|
||||
else:
|
||||
self.icon = "?"
|
||||
|
||||
def getAddresses(self):
|
||||
ipv4 = None
|
||||
ipv6 = None
|
||||
for address in self.parent.addrs[self.iface]:
|
||||
if address.family == socket.AF_INET:
|
||||
ipv4 = address
|
||||
elif address.family == socket.AF_INET6:
|
||||
ipv6 = address
|
||||
return ipv4, ipv6
|
||||
|
||||
def fetcher(self):
|
||||
self.icon = None
|
||||
self.persistent = False
|
||||
if (
|
||||
self.iface not in self.parent.stats
|
||||
or not self.parent.stats[self.iface].isup
|
||||
or self.iface.startswith("lo")
|
||||
):
|
||||
return None
|
||||
|
||||
# Get addresses
|
||||
ipv4, ipv6 = self.getAddresses()
|
||||
if ipv4 is None and ipv6 is None:
|
||||
return None
|
||||
|
||||
text = []
|
||||
self.persistent = True
|
||||
self.actType()
|
||||
|
||||
if self.showSsid and self.ssid:
|
||||
text.append(self.ssid)
|
||||
|
||||
if self.showAddress:
|
||||
if ipv4:
|
||||
netStrFull = "{}/{}".format(ipv4.address, ipv4.netmask)
|
||||
addr = ipaddress.IPv4Network(netStrFull, strict=False)
|
||||
addrStr = "{}/{}".format(ipv4.address, addr.prefixlen)
|
||||
text.append(addrStr)
|
||||
# TODO IPV6
|
||||
# if ipv6:
|
||||
# text += ' ' + ipv6.address
|
||||
|
||||
if self.showSpeed:
|
||||
recvDiff = (
|
||||
self.parent.IO[self.iface].bytes_recv
|
||||
- self.parent.prevIO[self.iface].bytes_recv
|
||||
)
|
||||
sentDiff = (
|
||||
self.parent.IO[self.iface].bytes_sent
|
||||
- self.parent.prevIO[self.iface].bytes_sent
|
||||
)
|
||||
recvDiff /= self.parent.dt
|
||||
sentDiff /= self.parent.dt
|
||||
text.append("↓{}↑{}".format(humanSize(recvDiff), humanSize(sentDiff)))
|
||||
|
||||
if self.showTransfer:
|
||||
text.append(
|
||||
"⇓{}⇑{}".format(
|
||||
humanSize(self.parent.IO[self.iface].bytes_recv),
|
||||
humanSize(self.parent.IO[self.iface].bytes_sent),
|
||||
)
|
||||
)
|
||||
|
||||
return " ".join(text)
|
||||
|
||||
def onChangeState(self, state):
|
||||
self.showSsid = state >= 1
|
||||
self.showAddress = state >= 2
|
||||
self.showSpeed = state >= 3
|
||||
self.showTransfer = state >= 4
|
||||
|
||||
def __init__(self, iface, parent):
|
||||
Updater.__init__(self)
|
||||
StatefulSection.__init__(self, theme=parent.theme)
|
||||
self.iface = iface
|
||||
self.parent = parent
|
||||
|
||||
|
||||
class NetworkProvider(Section, PeriodicUpdater):
|
||||
def fetchData(self):
|
||||
self.prev = self.last
|
||||
self.prevIO = self.IO
|
||||
|
||||
self.stats = psutil.net_if_stats()
|
||||
self.addrs = psutil.net_if_addrs()
|
||||
self.IO = psutil.net_io_counters(pernic=True)
|
||||
self.ifaces = self.stats.keys()
|
||||
|
||||
self.last = time.perf_counter()
|
||||
self.dt = self.last - self.prev
|
||||
|
||||
def fetcher(self):
|
||||
self.fetchData()
|
||||
|
||||
# Add missing sections
|
||||
lastSection = self
|
||||
for iface in sorted(list(self.ifaces)):
|
||||
if iface not in self.sections.keys():
|
||||
section = NetworkProviderSection(iface, self)
|
||||
lastSection.appendAfter(section)
|
||||
self.sections[iface] = section
|
||||
else:
|
||||
section = self.sections[iface]
|
||||
lastSection = section
|
||||
|
||||
# Refresh section text
|
||||
for section in self.sections.values():
|
||||
section.refreshData()
|
||||
|
||||
return None
|
||||
|
||||
def addParent(self, parent):
|
||||
self.parents.add(parent)
|
||||
self.refreshData()
|
||||
|
||||
def __init__(self, theme=None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
|
||||
self.sections = dict()
|
||||
self.last = 0
|
||||
self.IO = dict()
|
||||
self.fetchData()
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class RfkillProvider(Section, PeriodicUpdater):
|
||||
# TODO FEAT rfkill doesn't seem to indicate that the hardware switch is
|
||||
# toggled
|
||||
PATH = "/sys/class/rfkill"
|
||||
|
||||
def fetcher(self):
|
||||
t = Text()
|
||||
for device in os.listdir(self.PATH):
|
||||
with open(os.path.join(self.PATH, device, "soft"), "rb") as f:
|
||||
softBlocked = f.read().strip() != b"0"
|
||||
with open(os.path.join(self.PATH, device, "hard"), "rb") as f:
|
||||
hardBlocked = f.read().strip() != b"0"
|
||||
|
||||
if not hardBlocked and not softBlocked:
|
||||
continue
|
||||
|
||||
with open(os.path.join(self.PATH, device, "type"), "rb") as f:
|
||||
typ = f.read().strip()
|
||||
|
||||
fg = (hardBlocked and "#CCCCCC") or (softBlocked and "#FF0000")
|
||||
if typ == b"wlan":
|
||||
icon = ""
|
||||
elif typ == b"bluetooth":
|
||||
icon = ""
|
||||
else:
|
||||
icon = "?"
|
||||
|
||||
t.append(Text(icon, fg=fg))
|
||||
return t
|
||||
|
||||
def __init__(self, theme=None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class SshAgentProvider(PeriodicUpdater):
|
||||
def fetcher(self):
|
||||
cmd = ["ssh-add", "-l"]
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
if proc.returncode != 0:
|
||||
return None
|
||||
text = Text()
|
||||
for line in proc.stdout.split(b"\n"):
|
||||
if not len(line):
|
||||
continue
|
||||
fingerprint = line.split()[1]
|
||||
text.append(Text("", fg=randomColor(seed=fingerprint)))
|
||||
return text
|
||||
|
||||
def __init__(self):
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class GpgAgentProvider(PeriodicUpdater):
|
||||
def fetcher(self):
|
||||
cmd = ["gpg-connect-agent", "keyinfo --list", "/bye"]
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
# proc = subprocess.run(cmd)
|
||||
if proc.returncode != 0:
|
||||
return None
|
||||
text = Text()
|
||||
for line in proc.stdout.split(b"\n"):
|
||||
if not len(line) or line == b"OK":
|
||||
continue
|
||||
spli = line.split()
|
||||
if spli[6] != b"1":
|
||||
continue
|
||||
keygrip = spli[2]
|
||||
text.append(Text("", fg=randomColor(seed=keygrip)))
|
||||
return text
|
||||
|
||||
def __init__(self):
|
||||
PeriodicUpdater.__init__(self)
|
||||
self.changeInterval(5)
|
||||
|
||||
|
||||
class KeystoreProvider(Section, MergedUpdater):
|
||||
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless
|
||||
ICON = ""
|
||||
|
||||
def __init__(self, theme=None):
|
||||
MergedUpdater.__init__(self, SshAgentProvider(), GpgAgentProvider())
|
||||
Section.__init__(self, theme)
|
||||
|
||||
|
||||
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater):
|
||||
COLORABLE_ICON = ""
|
||||
|
||||
def subfetcher(self):
|
||||
db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY, path=self.dir)
|
||||
counts = []
|
||||
for account in self.accounts:
|
||||
queryStr = "folder:/{}/ and tag:unread".format(account)
|
||||
query = notmuch.Query(db, queryStr)
|
||||
nbMsgs = query.count_messages()
|
||||
if account == "frogeye":
|
||||
global q
|
||||
q = query
|
||||
if nbMsgs < 1:
|
||||
continue
|
||||
counts.append((nbMsgs, self.colors[account]))
|
||||
# db.close()
|
||||
return counts
|
||||
|
||||
def __init__(self, dir="~/.mail/", theme=None):
|
||||
PeriodicUpdater.__init__(self)
|
||||
ColorCountsSection.__init__(self, theme)
|
||||
|
||||
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||
assert os.path.isdir(self.dir)
|
||||
|
||||
# Fetching account list
|
||||
self.accounts = sorted(
|
||||
[a for a in os.listdir(self.dir) if not a.startswith(".")]
|
||||
)
|
||||
# Fetching colors
|
||||
self.colors = dict()
|
||||
for account in self.accounts:
|
||||
filename = os.path.join(self.dir, account, "color")
|
||||
with open(filename, "r") as f:
|
||||
color = f.read().strip()
|
||||
self.colors[account] = color
|
||||
|
||||
self.addPath(os.path.join(self.dir, ".notmuch", "xapian"))
|
||||
|
||||
|
||||
class TodoProvider(ColorCountsSection, InotifyUpdater):
|
||||
# TODO OPT/UX Maybe we could get more data from the todoman python module
|
||||
# TODO OPT Specific callback for specific directory
|
||||
COLORABLE_ICON = ""
|
||||
|
||||
def updateCalendarList(self):
|
||||
calendars = sorted(os.listdir(self.dir))
|
||||
for calendar in calendars:
|
||||
# If the calendar wasn't in the list
|
||||
if calendar not in self.calendars:
|
||||
self.addPath(os.path.join(self.dir, calendar), refresh=False)
|
||||
|
||||
# Fetching name
|
||||
path = os.path.join(self.dir, calendar, "displayname")
|
||||
with open(path, "r") as f:
|
||||
self.names[calendar] = f.read().strip()
|
||||
|
||||
# Fetching color
|
||||
path = os.path.join(self.dir, calendar, "color")
|
||||
with open(path, "r") as f:
|
||||
self.colors[calendar] = f.read().strip()
|
||||
self.calendars = calendars
|
||||
|
||||
def __init__(self, dir, theme=None):
|
||||
"""
|
||||
:parm str dir: [main]path value in todoman.conf
|
||||
"""
|
||||
InotifyUpdater.__init__(self)
|
||||
ColorCountsSection.__init__(self, theme=theme)
|
||||
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||
assert os.path.isdir(self.dir)
|
||||
|
||||
self.calendars = []
|
||||
self.colors = dict()
|
||||
self.names = dict()
|
||||
self.updateCalendarList()
|
||||
self.refreshData()
|
||||
|
||||
def countUndone(self, calendar):
|
||||
cmd = ["todo", "--porcelain", "list"]
|
||||
if calendar:
|
||||
cmd.append(self.names[calendar])
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
data = json.loads(proc.stdout)
|
||||
return len(data)
|
||||
|
||||
def subfetcher(self):
|
||||
counts = []
|
||||
|
||||
# TODO This an ugly optimisation that cuts on features, but todoman
|
||||
# calls are very expensive so we keep that in the meanwhile
|
||||
if self.state < 2:
|
||||
c = self.countUndone(None)
|
||||
if c > 0:
|
||||
counts.append((c, "#00000"))
|
||||
counts.append((0, "#FFFFF"))
|
||||
return counts
|
||||
# Optimisation ends here
|
||||
|
||||
for calendar in self.calendars:
|
||||
c = self.countUndone(calendar)
|
||||
if c <= 0:
|
||||
continue
|
||||
counts.append((c, self.colors[calendar]))
|
||||
return counts
|
||||
|
||||
|
||||
class I3WindowTitleProvider(Section, I3Updater):
|
||||
# TODO FEAT To make this available from start, we need to find the
|
||||
# `focused=True` element following the `focus` array
|
||||
# TODO Feat Make this output dependant if wanted
|
||||
def on_window(self, i3, e):
|
||||
self.updateText(e.container.name)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
I3Updater.__init__(self)
|
||||
Section.__init__(self, theme=theme)
|
||||
self.on("window", self.on_window)
|
||||
|
||||
|
||||
class I3WorkspacesProviderSection(Section):
|
||||
def selectTheme(self):
|
||||
if self.urgent:
|
||||
return self.parent.themeUrgent
|
||||
elif self.focused:
|
||||
return self.parent.themeFocus
|
||||
else:
|
||||
return self.parent.themeNormal
|
||||
|
||||
# TODO On mode change the state (shown / hidden) gets overriden so every
|
||||
# tab is shown
|
||||
|
||||
def show(self):
|
||||
self.updateTheme(self.selectTheme())
|
||||
self.updateText(self.fullName if self.focused else self.shortName)
|
||||
|
||||
def changeState(self, focused, urgent):
|
||||
self.focused = focused
|
||||
self.urgent = urgent
|
||||
self.show()
|
||||
|
||||
def setName(self, name):
|
||||
self.shortName = name
|
||||
self.fullName = (
|
||||
self.parent.customNames[name] if name in self.parent.customNames else name
|
||||
)
|
||||
|
||||
def switchTo(self):
|
||||
self.parent.i3.command("workspace {}".format(self.shortName))
|
||||
|
||||
def __init__(self, name, parent):
|
||||
Section.__init__(self)
|
||||
self.parent = parent
|
||||
self.setName(name)
|
||||
self.setDecorators(clickLeft=self.switchTo)
|
||||
self.tempText = None
|
||||
|
||||
def empty(self):
|
||||
self.updateTheme(self.parent.themeNormal)
|
||||
self.updateText(None)
|
||||
|
||||
def tempShow(self):
|
||||
self.updateText(self.tempText)
|
||||
|
||||
def tempEmpty(self):
|
||||
self.tempText = self.dstText[1]
|
||||
self.updateText(None)
|
||||
|
||||
|
||||
class I3WorkspacesProvider(Section, I3Updater):
|
||||
# TODO FEAT Multi-screen
|
||||
|
||||
def initialPopulation(self, parent):
|
||||
"""
|
||||
Called on init
|
||||
Can't reuse addWorkspace since i3.get_workspaces() gives dict and not
|
||||
ConObjects
|
||||
"""
|
||||
workspaces = self.i3.get_workspaces()
|
||||
lastSection = self.modeSection
|
||||
for workspace in workspaces:
|
||||
# if parent.display != workspace["display"]:
|
||||
# continue
|
||||
|
||||
section = I3WorkspacesProviderSection(workspace.name, self)
|
||||
section.focused = workspace.focused
|
||||
section.urgent = workspace.urgent
|
||||
section.show()
|
||||
parent.addSectionAfter(lastSection, section)
|
||||
self.sections[workspace.num] = section
|
||||
|
||||
lastSection = section
|
||||
|
||||
def on_workspace_init(self, i3, e):
|
||||
workspace = e.current
|
||||
i = workspace.num
|
||||
if i in self.sections:
|
||||
section = self.sections[i]
|
||||
else:
|
||||
# Find the section just before
|
||||
while i not in self.sections.keys() and i > 0:
|
||||
i -= 1
|
||||
prevSection = self.sections[i] if i != 0 else self.modeSection
|
||||
|
||||
section = I3WorkspacesProviderSection(workspace.name, self)
|
||||
prevSection.appendAfter(section)
|
||||
self.sections[workspace.num] = section
|
||||
section.focused = workspace.focused
|
||||
section.urgent = workspace.urgent
|
||||
section.show()
|
||||
|
||||
def on_workspace_empty(self, i3, e):
|
||||
self.sections[e.current.num].empty()
|
||||
|
||||
def on_workspace_focus(self, i3, e):
|
||||
self.sections[e.old.num].focused = False
|
||||
self.sections[e.old.num].show()
|
||||
self.sections[e.current.num].focused = True
|
||||
self.sections[e.current.num].show()
|
||||
|
||||
def on_workspace_urgent(self, i3, e):
|
||||
self.sections[e.current.num].urgent = e.current.urgent
|
||||
self.sections[e.current.num].show()
|
||||
|
||||
def on_workspace_rename(self, i3, e):
|
||||
self.sections[e.current.num].setName(e.name)
|
||||
self.sections[e.current.num].show()
|
||||
|
||||
def on_mode(self, i3, e):
|
||||
if e.change == "default":
|
||||
self.modeSection.updateText(None)
|
||||
for section in self.sections.values():
|
||||
section.tempShow()
|
||||
else:
|
||||
self.modeSection.updateText(e.change)
|
||||
for section in self.sections.values():
|
||||
section.tempEmpty()
|
||||
|
||||
def __init__(
|
||||
self, theme=0, themeFocus=3, themeUrgent=1, themeMode=2, customNames=dict()
|
||||
):
|
||||
I3Updater.__init__(self)
|
||||
Section.__init__(self)
|
||||
self.themeNormal = theme
|
||||
self.themeFocus = themeFocus
|
||||
self.themeUrgent = themeUrgent
|
||||
self.customNames = customNames
|
||||
|
||||
self.sections = dict()
|
||||
self.on("workspace::init", self.on_workspace_init)
|
||||
self.on("workspace::focus", self.on_workspace_focus)
|
||||
self.on("workspace::empty", self.on_workspace_empty)
|
||||
self.on("workspace::urgent", self.on_workspace_urgent)
|
||||
self.on("workspace::rename", self.on_workspace_rename)
|
||||
# TODO Un-handled/tested: reload, rename, restored, move
|
||||
|
||||
self.on("mode", self.on_mode)
|
||||
self.modeSection = Section(theme=themeMode)
|
||||
|
||||
def addParent(self, parent):
|
||||
self.parents.add(parent)
|
||||
parent.addSection(self.modeSection)
|
||||
self.initialPopulation(parent)
|
||||
|
||||
|
||||
class MpdProvider(Section, ThreadedUpdater):
|
||||
# TODO FEAT More informations and controls
|
||||
|
||||
MAX_LENGTH = 50
|
||||
|
||||
def connect(self):
|
||||
self.mpd.connect("localhost", 6600)
|
||||
|
||||
def __init__(self, theme=None):
|
||||
ThreadedUpdater.__init__(self)
|
||||
Section.__init__(self, theme)
|
||||
|
||||
self.mpd = mpd.MPDClient()
|
||||
self.connect()
|
||||
self.refreshData()
|
||||
self.start()
|
||||
|
||||
def fetcher(self):
|
||||
stat = self.mpd.status()
|
||||
if not len(stat) or stat["state"] == "stop":
|
||||
return None
|
||||
|
||||
cur = self.mpd.currentsong()
|
||||
if not len(cur):
|
||||
return None
|
||||
|
||||
infos = []
|
||||
|
||||
def tryAdd(field):
|
||||
if field in cur:
|
||||
infos.append(cur[field])
|
||||
|
||||
tryAdd("title")
|
||||
tryAdd("album")
|
||||
tryAdd("artist")
|
||||
|
||||
infosStr = " - ".join(infos)
|
||||
if len(infosStr) > MpdProvider.MAX_LENGTH:
|
||||
infosStr = infosStr[: MpdProvider.MAX_LENGTH - 1] + "…"
|
||||
|
||||
return " {}".format(infosStr)
|
||||
|
||||
def loop(self):
|
||||
try:
|
||||
self.mpd.idle("player")
|
||||
self.refreshData()
|
||||
except mpd.base.ConnectionError as e:
|
||||
log.warn(e, exc_info=True)
|
||||
self.connect()
|
||||
except BaseException as e:
|
||||
log.error(e, exc_info=True)
|
271
hm/frobar/frobar/updaters.py
Normal file
271
hm/frobar/frobar/updaters.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
import coloredlogs
|
||||
import i3ipc
|
||||
import pyinotify
|
||||
|
||||
from frobar.display import Text
|
||||
from frobar.notbusy import notBusy
|
||||
|
||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||
log = logging.getLogger()
|
||||
|
||||
# TODO Sync bar update with PeriodicUpdater updates
|
||||
|
||||
|
||||
|
||||
class Updater:
|
||||
@staticmethod
|
||||
def init():
|
||||
PeriodicUpdater.init()
|
||||
InotifyUpdater.init()
|
||||
notBusy.set()
|
||||
|
||||
def updateText(self, text):
|
||||
print(text)
|
||||
|
||||
def fetcher(self):
|
||||
return "{} refreshed".format(self)
|
||||
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def refreshData(self):
|
||||
# TODO OPTI Maybe discard the refresh if there's already another one?
|
||||
self.lock.acquire()
|
||||
try:
|
||||
data = self.fetcher()
|
||||
except BaseException as e:
|
||||
log.error(e, exc_info=True)
|
||||
data = ""
|
||||
self.updateText(data)
|
||||
self.lock.release()
|
||||
|
||||
|
||||
class PeriodicUpdaterThread(threading.Thread):
|
||||
def run(self):
|
||||
# TODO Sync with system clock
|
||||
counter = 0
|
||||
while True:
|
||||
notBusy.set()
|
||||
if PeriodicUpdater.intervalsChanged.wait(
|
||||
timeout=PeriodicUpdater.intervalStep
|
||||
):
|
||||
# ↑ sleeps here
|
||||
notBusy.clear()
|
||||
PeriodicUpdater.intervalsChanged.clear()
|
||||
counter = 0
|
||||
for providerList in PeriodicUpdater.intervals.copy().values():
|
||||
for provider in providerList.copy():
|
||||
provider.refreshData()
|
||||
else:
|
||||
notBusy.clear()
|
||||
counter += PeriodicUpdater.intervalStep
|
||||
counter = counter % PeriodicUpdater.intervalLoop
|
||||
for interval in PeriodicUpdater.intervals.keys():
|
||||
if counter % interval == 0:
|
||||
for provider in PeriodicUpdater.intervals[interval]:
|
||||
provider.refreshData()
|
||||
|
||||
|
||||
class PeriodicUpdater(Updater):
|
||||
"""
|
||||
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||
"""
|
||||
|
||||
intervals = dict()
|
||||
intervalStep = None
|
||||
intervalLoop = None
|
||||
updateThread = PeriodicUpdaterThread(daemon=True)
|
||||
intervalsChanged = threading.Event()
|
||||
|
||||
@staticmethod
|
||||
def gcds(*args):
|
||||
return functools.reduce(math.gcd, args)
|
||||
|
||||
@staticmethod
|
||||
def lcm(a, b):
|
||||
"""Return lowest common multiple."""
|
||||
return a * b // math.gcd(a, b)
|
||||
|
||||
@staticmethod
|
||||
def lcms(*args):
|
||||
"""Return lowest common multiple."""
|
||||
return functools.reduce(PeriodicUpdater.lcm, args)
|
||||
|
||||
@staticmethod
|
||||
def updateIntervals():
|
||||
intervalsList = list(PeriodicUpdater.intervals.keys())
|
||||
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
||||
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
||||
PeriodicUpdater.intervalsChanged.set()
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
PeriodicUpdater.updateThread.start()
|
||||
|
||||
def __init__(self):
|
||||
Updater.__init__(self)
|
||||
self.interval = None
|
||||
|
||||
def changeInterval(self, interval):
|
||||
assert isinstance(interval, int)
|
||||
|
||||
if self.interval is not None:
|
||||
PeriodicUpdater.intervals[self.interval].remove(self)
|
||||
|
||||
self.interval = interval
|
||||
|
||||
if interval not in PeriodicUpdater.intervals:
|
||||
PeriodicUpdater.intervals[interval] = set()
|
||||
PeriodicUpdater.intervals[interval].add(self)
|
||||
|
||||
PeriodicUpdater.updateIntervals()
|
||||
|
||||
|
||||
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
||||
def process_default(self, event):
|
||||
# DEBUG
|
||||
# from pprint import pprint
|
||||
# pprint(event.__dict__)
|
||||
# return
|
||||
|
||||
assert event.path in InotifyUpdater.paths
|
||||
|
||||
if 0 in InotifyUpdater.paths[event.path]:
|
||||
for provider in InotifyUpdater.paths[event.path][0]:
|
||||
provider.refreshData()
|
||||
|
||||
if event.name in InotifyUpdater.paths[event.path]:
|
||||
for provider in InotifyUpdater.paths[event.path][event.name]:
|
||||
provider.refreshData()
|
||||
|
||||
|
||||
class InotifyUpdater(Updater):
|
||||
"""
|
||||
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||
"""
|
||||
|
||||
wm = pyinotify.WatchManager()
|
||||
paths = dict()
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
notifier = pyinotify.ThreadedNotifier(
|
||||
InotifyUpdater.wm, InotifyUpdaterEventHandler()
|
||||
)
|
||||
notifier.start()
|
||||
|
||||
# TODO Mask for folders
|
||||
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
||||
|
||||
def addPath(self, path, refresh=True):
|
||||
path = os.path.realpath(os.path.expanduser(path))
|
||||
|
||||
# Detect if file or folder
|
||||
if os.path.isdir(path):
|
||||
self.dirpath = path
|
||||
# 0: Directory watcher
|
||||
self.filename = 0
|
||||
elif os.path.isfile(path):
|
||||
self.dirpath = os.path.dirname(path)
|
||||
self.filename = os.path.basename(path)
|
||||
else:
|
||||
raise FileNotFoundError("No such file or directory: '{}'".format(path))
|
||||
|
||||
# Register watch action
|
||||
if self.dirpath not in InotifyUpdater.paths:
|
||||
InotifyUpdater.paths[self.dirpath] = dict()
|
||||
if self.filename not in InotifyUpdater.paths[self.dirpath]:
|
||||
InotifyUpdater.paths[self.dirpath][self.filename] = set()
|
||||
InotifyUpdater.paths[self.dirpath][self.filename].add(self)
|
||||
|
||||
# Add watch
|
||||
InotifyUpdater.wm.add_watch(self.dirpath, InotifyUpdater.MASK)
|
||||
|
||||
if refresh:
|
||||
self.refreshData()
|
||||
|
||||
|
||||
class ThreadedUpdaterThread(threading.Thread):
|
||||
def __init__(self, updater, *args, **kwargs):
|
||||
self.updater = updater
|
||||
threading.Thread.__init__(self, *args, **kwargs)
|
||||
self.looping = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while self.looping:
|
||||
self.updater.loop()
|
||||
except BaseException as e:
|
||||
log.error("Error with {}".format(self.updater))
|
||||
log.error(e, exc_info=True)
|
||||
self.updater.updateText("")
|
||||
|
||||
|
||||
class ThreadedUpdater(Updater):
|
||||
"""
|
||||
Must implement loop(), and call start()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
Updater.__init__(self)
|
||||
self.thread = ThreadedUpdaterThread(self, daemon=True)
|
||||
|
||||
def loop(self):
|
||||
self.refreshData()
|
||||
time.sleep(10)
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
|
||||
|
||||
class I3Updater(ThreadedUpdater):
|
||||
# TODO OPTI One i3 connection for all
|
||||
|
||||
def __init__(self):
|
||||
ThreadedUpdater.__init__(self)
|
||||
self.i3 = i3ipc.Connection()
|
||||
self.start()
|
||||
|
||||
def on(self, event, function):
|
||||
self.i3.on(event, function)
|
||||
|
||||
def loop(self):
|
||||
self.i3.main()
|
||||
|
||||
|
||||
class MergedUpdater(Updater):
|
||||
# TODO OPTI Do not update until end of periodic batch
|
||||
def fetcher(self):
|
||||
text = Text()
|
||||
for updater in self.updaters:
|
||||
text.append(self.texts[updater])
|
||||
if not len(text):
|
||||
return None
|
||||
return text
|
||||
|
||||
def __init__(self, *args):
|
||||
Updater.__init__(self)
|
||||
|
||||
self.updaters = []
|
||||
self.texts = dict()
|
||||
|
||||
for updater in args:
|
||||
assert isinstance(updater, Updater)
|
||||
|
||||
def newUpdateText(updater, text):
|
||||
self.texts[updater] = text
|
||||
self.refreshData()
|
||||
|
||||
updater.updateText = newUpdateText.__get__(updater, Updater)
|
||||
|
||||
self.updaters.append(updater)
|
||||
self.texts[updater] = ""
|
20
hm/frobar/setup.py
Normal file
20
hm/frobar/setup.py
Normal 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
26
hm/inputrc
Normal 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
12
hm/loader.nix
Normal 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
47
hm/pythonstartup.py
Normal 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
9
hm/screenrc
Normal 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
47
hm/style.nix
Normal 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
17
hm/tmux.conf
Normal 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
408
hm/vim.nix
Normal 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
167
hm/vim/feline.lua
Normal 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
9
hm/vim/feline_test.vim
Normal 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
|
3
hm/vim/lsp_signature-nvim.lua
Normal file
3
hm/vim/lsp_signature-nvim.lua
Normal file
|
@ -0,0 +1,3 @@
|
|||
require'lsp_signature'.on_attach({
|
||||
hint_enable = false,
|
||||
})
|
72
hm/vim/nvim-compe.lua
Normal file
72
hm/vim/nvim-compe.lua
Normal 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})
|
7
hm/vim/nvim-ts-rainbow.lua
Normal file
7
hm/vim/nvim-ts-rainbow.lua
Normal 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
|
||||
}
|
||||
}
|
19
hm/vim/symbols-outline-nvim.lua
Normal file
19
hm/vim/symbols-outline-nvim.lua
Normal 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
112
hm/zshrc.sh
Normal 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.
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue