From e9e2f75fa2e5e5af456cca3b022a908152bada1c Mon Sep 17 00:00:00 2001 From: diego castillo salazar Date: Tue, 1 Nov 2022 13:12:15 -0500 Subject: [PATCH] Uploading config dotfiles --- .Xauthority | Bin 0 -> 49 bytes .aliases | 91 + .asoundrc | 5 + .config/asound.state | 351 +++ .config/nnn/.selection | 0 .config/nnn/plugins/.cbcp | 51 + .config/nnn/plugins/.iconlookup | 428 ++++ .config/nnn/plugins/.nmv | 180 ++ .config/nnn/plugins/.nnn-plugin-helper | 38 + .config/nnn/plugins/.ntfy | 22 + .config/nnn/plugins/autojump | 74 + .config/nnn/plugins/boom | 50 + .config/nnn/plugins/bulknew | 32 + .config/nnn/plugins/cdpath | 56 + .config/nnn/plugins/chksum | 72 + .config/nnn/plugins/cmusq | 80 + .config/nnn/plugins/di-nuke | 79 + .config/nnn/plugins/diffs | 62 + .config/nnn/plugins/dragdrop | 77 + .config/nnn/plugins/dups | 70 + .config/nnn/plugins/finder | 89 + .config/nnn/plugins/fixname | 75 + .config/nnn/plugins/fzcd | 89 + .config/nnn/plugins/fzhist | 40 + .config/nnn/plugins/fzopen | 83 + .config/nnn/plugins/fzplug | 59 + .config/nnn/plugins/getplugs | 70 + .config/nnn/plugins/gitroot | 15 + .config/nnn/plugins/gpgd | 28 + .config/nnn/plugins/gpge | 44 + .config/nnn/plugins/gsconnect | 21 + .config/nnn/plugins/gutenread | 49 + .config/nnn/plugins/imgresize | 31 + .config/nnn/plugins/imgur | 597 +++++ .config/nnn/plugins/imgview | 111 + .config/nnn/plugins/ipinfo | 13 + .config/nnn/plugins/kdeconnect | 24 + .config/nnn/plugins/launch | 42 + .config/nnn/plugins/mimelist | 15 + .config/nnn/plugins/moclyrics | 40 + .config/nnn/plugins/mocq | 89 + .config/nnn/plugins/mp3conv | 41 + .config/nnn/plugins/mtpmount | 76 + .config/nnn/plugins/nbak | 75 + .config/nnn/plugins/nmount | 55 + .config/nnn/plugins/nuke | 555 +++++ .config/nnn/plugins/oldbigfile | 16 + .config/nnn/plugins/openall | 49 + .config/nnn/plugins/organize | 62 + .config/nnn/plugins/pdfread | 30 + .config/nnn/plugins/preview-tabbed | 211 ++ .config/nnn/plugins/preview-tui | 481 ++++ .config/nnn/plugins/pskill | 35 + .config/nnn/plugins/renamer | 45 + .config/nnn/plugins/ringtone | 36 + .config/nnn/plugins/rsynccp | 26 + .config/nnn/plugins/splitjoin | 52 + .config/nnn/plugins/suedit | 16 + .config/nnn/plugins/togglex | 21 + .config/nnn/plugins/umounttree | 52 + .config/nnn/plugins/upload | 30 + .config/nnn/plugins/wallpaper | 26 + .config/nnn/plugins/x2sel | 62 + .config/nnn/plugins/xdgdefault | 53 + .config/qutebrowser/autoconfig.yml | 10 + .config/qutebrowser/bookmarks/urls | 0 .config/qutebrowser/config.py | 527 +++++ .../qutebrowser/greasemonkey/block-yt-ads.js | 18 + .config/qutebrowser/qsettings/QtProject.conf | 8 + .config/qutebrowser/quickmarks | 0 .config/sbase | 1 + .config/startpages/minim/favicon.svg | 1 + .config/startpages/minim/index.html | 113 + .config/startpages/minim/js/script.js | 65 + .config/startpages/minim/robots.txt | 2 + .config/startpages/minim/style/main.css | 111 + .config/startpages/term/index.html | 58 + .config/startpages/term/manifest.json | 10 + .config/startpages/term/style.css | 106 + .config/vis/plugins/complete-filename.lua | 56 + .config/vis/plugins/complete-word.lua | 22 + .config/vis/plugins/digraph.lua | 23 + .config/vis/plugins/filetype.lua | 609 +++++ .config/vis/plugins/number-inc-dec.lua | 59 + .config/vis/plugins/textobject-lexer.lua | 31 + .config/vis/plugins/vis-filetype-settings.lua | 39 + .../vis/plugins/vis-spellcheck/.editorconfig | 11 + .../vis/plugins/vis-spellcheck/.gitlab-ci.yml | 23 + .../vis/plugins/vis-spellcheck/.lua-format | 27 + .config/vis/plugins/vis-spellcheck/LICENSE | 19 + .config/vis/plugins/vis-spellcheck/Makefile | 14 + .config/vis/plugins/vis-spellcheck/Readme.md | 45 + .config/vis/plugins/vis-spellcheck/init.lua | 6 + .../vis/plugins/vis-spellcheck/spellcheck.lua | 445 ++++ .../plugins/vis-spellcheck/tools/check-format | 8 + .config/vis/plugins/vis-vim-compatible.lua | 20 + .config/vis/themes/base16-grayscale-dark.lua | 61 + .config/vis/themes/dark-16.lua | 37 + .config/vis/themes/default-16.lua | 1 + .config/vis/themes/default-256.lua | 1 + .config/vis/themes/light-16.lua | 37 + .config/vis/themes/solarized.lua | 69 + .config/vis/themes/zenburn.lua | 39 + .config/vis/vis.lua | 306 +++ .config/vis/visrc.lua | 52 + .config/ytfzf/conf.sh | 13 + .config/zathura/zathurarc | 36 + .crontab | 19 + .gitconfig | 2 + .mkshrc | 19 + .profile | 38 + .xinitrc | 55 + Scripts/DWM | 5 + Scripts/autogit | 83 + Scripts/brightness | 19 + Scripts/dmenurecord | 134 ++ Scripts/dwm-screenshot | 38 + Scripts/menu-apagar | 27 + Scripts/music | 24 + Scripts/pash | 240 ++ Scripts/passmenu2 | 60 + Scripts/pulsemixer | 2049 +++++++++++++++++ Scripts/scriptfetch | 57 + Scripts/sctpad | 8 + Scripts/tabn3 | 5 + Scripts/udevil-mounter | 62 + Scripts/vi-nuke | 79 + 127 files changed, 11709 insertions(+) create mode 100644 .Xauthority create mode 100644 .aliases create mode 100644 .asoundrc create mode 100644 .config/asound.state create mode 100644 .config/nnn/.selection create mode 100755 .config/nnn/plugins/.cbcp create mode 100755 .config/nnn/plugins/.iconlookup create mode 100755 .config/nnn/plugins/.nmv create mode 100644 .config/nnn/plugins/.nnn-plugin-helper create mode 100755 .config/nnn/plugins/.ntfy create mode 100755 .config/nnn/plugins/autojump create mode 100755 .config/nnn/plugins/boom create mode 100755 .config/nnn/plugins/bulknew create mode 100755 .config/nnn/plugins/cdpath create mode 100755 .config/nnn/plugins/chksum create mode 100755 .config/nnn/plugins/cmusq create mode 100755 .config/nnn/plugins/di-nuke create mode 100755 .config/nnn/plugins/diffs create mode 100755 .config/nnn/plugins/dragdrop create mode 100755 .config/nnn/plugins/dups create mode 100755 .config/nnn/plugins/finder create mode 100755 .config/nnn/plugins/fixname create mode 100755 .config/nnn/plugins/fzcd create mode 100755 .config/nnn/plugins/fzhist create mode 100755 .config/nnn/plugins/fzopen create mode 100755 .config/nnn/plugins/fzplug create mode 100755 .config/nnn/plugins/getplugs create mode 100755 .config/nnn/plugins/gitroot create mode 100755 .config/nnn/plugins/gpgd create mode 100755 .config/nnn/plugins/gpge create mode 100755 .config/nnn/plugins/gsconnect create mode 100755 .config/nnn/plugins/gutenread create mode 100755 .config/nnn/plugins/imgresize create mode 100755 .config/nnn/plugins/imgur create mode 100755 .config/nnn/plugins/imgview create mode 100755 .config/nnn/plugins/ipinfo create mode 100755 .config/nnn/plugins/kdeconnect create mode 100755 .config/nnn/plugins/launch create mode 100755 .config/nnn/plugins/mimelist create mode 100755 .config/nnn/plugins/moclyrics create mode 100755 .config/nnn/plugins/mocq create mode 100755 .config/nnn/plugins/mp3conv create mode 100755 .config/nnn/plugins/mtpmount create mode 100755 .config/nnn/plugins/nbak create mode 100755 .config/nnn/plugins/nmount create mode 100755 .config/nnn/plugins/nuke create mode 100755 .config/nnn/plugins/oldbigfile create mode 100755 .config/nnn/plugins/openall create mode 100755 .config/nnn/plugins/organize create mode 100755 .config/nnn/plugins/pdfread create mode 100755 .config/nnn/plugins/preview-tabbed create mode 100755 .config/nnn/plugins/preview-tui create mode 100755 .config/nnn/plugins/pskill create mode 100755 .config/nnn/plugins/renamer create mode 100755 .config/nnn/plugins/ringtone create mode 100755 .config/nnn/plugins/rsynccp create mode 100755 .config/nnn/plugins/splitjoin create mode 100755 .config/nnn/plugins/suedit create mode 100755 .config/nnn/plugins/togglex create mode 100755 .config/nnn/plugins/umounttree create mode 100755 .config/nnn/plugins/upload create mode 100755 .config/nnn/plugins/wallpaper create mode 100755 .config/nnn/plugins/x2sel create mode 100755 .config/nnn/plugins/xdgdefault create mode 100644 .config/qutebrowser/autoconfig.yml create mode 100644 .config/qutebrowser/bookmarks/urls create mode 100644 .config/qutebrowser/config.py create mode 100644 .config/qutebrowser/greasemonkey/block-yt-ads.js create mode 100644 .config/qutebrowser/qsettings/QtProject.conf create mode 100644 .config/qutebrowser/quickmarks create mode 160000 .config/sbase create mode 100644 .config/startpages/minim/favicon.svg create mode 100644 .config/startpages/minim/index.html create mode 100644 .config/startpages/minim/js/script.js create mode 100644 .config/startpages/minim/robots.txt create mode 100644 .config/startpages/minim/style/main.css create mode 100644 .config/startpages/term/index.html create mode 100644 .config/startpages/term/manifest.json create mode 100644 .config/startpages/term/style.css create mode 100644 .config/vis/plugins/complete-filename.lua create mode 100644 .config/vis/plugins/complete-word.lua create mode 100644 .config/vis/plugins/digraph.lua create mode 100644 .config/vis/plugins/filetype.lua create mode 100644 .config/vis/plugins/number-inc-dec.lua create mode 100644 .config/vis/plugins/textobject-lexer.lua create mode 100644 .config/vis/plugins/vis-filetype-settings.lua create mode 100644 .config/vis/plugins/vis-spellcheck/.editorconfig create mode 100644 .config/vis/plugins/vis-spellcheck/.gitlab-ci.yml create mode 100644 .config/vis/plugins/vis-spellcheck/.lua-format create mode 100644 .config/vis/plugins/vis-spellcheck/LICENSE create mode 100644 .config/vis/plugins/vis-spellcheck/Makefile create mode 100644 .config/vis/plugins/vis-spellcheck/Readme.md create mode 100644 .config/vis/plugins/vis-spellcheck/init.lua create mode 100644 .config/vis/plugins/vis-spellcheck/spellcheck.lua create mode 100755 .config/vis/plugins/vis-spellcheck/tools/check-format create mode 100644 .config/vis/plugins/vis-vim-compatible.lua create mode 100644 .config/vis/themes/base16-grayscale-dark.lua create mode 100644 .config/vis/themes/dark-16.lua create mode 120000 .config/vis/themes/default-16.lua create mode 120000 .config/vis/themes/default-256.lua create mode 100644 .config/vis/themes/light-16.lua create mode 100644 .config/vis/themes/solarized.lua create mode 100644 .config/vis/themes/zenburn.lua create mode 100644 .config/vis/vis.lua create mode 100644 .config/vis/visrc.lua create mode 100755 .config/ytfzf/conf.sh create mode 100644 .config/zathura/zathurarc create mode 100644 .crontab create mode 100644 .gitconfig create mode 100644 .mkshrc create mode 100644 .profile create mode 100644 .xinitrc create mode 100755 Scripts/DWM create mode 100755 Scripts/autogit create mode 100755 Scripts/brightness create mode 100755 Scripts/dmenurecord create mode 100755 Scripts/dwm-screenshot create mode 100755 Scripts/menu-apagar create mode 100755 Scripts/music create mode 100755 Scripts/pash create mode 100755 Scripts/passmenu2 create mode 100755 Scripts/pulsemixer create mode 100755 Scripts/scriptfetch create mode 100755 Scripts/sctpad create mode 100755 Scripts/tabn3 create mode 100755 Scripts/udevil-mounter create mode 100755 Scripts/vi-nuke diff --git a/.Xauthority b/.Xauthority new file mode 100644 index 0000000000000000000000000000000000000000..9597377cbffad713224c70456b9a0e3feb8c89e8 GIT binary patch literal 49 zcmZQ%U|=cB&rD%pG++?&^$gMVb#(W1)^+yx_x5zvHDnN2_iD*+i%Gl(EFS#ay{qeQ FIRG%85gGsh literal 0 HcmV?d00001 diff --git a/.aliases b/.aliases new file mode 100644 index 0000000..34c9ebb --- /dev/null +++ b/.aliases @@ -0,0 +1,91 @@ +#alias startx + +alias sx='startx' + +# alias doas + +alias ds='doas' + +# alias xbps + +alias xbl='xbps-query -m | column' +alias xbup='doas xbps-install -Syu' +alias xbins='doas xbps-install -Su' +alias xbdel='doas xbps-remove -RcOon' +alias xbclean='doas xbps-remove -oO' +alias xbser='xbps-query -Rs' +alias xbse='xbps-query -s' +alias xbd='xbps-query -Rx' +alias xbdi='xbps-query -RX' +alias xbrc='doas xbps-reconfigure -f' +alias xbh='doas xbps-pkgdb -m hold' +alias xbuh='doas xbps-pkgdb -m unhold' + + +# alias xbps-src + +alias srclean='cd $HOME/void-packages/ && ./xbps-src clean-repocache && ./xbps-src clean && ./xbps-src remove-autodeps && rm -rf hostdir/sources/* && xbps-rindex -r ~/void-packages/hostdir/binpkgs && xbps-rindex -c ~/void-packages/hostdir/binpkgs' +alias srcins='cd $HOME/void-packages/ && doas xbps-install -R ./hostdir/binpkgs' +alias srcinf='cd $HOME/void-packages/ && doas xbps-install -f ./hostdir/binpkgs' +alias srcin='doas xbps-install -fR home/diegofcs/void-packages/hostdir/binpkgs/nonfree' +alias srcmp='cd $HOME/void-packages/ && ./xbps-src -j $(nproc) pkg' +alias srcmf='cd $HOME/void-packages/ && ./xbps-src -j $(nproc) -f pkg' +alias srcser='ls $HOME/.config/void-packages/srcpkgs | grep' +alias xbo='cd $HOME/void-packages/ && ./xbps-src show-options' +alias xboc='cd $HOME/void-packages./xbps-query -R --property=build-options' + + +# alias status services + +alias svl='ds sv status /var/service/*' + +# alias patch + +alias pt='patch -p1 <' +alias cmp='doas make clean install' + +# alias passmenu2 + +alias pss='passmenu2' + +# alias varios + +alias v='vis' +alias c='cd' +alias cl='clear' +alias chm='chmod +x' +alias chmr='chmod -x' +alias sz='du -sh' +alias cpr="cp -r" +alias rmr='rm -r' +alias ls='ls --color -h --group-directories-first -lha' +alias ..='cd ..' +alias ...='cd ../..' +alias ....='cd ../../..' +alias .....='cd ../../../..' +alias ......='cd ../../../../..' +alias top='top -d 1 -u diegofcs' +alias sf='scriptfetch' +alias gup='xbup && cd void-packages && git pull && ./xbps-src bootstrap-update && xbclean && srclean' +alias n3='nnn' +alias vim='echo "Que ahora es vis, idiota... ya que nos mudamos, Ahora debe ser así -> vis"' +alias yt='yt-dlp "$(ytfzf -I L "$1")" -o - | ffplay - -autoexit -loglevel quiet' +alias mu='yt-dlp "$(ytfzf -I L "$1")" -o - | ffplay - -nodisp -autoexit -loglevel quiet' +alias ping='ds ping -c 8 voidlinux.org' + +#alias source shell & alias + +alias kreload='source $HOME/.aliases && source $HOME/.mkshrc' + +#alias wifi + +alias wf='doas wpa_passphrase' +alias dt='doas vis /etc/wpa_supplicant/wpa_supplicant.conf' +alias ctw='doas wpa_supplicant -B -D wext -i wlp0s29u1u1 -c /etc/wpa_supplicant/wpa_supplicant.conf' +alias lwf='wpa_cli list_networks' +alias swf='wpa_cli select_networks' + +#alias lugares usados + +alias qt='cd $HOME/.config/qutebrowser' +alias vs='cd $HOME/.config/vis && vis visrc.lua' diff --git a/.asoundrc b/.asoundrc new file mode 100644 index 0000000..aad36ea --- /dev/null +++ b/.asoundrc @@ -0,0 +1,5 @@ +pcm.!default { + type asym + playback.pcm "plug:dmix" + capture.pcm "plug:dsnoop" + } diff --git a/.config/asound.state b/.config/asound.state new file mode 100644 index 0000000..d22ca16 --- /dev/null +++ b/.config/asound.state @@ -0,0 +1,351 @@ +state.PCH { + control.1 { + iface MIXER + name 'Headphone Playback Volume' + value.0 87 + value.1 87 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 87' + dbmin -6525 + dbmax 0 + dbvalue.0 0 + dbvalue.1 0 + } + } + control.2 { + iface MIXER + name 'Headphone Playback Switch' + value.0 true + value.1 true + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.3 { + iface MIXER + name 'Speaker Playback Volume' + value.0 87 + value.1 87 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 87' + dbmin -6525 + dbmax 0 + dbvalue.0 0 + dbvalue.1 0 + } + } + control.4 { + iface MIXER + name 'Speaker Playback Switch' + value.0 false + value.1 false + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.5 { + iface MIXER + name 'Loopback Mixing' + value Enabled + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Disabled + item.1 Enabled + } + } + control.6 { + iface MIXER + name 'Mic Playback Volume' + value.0 31 + value.1 31 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 31' + dbmin -3450 + dbmax 1200 + dbvalue.0 1200 + dbvalue.1 1200 + } + } + control.7 { + iface MIXER + name 'Mic Playback Switch' + value.0 false + value.1 false + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.8 { + iface MIXER + name 'Auto-Mute Mode' + value Enabled + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Disabled + item.1 Enabled + } + } + control.9 { + iface MIXER + name 'Capture Volume' + value.0 39 + value.1 39 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 63' + dbmin -1725 + dbmax 3000 + dbvalue.0 1200 + dbvalue.1 1200 + } + } + control.10 { + iface MIXER + name 'Capture Switch' + value.0 true + value.1 true + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.11 { + iface MIXER + name 'Mic Boost Volume' + value.0 3 + value.1 3 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 3' + dbmin 0 + dbmax 3000 + dbvalue.0 3000 + dbvalue.1 3000 + } + } + control.12 { + iface MIXER + name 'Internal Mic Boost Volume' + value.0 3 + value.1 3 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 3' + dbmin 0 + dbmax 3000 + dbvalue.0 3000 + dbvalue.1 3000 + } + } + control.13 { + iface MIXER + name 'Master Playback Volume' + value 87 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 87' + dbmin -6525 + dbmax 0 + dbvalue.0 0 + } + } + control.14 { + iface MIXER + name 'Master Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.15 { + iface CARD + name 'Mic Jack' + value false + comment { + access read + type BOOLEAN + count 1 + } + } + control.16 { + iface CARD + name 'Internal Mic Phantom Jack' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.17 { + iface CARD + name 'Headphone Jack' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.18 { + iface CARD + name 'Speaker Phantom Jack' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.19 { + iface PCM + name 'Playback Channel Map' + value.0 3 + value.1 4 + comment { + access read + type INTEGER + count 2 + range '0 - 36' + } + } + control.20 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + comment { + access read + type INTEGER + count 2 + range '0 - 36' + } + } + control.21 { + iface CARD + name 'HDMI/DP,pcm=3 Jack' + value false + comment { + access read + type BOOLEAN + count 1 + } + } + control.22 { + iface MIXER + name 'IEC958 Playback Con Mask' + value '0fff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.23 { + iface MIXER + name 'IEC958 Playback Pro Mask' + value '0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.24 { + iface MIXER + name 'IEC958 Playback Default' + value '0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write' + type IEC958 + count 1 + } + } + control.25 { + iface MIXER + name 'IEC958 Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.26 { + iface PCM + device 3 + name ELD + value '' + comment { + access 'read volatile' + type BYTES + count 0 + } + } + control.27 { + iface PCM + device 3 + name 'Playback Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + comment { + access 'read write' + type INTEGER + count 8 + range '0 - 36' + } + } + control.28 { + iface MIXER + name 'PCM Playback Volume' + value.0 255 + value.1 255 + comment { + access 'read write user' + type INTEGER + count 2 + range '0 - 255' + tlv '0000000100000008ffffec1400000014' + dbmin -5100 + dbmax 0 + dbvalue.0 0 + dbvalue.1 0 + } + } +} diff --git a/.config/nnn/.selection b/.config/nnn/.selection new file mode 100644 index 0000000..e69de29 diff --git a/.config/nnn/plugins/.cbcp b/.config/nnn/plugins/.cbcp new file mode 100755 index 0000000..70f9b75 --- /dev/null +++ b/.config/nnn/plugins/.cbcp @@ -0,0 +1,51 @@ +#!/usr/bin/env sh + +# Description: Copy selection to system clipboard as newline-separated entries +# Dependencies: +# - tr +# - xclip/xsel (Linux) +# - pbcopy (macOS) +# - termux-clipboard-set (Termux) +# - clip.exe (WSL) +# - clip (Cygwin) +# - wl-copy (Wayland) +# - clipboard (Haiku) +# +# Limitation: breaks if a filename has newline in it +# +# Note: For a space-separated list: +# xargs -0 < "$SELECTION" +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +[ -s "$selection" ] || { echo "plugin .cbcp error: empty selection" >&2 ; exit 1; } + +if type xsel >/dev/null 2>&1; then + # Linux + tr '\0' '\n' < "$selection" | xsel -bi +elif type xclip >/dev/null 2>&1; then + # Linux + tr '\0' '\n' < "$selection" | xclip -sel clip +elif type pbcopy >/dev/null 2>&1; then + # macOS + tr '\0' '\n' < "$selection" | pbcopy +elif type termux-clipboard-set >/dev/null 2>&1; then + # Termux + tr '\0' '\n' < "$selection" | termux-clipboard-set +elif type clip.exe >/dev/null 2>&1; then + # WSL + tr '\0' '\n' < "$selection" | clip.exe +elif type clip >/dev/null 2>&1; then + # Cygwin + tr '\0' '\n' < "$selection" | clip +elif type wl-copy >/dev/null 2>&1; then + # Wayland + tr '\0' '\n' < "$selection" | wl-copy +elif type clipboard >/dev/null 2>&1; then + # Haiku + tr '\0' '\n' < "$selection" | clipboard --stdin +fi diff --git a/.config/nnn/plugins/.iconlookup b/.config/nnn/plugins/.iconlookup new file mode 100755 index 0000000..d48d676 --- /dev/null +++ b/.config/nnn/plugins/.iconlookup @@ -0,0 +1,428 @@ +#!/usr/bin/env sh + +# Description: Print icons in front of list of directories/files + +# Dependencies: awk + +# Usage +# 1. Set colors and/or icons to your liking +# 2. Pipe any directory listing to iconlookup and it will output prepended icons +# 3. preview-tui uses the script to prepend icon to directory listings +# 4. Aditionally you can consider adding it to your PATH and/or FZF_DEFAULT_COMMAND to +# make it work with various fzf plugins (make sure you also add --ansi to your FZF_DEFAULT_OPTS) + +# Shell: POSIX compliant + +# Author: Luuk van Baal (https://github.com/luukvbaal/iconlookup) + +icon_lookup() { +awk 'BEGIN { +# Set your ANSI colorscheme below (https://en.wikipedia.org/wiki/ANSI_escape_code#Colors). +# Default uses standard nnn icon colors, 8 and 24-bit nord themes are commented out. + colordepth=8 #colordepth=8 #colordepth=24 + color_dirtxt=39 #color_dirtxt=111 #color_dirtxt="129;161;193" + color_filetxt=15 #color_filetxt=111 #color_filetxt="129;161;193" + color_default=39 #color_default=111 #color_default="129;161;193" + color_video=93 #color_video=110 #color_video="136;192;208" + color_audio=220 #color_audio=150 #color_audio="163;190;140" + color_image=82 #color_image=150 #color_image="163;190;140" + color_docs=202 #color_docs=173 #color_docs="208;135;112" + color_archive=209 #color_archive=179 #color_archive="235;203;139" + color_c=81 #color_c=150 #color_c="163;190;140" + color_java=32 #color_java=139 #color_java="180;142;173" + color_js=47 #color_js=109 #color_js="143;188;187" + color_react=39 #color_react=111 #color_react="129;161;193" + color_css=199 #color_css=110 #color_css="136;192;208" + color_python=227 #color_python=68 #color_python="94;129;172" + color_lua=19 #color_lua=167 #color_lua="191;97;106" + color_document=15 #color_document=173 #color_document="208;135;112" + color_fsharp=31 #color_fsharp=179 #color_fsharp="180;142;173" + color_ruby=160 #color_ruby=150 #color_ruby="163;190;140" + color_scala=196 #color_scala=139 #color_scala="143;188;187" + color_shell=47 #color_shell=109 #color_shell="143;188;187" + color_vim=28 #color_vim=109 #color_vim="143;188;187" + +# icons[][1] contains icon and icons[][2] contains color + icons["directory"][1] = ""; icons["directory"][2] = color_default + icons["file"][1] = ""; icons["file"][2] = color_default + icons["exec"][1] = ""; icons["exec"][2] = color_default + icons["manual"][1] = ""; icons["manual"][2] = color_docs + icons["pipe"][1] = "ﳣ"; icons["pipe"][2] = color_default + icons["socket"][1] = "ﳧ"; icons["socket"][2] = color_default + icons["door"][1] = "➡"; icons["door"][2] = color_default + +# top level and common icons + icons[".git"][1] = ""; icons[".git"][2] = color_default + icons["desktop"][1] = "ﲾ"; icons["desktop"][2] = color_default + icons["briefcase"][1] = ""; icons["briefcase"][2] = color_default + icons["document"][1] = ""; icons["document"][2] = color_default + icons["downloads"][1] = ""; icons["downloads"][2] = color_default + icons["music"][1] = ""; icons["music"][2] = color_default + icons["musicfile"][1] = ""; icons["musicfile"][2] = color_audio + icons["pictures"][1] = ""; icons["pictures"][2] = color_default + icons["picturefile"][1] = ""; icons["picturefile"][2] = color_image + icons["public"][1] = ""; icons["public"][2] = color_default + icons["templates"][1] = "陼"; icons["templates"][2] = color_default + icons["videos"][1] = ""; icons["videos"][2] = color_default + icons["videofile"][1] = "ﳜ"; icons["videofile"][2] = color_video + icons["changelog"][1] = ""; icons["changelog"][2] = color_docs + icons["configure"][1] = ""; icons["configure"][2] = color_default + icons["license"][1] = ""; icons["license"][2] = color_docs + icons["makefile"][1] = ""; icons["makefile"][2] = color_default + icons["archive"][1] = ""; icons["archive"][2] = color_archive + icons["script"][1] = ""; icons["script"][2] = color_shell + icons["cplusplus"][1] = ""; icons["cplusplus"][2] = color_c + icons["java"][1] = ""; icons["java"][2] = color_java + icons["clojure"][1] = ""; icons["clojure"][2] = color_default + icons["js"][1] = ""; icons["js"][2] = color_js + icons["linux"][1] = ""; icons["linux"][2] = color_default + icons["fsharp"][1] = ""; icons["fsharp"][2] = color_fsharp + icons["ruby"][1] = ""; icons["ruby"][2] = color_ruby + icons["c"][1] = ""; icons["c"][2] = color_c + icons["chess"][1] = ""; icons["chess"][2] = color_default + icons["haskell"][1] = ""; icons["haskell"][2] = color_vim + icons["html"][1] = ""; icons["html"][2] = color_default + icons["react"][1] = ""; icons["react"][2] = color_react + icons["python"][1] = ""; icons["python"][2] = color_python + icons["database"][1] = ""; icons["database"][2] = color_default + icons["worddoc"][1] = ""; icons["worddoc"][2] = color_document + icons["playlist"][1] = "蘿"; icons["playlist"][2] = color_audio + icons["opticaldisk"][1] = ""; icons["opticaldisk"][2] = color_archive + +# numbers + icons["1"][1] = icons["manual"][1]; icons["1"][2] = icons["manual"][2] + icons["7z"][1] = icons["archive"][1]; icons["7z"][2] = icons["archive"][2] + +# a + icons["a"][1] = icons["manual"][1]; icons["a"][2] = icons["manual"][2] + icons["apk"][1] = icons["archive"][1]; icons["apk"][2] = icons["archive"][2] + icons["asm"][1] = icons["file"][1]; icons["asm"][2] = icons["file"][2] + icons["aup"][1] = icons["musicfile"][1]; icons["aup"][2] = icons["musicfile"][2] + icons["avi"][1] = icons["videofile"][1]; icons["avi"][2] = icons["videofile"][2] + +# b + icons["bat"][1] = icons["script"][1]; icons["bat"][2] = icons["script"][2] + icons["bin"][1] = ""; icons["bin"][2] = color_default + icons["bmp"][1] = icons["picturefile"][1]; icons["bmp"][2] = icons["picturefile"][2] + icons["bz2"][1] = icons["archive"][1]; icons["bz2"][2] = icons["archive"][2] + +# c + icons["cplusplus"][1] = icons["cplusplus"][1]; icons["cplusplus"][2] = icons["cplusplus"][2] + icons["cabal"][1] = icons["haskell"][1]; icons["cab"][2] = icons["haskell"][2] + icons["cab"][1] = icons["archive"][1]; icons["cab"][2] = icons["archive"][2] + icons["cbr"][1] = icons["archive"][1]; icons["cbr"][2] = icons["archive"][2] + icons["cbz"][1] = icons["archive"][1]; icons["cbz"][2] = icons["archive"][2] + icons["cc"][1] = icons["cplusplus"][1]; icons["cc"][2] = icons["cplusplus"][2] + icons["class"][1] = icons["java"][1]; icons["class"][2] = icons["java"][2] + icons["clj"][1] = icons["clojure"][1]; icons["clj"][2] = icons["clojure"][2] + icons["cljc"][1] = icons["clojure"][1]; icons["cljc"][2] = icons["clojure"][2] + icons["cljs"][1] = icons["clojure"][1]; icons["cljs"][2] = icons["clojure"][2] + icons["cmake"][1] = icons["makefile"][1]; icons["cmake"][2] = icons["makefile"][2] + icons["coffee"][1] = ""; icons["coffee"][2] = color_default + icons["conf"][1] = icons["configure"][1]; icons["conf"][2] = icons["configure"][2] + icons["cpio"][1] = icons["archive"][1]; icons["cpio"][2] = icons["archive"][2] + icons["cpp"][1] = icons["cplusplus"][1]; icons["cpp"][2] = icons["cplusplus"][2] + icons["css"][1] = ""; icons["css"][2] = color_css + icons["cue"][1] = icons["playlist"][1]; icons["cue"][2] = icons["playlist"][2] + icons["cvs"][1] = icons["configure"][1]; icons["cvs"][2] = icons["configure"][2] + icons["cxx"][1] = icons["cplusplus"][1]; icons["cxx"][2] = icons["cplusplus"][2] + +# d + icons["db"][1] = icons["database"][1]; icons["db"][2] = icons["database"][2] + icons["deb"][1] = ""; icons["deb"][2] = color_archive + icons["diff"][1] = ""; icons["diff"][2] = color_default + icons["dll"][1] = icons["script"][1]; icons["dll"][2] = icons["script"][2] + icons["doc"][1] = icons["worddoc"][1]; icons["doc"][2] = icons["worddoc"][2] + icons["docx"][1] = icons["worddoc"][1]; icons["docx"][2] = icons["worddoc"][2] + +# e + icons["ejs"][1] = icons["js"][1]; icons["ejs"][2] = icons["js"][2] + icons["elf"][1] = icons["linux"][1]; icons["elf"][2] = icons["linux"][2] + icons["epub"][1] = icons["manual"][1]; icons["epub"][2] = icons["manual"][2] + icons["exe"][1] = icons["exec"][1]; icons["exe"][2] = icons["exec"][2] + +# f + icons["fsharp"][1] = icons["fsharp"][1]; icons["fsharp"][2] = icons["fsharp"][2] + icons["flac"][1] = icons["musicfile"][1]; icons["flac"][2] = icons["musicfile"][2] + icons["fen"][1] = icons["chess"][1]; icons["fen"][2] = icons["chess"][2] + icons["flv"][1] = icons["videofile"][1]; icons["flv"][2] = icons["videofile"][2] + icons["fs"][1] = icons["fsharp"][1]; icons["fs"][2] = icons["fsharp"][2] + icons["fsi"][1] = icons["fsharp"][1]; icons["fsi"][2] = icons["fsharp"][2] + icons["fsscript"][1] = icons["fsharp"][1]; icons["fsscript"][2] = icons["fsharp"][2] + icons["fsx"][1] = icons["fsharp"][1]; icons["fsx"][2] = icons["fsharp"][2] + +# g + icons["gem"][1] = icons["ruby"][1]; icons["gem"][2] = icons["ruby"][2] + icons["gif"][1] = icons["picturefile"][1]; icons["gif"][2] = icons["picturefile"][2] + icons["go"][1] = "ﳑ"; icons["go"][2] = color_default + icons["gz"][1] = icons["archive"][1]; icons["gz"][2] = icons["archive"][2] + icons["gzip"][1] = icons["archive"][1]; icons["gzip"][2] = icons["archive"][2] + +# h + icons["h"][1] = icons["c"][1]; icons["h"][2] = icons["c"][2] + icons["hh"][1] = icons["cplusplus"][1]; icons["hh"][2] = icons["cplusplus"][2] + icons["hpp"][1] = icons["cplusplus"][1]; icons["hpp"][2] = icons["cplusplus"][2] + icons["hs"][1] = icons["haskell"][1]; icons["hs"][2] = icons["haskell"][2] + icons["htaccess"][1] = icons["configure"][1]; icons["htaccess"][2] = icons["configure"][2] + icons["htpasswd"][1] = icons["configure"][1]; icons["htpasswd"][2] = icons["configure"][2] + icons["htm"][1] = icons["html"][1]; icons["htm"][2] = icons["html"][2] + icons["hxx"][1] = icons["cplusplus"][1]; icons["hxx"][2] = icons["cplusplus"][2] + +# i + icons["ico"][1] = icons["picturefile"][1]; icons["ico"][2] = icons["picturefile"][2] + icons["img"][1] = icons["opticaldisk"][1]; icons["img"][2] = icons["opticaldisk"][2] + icons["ini"][1] = icons["configure"][1]; icons["ini"][2] = icons["configure"][2] + icons["iso"][1] = icons["opticaldisk"][1]; icons["iso"][2] = icons["opticaldisk"][2] + +# j + icons["jar"][1] = icons["java"][1]; icons["jar"][2] = icons["java"][2] + icons["java"][1] = icons["java"][1]; icons["java"][2] = icons["java"][2] + icons["jl"][1] = icons["configure"][1]; icons["jl"][2] = icons["configure"][2] + icons["jpeg"][1] = icons["picturefile"][1]; icons["jpeg"][2] = icons["picturefile"][2] + icons["jpg"][1] = icons["picturefile"][1]; icons["jpg"][2] = icons["picturefile"][2] + icons["json"][1] = "ﬥ"; icons["json"][2] = color_js + icons["jsx"][1] = icons["react"][1]; icons["jsx"][2] = icons["react"][2] + +# k + +# l + icons["lha"][1] = icons["archive"][1]; icons["lha"][2] = icons["archive"][2] + icons["lhs"][1] = icons["haskell"][1]; icons["lhs"][2] = icons["haskell"][2] + icons["ilog"][1] = icons["document"][1]; icons["ilog"][2] = icons["document"][2] + icons["lua"][1] = ""; icons["lua"][2] = color_lua + icons["lzh"][1] = icons["archive"][1]; icons["lzh"][2] = icons["archive"][2] + icons["lzma"][1] = icons["archive"][1]; icons["lzma"][2] = icons["archive"][2] + +# m + icons["m"][1] = "ﴜ"; icons["mat"][2] = color_c + icons["m4a"][1] = icons["musicfile"][1]; icons["m4a"][2] = icons["musicfile"][2] + icons["m4v"][1] = icons["videofile"][1]; icons["m4v"][2] = icons["videofile"][2] + icons["mat"][1] = ""; icons["mat"][2] = color_c + icons["markdown"][1] = ""; icons["markdown"][2] = color_docs + icons["md"][1] = ""; icons["md"][2] = color_docs + icons["mk"][1] = icons["makefile"][1]; icons["mk"][2] = icons["makefile"][2] + icons["mkv"][1] = icons["videofile"][1]; icons["mkv"][2] = icons["videofile"][2] + icons["mov"][1] = icons["videofile"][1]; icons["mov"][2] = icons["videofile"][2] + icons["mp3"][1] = icons["musicfile"][1]; icons["mp3"][2] = icons["musicfile"][2] + icons["mp4"][1] = icons["videofile"][1]; icons["mp4"][2] = icons["videofile"][2] + icons["mpeg"][1] = icons["videofile"][1]; icons["mpeg"][2] = icons["videofile"][2] + icons["mpg"][1] = icons["videofile"][1]; icons["mpg"][2] = icons["videofile"][2] + icons["msi"][1] = ""; icons["msi"][2] = color_default + +# n + icons["nix"][1] = ""; icons["nix"][2] = color_fsharp + +# o + icons["o"][1] = icons["manual"][1]; icons["o"][2] = icons["manual"][2] + icons["ogg"][1] = icons["musicfile"][1]; icons["ogg"][2] = icons["musicfile"][2] + icons["odownload"][1] = icons["download"][1]; icons["odownload"][2] = icons["download"][2] + icons["out"][1] = icons["linux"][1]; icons["out"][2] = icons["linux"][2] + +# p + icons["part"][1] = icons["download"][1]; icons["part"][2] = icons["download"][2] + icons["patch"][1] = icons["diff"][1]; icons["patch"][2] = icons["diff"][2] + icons["pdf"][1] = ""; icons["pdf"][2] = color_docs + icons["pgn"][1] = icons["chess"][1]; icons["pgn"][2] = icons["chess"][2] + icons["php"][1] = ""; icons["php"][2] = color_default + icons["png"][1] = icons["picturefile"][1]; icons["png"][2] = icons["picturefile"][2] + icons["ppt"][1] = ""; icons["ppt"][2] = color_default + icons["pptx"][1] = ""; icons["pptx"][2] = color_default + icons["psb"][1] = ""; icons["psb"][2] = color_default + icons["psd"][1] = ""; icons["psd"][2] = color_default + icons["py"][1] = icons["python"][1]; icons["py"][2] = icons["python"][2] + icons["pyc"][1] = icons["python"][1]; icons["pyc"][2] = icons["python"][2] + icons["pyd"][1] = icons["python"][1]; icons["pyd"][2] = icons["python"][2] + icons["pyo"][1] = icons["python"][1]; icons["pyo"][2] = icons["python"][2] + +# q + +# r + icons["rar"][1] = icons["archive"][1]; icons["rar"][2] = icons["archive"][2] + icons["rc"][1] = icons["configure"][1]; icons["rc"][2] = icons["configure"][2] + icons["rom"][1] = ""; icons["rom"][2] = color_default + icons["rpm"][1] = icons["archive"][1]; icons["rpm"][2] = icons["archive"][2] + icons["rss"][1] = "參"; icons["rss"][2] = color_default + icons["rtf"][1] = ""; icons["rtf"][2] = color_default + +# s + icons["sass"][1] = ""; icons["sass"][2] = color_css + icons["scss"][1] = ""; icons["scss"][2] = color_css + icons["so"][1] = icons["manual"][1]; icons["so"][2] = icons["manual"][2] + icons["scala"][1] = ""; icons["scala"][2] = color_scala + icons["sh"][1] = icons["script"][1]; icons["sh"][2] = icons["script"][2] + icons["slim"][1] = icons["script"][1]; icons["slim"][2] = icons["script"][2] + icons["sln"][1] = ""; icons["sln"][2] = color_default + icons["sql"][1] = icons["database"][1]; icons["sql"][2] = icons["database"][2] + icons["srt"][1] = ""; icons["srt"][2] = color_default + icons["isub"][1] = ""; icons["isub"][2] = color_default + icons["svg"][1] = icons["picturefile"][1]; icons["svg"][2] = icons["picturefile"][2] + +# t + icons["tar"][1] = icons["archive"][1]; icons["tar"][2] = icons["archive"][2] + icons["tex"][1] = ""; icons["tex"][2] = color_default + icons["tgz"][1] = icons["archive"][1]; icons["tgz"][2] = icons["archive"][2] + icons["ts"][1] = ""; icons["ts"][2] = color_js + icons["tsx"][1] = icons["react"][1]; icons["tsx"][2] = icons["react"][2] + icons["txt"][1] = icons["document"][1]; icons["txt"][2] = icons["document"][2] + icons["txz"][1] = icons["archive"][1]; icons["txz"][2] = icons["archive"][2] + +# u + +# v + icons["vid"][1] = icons["videofile"][1]; icons["vid"][2] = icons["videofile"][2] + icons["vim"][1] = ""; icons["vim"][2] = color_vim + icons["vimrc"][1] = ""; icons["vimrc"][2] = color_vim + icons["vtt"][1] = ""; icons["vtt"][2] = color_default +# w + icons["wav"][1] = icons["musicfile"][1]; icons["wav"][2] = icons["musicfile"][2] + icons["webm"][1] = icons["videofile"][1]; icons["webm"][2] = icons["videofile"][2] + icons["wma"][1] = icons["videofile"][1]; icons["wma"][2] = icons["videofile"][2] + icons["wmv"][1] = icons["videofile"][1]; icons["wmv"][2] = icons["videofile"][2] + +# x + icons["xbps"][1] = icons["archive"][1]; icons["xbps"][2] = color_archive + icons["xcf"][1] = icons["picturefile"][1]; icons["xcf"][2] = color_image + icons["xhtml"][1] = icons["html"][1]; icons["xhtml"][2] = icons["html"][2] + icons["xls"][1] = ""; icons["xls"][2] = color_default + icons["xlsx"][1] = ""; icons["xlsx"][2] = color_default + icons["xml"][1] = icons["html"][1]; icons["xml"][2] = icons["html"][2] + icons["xz"][1] = icons["archive"][1]; icons["xz"][2] = icons["archive"][2] + +# y + icons["yaml"][1] = icons["configure"][1]; icons["yaml"][2] = icons["configure"][2] + icons["yml"][1] = icons["configure"][1]; icons["yml"][2] = icons["configure"][2] +# z + icons["zip"][1] = icons["archive"][1]; icons["zip"][2] = icons["archive"][2] + icons["zsh"][1] = icons["script"][1]; icons["zsh"][2] = icons["script"][2] + icons["zst"][1] = icons["archive"][1]; icons["zst"][2] = icons["archive"][2] + + FS = "." + limit = ENVIRON["limit"] + switch (colordepth) { + case "4": + escape="\033[" + break; + case "8": + escape="\033[38;5;" + break; + case "24": + escape="\033[38;2;" + break; + } + bstr = ENVIRON["beforestr"] +} +{ + # dont print cwd . and leading ./ from tree -f + if ($0 ~/^\.$/) + next + ent = ($0 ~/^\.\//) ? substr($0, 3, length($0) - 2) : $0 + ext = $NF + + # Print icons, set color and bold directories by using ansi escape codes + if (ext in icons) + printcolor(icons[ext][1], icons[ext][2], color_filetxt, ent, "10") + else + switch (substr(ent, length(ent), 1)) { + case "/": + printcolor(icons["directory"][1], color_default, color_dirtxt, ent, "1") + break; + case "*": + printcolor(icons["exe"][1], color_default, color_filetxt, ent, "10") + break; + case "|": + printcolor(icons["pipe"][1], color_default, color_filetxt, ent, "10") + break; + case "=": + printcolor(icons["socket"][1], color_default, color_filetxt, ent, "10") + break; + case ">": + printcolor(icons["door"][1], color_default, color_filetxt, ent, "10") + break; + default: + printcolor(icons["file"][1], color_default, color_filetxt, ent, "10") + } +} +function printcolor(i, c, d, n, b) { + if (limit != "" && length(n) + 2 > limit) + n = substr(n, 1, limit - 2) + printf "\033[0m" + printf "%s%s%s;%sm%s %s%sm%s\n", bstr, escape, c, b, i, escape, d, n +}' +printf '\033[0m' +} + +print_begin() { + printf '%s\n' "$1" | sed 's/\\n/\n/g' +} + +print_end() { + printf '%s\n' "$1" | sed 's/\\n/\n/g' +} + +print_help() { + printf 'Icon Lookup\n +Usage: + iconlookup [options] + iconlookup [-bBe] [string] + iconlookup -l [number] + iconlookup (-h | --help) + + Prepend icons to list of files based on extension or appended indicator by ls/tree "-F" flag ("/" for directory, "*" for executable etc.) + +Options: + -h --help -? Show this screen. + -b --before Prepend str before icon. + -B --begin Prepend str before output. + -e --end Append str after output. + -l --limit Limit line length to [number] characters.' +} + +while :; do + case $1 in + -h|-\?|--help) + print_help + exit ;; + -B|--begin) + if [ -n "$2" ]; then + print_begin "$2" + fi + shift ;; + -e|--end) + if [ -n "$2" ]; then + end=1 + endstr="$2" + fi + shift ;; + -b|--before) + if [ -n "$2" ]; then + export beforestr="$2" + fi + shift ;; + -l|--limit) + if [ -n "$2" ]; then + export limit="$2" + shift + else + printf 'ERROR: "--limit" requires a non-empty option argument.\n' + exit + fi ;; + --) + shift + break ;; + -?*) + printf 'WARNING: Unknown option ignored: %s\n' "$1" ;; + *) break ;; + esac + shift +done + +if [ ! -t 0 ]; then + [ -n "$beforestr" ] && limit="$((limit - ${#beforestr}))" + icon_lookup +else + printf 'ERROR: no data provided...\nExpecting a directory listing in stdin\n' +fi + +if [ -n "$end" ]; then + print_end "$endstr" +fi diff --git a/.config/nnn/plugins/.nmv b/.config/nnn/plugins/.nmv new file mode 100755 index 0000000..37e887d --- /dev/null +++ b/.config/nnn/plugins/.nmv @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +# Description: An almost fully POSIX compliant batch file renamer +# +# Note: nnn auto-detects and invokes this plugin if available +# Whitespace is used as delimiter for read. +# The plugin doesn't support filenames with leading or trailing whitespace +# To use NNN_LIST your shell must support readlink(1) +# +# Capabilities: +# 1. Basic file rename +# 2. Detects order change +# 3. Can move files +# 4. Can remove files +# 5. Switch number pairs to swap filenames +# +# Shell: bash +# Author: KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" +NNN_INCLUDE_HIDDEN="${NNN_INCLUDE_HIDDEN:-0}" +VERBOSE="${VERBOSE:-0}" +RECURSIVE="${RECURSIVE:-0}" + +case "$NNN_TRASH" in + 1) + RM_UTIL="trash-put" ;; + 2) + RM_UTIL="gio trash" ;; + *) + RM_UTIL="rm -ri" ;; +esac + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +exit_status=0 + +dst_file=$(mktemp "$TMPDIR/.nnnXXXXXX") + +if [ -s "$selection" ]; then + printf "Rename 'c'urrent / 's'election? " + read -r resp + + if ! [ "$resp" = "c" ] && ! [ "$resp" = "s" ]; then + exit 1 + fi +fi + +if [ "$resp" = "s" ]; then + arr=$(tr '\0' '\n' < "$selection") +else + findcmd="find . ! -name ." + + if [ "$RECURSIVE" -eq 0 ]; then + findcmd="$findcmd -prune" + fi + + if [ "$NNN_INCLUDE_HIDDEN" -eq 0 ]; then + findcmd="$findcmd ! -name \".*\"" + fi + + if [ -z "$NNN_LIST" ]; then + findcmd="$findcmd -print" + else + findcmd="$findcmd -printf "'"'"$NNN_LIST/%P\n"'"' + fi + + arr=$(eval "$findcmd" | sort) +fi + +lines=$(printf "%s\n" "$arr" | wc -l) +width=${#lines} + +printf "%s" "$arr" | awk '{printf("%'"${width}"'d %s\n", NR, $0)}' > "$dst_file" + +items=("~") +while IFS='' read -r line; do + if [ -n "$NNN_LIST" ]; then + line=$(readlink "$line" || printf "%s" "$line") + fi + + items+=("$line"); +done < <(printf "%s\n" "$arr") + +$EDITOR "$dst_file" + +while read -r num name; do + if [ -z "$name" ]; then + if [ -z "$num" ]; then + continue + fi + + printf "%s: unable to parse line, aborting\n" "$0" + exit 1 + fi + + # check if $num is an integer + if [ ! "$num" -eq "$num" ] 2> /dev/null; then + printf "%s: unable to parse line, aborting\n" "$0" + exit 1 + fi + + src=${items[$num]} + + if [ -z "$src" ]; then + printf "%s: unknown item number %s\n" "$0" "$num" > /dev/stderr + continue + elif [ "$name" != "$src" ]; then + if [ -z "$name" ]; then + continue + fi + + if [ ! -e "$src" ] && [ ! -L "$src" ]; then + printf "%s: %s does not exit\n" "$0" "$src" > /dev/stderr + + unset "items[$num]" + continue + fi + + # handle swaps + if [ -e "$name" ] || [ -L "$name" ]; then + tmp="$name~" + c=0 + + while [ -e "$tmp" ] || [ -L "$tmp" ]; do + c=$((c+1)) + tmp="$tmp~$c" + done + + if mv "$name" "$tmp"; then + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' -> '%s'\n" "$name" "$tmp" + fi + else + printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr + exit_status=1 + fi + + for key in "${!items[@]}"; do + if [ "${items[$key]}" = "$name" ]; then + items[$key]="$tmp" + fi + done + fi + + dir=$(dirname "$name") + if [ ! -d "$dir" ] && ! mkdir -p "$dir"; then + printf "%s: failed to create directory tree %s\n" "$0" "$dir" > /dev/stderr + exit_status=1 + elif ! mv -i "$src" "$name"; then + printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr + exit_status=1 + else + if [ -d "$name" ]; then + for key in "${!items[@]}"; do + items[$key]=$(printf "%s" "${items[$key]}" | sed "s|^$src\(\$\|\/\)|$name\1|") + done + + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' => '%s'\n" "$src" "$name" + fi + else + true + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' -> '%s'\n" "$src" "$name" + fi + fi + fi + fi + + unset "items[$num]" +done <"$dst_file" + +unset "items[0]" +for item in "${items[@]}"; do + $RM_UTIL "$item" +done + +rm "$dst_file" +exit $exit_status diff --git a/.config/nnn/plugins/.nnn-plugin-helper b/.config/nnn/plugins/.nnn-plugin-helper new file mode 100644 index 0000000..a0377ac --- /dev/null +++ b/.config/nnn/plugins/.nnn-plugin-helper @@ -0,0 +1,38 @@ +#!/usr/bin/env sh + +# Description: Helper script for plugins +# +# Shell: POSIX compliant +# Author: Anna Arad + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +export selection + +## Set CUR_CTX to 1 to open directory in current context +CUR_CTX=0 +export CUR_CTX + +## Ask nnn to switch to directory $1 in context $2. +## If $2 is not provided, the function asks explicitly. +nnn_cd () { + dir="$1" + + if [ -z "$NNN_PIPE" ]; then + echo "No pipe file found" 1>&2 + return + fi + + if [ -n "$2" ]; then + context=$2 + elif [ $CUR_CTX -ne 1 ]; then + printf "Choose context 1-4 (blank for current): " + read -r context + fi + + printf "%s" "${context:-0}c$dir" > "$NNN_PIPE" +} + +cmd_exists () { + type "$1" > /dev/null 2>&1 + echo $? +} diff --git a/.config/nnn/plugins/.ntfy b/.config/nnn/plugins/.ntfy new file mode 100755 index 0000000..2a61478 --- /dev/null +++ b/.config/nnn/plugins/.ntfy @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# Description: Show a notification +# +# Details: nnn invokes this plugin to show notification when a cp/mv/rm operation is complete. +# +# Dependencies: notify-send (Ubuntu)/ntfy (https://github.com/dschep/ntfy)/osascript (macOS)/notify (Haiku) +# +# Shell: POSIX compliant +# Author: Anna Arad + +OS="$(uname)" + +if type notify-send >/dev/null 2>&1; then + notify-send nnn "Done!" +elif [ "$OS" = "Darwin" ]; then + osascript -e 'display notification "Done!" with title "nnn"' +elif type ntfy >/dev/null 2>&1; then + ntfy -t nnn send "Done!" +elif [ "$OS" = "Haiku" ]; then + notify --title "nnn" "Done!" +fi diff --git a/.config/nnn/plugins/autojump b/.config/nnn/plugins/autojump new file mode 100755 index 0000000..6a24a52 --- /dev/null +++ b/.config/nnn/plugins/autojump @@ -0,0 +1,74 @@ +#!/usr/bin/env sh + +# Description: Navigate to directory using jump/autojump/zoxide/z +# +# Dependencies: +# - jump - https://github.com/gsamokovarov/jump +# - OR autojump - https://github.com/wting/autojump +# - OR zoxide - https://github.com/ajeetdsouza/zoxide +# - OR z - https://github.com/rupa/z (z requires fzf) +# - OR z (fish) - https://github.com/jethrokuan/z (z requires fzf) +# - OR z.lua - https://github.com/skywind3000/z.lua (z.lua can enhanced with fzf) +# +# Note: The dependencies STORE NAVIGATION PATTERNS +# +# to make z.lua work, you need to set $NNN_ZLUA to the path of script z.lua +# +# Shell: POSIX compliant +# Authors: Marty Buchaus, Dave Snider, Tim Adler, Nick Waywood + +if [ ! -p "$NNN_PIPE" ]; then + printf 'ERROR: NNN_PIPE is not set!' + read -r _ + exit 2 +fi + +if type jump >/dev/null 2>&1; then + printf "jump to : " + IFS= read -r line + # shellcheck disable=SC2086 + odir="$(jump cd ${line})" + printf "%s" "0c$odir" > "$NNN_PIPE" +elif type autojump >/dev/null 2>&1; then + printf "jump to : " + read -r dir + odir="$(autojump "$dir")" + printf "%s" "0c$odir" > "$NNN_PIPE" +elif type zoxide >/dev/null 2>&1; then + if type fzf >/dev/null 2>&1; then + odir="$(zoxide query -i --)" + printf "%s" "0c$odir" > "$NNN_PIPE" + else + printf "jump to : " + read -r dir + odir="$(zoxide query -- "$dir")" + printf "%s" "0c$odir" > "$NNN_PIPE" + fi +elif type lua >/dev/null 2>&1 && [ -n "$NNN_ZLUA" ]; then + printf "jump to : " + read -r line + if type fzf >/dev/null 2>&1; then + odir="$(lua "$NNN_ZLUA" -l "$line" | fzf --nth 2.. --reverse --inline-info --tac +s -e --height 35%)" + printf "%s" "0c$(echo "$odir" | awk '{print $2}')" > "$NNN_PIPE" + else + odir="$(lua "$NNN_ZLUA" -e "$line")" + printf "%s" "0c$odir" > "$NNN_PIPE" + fi +else + # rupa/z uses $_Z_DATA, jethrokuan/z (=port of z for fish) uses $Z_DATA + datafile="${_Z_DATA:-${Z_DATA:-$HOME/.z}}" + if type fzf >/dev/null 2>&1 && [ -f "$datafile" ]; then + # Read the data from z's file instead of calling + # z so the data doesn't need to be processed twice + sel=$(awk -F "|" '{print $1}' "$datafile" | fzf | awk '{$1=$1};1') + + # NOTE: Uncomment this line and comment out the line above if + # you want to see the weightings of the dir's in the fzf pane + # sel=$(awk -F "|" '{printf "%s %s\n", $2, $1}' "$datafile" | fzf | sed 's/^[0-9,.]* *//' | awk '{$1=$1};1') + + printf "%s" "0c$sel" > "$NNN_PIPE" + else + printf "No supported autojump script [jump/autojump/zoxide/z (needs fzf)] found" + read -r _ + fi +fi diff --git a/.config/nnn/plugins/boom b/.config/nnn/plugins/boom new file mode 100755 index 0000000..aab466f --- /dev/null +++ b/.config/nnn/plugins/boom @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# Description: Play random music (MP3, FLAC, M4A, WEBM, WMA) from current dir. +# +# Dependencies: mocp (or custom) +# +# Note: You may want to set GUIPLAYER. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +GUIPLAYER="${GUIPLAYER:-""}" +NUMTRACKS="${NUMTRACKS:-100}" + +if [ -n "$GUIPLAYER" ]; then + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | shuf -n "$NUMTRACKS" | xargs -d "\n" "$GUIPLAYER" > /dev/null 2>&1 & + + # detach the player + sleep 1 +elif type mocp >/dev/null 2>&1; then + cmd=$(pgrep -x mocp 2>/dev/null) + ret=$cmd + + if [ -z "$ret" ]; then + # start MOC server + mocp -S + mocp -o shuffle + else + # mocp running, check if it's playing + state=$(mocp -i | grep "State:" | cut -d' ' -f2) + if [ "$state" = 'PLAY' ]; then + # add up to 100 random audio files + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a + exit + fi + fi + + # clear MOC playlist + mocp -c + mocp -o shuffle + + # add up to 100 random audio files + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a + + # start playing + mocp -p +else + printf "moc missing" + read -r _ +fi diff --git a/.config/nnn/plugins/bulknew b/.config/nnn/plugins/bulknew new file mode 100755 index 0000000..64331e4 --- /dev/null +++ b/.config/nnn/plugins/bulknew @@ -0,0 +1,32 @@ +#!/usr/bin/env sh + +# Description: Allows for creation of multiple files/dirs simultaneously +# Creates a tmp file to write each entry in a separate line +# +# Note: Only relative paths are supported. Absolute paths are ignored +# Leading and trailing whitespace in path names is also ignored +# +# Shell: POSIX compliant +# Author: KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" + +printf "'f'ile / 'd'ir? " +read -r resp + +if [ "$resp" = "f" ]; then + #shellcheck disable=SC2016 + cmd='mkdir -p "$(dirname "{}")" && touch "{}"' +elif [ "$resp" = "d" ]; then + cmd='mkdir -p {}' +else + exit 1 +fi + +tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX") +$EDITOR "$tmpfile" + +sed "/^\//d" "$tmpfile" | xargs -n1 -I{} sh -c "$cmd" + +rm "$tmpfile" diff --git a/.config/nnn/plugins/cdpath b/.config/nnn/plugins/cdpath new file mode 100755 index 0000000..663cb26 --- /dev/null +++ b/.config/nnn/plugins/cdpath @@ -0,0 +1,56 @@ +#!/usr/bin/env sh + +# Description: 'cd' to the directory from CDPATH +# +# Details: If the CDPATH environmet variable is not set, the default value of +# ${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks will be used. +# You can create this directory and fill it with symbolic links to your +# favorite directories. It's a good idea to add it to CDPATH so that it +# could also be used from the command line outside of nnn. +# The fzf search is done on the directory basename (the first column). +# +# This plugin is an extended version of the bookmarks plugin. +# If you set your CDPATH to ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/bookmarks +# or to the value of BOOKMARKS_DIR, you can use it as a bookmarks replacement. +# +# Shell: POSIX compliant +# Author: Yuri Kloubakov + +# shellcheck disable=SC1090,SC1091 +. "$(dirname "$0")"/.nnn-plugin-helper + +# Get a list of (symbolic links to) directories for every element of CDPATH +get_dirs() { + IFS=':' + for path in $CDPATH; do + for entry in "$path"/*; do + if [ -d "$entry" ]; then + name=$(basename "$entry" | grep -o '^.\{1,24\}') + if [ -h "$entry" ]; then + slink=$(ls -dl -- "$entry") + entry=${slink#*" $entry -> "} + fi + printf "%-24s :%s\n" "${name}" "$entry" + fi + done + done +} + +abort() { + echo "$1" + read -r _ + exit 1 +} + +if [ -z "$CDPATH" ]; then + CDPATH="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks" + [ -d "$CDPATH" ] || abort "CDPATH is not set and there is no \"$CDPATH\" directory" +fi + +dir_list=$(get_dirs) +[ -n "$dir_list" ] || abort "There are no directories to choose from. Check your \"$CDPATH\"." + +dir=$(echo "$dir_list" | fzf --nth=1 --delimiter=':' | awk -F: 'END { print $2 }') +if [ -n "$dir" ]; then + nnn_cd "$dir" 0 +fi diff --git a/.config/nnn/plugins/chksum b/.config/nnn/plugins/chksum new file mode 100755 index 0000000..318f1c8 --- /dev/null +++ b/.config/nnn/plugins/chksum @@ -0,0 +1,72 @@ +#!/usr/bin/env sh + +# Description: Create and verify checksums +# +# Details: +# - selection: it will generate one file with the checksums and filenames +# (and with paths if they are in another directory) +# output checksum filename format: checksum_timestamp.checksum_type +# - file: if the file is a checksum, the plugin does the verification +# if the file is not a checksum, checksum will be generated for it +# the output checksum filename will be filename.checksum_type +# - directory: recursively calculates checksum for all the files in the dir +# the output checksum filename will be directory.checksum_type +# +# Shell: POSIX compliant +# Authors: ath3, Arun Prakash Jana + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=f +chsum=md5 + +checksum_type() +{ + echo "possible checksums: md5, sha1, sha224, sha256, sha384, sha512" + printf "create md5 (m), sha256 (s), sha512 (S) (or type one of the above checksums) [default=m]: " + read -r chsum_resp + for chks in md5 sha1 sha224 sha256 sha384 sha512 + do + if [ "$chsum_resp" = "$chks" ]; then + chsum=$chsum_resp + return + fi + done + if [ "$chsum_resp" = "s" ]; then + chsum=sha256 + elif [ "$chsum_resp" = "S" ]; then + chsum=sha512 + fi +} + +if [ -s "$selection" ]; then + printf "work with selection (s) or current file (f) [default=f]: " + read -r resp +fi + +if [ "$resp" = "s" ]; then + checksum_type + sed 's|'"$PWD/"'||g' < "$selection" | xargs -0 -I{} ${chsum}sum {} > "checksum_$(date '+%Y%m%d%H%M').$chsum" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +elif [ -n "$1" ]; then + if [ -f "$1" ]; then + for chks in md5 sha1 sha224 sha256 sha384 sha512 + do + if echo "$1" | grep -q \.${chks}$; then + ${chks}sum -c < "$1" + read -r _ + return + fi + done + checksum_type + file=$(basename "$1").$chsum + ${chsum}sum "$1" > "$file" + elif [ -d "$1" ]; then + checksum_type + file=$(basename "$1").$chsum + find "$1" -type f -exec ${chsum}sum "{}" + > "$file" + fi +fi diff --git a/.config/nnn/plugins/cmusq b/.config/nnn/plugins/cmusq new file mode 100755 index 0000000..fbaa485 --- /dev/null +++ b/.config/nnn/plugins/cmusq @@ -0,0 +1,80 @@ +#!/usr/bin/env sh + +# Description: Add selection or hovered file/directory to cmus queue +# +# Dependencies: cmus, pgrep, xdotool (optional) +# +# Notes: +# 1. If adding selection, files/dirs are added in the same order they were selected in nnn +# 2. A new window will be opened if cmus is not running already, playback will start immediately +# 3. If cmus is already running, files will be appended to the queue with no forced playback +# +# TODO: +# 1. Add cava and cmus-lyrics as optional dependencies +# 2. Start cava and/or cmus-lyrics in tmux or kitty panes next to cmus +# +# Shell: POSIX compliant +# Author: Kabouik + +# (Optional) Set preferred terminal emulator for cmus if not set in your env, +# or leave commented out to use OS default +#TERMINAL="kitty" + +if ! type cmus >/dev/null; then + printf "cmus missing" + read -r _ + exit 1 +fi + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +start_cmus() { + type xdotool >/dev/null && nnnwindow="$(xdotool getactivewindow)" + case "$TERMINAL" in + kitty | gnome-terminal | st) + nohup "$TERMINAL" -- cmus & ;; + havoc) + nohup "$TERMINAL" cmus & ;; + "") + nohup x-terminal-emulator -e cmus & ;; + *) + nohup "$TERMINAL" -e cmus & ;; + esac + # Give the new terminal some time to open + until cmus-remote -C; do sleep 0.1; done + [ -n "$nnnwindow" ] && xdotool windowactivate "$nnnwindow" +} >/dev/null 2>&1 + +fill_queue() { + if [ "$REPLY" = "s" ]; then + xargs < "$selection" -0 cmus-remote -q + elif [ -n "$1" ]; then + cmus-remote -q "$1" + fi +} + +# If active selection,then ask what to do +if [ -s "$selection" ]; then + printf "Queue [s]election or [c]urrently hovered? [default=c]: " + read -r REPLY +fi + +# If cmus is not running, start and play queue +if ! pgrep cmus >/dev/null; then + printf "cmus is not running, starting it in a new %s window.\n" "$TERMINAL" + start_cmus + fill_queue "$1" + cmus-remote -p + printf "Files added to cmus queue.\n" +else # Append to existing queue if cmus is already running + fill_queue "$1" + printf "Files appended to current cmus queue.\n" +fi + +# Change view +cmus-remote -C "view 4" + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/di-nuke b/.config/nnn/plugins/di-nuke new file mode 100755 index 0000000..3104f80 --- /dev/null +++ b/.config/nnn/plugins/di-nuke @@ -0,0 +1,79 @@ +#! /bin/sh + +FALLBACK_OPENER=xdg-open +entry="$1" +#mime="$(file -ibL "$entry" )" +FNAME=$(basename "$entry") +ext="${FNAME##*.}" +if [ -n "$ext" ]; then + ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" +fi + + case "${ext}" in + ## Archive + a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ + rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zst|zip|rar|7z) + extract "$entry" + exit 0;; + + ## PDF + pdf) + zathura "$entry" + exit 1;; + + ## Audio + aac|flac|m4a|mid|midi|mpa|mp2|mp3|ogg|wav|wma) + mpv --no-video --quiet "$entry" + exit 1;; + + ## Video + avi|mkv|mp4|gif) + devour mpv "$entry" + exit 1;; + + ## Image + png|jpg|jpeg|PNG|JPG|JPEG|svg) + "$IMAGEVIEWER" "$entry" + exit 1;; + + ## Log files + log) + "$EDITOR" "$entry" + exit 0;; + + ## BitTorrent + torrenti|magnet) + aria2c -i "$entry" + exit 0;; + + ## OpenDocument + odt|ods|odp|sxw) + "$BROWSER" "$entry" + exit 0;; + + ## Markdown + md) + "$EDITOR" "$entry" + exit 0;; + + ## HTML + htm|html|xhtml) + ## Preview as text conversion + "$BROWSER" "$entry" + exit 0;; + + ## JSON + json) + jq --color-output . "$entry" + exit 0 ;; + + esac + + +case "$mime" in + *text*) + "$EDITOR" "$entry" + exit 0 ;; +esac + +$FALLBACK_OPENER "$entry" diff --git a/.config/nnn/plugins/diffs b/.config/nnn/plugins/diffs new file mode 100755 index 0000000..0464781 --- /dev/null +++ b/.config/nnn/plugins/diffs @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Show diff of 2 directories or multiple files in vimdiff +# +# Notes: +# 1. vim may show the warning: 'Vim: Warning: Input is not from a terminal' +# press 'Enter' to ignore and proceed. +# 2. if only one file is in selection, the hovered file is considered as the +# second file to diff with +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if type nvim >/dev/null 2>&1; then + diffcmd="nvim -d" +else + diffcmd="vimdiff +0" +fi + +dirdiff() { + dir1=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$1")".XXXXXXXX) + dir2=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$2")".XXXXXXXX) + ls -A1 "$1" > "$dir1" + ls -A1 "$2" > "$dir2" + $diffcmd "$dir1" "$dir2" + rm "$dir1" "$dir2" +} + +if [ -s "$selection" ]; then + arr=$(tr '\0' '\n' < "$selection") + if [ "$(echo "$arr" | wc -l)" -gt 1 ]; then + f1="$(echo "$arr" | sed -n '1p')" + f2="$(echo "$arr" | sed -n '2p')" + if [ -d "$f1" ] && [ -d "$f2" ]; then + dirdiff "$f1" "$f2" + else + # If xargs supports the -o option, use it to get rid of: + # Vim: Warning: Input is not from a terminal + # xargs -0 -o vimdiff < $selection + + eval xargs -0 "$diffcmd" < "$selection" + fi + elif [ -n "$1" ]; then + f1="$(echo "$arr" | sed -n '1p')" + if [ -d "$f1" ] && [ -d "$1" ]; then + dirdiff "$f1" "$1" + elif [ -f "$f1" ] && [ -f "$1" ]; then + $diffcmd "$f1" "$1" + else + echo "cannot compare file with directory" + fi + else + echo "needs at least 2 files or directories selected for comparison" + fi +fi + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/dragdrop b/.config/nnn/plugins/dragdrop new file mode 100755 index 0000000..812f970 --- /dev/null +++ b/.config/nnn/plugins/dragdrop @@ -0,0 +1,77 @@ +#!/usr/bin/env sh + +# Description: Open a Drag and drop window, to drop files onto other programs. +# Also provides drag and drop window for files. +# +# Dependencies: dragon - https://github.com/mwh/dragon +# +# Notes: +# 1. Files that are dropped will be added to nnn's selection +# Some web-based files will be downloaded to current dir +# with curl and it may overwrite some existing files +# 2. The user has to mm to clear nnn's selection first +# +# Shell: POSIX compliant +# Author: 0xACE + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=f +all= +if type dragon-drag-and-drop >/dev/null 2>&1; then + dnd="dragon-drag-and-drop" +elif type dragon-drop >/dev/null 2>&1; then + dnd="dragon-drop" +else + dnd="dragon" +fi + +add_file () +{ + printf '%s\0' "$@" >> "$selection" +} + +use_all () +{ + printf "mark --all (a) [default=none]: " + read -r resp + if [ "$resp" = "a" ]; then + all="--all" + else + all="" + fi +} + +if [ -s "$selection" ]; then + printf "Drop file (r). Drag selection (s), Drag current directory (d) or drag current file (f) [default=f]: " + read -r resp +else + printf "Drop file (r). Drag current directory (d) or drag current file (f) [default=f]: " + read -r resp + if [ "$resp" = "s" ]; then + resp=f + fi +fi + +if [ "$resp" = "s" ]; then + use_all + sed -z 's|'"$PWD/"'||g' < "$selection" | xargs -0 "$dnd" "$all" & +elif [ "$resp" = "d" ]; then + use_all + "$dnd" "$all" "$PWD/"* & +elif [ "$resp" = "r" ]; then + true > "$selection" + "$dnd" --print-path --target | while read -r f + do + if printf "%s" "$f" | grep '^\(https\?\|ftps\?\|s\?ftp\):\/\/' ; then + curl -LJO "$f" + add_file "$PWD/$(basename "$f")" + elif [ -e "$f" ]; then + add_file "$f" + fi + done & +else + if [ -n "$1" ] && [ -e "$1" ]; then + "$dnd" "$1" & + fi +fi + diff --git a/.config/nnn/plugins/dups b/.config/nnn/plugins/dups new file mode 100755 index 0000000..27c1807 --- /dev/null +++ b/.config/nnn/plugins/dups @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# Description: List non-empty duplicates in the current dir (based on size followed by MD5) +# +# Source: https://www.commandlinefu.com/commands/view/3555/find-duplicate-files-based-on-size-first-then-md5-hash +# +# Dependencies: find md5sum sort uniq xargs gsed +# +# Notes: +# 1. If the file size exceeds $size_digits digits the file will be misplaced +# 12 digits fit files up to 931GiB +# 2. Bash compatible required for mktemp +# +# Shell: Bash +# Authors: syssyphus, KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" + +size_digits=12 +tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX") + +printf "\ +## This is an overview of all duplicate files found. +## Comment out the files you wish to remove. You will be given an option to cancel. +## Lines with double comments (##) are ignored. +## You will have the option to remove the files with force or interactively.\n +" > "$tmpfile" + +# shellcheck disable=SC2016 +find . -size +0 -type f -printf "%${size_digits}s %p\n" | sort -rn | uniq -w"${size_digits}" -D | sed -e ' +s/^ \{0,12\}\([0-9]\{0,12\}\) \(.*\)$/printf "%s %s\\n" "$(md5sum "\2")" "d\1"/ +' | tr '\n' '\0' | xargs -0 -n1 sh -c | sort | { uniq -w32 --all-repeated=separate; echo; } | sed -ne ' +h +s/^\(.\{32\}\).* d\([0-9]*\)$/## md5sum: \1 size: \2 bytes/p +g + +:loop +N +/.*\n$/!b loop +p' | sed -e 's/^.\{32\} \(.*\) d[0-9]*$/\1/' >> "$tmpfile" + +"$EDITOR" "$tmpfile" + +printf "Remove commented files? (yes/no) [default=n]: " +read -r commented + +if [ "$commented" = "y" ]; then + sedcmd="/^##.*/d; /^[^#].*/d; /^$/d; s/^# *\(.*\)$/\1/" +else + printf "Press any key to exit" + read -r _ + exit +fi + +printf "Remove with force or interactive? (f/i) [default=i]: " +read -r force + +if [ "$force" = "f" ]; then + #shellcheck disable=SC2016 + sed -e "$sedcmd" "$tmpfile" | tr '\n' '\0' | xargs -0 -r sh -c 'rm -f "$0" "$@" /dev/null; then + fexpr=${fexprs[$((fexpr - 1))]} + read -r -e -p "Search expression: " -i "$fexpr" fexpr + else + return 1 + fi +} + +readexpr() { + case "$fexpr" in + h) clear + printf "Examples:\n" + mapfile -t fexprs < <(printexamples) + printexprs 0 + read -r -p "Search expression or index: " fexpr + mapexpr + [ -n "$fexpr" ] && readexpr ;; + \$*) cmd="${fexpr:1}" ;; + *) mapexpr && readexpr + cmd="find $fexpr -print0" ;; + esac +} + +clear +[ -f "$NNN_FINDHIST" ] || printexamples > "$NNN_FINDHIST" + +mapfile -t fexprs < <(sort "$NNN_FINDHIST" | uniq -c | sort -nr | head -n5 |\ + awk '{for (i=2; i "$NNN_PIPE" + while :; do + readexpr + eval "$cmd" > "$NNN_PIPE" && break + read -r -e -p "Search expression: " -i "$fexpr" fexpr + done + if [ -n "$fexpr" ]; then + tail -n"$NNN_FINDHISTLEN" "$NNN_FINDHIST" > "$TMPDIR/finderbms" + printf "%s\n" "$fexpr" >> "$TMPDIR/finderbms" + mv "$TMPDIR/finderbms" "$NNN_FINDHIST" + fi +fi diff --git a/.config/nnn/plugins/fixname b/.config/nnn/plugins/fixname new file mode 100755 index 0000000..4047152 --- /dev/null +++ b/.config/nnn/plugins/fixname @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# Description: Clean filename or dirname (either hovered or selections) +# to be more shell-friendly. This script cleans +# non A-Za-z0-9._- characters. +# and replaces it with underscore (_). +# +# It supports cleaning single/double quote, newline, +# leading, trailing spaces. +# +# eg. +# to be continued (つづく).mp4 -> to_be_continued______.mp4 +# [work] stuff.txt -> _work__stuff.txt +# home's server -> home_s_server +# qwe\trty -> __qwe_rty +# +# And if there are two almost similar filenames +# like: 'asd]f' and 'asd f' both will be renamed to 'asd_f', +# to avoid overwriting, the last file will be prepended by _. +# So they will be: 'asd_f' and '_asd_f' +# +# Dependencies: sed +# +# Shell: Bash +# Author: Benawi Adha + +prompt=true +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +cleanup() { + # printf "%s" "$1" | sed -e 's/[^A-Za-z0-9._-]/_/g' + printf "%s" "$1" | sed 's/[^A-Za-z0-9._-]/_/g' | sed ':a;N;$!ba;s/\n/_/g' +} + +if [ -s "$sel" ]; then + targets=() + while IFS= read -r -d '' i || [ -n "$i" ]; do + targets+=( "$(basename "$i")" ) + done < "$sel" +else + targets=("$1") +fi + +for i in "${targets[@]}"; do + printf "%s -> %s\n" "$i" "$(cleanup "$i")"; +done + +if $prompt; then + echo + printf "Proceed [Yn]? " + read -r input + case "$input" in + y|Y|'') + ;; + *) + echo "Canceled" + exit + ;; + esac +fi + +for i in "${targets[@]}"; do + if [ "$i" != "$(cleanup "$i")" ]; then + tmp='' + if [ -e "$(cleanup "$i")" ]; then + tmp='_' + fi + mv "$i" "$tmp$(cleanup "$i")"; + fi +done + +# Clear selection +if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/fzcd b/.config/nnn/plugins/fzcd new file mode 100755 index 0000000..125092c --- /dev/null +++ b/.config/nnn/plugins/fzcd @@ -0,0 +1,89 @@ +#!/usr/bin/env sh + +# Description: Fuzzy search multiple locations read-in from a path-list file +# (or $PWD) and open the selected file's dir in a smart context. +# Dependencies: fzf, find (only for multi-location search) +# +# Details: Paths in list file should be newline-separated absolute paths. +# Paths can be file paths; the script will scan the parent dirs. +# +# The path-list file precedence is: +# - "$1" (the hovered file) if it exists, is plain-text and the +# first line points to an existing file +# - "$LIST" if set below +# - "$2" (the current directory) [mimics plugin fzcd behaviour] +# +# The path-list file can be generated easily: +# - pick the (file)paths in picker mode to path-list file +# - OR, edit selection in nnn and save as path-list file +# +# Shell: POSIX compliant +# Author: Anna Arad, Arun Prakash Jana, KlzXS + +IFS="$(printf '\n\r')" + +# shellcheck disable=SC1090,SC1091 +. "$(dirname "$0")"/.nnn-plugin-helper + +CTX=+ +LIST="${LIST:-""}" + +if ! type fzf >/dev/null 2>&1; then + printf "fzf missing" + read -r _ + exit 1 +fi + +if [ -n "$1" ] && [ "$(file -b --mime-type "$1")" = 'text/plain' ] && [ -e "$(head -1 "$1")" ]; then + LIST="$1" +elif ! [ -s "$LIST" ]; then + sel=$(fzf) + # Show only the file and parent dir + # sel=$(fzf --delimiter / --with-nth=-2,-1 --tiebreak=begin --info=hidden) + + LIST='' +fi + +if [ -n "$LIST" ]; then + if type find >/dev/null 2>&1; then + tmpfile=$(mktemp /tmp/abc-script.XXXXXX) + + while IFS= read -r path; do + if [ -d "$path" ]; then + printf "%s\n" "$path" >> "$tmpfile" + elif [ -f "$path" ]; then + printf "%s\n" "$(dirname "$path")" >> "$tmpfile" + fi + done < "$LIST" + + sel=$(xargs -d '\n' < "$tmpfile" -I{} find {} -type f -printf "%H//%P\n" | sed '/.*\/\/\(\..*\|.*\/\..*\)/d; s:/\+:/:g' | fzf --delimiter / --tiebreak=begin --info=hidden) + # Alternative for 'fd' + # sel=$(xargs -d '\n' < "$tmpfile" fd . | fzf --delimiter / --tiebreak=begin --info=hidden) + + rm "$tmpfile" + else + printf "find missing" + read -r _ + exit 1 + fi +fi + +if [ -n "$sel" ]; then + if [ "$sel" = "." ] || { ! [ -d "$sel" ] && ! [ -f "$sel" ]; }; then + exit 0 + fi + + # Check if the selected path returned by fzf command is absolute + case $sel in + /*) nnn_cd "$sel" "$CTX" ;; + *) + # Remove "./" prefix if it exists + sel="${sel#./}" + + if [ "$PWD" = "/" ]; then + nnn_cd "/$sel" "$CTX" + else + nnn_cd "$PWD/$sel" "$CTX" + fi;; + esac +fi diff --git a/.config/nnn/plugins/fzhist b/.config/nnn/plugins/fzhist new file mode 100755 index 0000000..111bc22 --- /dev/null +++ b/.config/nnn/plugins/fzhist @@ -0,0 +1,40 @@ +#!/usr/bin/env sh + +# Description: Fuzzy find a command from history, +# edit in $EDITOR and run as a command +# +# Note: Supports only bash and fish history +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if type fzf >/dev/null 2>&1; then + fuzzy=fzf +else + exit 1 +fi + +shellname="$(basename "$SHELL")" + +if [ "$shellname" = "bash" ]; then + hist_file="$HOME/.bash_history" + entry="$("$fuzzy" < "$hist_file")" +elif [ "$shellname" = "fish" ]; then + hist_file="$HOME/.local/share/fish/fish_history" + entry="$(grep "\- cmd: " "$hist_file" | cut -c 8- | "$fuzzy")" +fi + +if [ -n "$entry" ]; then + tmpfile=$(mktemp) + echo "$entry" >> "$tmpfile" + $EDITOR "$tmpfile" + + if [ -s "$tmpfile" ]; then + $SHELL -c "$(cat "$tmpfile")" + fi + + rm "$tmpfile" + + printf "Press any key to exit" + read -r _ +fi diff --git a/.config/nnn/plugins/fzopen b/.config/nnn/plugins/fzopen new file mode 100755 index 0000000..17b0585 --- /dev/null +++ b/.config/nnn/plugins/fzopen @@ -0,0 +1,83 @@ +#!/usr/bin/env sh + +# Description: Regular mode: +# Fuzzy find a file in directory subtree. +# Opens in $VISUAL or $EDITOR if text. +# Opens other type of files with xdg-open. +# Work only with a single file selected. +# +# Picker mode: +# If picker mode output file is passed, it +# will be overwritten with any picked files. +# Leaves untouched if no file is picked. +# Works with single/multiple files selected. +# +# Dependencies: fd/find, fzf/skim, xdg-open/open (on macOS) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" +USE_NUKE=0 + +# shellcheck disable=SC1090,SC1091 +. "$(dirname "$0")"/.nnn-plugin-helper + +if type fzf >/dev/null 2>&1; then + cmd="$FZF_DEFAULT_COMMAND" + if type fd >/dev/null 2>&1; then + [ -z "$cmd" ] && cmd="fd -t f 2>/dev/null" + else + [ -z "$cmd" ] && cmd="find . -type f 2>/dev/null" + fi + entry="$(eval "$cmd" | fzf -m)" + # To show only the file name + # entry=$(find . -type f 2>/dev/null | fzf --delimiter / --with-nth=-1 --tiebreak=begin --info=hidden) +elif type sk >/dev/null 2>&1; then + entry=$(find . -type f 2>/dev/null | sk) +else + exit 1 +fi + +# Check for picker mode +if [ "$3" ]; then + if [ "$entry" ]; then + case "$entry" in + /*) fullpath="$entry" ;; + *) fullpath="$PWD/$entry" ;; + esac + if [ "-" = "$3" ]; then + printf "%s\n" "$fullpath" + else + printf "%s\n" "$fullpath" > "$3" + fi + + # Tell `nnn` to clear its internal selection + printf "%s" "0p" > "$NNN_PIPE" + fi + + exit 0 +fi + +if [ "$USE_NUKE" -ne 0 ]; then + "$NUKE" "$entry" + exit 0 +fi + +# Open the file (works for a single file only) +cmd_file="" +cmd_open="" +if uname | grep -q "Darwin"; then + cmd_file="file -bIL" + cmd_open="open" +else + cmd_file="file -biL" + cmd_open="xdg-open" +fi + +case "$($cmd_file "$entry")" in + *text*) + "${VISUAL:-$EDITOR}" "$entry" ;; + *) + $cmd_open "$entry" >/dev/null 2>&1 ;; +esac diff --git a/.config/nnn/plugins/fzplug b/.config/nnn/plugins/fzplug new file mode 100755 index 0000000..11dcf7f --- /dev/null +++ b/.config/nnn/plugins/fzplug @@ -0,0 +1,59 @@ +#!/usr/bin/env sh + +# Description: Fuzzy find and execute nnn plugins (and optionally, +# custom scripts located elsewhere). +# Description and details of plugins can be previewed +# from the fzf interface. Use `?` to toggle preview +# pane on and off, ^Up/^Dn to scroll. +# +# Dependencies: find, fzf, cat (or bat, if installed) +# +# Note: For better compatibility with as many nnn plugins as possible, +# fzplug will first execute the chosen script on the file hovered +# in nnn, and upon failure, try to run it with no target (i.e on +# an active selection, if present). +# +# Shell: POSIX compliant +# Author: Kabouik + +# Optional scripts sources + +# Leave blank or fill with the absolute path of a folder containing executable +# scripts other than nnn plugins (e.g., "$HOME/.local/share/nautilus/scripts", +# since there are numerous Nautilus script git repositories). +# Add extra variables if needed, make sure you call them in the find command. + +#CUSTOMDIR1="$HOME/.local/share/nautilus/scripts" +CUSTOMDIR1="" +CUSTOMDIR2="" + +nnnpluginsdir="$HOME/.config/nnn/plugins" + +# Preview with bat if installed +if type bat >/dev/null; then + BAT="bat --terminal-width='$(tput cols)' --decorations=always --color=always --style='${BAT_STYLE:-header,numbers}'" +fi + +plugin=$(find "$nnnpluginsdir" "$CUSTOMDIR1" "$CUSTOMDIR2" \ +-maxdepth 3 -perm -111 -type f 2>/dev/null | fzf --ansi --preview \ + "${BAT:-cat} {}" --preview-window="right:66%:wrap" --delimiter / \ + --with-nth -1 --bind="?:toggle-preview") + +# Try running the script on the hovered file, and abort +# abort if no plugin was selected (ESC or ^C pressed). +err=0 +if ! [ "$plugin" = "" ]; then + "$plugin" "$1" || err=1 +fi + +# If attempt with hovered file fails, try without any target +# (nnn selections should still be passed to the script in that case) +if [ "$err" -eq "1" ]; then + clear && "$plugin" || err=2 +fi + +# Abort and show error if both fail +if [ "$err" -eq "2" ]; then + sep="\n---\n" + printf "$sep""Failed to execute '%s'. See error above or try without fzfplug. Press return to continue. " "$plugin" && read -r _ && clear +fi diff --git a/.config/nnn/plugins/getplugs b/.config/nnn/plugins/getplugs new file mode 100755 index 0000000..361a605 --- /dev/null +++ b/.config/nnn/plugins/getplugs @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# Description: Update nnn plugins to installed nnn version +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, KlzXS + +CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/ +PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins + +merge () { + if type nvim >/dev/null 2>&1; then + nvim -d "$1" "$2" + else + vimdiff +0 "$1" "$2" + fi +} + +prompt () { + printf "%s\n" "Plugin $1 already exists and is different." + printf "Keep (k), merge (m), overwrite (o) [default: k]? " + read -r operation + + if [ "$operation" = "m" ]; then + op="merge" + elif [ "$operation" = "o" ]; then + op="cp -vRf" + else + op="true" + fi +} + +if [ "$1" = "master" ] ; then + VER="master" + ARCHIVE_URL=https://github.com/jarun/nnn/archive/master.tar.gz +elif type nnn >/dev/null 2>&1; then + VER=$(nnn -V) + ARCHIVE_URL=https://github.com/jarun/nnn/releases/download/v"$VER"/nnn-v"$VER".tar.gz +else + echo "nnn is not installed" + exit 1 +fi + +# backup any earlier plugins +if [ -d "$PLUGIN_DIR" ]; then + tar -C "$CONFIG_DIR" -czf "$CONFIG_DIR""plugins-$(date '+%Y%m%d%H%M').tar.gz" plugins/ +fi + +mkdir -p "$PLUGIN_DIR" +cd "$CONFIG_DIR" || exit 1 +curl -Ls "$ARCHIVE_URL" -o nnn-"$VER".tar.gz +tar -zxf nnn-"$VER".tar.gz + +cd nnn-"$VER"/plugins || exit 1 + +# shellcheck disable=SC2044 +# We do not use obnoxious names for plugins +for f in $(find . -maxdepth 1 \( ! -iname "." ! -iname "*.md" \)); do + if [ -f ../../plugins/"$f" ]; then + if [ "$(diff --brief "$f" ../../plugins/"$f")" ]; then + prompt "$f" + $op "$f" ../../plugins/ + fi + else + cp -vRf "$f" ../../plugins/ + fi +done +cd ../.. || exit 1 + +rm -rf nnn-"$VER"/ nnn-"$VER".tar.gz diff --git a/.config/nnn/plugins/gitroot b/.config/nnn/plugins/gitroot new file mode 100755 index 0000000..4428d1e --- /dev/null +++ b/.config/nnn/plugins/gitroot @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Description: cd to the top level of the current git repository in the current context +# Dependencies: git +# Shell: sh +# Author: https://github.com/PatrickF1 + +root="$(git rev-parse --show-toplevel 2>/dev/null)" +if [ -n "$root" ]; then + printf "%s" "0c$root" > "$NNN_PIPE" +else + printf "Not in a git repository" + read -r _ + exit 1 +fi diff --git a/.config/nnn/plugins/gpgd b/.config/nnn/plugins/gpgd new file mode 100755 index 0000000..44d5c0f --- /dev/null +++ b/.config/nnn/plugins/gpgd @@ -0,0 +1,28 @@ +#!/usr/bin/env sh + +# Description: Decrypts selected files using gpg. The contents of the +# decrypted file are stored in a file with extension .dec +# +# Note: If an appropriate private key cannot be found gpg silently +# prints a message in the background and no files are written. +# +# Shell: POSIX compliant +# Author: KlzXS + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +printf "(s)election/(c)urrent? [default=c] " +read -r resp + +if [ "$resp" = "s" ]; then + files=$(tr '\0' '\n' < "$selection") +else + files=$1 +fi + +printf "%s" "$files" | xargs -n1 -I{} gpg --decrypt --output "{}.dec" {} + +# Clear selection +if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/gpge b/.config/nnn/plugins/gpge new file mode 100755 index 0000000..69016a9 --- /dev/null +++ b/.config/nnn/plugins/gpge @@ -0,0 +1,44 @@ +#!/usr/bin/env sh + +# Description: Encrypts selected files using gpg. Can encrypt +# asymmetrically (key) or symmetrically (passphrase). +# If asymmetric encryption is chosen a key can be +# chosen from the list of capable public keys using fzf. +# +# Note: Symmetric encryption only works for a single (current) file as per gpg limitations +# +# Shell: POSIX compliant +# Author: KlzXS + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +printf "(s)ymmetric, (a)symmetric? [default=a] " +read -r symmetry + +if [ "$symmetry" = "s" ]; then + gpg --symmetric "$1" +else + printf "(s)election/(c)urrent? [default=c] " + read -r resp + + if [ "$resp" = "s" ]; then + files=$(tr '\0' '\n' < "$selection") + else + files=$1 + fi + + keyids=$(gpg --list-public-keys --with-colons | grep -E "pub:(.*:){10}.*[eE].*:" | awk -F ":" '{print $5}') + + #awk needs literal $10 + #shellcheck disable=SC2016 + keyuids=$(printf "%s" "$keyids" | xargs -n1 -I{} sh -c 'gpg --list-key --with-colons "{}" | grep "uid" | awk -F ":" '\''{printf "%s %s\n", "{}", $10}'\''') + + recipient=$(printf "%s" "$keyuids" | fzf | awk '{print $1}') + + printf "%s" "$files" | xargs -n1 gpg --encrypt --recipient "$recipient" + + # Clear selection + if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/.config/nnn/plugins/gsconnect b/.config/nnn/plugins/gsconnect new file mode 100755 index 0000000..f45f3d3 --- /dev/null +++ b/.config/nnn/plugins/gsconnect @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +#set -x +# Description: Send the selected (or hovered) files to your Android device using gsconnect daemon.js. +# GSConnect must be configured on the Android device and the PC. +# +# Shell: POSIX compliant +# Author: Darukutsu +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +gsconnect=$HOME/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/daemon.js +ids=$($gsconnect -l) + +for id in $ids; do + if [ -s "$selection" ]; then + xargs -0 < "$selection" -I{} "$gsconnect" -d "$id" --share-file="{}" + # Clear selection + printf "-" > "$NNN_PIPE" + else + "$gsconnect" -d "$id" --share-file="$2/$1" + fi +done diff --git a/.config/nnn/plugins/gutenread b/.config/nnn/plugins/gutenread new file mode 100755 index 0000000..036ff35 --- /dev/null +++ b/.config/nnn/plugins/gutenread @@ -0,0 +1,49 @@ +#!/usr/bin/env sh + +# Description: Browse Project Gutenberg catalogue by popularity, then download +# and read a book of your choice. +# +# Details: Set the variable EBOOK_ID to download in html format and read in w3m. +# Clear EBOOK_ID to browse available ebooks by popularity and set it to +# the ID once you find an interesting one. +# To download and read in epub format set READER to an epub reader like +# epr: https://github.com/wustho/epr +# +# More on EBOOK_ID: +# Wuthering Heights by Emily Brontë is at https://www.gutenberg.org/ebooks/768 +# So EBOOK_ID would be 768 +# +# Downloaded ebooks are at ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/ +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +EBOOK_ID="${EBOOK_ID:-""}" +DIR="${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/$EBOOK_ID" +BROWSE_LINK="https://www.gutenberg.org/ebooks/search/?sort_order=downloads" +BROWSER="${BROWSER:-w3m}" +READER="${READER:-""}" + +if [ -n "$EBOOK_ID" ]; then + if [ ! -e "$DIR" ]; then + mkdir -p "$DIR" + cd "$DIR" || exit 1 + + if [ -z "$READER" ]; then + curl -L -O "https://www.gutenberg.org/files/$EBOOK_ID/$EBOOK_ID-h.zip" + unzip "$EBOOK_ID"-h.zip + else + curl -L -o "$EBOOK_ID".epub "https://www.gutenberg.org/ebooks/$EBOOK_ID.epub.noimages" + fi + fi + + if [ -d "$DIR" ]; then + if [ -z "$READER" ]; then + "$BROWSER" "$DIR/$EBOOK_ID-h/$EBOOK_ID-h.htm" + else + "$READER" "$DIR/$EBOOK_ID.epub" + fi + fi +else + "$BROWSER" "$BROWSE_LINK" +fi diff --git a/.config/nnn/plugins/imgresize b/.config/nnn/plugins/imgresize new file mode 100755 index 0000000..351fe71 --- /dev/null +++ b/.config/nnn/plugins/imgresize @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +# Description: Resize images in a directory to screen resolution with imgp +# +# Dependencipes: imgp - https://github.com/jarun/imgp +# +# Notes: +# 1. Set res to avoid the desktop resolution prompt each time +# 2. MINSIZE is set to 1MB by default, adjust it if you want +# 3. imgp options used: +# a - adaptive mode +# c - convert PNG to JPG +# k - skip images matching specified hres/vres +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# set resolution (e.g. 1920x1080) +res="${RESOLUTION}" + +# set minimum image size (in bytes) to resize (default: 1MB) +MINSIZE="${MINSIZE:-1048576}" + +if [ -z "$res" ]; then + printf "desktop resolution (hxv): " + read -r res +fi + +if [ -n "$res" ] && [ -n "$MINSIZE" ]; then + imgp -ackx "$res" -s "$MINSIZE" +fi diff --git a/.config/nnn/plugins/imgur b/.config/nnn/plugins/imgur new file mode 100755 index 0000000..16d21bc --- /dev/null +++ b/.config/nnn/plugins/imgur @@ -0,0 +1,597 @@ +#!/usr/bin/env bash + +########################################################################## +# The MIT License +# +# Copyright (c) jomo +# +# Permission is hereby granted, free of charge, +# to any person obtaining a copy of this software and +# associated documentation files (the "Software"), to +# deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom +# the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +########################################################################## + +# https://github.com/jomo/imgur-screenshot +# https://help.imgur.com/hc/en-us/articles/209592766-Tools-for-Imgur +# +# Slightly modified for `nnn` integration +# +# Shell: Bash +# Description: Upload an image file to imgur + +if [ "${1}" = "--debug" ]; then + echo "########################################" + echo "Enabling debug mode" + echo "Please remove credentials before pasting" + echo "########################################" + echo "" + uname -a + for arg in ${0} "${@}"; do + echo -n "'${arg}' " + done + echo -e "\n" + shift + set -x +fi + +current_version="v1.7.4" + +function is_mac() { + uname | grep -q "Darwin" +} + +### IMGUR-SCREENSHOT DEFAULT CONFIG #### + +# You can override the config in ~/.config/imgur-screenshot/settings.conf + +imgur_anon_id="ea6c0ef2987808e" +imgur_icon_path="${HOME}/Pictures/imgur.png" + +imgur_acct_key="" +imgur_secret="" +login="false" +album_title="" +album_id="" +credentials_file="${HOME}/.config/imgur-screenshot/credentials.conf" + +file_name_format="imgur-%Y_%m_%d-%H:%M:%S.png" # when using scrot, must end with .png! +file_dir="${HOME}/Pictures" + +upload_connect_timeout="5" +upload_timeout="120" +upload_retries="1" + +# shellcheck disable=SC2034 +if is_mac; then + screenshot_select_command="screencapture -i %img" + screenshot_window_command="screencapture -iWa %img" + screenshot_full_command="screencapture %img" + open_command="open %url" +else + screenshot_select_command="scrot -s %img" + screenshot_window_command="scrot %img" + screenshot_full_command="scrot %img" + open_command="xdg-open %url" +fi +open="true" + +mode="select" +edit_command="gimp %img" +edit="false" +exit_on_album_creation_fail="true" + +log_file="${HOME}/.imgur-screenshot.log" + +auto_delete="" +copy_url="true" +keep_file="true" +check_update="true" + +# NOTICE: if you make changes here, also edit the docs at +# https://github.com/jomo/imgur-screenshot/wiki/Config + +# You can override the config in ~/.config/imgur-screenshot/settings.conf + +############## END CONFIG ############## + +settings_path="${HOME}/.config/imgur-screenshot/settings.conf" +if [ -f "${settings_path}" ]; then + # shellcheck disable=SC1090 + source "${settings_path}" +fi + +# dependency check +if [ "${1}" = "--check" ]; then + (type grep &>/dev/null && echo "OK: found grep") || echo "ERROR: grep not found" + if is_mac; then + if type growlnotify &>/dev/null; then + echo "OK: found growlnotify" + elif type terminal-notifier &>/dev/null; then + echo "OK: found terminal-notifier" + else + echo "ERROR: growlnotify nor terminal-notifier found" + fi + (type screencapture &>/dev/null && echo "OK: found screencapture") || echo "ERROR: screencapture not found" + (type pbcopy &>/dev/null && echo "OK: found pbcopy") || echo "ERROR: pbcopy not found" + else + (type notify-send &>/dev/null && echo "OK: found notify-send") || echo "ERROR: notify-send (from libnotify-bin) not found" + (type scrot &>/dev/null && echo "OK: found scrot") || echo "ERROR: scrot not found" + (type xclip &>/dev/null && echo "OK: found xclip") || echo "ERROR: xclip not found" + fi + (type curl &>/dev/null && echo "OK: found curl") || echo "ERROR: curl not found" + exit 0 +fi + + +# notify <'ok'|'error'> <text> +function notify() { + if is_mac; then + if type growlnotify &>/dev/null; then + growlnotify --icon "${imgur_icon_path}" --iconpath "${imgur_icon_path}" --title "${2}" --message "${3}" + else + terminal-notifier -appIcon "${imgur_icon_path}" -contentImage "${imgur_icon_path}" -title "imgur: ${2}" -message "${3}" + fi + else + if [ "${1}" = "error" ]; then + notify-send -a ImgurScreenshot -u critical -c "im.error" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}" + else + notify-send -a ImgurScreenshot -u low -c "transfer.complete" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}" + fi + fi +} + +function take_screenshot() { + echo "Please select area" + is_mac || sleep 0.1 # https://bbs.archlinux.org/viewtopic.php?pid=1246173#p1246173 + + cmd="screenshot_${mode}_command" + cmd=${!cmd//\%img/${1}} + + if ! shot_err="$(${cmd} &>/dev/null)"; then #takes a screenshot with selection + echo "Failed to take screenshot '${1}': '${shot_err}'. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}" + notify error "Something went wrong :(" "Information has been logged" + exit 1 + fi +} + +function check_for_update() { + # exit non-zero on HTTP error, output only the body (no stats) but output errors, follow redirects, output everything to stdout + remote_version="$(curl --compressed -fsSL --stderr - "https://api.github.com/repos/jomo/imgur-screenshot/releases" | grep -Em 1 --color 'tag_name":\s*".*"' | cut -d '"' -f 4)" + if [ -n "$remote_version" ]; then + if [ ! "${current_version}" = "${remote_version}" ] && [ -n "${current_version}" ] && [ -n "${remote_version}" ]; then + echo "Update found!" + echo "Version ${remote_version} is available (You have ${current_version})" + notify ok "Update found" "Version ${remote_version} is available (You have ${current_version}). https://github.com/jomo/imgur-screenshot" + echo "Check https://github.com/jomo/imgur-screenshot/releases/${remote_version} for more info." + elif [ -z "${current_version}" ] || [ -z "${remote_version}" ]; then + echo "Invalid empty version string" + echo "Current (local) version: '${current_version}'" + echo "Latest (remote) version: '${remote_version}'" + else + echo "Version ${current_version} is up to date." + fi + else + echo "Failed to check for latest version: ${remote_version}" + fi +} + +function check_oauth2_client_secrets() { + if [ -z "${imgur_acct_key}" ] || [ -z "${imgur_secret}" ]; then + echo "In order to upload to your account, register a new application at:" + echo "https://api.imgur.com/oauth2/addclient" + echo "Select 'OAuth 2 authorization without a callback URL'" + echo "Then, set the imgur_acct_key (Client ID) and imgur_secret in your config." + exit 1 + fi +} + +function load_access_token() { + token_expire_time=0 + # check for saved access_token and its expiration date + if [ -f "${credentials_file}" ]; then + # shellcheck disable=SC1090 + source "${credentials_file}" + fi + current_time="$(date +%s)" + preemptive_refresh_time="$((10*60))" + expired="$((current_time > (token_expire_time - preemptive_refresh_time)))" + if [ -n "${refresh_token}" ]; then + # token already set + if [ "${expired}" -eq "0" ]; then + # token expired + refresh_access_token "${credentials_file}" + fi + else + acquire_access_token "${credentials_file}" + fi +} + +function acquire_access_token() { + check_oauth2_client_secrets + # prompt for a PIN + authorize_url="https://api.imgur.com/oauth2/authorize?client_id=${imgur_acct_key}&response_type=pin" + echo "Go to" + echo "${authorize_url}" + echo "and grant access to this application." + read -rp "Enter the PIN: " imgur_pin + + if [ -z "${imgur_pin}" ]; then + echo "PIN not entered, exiting" + exit 1 + fi + + # exchange the PIN for access token and refresh token + response="$(curl --compressed -fsSL --stderr - \ + -F "client_id=${imgur_acct_key}" \ + -F "client_secret=${imgur_secret}" \ + -F "grant_type=pin" \ + -F "pin=${imgur_pin}" \ + https://api.imgur.com/oauth2/token)" + save_access_token "${response}" "${1}" +} + +function refresh_access_token() { + check_oauth2_client_secrets + token_url="https://api.imgur.com/oauth2/token" + # exchange the refresh token for access_token and refresh_token + if ! response="$(curl --compressed -fsSL --stderr - \ + -F "client_id=${imgur_acct_key}" \ + -F "client_secret=${imgur_secret}" \ + -F "grant_type=refresh_token" \ + -F "refresh_token=${refresh_token}" \ + "${token_url}" + )"; then + # curl failed + handle_upload_error "${response}" "${token_url}" + exit 1 + fi + save_access_token "${response}" "${1}" +} + +function save_access_token() { + if ! grep -q "access_token" <<<"${1}"; then + # server did not send access_token + echo "Error: Something is wrong with your credentials:" + echo "${1}" + exit 1 + fi + + access_token="$(grep -Eo 'access_token":".*"' <<<"${1}" | cut -d '"' -f 3)" + refresh_token="$(grep -Eo 'refresh_token":".*"' <<<"${1}" | cut -d '"' -f 3)" + expires_in="$(grep -Eo 'expires_in":[0-9]*' <<<"${1}" | cut -d ':' -f 2)" + token_expire_time="$(( $(date +%s) + expires_in ))" + + # create dir if not exist + mkdir -p "$(dirname "${2}")" 2>/dev/null + touch "${2}" && chmod 600 "${2}" + cat <<EOF > "${2}" +access_token="${access_token}" +refresh_token="${refresh_token}" +token_expire_time="${token_expire_time}" +EOF +} + +function fetch_account_info() { + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/account/me)" + if grep -Eq '"success":\s*true' <<<"${response}"; then + username="$(grep -Eo '"url":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + echo "Logged in as ${username}." + echo "https://${username}.imgur.com" + else + echo "Failed to fetch info: ${response}" + fi +} + +function delete_image() { + response="$(curl --compressed -X DELETE -fsSL --stderr - -H "Authorization: Client-ID ${1}" "https://api.imgur.com/3/image/${2}")" + if grep -Eq '"success":\s*true' <<<"${response}"; then + echo "Image successfully deleted (delete hash: ${2})." >> "${3}" + else + echo "The Image could not be deleted: ${response}." >> "${3}" + fi +} + +function upload_authenticated_image() { + echo "Uploading '${1}'..." + title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)" + if [ -n "${album_id}" ]; then + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)" + else + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)" + fi + + # JSON parser premium edition (not really) + if grep -Eq '"success":\s*true' <<<"${response}"; then + img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https! + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + + if [ -n "${auto_delete}" ]; then + export -f delete_image + echo "Deleting image in ${auto_delete} seconds." + nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" & + fi + + handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}" + else # upload failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_upload_error "${err_msg}" "${1}" + fi +} + +function upload_anonymous_image() { + echo "Uploading '${1}'..." + title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)" + if [ -n "${album_id}" ]; then + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" https://api.imgur.com/3/image)" + else + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" https://api.imgur.com/3/image)" + fi + # JSON parser premium edition (not really) + if grep -Eq '"success":\s*true' <<<"${response}"; then + img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https! + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + + if [ -n "${auto_delete}" ]; then + export -f delete_image + echo "Deleting image in ${auto_delete} seconds." + nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" & + fi + + handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}" + else # upload failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_upload_error "${err_msg}" "${1}" + fi +} + +function handle_upload_success() { + echo "" + echo "image link: ${1}" + echo "delete link: ${2}" + + if [ "${copy_url}" = "true" ] && [ -z "${album_title}" ]; then + if is_mac; then + echo -n "${1}" | pbcopy + else + echo -n "${1}" | xclip -selection clipboard + fi + echo "URL copied to clipboard" + fi + + # print to log file: image link, image location, delete link + echo -e "${1}\t${3}\t${2}" >> "${log_file}" + + notify ok "Upload done!" "${1}" + +# if [ ! -z "${open_command}" ] && [ "${open}" = "true" ]; then +# open_cmd=${open_command//\%url/${1}} +# open_cmd=${open_cmd//\%img/${2}} +# echo "Opening '${open_cmd}'" +# eval "${open_cmd}" +# fi +} + +function handle_upload_error() { + error="Upload failed: \"${1}\"" + echo "${error}" + echo -e "Error\t${2}\t${error}" >> "${log_file}" + notify error "Upload failed :(" "${1}" +} + +function handle_album_creation_success() { + echo "" + echo "Album link: ${1}" + echo "Delete hash: ${2}" + echo "" + + notify ok "Album created!" "${1}" + + if [ "${copy_url}" = "true" ]; then + if is_mac; then + echo -n "${1}" | pbcopy + else + echo -n "${1}" | xclip -selection clipboard + fi + echo "URL copied to clipboard" + fi + + # print to log file: album link, album title, delete hash + echo -e "${1}\t\"${3}\"\t${2}" >> "${log_file}" +} + +function handle_album_creation_error() { + error="Album creation failed: \"${1}\"" + echo -e "Error\t${2}\t${error}" >> "${log_file}" + notify error "Album creation failed :(" "${1}" + if [ ${exit_on_album_creation_fail} ]; then + exit 1 + fi +} + +while [ ${#} != 0 ]; do + case "${1}" in + -h | --help) + echo "usage: ${0} [--debug] [-c | --check | -v | -h | -u]" + echo " ${0} [--debug] [option]... [file]..." + echo "" + echo " --debug Enable debugging, must be first option" + echo " -h, --help Show this help, exit" + echo " -v, --version Show current version, exit" + echo " --check Check if all dependencies are installed, exit" + echo " -c, --connect Show connected imgur account, exit" + echo " -o, --open <true|false> Override 'open' config" + echo " -e, --edit <true|false> Override 'edit' config" + echo " -i, --edit-command <command> Override 'edit_command' config (include '%img'), sets --edit 'true'" + echo " -l, --login <true|false> Override 'login' config" + echo " -a, --album <album_title> Create new album and upload there" + echo " -A, --album-id <album_id> Override 'album_id' config" + echo " -k, --keep-file <true|false> Override 'keep_file' config" + echo " -d, --auto-delete <s> Automatically delete image after <s> seconds" + echo " -u, --update Check for updates, exit" + echo " file Upload file instead of taking a screenshot" + exit 0;; + -v | --version) + echo "${current_version}" + exit 0;; + -s | --select) + mode="select" + shift;; + -w | --window) + mode="window" + shift;; + -f | --full) + mode="full" + shift;; + -o | --open) + # shellcheck disable=SC2034 + open="${2}" + shift 2;; + -e | --edit) + edit="${2}" + shift 2;; + -i | --edit-command) + edit_command="${2}" + edit="true" + shift 2;; + -l | --login) + login="${2}" + shift 2;; + -c | --connect) + load_access_token + fetch_account_info + exit 0;; + -a | --album) + album_title="${2}" + shift 2;; + -A | --album-id) + album_id="${2}" + shift 2;; + -k | --keep-file) + keep_file="${2}" + shift 2;; + -d | --auto-delete) + auto_delete="${2}" + shift 2;; + -u | --update) + check_for_update + exit 0;; + *) + upload_files=("${@}") + break;; + esac +done + +if [ "${login}" = "true" ]; then + # load before changing directory + load_access_token +fi + + +if [ -n "${album_title}" ]; then + if [ "${login}" = "true" ]; then + response="$(curl -fsSL --stderr - \ + -F "title=${album_title}" \ + -H "Authorization: Bearer ${access_token}" \ + https://api.imgur.com/3/album)" + else + response="$(curl -fsSL --stderr - \ + -F "title=${album_title}" \ + -H "Authorization: Client-ID ${imgur_anon_id}" \ + https://api.imgur.com/3/album)" + fi + if grep -Eq '"success":\s*true' <<<"${response}"; then # Album creation successful + echo "Album '${album_title}' successfully created" + album_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + handle_album_creation_success "https://imgur.com/a/${album_id}" "${del_id}" "${album_title}" + + if [ "${login}" = "false" ]; then + album_id="${del_id}" + fi + else # Album creation failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_album_creation_error "${err_msg}" "${album_title}" + fi +fi + +if [ -z "${upload_files[*]}" ]; then + upload_files[0]="" +fi + +for upload_file in "${upload_files[@]}"; do + + if [ -z "${upload_file}" ]; then + cd "${file_dir}" || exit 1 + + # new filename with date + img_file="$(date +"${file_name_format}")" + take_screenshot "${img_file}" + else + # upload file instead of screenshot + img_file="${upload_file}" + fi + + # get full path + #cd "$(dirname "$(realpath "${img_file}")")" + #img_file="$(realpath "${img_file}")" + + # check if file exists + if ! [ -f "${img_file}" ]; then + echo "file '${img_file}' doesn't exist !" + read -r _ + exit 1 + fi + + # open image in editor if configured + if [ "${edit}" = "true" ]; then + edit_cmd=${edit_command//\%img/${img_file}} + echo "Opening editor '${edit_cmd}'" + if ! (eval "${edit_cmd}"); then + echo "Error for image '${img_file}': command '${edit_cmd}' failed, not uploading. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}" + notify error "Something went wrong :(" "Information has been logged" + exit 1 + fi + fi + + if [ "${login}" = "true" ]; then + upload_authenticated_image "${img_file}" + else + upload_anonymous_image "${img_file}" + fi + + # delete file if configured + if [ "${keep_file}" = "false" ] && [ -z "${1}" ]; then + echo "Deleting temp file ${file_dir}/${img_file}" + rm -rf "${img_file}" + fi + + echo "" +done + + +if [ "${check_update}" = "true" ]; then + check_for_update +fi + +read -r _ diff --git a/.config/nnn/plugins/imgview b/.config/nnn/plugins/imgview new file mode 100755 index 0000000..58077a9 --- /dev/null +++ b/.config/nnn/plugins/imgview @@ -0,0 +1,111 @@ +#!/usr/bin/env sh + +# Description: Open hovered or current directory in image viewer. +# Generates media thumbnails with optional dependencies. +# +# Dependencies: +# - imv (https://github.com/eXeC64/imv) or, +# - sxiv (https://github.com/muennich/sxiv) or, +# - nsxiv (https://github.com/nsxiv/nsxiv) or, +# - ucollage (https://github.com/ckardaris/ucollage) or, +# - lsix (https://github.com/hackerb9/lsix), or +# - viu (https://github.com/atanunq/viu), or +# - catimg (https://github.com/posva/catimg), or +# - optional: ffmpeg for audio thumbnails (album art) +# - optional: ffmpegthumbnailer for video thumbnails +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana, Luuk van Baal +# +# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews +# if you want to keep media thumbnails on disk between reboots. +NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-${TMPDIR:-/tmp}/nnn/previews}" + +exit_prompt() { + [ -n "$1" ] && printf "%s\n" "$1" + printf "%s" "Press any key to exit..." + cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" + clear + exit +} + +make_thumbs() { + mkdir -p "$NNN_PREVIEWDIR$dir" || return + if [ "$1" -eq 3 ]; then + [ -d "$target" ] && exit_prompt "$2 can only display a single image" + mime="$(file -bL --mime-type -- "$target")" + case "$mime" in + audio/*) ffmpeg -i "$target" "$NNN_PREVIEWDIR$target.jpg" -y >/dev/null 2>&1 + ret="$NNN_PREVIEWDIR/$target.jpg" ;; + video/*) ffmpegthumbnailer -i "$target" -o "$NNN_PREVIEWDIR$target.jpg" 2> /dev/null + ret="$NNN_PREVIEWDIR/$target.jpg" ;; + *) ret="$target" ;; + esac + fi + for file in "$dir"/*; do + if [ ! -f "$NNN_PREVIEWDIR$file.jpg" ]; then + case "$(file -bL --mime-type -- "$file")" in + audio/*) [ "$1" -ne 0 ] && ffmpeg -i "$file" "$NNN_PREVIEWDIR$file.jpg" -y >/dev/null 2>&1 ;; + video/*) [ "$1" -ne 1 ] && ffmpegthumbnailer -i "$file" -o "$NNN_PREVIEWDIR$file.jpg" 2> /dev/null ;; + esac + fi + done + for file in "$NNN_PREVIEWDIR$dir"/*; do + filename="$(basename "$file" .jpg)" + [ ! -e "$dir/$filename" ] && rm "$file" 2>/dev/null + done +} + +listimages() { + find -L "$dir" "$NNN_PREVIEWDIR$dir" -maxdepth 1 -type f -print0 2>/dev/null | sort -z +} + +view_files() { + [ -f "$target" ] && count="-n $(listimages | grep -a -m 1 -ZznF "$target" | cut -d: -f1)" + case "$1" in + nsxiv) listimages | xargs -0 nsxiv -a "${count:--t}" -- ;; + sxiv) listimages | xargs -0 sxiv -a "${count:--t}" -- ;; + imv*) listimages | xargs -0 "$1" "${count:-}" -- ;; + esac +} + +target="$(readlink -f "$1")" +[ -d "$target" ] && dir="$target" || dir="${target%/*}" +if uname | grep -q "Darwin"; then + [ -f "$1" ] && open "$1" >/dev/null 2>&1 & +elif type lsix >/dev/null 2>&1; then + if [ -d "$target" ]; then + cd "$target" || exit_prompt + fi + make_thumbs "" + clear + lsix + cd "$NNN_PREVIEWDIR$dir" && lsix + exit_prompt +elif type ucollage >/dev/null 2>&1; then + type ffmpeg >/dev/null 2>&1 && make_thumbs 1 + UCOLLAGE_EXPAND_DIRS=1 ucollage "$dir" "$NNN_PREVIEWDIR$dir" || exit_prompt +elif type sxiv >/dev/null 2>&1; then + type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs 0 + view_files sxiv >/dev/null 2>&1 & +elif type nsxiv >/dev/null 2>&1; then + type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs 0 + view_files nsxiv >/dev/null 2>&1 & +elif type imv >/dev/null 2>&1; then + make_thumbs "" + view_files imv >/dev/null 2>&1 & +elif type imvr >/dev/null 2>&1; then + make_thumbs "" + view_files imvr >/dev/null 2>&1 & +elif type viu >/dev/null 2>&1; then + clear + make_thumbs 3 viu + viu -n "$ret" + exit_prompt +elif type catimg >/dev/null 2>&1; then + make_thumbs 3 catimg + catimg "$ret" + exit_prompt +else + exit_prompt "Please install sxiv/nsxiv/imv/viu/catimg/lsix." +fi diff --git a/.config/nnn/plugins/ipinfo b/.config/nnn/plugins/ipinfo new file mode 100755 index 0000000..4ff6f41 --- /dev/null +++ b/.config/nnn/plugins/ipinfo @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# Description: Shows the external IP address and whois information. Useful over VPNs. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +IP=$(curl -s ifconfig.me) + +whois "$IP" +echo your external IP address is "$IP" + +read -r _ diff --git a/.config/nnn/plugins/kdeconnect b/.config/nnn/plugins/kdeconnect new file mode 100755 index 0000000..5f63d8b --- /dev/null +++ b/.config/nnn/plugins/kdeconnect @@ -0,0 +1,24 @@ +#!/usr/bin/env sh + +# Description: Send the selected files to your Android device using kdeconnect-cli. +# kdeconnect must be configured on the Android device and the PC. +# +# Shell: POSIX compliant +# Author: juacq97 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +id=$(kdeconnect-cli -a --id-only | awk '{print $1}') +if [ -s "$selection" ]; then + kdeconnect-cli -d "$id" --share "$(cat "$selection")" + + # If you want a system notification, uncomment the next 3 lines. + #notify-send -a "Kdeconnect" "Sending $(cat "$selection")" +#else + #notify-send -a "Kdeconnect" "No file selected" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/.config/nnn/plugins/launch b/.config/nnn/plugins/launch new file mode 100755 index 0000000..d666cc5 --- /dev/null +++ b/.config/nnn/plugins/launch @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +# Description: Independent POSIX-compliant GUI application launcher. +# Fuzzy find executables in $PATH and launch an application. +# stdin, stdout, stderr are suppressed so CLI tools exit silently. +# +# To configure launch as an independent app launcher add a keybind +# to open launch in a terminal e.g., +# +# xfce4-terminal -e "${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/launch +# +# Dependencies: fzf +# +# Usage: launch [delay] +# delay is in seconds, if omitted launch waits for 1 sec +# +# Integration with nnn: launch is installed with other plugins, nnn picks it up. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# shellcheck disable=SC2086 + +IFS=':' + +get_selection() { + if type fzf >/dev/null 2>&1; then + { IFS=':'; ls -H $PATH; } | sort | fzf + else + exit 1 + fi +} + +if selection=$( get_selection ); then + setsid "$selection" 2>/dev/null 1>/dev/null & + + if [ -n "$1" ]; then + sleep "$1" + else + sleep 1 + fi +fi diff --git a/.config/nnn/plugins/mimelist b/.config/nnn/plugins/mimelist new file mode 100755 index 0000000..ccfe05a --- /dev/null +++ b/.config/nnn/plugins/mimelist @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Description: Find and list files by mime type in smart context +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# shellcheck disable=SC1090,SC1091 +. "$(dirname "$0")"/.nnn-plugin-helper + +printf "mime (e.g., video/audio/image): " +read -r mime + +printf "%s" "+l" > "$NNN_PIPE" +find . | file -if- | grep "$mime" | awk -F: '{printf "%s\0", $1}' > "$NNN_PIPE" diff --git a/.config/nnn/plugins/moclyrics b/.config/nnn/plugins/moclyrics new file mode 100755 index 0000000..2f69807 --- /dev/null +++ b/.config/nnn/plugins/moclyrics @@ -0,0 +1,40 @@ +#!/usr/bin/env sh + +# Description: Fetches the lyrics of the track currently playing in MOC +# +# Dependencies: ddgr (https://github.com/jarun/ddgr) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# Check if MOC server is running +cmd=$(pgrep -x mocp 2>/dev/null) +ret=$cmd +if [ -z "$ret" ]; then + exit +fi + +# Grab the output +out="$(mocp -i)" + +# Check if anything is playing +state=$(echo "$out" | grep "State:" | cut -d' ' -f2) +if ! [ "$state" = 'PLAY' ]; then + exit +fi + +# Try by Artist and Song Title first +ARTIST="$(echo "$out" | grep 'Artist:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')" +TITLE="$(echo "$out" | grep 'SongTitle:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')" + +if [ -n "$ARTIST" ] && [ -n "$TITLE" ]; then + ddgr -w azlyrics.com --ducky "$ARTIST" "$TITLE" +else + # Try by file name + FILENAME="$(basename "$(echo "$out" | grep 'File:' | cut -d':' -f2)")" + FILENAME="$(echo "${FILENAME%%.*}" | tr -d -)" + + if [ -n "$FILENAME" ]; then + ddgr -w azlyrics.com --ducky "$FILENAME" + fi +fi diff --git a/.config/nnn/plugins/mocq b/.config/nnn/plugins/mocq new file mode 100755 index 0000000..038ecc9 --- /dev/null +++ b/.config/nnn/plugins/mocq @@ -0,0 +1,89 @@ +#!/usr/bin/env sh + +# Description: Appends and optionally plays music in MOC +# +# Notes: +# - if selection is available, plays it, else plays the current file or directory +# - appends tracks and exits is MOC is running, else clears playlist and adds tracks +# - to let mocp shuffle tracks, set SHUFFLE=1 +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +IFS="$(printf '\n\r')" +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +cmd=$(pgrep -x mocp 2>/dev/null) +ret=$cmd + +SHUFFLE="${SHUFFLE:-0}" + +mocp_add () +{ + if [ "$SHUFFLE" = 1 ]; then + if [ "$resp" = "y" ]; then + arr=$(tr '\0' '\n' < "$selection") + elif [ -n "$1" ]; then + arr="$1" + fi + + for entry in $arr + do + if [ -d "$entry" ]; then + arr2=$arr2$(find "$entry" -type f \( ! -iname "*.m3u" ! -iname "*.pls" \)) + elif echo "$entry" | grep -qv '\.m3u$\|\.pls$' ; then + arr2=$(printf "%s\n%s" "$entry" "$arr2") + fi + done + + mocp -o shuffle + echo "$arr2" | xargs -d "\n" mocp -a + else + if [ "$resp" = "y" ]; then + xargs < "$selection" -0 mocp -a + else + mocp -a "$1" + fi + fi +} + +if [ ! -s "$selection" ] && [ -z "$1" ]; then + exit +fi + +if [ "$2" = "opener" ]; then + : +elif [ -s "$selection" ]; then + printf "Work with selection? Enter 'y' to confirm: " + read -r resp +fi + +if [ -z "$ret" ]; then + # mocp not running + mocp -S +else + # mocp running, check if it's playing + state=$(mocp -i | grep "State:" | cut -d' ' -f2) + + if [ "$state" = 'PLAY' ]; then + # add to playlist and exit + mocp_add "$1" + + # uncomment the line below to show mocp interface after appending + # mocp + + exit + fi +fi + +# clear selection and play +mocp -c +mocp_add "$1" "$resp" +mocp -p + +# uncomment the line below to show mocp interface after appending +# mocp + +# Clear selection +if [ "$resp" = "y" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/mp3conv b/.config/nnn/plugins/mp3conv new file mode 100755 index 0000000..029f544 --- /dev/null +++ b/.config/nnn/plugins/mp3conv @@ -0,0 +1,41 @@ +#!/usr/bin/env sh + +# Description: Extract audio from multimedia files and convert to mp3 +# +# Dependencies: ffmpeg compiled with libmp3lame audio codec support +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +outdir=_mp3files + +handle_multimedia() { + mime="${1}" + file="${2}" + + case "${mime}" in + audio/* | video/*) + ffmpeg -i "${file}" -vn -codec:a libmp3lame -q:a 2 "${outdir}/${file%.*}.mp3" + ;; + *) + ;; + esac +} + +printf "Process 'a'll in directory or 'c'urrent? " +read -r resp + +if [ "$resp" = "a" ]; then + if ! [ -e "${outdir}" ]; then + mkdir "${outdir}" + fi + + for f in *; do + if [ -f "${f}" ]; then + mimestr="$( file --dereference --brief --mime-type -- "${f}" )" + handle_multimedia "${mimestr}" "${f}" + fi + done +elif [ "$resp" = "c" ] && [ -f "$1" ]; then + ffmpeg -i "${1}" -vn -codec:a libmp3lame -q:a 2 "${1%.*}.mp3" +fi diff --git a/.config/nnn/plugins/mtpmount b/.config/nnn/plugins/mtpmount new file mode 100755 index 0000000..d6feea0 --- /dev/null +++ b/.config/nnn/plugins/mtpmount @@ -0,0 +1,76 @@ +#!/usr/bin/env sh + +# Description: Toggle mount of MTP device (eg. Android device) +# 'l' to list mountable devices +# 'n' integer associated to device to mount +# 'q'/'Return' exit +# +# Dependencies: gvfs-mtp +# +# Notes: The MTP device should be mounted at /run/user/$UID/gvfs. +# Put /run/user/$UID/gvfs to bookmark entries (NNN_BMS) for faster access. +# Make sure the device is unlocked when mounting. +# +# When doing copy-paste into MTP device, you will get an error like this: +# cp: preserving times for './gambar1.png': Operation not supported +# That just means the file is copied but timestamp won't be preserved. +# It's like doing `cp -p localfile.txt file-to-SMB.txt`. +# +# Shell: POSIX compliant +# Author: Benawi Adha + +prompt="Device number ('l' to list): " + +IFS=' +' + +lsmtp () { + devs=$(gio mount -li | grep -e 'activation_root' | sed 's/\s*activation_root=//g') + c=1 + printf "Devices list:\n" + for i in $devs; do + printf "%s %s\\n" "$c" "$i" + c=$(( c + 1 )) + done + echo +} + +lsmtp +printf "%s" "$prompt" +read -r input + +while [ -n "$input" ] +do + if [ "$input" = "l" ]; then + lsmtp + elif [ "$input" = "q" ] || [ "$input" -eq 0 ]; then + exit + elif [ "$input" -le "$(printf '%s\n' "${devs}" | grep -c '^')" ]; then + # dev=$(printf "%s\n" "$devs" | cut -d$'\n' -f${input}) + c=1 + for i in $devs; do + dev=$i + if [ "$input" -eq $c ]; then + break + fi + c=$(( c + 1 )) + done + + if (gio mount -l | grep '^Mount([1-9]).*'"$dev" ) 1>/dev/null; then + if gio mount -u "${dev}"; then + printf "%s unmounted\n" "$dev" + fi + else + if gio mount "${dev}"; then + printf "%s mounted to /run/user/\$UID/gvfs\n" "$dev" + fi + fi + echo + else + printf "Invalid input\n" + fi + + printf "%s" "$prompt" + read -r input +done + diff --git a/.config/nnn/plugins/nbak b/.config/nnn/plugins/nbak new file mode 100755 index 0000000..f9cb644 --- /dev/null +++ b/.config/nnn/plugins/nbak @@ -0,0 +1,75 @@ +#!/usr/bin/env sh + +# Description: Backup nnn configuration +# - config dir content +# - environment config +# - shell functions and aliases +# +# Shell: POSIX compliant +# Author: Léo Villeveygoux + +nnn_aliases="n nnn" + +outdir="nnn-$(whoami)@$(hostname)" + +outfile="${outdir}.tar.bz2" + +shellname="$(basename "$SHELL")" + +conffile="config.txt" + +configdir="${XDG_CONFIG_HOME:-$HOME/.config}/nnn" + +workdir="$PWD" + +tempdir="$(mktemp -d)" + +mkdir "$tempdir/$outdir" + +if [ ! -d "$tempdir" ]; then + echo "Can't create work directory." >&2 + exit 1 +fi + +cd "$tempdir/$outdir" || exit 1 + +# Backing up config dir content +cp -r "$configdir" . || exit 1 + +# Environment config +env | sed "s/'/'\\\\''/" |\ + awk '/^NNN_/{print "export '\''"$0"'\''"}' > "$conffile" + +# Shell functions/aliases +case "$shellname" in + bash) + for name in $nnn_aliases ; do + if [ "$(bash -ic "type -t $name")" = "function" ] ; then + bash -ic "type $name" | tail -n+2 >> "$conffile" + elif bash -ic "alias $name" >/dev/null 2>&1 ; then + bash -ic "alias $name" >> "$conffile" + fi + done + ;; + zsh) + for name in $nnn_aliases ; do + if zsh -ic "functions $name" ; then + zsh -ic "functions $name" >> "$conffile" + elif zsh -ic "alias $name" ; then + echo alias "$(zsh -ic "alias $name")" >> "$conffile" + fi + done + ;; + + *) + echo "Unknown shell, skipping alias/function checking." >&2 + ;; +esac + +cd .. || exit 1 + +printf "Saving as '%s' ... " "$workdir/$outfile" + +tar caf "$workdir/$outfile" "$outdir" && echo "Done" || echo "Failed" + +cd "$workdir" && rm -rf "$tempdir" diff --git a/.config/nnn/plugins/nmount b/.config/nnn/plugins/nmount new file mode 100755 index 0000000..e92fd5d --- /dev/null +++ b/.config/nnn/plugins/nmount @@ -0,0 +1,55 @@ +#!/usr/bin/env sh + +# Description: Toggle mount status of a device using pmount +# If the device is not mounted, it will be mounted. +# If the device is mounted, it will be unmounted and powered down. +# +# Dependencies: lsblk, pmount +# +# Usage: Runs `lsblk` on 'l', exits on 'Return`. +# +# Notes: +# - The script uses Linux-specific lsblk to list block devices. Alternatives: +# macOS: "diskutil list" +# BSD: "geom disk list" +# - The script uses udisksctl (from udisks2) to power down devices. This is also Linux-specific. +# Users on non-Linux platforms can comment it and use an alterntive to power-down disks. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +prompt="device name [e.g. sdXn] ('l'ist, 'q'uit): " + +lsblk + +printf "\nEnsure you aren't still in the mounted device.\n" +printf "%s" "$prompt" +read -r dev + +while [ -n "$dev" ] +do + if [ "$dev" = "l" ]; then + lsblk + elif [ "$dev" = "q" ]; then + exit + else + if grep -qs "$dev " /proc/mounts; then + sync + if pumount "$dev" + then + echo "$dev" unmounted. + if udisksctl power-off -b /dev/"$dev" + then + echo "$dev" ejected. + fi + fi + else + pmount "$dev" + echo "$dev" mounted to "$(lsblk -n /dev/"$dev" | rev | cut -d' ' -f1 | rev)". + fi + fi + + echo + printf "%s" "$prompt" + read -r dev +done diff --git a/.config/nnn/plugins/nuke b/.config/nnn/plugins/nuke new file mode 100755 index 0000000..b3eafc8 --- /dev/null +++ b/.config/nnn/plugins/nuke @@ -0,0 +1,555 @@ +#!/usr/bin/env sh + +# Description: Sample script to play files in apps by file type or mime +# +# Shell: POSIX compliant +# Usage: nuke filepath +# +# Integration with nnn: +# 1. Export the required config: +# export NNN_OPENER=/absolute/path/to/nuke +# # Otherwise, if nuke is in $PATH +# # export NNN_OPENER=nuke +# 2. Run nnn with the program option to indicate a CLI opener +# nnn -c +# # The -c program option overrides option -e +# 3. nuke can use nnn plugins (e.g. mocq is used for audio), $PATH is updated. +# +# Details: +# Inspired by ranger's scope.sh, modified for usage with nnn. +# +# Guards against accidentally opening mime types like executables, shared libs etc. +# +# Tries to play 'file' (1st argument) in the following order: +# 1. by extension +# 2. by mime (image, video, audio, pdf) +# 3. by mime (other file types) +# 4. by mime (prompt and run executables) +# +# Modification tips: +# 1. Invokes CLI utilities by default. Set GUI to 1 to enable GUI apps. +# 2. PAGER is "less -R". +# 3. Start GUI apps in bg to unblock. Redirect stdout and strerr if required. +# 4. Some CLI utilities are piped to the $PAGER, to wait and quit uniformly. +# 5. If the output cannot be paged use "read -r _" to wait for user input. +# 6. On a DE, try 'xdg-open' or 'open' in handle_fallback() as last resort. +# +# Feel free to change the utilities to your favourites and add more mimes. +# +# Defaults: +# By extension (only the enabled ones): +# most archives: list with atool, bsdtar +# rar: list with unrar +# 7-zip: list with 7z +# pdf: zathura (GUI), pdftotext, mutool, exiftool +# audio: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool +# avi|mkv|mp4: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool +# log: vi +# torrent: rtorrent, transmission-show +# odt|ods|odp|sxw: odt2txt +# md: glow (https://github.com/charmbracelet/glow), lowdown (https://kristaps.bsd.lv/lowdown) +# htm|html|xhtml: w3m, lynx, elinks +# json: jq, python (json.tool module) +# Multimedia by mime: +# image/*: imv/sxiv/nsxiv (GUI), viu (https://github.com/atanunq/viu), img2txt, exiftool +# video/*: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool +# audio/*: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool +# application/pdf: zathura (GUI), pdftotext, mutool, exiftool +# Other mimes: +# text/troff: man -l +# text/* | */xml: vi +# image/vnd.djvu): djvutxt, exiftool +# +# TODO: +# 1. Adapt, test and enable all mimes +# 2. Clean-up the unnecessary exit codes + +# set to 1 to enable GUI apps and/or BIN execution +GUI="${GUI:-0}" +BIN="${BIN:-0}" + +set -euf -o noclobber -o noglob -o nounset +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +PATH=$PATH:"${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins" +IMAGE_CACHE_PATH="$(dirname "$1")"/.thumbs + +FPATH="$1" +FNAME=$(basename "$1") +EDITOR="${VISUAL:-${EDITOR:-vi}}" +PAGER="${PAGER:-less -R}" +ext="${FNAME##*.}" +if [ -n "$ext" ]; then + ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" +fi + +is_mac() { + uname | grep -q "Darwin" +} + +handle_pdf() { + if [ "$GUI" -ne 0 ]; then + if is_mac; then + nohup open "${FPATH}" >/dev/null 2>&1 & + elif type zathura >/dev/null 2>&1; then + nohup zathura "${FPATH}" >/dev/null 2>&1 & + else + return + fi + elif type pdftotext >/dev/null 2>&1; then + ## Preview as text conversion + pdftotext -l 10 -nopgbrk -q -- "${FPATH}" - | eval "$PAGER" + elif type mutool >/dev/null 2>&1; then + mutool draw -F txt -i -- "${FPATH}" 1-10 | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + else + return + fi + exit 0 +} + +handle_audio() { + if type mocp >/dev/null 2>&1 && type mocq >/dev/null 2>&1; then + mocq "${FPATH}" "opener" >/dev/null 2>&1 + elif type mpv >/dev/null 2>&1; then + mpv "${FPATH}" >/dev/null 2>&1 & + elif type media_client >/dev/null 2>&1; then + media_client play "${FPATH}" >/dev/null 2>&1 & + elif type mediainfo >/dev/null 2>&1; then + mediainfo "${FPATH}" | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}"| eval "$PAGER" + else + return + fi + exit 0 +} + +handle_video() { + if [ "$GUI" -ne 0 ]; then + if is_mac; then + nohup open "${FPATH}" >/dev/null 2>&1 & + elif type smplayer >/dev/null 2>&1; then + nohup smplayer "${FPATH}" >/dev/null 2>&1 & + elif type mpv >/dev/null 2>&1; then + nohup mpv "${FPATH}" >/dev/null 2>&1 & + else + return + fi + elif type ffmpegthumbnailer >/dev/null 2>&1; then + # Thumbnail + [ -d "${IMAGE_CACHE_PATH}" ] || mkdir "${IMAGE_CACHE_PATH}" + ffmpegthumbnailer -i "${FPATH}" -o "${IMAGE_CACHE_PATH}/${FNAME}.jpg" -s 0 + viu -n "${IMAGE_CACHE_PATH}/${FNAME}.jpg" | eval "$PAGER" + elif type mediainfo >/dev/null 2>&1; then + mediainfo "${FPATH}" | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}"| eval "$PAGER" + else + return + fi + exit 0 +} + +# handle this extension and exit +handle_extension() { + case "${ext}" in + ## Archive + a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ + rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip) + if type atool >/dev/null 2>&1; then + atool --list -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type bsdtar >/dev/null 2>&1; then + bsdtar --list --file "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + rar) + if type unrar >/dev/null 2>&1; then + ## Avoid password prompt by providing empty password + unrar lt -p- -- "${FPATH}" | eval "$PAGER" + fi + exit 1;; + 7z) + if type 7z >/dev/null 2>&1; then + ## Avoid password prompt by providing empty password + 7z l -p -- "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + + ## PDF + pdf) + handle_pdf + exit 1;; + + ## Audio + aac|flac|m4a|mid|midi|mpa|mp2|mp3|ogg|wav|wma) + handle_audio + exit 1;; + + ## Video + avi|mkv|mp4) + handle_video + exit 1;; + + ## Log files + log) + "$EDITOR" "${FPATH}" + exit 0;; + + ## BitTorrent + torrent) + if type rtorrent >/dev/null 2>&1; then + rtorrent "${FPATH}" + exit 0 + elif type transmission-show >/dev/null 2>&1; then + transmission-show -- "${FPATH}" + exit 0 + fi + exit 1;; + + ## OpenDocument + odt|ods|odp|sxw) + if type odt2txt >/dev/null 2>&1; then + ## Preview as text conversion + odt2txt "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + + ## Markdown + md) + if type glow >/dev/null 2>&1; then + glow -sdark "${FPATH}" | eval "$PAGER" + exit 0 + elif type lowdown >/dev/null 2>&1; then + lowdown -Tterm "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + + ## HTML + htm|html|xhtml) + ## Preview as text conversion + if type w3m >/dev/null 2>&1; then + w3m -dump "${FPATH}" | eval "$PAGER" + exit 0 + elif type lynx >/dev/null 2>&1; then + lynx -dump -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type elinks >/dev/null 2>&1; then + elinks -dump "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + + ## JSON + json) + if type jq >/dev/null 2>&1; then + jq --color-output . "${FPATH}" | eval "$PAGER" + exit 0 + elif type python >/dev/null 2>&1; then + python -m json.tool -- "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + esac +} + +# sets the variable abs_target, this should be faster than calling printf +abspath() { + case "$1" in + /*) abs_target="$1";; + *) abs_target="$PWD/$1";; + esac +} + +# storing the result to a tmp file is faster than calling listimages twice +listimages() { + find -L "///${1%/*}" -maxdepth 1 -type f -print0 | + grep -izZE '\.(jpe?g|png|gif|webp|tiff|bmp|ico|svg)$' | + sort -z | tee "$tmp" +} + +load_dir() { + abspath "$2" + tmp="${TMPDIR:-/tmp}/nuke_$$" + trap 'rm -f $tmp' EXIT + count="$(listimages "$abs_target" | grep -a -m 1 -ZznF "$abs_target" | cut -d: -f1)" + + if [ -n "$count" ]; then + if [ "$GUI" -ne 0 ]; then + xargs -0 nohup "$1" -n "$count" -- < "$tmp" + else + xargs -0 "$1" -n "$count" -- < "$tmp" + fi + else + shift + "$1" -- "$@" # fallback + fi +} + +handle_multimedia() { + ## Size of the preview if there are multiple options or it has to be + ## rendered from vector graphics. If the conversion program allows + ## specifying only one dimension while keeping the aspect ratio, the width + ## will be used. + # local DEFAULT_SIZE="1920x1080" + + mimetype="${1}" + case "${mimetype}" in + ## SVG + # image/svg+xml|image/svg) + # convert -- "${FPATH}" "${IMAGE_CACHE_PATH}" && exit 6 + # exit 1;; + + ## DjVu + # image/vnd.djvu) + # ddjvu -format=tiff -quality=90 -page=1 -size="${DEFAULT_SIZE}" \ + # - "${IMAGE_CACHE_PATH}" < "${FPATH}" \ + # && exit 6 || exit 1;; + + ## Image + image/*) + if [ "$GUI" -ne 0 ]; then + if is_mac; then + nohup open "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type imv >/dev/null 2>&1; then + load_dir imv "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type imvr >/dev/null 2>&1; then + load_dir imvr "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type sxiv >/dev/null 2>&1; then + load_dir sxiv "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type nsxiv >/dev/null 2>&1; then + load_dir nsxiv "${FPATH}" >/dev/null 2>&1 & + exit 0 + fi + elif type viu >/dev/null 2>&1; then + viu -n "${FPATH}" | eval "$PAGER" + exit 0 + elif type img2txt >/dev/null 2>&1; then + img2txt --gamma=0.6 -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + exit 0 + fi + # local orientation + # orientation="$( identify -format '%[EXIF:Orientation]\n' -- "${FPATH}" )" + ## If orientation data is present and the image actually + ## needs rotating ("1" means no rotation)... + # if [[ -n "$orientation" && "$orientation" != 1 ]]; then + ## ...auto-rotate the image according to the EXIF data. + # convert -- "${FPATH}" -auto-orient "${IMAGE_CACHE_PATH}" && exit 6 + # fi + + ## `w3mimgdisplay` will be called for all images (unless overridden + ## as above), but might fail for unsupported types. + exit 7;; + + ## PDF + application/pdf) + handle_pdf + exit 1;; + + ## Audio + audio/*) + handle_audio + exit 1;; + + ## Video + video/*) + handle_video + exit 1;; + + # pdftoppm -f 1 -l 1 \ + # -scale-to-x "${DEFAULT_SIZE%x*}" \ + # -scale-to-y -1 \ + # -singlefile \ + # -jpeg -tiffcompression jpeg \ + # -- "${FPATH}" "${IMAGE_CACHE_PATH%.*}" \ + # && exit 6 || exit 1;; + + + ## ePub, MOBI, FB2 (using Calibre) + # application/epub+zip|application/x-mobipocket-ebook|\ + # application/x-fictionbook+xml) + # # ePub (using https://github.com/marianosimone/epub-thumbnailer) + # epub-thumbnailer "${FPATH}" "${IMAGE_CACHE_PATH}" \ + # "${DEFAULT_SIZE%x*}" && exit 6 + # ebook-meta --get-cover="${IMAGE_CACHE_PATH}" -- "${FPATH}" \ + # >/dev/null && exit 6 + # exit 1;; + + ## Font + # application/font*|application/*opentype) + # preview_png="/tmp/$(basename "${IMAGE_CACHE_PATH%.*}").png" + # if fontimage -o "${preview_png}" \ + # --pixelsize "120" \ + # --fontname \ + # --pixelsize "80" \ + # --text " ABCDEFGHIJKLMNOPQRSTUVWXYZ " \ + # --text " abcdefghijklmnopqrstuvwxyz " \ + # --text " 0123456789.:,;(*!?') ff fl fi ffi ffl " \ + # --text " The quick brown fox jumps over the lazy dog. " \ + # "${FPATH}"; + # then + # convert -- "${preview_png}" "${IMAGE_CACHE_PATH}" \ + # && rm "${preview_png}" \ + # && exit 6 + # else + # exit 1 + # fi + # ;; + + ## Preview archives using the first image inside. + ## (Very useful for comic book collections for example.) + # application/zip|application/x-rar|application/x-7z-compressed|\ + # application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar) + # local fn=""; local fe="" + # local zip=""; local rar=""; local tar=""; local bsd="" + # case "${mimetype}" in + # application/zip) zip=1 ;; + # application/x-rar) rar=1 ;; + # application/x-7z-compressed) ;; + # *) tar=1 ;; + # esac + # { [ "$tar" ] && fn=$(tar --list --file "${FPATH}"); } || \ + # { fn=$(bsdtar --list --file "${FPATH}") && bsd=1 && tar=""; } || \ + # { [ "$rar" ] && fn=$(unrar lb -p- -- "${FPATH}"); } || \ + # { [ "$zip" ] && fn=$(zipinfo -1 -- "${FPATH}"); } || return + # + # fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \ + # [ print(l, end='') for l in sys.stdin if \ + # (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\ + # sort -V | head -n 1) + # [ "$fn" = "" ] && return + # [ "$bsd" ] && fn=$(printf '%b' "$fn") + # + # [ "$tar" ] && tar --extract --to-stdout \ + # --file "${FPATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6 + # fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g') + # [ "$bsd" ] && bsdtar --extract --to-stdout \ + # --file "${FPATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}" + # [ "$rar" ] && unrar p -p- -inul -- "${FPATH}" "$fn" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$zip" ] && unzip -pP "" -- "${FPATH}" "$fe" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}" + # ;; + esac +} + +handle_mime() { + mimetype="${1}" + case "${mimetype}" in + ## Manpages + text/troff) + man -l "${FPATH}" + exit 0;; + + ## Text + text/* | */xml) + "$EDITOR" "${FPATH}" + exit 0;; + ## Syntax highlight + # if [[ "$( stat --printf='%s' -- "${FPATH}" )" -gt "${HIGHLIGHT_SIZE_MAX}" ]]; then + # exit 2 + # fi + # if [[ "$( tput colors )" -ge 256 ]]; then + # local pygmentize_format='terminal256' + # local highlight_format='xterm256' + # else + # local pygmentize_format='terminal' + # local highlight_format='ansi' + # fi + # env HIGHLIGHT_OPTIONS="${HIGHLIGHT_OPTIONS}" highlight \ + # --out-format="${highlight_format}" \ + # --force -- "${FPATH}" && exit 5 + # pygmentize -f "${pygmentize_format}" -O "style=${PYGMENTIZE_STYLE}"\ + # -- "${FPATH}" && exit 5 + # exit 2;; + + ## DjVu + image/vnd.djvu) + if type djvutxt >/dev/null 2>&1; then + ## Preview as text conversion (requires djvulibre) + djvutxt "${FPATH}" | eval "$PAGER" + exit 0 + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + esac +} + +handle_fallback() { + if [ "$GUI" -ne 0 ]; then + if type xdg-open >/dev/null 2>&1; then + nohup xdg-open "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type open >/dev/null 2>&1; then + nohup open "${FPATH}" >/dev/null 2>&1 & + exit 0 + fi + fi + + echo '----- File details -----' && file --dereference --brief -- "${FPATH}" + exit 1 +} + +handle_blocked() { + case "${MIMETYPE}" in + application/x-sharedlib) + exit 0;; + + application/x-shared-library-la) + exit 0;; + + application/x-executable) + exit 0;; + + application/x-shellscript) + exit 0;; + + application/octet-stream) + exit 0;; + esac +} + +handle_bin() { + case "${MIMETYPE}" in + application/x-executable|application/x-shellscript) + clear + echo '-------- Executable File --------' && file --dereference --brief -- "${FPATH}" + printf "Run executable (y/N/'a'rgs)? " + read -r answer + case "$answer" in + [Yy]* ) exec "${FPATH}";; + [Aa]* ) + printf "args: " + read -r args + exec "${FPATH}" "$args";; + [Nn]* ) exit;; + esac + esac +} + +MIMETYPE="$( file -bL --mime-type -- "${FPATH}" )" +handle_extension +handle_multimedia "${MIMETYPE}" +handle_mime "${MIMETYPE}" +[ "$BIN" -ne 0 ] && [ -x "${FPATH}" ] && handle_bin +handle_blocked "${MIMETYPE}" +handle_fallback + +exit 1 diff --git a/.config/nnn/plugins/oldbigfile b/.config/nnn/plugins/oldbigfile new file mode 100755 index 0000000..0a21527 --- /dev/null +++ b/.config/nnn/plugins/oldbigfile @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Description: List files bigger than input size by ascending access date. +# +# Dependencies: find sort +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +printf "Min file size (MB): " +read -r size + +find . -size +"$size"M -type f -printf '%A+ %s %p\n' | sort + +echo "Press any key to exit" +read -r _ diff --git a/.config/nnn/plugins/openall b/.config/nnn/plugins/openall new file mode 100755 index 0000000..5a7941f --- /dev/null +++ b/.config/nnn/plugins/openall @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Description: Open selected files in nuke one by one or in oneshot +# +# Notes: 1. Opens the hovered file if the selection is empty +# 2. nuke is the default, set OPENER below for custom +# 3. Opener is invoked once for each file in a loop +# 4. Keep pressing "Enter" to open files one by one +# +# Shell: bash +# Author: Arun Prakash Jana + +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +OPENER="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" + +if [ -s "$sel" ]; then + targets=() + while IFS= read -r -d '' entry || [ -n "$entry" ]; do + targets+=( "$entry" ) + done < "$sel" + + elements=${#targets[@]} + + if (( elements == 1 )); then + # If there's only one file selected, open without prompts + "$OPENER" "${targets[0]}" + else + printf "open [A]ll? " + read -r all + + for ((index=0; index <= ${#targets[@]}; index++)); do + "$OPENER" "${targets[index]}" + if [ "$all" != "A" ] && (( index+1 < elements )); then + printf "press Enter to open '%s'\n" "${targets[index+1]}" + read -r -s -n 1 key + if [[ $key != "" ]]; then + break + fi + fi + done + fi + + # Clear selection + if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +elif [ -n "$1" ]; then + "$OPENER" "$1" +fi diff --git a/.config/nnn/plugins/organize b/.config/nnn/plugins/organize new file mode 100755 index 0000000..fb70aaf --- /dev/null +++ b/.config/nnn/plugins/organize @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Organize files in directories by category +# +# Note: This plugin clears the selection as it changes the contents of the current dir +# +# Shell: POSIX compliant +# Author: th3lusive + +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +organize() { + case "$(file -biL "$1")" in + *video*) + [ ! -d "Videos" ] && mkdir "Videos" + mv "$1" "Videos/$1" + printf "Moved %s to Videos\n" "$1" ;; + + *audio*) [ ! -d "Audio" ] && mkdir "Audio" + mv "$1" "Audio/$1" + printf "Moved %s to Audio\n" "$1" ;; + + *image*) + [ ! -d "Images" ] && mkdir "Images" + mv "$1" "Images/$1" + printf "Moved %s to Images\n" "$1" ;; + + *pdf*|*document*|*epub*|*djvu*|*cb*) + [ ! -d "Documents" ] && mkdir "Documents" + mv "$1" "Documents/$1" + printf "Moved %s to Documents\n" "$1" ;; + + *text*) + [ ! -d "Plaintext" ] && mkdir "Plaintext" + mv "$1" "Plaintext/$1" + printf "Moved %s to Plaintext\n" "$1" ;; + + *tar*|*xz*|*compress*|*7z*|*rar*|*zip*) + [ ! -d "Archives" ] && mkdir "Archives" + mv "$1" "Archives/$1" + printf "Moved %s to Archives\n" "$1" ;; + + *binary*) + [ ! -d "Binaries" ] && mkdir "Binaries" + mv "$1" "Binaries/$1" + printf "Moved %s to Binaries\n" "$1" ;; + esac +} + +main() { + for file in * + do + [ -f "$file" ] && organize "$file" + done + + # Clear selection + if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +} + +main "$@" diff --git a/.config/nnn/plugins/pdfread b/.config/nnn/plugins/pdfread new file mode 100755 index 0000000..1e889be --- /dev/null +++ b/.config/nnn/plugins/pdfread @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# Description: Read a text or PDF file in British English +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ]; then + tmpf="$(basename "$1")" + tmpf="${TMPDIR:-/tmp}"/"${tmpf%.*}" + + if [ "$(head -c 4 "$1")" = "%PDF" ]; then + # Convert using pdftotext + pdftotext -nopgbrk -layout "$1" - | sed 's/\xe2\x80\x8b//g' > "$tmpf".txt + + pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$tmpf".txt)" + + rm "$tmpf".txt + else + pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$1")" + fi + + # to jump around and note the time + mpv "$tmpf".wav + + # flat read but better quality + # play -qV0 "$tmpf".wav treble 2 gain -l 2 + + rm "$tmpf".wav +fi diff --git a/.config/nnn/plugins/preview-tabbed b/.config/nnn/plugins/preview-tabbed new file mode 100755 index 0000000..49c0bc9 --- /dev/null +++ b/.config/nnn/plugins/preview-tabbed @@ -0,0 +1,211 @@ +#!/usr/bin/env bash + +# Description: tabbed/xembed based file previewer +# +# Dependencies: +# - tabbed (https://tools.suckless.org/tabbed): xembed host +# - xterm (or urxvt or st) : xembed client for text-based preview +# - mpv (https://mpv.io): xembed client for video/audio +# - sxiv (https://github.com/muennich/sxiv) or, +# - nsxiv (https://github.com/nsxiv/nsxiv) : xembed client for images +# - zathura (https://pwmt.org/projects/zathura): xembed client for PDF +# - nnn's nuke plugin for text preview and fallback +# nuke is a fallback for 'mpv', 'sxiv'/'nsxiv', and 'zathura', but has its +# own dependencies, see the script for more information +# - vim (or any editor/pager really) +# - file +# - mktemp +# - xdotool (optional, to keep main window focused) +# +# Usage: +# - Install the dependencies. Then set a NNN_FIFO +# and set a key for the plugin, then start `nnn`: +# $ NNN_FIFO=/tmp/nnn.fifo nnn +# - Launch the plugin with the designated key from nnn +# +# Notes: +# 1. This plugin needs a "NNN_FIFO" to work. See man. +# 2. If the same NNN_FIFO is used in multiple nnn instances, there will be one +# common preview window. With different FIFO paths, they will be independent. +# +# How it works: +# We use `tabbed` [1] as a xembed [2] host, to have a single window +# owning each previewer window. So each previewer must be a xembed client. +# For text previewers, this is not an issue, as there are a lot of +# xembed-able terminal emulator (we default to `xterm`, but examples are +# provided for `urxvt` and `st`). For graphic preview this can be trickier, +# but a few popular viewers are xembed-able, we use: +# - `mpv`: multimedia player, for video/audio preview +# - `sxiv`/`nsxiv`: image viewer +# - `zathura`: PDF viewer +# - but we always fallback to `nuke` plugin +# +# [1]: https://tools.suckless.org/tabbed/ +# [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html +# +# Shell: Bash (job control is weakly specified in POSIX) +# Author: Léo Villeveygoux + + +XDOTOOL_TIMEOUT=2 +PAGER=${PAGER:-"vim -R"} +NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" + + +if type xterm >/dev/null 2>&1 ; then + TERMINAL="xterm -into" +elif type urxvt >/dev/null 2>&1 ; then + TERMINAL="urxvt -embed" +elif type st >/dev/null 2>&1 ; then + TERMINAL="st -w" +else + echo "No xembed term found" >&2 +fi + + +term_nuke () { + # $1 -> $XID, $2 -> $FILE + $TERMINAL "$1" -e "$NUKE" "$2" & +} + +start_tabbed () { + FIFO="$(mktemp -u)" + mkfifo "$FIFO" + + tabbed > "$FIFO" & + + jobs # Get rid of the "Completed" entries + + TABBEDPID="$(jobs -p %%)" + + if [ -z "$TABBEDPID" ] ; then + echo "Can't start tabbed" + exit 1 + fi + + read -r XID < "$FIFO" + + rm "$FIFO" +} + +get_viewer_pid () { + VIEWERPID="$(jobs -p %%)" +} + +kill_viewer () { + if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then + kill "$VIEWERPID" + fi +} + +sigint_kill () { + kill_viewer + kill "$TABBEDPID" + exit 0 +} + +previewer_loop () { + unset -v NNN_FIFO + # mute from now + exec >/dev/null 2>&1 + + MAINWINDOW="$(xdotool getactivewindow)" + + start_tabbed + trap sigint_kill SIGINT + + xdotool windowactivate "$MAINWINDOW" + + # Bruteforce focus stealing prevention method, + # works well in floating window managers like XFCE + # but make interaction with the preview window harder + # (uncomment to use): + #xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & + + while read -r FILE ; do + + jobs # Get rid of the "Completed" entries + + if ! jobs | grep tabbed ; then + break + fi + + if [ ! -e "$FILE" ] ; then + continue + fi + + kill_viewer + + MIME="$(file -bL --mime-type "$FILE")" + + case "$MIME" in + video/*) + if type mpv >/dev/null 2>&1 ; then + mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + audio/*) + if type mpv >/dev/null 2>&1 ; then + mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + image/*) + if type sxiv >/dev/null 2>&1 ; then + sxiv -ae "$XID" "$FILE" & + elif type nsxiv >/dev/null 2>&1 ; then + nsxiv -ae "$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + application/pdf) + if type zathura >/dev/null 2>&1 ; then + zathura -e "$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + inode/directory) + $TERMINAL "$XID" -e nnn "$FILE" & + ;; + text/*) + if [ -x "$NUKE" ] ; then + term_nuke "$XID" "$FILE" + else + # shellcheck disable=SC2086 + $TERMINAL "$XID" -e $PAGER "$FILE" & + fi + ;; + *) + if [ -x "$NUKE" ] ; then + term_nuke "$XID" "$FILE" + else + $TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" & + fi + ;; + esac + get_viewer_pid + + # following lines are not needed with the bruteforce xdotool method + ACTIVE_XID="$(xdotool getactivewindow)" + if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then + xdotool windowactivate "$MAINWINDOW" + else + timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & + fi + done + kill "$TABBEDPID" + kill_viewer +} + +if [ ! -r "$NNN_FIFO" ] ; then + echo "Can't read \$NNN_FIFO ('$NNN_FIFO')" + exit 1 +fi + +previewer_loop < "$NNN_FIFO" & +disown diff --git a/.config/nnn/plugins/preview-tui b/.config/nnn/plugins/preview-tui new file mode 100755 index 0000000..18791fd --- /dev/null +++ b/.config/nnn/plugins/preview-tui @@ -0,0 +1,481 @@ +#!/usr/bin/env sh + +# Description: Terminal based file previewer +# +# Note: This plugin needs a "NNN_FIFO" to work. See man. +# +# Dependencies: +# - Supports 5 independent methods to preview with: +# - tmux (>=3.0), or +# - kitty with allow_remote_control and listen_on set in kitty.conf, or +# - QuickLook on WSL (https://github.com/QL-Win/QuickLook), or +# - Windows Terminal (https://github.com/Microsoft/Terminal | https://aka.ms/terminal) with WSL, or +# - $TERMINAL set to a terminal (it's xterm by default). +# - less or $PAGER +# - tree or exa or ls +# - mediainfo or file +# - mktemp +# - unzip +# - tar +# - man +# - optional: bsdtar or atool for additional archive preview +# - optional: bat for code syntax highlighting +# - optional: ueberzug, kitty terminal, viu or catimg for images +# - optional: convert(ImageMagick) for playing gif preview (required for kitty image previews) +# - optional: ffmpegthumbnailer for video thumbnails (https://github.com/dirkvdb/ffmpegthumbnailer) +# - optional: ffmpeg for audio thumbnails +# - optional: libreoffce for opendocument/officedocument preview +# - optional: pdftoppm(poppler) for pdf thumbnails +# - optional: gnome-epub-thumbnailer for epub thumbnails (https://gitlab.gnome.org/GNOME/gnome-epub-thumbnailer) +# - optional: fontpreview for font preview (https://github.com/sdushantha/fontpreview) +# - optional: djvulibre for djvu +# - optional: glow or lowdown for markdown +# - optional: w3m or lynx or elinks for html +# - optional: set/export ICONLOOKUP as 1 to enable file icons in front of directory previews with .iconlookup +# Icons and colors are configureable in .iconlookup +# - optional: scope.sh file viewer from ranger. +# 1. drop scope.sh executable in $PATH +# 2. set/export $USE_SCOPE as 1 +# - optional: pistol file viewer (https://github.com/doronbehar/pistol). +# 1. install pistol +# 2. set/export $USE_PISTOL as 1 +# +# Usage: +# You need to set a NNN_FIFO path and a key for the plugin with NNN_PLUG, +# then start `nnn`: +# +# $ nnn -a +# +# or +# +# $ NNN_FIFO=/tmp/nnn.fifo nnn +# +# Then launch the `preview-tui` plugin in `nnn`. +# +# If you provide the same NNN_FIFO to all nnn instances, there will be a +# single common preview window. If you provide different FIFO path (e.g. +# with -a), they will be independent. +# +# The previews will be shown in a tmux split. If that isn't possible, it +# will try to use a kitty terminal split. And as a final fallback, a +# different terminal window will be used ($TERMINAL). +# +# Tmux and kitty users can configure $SPLIT to either "h" or "v" to set a +# 'h'orizontal split or a 'v'ertical split (as in, the line that splits the +# windows will be horizontal or vertical). +# +# Kitty users need something similar to the following in their kitty.conf: +# - `allow_remote_control yes` +# - `listen_on unix:$TMPDIR/kitty` +# - `enabled_layouts splits` (optional) +# With ImageMagick installed, this terminal can use the icat kitten to display images. +# Refer to kitty documentation for further details. +# +# Iterm2 users are recommended to use viu to view images without getting pixelated. +# +# Windows Terminal users can set "Profile termination behavior" under "Profile > Advanced" settings +# to automaticaly close pane on quit when exit code is 0. +# +# Shell: POSIX compliant +# Authors: Todd Yamakawa, Léo Villeveygoux, @Recidiviste, Mario Ortiz Manero, Luuk van Baal, @WanderLanz + +#SPLIT="$SPLIT" # you can set a permanent split here +#TERMINAL="$TERMINAL" # same goes for the terminal +SPLIT_SIZE="${SPLIT_SIZE:-50}" # split size in percentage for supported previewers +USE_SCOPE="${USE_SCOPE:-0}" +USE_PISTOL="${USE_PISTOL:-0}" +ICONLOOKUP="${ICONLOOKUP:-0}" +PAGER="${PAGER:-less -P?n -R}" +TMPDIR="${TMPDIR:-/tmp}" +BAT_STYLE="${BAT_STYLE:-numbers}" +BAT_THEME="${BAT_THEME:-ansi}" +# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews if you want to keep previews on disk between reboots +NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-$TMPDIR/nnn/previews}" +NNN_PREVIEWWIDTH="${NNN_PREVIEWWIDTH:-1920}" +NNN_PREVIEWHEIGHT="${NNN_PREVIEWHEIGHT:-1080}" +NNN_PARENT="${NNN_FIFO#*.}" +[ "$NNN_PARENT" -eq "$NNN_PARENT" ] 2>/dev/null || NNN_PARENT="" +FIFOPID="$TMPDIR/nnn-preview-tui-fifopid.$NNN_PARENT" +PREVIEWPID="$TMPDIR/nnn-preview-tui-pagerpid.$NNN_PARENT" +CURSEL="$TMPDIR/nnn-preview-tui-selection.$NNN_PARENT" +FIFO_UEBERZUG="$TMPDIR/nnn-preview-tui-ueberzug-fifo.$NNN_PARENT" +POSOFFSET="$TMPDIR/nnn-preview-tui-posoffset" + +exists() { type "$1" >/dev/null 2>&1 ;} +pkill() { command pkill "$@" >/dev/null 2>&1 ;} +pidkill() { [ -f "$1" ] && kill "$(cat "$1")" >/dev/null 2>&1 ;} +prompt() { printf "%b" "$@"; cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" ;} + +start_preview() { + [ "$PAGER" = "most" ] && PAGER="less -R" + + if [ -e "${TMUX%%,*}" ] && tmux -V | grep -q '[ -][3456789]\.'; then + TERMINAL=tmux + elif [ -n "$KITTY_LISTEN_ON" ]; then + TERMINAL=kitty + elif [ -z "$TERMINAL" ] && [ "$TERM_PROGRAM" = "iTerm.app" ]; then + TERMINAL=iterm + elif [ -n "$WT_SESSION" ]; then + TERMINAL=winterm + else + TERMINAL="${TERMINAL:-xterm}" + fi + + if [ -z "$SPLIT" ] && [ $(($(tput lines) * 2)) -gt "$(tput cols)" ]; then + SPLIT='h' + elif [ "$SPLIT" != 'h' ]; then + SPLIT='v' + fi + + case "$TERMINAL" in + tmux) # tmux splits are inverted + if [ "$SPLIT" = "v" ]; then DSPLIT="h"; else DSPLIT="v"; fi + tmux split-window -e "NNN_FIFO=$NNN_FIFO" -e "PREVIEW_MODE=1" -e "CURSEL=$CURSEL" \ + -e "TMPDIR=$TMPDIR" -e "FIFOPID=$FIFOPID" -e "POSOFFSET=$POSOFFSET" \ + -e "BAT_STYLE=$BAT_STYLE" -e "BAT_THEME=$BAT_THEME" -e "PREVIEWPID=$PREVIEWPID" \ + -e "PAGER=$PAGER" -e "ICONLOOKUP=$ICONLOOKUP" -e "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" \ + -e "USE_SCOPE=$USE_SCOPE" -e "SPLIT=$SPLIT" -e "USE_PISTOL=$USE_PISTOL" \ + -e "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" -e "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \ + -e "FIFO_UEBERZUG=$FIFO_UEBERZUG" -e "QLPATH=$2" -d"$DSPLIT" -p"$SPLIT_SIZE" "$0" "$1" ;; + kitty) # Setting the layout for the new window. It will be restored after the script ends. + kitty @ goto-layout splits + # Trying to use kitty's integrated window management as the split window. All + # environmental variables that will be used in the new window must be explicitly passed. + kitty @ launch --no-response --title "nnn preview" --keep-focus \ + --cwd "$PWD" --env "PATH=$PATH" --env "NNN_FIFO=$NNN_FIFO" \ + --env "PREVIEW_MODE=1" --env "PAGER=$PAGER" --env "TMPDIR=$TMPDIR" \ + --env "USE_SCOPE=$USE_SCOPE" --env "SPLIT=$SPLIT" --env "TERMINAL=$TERMINAL"\ + --env "PREVIEWPID=$PREVIEWPID" --env "FIFO_UEBERZUG=$FIFO_UEBERZUG" \ + --env "ICONLOOKUP=$ICONLOOKUP" --env "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \ + --env "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" --env "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" \ + --env "USE_PISTOL=$USE_PISTOL" --env "BAT_STYLE=$BAT_STYLE" \ + --env "BAT_THEME=$BAT_THEME" --env "FIFOPID=$FIFOPID" \ + --env "CURSEL=$CURSEL" --location "${SPLIT}split" "$0" "$1" ;; + iterm) + command="$SHELL -c 'cd $PWD; \ + PATH=\\\"$PATH\\\" NNN_FIFO=\\\"$NNN_FIFO\\\" PREVIEW_MODE=1 PAGER=\\\"$PAGER\\\" \ + USE_SCOPE=\\\"$USE_SCOPE\\\" SPLIT=\\\"$SPLIT\\\" TERMINAL=\\\"$TERMINAL\\\" \ + PREVIEWPID=\\\"$PREVIEWPID\\\" CURSEL=\\\"$CURSEL\\\" TMPDIR=\\\"$TMPDIR\\\" \ + ICONLOOKUP=\\\"$ICONLOOKUP\\\" NNN_PREVIEWHEIGHT=\\\"$NNN_PREVIEWHEIGHT\\\" \ + NNN_PREVIEWWIDTH=\\\"$NNN_PREVIEWWIDTH\\\" NNN_PREVIEWDIR=\\\"$NNN_PREVIEWDIR\\\" \ + USE_PISTOL=\\\"$USE_PISTOL\\\" BAT_STYLE=\\\"$BAT_STYLE\\\" \ + BAT_THEME=\\\"$BAT_THEME\\\" FIFOPID=\\\"$FIFOPID\\\" \\\"$0\\\" \\\"$1\\\"'" + if [ "$SPLIT" = "h" ]; then split="horizontally"; else split="vertically"; fi + osascript <<-EOF + tell application "iTerm" + tell current session of current window + split $split with default profile command "$command" + end tell + end tell +EOF + ;; + winterm) + if [ "$SPLIT" = "h" ]; then split="H"; else split="V"; fi + cmd.exe /c wt -w 0 sp -$split -s$((SPLIT_SIZE / 100)) bash -c "cd $PWD \; \ + PATH='$PATH' NNN_FIFO=$NNN_FIFO PREVIEW_MODE=1 CURSEL=$CURSEL TMPDIR=$TMPDIR \ + FIFOPID=$FIFOPID BAT_STYLE=$BAT_STYLE BAT_THEME=$BAT_THEME PREVIEWPID=$PREVIEWPID \ + PAGER='$PAGER' ICONLOOKUP=$ICONLOOKUP NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH \ + USE_SCOPE=$USE_SCOPE SPLIT=$SPLIT USE_PISTOL=$USE_PISTOL \ + NNN_PREVIEWDIR=$NNN_PREVIEWDIR NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT \ + FIFO_UEBERZUG=$FIFO_UEBERZUG QLPATH=$2 $0 $1" \; -w 0 mf previous + ;; + *) if [ -n "$2" ]; then + QUICKLOOK=1 QLPATH="$2" PREVIEW_MODE=1 "$0" "$1" & + else + PREVIEWPID="$PREVIEWPID" CURSEL="$CURSEL" PREVIEW_MODE=1 \ + FIFOPID="$FIFOPID" FIFO_UEBERZUG="$FIFO_UEBERZUG" $TERMINAL -e "$0" "$1" & + fi ;; + esac +} + +toggle_preview() { + if exists QuickLook.exe; then + QLPATH="QuickLook.exe" + elif exists Bridge.exe; then + QLPATH="Bridge.exe" + fi + if pidkill "$FIFOPID"; then + [ -p "$NNN_PPIPE" ] && printf "0" > "$NNN_PPIPE" + pidkill "$PREVIEWPID" + pkill -f "tail --follow $FIFO_UEBERZUG" + if [ -n "$QLPATH" ] && stat "$1"; then + f="$(wslpath -w "$1")" && "$QLPATH" "$f" & + fi + else + [ -p "$NNN_PPIPE" ] && printf "1" > "$NNN_PPIPE" + start_preview "$1" "$QLPATH" + fi +} + +fifo_pager() { + cmd="$1" + shift + + # We use a FIFO to access $PAGER PID in jobs control + tmpfifopath="$TMPDIR/nnn-preview-tui-fifo.$$" + mkfifo "$tmpfifopath" || return + + $PAGER < "$tmpfifopath" & + printf "%s" "$!" > "$PREVIEWPID" + + ( + exec > "$tmpfifopath" + if [ "$cmd" = "pager" ]; then + if exists bat; then + bat --terminal-width="$cols" --decorations=always --color=always \ + --paging=never --style="$BAT_STYLE" --theme="$BAT_THEME" "$@" & + else + $PAGER "$@" & + fi + else + "$cmd" "$@" & + fi + ) + + rm "$tmpfifopath" +} + +# Binary file: show file info inside the pager +print_bin_info() { + printf -- "-------- \033[1;31mBinary file\033[0m --------\n" + if exists mediainfo; then + mediainfo "$1" + else + file -b "$1" + fi +} + +handle_mime() { + case "$2" in + image/jpeg) image_preview "$cols" "$lines" "$1" ;; + image/gif) generate_preview "$cols" "$lines" "$1" "gif" ;; + image/vnd.djvu) generate_preview "$cols" "$lines" "$1" "djvu" ;; + image/*) generate_preview "$cols" "$lines" "$1" "image" ;; + video/*) generate_preview "$cols" "$lines" "$1" "video" ;; + audio/*) generate_preview "$cols" "$lines" "$1" "audio" ;; + application/font*|application/*opentype|font/*) generate_preview "$cols" "$lines" "$1" "font" ;; + */*office*|*/*document*) generate_preview "$cols" "$lines" "$1" "office" ;; + application/zip) fifo_pager unzip -l "$1" ;; + text/troff) + if exists man; then + fifo_pager man -Pcat -l "$1" + else + fifo_pager pager "$1" + fi ;; + *) handle_ext "$1" "$3" "$4" ;; + esac +} + +handle_ext() { + case "$2" in + epub) generate_preview "$cols" "$lines" "$1" "epub" ;; + pdf) generate_preview "$cols" "$lines" "$1" "pdf" ;; + gz|bz2) fifo_pager tar -tvf "$1" ;; + md) if exists glow; then + fifo_pager glow -s dark "$1" + elif exists lowdown; then + fifo_pager lowdown -Tterm "$1" + else + fifo_pager pager "$1" + fi ;; + htm|html|xhtml) + if exists w3m; then + fifo_pager w3m "$1" + elif exists lynx; then + fifo_pager lynx "$1" + elif exists elinks; then + fifo_pager elinks "$1" + else + fifo_pager pager "$1" + fi ;; + 7z|a|ace|alz|arc|arj|bz|cab|cpio|deb|jar|lha|lz|lzh|lzma|lzo\ + |rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z) + if exists atool; then + fifo_pager atool -l "$1" + elif exists bsdtar; then + fifo_pager bsdtar -tvf "$1" + fi ;; + *) if [ "$3" = "bin" ]; then + fifo_pager print_bin_info "$1" + else + fifo_pager pager "$1" + fi ;; + esac +} + +preview_file() { + clear + # Trying to use pistol if it's available. + if [ "$USE_PISTOL" -ne 0 ] && exists pistol; then + fifo_pager pistol "$1" + return + fi + + # Trying to use scope.sh if it's available. + if [ "$USE_SCOPE" -ne 0 ] && exists scope.sh; then + fifo_pager scope.sh "$1" "$cols" "$lines" "$(mktemp -d)" "True" + return + fi + + # Use QuickLook if it's available. + if [ -n "$QUICKLOOK" ]; then + stat "$1" && f="$(wslpath -w "$1")" && "$QLPATH" "$f" & + return + fi + + # Detecting the exact type of the file: the encoding, mime type, and extension in lowercase. + encoding="$(file -bL --mime-encoding -- "$1")" + mimetype="$(file -bL --mime-type -- "$1")" + ext="${1##*.}" + [ -n "$ext" ] && ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" + lines=$(tput lines) + cols=$(tput cols) + + # Otherwise, falling back to the defaults. + if [ -d "$1" ]; then + cd "$1" || return + if [ "$ICONLOOKUP" -ne 0 ] && [ -f "$(dirname "$0")"/.iconlookup ]; then + [ "$SPLIT" = v ] && BSTR="\n" + # shellcheck disable=SC2012 + ls -F --group-directories-first | head -n "$((lines - 3))" | "$(dirname "$0")"/.iconlookup -l "$cols" -B "$BSTR" -b " " + elif exists tree; then + fifo_pager tree --filelimit "$(find . -maxdepth 1 | wc -l)" -L 3 -C -F --dirsfirst --noreport + elif exists exa; then + exa -G --group-directories-first --colour=always + else + fifo_pager ls -F --group-directories-first --color=always + fi + elif [ "${encoding#*)}" = "binary" ]; then + handle_mime "$1" "$mimetype" "$ext" "bin" + else + handle_mime "$1" "$mimetype" "$ext" + fi +} + +generate_preview() { + if [ -n "$QLPATH" ] && stat "$3"; then + f="$(wslpath -w "$3")" && "$QLPATH" "$f" & + elif [ ! -f "$NNN_PREVIEWDIR/$3.jpg" ] || [ -n "$(find -L "$3" -newer "$NNN_PREVIEWDIR/$3.jpg")" ]; then + mkdir -p "$NNN_PREVIEWDIR/${3%/*}" + case $4 in + audio) ffmpeg -i "$3" -filter_complex "scale=iw*min(1\,min($NNN_PREVIEWWIDTH/iw\,ih)):-1" "$NNN_PREVIEWDIR/$3.jpg" -y ;; + epub) gnome-epub-thumbnailer "$3" "$NNN_PREVIEWDIR/$3.jpg" ;; + font) fontpreview -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" ;; + gif) if [ -p "$FIFO_UEBERZUG" ] && exists convert; then + frameprefix="$NNN_PREVIEWDIR/$3/${3##*/}" + if [ ! -d "$NNN_PREVIEWDIR/$3" ]; then + mkdir -p "$NNN_PREVIEWDIR/$3" + convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg" || + MAGICK_TMPDIR="/tmp" convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg" + fi + frames=$(($(find "$NNN_PREVIEWDIR/$3" | wc -l) - 2)) + [ $frames -lt 0 ] && return + while true; do + for i in $(seq 0 $frames); do + image_preview "$1" "$2" "$frameprefix-$i.jpg" + sleep 0.1 + done + done & + printf "%s" "$!" > "$PREVIEWPID" + return + else + exec >/dev/tty + image_preview "$1" "$2" "$3" + return + fi ;; + image) if exists convert; then + convert "$3" -flatten -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$NNN_PREVIEWDIR/$3.jpg" + else + image_preview "$1" "$2" "$3" && return + fi ;; + office) libreoffice --convert-to jpg "$3" --outdir "$NNN_PREVIEWDIR/${3%/*}" + filename="$(printf "%s" "${3##*/}" | cut -d. -f1)" + mv "$NNN_PREVIEWDIR/${3%/*}/$filename.jpg" "$NNN_PREVIEWDIR/$3.jpg" ;; + pdf) pdftoppm -jpeg -f 1 -singlefile "$3" "$NNN_PREVIEWDIR/$3" ;; + djvu) ddjvu -format=ppm -page=1 "$3" "$NNN_PREVIEWDIR/$3.jpg" ;; + video) ffmpegthumbnailer -s0 -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" || rm "$NNN_PREVIEWDIR/$3.jpg" ;; + esac + fi + if [ -f "$NNN_PREVIEWDIR/$3.jpg" ]; then + image_preview "$1" "$2" "$NNN_PREVIEWDIR/$3.jpg" + else + fifo_pager print_bin_info "$3" + fi +} >/dev/null 2>&1 + +image_preview() { + clear + if [ "$TERMINAL" = "kitty" ]; then + # Kitty terminal users can use the native image preview method + kitty +kitten icat --silent --scale-up --place "$1"x"$2"@0x0 --transfer-mode=stream --stdin=no "$3" & + elif exists ueberzug; then + ueberzug_layer "$1" "$2" "$3" && return + elif exists catimg; then + catimg "$3" & + elif exists viu; then + viu -t "$3" & + else + fifo_pager print_bin_info "$3" && return + fi + printf "%s" "$!" > "$PREVIEWPID" +} + +ueberzug_layer() { + [ -f "$POSOFFSET" ] && read -r x y < "$POSOFFSET" + printf '{"action": "add", "identifier": "nnn_ueberzug", "x": %d, "y": %d, "width": "%d", "height": "%d", "scaler": "fit_contain", "path": "%s"}\n'\ + "${x:-0}" "${y:-0}" "$1" "$2" "$3" > "$FIFO_UEBERZUG" +} + +ueberzug_remove() { + printf '{"action": "remove", "identifier": "nnn_ueberzug"}\n' > "$FIFO_UEBERZUG" +} + +winch_handler() { + clear + pidkill "$PREVIEWPID" + if [ -p "$FIFO_UEBERZUG" ]; then + pkill -f "tail --follow $FIFO_UEBERZUG" + tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json & + fi + preview_file "$(cat "$CURSEL")" +} + +preview_fifo() { + while read -r selection; do + if [ -n "$selection" ]; then + pidkill "$PREVIEWPID" + [ -p "$FIFO_UEBERZUG" ] && ueberzug_remove + [ "$selection" = "close" ] && break + preview_file "$selection" + printf "%s" "$selection" > "$CURSEL" + fi + done < "$NNN_FIFO" + sleep 0.1 # make sure potential preview by winch_handler is killed + pkill -P "$$" +} + +if [ "$PREVIEW_MODE" ]; then + if [ "$TERMINAL" != "kitty" ] && exists ueberzug; then + mkfifo "$FIFO_UEBERZUG" + tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json & + fi + + preview_file "$PWD/$1" + preview_fifo & + printf "%s" "$!" > "$FIFOPID" + printf "%s" "$PWD/$1" > "$CURSEL" + trap 'winch_handler; wait' WINCH + trap 'rm "$PREVIEWPID" "$CURSEL" "$FIFO_UEBERZUG" "$FIFOPID" "$POSOFFSET" 2>/dev/null' INT HUP EXIT + wait "$!" 2>/dev/null + exit 0 +else + if [ ! -r "$NNN_FIFO" ]; then + clear + prompt "No FIFO available! (\$NNN_FIFO='$NNN_FIFO')\nPlease read Usage in preview-tui." + elif [ "$KITTY_WINDOW_ID" ] && [ -z "$TMUX" ] && [ -z "$KITTY_LISTEN_ON" ]; then + clear + prompt "\$KITTY_LISTEN_ON not set!\nPlease read Usage in preview-tui." + else + toggle_preview "$1" & + fi +fi diff --git a/.config/nnn/plugins/pskill b/.config/nnn/plugins/pskill new file mode 100755 index 0000000..e01c739 --- /dev/null +++ b/.config/nnn/plugins/pskill @@ -0,0 +1,35 @@ +#!/usr/bin/env sh + +# Description: Fuzzy list and kill a (zombie) process by name +# +# Dependencies: fzf, ps +# +# Note: To kill a zombie process enter "zombie" +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +printf "Enter process name ['defunct' for zombies]: " +read -r psname + +# shellcheck disable=SC2009 +if [ -n "$psname" ]; then + if type sudo >/dev/null 2>&1; then + sucmd=sudo + elif type doas >/dev/null 2>&1; then + sucmd=doas + else + sucmd=: # noop + fi + + if type fzf >/dev/null 2>&1; then + fuzzy=fzf + else + exit 1 + fi + + cmd="$(ps -ax | grep -iw "$psname" | "$fuzzy" | sed -e 's/^[ \t]*//' | cut -d' ' -f1)" + if [ -n "$cmd" ]; then + $sucmd kill -9 "$cmd" + fi +fi diff --git a/.config/nnn/plugins/renamer b/.config/nnn/plugins/renamer new file mode 100755 index 0000000..51c586e --- /dev/null +++ b/.config/nnn/plugins/renamer @@ -0,0 +1,45 @@ +#!/usr/bin/env sh + +# Description: Batch rename selection or current directory with qmv or vidir +# +# Notes: +# - Try to mimic current batch rename functionality but with correct +# handling of edge cases by qmv or vidir. +# - Qmv opens with hidden files if no selection is used. Selected +# directories are shown. +# - Vidir don't show directories nor hidden files. +# +# Shell: POSIX compliant +# Author: José Neder + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if type qmv >/dev/null 2>&1; then + batchrenamesel="qmv -fdo -da" + batchrename="qmv -fdo -a" +elif type vidir >/dev/null 2>&1; then + batchrenamesel="vidir" + batchrename="vidir" +else + printf "there is not batchrename program installed." + exit +fi + +if [ -s "$selection" ]; then + printf "rename selection? " + read -r resp +fi + +if [ "$resp" = "y" ]; then + # -o flag is necessary for interactive editors + xargs -o -0 $batchrenamesel < "$selection" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +elif [ ! "$(LC_ALL=C ls -a)" = ". +.." ]; then + # On older systems that don't have ls -A + $batchrename +fi diff --git a/.config/nnn/plugins/ringtone b/.config/nnn/plugins/ringtone new file mode 100755 index 0000000..c18f255 --- /dev/null +++ b/.config/nnn/plugins/ringtone @@ -0,0 +1,36 @@ +#!/usr/bin/env sh + +# Description: Create an mp3 ringtone out of an audio file in any format +# Needs user to provide start and end where to cut the file +# Input file audio.ext results in audio_ringtone.mp3 +# +# Tip: To convert a complete media file, set start as 0 and +# the runtime of the file as end. +# +# Dependencies: date, ffmpeg +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ]; then + printf "start (hh:mm:ss): " + read -r start + st=$(date -d "$start" +%s) || exit 1 + + printf "end (hh:mm:ss): " + read -r end + et=$(date -d "$end" +%s) || exit 1 + + if [ "$st" -ge "$et" ]; then + printf "error: start >= end " + read -r _ + exit 1 + fi + + interval=$(( et - st )) + + outfile=$(basename "$1") + outfile="${outfile%.*}"_ringtone.mp3 + + ffmpeg -i "$1" -ss "$start" -t "$interval" -vn -sn -acodec libmp3lame -q:a 2 "$outfile" +fi diff --git a/.config/nnn/plugins/rsynccp b/.config/nnn/plugins/rsynccp new file mode 100755 index 0000000..902f9e7 --- /dev/null +++ b/.config/nnn/plugins/rsynccp @@ -0,0 +1,26 @@ +#!/usr/bin/env sh + +# Description: Simple script to give copy-paste a progress percentage +# by utilizing rsync. +# +# LIMITATION: this won't work when pasting to MTP device. +# +# Dependencies: rsync +# +# Shell: POSIX compliant +# Author: Benawi Adha + +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +# Choose one of these two schemes by commenting + +# more verbose +xargs -0 -I % rsync -ah --progress % "$PWD" < "$sel" + +# less verbose +# xargs -0 -I % rsync -ah --info=progress2 % "$PWD" < "$sel" + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/.config/nnn/plugins/splitjoin b/.config/nnn/plugins/splitjoin new file mode 100755 index 0000000..5ba081b --- /dev/null +++ b/.config/nnn/plugins/splitjoin @@ -0,0 +1,52 @@ +#!/usr/bin/env sh + +# Description: Splits the file passed as argument or joins selection +# +# Note: Adds numeric suffix to split files +# Adds '.out suffix to the first file to be joined and saves as output file for join +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=s + +if [ -s "$selection" ]; then + printf "press 's' (split current file) or 'j' (join selection): " + read -r resp +fi + +if [ "$resp" = "j" ]; then + if [ -s "$selection" ]; then + arr=$(tr '\0' '\n' < "$selection") + if [ "$(echo "$arr" | wc -l)" -lt 2 ]; then + echo "joining needs at least 2 files" + exit + fi + for entry in $arr + do + if [ -d "$entry" ]; then + echo "cant join directories" + exit + fi + done + + file="$(basename "$(echo "$arr" | sed -n '1p' | sed -e 's/[0-9][0-9]$//')")" + sort -z < "$selection" | xargs -0 -I{} cat {} > "${file}.out" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi + fi +elif [ "$resp" = "s" ]; then + if [ -n "$1" ] && [ -f "$1" ]; then + # a single file is passed + printf "split size in MB: " + read -r size + + if [ -n "$size" ]; then + split -d -b "$size"M "$1" "$1" + fi + fi +fi diff --git a/.config/nnn/plugins/suedit b/.config/nnn/plugins/suedit new file mode 100755 index 0000000..21dddf4 --- /dev/null +++ b/.config/nnn/plugins/suedit @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Description: Edit file as superuser +# +# Shell: POSIX compliant +# Author: Anna Arad + +EDITOR="${EDITOR:-vim}" + +if type sudo >/dev/null 2>&1; then + sudo -E "$EDITOR" "$1" +elif type sudoedit >/dev/null 2>&1; then + sudoedit -E "$1" +elif type doas >/dev/null 2>&1; then + doas "$EDITOR" "$1" +fi diff --git a/.config/nnn/plugins/togglex b/.config/nnn/plugins/togglex new file mode 100755 index 0000000..e42d65a --- /dev/null +++ b/.config/nnn/plugins/togglex @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# Description: Toggles executable mode for selection +# +# Dependencies: chmod +# +# Note: Works _only_ with selection (nnn can toggle the mode for the hovered file) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if [ -s "$selection" ]; then + xargs -0 -I {} sh -c 'if [ -x "{}" ] ; then chmod -x "{}" ; else chmod +x "{}" ; fi' < "$selection" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/.config/nnn/plugins/umounttree b/.config/nnn/plugins/umounttree new file mode 100755 index 0000000..011d74d --- /dev/null +++ b/.config/nnn/plugins/umounttree @@ -0,0 +1,52 @@ +#!/usr/bin/env sh + +# Description: Autodetects a nnn remote mountpoint (mounted with `c`) +# from any of its subfolders and allows unmounting it +# from the subdir without navigating to the mountppoint +# or entering the remote name. Also works when hovering +# the mountpoint directly like vanilla `u`. +# +# Dependencies: fusermount +# +# Shell: POSIX compliant +# Authors: Kabouik & 0xACE +# +# TODO: +# - Avoid lazy unmount by forcing nnn context to leave the subfolder before fusermount. +# Tried `printf "%s" "0c$m" > "$NNN_PIPE"` but it breaks the nnn interface, see #854. + +err=0 +m=$HOME/.config/nnn/mounts +if [ "$PWD" = "$m" ]; then + # Allow running the script on hovered directory if user is in ~/.config/nnn/mounts + d="$1" +else + d=$(dirname "$(readlink -f "$1")" | grep -oP "^$m\K.*" | cut -d"/" -f2) +fi + +# Test if user is within $m or a subdir, abort if not +if [ "$d" = "" ]; then + clear && printf "You are not in a remote folder mounted with nnn. Press return to continue. " && read -r _ +else + # Test if $m/$d is a mountpoint and try unmounting if it is + mountpoint -q -- "$m/$d" + if [ "$?" -eq "1" ]; then + clear && printf "Parent '%s' is not a mountpoint. Press return to continue. " "$d" && read -r _ + else + cd "$m" && fusermount -uq "$m/$d" || err=1 + if [ "$err" -eq "0" ]; then + rmdir "$m/$d" && clear && printf "Parent '%s' unmounted." "$d" + else + clear && printf "Failed to unmount. Try lazy unmount? [Yy/Nn] " && read -r + fi + fi +fi + +# If unmount fails, offer lazy unmount +if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then + err=0 + cd "$m" && fusermount -uqz "$m/$d" || err=1 + if [ "$err" -eq "0" ]; then + rmdir "$m/$d" && clear && printf "Parent '%s' unmounted with lazy unmount. " "$d" + fi +fi diff --git a/.config/nnn/plugins/upload b/.config/nnn/plugins/upload new file mode 100755 index 0000000..733cf87 --- /dev/null +++ b/.config/nnn/plugins/upload @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# Description: Upload to Firefox Send if ffsend is found, else +# Paste contents of a text a file http://ix.io +# Upload a binary file to file.io +# +# Dependencies: ffsend (https://github.com/timvisee/ffsend), curl, jq, tr +# +# Note: Binary file set to expire after a week +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ] && [ -s "$1" ]; then + if type ffsend >/dev/null 2>&1; then + ffsend -fiq u "$1" + elif [ "$(mimetype --output-format %m "$1" | awk -F '/' '{print $1}')" = "text" ]; then + curl -F "f:1=@$1" ix.io + else + # Upload the file, show the download link and wait till user presses any key + curl -s -F "file=@$1" https://file.io/?expires=1w | jq '.link' | tr -d '"' + + # To write download link to "$1".loc and exit + # curl -s -F "file=@$1" https://file.io/?expires=1w -o `basename "$1"`.loc + fi +else + printf "empty file!" +fi + +read -r _ diff --git a/.config/nnn/plugins/wallpaper b/.config/nnn/plugins/wallpaper new file mode 100755 index 0000000..5940df1 --- /dev/null +++ b/.config/nnn/plugins/wallpaper @@ -0,0 +1,26 @@ +#!/usr/bin/env sh + +# Description: Set the selected image as wallpaper using nitrogen or pywal. +# +# Usage: Hover on an image and run the script to set it as wallpaper. +# +# Shell: POSIX compliant +# Author: juacq97 + +if [ -n "$1" ]; then + if [ "$(file --mime-type "$1" | awk '{print $NF}' | awk -F '/' '{print $1}')" = "image" ]; then + if type nitrogen >/dev/null 2>&1; then + nitrogen --set-zoom-fill --save "$1" + elif type wal >/dev/null 2>&1; then + wal -i "$1" + else + printf "nitrogen or pywal missing" + read -r _ + fi + + # If you want a system notification, uncomment the next 3 lines. + # notify-send -a "nnn" "Wallpaper changed!" + # else + # notify-send -a "nnn" "No image selected" + fi +fi diff --git a/.config/nnn/plugins/x2sel b/.config/nnn/plugins/x2sel new file mode 100755 index 0000000..70104d7 --- /dev/null +++ b/.config/nnn/plugins/x2sel @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Copy system clipboard newline-separated file list to selection +# +# Dependencies: +# - tr +# - xclip/xsel (Linux) +# - pbpaste (macOS) +# - termux-clipboard-get (Termux) +# - powershell (WSL) +# - cygwim's /dev/clipboard (Cygwin) +# - wl-paste (Wayland) +# - clipboard (Haiku) +# +# Note: +# - Limitation: breaks if a filename has newline in it +# +# Shell: POSIX compliant +# Author: Léo Villeveygoux, after Arun Prakash Jana's .cbcp + +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +getclip () { + if type xsel >/dev/null 2>&1; then + # Linux + xsel -bo + elif type xclip >/dev/null 2>&1; then + # Linux + xclip -sel clip -o + elif type pbpaste >/dev/null 2>&1; then + # macOS + pbpaste + elif type termux-clipboard-get >/dev/null 2>&1; then + # Termux + termux-clipboard-get + elif type powershell.exe >/dev/null 2>&1; then + # WSL + powershell.exe Get-Clipboard + elif [ -r /dev/clipboard ] ; then + # Cygwin + cat /dev/clipboard + elif type wl-paste >/dev/null 2>&1; then + # Wayland + wl-paste + elif type clipboard >/dev/null 2>&1; then + # Haiku + clipboard --print + fi +} + +CLIPBOARD=$(getclip) + +# Check if clipboard actually contains a file list +for file in $CLIPBOARD ; do + if [ ! -e "$file" ] ; then + exit 1; + fi +done + +printf "%s" "$CLIPBOARD" | tr '\n' '\0' > "$selection" diff --git a/.config/nnn/plugins/xdgdefault b/.config/nnn/plugins/xdgdefault new file mode 100755 index 0000000..cf64a2f --- /dev/null +++ b/.config/nnn/plugins/xdgdefault @@ -0,0 +1,53 @@ +#!/usr/bin/env sh + +# Description: Sets the xdg-open's default application for the current entry's file +# type. ${XDG_DATA_DIRS} and ${XDG_DATA_HOME} are set to the recommended +# defaults if unset, as specified in XDG Base Directory Specification +# - http://specifications.freedesktop.org/basedir-spec/. +# +# Dependencies: xdg-utils, fzf or dmenu (GUI) +# +# Shell: POSIX compliant +# Author: lwnctd + +# set to 1 to enable GUI apps +GUI="${GUI:-0}" + +if [ "$GUI" -ne 0 ] && command -v dmenu > /dev/null 2>& 1; then + menu="dmenu -i -l 7" +elif command -v fzf > /dev/null 2>& 1; then + menu="fzf -e --tiebreak=begin" +fi + +if [ -z "$1" ] || [ -z "$menu" ] > /dev/null 2>& 1; then + exit 1 +fi + +ftype=$(xdg-mime query filetype "$2/$1") + +if [ -z "$ftype" ]; then + exit 1 +fi + +dirs=${XDG_DATA_DIRS:-/usr/local/share:/usr/share} +dirs=${dirs}:${XDG_DATA_HOME:-$HOME/.local/share}: + +while [ -n "$dirs" ]; do + d=${dirs%%:*} + if [ -n "$d" ] && [ -d "$d"/applications ]; then + set -- "$@" "$d"/applications + fi + dirs=${dirs#*:} +done + +app=$(find "$@" -iname '*.desktop' -exec grep '^Name=' {} + \ + | sort -u -t ':' -k 1,1 \ + | sed -e 's;..*/\(..*desktop\):Name=\(..*\);\2:\1;' \ + | sort -t ':' -k 1,1 \ + | column -t -s ':' -o "$(printf '\t')" \ + | $menu \ + | cut -f 2) + +if [ -n "$app" ]; then + xdg-mime default "${app%%[[:blank:]]*}" "$ftype" +fi diff --git a/.config/qutebrowser/autoconfig.yml b/.config/qutebrowser/autoconfig.yml new file mode 100644 index 0000000..31e3641 --- /dev/null +++ b/.config/qutebrowser/autoconfig.yml @@ -0,0 +1,10 @@ +# If a config.py file exists, this file is ignored unless it's explicitly loaded +# via config.load_autoconfig(). For more information, see: +# https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#loading-autoconfigyml +# DO NOT edit this file by hand, qutebrowser will overwrite it. +# Instead, create a config.py - see :help for details. + +config_version: 2 +settings: + content.media.video_capture: + https://meet.google.com: true diff --git a/.config/qutebrowser/bookmarks/urls b/.config/qutebrowser/bookmarks/urls new file mode 100644 index 0000000..e69de29 diff --git a/.config/qutebrowser/config.py b/.config/qutebrowser/config.py new file mode 100644 index 0000000..6eb4de7 --- /dev/null +++ b/.config/qutebrowser/config.py @@ -0,0 +1,527 @@ +# Autogenerated config.py +# +# NOTE: config.py is intended for advanced users who are comfortable +# with manually migrating the config file on qutebrowser upgrades. If +# you prefer, you can also configure qutebrowser using the +# :set/:bind/:config-* commands without having to write a config.py +# file. +# +# Documentation: +# qute://help/configuring.html +# qute://help/settings.html + +# Change the argument to True to still load settings configured via autoconfig.yml +config.load_autoconfig(False) + +# Which cookies to accept. With QtWebEngine, this setting also controls +# other features with tracking capabilities similar to those of cookies; +# including IndexedDB, DOM storage, filesystem API, service workers, and +# AppCache. Note that with QtWebKit, only `all` and `never` are +# supported as per-domain values. Setting `no-3rdparty` or `no- +# unknown-3rdparty` per-domain on QtWebKit will have the same effect as +# `all`. If this setting is used with URL patterns, the pattern gets +# applied to the origin/first party URL of the page making the request, +# not the request URL. With QtWebEngine 5.15.0+, paths will be stripped +# from URLs, so URL patterns using paths will not match. With +# QtWebEngine 5.15.2+, subdomains are additionally stripped as well, so +# you will typically need to set this setting for `example.com` when the +# cookie is set on `somesubdomain.example.com` for it to work properly. +# To debug issues with this setting, start qutebrowser with `--debug +# --logfilter network --debug-flag log-cookies` which will show all +# cookies being set. +# Type: String +# Valid values: +# - all: Accept all cookies. +# - no-3rdparty: Accept cookies from the same origin only. This is known to break some sites, such as GMail. +# - no-unknown-3rdparty: Accept cookies from the same origin only, unless a cookie is already set for the domain. On QtWebEngine, this is the same as no-3rdparty. +# - never: Don't accept cookies at all. +config.set('content.cookies.accept', 'all', 'chrome-devtools://*') + +# Which cookies to accept. With QtWebEngine, this setting also controls +# other features with tracking capabilities similar to those of cookies; +# including IndexedDB, DOM storage, filesystem API, service workers, and +# AppCache. Note that with QtWebKit, only `all` and `never` are +# supported as per-domain values. Setting `no-3rdparty` or `no- +# unknown-3rdparty` per-domain on QtWebKit will have the same effect as +# `all`. If this setting is used with URL patterns, the pattern gets +# applied to the origin/first party URL of the page making the request, +# not the request URL. With QtWebEngine 5.15.0+, paths will be stripped +# from URLs, so URL patterns using paths will not match. With +# QtWebEngine 5.15.2+, subdomains are additionally stripped as well, so +# you will typically need to set this setting for `example.com` when the +# cookie is set on `somesubdomain.example.com` for it to work properly. +# To debug issues with this setting, start qutebrowser with `--debug +# --logfilter network --debug-flag log-cookies` which will show all +# cookies being set. +# Type: String +# Valid values: +# - all: Accept all cookies. +# - no-3rdparty: Accept cookies from the same origin only. This is known to break some sites, such as GMail. +# - no-unknown-3rdparty: Accept cookies from the same origin only, unless a cookie is already set for the domain. On QtWebEngine, this is the same as no-3rdparty. +# - never: Don't accept cookies at all. +config.set('content.cookies.accept', 'all', 'devtools://*') + +# Value to send in the `Accept-Language` header. Note that the value +# read from JavaScript is always the global value. +# Type: String +config.set('content.headers.accept_language', '', 'https://matchmaker.krunker.io/*') + +# User agent to send. The following placeholders are defined: * +# `{os_info}`: Something like "X11; Linux x86_64". * `{webkit_version}`: +# The underlying WebKit version (set to a fixed value with +# QtWebEngine). * `{qt_key}`: "Qt" for QtWebKit, "QtWebEngine" for +# QtWebEngine. * `{qt_version}`: The underlying Qt version. * +# `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for +# QtWebEngine. * `{upstream_browser_version}`: The corresponding +# Safari/Chrome version. * `{qutebrowser_version}`: The currently +# running qutebrowser version. The default value is equal to the +# unchanged user agent of QtWebKit/QtWebEngine. Note that the value +# read from JavaScript is always the global value. With QtWebEngine +# between 5.12 and 5.14 (inclusive), changing the value exposed to +# JavaScript requires a restart. +# Type: FormatString +config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version} Safari/{webkit_version}', 'https://web.whatsapp.com/') + +# User agent to send. The following placeholders are defined: * +# `{os_info}`: Something like "X11; Linux x86_64". * `{webkit_version}`: +# The underlying WebKit version (set to a fixed value with +# QtWebEngine). * `{qt_key}`: "Qt" for QtWebKit, "QtWebEngine" for +# QtWebEngine. * `{qt_version}`: The underlying Qt version. * +# `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for +# QtWebEngine. * `{upstream_browser_version}`: The corresponding +# Safari/Chrome version. * `{qutebrowser_version}`: The currently +# running qutebrowser version. The default value is equal to the +# unchanged user agent of QtWebKit/QtWebEngine. Note that the value +# read from JavaScript is always the global value. With QtWebEngine +# between 5.12 and 5.14 (inclusive), changing the value exposed to +# JavaScript requires a restart. +# Type: FormatString +config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0', 'https://accounts.google.com/*') + +# User agent to send. The following placeholders are defined: * +# `{os_info}`: Something like "X11; Linux x86_64". * `{webkit_version}`: +# The underlying WebKit version (set to a fixed value with +# QtWebEngine). * `{qt_key}`: "Qt" for QtWebKit, "QtWebEngine" for +# QtWebEngine. * `{qt_version}`: The underlying Qt version. * +# `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for +# QtWebEngine. * `{upstream_browser_version}`: The corresponding +# Safari/Chrome version. * `{qutebrowser_version}`: The currently +# running qutebrowser version. The default value is equal to the +# unchanged user agent of QtWebKit/QtWebEngine. Note that the value +# read from JavaScript is always the global value. With QtWebEngine +# between 5.12 and 5.14 (inclusive), changing the value exposed to +# JavaScript requires a restart. +# Type: FormatString +config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99 Safari/537.36', 'https://*.slack.com/*') + +# Load images automatically in web pages. +# Type: Bool +config.set('content.images', True, 'chrome-devtools://*') + +# Load images automatically in web pages. +# Type: Bool +config.set('content.images', True, 'devtools://*') + +# Enable JavaScript. +# Type: Bool +config.set('content.javascript.enabled', True, 'chrome-devtools://*') + +# Enable JavaScript. +# Type: Bool +config.set('content.javascript.enabled', True, 'devtools://*') + +# Enable JavaScript. +# Type: Bool +config.set('content.javascript.enabled', True, 'chrome://*/*') + +# Enable JavaScript. +# Type: Bool +config.set('content.javascript.enabled', True, 'qute://*/*') + +##################################################################################### +#Configuraciones personales DiegoFcs +##################################################################################### + +#Configuraciones de tipos de letra# + +# Default font families to use. Whenever "default_family" is used in a +# font setting, it's replaced with the fonts listed here. If set to an +# empty value, a system-specific monospace default is used. +# Type: List of Font, or Font +c.fonts.default_family = '"Hack Nerd Font"' + +# Default font size to use. Whenever "default_size" is used in a font +# setting, it's replaced with the size listed here. Valid values are +# either a float value with a "pt" suffix, or an integer value with a +# "px" suffix. +# Type: String +c.fonts.default_size = '8pt' + +# Font used in the completion widget. +# Type: Font +c.fonts.completion.entry = '8pt "Hack Nerd Font"' + +# Font used for the debugging console. +# Type: Font +c.fonts.debug_console = '8pt "Hack Nerd Font"' + +# Font used for prompts. +# Type: Font +c.fonts.prompts = '8pt "Hack Nerd Font"' + +# Font used in the statusbar. +# Type: Font +c.fonts.statusbar = '8pt "Hack Nerd Font"' + +#Configuracion de pagina de inicio# + +# Setting default page for when opening new tabs or new windows with +# commands like :open -t and :open -w . +c.url.default_page = 'file:///home/diegofcs/.config/startpages/term/index.html' +c.url.start_pages = 'file:///home/diegofcs/.config/startpages/term/index.html' + +#Configuracion de la visibilidad de las tabs# + +# When to show the tab bar. +# Type: String +# Valid values: +# - always: Always show the tab bar. +# - never: Always hide the tab bar. +# - multiple: Hide the tab bar if only one tab is open. +# - switching: Show the tab bar when switching tabs. +c.tabs.show = 'switching' + +#Configuracion de descargas# + +# Directory to save downloads to. If unset, a sensible OS-specific +# default is used. +# Type: Directory +c.downloads.location.directory = '~/Descargas' +c.downloads.location.prompt = False +c.downloads.remove_finished = 500 + +#Configuracion de color# + +# base16-qutebrowser (https://github.com/theova/base16-qutebrowser) +# Base16 qutebrowser template by theova +# Grayscale Dark scheme by Alexandre Gavioli (https://github.com/Alexx2/) + +# set qutebrowser colors + +# Text color of the completion widget. May be a single color to use for +# all columns or a list of three colors, one for each column. +c.colors.completion.fg = "#b9b9b9" + +# Background color of the completion widget for odd rows. +c.colors.completion.odd.bg = "#252525" + +# Background color of the completion widget for even rows. +c.colors.completion.even.bg = "#101010" + +# Foreground color of completion widget category headers. +c.colors.completion.category.fg = "#a0a0a0" + +# Background color of the completion widget category headers. +c.colors.completion.category.bg = "#101010" + +# Top border color of the completion widget category headers. +c.colors.completion.category.border.top = "#101010" + +# Bottom border color of the completion widget category headers. +c.colors.completion.category.border.bottom = "#101010" + +# Foreground color of the selected completion item. +c.colors.completion.item.selected.fg = "#b9b9b9" + +# Background color of the selected completion item. +c.colors.completion.item.selected.bg = "#464646" + +# Top border color of the selected completion item. +c.colors.completion.item.selected.border.top = "#464646" + +# Bottom border color of the selected completion item. +c.colors.completion.item.selected.border.bottom = "#464646" + +# Foreground color of the matched text in the selected completion item. +c.colors.completion.item.selected.match.fg = "#8e8e8e" + +# Foreground color of the matched text in the completion. +c.colors.completion.match.fg = "#8e8e8e" + +# Color of the scrollbar handle in the completion view. +c.colors.completion.scrollbar.fg = "#b9b9b9" + +# Color of the scrollbar in the completion view. +c.colors.completion.scrollbar.bg = "#101010" + +# Background color of disabled items in the context menu. +c.colors.contextmenu.disabled.bg = "#252525" + +# Foreground color of disabled items in the context menu. +c.colors.contextmenu.disabled.fg = "#ababab" + +# Background color of the context menu. If set to null, the Qt default is used. +c.colors.contextmenu.menu.bg = "#101010" + +# Foreground color of the context menu. If set to null, the Qt default is used. +c.colors.contextmenu.menu.fg = "#b9b9b9" + +# Background color of the context menu’s selected item. If set to null, the Qt default is used. +c.colors.contextmenu.selected.bg = "#464646" + +#Foreground color of the context menu’s selected item. If set to null, the Qt default is used. +c.colors.contextmenu.selected.fg = "#b9b9b9" + +# Background color for the download bar. +c.colors.downloads.bar.bg = "#101010" + +# Color gradient start for download text. +c.colors.downloads.start.fg = "#101010" + +# Color gradient start for download backgrounds. +c.colors.downloads.start.bg = "#686868" + +# Color gradient end for download text. +c.colors.downloads.stop.fg = "#101010" + +# Color gradient stop for download backgrounds. +c.colors.downloads.stop.bg = "#868686" + +# Foreground color for downloads with errors. +c.colors.downloads.error.fg = "#7c7c7c" + +# Font color for hints. +c.colors.hints.fg = "#101010" + +# Foreground color of the URL in the statusbar when there's a warning. +# Type: QssColor +c.colors.statusbar.url.warn.fg = "#7c7c7c" + +#hints.border +#CSS border value for hints. +#Type: String +c.hints.border = "#101010" + +# Background color for hints. Note that you can use a `rgba(...)` value +# for transparency. +c.colors.hints.bg = "#a0a0a0" + +# Font color for the matched part of hints. +c.colors.hints.match.fg = "#b9b9b9" + +# Text color for the keyhint widget. +c.colors.keyhint.fg = "#b9b9b9" + +# Highlight color for keys to complete the current keychain. +c.colors.keyhint.suffix.fg = "#b9b9b9" + +# Background color of the keyhint widget. +c.colors.keyhint.bg = "#101010" + +# Foreground color of an error message. +c.colors.messages.error.fg = "#101010" + +# Background color of an error message. +c.colors.messages.error.bg = "#7c7c7c" + +# Border color of an error message. +c.colors.messages.error.border = "#7c7c7c" + +# Foreground color of a warning message. +c.colors.messages.warning.fg = "#101010" + +# Background color of a warning message. +c.colors.messages.warning.bg = "#747474" + +# Border color of a warning message. +c.colors.messages.warning.border = "#747474" + +# Foreground color of an info message. +c.colors.messages.info.fg = "#b9b9b9" + +# Background color of an info message. +c.colors.messages.info.bg = "#101010" + +# Border color of an info message. +c.colors.messages.info.border = "#101010" + +# Foreground color for prompts. +c.colors.prompts.fg = "#b9b9b9" + +# Border used around UI elements in prompts. +c.colors.prompts.border = "#101010" + +# Background color for prompts. +c.colors.prompts.bg = "#101010" + +# Background color for the selected item in filename prompts. +c.colors.prompts.selected.bg = "#464646" + +# Foreground color for the selected item in filename prompts. +c.colors.prompts.selected.fg = "#b9b9b9" + +# Foreground color of the statusbar. +c.colors.statusbar.normal.fg = "#8e8e8e" + +# Background color of the statusbar. +c.colors.statusbar.normal.bg = "#101010" + +# Foreground color of the statusbar in insert mode. +c.colors.statusbar.insert.fg = "#101010" + +# Background color of the statusbar in insert mode. +c.colors.statusbar.insert.bg = "#686868" + +# Foreground color of the statusbar in passthrough mode. +c.colors.statusbar.passthrough.fg = "#101010" + +# Background color of the statusbar in passthrough mode. +c.colors.statusbar.passthrough.bg = "#868686" + +# Foreground color of the statusbar in private browsing mode. +c.colors.statusbar.private.fg = "#101010" + +# Background color of the statusbar in private browsing mode. +c.colors.statusbar.private.bg = "#252525" + +# Foreground color of the statusbar in command mode. +c.colors.statusbar.command.fg = "#b9b9b9" + +# Background color of the statusbar in command mode. +c.colors.statusbar.command.bg = "#101010" + +# Foreground color of the statusbar in private browsing + command mode. +c.colors.statusbar.command.private.fg = "#b9b9b9" + +# Background color of the statusbar in private browsing + command mode. +c.colors.statusbar.command.private.bg = "#101010" + +# Foreground color of the statusbar in caret mode. +c.colors.statusbar.caret.fg = "#101010" + +# Background color of the statusbar in caret mode. +c.colors.statusbar.caret.bg = "#747474" + +# Foreground color of the statusbar in caret mode with a selection. +c.colors.statusbar.caret.selection.fg = "#101010" + +# Background color of the statusbar in caret mode with a selection. +c.colors.statusbar.caret.selection.bg = "#686868" + +# Background color of the progress bar. +c.colors.statusbar.progress.bg = "#686868" + +# Default foreground color of the URL in the statusbar. +c.colors.statusbar.url.fg = "#b9b9b9" + +# Foreground color of the URL in the statusbar on error. +c.colors.statusbar.url.error.fg = "#7c7c7c" + +# Foreground color of the URL in the statusbar for hovered links. +c.colors.statusbar.url.hover.fg = "#b9b9b9" + +# Foreground color of the URL in the statusbar on successful load +# (http). +c.colors.statusbar.url.success.http.fg = "#868686" + +# Foreground color of the URL in the statusbar on successful load +# (https). +c.colors.statusbar.url.success.https.fg = "#8e8e8e" + +# Foreground color of the URL in the statusbar when there's a warning. +c.colors.statusbar.url.warn.fg = "#747474" + +# Background color of the tab bar. +c.colors.tabs.bar.bg = "#101010" + +# Color gradient start for the tab indicator. +c.colors.tabs.indicator.start = "#686868" + +# Color gradient end for the tab indicator. +c.colors.tabs.indicator.stop = "#868686" + +# Color for the tab indicator on errors. +c.colors.tabs.indicator.error = "#7c7c7c" + +# Foreground color of unselected odd tabs. +c.colors.tabs.odd.fg = "#b9b9b9" + +# Background color of unselected odd tabs. +c.colors.tabs.odd.bg = "#252525" + +# Foreground color of unselected even tabs. +c.colors.tabs.even.fg = "#b9b9b9" + +# Background color of unselected even tabs. +c.colors.tabs.even.bg = "#101010" + +# Background color of pinned unselected even tabs. +c.colors.tabs.pinned.even.bg = "#868686" + +# Foreground color of pinned unselected even tabs. +c.colors.tabs.pinned.even.fg = "#f7f7f7" + +# Background color of pinned unselected odd tabs. +c.colors.tabs.pinned.odd.bg = "#8e8e8e" + +# Foreground color of pinned unselected odd tabs. +c.colors.tabs.pinned.odd.fg = "#f7f7f7" + +# Background color of pinned selected even tabs. +c.colors.tabs.pinned.selected.even.bg = "#464646" + +# Foreground color of pinned selected even tabs. +c.colors.tabs.pinned.selected.even.fg = "#b9b9b9" + +# Background color of pinned selected odd tabs. +c.colors.tabs.pinned.selected.odd.bg = "#464646" + +# Foreground color of pinned selected odd tabs. +c.colors.tabs.pinned.selected.odd.fg = "#b9b9b9" + +# Foreground color of selected odd tabs. +c.colors.tabs.selected.odd.fg = "#b9b9b9" + +# Background color of selected odd tabs. +c.colors.tabs.selected.odd.bg = "#464646" + +# Foreground color of selected even tabs. +c.colors.tabs.selected.even.fg = "#b9b9b9" + +# Background color of selected even tabs. +c.colors.tabs.selected.even.bg = "#464646" + +# Background color for webpages if unset (or empty to use the theme's +# color). +c.colors.webpage.bg = "#101010" + +# Text color of the completion widget. May be a single color to use for +# all columns or a list of three colors, one for each column. +# Type: List of QtColor, or QtColor +#c.colors.completion.fg = ['#f7f7f7', 'white', 'white'] + +# Background color of the completion widget for odd rows. +# Type: QssColor +c.colors.completion.odd.bg = '#252525' + +# Background color of the completion widget for even rows. +# Type: QssColor +c.colors.completion.even.bg = '#464646' + +# Foreground color of completion widget category headers. +# Type: QtColor +c.colors.completion.category.fg = '#999999' + +########################################################## +# Keybinds personalizados +########################################################## + +config.bind('Y', 'hint links spawn bash -c "~/.local/bin/qute-dl "$1"" _ {hint-url}') + +#config.bind('M', 'hint links spawn nohup mpv --cache=yes --demuxer-max-bytes=500M --demuxer-max-back-bytes=100M --save-position-on-quit=no -ytdl-format="bv*[height=720][ext=mp4][fps=30]+ba/b" {hint-url}') + +######################################################### +#Bangs! +######################################################## + +c.url.searchengines = {'DEFAULT': 'https://duckduckgo.com/?q={}', 'aw': 'https://wiki.archlinux.org/?search={}', 're': 'https://www.reddit.com/r/{}', 'yt': 'https://www.youtube.com/results?search_query={}'} diff --git a/.config/qutebrowser/greasemonkey/block-yt-ads.js b/.config/qutebrowser/greasemonkey/block-yt-ads.js new file mode 100644 index 0000000..eb1fd2d --- /dev/null +++ b/.config/qutebrowser/greasemonkey/block-yt-ads.js @@ -0,0 +1,18 @@ +// ==UserScript== +// @name Auto Skip YouTube Ads +// @version 1.0.0 +// @description Speed up and skip YouTube ads automatically +// @author jso8910 +// @match *://*.youtube.com/* +// @exclude *://*.youtube.com/subscribe_embed?* +// ==/UserScript== +setInterval(() => { + const btn = document.querySelector('.videoAdUiSkipButton,.ytp-ad-skip-button') + if (btn) { + btn.click() + } + const ad = [...document.querySelectorAll('.ad-showing')][0]; + if (ad) { + document.querySelector('video').playbackRate = 10; + } +}, 50) diff --git a/.config/qutebrowser/qsettings/QtProject.conf b/.config/qutebrowser/qsettings/QtProject.conf new file mode 100644 index 0000000..f132677 --- /dev/null +++ b/.config/qutebrowser/qsettings/QtProject.conf @@ -0,0 +1,8 @@ +[FileDialog] +history=file:///home/diegofcs/Documentos/Trabajo_oficina/Proceso_Oscar, file:///home/diegofcs/Documentos/Trabajo_oficina/Trabajo_SINTRASECOL/Activos/Procesos_Fabiano/Proceso_adtivo, file:///home/diegofcs/Imagenes/Screenshots, file:///home/diegofcs/Descargas, file:///home/diegofcs/Imagenes/Wallpapers +lastVisited=file:///home/diegofcs/Imagenes/Wallpapers +qtVersion=5.15.5 +shortcuts=file:, file:///home/diegofcs +sidebarWidth=83 +treeViewHeader=@ByteArray(\0\0\0\xff\0\0\0\0\0\0\0\x1\0\0\0\x1\0\0\0\0\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x3#\0\0\0\x4\x1\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x64\xff\xff\xff\xff\0\0\0\x81\0\0\0\0\0\0\0\x4\0\0\x1\xec\0\0\0\x1\0\0\0\0\0\0\0K\0\0\0\x1\0\0\0\0\0\0\0\x31\0\0\0\x1\0\0\0\0\0\0\0\xbb\0\0\0\x1\0\0\0\0\0\0\x3\xe8\0\xff\xff\xff\xff) +viewMode=List diff --git a/.config/qutebrowser/quickmarks b/.config/qutebrowser/quickmarks new file mode 100644 index 0000000..e69de29 diff --git a/.config/sbase b/.config/sbase new file mode 160000 index 0000000..2c2a7f5 --- /dev/null +++ b/.config/sbase @@ -0,0 +1 @@ +Subproject commit 2c2a7f54ab55a022a617e510b6e00c3e2736fabd diff --git a/.config/startpages/minim/favicon.svg b/.config/startpages/minim/favicon.svg new file mode 100644 index 0000000..d698f6a --- /dev/null +++ b/.config/startpages/minim/favicon.svg @@ -0,0 +1 @@ +<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M19.624 20.664c-.96 0-1.824-.208-2.592-.624-.736-.416-1.44-.88-2.112-1.392-.64-.512-1.28-.976-1.92-1.392-.608-.416-1.248-.624-1.92-.624-.736 0-1.44.272-2.112.816-.64.544-1.232 1.648-1.776 3.312l-2.544-1.104c.832-2.24 1.824-3.792 2.976-4.656 1.152-.896 2.336-1.344 3.552-1.344.96 0 1.808.208 2.544.624.768.416 1.472.88 2.112 1.392.672.512 1.312.976 1.92 1.392.64.416 1.296.624 1.968.624.384 0 .736-.048 1.056-.144a3.089 3.089 0 001.008-.624c.352-.32.672-.752.96-1.296.288-.544.576-1.232.864-2.064l2.544 1.152c-.832 2.24-1.824 3.792-2.976 4.656-1.152.864-2.336 1.296-3.552 1.296z" fill="#B3C9D5"/></g><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></svg> \ No newline at end of file diff --git a/.config/startpages/minim/index.html b/.config/startpages/minim/index.html new file mode 100644 index 0000000..8d9b1cd --- /dev/null +++ b/.config/startpages/minim/index.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" type="text/css" href="style/main.css"> + <title>diego@void~ $ + + + + + + + + + + diff --git a/.config/startpages/minim/js/script.js b/.config/startpages/minim/js/script.js new file mode 100644 index 0000000..57e4b38 --- /dev/null +++ b/.config/startpages/minim/js/script.js @@ -0,0 +1,65 @@ +var data = [ + { + AboutDevTypeText: "", + }, +]; + +var allElements = document.getElementsByClassName("typing"); +for (var j = 0; j < allElements.length; j++) { + var currentElementId = allElements[j].id; + var currentElementIdContent = data[0][currentElementId]; + var element = document.getElementById(currentElementId); + var devTypeText = currentElementIdContent; + + // type code + var i = 0, + isTag, + text; + (function type() { + text = devTypeText.slice(0, ++i); + if (text === devTypeText) return; + element.innerHTML = text; + var char = text.slice(-1); + if (char === "<") isTag = true; + if (char === ">") isTag = false; + if (isTag) return type(); + setTimeout(type, 60); + })(); +} + +function updateClock() { + var now = new Date(); + hours = now.getHours(); + minutes = now.getMinutes(); + seconds = now.getSeconds(); + if (seconds < 10) { + seconds_ = ":" + "0" + seconds; + } else { + seconds_ = ":" + seconds; + } + if (minutes < 10) { + time = hours + ":" + "0" + minutes + seconds_; + } else { + time = hours + ":" + minutes + seconds_; + } + if (hours < 10) { + time = "0" + time; + } + document.getElementById("time").innerHTML = time; + setTimeout(updateClock, 1000); +} + +function updateDate() { + const d = new Date(); + document.getElementById("date").innerHTML = d.toDateString(); +} + +function getLanguage() { + var lang = navigator.language; + + document.getElementById("lang").innerHTML = lang; +} + +updateClock(); +updateDate(); +getLanguage(); diff --git a/.config/startpages/minim/robots.txt b/.config/startpages/minim/robots.txt new file mode 100644 index 0000000..77470cb --- /dev/null +++ b/.config/startpages/minim/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/.config/startpages/minim/style/main.css b/.config/startpages/minim/style/main.css new file mode 100644 index 0000000..46bb74d --- /dev/null +++ b/.config/startpages/minim/style/main.css @@ -0,0 +1,111 @@ +:root { + --bgColor: #101010; + --textColor: #e3e3e3; + --linkColor: var(--textColor); + --textColorHover: #f7f7f7; +} + +@font-face { + font-family: "Hack Nerd Font"; +} + +body { + font-family: "Hack Nerd Font"; + font-size: 11px; + background: var(--bgColor); + color: var(--textColor); + border: #f7f7f7; + display: flex; + /* align-items: center; */ + justify-content: center; +} + +a { + color: var(--linkColor); + text-decoration: underline; + margin-top: .2rem; +} + +a > div:hover { + color: var(--textColorHover) +} + +.flex-container { + display: flex; + justify-content: center; + flex-direction: column; + width: auto; + margin-top: 15rem; + outline-style: solid; + outline-color: #f7f7f7; + outline-width: 1px; +} + +.flex-top-bar { + display: flex; + flex-direction: row; +} + +.top-bar { + margin-left: 0.2rem; + margin-bottom: 0.1rem; + +} + +.flex-center { + flex-direction: row; + display: flex; + justify-content: space-between; +} + +.center-box { + flex-direction: column; + display: flex; + margin: .75rem; + padding-bottom: 1rem; +} + +.flex-bottom-bar { + flex-direction: row; + display: flex; + justify-content: space-between; + color: var(--bgColor); + background-color: var(--textColor); +} + +.botton-bar { + padding: 0.5rem; +} + +.search-form { + width: 10rem; + outline: none; + border: none; + color: var(--textColor); + background-color: var(--bgColor); + font-family: "Hack Nerd Font"; + font-size: 11px; + padding-left: .4rem; + caret-color: var(--textColor); +} + + +.tilde { + color: #FF0000; +} + +/* .blinker { + opacity: 1; + margin-bottom: -2px; + height: 15px; + margin-left: -5px; + border-left: 3px solid yellow; + animation: blinker 0.9s steps(2, start) infinite; + } + + @keyframes blinker { + to { + visibility: hidden; + } + } + */ diff --git a/.config/startpages/term/index.html b/.config/startpages/term/index.html new file mode 100644 index 0000000..77e2415 --- /dev/null +++ b/.config/startpages/term/index.html @@ -0,0 +1,58 @@ + + + + + + + diego@void~ $ + + +
+
diego@void~ $ tree
+
+

.

+ +
+
+ + diff --git a/.config/startpages/term/manifest.json b/.config/startpages/term/manifest.json new file mode 100644 index 0000000..0938a2d --- /dev/null +++ b/.config/startpages/term/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "My custom new tab page", + "description": "Overrides the new tab page", + "version": "0.1", + "incognito": "split", + "chrome_url_overrides": { + "newtab": "index.html" + }, + "manifest_version": 2 +} \ No newline at end of file diff --git a/.config/startpages/term/style.css b/.config/startpages/term/style.css new file mode 100644 index 0000000..c77d375 --- /dev/null +++ b/.config/startpages/term/style.css @@ -0,0 +1,106 @@ +:root { + --font: "Hack Nerd Font"; + --background: #101010; + --foreground: #e3e3e3; + --green: #f7f7f7; + --red: #f7f7f7; + --orange: #f7f7f7; + --branch: 1px solid #e3e3e3; +} + +html { + font-size: 11px; +} + +body { + background: var(--background); +} + +.container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.prompt { + font-family: var(--font); + color: var(--foreground); +} + +.prompt~.prompt { + padding: 1.5rem 0 0.3125rem; +} + +span { + color: var(--green); +} + +h1 { + display: inline; + font-family: var(--font); + font-size: 1rem; + font-weight: normal; + color: var(--red); +} + +.tree > ul { + margin: 0; + padding-left: 1rem; +} + +ul { + list-style: none; + padding-left: 2.5rem; +} + +li { + position: relative; +} + +li::before, li::after { + content: ""; + position: absolute; + left: -0.75rem; +} + +li::before { + border-top: var(--branch); + top: 0.75rem; + width: 0.5rem; +} + +li::after { + border-left: var(--branch); + height: 100%; + top: 0.25rem; +} + +li:last-child::after { + height: 0.5rem; +} + +a { + font-family: var(--font); + font-size: 1rem; + color: var(--foreground); + text-decoration: none; + outline: none; +} + +a:hover { + color: var(--background); + background: var(--orange); +} + +form h1 { + padding-left: 0.125rem; +} + +input { + font-family: var(--font); + font-size: 1rem; + color: var(--foreground); + background-color: var(--background); + border: none; +} diff --git a/.config/vis/plugins/complete-filename.lua b/.config/vis/plugins/complete-filename.lua new file mode 100644 index 0000000..e955347 --- /dev/null +++ b/.config/vis/plugins/complete-filename.lua @@ -0,0 +1,56 @@ +-- complete file path at primary selection location using vis-complete(1) + +vis:map(vis.modes.INSERT, "", function() + local win = vis.win + local file = win.file + local pos = win.selection.pos + if not pos then return end + -- TODO do something clever here + local range = file:text_object_longword(pos > 0 and pos-1 or pos); + if not range then return end + if range.finish > pos then range.finish = pos end + if range.start == range.finish then return end + local prefix = file:content(range) + if not prefix then return end + -- Strip leading delimiters for some languages + local _, j = string.find(prefix, "[[(<'\"]+") + if j then prefix = prefix:sub(j + 1) end + local cmd = string.format("vis-complete --file '%s'", prefix:gsub("'", "'\\''")) + local status, out, err = vis:pipe(file, { start = 0, finish = 0 }, cmd) + if status ~= 0 or not out then + if err then vis:info(err) end + return + end + file:insert(pos, out) + win.selection.pos = pos + #out +end, "Complete file path") + +-- complete file path at primary selection location using vis-open(1) + +vis:map(vis.modes.INSERT, "", function() + local win = vis.win + local file = win.file + local pos = win.selection.pos + if not pos then return end + -- TODO do something clever here + local range = file:text_object_longword(pos > 0 and pos-1 or pos); + if not range then return end + if range.finish > pos then range.finish = pos end + local prefix = file:content(range) + if not prefix then return end + if prefix:match("^%s*$") then + prefix = "" + range.start = pos + range.finish = pos + end + local cmd = string.format("vis-open -- '%s'*", prefix:gsub("'", "'\\''")) + local status, out, err = vis:pipe(file, { start = 0, finish = 0 }, cmd) + if status ~= 0 or not out then + if err then vis:info(err) end + return + end + out = out:gsub("\n$", "") + file:delete(range) + file:insert(range.start, out) + win.selection.pos = range.start + #out +end, "Complete file name") diff --git a/.config/vis/plugins/complete-word.lua b/.config/vis/plugins/complete-word.lua new file mode 100644 index 0000000..41d112c --- /dev/null +++ b/.config/vis/plugins/complete-word.lua @@ -0,0 +1,22 @@ +-- complete word at primary selection location using vis-complete(1) + +vis:map(vis.modes.INSERT, "", function() + local win = vis.win + local file = win.file + local pos = win.selection.pos + if not pos then return end + local range = file:text_object_word(pos > 0 and pos-1 or pos); + if not range then return end + if range.finish > pos then range.finish = pos end + if range.start == range.finish then return end + local prefix = file:content(range) + if not prefix then return end + local cmd = string.format("vis-complete --word '%s'", prefix:gsub("'", "'\\''")) + local status, out, err = vis:pipe(file, { start = 0, finish = file.size }, cmd) + if status ~= 0 or not out then + if err then vis:info(err) end + return + end + file:insert(pos, out) + win.selection.pos = pos + #out +end, "Complete word in file") diff --git a/.config/vis/plugins/digraph.lua b/.config/vis/plugins/digraph.lua new file mode 100644 index 0000000..b783612 --- /dev/null +++ b/.config/vis/plugins/digraph.lua @@ -0,0 +1,23 @@ +-- insert digraphs using vis-digraph(1) + +vis:map(vis.modes.INSERT, "", function(keys) + if #keys < 2 then + return -1 -- need more input + end + local file = io.popen(string.format("vis-digraph '%s' 2>&1", keys:gsub("'", "'\\''"))) + local output = file:read('*all') + local success, msg, status = file:close() + if success then + if vis.mode == vis.modes.INSERT then + vis:insert(output) + elseif vis.mode == vis.modes.REPLACE then + vis:replace(output) + end + elseif msg == 'exit' then + if status == 2 then + return -1 -- prefix need more input + end + vis:info(output) + end + return #keys +end, "Insert digraph") diff --git a/.config/vis/plugins/filetype.lua b/.config/vis/plugins/filetype.lua new file mode 100644 index 0000000..88ae9f2 --- /dev/null +++ b/.config/vis/plugins/filetype.lua @@ -0,0 +1,609 @@ +vis.ftdetect = {} + +vis.ftdetect.ignoresuffixes = { + "~$", "%.orig$", "%.bak$", "%.old$", "%.new$" +} + +vis.ftdetect.filetypes = { + actionscript = { + ext = { "%.as$", "%.asc$" }, + }, + ada = { + ext = { "%.adb$", "%.ads$" }, + }, + ansi_c = { + ext = { "%.c$", "%.C$", "%.h$" }, + mime = { "text/x-c" }, + }, + antlr = { + ext = { "%.g$", "%.g4$" }, + }, + apdl = { + ext = { "%.ans$", "%.inp$", "%.mac$" }, + }, + apl = { + ext = { "%.apl$" } + }, + applescript = { + ext = { "%.applescript$" }, + }, + asm = { + ext = { "%.asm$", "%.ASM$", "%.s$", "%.S$" }, + }, + asp = { + ext = { "%.asa$", "%.asp$", "%.hta$" }, + }, + autoit = { + ext = { "%.au3$", "%.a3x$" }, + }, + awk = { + hashbang = { "^/usr/bin/[mng]awk%s+%-f" }, + utility = { "^[mgn]?awk$", "^goawk$" }, + ext = { "%.awk$" }, + }, + bash = { + utility = { "^[db]ash$", "^sh$","^t?csh$","^zsh$" }, + ext = { "%.bash$", "%.csh$", "%.sh$", "%.zsh$" ,"^APKBUILD$", "%.ebuild$", "^.bashrc$", "^.bash_profile$" }, + mime = { "text/x-shellscript", "application/x-shellscript" }, + }, + batch = { + ext = { "%.bat$", "%.cmd$" }, + }, + bibtex = { + ext = { "%.bib$" }, + }, + boo = { + ext = { "%.boo$" }, + }, + caml = { + ext = { "%.caml$", "%.ml$", "%.mli$", "%.mll$", "%.mly$" }, + }, + chuck = { + ext = { "%.ck$" }, + }, + clojure = { + ext = { "%.clj$", "%.cljc$", "%.cljs$", "%.edn$" } + }, + cmake = { + ext = { "%.cmake$", "%.cmake.in$", "%.ctest$", "%.ctest.in$" }, + }, + coffeescript = { + ext = { "%.coffee$" }, + mime = { "text/x-coffee" }, + }, + cpp = { + ext = { "%.cpp$", "%.cxx$", "%.c++$", "%.cc$", "%.hh$", "%.hpp$", "%.hxx$", "%.h++$" }, + mime = { "text/x-c++" }, + }, + crontab = { + ext = { "^crontab.*$" }, + cmd = { "set savemethod inplace" }, + }, + crystal = { + ext = { "%.cr$" }, + }, + csharp = { + ext = { "%.cs$" }, + }, + css = { + ext = { "%.css$" }, + mime = { "text/x-css" }, + }, + cuda = { + ext = { "%.cu$", "%.cuh$" }, + }, + dart = { + ext = { "%.dart$" }, + }, + desktop = { + ext = { "%.desktop$" }, + }, + diff = { + ext = { "%.diff$", "%.patch$", "%.rej$", "^COMMIT_EDITMSG$" }, + cmd = { "set colorcolumn 72" }, + }, + dmd = { + ext = { "%.d$", "%.di$" }, + }, + dockerfile = { + ext = { "^Dockerfile$", "%.Dockerfile$" }, + }, + dot = { + ext = { "%.dot$" }, + }, + dsv = { + ext = { "^group$", "^gshadow$", "^passwd$", "^shadow$" }, + }, + eiffel = { + ext = { "%.e$", "%.eif$" }, + }, + elixir = { + ext = { "%.ex$", "%.exs$" }, + }, + elm = { + ext = { "%.elm$" }, + }, + erlang = { + ext = { "%.erl$", "%.hrl$" }, + }, + fantom = { + ext = { "%.fan$" }, + }, + faust = { + ext = { "%.dsp$" }, + }, + fennel = { + ext = { "%.fnl$" }, + }, + fish = { + utility = { "^fish$" }, + ext = { "%.fish$" }, + }, + forth = { + ext = { "%.forth$", "%.frt$", "%.fs$", "%.fth$" }, + }, + fortran = { + ext = { "%.f$", "%.for$", "%.ftn$", "%.fpp$", "%.f77$", "%.f90$", "%.f95$", "%.f03$", "%.f08$" }, + }, + fsharp = { + ext = { "%.fs$" }, + }, + fstab = { + ext = { "fstab" }, + }, + gap = { + ext = { "%.g$", "%.gd$", "%.gi$", "%.gap$" }, + }, + gemini = { + ext = { "%.gmi" }, + mime = { "text/gemini" }, + }, + gettext = { + ext = { "%.po$", "%.pot$" }, + }, + gherkin = { + ext = { "%.feature$" }, + }, + ['git-rebase'] = { + ext = { "git%-rebase%-todo" }, + }, + glsl = { + ext = { "%.glslf$", "%.glslv$" }, + }, + gnuplot = { + ext = { "%.dem$", "%.plt$" }, + }, + go = { + ext = { "%.go$" }, + }, + groovy = { + ext = { "%.groovy$", "%.gvy$", "^Jenkinsfile$" }, + }, + gtkrc = { + ext = { "%.gtkrc$" }, + }, + hare = { + ext = { "%.ha$" } + }, + haskell = { + ext = { "%.hs$" }, + mime = { "text/x-haskell" }, + }, + html = { + ext = { "%.htm$", "%.html$", "%.shtm$", "%.shtml$", "%.xhtml$" }, + mime = { "text/x-html" }, + }, + icon = { + ext = { "%.icn$" }, + }, + idl = { + ext = { "%.idl$", "%.odl$" }, + }, + inform = { + ext = { "%.inf$", "%.ni$" }, + }, + ini = { + ext = { "%.cfg$", "%.cnf$", "%.conf$", "%.inf$", "%.ini$", "%.reg$" }, + }, + io_lang = { + ext = { "%.io$" }, + }, + java = { + ext = { "%.bsh$", "%.java$" }, + }, + javascript = { + ext = { "%.cjs$", "%.js$", "%.jsfl$", "%.mjs$", "%.ts$", "%.jsx$", "%.tsx$" }, + }, + json = { + ext = { "%.json$" }, + mime = { "text/x-json" }, + }, + jsp = { + ext = { "%.jsp$" }, + }, + julia = { + ext = { "%.jl$" }, + }, + latex = { + ext = { "%.bbl$", "%.cls$", "%.dtx$", "%.ins$", "%.ltx$", "%.tex$", "%.sty$" }, + mime = { "text/x-tex" }, + }, + ledger = { + ext = { "%.ledger$", "%.journal$" }, + }, + less = { + ext = { "%.less$" }, + }, + lilypond = { + ext = { "%.ily$", "%.ly$" }, + }, + lisp = { + ext = { "%.cl$", "%.el$", "%.lisp$", "%.lsp$" }, + mime = { "text/x-lisp" }, + }, + litcoffee = { + ext = { "%.litcoffee$" }, + }, + logtalk = { + ext = { "%.lgt$" }, + }, + lua = { + utility = {"^lua%-?5?%d?$", "^lua%-?5%.%d$" }, + ext = { "%.lua$" }, + mime = { "text/x-lua" }, + }, + makefile = { + hashbang = {"^#!/usr/bin/make"}, + utility = {"^make$"}, + ext = { "%.iface$", "%.mak$", "%.mk$", "GNUmakefile", "makefile", "Makefile" }, + mime = { "text/x-makefile" }, + }, + man = { + ext = { + "%.1$", "%.2$", "%.3$", "%.4$", "%.5$", "%.6$", "%.7$", + "%.8$", "%.9$", "%.1x$", "%.2x$", "%.3x$", "%.4x$", + "%.5x$", "%.6x$", "%.7x$", "%.8x$", "%.9x$" + }, + }, + markdown = { + ext = { "%.md$", "%.markdown$" }, + mime = { "text/x-markdown" }, + }, + meson = { + ext = { "^meson%.build$" }, + }, + moonscript = { + ext = { "%.moon$" }, + mime = { "text/x-moon" }, + }, + myrddin = { + ext = { "%.myr$" }, + }, + nemerle = { + ext = { "%.n$" }, + }, + networkd = { + ext = { "%.link$", "%.network$", "%.netdev$" }, + }, + nim = { + ext = { "%.nim$" }, + }, + nsis = { + ext = { "%.nsh$", "%.nsi$", "%.nsis$" }, + }, + objective_c = { + ext = { "%.m$", "%.mm$", "%.objc$" }, + mime = { "text/x-objc" }, + }, + pascal = { + ext = { "%.dpk$", "%.dpr$", "%.p$", "%.pas$" }, + }, + perl = { + ext = { "%.al$", "%.perl$", "%.pl$", "%.pm$", "%.pod$" }, + mime = { "text/x-perl" }, + }, + php = { + ext = { "%.inc$", "%.php$", "%.php3$", "%.php4$", "%.phtml$" }, + }, + pico8 = { + ext = { "%.p8$" }, + }, + pike = { + ext = { "%.pike$", "%.pmod$" }, + }, + pkgbuild = { + ext = { "^PKGBUILD$", "%.PKGBUILD$" }, + }, + pony = { + ext = { "%.pony$" }, + }, + powershell = { + ext = { "%.ps1$" }, + }, + prolog = { + ext = { "%.pl$", "%.pro$", "%.prolog$" }, + }, + props = { + ext = { "%.props$", "%.properties$" }, + }, + protobuf = { + ext = { "%.proto$" }, + }, + ps = { + ext = { "%.eps$", "%.ps$" }, + }, + pure = { + ext = { "%.pure$" }, + }, + python = { + utility = { "^python%d?" }, + ext = { "%.sc$", "%.py$", "%.pyw$" }, + mime = { "text/x-python", "text/x-script.python" }, + }, + reason = { + ext = { "%.re$" }, + }, + rc = { + utility = {"^rc$"}, + ext = { "%.rc$", "%.es$" }, + }, + rebol = { + ext = { "%.r$", "%.reb$" }, + }, + rest = { + ext = { "%.rst$" }, + }, + rexx = { + ext = { "%.orx$", "%.rex$" }, + }, + rhtml = { + ext = { "%.erb$", "%.rhtml$" }, + }, + routeros = { + ext = { "%.rsc" }, + detect = function(_, data) + return data:match("^#.* by RouterOS") + end + }, + rstats = { + ext = { "%.R$", "%.Rout$", "%.Rhistory$", "%.Rt$", "Rout.save", "Rout.fail" }, + }, + ruby = { + ext = { "%.Rakefile$", "%.rake$", "%.rb$", "%.rbw$", "^Vagrantfile$" }, + mime = { "text/x-ruby" }, + }, + rust = { + ext = { "%.rs$" }, + mime = { "text/x-rust" }, + }, + sass = { + ext = { "%.sass$", "%.scss$" }, + mime = { "text/x-sass", "text/x-scss" }, + }, + scala = { + ext = { "%.scala$" }, + mime = { "text/x-scala" }, + }, + scheme = { + ext = { "%.rkt$", "%.sch$", "%.scm$", "%.sld$", "%.sls$", "%.ss$" }, + }, + smalltalk = { + ext = { "%.changes$", "%.st$", "%.sources$" }, + }, + sml = { + ext = { "%.sml$", "%.fun$", "%.sig$" }, + }, + snobol4 = { + ext = { "%.sno$", "%.SNO$" }, + }, + spin = { + ext = { "%.spin$" } + }, + sql= { + ext = { "%.ddl$", "%.sql$" }, + }, + strace = { + detect = function(_, data) + return data:match("^execve%(") + end + }, + systemd = { + ext = { + "%.automount$", "%.device$", "%.mount$", "%.path$", + "%.scope$", "%.service$", "%.slice$", "%.socket$", + "%.swap$", "%.target$", "%.timer$" + }, + }, + taskpaper = { + ext = { "%.taskpaper$" }, + }, + tcl = { + utility = {"^tclsh$", "^jimsh$" }, + ext = { "%.tcl$", "%.tk$" }, + }, + texinfo = { + ext = { "%.texi$" }, + }, + text = { + ext = { "%.txt$" }, + -- Do *not* list mime "text/plain" here, it is covered below, + -- see 'try text lexer as a last resort' + }, + toml = { + ext = { "%.toml$" }, + }, + vala = { + ext = { "%.vala$" } + }, + vb = { + ext = { + "%.asa$", "%.bas$", "%.ctl$", "%.dob$", + "%.dsm$", "%.dsr$", "%.frm$", "%.pag$", "%.vb$", + "%.vba$", "%.vbs$" + }, + }, + vcard = { + ext = { "%.vcf$", "%.vcard$" }, + }, + verilog = { + ext = { "%.v$", "%.ver$", "%.sv$" }, + }, + vhdl = { + ext = { "%.vh$", "%.vhd$", "%.vhdl$" }, + }, + wsf = { + ext = { "%.wsf$" }, + }, + xs = { + ext = { "%.xs$", "^%.xsin$", "^%.xsrc$" }, + }, + xml = { + ext = { + "%.dtd$", "%.glif$", "%.plist$", "%.svg$", "%.xml$", + "%.xsd$", "%.xsl$", "%.xslt$", "%.xul$" + }, + }, + xtend = { + ext = {"%.xtend$" }, + }, + yaml = { + ext = { "%.yaml$", "%.yml$" }, + mime = { "text/x-yaml" }, + }, + zig = { + ext = { "%.zig$" }, + }, +} + +vis.events.subscribe(vis.events.WIN_OPEN, function(win) + + local set_filetype = function(syntax, filetype) + for _, cmd in pairs(filetype.cmd or {}) do + vis:command(cmd) + end + win:set_syntax(syntax) + end + + local name = win.file.name + -- remove ignored suffixes from filename + local sanitizedfn = name + if sanitizedfn ~= nil then + sanitizedfn = sanitizedfn:gsub('^.*/', '') + repeat + local changed = false + for _, pattern in pairs(vis.ftdetect.ignoresuffixes) do + local start = sanitizedfn:find(pattern) + if start then + sanitizedfn = sanitizedfn:sub(1, start-1) + changed = true + end + end + until not changed + end + + -- detect filetype by filename ending with a configured extension + if sanitizedfn ~= nil then + for lang, ft in pairs(vis.ftdetect.filetypes) do + for _, pattern in pairs(ft.ext or {}) do + if sanitizedfn:match(pattern) then + set_filetype(lang, ft) + return + end + end + end + end + + -- run file(1) to determine mime type + local mime + if name ~= nil then + local file = io.popen(string.format("file -bL --mime-type -- '%s'", name:gsub("'", "'\\''"))) + if file then + mime = file:read('*all') + file:close() + if mime then + mime = mime:gsub('%s*$', '') + end + if mime and #mime > 0 then + for lang, ft in pairs(vis.ftdetect.filetypes) do + for _, ft_mime in pairs(ft.mime or {}) do + if mime == ft_mime then + set_filetype(lang, ft) + return + end + end + end + end + end + end + + -- pass first few bytes of file to custom file type detector functions + local file = win.file + local data = file:content(0, 256) + if data and #data > 0 then + for lang, ft in pairs(vis.ftdetect.filetypes) do + if type(ft.detect) == 'function' and ft.detect(file, data) then + set_filetype(lang, ft) + return + end + end + +--[[ hashbang check + hashbangs only have command argument + if /env, find utility in args + discard first arg if /-[^S]*S/; and all subsequent /=/ + NOTE: this means you can't have a command with /^-|=/ + return first field, which should be the utility. + NOTE: long-options unsupported +--]] + local fullhb, utility = data:match"^#![ \t]*(/+[^/\n]+[^\n]*)" + if fullhb then + local i, field = 1, {} + for m in fullhb:gmatch"%g+" do field[i],i = m,i+1 end + -- NOTE: executables should not have a space (or =, see below) + if field[1]:match"/env$" then + table.remove(field,1) + -- it is assumed that the first argument are short options, with -S inside + if string.match(field[1] or "", "^%-[^S-]*S") then -- -S found + table.remove(field,1) + -- skip all name=value + while string.match(field[1] or "","=") do + table.remove(field,1) + end + -- (hopefully) whatever is left in field[1] should be the utility or nil + end + end + utility = string.match(field[1] or "", "[^/]+$") -- remove filepath + end + + local function searcher(tbl, subject) + for i, pattern in ipairs(tbl or {}) do + if string.match(subject, pattern) then + return true + end + end + return false + end + + if utility or fullhb then + for lang, ft in pairs(vis.ftdetect.filetypes) do + if + utility and searcher(ft.utility, utility) + or + fullhb and searcher(ft.hashbang, fullhb) + then + set_filetype(lang, ft) + return + end + end + end + end + + -- try text lexer as a last resort + if (mime or 'text/plain'):match('^text/.+$') then + set_filetype('text', vis.ftdetect.filetypes.text) + return + end + + win:set_syntax(nil) +end) + diff --git a/.config/vis/plugins/number-inc-dec.lua b/.config/vis/plugins/number-inc-dec.lua new file mode 100644 index 0000000..ca794ca --- /dev/null +++ b/.config/vis/plugins/number-inc-dec.lua @@ -0,0 +1,59 @@ +-- increment/decrement number in dec/hex/oct format +local lexer = vis.lexers +local lpeg = vis.lpeg +if not lexer.load or not lpeg then return end + +local Cp = lpeg.Cp() +local dec_num = lpeg.S('+-')^-1 * lexer.dec_num +local pattern = lpeg.P{ Cp * (lexer.hex_num + lexer.oct_num + dec_num) * Cp + 1 * lpeg.V(1) } + +local change = function(delta) + + local win = vis.win + local file = win.file + local count = vis.count + if not count then count = 1 end + vis.count = nil -- reset count, otherwise it affects next motion + + for selection in win:selections_iterator() do + local pos = selection.pos + if not pos then goto continue end + local word = file:text_object_word(pos); + if not word then goto continue end + local data = file:content(word.start, 1024) + if not data then goto continue end + local s, e = pattern:match(data) + if not s then goto continue end + data = string.sub(data, s, e-1) + if #data == 0 then goto continue end + -- align start and end for fileindex + s = word.start + s - 1 + e = word.start + e - 1 + local base, format, padding = 10, 'd', 0 + if lexer.oct_num:match(data) then + base = 8 + format = 'o' + padding = #data + elseif lexer.hex_num:match(data) then + base = 16 + format = 'x' + padding = #data - #"0x" + end + local number = tonumber(data, base == 8 and 8 or nil) + if not number then goto continue end + number = number + delta * count + -- string.format does not support negative hex/oct values + if base ~= 10 and number < 0 then number = 0 end + number = string.format((base == 16 and "0x" or "") .. "%0"..padding..format, number) + if base == 8 and string.sub(number, 0, 1) ~= "0" then + number = '0' .. number + end + file:delete(s, e - s) + file:insert(s, number) + selection.pos = s + ::continue:: + end +end + +vis:map(vis.modes.NORMAL, "", function() change( 1) end, "Increment number") +vis:map(vis.modes.NORMAL, "", function() change(-1) end, "Decrement number") diff --git a/.config/vis/plugins/textobject-lexer.lua b/.config/vis/plugins/textobject-lexer.lua new file mode 100644 index 0000000..2f9d757 --- /dev/null +++ b/.config/vis/plugins/textobject-lexer.lua @@ -0,0 +1,31 @@ +-- text object matching a lexer token + +local MAX_CONTEXT = 32768 + +vis:textobject_new("ii", function(win, pos) + + if not win.syntax or not vis.lexers.load then + return nil + end + + local before, after = pos - MAX_CONTEXT, pos + MAX_CONTEXT + if before < 0 then + before = 0 + end + -- TODO make sure we start at a line boundary? + + local lexer = vis.lexers.load(win.syntax, nil, true) + local data = win.file:content(before, after - before) + local tokens = lexer:lex(data) + local cur = before + + for i = 1, #tokens, 2 do + local token_next = before + tokens[i+1] - 1 + if cur <= pos and pos < token_next then + return cur, token_next + end + cur = token_next + end + + return nil +end, "Current lexer token") diff --git a/.config/vis/plugins/vis-filetype-settings.lua b/.config/vis/plugins/vis-filetype-settings.lua new file mode 100644 index 0000000..d96a0af --- /dev/null +++ b/.config/vis/plugins/vis-filetype-settings.lua @@ -0,0 +1,39 @@ +-- vis-filetype-settings +-- (https://github.com/jocap/vis-filetype-settings) + +-- This plugin provides a declarative interface for setting vis +-- options depending on filetype. +-- +-- It expects a global variable called `settings` to be defined: +-- +-- settings = { +-- markdown = {"set expandtab on", "set tabwidth 4"} +-- } +-- +-- In this variable, filetypes are mapped to sets of settings that are +-- to be executed when a window containing the specified filetype is +-- opened. +-- +-- If you want to do more than setting simple options, you can specify a function instead: +-- +-- settings = { +-- bash = function(win) +-- -- do things for shell scripts +-- end +-- } +-- +-- Be sure not to run commands that open another window with the same +-- filetype, leading to an infinite loop. + +vis.events.subscribe(vis.events.WIN_OPEN, function(win) + if settings == nil then return end + local window_settings = settings[win.syntax] + + if type(window_settings) == "table" then + for _, setting in pairs(window_settings) do + vis:command(setting) + end + elseif type(window_settings) == "function" then + window_settings(win) + end +end) diff --git a/.config/vis/plugins/vis-spellcheck/.editorconfig b/.config/vis/plugins/vis-spellcheck/.editorconfig new file mode 100644 index 0000000..1635025 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/.editorconfig @@ -0,0 +1,11 @@ +# http://editorconfig.org + +root = true + +[*.lua] +indent_style = space +indent_size = 2 + +[.gitlab-ci.yml] +indent_style = space +indent_size = 2 diff --git a/.config/vis/plugins/vis-spellcheck/.gitlab-ci.yml b/.config/vis/plugins/vis-spellcheck/.gitlab-ci.yml new file mode 100644 index 0000000..3969fef --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/.gitlab-ci.yml @@ -0,0 +1,23 @@ +image: imolein/luarocks:5.4 + +stages: + - check + - test + +check-luacheck: + stage: check + script: + - luarocks install luacheck + - make check-luacheck + +# check-format: + # stage: check + # script: + # - luarocks install --server=https://luarocks.org/dev luaformatter + # - make check-format + +# test: + # stage: test + # script: + # - luarocks install lunatest + # - make test diff --git a/.config/vis/plugins/vis-spellcheck/.lua-format b/.config/vis/plugins/vis-spellcheck/.lua-format new file mode 100644 index 0000000..43ff46c --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/.lua-format @@ -0,0 +1,27 @@ +column_limit: 100 +indent_width: 2 +use_tab: false +spaces_before_call: 1 +keep_simple_control_block_one_line: false +keep_simple_function_one_line: false +align_args: true +break_after_functioncall_lp: false +break_before_functioncall_rp: false +spaces_inside_functioncall_parens: false +spaces_inside_functiondef_parens: false +align_parameter: true +chop_down_parameter: false +break_after_functiondef_lp: false +break_before_functiondef_rp: false +align_table_field: true +break_after_table_lb: true +break_before_table_rb: true +chop_down_table: true +chop_down_kv_table: true +table_sep: "," +extra_sep_at_table_end: true +column_table_limit: 80 +spaces_inside_table_braces: false +break_after_operator: true +double_quote_to_single_quote: true +spaces_around_equals_in_field: true diff --git a/.config/vis/plugins/vis-spellcheck/LICENSE b/.config/vis/plugins/vis-spellcheck/LICENSE new file mode 100644 index 0000000..404df62 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Florian Fischer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.config/vis/plugins/vis-spellcheck/Makefile b/.config/vis/plugins/vis-spellcheck/Makefile new file mode 100644 index 0000000..0fcc468 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/Makefile @@ -0,0 +1,14 @@ +.PHONY: check format check-luacheck check-format + +LUA_FILES := $(wildcard *.lua) + +check: check-luacheck check-format + +check-luacheck: + luacheck --globals=vis -- $(LUA_FILES) + +check-format: + for lf in $(LUA_FILES); do tools/check-format "$${lf}"; done + +format: + lua-format -i $(LUA_FILES) diff --git a/.config/vis/plugins/vis-spellcheck/Readme.md b/.config/vis/plugins/vis-spellcheck/Readme.md new file mode 100644 index 0000000..48d5f15 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/Readme.md @@ -0,0 +1,45 @@ +# vis-spellcheck + +A spellchecking lua plugin for the [vis editor](https://github.com/martanne/vis). + +## Installation + +1. Download `spellcheck.lua` or clone this repository into your plugin directory +2. Load the plugin in your `visrc.lua` with `require('plugins/vis-spellcheck')` + +## Usage + ++ To enable highlighting of misspelled words press `e` in normal mode. ++ To disable highlighting press `d` in normal mode. ++ To toggle highlighting press `` in normal mode. ++ To correct the word under the cursor press `w` in normal mode. ++ To ignore the word under the cursor press `i` in normal mode. + +## Configuration + +The module table returned from `require(...)` has some configuration options: + +* `cmd`: cmd that is passed to popen() and must return word corrections in Ispell format. + * default: `enchant -d %s` +* `list_cmd`: cmd that is passed to `popen()` and must output a list of misspelled words. + * default: `enchant -l -d %s` +* `default_lang`: The name of the used dictionary if the opened file does not specify one. The selected language is inserted in the cmd-strings at `%s`. + * default: `$LANG` or `en_US` +* `typo_style`: The style string with which misspellings should be highlighted when using the _full viewport_ method + * default: `fore:red` +* `check_tokens`: A table mapping all token names we consider for spellchecking to true + * default: `{[vis.lexers.STRING]=true, [vis.lexers.COMMENT]=true, [vis.lexers.DEFAULT]=true}` +* `disable_syntax_awareness`: Disable the syntax aware spellchecking and use always _full viewport_ + * default: `false` + +A possible configuration could look like this: + + spellcheck = require(...) + spellcheck.cmd = "aspell -l %s -a" + spellcheck.list_cmd = "aspell list -l %s -a" + spellcheck.default_lang = "de_DE" + +Changing language during runtime: + + :set spelllang en_US + diff --git a/.config/vis/plugins/vis-spellcheck/init.lua b/.config/vis/plugins/vis-spellcheck/init.lua new file mode 100644 index 0000000..60552a8 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/init.lua @@ -0,0 +1,6 @@ +-- Copyright (c) 2021 Florian Fischer. All rights reserved. +-- Use of this source code is governed by a MIT license found in the LICENSE file. +local source_str = debug.getinfo(1, 'S').source:sub(2) +local script_path = source_str:match('(.*/)') + +return dofile(script_path .. 'spellcheck.lua') diff --git a/.config/vis/plugins/vis-spellcheck/spellcheck.lua b/.config/vis/plugins/vis-spellcheck/spellcheck.lua new file mode 100644 index 0000000..403c985 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/spellcheck.lua @@ -0,0 +1,445 @@ +-- Copyright (c) 2017-2019 Florian Fischer. All rights reserved. +-- Use of this source code is governed by a MIT license found in the LICENSE file. +local spellcheck = {} +if os.getenv('LANG') then + spellcheck.default_lang = os.getenv('LANG'):sub(0, 5) +else + spellcheck.default_lang = 'es_US' +end + +spellcheck.get_lang = function() + if vis.win.file.spell_language then + return vis.win.file.spell_language + else + return spellcheck.default_lang + end +end + +local supress_stdout = ' >/dev/null' +local supress_stderr = ' 2>/dev/null' +local supress_output = supress_stdout .. supress_stderr +if os.execute('type enchant' .. supress_output) then + spellcheck.cmd = 'aspell -l %s -a' + spellcheck.list_cmd = 'aspell list -l %s -a' +elseif os.execute('type enchant-2' .. supress_output) then + spellcheck.cmd = 'enchant-2 -d %s -a' + spellcheck.list_cmd = 'enchant-2 -l -d %s' +elseif os.execute('type aspell' .. supress_output) then + spellcheck.cmd = 'aspell pipe -l %s' + spellcheck.list_cmd = 'aspell list -l %s' +elseif os.execute('type hunspell' .. supress_output) then + spellcheck.cmd = 'hunspell -d %s' + spellcheck.list_cmd = 'hunspell -l -d %s' +else + vis:info('WARNING: vis-spellcheck loaded but no spellchecker found') + return nil +end + +spellcheck.typo_style_id = 42 +spellcheck.typo_style = 'fore:red' +spellcheck.check_full_viewport = {} +spellcheck.disable_syntax_awareness = false + +spellcheck.check_tokens = { + [vis.lexers.STRING] = true, + [vis.lexers.COMMENT] = true, + [vis.lexers.DEFAULT] = true, +} + +-- Return nil or a string of misspelled word in a specific file range or text +-- by calling the spellchecker's list command. +-- If given a range we will use vis:pipe to get our typos from the spellchecker. +-- If a string was passed we call the spellchecker ourself and redirect its stdout +-- to a temporary file. See http://lua-users.org/lists/lua-l/2007-10/msg00189.html. +-- The returned string consists of each misspell followed by a newline. +local function get_typos(range_or_text) + local cmd = spellcheck.list_cmd:format(spellcheck.get_lang()) + local typos + if type(range_or_text) == 'string' then + local text = range_or_text + local tmp_name = os.tmpname() + local full_cmd = cmd .. '> ' .. tmp_name .. supress_stderr + local proc = assert(io.popen(full_cmd, 'w')) + proc:write(text) + -- this error detection may need lua5.2 + local success, _, exit_code = proc:close() + if not success then + vis:info('calling ' .. cmd .. ' failed (' .. exit_code .. ')') + return nil + end + + local tmp_file = assert(io.open(tmp_name, 'r')) + typos = tmp_file:read('*a') + tmp_file:close() + os.remove(tmp_name) + else + local range = range_or_text + local ret, so, _ = vis:pipe(vis.win.file, range, cmd) + + if ret ~= 0 then + vis:info('calling ' .. cmd .. ' failed (' .. ret .. ')') + return nil + end + typos = so + end + + return typos +end + +-- plugin global list of ignored typos +local ignored = {} + +-- Return an iterator over all not ignored typos and their positions in text. +-- The returned iterator is a self contained statefull iterator function closure. +-- Which will return the next typo and its start and finish in the text, starting by 1. +local function typo_iter(text, typos, ignored) -- luacheck: ignore ignored + local index = 1 + local unfiltered_iterator, iter_state = typos:gmatch('(.-)\n') + + -- see https://stackoverflow.com/questions/6705872/how-to-escape-a-variable-in-lua + local escape_lua_pattern + do + local matches = { + ['^'] = '%^', + ['$'] = '%$', + ['('] = '%(', + [')'] = '%)', + ['%'] = '%%', + ['.'] = '%.', + ['['] = '%[', + [']'] = '%]', + ['*'] = '%*', + ['+'] = '%+', + ['-'] = '%-', + ['?'] = '%?', + } + + escape_lua_pattern = function(s) + return (s:gsub('.', matches)) + end + end + + return function() + local typo + repeat + typo = unfiltered_iterator(iter_state) + until (not typo or (typo ~= '' and not ignored[typo])) + + if typo then + -- to prevent typos from being found in correct words before them + -- ("stuff stuf", "broken ok", ...) + -- we match typos only when they are enclosed in non-letter characters. + local start, finish = text:find('[%A]' .. escape_lua_pattern(typo) .. + '[%A]', index) + -- typo was not found by our pattern this means it must be either + -- the first or last word in the text + if not start then + -- check start of text + start = 1 + finish = #typo + -- typo is not the beginning must be the end of text + if text:sub(start, finish) ~= typo then + start = #text - #typo + 1 + finish = start + #typo - 1 + end + + if text:sub(start, finish) ~= typo then + vis:info(string.format( + 'can\'t find typo %s after %d. Please report this bug.', + typo, index)) + end + -- our pettern [%A]typo[%A] found it + else + start = start + 1 -- ignore leading non letter char + finish = finish - 1 -- ignore trailing non letter char + end + index = finish + + return typo, start, finish + end + end +end + +local last_viewport, last_data, last_typos = nil, '', '' + +vis.events.subscribe(vis.events.WIN_HIGHLIGHT, function(win) + if not spellcheck.check_full_viewport[win] or + not win:style_define(spellcheck.typo_style_id, spellcheck.typo_style) then + return + end + local viewport = win.viewport + local viewport_text = win.file:content(viewport) + + local typos + if last_viewport == viewport_text then + typos = last_typos + else + typos = get_typos(viewport) or '' + if not typos then + return + end + end + + for _, start, finish in typo_iter(viewport_text, typos, ignored) do + win:style(spellcheck.typo_style_id, viewport.start + start - 1, + viewport.start + finish - 1) + end + + last_viewport = viewport_text + last_typos = typos + -- TODO: think about returning true here + -- http://martanne.github.io/vis/doc/index.html#Events + -- The vis documentation states that a no further event handlers are called if one + -- returns not nil + -- Should we terminate the WIN_HIGHLIGHT event handling if we are ready highltighting + -- typos ? + return true +end) + +local wrapped_lex_funcs = {} + +local wrap_lex_func = function(old_lex_func) + local old_new_tokens = {} + + return function(lexer, data, index, redrawtime_max) + local tokens, timedout = old_lex_func(lexer, data, index, redrawtime_max) + + -- quit early if the lexer already took to long + -- TODO: investigate further if timedout is actually set by the lexer. + -- As I understand lpeg.match used by lexer.lex timedout will always be nil + if timedout then + return tokens, timedout + end + + local new_tokens = {} + + local typos + if last_data ~= data then + typos = get_typos(data) + if not typos then + return tokens, timedout + end + last_data = data + else + return old_new_tokens + end + + local i = 1 + for _, typo_start, typo_end in typo_iter(data, typos, ignored) do + repeat + -- no tokens left + if i > #tokens - 1 then + break + end + + local token_type = tokens[i] + local token_start = (tokens[i - 1] or 1) - 1 + local token_end = tokens[i + 1] + + -- the current token ends before our typo -> append to new stream + -- or is not spellchecked + if token_end < typo_start or not spellcheck.check_tokens[token_type] then + table.insert(new_tokens, token_type) + table.insert(new_tokens, token_end) + + -- done with this token -> advance token stream + i = i + 2 + -- typo and checked token overlap + else + local pre_typo_end = typo_start - 1 + -- unchanged token part before typo + if pre_typo_end > token_start then + table.insert(new_tokens, token_type) + table.insert(new_tokens, pre_typo_end + 1) + end + + -- highlight typo + table.insert(new_tokens, vis.lexers.ERROR) + -- the typo spans multiple tokens + if token_end < typo_end then + table.insert(new_tokens, token_end + 1) + i = i + 2 + else + table.insert(new_tokens, typo_end + 1) + end + end + until (not token_end or token_end >= typo_end) + end + + -- add tokens left after we handled all typos + for i = i, #tokens, 1 do -- luacheck: ignore i + table.insert(new_tokens, tokens[i]) + end + + old_new_tokens = new_tokens + return new_tokens, timedout + end +end + +local enable_spellcheck = function() + -- prevent wrapping the lex function multiple times + if wrapped_lex_funcs[vis.win] then + return + end + + if not spellcheck.disable_syntax_awareness and vis.win.syntax and + vis.lexers.load then + local lexer = vis.lexers.load(vis.win.syntax, nil, true) + if lexer and lexer.lex then + local old_lex_func = lexer.lex + wrapped_lex_funcs[vis.win] = old_lex_func + lexer.lex = wrap_lex_func(old_lex_func) + -- reset last data to enforce new highlighting + last_data = '' + return + end + end + + -- fallback check spellcheck the full viewport + spellcheck.check_full_viewport[vis.win] = true +end + +local is_spellcheck_enabled = function() + return spellcheck.check_full_viewport[vis.win] or wrapped_lex_funcs[vis.win] +end + +vis:map(vis.modes.NORMAL, 'e', function() + enable_spellcheck() +end, 'Enable spellchecking in the current window') + +local disable_spellcheck = function() + local old_lex_func = wrapped_lex_funcs[vis.win] + if old_lex_func then + local lexer = vis.lexers.load(vis.win.syntax, nil, true) + lexer.lex = old_lex_func + wrapped_lex_funcs[vis.win] = nil + else + spellcheck.check_full_viewport[vis.win] = nil + end +end + +vis:map(vis.modes.NORMAL, 'd', function() + disable_spellcheck() + -- force new highlight + vis.win:draw() +end, 'Disable spellchecking in the current window') + +-- toggle spellchecking on +-- is used by some word processors (LibreOffice) for spellchecking +-- Thanks to @leorosa for the hint. +vis:map(vis.modes.NORMAL, '', function() + if not is_spellcheck_enabled() then + enable_spellcheck() + else + disable_spellcheck() + vis.win:draw() + end + return 0 +end, 'Toggle spellchecking in the current window') + +vis:map(vis.modes.NORMAL, 'w', function() + local win = vis.win + local file = win.file + local pos = win.selection.pos + if not pos then + return + end + local range = file:text_object_word(pos); + if not range then + return + end + if range.start == range.finish then + return + end + + local cmd = spellcheck.cmd:format(spellcheck.get_lang()) + local ret, so, se = vis:pipe(win.file, range, cmd) + if ret ~= 0 then + vis:message('calling ' .. cmd .. ' failed (' .. se .. ')') + return false + end + + local answer_line = so:match('.-\n(.-)\n.*') + if not answer_line then + return false + end + + local suggestions + local first_char = answer_line:sub(0, 1) + if first_char == '*' then + vis:info(file:content(range) .. ' is correctly spelled') + return true + elseif first_char == '#' then + vis:info('No corrections available for ' .. file:content(range)) + return false + elseif first_char == '&' then + suggestions = answer_line:match('& %S+ %d+ %d+: (.*)') + else + vis:info('Unknown answer: ' .. answer_line) + return false + end + + -- select a correction + cmd = 'printf "' .. suggestions:gsub(', ', '\\n') .. '\\n" | vis-menu' + local status, correction = vis:pipe(file, {start = 0, finish = 0}, cmd) + if status == 0 then + -- trim correction + correction = correction:match('^%s*(.-)%s*$') + win.file:delete(range) + win.file:insert(range.start, correction) + end + + win.selection.pos = pos + + win:draw() + + return 0 +end, 'Correct misspelled word') + +vis:map(vis.modes.NORMAL, 'i', function() + local win = vis.win + local file = win.file + local pos = win.selection.pos + if not pos then + return + end + local range = file:text_object_word(pos); + if not range then + return + end + if range.start == range.finish then + return + end + + ignored[file:content(range)] = true + + -- Check if we use our syntax aware spellcheck lex-closure + -- We must rebuild the closure because it captures ignored + -- to include the new addition + local old_lex_func = wrapped_lex_funcs[vis.win] + if old_lex_func then + local lexer = vis.lexers.load(vis.win.syntax, nil, true) + lexer.lex = wrap_lex_func(old_lex_func) + -- reset last data to enforce new highlighting + last_data = '' + end + + win:draw() + return 0 +end, 'Ignore misspelled word') + +vis:option_register('spelllang', 'string', function(value) + vis.win.file.spell_language = value + vis:info('Spellchecking language is now ' .. value) + -- force new highlight for full viewport + last_viewport = nil + -- force new highlight for syntax aware + last_data = nil + return true +end, 'The language used for spellchecking') + +vis:command_register('spelllang', function() + vis:info('The spellchecking language is ' .. spellcheck.get_lang()) +end, 'Print the language used for spellchecking') + +return spellcheck diff --git a/.config/vis/plugins/vis-spellcheck/tools/check-format b/.config/vis/plugins/vis-spellcheck/tools/check-format new file mode 100755 index 0000000..392d023 --- /dev/null +++ b/.config/vis/plugins/vis-spellcheck/tools/check-format @@ -0,0 +1,8 @@ +#!/bin/sh + +LUA_FILE=$1 +lua-format "${LUA_FILE}" > "${LUA_FILE}.fmt" +diff "${LUA_FILE}" "${LUA_FILE}.fmt" +RET=$? +rm "${LUA_FILE}.fmt" +exit ${RET} diff --git a/.config/vis/plugins/vis-vim-compatible.lua b/.config/vis/plugins/vis-vim-compatible.lua new file mode 100644 index 0000000..c63b5dd --- /dev/null +++ b/.config/vis/plugins/vis-vim-compatible.lua @@ -0,0 +1,20 @@ +function vimMotions() + -- make `_` move to the first non-whitespace character + vis:command('map! normal _ ^') + +end + +function vimCommands() + -- support :cq + vis:command_register("cq", function(argv, force, win, selection, range) + vis:command("qall") + os.exit(-1) + end) +end + +function vimCompatibilityInit() + vimMotions() + vimCommands() +end + +vimCompatibilityInit() diff --git a/.config/vis/themes/base16-grayscale-dark.lua b/.config/vis/themes/base16-grayscale-dark.lua new file mode 100644 index 0000000..c40cc01 --- /dev/null +++ b/.config/vis/themes/base16-grayscale-dark.lua @@ -0,0 +1,61 @@ +-- Base16-vis (https://github.com/pshevtsov/base16-vis) +-- by Petr Shevtsov +-- Grayscale Dark scheme by Alexandre Gavioli (https://github.com/Alexx2/) + +local lexers = vis.lexers + +local colors = { + ['base00'] = '#101010', + ['base01'] = '#252525', + ['base02'] = '#464646', + ['base03'] = '#525252', + ['base04'] = '#ababab', + ['base05'] = '#b9b9b9', + ['base06'] = '#e3e3e3', + ['base07'] = '#f7f7f7', + ['base08'] = '#7c7c7c', + ['base09'] = '#999999', + ['base0A'] = '#a0a0a0', + ['base0B'] = '#8e8e8e', + ['base0C'] = '#868686', + ['base0D'] = '#686868', + ['base0E'] = '#747474', + ['base0F'] = '#5e5e5e', + } + + local fg = ',fore:'..colors.base05..',' + local bg = ',back:'..colors.base00..',' + + lexers.STYLE_DEFAULT = bg..fg + lexers.STYLE_NOTHING = bg + lexers.STYLE_CLASS = 'fore:'..colors.base0A + lexers.STYLE_COMMENT = 'fore:'..colors.base03..',italics' + lexers.STYLE_CONSTANT = 'fore:'..colors.base09 + lexers.STYLE_DEFINITION = 'fore:'..colors.base0E + lexers.STYLE_ERROR = 'fore:'..colors.base08..',italics' + lexers.STYLE_FUNCTION = 'fore:'..colors.base0D + lexers.STYLE_KEYWORD = 'fore:'..colors.base0E + lexers.STYLE_LABEL = 'fore:'..colors.base0A + lexers.STYLE_NUMBER = 'fore:'..colors.base09 + lexers.STYLE_OPERATOR = 'fore:'..colors.base05 + lexers.STYLE_REGEX = 'fore:'..colors.base0C + lexers.STYLE_STRING = 'fore:'..colors.base0B + lexers.STYLE_PREPROCESSOR = 'fore:'..colors.base0A + lexers.STYLE_TAG = 'fore:'..colors.base0A + lexers.STYLE_TYPE = 'fore:'..colors.base0A + lexers.STYLE_VARIABLE = 'fore:'..colors.base0D + lexers.STYLE_WHITESPACE = 'fore:'..colors.base02 + lexers.STYLE_EMBEDDED = 'fore:'..colors.base0F + lexers.STYLE_IDENTIFIER = 'fore:'..colors.base08 + + lexers.STYLE_LINENUMBER = 'fore:'..colors.base02..',back:'..colors.base00 + lexers.STYLE_CURSOR = 'fore:'..colors.base00..',back:'..colors.base05 + lexers.STYLE_CURSOR_PRIMARY = 'fore:'..colors.base00..',back:'..colors.base05 + lexers.STYLE_CURSOR_LINE = 'back:'..colors.base01 + lexers.STYLE_COLOR_COLUMN = 'back:'..colors.base01 + lexers.STYLE_SELECTION = 'back:'..colors.base02 + lexers.STYLE_STATUS = 'fore:'..colors.base04..',back:'..colors.base01 + lexers.STYLE_STATUS_FOCUSED = 'fore:'..colors.base09..',back:'..colors.base01 + lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT + lexers.STYLE_INFO = 'fore:default,back:default,bold' + lexers.STYLE_EOF = '' diff --git a/.config/vis/themes/dark-16.lua b/.config/vis/themes/dark-16.lua new file mode 100644 index 0000000..fd7e1db --- /dev/null +++ b/.config/vis/themes/dark-16.lua @@ -0,0 +1,37 @@ +-- Eight-color scheme +local lexers = vis.lexers +-- dark +lexers.STYLE_DEFAULT ='back:black,fore:white' +lexers.STYLE_NOTHING = 'back:black' +lexers.STYLE_CLASS = 'fore:yellow,bold' +lexers.STYLE_COMMENT = 'fore:blue,bold' +lexers.STYLE_CONSTANT = 'fore:cyan,bold' +lexers.STYLE_DEFINITION = 'fore:blue,bold' +lexers.STYLE_ERROR = 'fore:red,italics' +lexers.STYLE_FUNCTION = 'fore:blue,bold' +lexers.STYLE_KEYWORD = 'fore:yellow,bold' +lexers.STYLE_LABEL = 'fore:green,bold' +lexers.STYLE_NUMBER = 'fore:red,bold' +lexers.STYLE_OPERATOR = 'fore:cyan,bold' +lexers.STYLE_REGEX = 'fore:green,bold' +lexers.STYLE_STRING = 'fore:red,bold' +lexers.STYLE_PREPROCESSOR = 'fore:magenta,bold' +lexers.STYLE_TAG = 'fore:red,bold' +lexers.STYLE_TYPE = 'fore:green,bold' +lexers.STYLE_VARIABLE = 'fore:blue,bold' +lexers.STYLE_WHITESPACE = '' +lexers.STYLE_EMBEDDED = 'back:blue,bold' +lexers.STYLE_IDENTIFIER = 'fore:white' + +lexers.STYLE_LINENUMBER = 'fore:white' +lexers.STYLE_LINENUMBER_CURSOR = lexers.STYLE_LINENUMBER +lexers.STYLE_CURSOR = 'reverse' +lexers.STYLE_CURSOR_PRIMARY = lexers.STYLE_CURSOR..',fore:yellow' +lexers.STYLE_CURSOR_LINE = 'underlined' +lexers.STYLE_COLOR_COLUMN = 'back:red' +lexers.STYLE_SELECTION = 'back:white' +lexers.STYLE_STATUS = 'reverse' +lexers.STYLE_STATUS_FOCUSED = 'reverse,bold' +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' diff --git a/.config/vis/themes/default-16.lua b/.config/vis/themes/default-16.lua new file mode 120000 index 0000000..51192b4 --- /dev/null +++ b/.config/vis/themes/default-16.lua @@ -0,0 +1 @@ +dark-16.lua \ No newline at end of file diff --git a/.config/vis/themes/default-256.lua b/.config/vis/themes/default-256.lua new file mode 120000 index 0000000..e12776c --- /dev/null +++ b/.config/vis/themes/default-256.lua @@ -0,0 +1 @@ +zenburn.lua \ No newline at end of file diff --git a/.config/vis/themes/light-16.lua b/.config/vis/themes/light-16.lua new file mode 100644 index 0000000..53d0e45 --- /dev/null +++ b/.config/vis/themes/light-16.lua @@ -0,0 +1,37 @@ +-- Eight-color scheme +local lexers = vis.lexers +-- light +lexers.STYLE_DEFAULT = 'back:white,fore:black' +lexers.STYLE_NOTHING = 'back:white' +lexers.STYLE_CLASS = 'fore:yellow,bold' +lexers.STYLE_COMMENT = 'fore:blue,bold' +lexers.STYLE_CONSTANT = 'fore:cyan,bold' +lexers.STYLE_DEFINITION = 'fore:blue,bold' +lexers.STYLE_ERROR = 'fore:red,italics' +lexers.STYLE_FUNCTION = 'fore:blue,bold' +lexers.STYLE_KEYWORD = 'fore:yellow,bold' +lexers.STYLE_LABEL = 'fore:green,bold' +lexers.STYLE_NUMBER = 'fore:red,bold' +lexers.STYLE_OPERATOR = 'fore:cyan,bold' +lexers.STYLE_REGEX = 'fore:green,bold' +lexers.STYLE_STRING = 'fore:red,bold' +lexers.STYLE_PREPROCESSOR = 'fore:magenta,bold' +lexers.STYLE_TAG = 'fore:red,bold' +lexers.STYLE_TYPE = 'fore:green,bold' +lexers.STYLE_VARIABLE = 'fore:blue,bold' +lexers.STYLE_WHITESPACE = '' +lexers.STYLE_EMBEDDED = 'back:blue,bold' +lexers.STYLE_IDENTIFIER = 'fore:black' + +lexers.STYLE_LINENUMBER = 'fore:black' +lexers.STYLE_LINENUMBER_CURSOR = lexers.STYLE_LINENUMBER +lexers.STYLE_CURSOR = 'reverse' +lexers.STYLE_CURSOR_PRIMARY = lexers.STYLE_CURSOR..',fore:yellow' +lexers.STYLE_CURSOR_LINE = 'underlined' +lexers.STYLE_COLOR_COLUMN = 'back:red' +lexers.STYLE_SELECTION = 'back:black' +lexers.STYLE_STATUS = 'reverse' +lexers.STYLE_STATUS_FOCUSED = 'reverse,bold' +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' diff --git a/.config/vis/themes/solarized.lua b/.config/vis/themes/solarized.lua new file mode 100644 index 0000000..7f40858 --- /dev/null +++ b/.config/vis/themes/solarized.lua @@ -0,0 +1,69 @@ +-- Solarized color codes Copyright (c) 2011 Ethan Schoonover +local lexers = vis.lexers + +local colors = { + base03 = '#002b36', + base02 = '#073642', + base01 = '#586e75', + base00 = '#657b83', + base0 = '#839496', + base1 = '#93a1a1', + base2 = '#eee8d5', + base3 = '#fdf6e3', + yellow = '#b58900', + orange = '#cb4b16', + red = '#dc322f', + magenta = '#d33682', + violet = '#6c71c4', + blue = '#268bd2', + cyan = '#2aa198', + green = '#859900', +} + +lexers.colors = colors +-- dark +local fg = ',fore:'..colors.base0..',' +local bg = ',back:'..colors.base03..',' +-- light +-- local fg = ',fore:'..colors.base03..',' +-- local bg = ',back:'..colors.base3..',' +-- solarized term +-- local fg = ',fore:default,' +-- local bg = ',back:default,' + + +lexers.STYLE_DEFAULT = bg..fg +lexers.STYLE_NOTHING = bg +lexers.STYLE_CLASS = 'fore:yellow' +lexers.STYLE_COMMENT = 'fore:'..colors.base01 +lexers.STYLE_CONSTANT = 'fore:'..colors.cyan +lexers.STYLE_DEFINITION = 'fore:'..colors.blue +lexers.STYLE_ERROR = 'fore:'..colors.red..',italics' +lexers.STYLE_FUNCTION = 'fore:'..colors.blue +lexers.STYLE_KEYWORD = 'fore:'..colors.green +lexers.STYLE_LABEL = 'fore:'..colors.green +lexers.STYLE_NUMBER = 'fore:'..colors.cyan +lexers.STYLE_OPERATOR = 'fore:'..colors.green +lexers.STYLE_REGEX = 'fore:green' +lexers.STYLE_STRING = 'fore:'..colors.cyan +lexers.STYLE_PREPROCESSOR = 'fore:'..colors.orange +lexers.STYLE_TAG = 'fore:'..colors.red +lexers.STYLE_TYPE = 'fore:'..colors.yellow +lexers.STYLE_VARIABLE = 'fore:'..colors.blue +lexers.STYLE_WHITESPACE = 'fore:'..colors.base01 +lexers.STYLE_EMBEDDED = 'back:blue' +lexers.STYLE_IDENTIFIER = fg + +lexers.STYLE_LINENUMBER = 'fore:'..colors.base00..',back:'..colors.base02 +lexers.STYLE_LINENUMBER_CURSOR = 'back:'..colors.base00..',fore:'..colors.base02 +lexers.STYLE_CURSOR = 'fore:'..colors.base03..',back:'..colors.base0 +lexers.STYLE_CURSOR_PRIMARY = lexers.STYLE_CURSOR..',back:yellow' +lexers.STYLE_CURSOR_LINE = 'back:'..colors.base02 +lexers.STYLE_COLOR_COLUMN = 'back:'..colors.base02 +-- lexers.STYLE_SELECTION = 'back:'..colors.base02 +lexers.STYLE_SELECTION = 'back:white' +lexers.STYLE_STATUS = 'back:'..colors.base00..',fore:'..colors.base02 +lexers.STYLE_STATUS_FOCUSED = 'back:'..colors.base1..',fore:'..colors.base02 +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = 'fore:'..colors.base01 diff --git a/.config/vis/themes/zenburn.lua b/.config/vis/themes/zenburn.lua new file mode 100644 index 0000000..f60e900 --- /dev/null +++ b/.config/vis/themes/zenburn.lua @@ -0,0 +1,39 @@ +-- A poor imitation of the original Vim colourscheme which can be +-- found at https://github.com/jnurmine/Zenburn + +local lexers = vis.lexers + +lexers.STYLE_DEFAULT = 'fore:#d7d7d7,back:#3a3a3a' +lexers.STYLE_NOTHING = '' +lexers.STYLE_CLASS = '' +lexers.STYLE_COMMENT = 'fore:#87af87' +lexers.STYLE_CONSTANT = 'fore:#d7afaf,bold' +lexers.STYLE_DEFINITION = 'fore:#ffd7af,bold' +lexers.STYLE_ERROR = 'fore:#87d7af,back:#303030,bold' +lexers.STYLE_FUNCTION = 'fore:#d7d7af' +lexers.STYLE_KEYWORD = 'fore:#afaf87,bold' +lexers.STYLE_LABEL = 'fore:#d7d7af' +lexers.STYLE_NUMBER = 'fore:#87d7d7' +lexers.STYLE_OPERATOR = 'fore:#ffffd7' +lexers.STYLE_REGEX = '' +lexers.STYLE_STRING = 'fore:#d78787' +lexers.STYLE_PREPROCESSOR = 'fore:#ffd7af,bold' +lexers.STYLE_TAG = 'fore:#d7afaf,bold' +lexers.STYLE_TYPE = 'fore:#d7d7af,bold' +lexers.STYLE_VARIABLE = '' +lexers.STYLE_WHITESPACE = '' +lexers.STYLE_EMBEDDED = '' +lexers.STYLE_IDENTIFIER = '' + +lexers.STYLE_LINENUMBER = 'fore:#585858' +lexers.STYLE_LINENUMBER_CURSOR = 'fore:#666666' +lexers.STYLE_CURSOR = 'back:#585858' +lexers.STYLE_CURSOR_PRIMARY = 'fore:#1c1c1c,back:#87afaf,bold' +lexers.STYLE_CURSOR_LINE = 'back:#444444' +lexers.STYLE_COLOR_COLUMN = 'back:#444444' +lexers.STYLE_SELECTION = 'back:#5f875f' +lexers.STYLE_STATUS = 'back:#262626,fore:#87af87' +lexers.STYLE_STATUS_FOCUSED = 'back:#303030,fore:#afaf87,bold' +lexers.STYLE_SEPARATOR = '' +lexers.STYLE_INFO = '' +lexers.STYLE_EOF = 'fore:#585858' diff --git a/.config/vis/vis.lua b/.config/vis/vis.lua new file mode 100644 index 0000000..699d2e2 --- /dev/null +++ b/.config/vis/vis.lua @@ -0,0 +1,306 @@ +--- +-- Vis Lua plugin API standard library +-- @module vis + +--- +-- @type Vis + +--- Map a new operator. +-- +-- Sets up a mapping in normal, visual and operator pending mode. +-- The operator function will receive the @{File}, @{Range} and position +-- to operate on and is expected to return the new cursor position. +-- +-- @tparam string key the key to associate with the new operator +-- @tparam function operator the operator logic implemented as Lua function +-- @tparam[opt] string help the single line help text as displayed in `:help` +-- @treturn bool whether the new operator could be installed +-- @usage +-- vis:operator_new("gq", function(file, range, pos) +-- local status, out, err = vis:pipe(file, range, "fmt") +-- if not status then +-- vis:info(err) +-- else +-- file:delete(range) +-- file:insert(range.start, out) +-- end +-- return range.start -- new cursor location +-- end, "Formating operator, filter range through fmt(1)") +-- +vis.operator_new = function(vis, key, operator, help) + local id = vis:operator_register(operator) + if id < 0 then + return false + end + local binding = function() + vis:operator(id) + end + vis:map(vis.modes.NORMAL, key, binding, help) + vis:map(vis.modes.VISUAL, key, binding, help) + vis:map(vis.modes.OPERATOR_PENDING, key, binding, help) + return true +end + +--- Map a new motion. +-- +-- Sets up a mapping in normal, visual and operator pending mode. +-- The motion function will receive the @{Window} and an initial position +-- (in bytes from the start of the file) as argument and is expected to +-- return the resulting position. +-- @tparam string key the key to associate with the new mption +-- @tparam function motion the motion logic implemented as Lua function +-- @tparam[opt] string help the single line help text as displayed in `:help` +-- @treturn bool whether the new motion could be installed +-- @usage +-- vis:motion_new("", function(win, pos) +-- return pos+1 +-- end, "Advance to next byte") +-- +vis.motion_new = function(vis, key, motion, help) + local id = vis:motion_register(motion) + if id < 0 then + return false + end + local binding = function() + vis:motion(id) + end + vis:map(vis.modes.NORMAL, key, binding, help) + vis:map(vis.modes.VISUAL, key, binding, help) + vis:map(vis.modes.OPERATOR_PENDING, key, binding, help) + return true +end + +--- Map a new text object. +-- +-- Sets up a mapping in visual and operator pending mode. +-- The text object function will receive the @{Window} and an initial +-- position(in bytes from the start of the file) as argument and is +-- expected to return the resulting range or `nil`. +-- @tparam string key the key associated with the new text object +-- @tparam function textobject the text object logic implemented as Lua function +-- @tparam[opt] string help the single line help text as displayed in `:help` +-- @treturn bool whether the new text object could be installed +-- @usage +-- vis:textobject_new("", function(win, pos) +-- return pos, pos+1 +-- end, "Single byte text object") +-- +vis.textobject_new = function(vis, key, textobject, help) + local id = vis:textobject_register(textobject) + if id < 0 then + return false + end + local binding = function() + vis:textobject(id) + end + vis:map(vis.modes.VISUAL, key, binding, help) + vis:map(vis.modes.OPERATOR_PENDING, key, binding, help) + return true +end + +--- Check whether a Lua module exists +-- +-- Checks whether a subsequent @{require} call will succeed. +-- @tparam string name the module name to check +-- @treturn bool whether the module was found +vis.module_exist = function(_, name) + for _, searcher in ipairs(package.searchers or package.loaders) do + local loader = searcher(name) + if type(loader) == 'function' then + return true + end + end + return false +end + +vis.lexers = {} + +if not vis:module_exist('lpeg') then + vis:info('WARNING: could not find lpeg module') +elseif not vis:module_exist('lexer') then + vis:info('WARNING: could not find lexer module') +else + vis.lexers = require('lexer') + vis.lpeg = require('lpeg') +end + +--- Events. +-- +-- User scripts can subscribe Lua functions to certain events. Multiple functions +-- can be associated with the same event. They will be called in the order they were +-- registered. The first function which returns a non `nil` value terminates event +-- propagation. The remaining event handler will not be called. +-- +-- Keep in mind that the editor is blocked while the event handlers +-- are being executed, avoid long running tasks. +-- +-- @section Events + +--- Event names. +--- @table events +local events = { + FILE_CLOSE = "Event::FILE_CLOSE", -- see @{file_close} + FILE_OPEN = "Event::FILE_OPEN", -- see @{file_open} + FILE_SAVE_POST = "Event::FILE_SAVE_POST", -- see @{file_save_post} + FILE_SAVE_PRE = "Event::FILE_SAVE_PRE", -- see @{file_save_pre} + INIT = "Event::INIT", -- see @{init} + INPUT = "Event::INPUT", -- see @{input} + QUIT = "Event::QUIT", -- see @{quit} + START = "Event::START", -- see @{start} + WIN_CLOSE = "Event::WIN_CLOSE", -- see @{win_close} + WIN_HIGHLIGHT = "Event::WIN_HIGHLIGHT", -- see @{win_highlight} + WIN_OPEN = "Event::WIN_OPEN", -- see @{win_open} + WIN_STATUS = "Event::WIN_STATUS", -- see @{win_status} + TERM_CSI = "Event::TERM_CSI", -- see @{term_csi} +} + +events.file_close = function(...) events.emit(events.FILE_CLOSE, ...) end +events.file_open = function(...) events.emit(events.FILE_OPEN, ...) end +events.file_save_post = function(...) events.emit(events.FILE_SAVE_POST, ...) end +events.file_save_pre = function(...) return events.emit(events.FILE_SAVE_PRE, ...) end +events.init = function(...) events.emit(events.INIT, ...) end +events.input = function(...) return events.emit(events.INPUT, ...) end +events.quit = function(...) events.emit(events.QUIT, ...) end +events.start = function(...) events.emit(events.START, ...) end +events.win_close = function(...) events.emit(events.WIN_CLOSE, ...) end +events.win_highlight = function(...) events.emit(events.WIN_HIGHLIGHT, ...) end +events.win_open = function(...) events.emit(events.WIN_OPEN, ...) end +events.win_status = function(...) events.emit(events.WIN_STATUS, ...) end +events.term_csi = function(...) events.emit(events.TERM_CSI, ...) end + +local handlers = {} + +--- Subscribe to an event. +-- +-- Register an event handler. +-- @tparam string event the event name +-- @tparam function handler the event handler +-- @tparam[opt] int index the index at which to insert the handler (1 is the highest priority) +-- @usage +-- vis.events.subscribe(vis.events.FILE_SAVE_PRE, function(file, path) +-- -- do something useful +-- return true +-- end) +events.subscribe = function(event, handler, index) + if not event then error("Invalid event name") end + if type(handler) ~= 'function' then error("Invalid event handler") end + if not handlers[event] then handlers[event] = {} end + events.unsubscribe(event, handler) + table.insert(handlers[event], index or #handlers[event]+1, handler) +end + +--- Unsubscribe from an event. +-- +-- Remove a registered event handler. +-- @tparam string event the event name +-- @tparam function handler the event handler to unsubscribe +-- @treturn bool whether the handler was successfully removed +events.unsubscribe = function(event, handler) + local h = handlers[event] + if not h then return end + for i = 1, #h do + if h[i] == handler then + table.remove(h, i) + return true + end + end + return false +end + +--- Generate event. +-- +-- Invokes all event handlers in the order they were registered. +-- Passes all arguments to the handler. The first handler which returns a non `nil` +-- value terminates the event propagation. The other handlers will not be called. +-- +-- @tparam string event the event name +-- @tparam ... ... the remaining paramters are passed on to the handler +events.emit = function(event, ...) + local h = handlers[event] + if not h then return end + for i = 1, #h do + local ret = h[i](...) + if type(ret) ~= 'nil' then return ret end + end +end + +vis.events = events + +--- +-- @type Window + +--- The file type associated with this window. +-- @tfield string syntax the syntax lexer name or `nil` if unset + +--- Change syntax lexer to use for this window +-- @function set_syntax +-- @tparam string syntax the syntax lexer name or `nil` to disable syntax highlighting +-- @treturn bool whether the lexer could be changed +vis.types.window.set_syntax = function(win, syntax) + + local lexers = vis.lexers + + win:style_define(win.STYLE_DEFAULT, lexers.STYLE_DEFAULT or '') + win:style_define(win.STYLE_CURSOR, lexers.STYLE_CURSOR or '') + win:style_define(win.STYLE_CURSOR_PRIMARY, lexers.STYLE_CURSOR_PRIMARY or '') + win:style_define(win.STYLE_CURSOR_LINE, lexers.STYLE_CURSOR_LINE or '') + win:style_define(win.STYLE_SELECTION, lexers.STYLE_SELECTION or '') + win:style_define(win.STYLE_LINENUMBER, lexers.STYLE_LINENUMBER or '') + win:style_define(win.STYLE_LINENUMBER_CURSOR, lexers.STYLE_LINENUMBER_CURSOR or '') + win:style_define(win.STYLE_COLOR_COLUMN, lexers.STYLE_COLOR_COLUMN or '') + win:style_define(win.STYLE_STATUS, lexers.STYLE_STATUS or '') + win:style_define(win.STYLE_STATUS_FOCUSED, lexers.STYLE_STATUS_FOCUSED or '') + win:style_define(win.STYLE_SEPARATOR, lexers.STYLE_SEPARATOR or '') + win:style_define(win.STYLE_INFO, lexers.STYLE_INFO or '') + win:style_define(win.STYLE_EOF, lexers.STYLE_EOF or '') + + if syntax == nil or syntax == 'off' then + win.syntax = nil + return true + end + + if not lexers.load then return false end + local lexer = lexers.load(syntax) + if not lexer then return false end + + for token_name, id in pairs(lexer._TOKENSTYLES) do + local style = lexers['STYLE_'..string.upper(token_name)] or lexer._EXTRASTYLES[token_name] + win:style_define(id, style) + end + + win.syntax = syntax + return true +end + +--- +-- @type File + +--- Check whether LPeg pattern matches at a given file position. +-- @function match_at +-- @param pattern the LPeg pattern +-- @tparam int pos the absolute file position which should be tested for a match +-- @tparam[opt] int horizon the number of bytes around `pos` to consider (defaults to 1K) +-- @treturn int start,end the range of the matched region or `nil` +vis.types.file.match_at = function(file, pattern, pos, horizon) + horizon = horizon or 1024 + local lpeg = vis.lpeg + if not lpeg then return nil end + local before, after = pos - horizon, pos + horizon + if before < 0 then before = 0 end + local data = file:content(before, after - before) + local string_pos = pos - before + 1 + + local I = lpeg.Cp() + local p = lpeg.P{ I * pattern * I + 1 * lpeg.V(1) } + local s, e = 1 + while true do + s, e = p:match(data, s) + if not s then return nil end + if s <= string_pos and string_pos < e then + return before + s - 1, before + e - 1 + end + s = e + end +end + +require('vis-std') diff --git a/.config/vis/visrc.lua b/.config/vis/visrc.lua new file mode 100644 index 0000000..0887c9f --- /dev/null +++ b/.config/vis/visrc.lua @@ -0,0 +1,52 @@ +--- Cargar el módulo vis estandar +require('vis') +require('plugins/filetype') +require('plugins/vis-vim-compatible') + +--- Plugins +require('plugins/vis-filetype-settings') +require('plugins/vis-vim-compatible') + +--- Opciones de configuración global +vis.events.subscribe(vis.events.INIT, function() + vis:command('set theme base16-grayscale-dark') + vis:command('set autoindent') + vis:command('set ignorecase') + vis:command('set tabwidth 4') + vis:command('set expandtab') +end) + +--- Opciones de scripts +settings = { + filetype = settings +} +--- Opciones de configuracion por ventana +vis.events.subscribe(vis.events.WIN_OPEN, function(win) + vis:command('set relativenumbers') + + if vis.win.syntax == 'bash' then + vis:command('set colorcolumn 110') +end + +--- Abrir plantillas LATEX +vis:map(vis.modes.NORMAL, "", ':e $HOME/.plantillas_latex/membrete.tex') +vis:map(vis.modes.NORMAL, "", ':e $HOME/.plantillas_latex/normal.tex') + +--- Abrir explorador de vis +vis:map(vis.modes.NORMAL, "", + function() + vis:command(':e $HOME') +end) + +--- Compilar archivos de LATEX +vis:map(vis.modes.NORMAL, "", + function() + vis:command('!lualatex $vis_filename') +end) + +--- Abrir PDF +vis:map(vis.modes.NORMAL, "", + function() + vis:command('!vis-open -p :e *.pdf | xargs zathura &') + end) +end) diff --git a/.config/ytfzf/conf.sh b/.config/ytfzf/conf.sh new file mode 100755 index 0000000..ed4b659 --- /dev/null +++ b/.config/ytfzf/conf.sh @@ -0,0 +1,13 @@ +# This is a sample config file, refer to ytfzf(5) for more information + +# In the previous version of ytfzf this file had all the examples, with all defaults set, +# this has been changed because it made it impossible for us to change default values that were broken or causing bugs, +# as everyone used the default configuration file. +# we are now going to only have this sample config file, and the ytfzf(5) manual, which has explanation of every variable and function that can be set. + +#a sample config below: + +# user ffplay instead of mpv +video_player () { + ffplayer.sh "$@" +} diff --git a/.config/zathura/zathurarc b/.config/zathura/zathurarc new file mode 100644 index 0000000..10696ec --- /dev/null +++ b/.config/zathura/zathurarc @@ -0,0 +1,36 @@ +# Base16 Grayscale Dark +# Author: Alexandre Gavioli (https://github.com/Alexx2/) + +set font "Hack Nerd Font 8" +set default-bg "#101010" +set default-fg "#252525" + +set statusbar-fg "#ababab" +set statusbar-bg "#464646" + +set inputbar-bg "#101010" +set inputbar-fg "#f7f7f7" + +set notification-bg "#101010" +set notification-fg "#f7f7f7" + +set notification-error-bg "#101010" +set notification-error-fg "#7c7c7c" + +set notification-warning-bg "#101010" +set notification-warning-fg "#7c7c7c" + +set highlight-color "#a0a0a0" +set highlight-active-color "#686868" + +set completion-bg "#252525" +set completion-fg "#686868" + +set completion-highlight-fg "#f7f7f7" +set completion-highlight-bg "#686868" + +set recolor-lightcolor "#101010" +set recolor-darkcolor "#e3e3e3" + +set recolor "true" +set recolor-keephue "false" diff --git a/.crontab b/.crontab new file mode 100644 index 0000000..48884cb --- /dev/null +++ b/.crontab @@ -0,0 +1,19 @@ +######################################################################### +#Minuto (0-59) # +# | Hora (0-23) # +# | | Día del mes (1-31) # +# | | | Mes (1-12) # +# | | | | Día de la semana (0-6 donde 0=Domingo) # +# | | | | | Comandos # +# * * * * * * # +######################################################################### +# Anualmente -> 0 0 1 1 * +# Mensualmente -> 0 0 1 * * +# Semanalmente -> 0 0 * * 0 +# Diariamente -> 0 0 * * * +# Cada hora -> 0 * * * * + +#DISPLAY=:0.0 + +# Notificaciones de actualizaciones del sistema +00 9 * * * "$HOME"/.local/bin/updates-void diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..5c6a9ab --- /dev/null +++ b/.gitconfig @@ -0,0 +1,2 @@ +[user] + email = d13g0x diff --git a/.mkshrc b/.mkshrc new file mode 100644 index 0000000..98b1fae --- /dev/null +++ b/.mkshrc @@ -0,0 +1,19 @@ +# .mkshrc + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +PS1="diego@void$(echo '$(pwd | sed "s,^$HOME,~,") $') " + +[ -r /home/diegofcs/.aliases ] && . /home/diegofcs/.aliases +########################################################### + +export XDG_RUNTIME_DIR=/tmp/runtime-diegofcs/qutebrowser +export TZ=America/Bogota + +########################################################### + +export PATH="$PATH:$(du "$HOME/Scripts/" | cut -f2 | tr '\n' ':' | sed 's/:*$//')" +export PATH="$HOME/.local/bin/:$PATH" +########################################################### + diff --git a/.profile b/.profile new file mode 100644 index 0000000..3ae7df5 --- /dev/null +++ b/.profile @@ -0,0 +1,38 @@ +#!/bin/sh + +export ENV=/home/diegofcs/.mkshrc + +# nnn Plugins + +NNN_PLUG_DIR='r:renamer' +NNN_PLUG_FILE='z:-!ffplay -x 850 -y 650 $nnn*' +NNN_PLUG_VARIOS='p:di-nuke' +NNN_PLUG_VIS='e:-!vis $nnn*' +NNN_PLUG="$NNN_PLUG_DIR;$NNN_PLUG_FILE;$NNN_PLUG_VARIOS;$NNN_PLUG_VIS" +export NNN_PLUG + +# nnn options +export NNN_ARCHIVE='\\.(7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)$' +export NNN_OPENER=/home/diegofcs/.config/nnn/plugins/di-nuke +export NNN_USE_EDITOR=1 +export NNN_OPTS="ceEuo" +export NNN_FIFO=/tmp/nnn.fifo +export PAGER="less" +export EDITOR="vis" +export VISUAL="$PAGER" +export IMAGEVIEWER="display" +export FILE="nnn" +export BROWSER='qutebrowser' +export TERM="st-256color" +export LANG=es_CO.UTF-8 +export MENU="dmenu" +export sel=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection +export READER="zathura" + +#nnn bookmarks +export NNN_BMS='t:~/Documentos/Trabajo_oficina/;i:~/Documentos/Trabajo_independiente;v:~/Documentos/Variados;d:~/Descargas;s:~/Scripts;w:~/Imagenes/Wallpapers;c:~/Imagenes/Screenshots' + +#nnn colors +BLK="0B" CHR="0B" DIR="04" EXE="06" REG="00" HARDLINK="06" SYMLINK="06" MISSING="00" ORPHAN="09" FIFO="06" SOCK="0B" OTHER="06" +export NNN_FCOLORS="$BLK$CHR$DIR$EXE$REG$HARDLINK$SYMLINK$MISSING$ORPHAN$FIFO$SOCK$OTHER" + diff --git a/.xinitrc b/.xinitrc new file mode 100644 index 0000000..c6984ee --- /dev/null +++ b/.xinitrc @@ -0,0 +1,55 @@ +#!/bin/sh + +userresources=$HOME/.Xresources +usermodmap=$HOME/.Xmodmap +sysresources=/etc/X11/xinit/.Xresources +sysmodmap=/etc/X11/xinit/.Xmodmap + +# merge in defaults and keymaps + +if [ -f $sysresources ]; then + + + + + + + + xrdb -merge $sysresources + +fi + +if [ -f $sysmodmap ]; then + xmodmap $sysmodmap +fi + +if [ -f "$userresources" ]; then + + + + + + + + xrdb -merge "$userresources" + +fi + +if [ -f "$usermodmap" ]; then + xmodmap "$usermodmap" +fi + +# start some nice programs + +if [ -d /etc/X11/xinit/xinitrc.d ] ; then + for f in /etc/X11/xinit/xinitrc.d/?*.sh ; do + [ -x "$f" ] && . "$f" + done + unset f +fi + +#DWM +exec cron -f /home/diegofcs/.crontab & +exec display -window root $HOME/Imagenes/Wallpapers/void.png & +exec slstatus & +exec DWM diff --git a/Scripts/DWM b/Scripts/DWM new file mode 100755 index 0000000..71aa391 --- /dev/null +++ b/Scripts/DWM @@ -0,0 +1,5 @@ +#!/bin/sh +while true; +do + dwm +done diff --git a/Scripts/autogit b/Scripts/autogit new file mode 100755 index 0000000..3093d43 --- /dev/null +++ b/Scripts/autogit @@ -0,0 +1,83 @@ +#!/bin/sh + +set -e + +# new repo +create() { + +dir=$(basename $(pwd)) +user="visone" + +# config credentials + +git config --global user.name "Victor Tebar" +git config --global user.email "victortebar@gmail.com" + + +# Initialize repo +if [ -f .git/config ] +then + printf "This si already a git repo!!!" + exit +else + + git init + +case "$2" in + + gitea) +# crating repo gitea + + git remote add origin git@gitea.com:$user/"$dir".git + git push origin main ;; + + gitlab) +# creating repo gitlab + + git remote add origin git@gitlab.com:$user/"$dir".git + git push origin main ;; +esac + +# adding all files and setting first commit + + git add . + git commit -m "First commit" + + +fi + +} + +update() { + +printf "Your're about to commit your changes in:\n\t\tBranch: "$(git branch --show-current)"\n\t\tRepo "$(basename $(pwd))"\n\t\tAre you sure? Y/N\n" +read op +if [ "$op" = y ] +then +git add . +printf "Write commit:\n" +read commit +git commit -m "$commit" + if [ "$(git remote -v | awk 'NR==1 { print $1}')" = "all" ] + then + git push all "$(git branch --show-current)" + else + git push origin "$(git branch --show-current)" + fi +fi +} + + +hp (){ + +printf "\n ##### Help #####\n" +printf "\nUsage:\nTo use this script you need to pass it some +options.\n\n\tOptions:\n\t\t$(basename $0) -c it will create a new repo in the "$(basename $(pwd))" dir \n\t\t$(basename $0) -u it will update the "$(basename $(pwd))" repo\n\t\t$(basename $0) -h show this help" + +} + +case "$1" in + -c) create ;; + -u) update ;; + *) hp ;; +esac diff --git a/Scripts/brightness b/Scripts/brightness new file mode 100755 index 0000000..03683b4 --- /dev/null +++ b/Scripts/brightness @@ -0,0 +1,19 @@ +#!/bin/sh + +#Brightness changer using xrandr +# Usage: +# bright + to up brightness in 0.1 step +# bright - to down brightness in 0.1 step + +# Change the output name, use xrandr to know yours +output="LVDS1" + +case "$1" in + + +) + xrandr --output $output --brightness $(echo "$(xrandr --verbose |grep Brightness |grep -o '[0-9].*')+0.1" | bc) ;; + + -) + xrandr --output $output --brightness $(echo "$(xrandr --verbose |grep Brightness |grep -o '[0-9].*')-0.1" | bc) ;; + +esac diff --git a/Scripts/dmenurecord b/Scripts/dmenurecord new file mode 100755 index 0000000..1643dfe --- /dev/null +++ b/Scripts/dmenurecord @@ -0,0 +1,134 @@ +#!/bin/sh + +# Usage: +# `$0`: Ask for recording type via dmenu +# `$0 screencast`: Record both audio and screen +# `$0 video`: Record only screen +# `$0 audio`: Record only audio +# `$0 kill`: Kill existing recording +# +# If there is already a running instance, user will be prompted to end it. + +updateicon() { \ + echo "$1" > /tmp/recordingicon + pkill -RTMIN+9 "${STATUSBAR:?}" + } + +killrecording() { + recpid="$(cat /tmp/recordingpid)" + # kill with SIGTERM, allowing finishing touches. + kill -15 "$recpid" + rm -f /tmp/recordingpid + updateicon "" + pkill -RTMIN+9 "${STATUSBAR:?}" + # even after SIGTERM, ffmpeg may still run, so SIGKILL it. + sleep 3 + kill -9 "$recpid" + exit + } + +screencast() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 24 \ + -s $(xdpyinfo | grep dimensions | awk '{print $2;}') \ + -i $DISPLAY \ +# -f alsa -i default \ + -f sndio -i snd/0 \ + -r 24 -async 1 -vsync -1 \ + -c:v libx264rgb -crf 0 -preset ultrafast -c:a aac \ +# -preset ultrafast -c:a libvorbis \ + "$HOME/Videos/screencast-$(date '+%y%m%d-%H%M%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "⏺️🎙️" + } + +screencastmobile() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 60 \ + -s $(xdpyinfo | grep dimensions | awk '{print $2;}') \ + -i $DISPLAY \ + -f alsa -i default \ + -r 30 \ + -c:v libx264 -profile:v baseline -level 3.0 -pix_fmt yuv420p -loglevel panic -c:a aac \ + "$HOME/Videos/screencast-$(date '+%y%m%d-%H%M%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "⏺️🎙️" + } + +video() { ffmpeg \ + -f x11grab \ + -s $(xdpyinfo | grep dimensions | awk '{print $2;}') \ + -i $DISPLAY \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/Videos/video-$(date '+%y%m%d-%H%M%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "⏺️" + } + +videomobile() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 60 \ + -s $(xdpyinfo | grep dimensions | awk '{print $2;}') \ + -i $DISPLAY \ + -r 30 \ + -c:v libx264 -profile:v baseline -level 3.0 -pix_fmt yuv420p -loglevel panic\ + "$HOME/Videos/video-$(date '+%y%m%d-%H%M%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "⏺️🎙️" + } + +webcamhidef() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 1920x1080 \ + "$HOME/Videos/webcam-$(date '+%y%m%d-%H%M%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "🎥" + } + +webcam() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 640x480 \ + "$HOME/Videos/webcam-$(date '+%y%m%d-%H%M%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "🎥" + } + +audio() { \ + ffmpeg \ + -f alsa -i default \ + -c:a flac \ + "$HOME/Videos/audio-$(date '+%y%m%d-%H%M%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "🎙️" + } + +askrecording() { \ + choice=$(printf "screencast\\nvideo\\naudio\\nwebcam\\nscreencastmobile\\nvideomobile" | dmenu -i -sb "#252525" -p "Seleccione el modo de grabación:") + case "$choice" in + screencast) screencast;; + audio) audio;; + video) video;; + webcam) webcam;; + screencastmobile) screencastmobile;; + videomobile) videomobile;; + esac + } + +asktoend() { \ + response=$(printf "No\\nSí" | dmenu -i -sb "#252525" -p "Grabación activa.¿Desea finalizar?") && + [ "$response" = "Sí" ] && killrecording + } + + +case "$1" in + screencast) screencast;; + audio) audio;; + video) video;; + kill) killrecording;; + *) ([ -f /tmp/recordingpid ] && asktoend && exit) || askrecording;; +esac diff --git a/Scripts/dwm-screenshot b/Scripts/dwm-screenshot new file mode 100755 index 0000000..e4ecd34 --- /dev/null +++ b/Scripts/dwm-screenshot @@ -0,0 +1,38 @@ +#!/bin/sh + + + menu() { + cd ~/Imagenes/Screenshots + file="$(date +%d:%b-%I:%M:%S).png" + dm="dmenu -i" + # Options + desktop="desktop" + area="area" + area2="area2" + + ch=$( printf "$desktop\n$area\n$area2" | $dm -p "ScreenShots:") + case "$ch" in + $desktop)dsk ;; + $area) rg ;; + $area2) mr ;; + esac + } + + dsk() { + sleep 3 && import -window root $file && herbe "A New ScreenShoot Has Been Taken " " "$file" " + } + + rg() { + import $file && herbe "A New ScreenShoot Has Been Taken " " "$file" " + } + + mr() { + import -snaps 3 $file && herbe "A New ScreenShoot Has Been Taken " " "$file" " + } + + case $1 in + -s) cd ~/Imagenes/Screenshots/ + file="$(date +%d:%b-%I:%M:%S).png" + sleep 3 && import -window root $file && herbe "A New ScreenShoot Has Been Taken " " "$file" " ;; + -m) menu ;; + esac diff --git a/Scripts/menu-apagar b/Scripts/menu-apagar new file mode 100755 index 0000000..4dcdddc --- /dev/null +++ b/Scripts/menu-apagar @@ -0,0 +1,27 @@ +#!/bin/sh + +RET=$(printf "Turn off\nReboot\nBlock\nSuspend\nHibernate\nCancel" | dmenu -c -l 7 ) + +#RET=$(echo "" Apagar"\n" Reiniciar"\n" Bloquear"\n" Suspender"\n" logout"\ncancel" | dmenu -l 7 -p " Logout") +case $RET in + "Turn off") + st -T "warning" -g "42x8+480+300" -f "Hack Nerd Font:size=8" -e su - root -c 'shutdown -h now' + #urxvtc -T 'warning' -geometry '42x8-540-320' -imfont 'liberationmono:bold:pixelsize=12' -e su - root -c 'shutdown -h now' + ;; + "Reboot") + st -T "warning" -g "42x8+480+300" -f "Hack Nerd Font:size=8" -e su - root -c 'shutdown -r now' + #urxvtc -T 'warning' -geometry '42x8-540-320' -imfont 'liberationmono:bold:pixelsize=12' -e su - root -c 'shutdown -r now' + ;; + "Block") + slock + ;; + "Suspend") + st -T 'warning' -g '42x8+480+300' -f 'Hack Nerd Font:size=8' -e su - root -c zzz && slock + #urxvtc -T 'warning' -geometry '42x8-540-320' -imfont 'liberationmono:bold:pixelsize=12' -e su - root -c 'zzz && slock' + ;; + "Hibernate") + st -T "warning" -g "42x8+480+300" -f "Hack Nerd Font:size=8" -e su - root -c ZZZ && slock + #urxvtc -T 'warning' -geometry '42x8-540-320' -imfont 'liberationmono:bold:pixelsize=12' -e su - root -c 'ZZZ && slock' + ;; + *) ;; +esac diff --git a/Scripts/music b/Scripts/music new file mode 100755 index 0000000..075635e --- /dev/null +++ b/Scripts/music @@ -0,0 +1,24 @@ +#!/bin/sh + +MDIR="$HOME/Musica" +INPUT=$(echo "Add\nPlay\nPause\nContinue\nNext\nExit" | dmenu -p "Ffplay reproductor:") +case $INPUT in + Add) + find $MDIR$(ls $MDIR | dmenu -i -l 8) + ;; + Play) + for f in "$MDIR"/*; do herbe $f | ffplay -x 800 -nodisp -loglevel quiet -autoexit $f; done + ;; + Pause) + kill -STOP $(pgrep ffplay) + ;; + Continue) + kill -CONT $(pgrep ffplay) + ;; + Next) + kill $(pgrep ffplay) + ;; + Exit) + pkill music && pkill ffplay + ;; + esac diff --git a/Scripts/pash b/Scripts/pash new file mode 100755 index 0000000..a29fb2b --- /dev/null +++ b/Scripts/pash @@ -0,0 +1,240 @@ +#!/bin/sh +# +# pash - simple password manager. + +pw_add() { + name=$1 + + if yn "Generate a password?"; then + # Generate a password by reading '/dev/urandom' with the + # 'tr' command to translate the random bytes into a + # configurable character set. + # + # The 'dd' command is then used to read only the desired + # password length. + # + # Regarding usage of '/dev/urandom' instead of '/dev/random'. + # See: https://www.2uo.de/myths-about-urandom + pass=$(LC_ALL=C tr -dc "${PASH_PATTERN:-_A-Z-a-z-0-9}" < /dev/urandom | + dd ibs=1 obs=1 count="${PASH_LENGTH:-50}" 2>/dev/null) + + else + # 'sread()' is a simple wrapper function around 'read' + # to prevent user input from being printed to the terminal. + sread pass "Enter password" + sread pass2 "Enter password (again)" + + # Disable this check as we dynamically populate the two + # passwords using the 'sread()' function. + # shellcheck disable=2154 + [ "$pass" = "$pass2" ] || die "Passwords do not match" + fi + + [ "$pass" ] || die "Failed to generate a password" + + # Mimic the use of an array for storing arguments by... using + # the function's argument list. This is very apt isn't it? + if [ "$PASH_KEYID" ]; then + set -- --trust-model always -aer "$PASH_KEYID" + else + set -- -c + fi + + # Use 'gpg' to store the password in an encrypted file. + # A heredoc is used here instead of a 'printf' to avoid + # leaking the password through the '/proc' filesystem. + # + # Heredocs are sometimes implemented via temporary files, + # however this is typically done using 'mkstemp()' which + # is more secure than a leak in '/proc'. + "$gpg" "$@" -o "$name.gpg" <<-EOF && + $pass + EOF + printf '%s\n' "Saved '$name' to the store." +} + +pw_del() { + yn "Delete pass file '$1'?" && { + rm -f "$1.gpg" + + # Remove empty parent directories of a password + # entry. It's fine if this fails as it means that + # another entry also lives in the same directory. + rmdir -p "${1%/*}" 2>/dev/null || : + } +} + +pw_show() { + "$gpg" -dq "$1.gpg" +} + +pw_copy() { + # Disable warning against word-splitting as it is safe + # and intentional (globbing is disabled). + # shellcheck disable=2086 + : "${PASH_CLIP:=xclip -sel c}" + #: "${PASH_CLIP:=xsel -f}" + + # Wait in the background for the password timeout and + # clear the clipboard when the timer runs out. + # + # If the 'sleep' fails, kill the script. This is the + # simplest method of aborting from a subshell. + [ "$PASH_TIMEOUT" != off ] && { + printf 'Clearing clipboard in "%s" seconds.\n' "${PASH_TIMEOUT:=15}" + + sleep "$PASH_TIMEOUT" || kill 0 + $PASH_CLIP /dev/null 2>&1 || + die "'tree' command not found" + + tree --noreport | sed 's/\.gpg$//' +} + +yn() { + printf '%s [y/n]: ' "$1" + + # Enable raw input to allow for a single byte to be read from + # stdin without needing to wait for the user to press Return. + stty -icanon + + # Read a single byte from stdin using 'dd'. POSIX 'read' has + # no support for single/'N' byte based input from the user. + answer=$(dd ibs=1 count=1 2>/dev/null) + + # Disable raw input, leaving the terminal how we *should* + # have found it. + stty icanon + + printf '\n' + + # Handle the answer here directly, enabling this function's + # return status to be used in place of checking for '[yY]' + # throughout this program. + glob "$answer" '[yY]' +} + +sread() { + printf '%s: ' "$2" + + # Disable terminal printing while the user inputs their + # password. POSIX 'read' has no '-s' flag which would + # effectively do the same thing. + stty -echo + read -r "$1" + stty echo + + printf '\n' +} + +glob() { + # This is a simple wrapper around a case statement to allow + # for simple string comparisons against globs. + # + # Example: if glob "Hello World" '* World'; then + # + # Disable this warning as it is the intended behavior. + # shellcheck disable=2254 + case $1 in $2) return 0; esac; return 1 +} + +die() { + printf 'error: %s.\n' "$1" >&2 + exit 1 +} + +usage() { printf %s "\ +pash 2.3.0 - simple password manager. +=> [a]dd [name] - Create a new password entry. +=> [c]opy [name] - Copy entry to the clipboard. +=> [d]el [name] - Delete a password entry. +=> [l]ist - List all entries. +=> [s]how [name] - Show password for an entry. +=> [t]ree - List all entries in a tree. +Using a key pair: export PASH_KEYID=XXXXXXXX +Password length: export PASH_LENGTH=50 +Password pattern: export PASH_PATTERN=_A-Z-a-z-0-9 +Store location: export PASH_DIR=~/.local/share/pash +Clipboard tool: export PASH_CLIP='xclip -sel c' +Clipboard timeout: export PASH_TIMEOUT=15 ('off' to disable) +" +exit 0 +} + +main() { + : "${PASH_DIR:=${XDG_DATA_HOME:=$HOME/.local/share}/pash}" + + # Look for both 'gpg' and 'gpg2', + # preferring 'gpg2' if it is available. + command -v gpg >/dev/null 2>&1 && gpg=gpg + command -v gpg2 >/dev/null 2>&1 && gpg=gpg2 + + [ "$gpg" ] || + die "GPG not found" + + mkdir -p "$PASH_DIR" || + die "Couldn't create password directory" + + cd "$PASH_DIR" || + die "Can't access password directory" + + glob "$1" '[acds]*' && [ -z "$2" ] && + die "Missing [name] argument" + + glob "$1" '[cds]*' && [ ! -f "$2.gpg" ] && + die "Pass file '$2' doesn't exist" + + glob "$1" 'a*' && [ -f "$2.gpg" ] && + die "Pass file '$2' already exists" + + glob "$2" '*/*' && glob "$2" '*../*' && + die "Category went out of bounds" + + glob "$2" '/*' && + die "Category can't start with '/'" + + glob "$2" '*/*' && { mkdir -p "${2%/*}" || + die "Couldn't create category '${2%/*}'"; } + + # Set 'GPG_TTY' to the current 'TTY' if it + # is unset. Fixes a somewhat rare `gpg` issue. + export GPG_TTY=${GPG_TTY:-$(tty)} + + # Restrict permissions of any new files to + # only the current user. + umask 077 + + # Ensure that we leave the terminal in a usable + # state on exit or Ctrl+C. + [ -t 1 ] && trap 'stty echo icanon' INT EXIT + + case $1 in + a*) pw_add "$2" ;; + c*) pw_copy "$2" ;; + d*) pw_del "$2" ;; + s*) pw_show "$2" ;; + l*) pw_list ;; + t*) pw_tree ;; + *) usage + esac +} + +# Ensure that debug mode is never enabled to +# prevent the password from leaking. +set +x + +# Ensure that globbing is globally disabled +# to avoid insecurities with word-splitting. +set -f + +[ "$1" ] || usage && main "$@" diff --git a/Scripts/passmenu2 b/Scripts/passmenu2 new file mode 100755 index 0000000..d48765c --- /dev/null +++ b/Scripts/passmenu2 @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +shopt -s nullglob globstar + +typeit=0 +if [[ $1 == "--type" ]]; then + typeit=1 + shift +fi + + +STARTDIR=${PASSWORD_STORE_DIR-~/.password-store} +BASEDIR=$STARTDIR +DONE=0 +LEVEL=0 +PREVSELECTION="" +SELECTION="" + +while [ "$DONE" -eq 0 ] ; do + password_files=( "$STARTDIR"/* ) + password_files=( "${password_files[@]#"$STARTDIR"/}" ) + password_files=( "${password_files[@]%.gpg}" ) + + if [ "$LEVEL" -ne 0 ] ; then + password_files=(".." "${password_files[@]}") + fi + entry=$(printf '%s\n' "${password_files[@]}" | dmenu -p "Passwords:" "$@") + + echo "entry: $entry" + if [ -z "$entry" ] ; then + DONE=1 + exit + fi + + if [ "$entry" != ".." ] ; then + PREVSELECTION=$SELECTION + SELECTION="$SELECTION/$entry" + + # check if another dir + if [ -d "$STARTDIR/$entry" ] ; then + STARTDIR="$STARTDIR/$entry" + LEVEL=$((LEVEL+1)) + else + # not a directory so it must be a real password entry + + if [[ $typeit -eq 0 ]]; then + pass show -c "$SELECTION" 2>/dev/null + else + xdotool - <<<"type --clearmodifiers -- $(pass show "$SELECTION" | head -n 1)" + fi + DONE=1 + fi + + else + LEVEL=$((LEVEL-1)) + SELECTION=$PREVSELECTION + STARTDIR="$BASEDIR/$SELECTION" + fi +done + diff --git a/Scripts/pulsemixer b/Scripts/pulsemixer new file mode 100755 index 0000000..2b5a9a1 --- /dev/null +++ b/Scripts/pulsemixer @@ -0,0 +1,2049 @@ +#!/usr/bin/env python3 +'''Usage of pulsemixer: + -h, --help show this help message and exit + -v, --version print version + -l, --list list everything + --list-sources list sources + --list-sinks list sinks + --id ID specify ID, default sink is used if no ID specified + --get-volume get volume for ID + --set-volume n set volume for ID + --set-volume-all n:n set volume for ID, for every channel + --change-volume +-n change volume for ID + --max-volume n set volume to n if volume is higher than n + --get-mute get mute for ID + --mute mute ID + --unmute unmute ID + --toggle-mute toggle mute for ID + --server choose the server to connect to + --color n 0 no color, 1 color currently selected, 2 full-color + --no-mouse disable mouse support + --create-config generate configuration file''' + +VERSION = '1.5.1' + +import curses +import functools +import getopt +import operator +import os +import re +import signal +import sys +import threading +import traceback +from collections import OrderedDict +from configparser import ConfigParser +from ctypes import * +from itertools import takewhile +from pprint import pprint +from select import select +from shutil import get_terminal_size +from textwrap import dedent +from time import sleep +from unicodedata import east_asian_width + +######################################################################################### +# v bindings + +try: + DLL = CDLL("libpulse.so.0") +except Exception as e: + sys.exit(e) + +PA_VOLUME_NORM = 65536 +PA_CHANNELS_MAX = 32 +PA_USEC_T = c_uint64 +PA_CONTEXT_READY = 4 +PA_CONTEXT_FAILED = 5 +PA_SUBSCRIPTION_MASK_ALL = 0x02ff + + +class Struct(Structure): pass +PA_PROPLIST = PA_OPERATION = PA_CONTEXT = PA_THREADED_MAINLOOP = PA_MAINLOOP_API = Struct + + +class PA_SAMPLE_SPEC(Structure): + _fields_ = [ + ("format", c_int), + ("rate", c_uint32), + ("channels", c_uint32) + ] + + +class PA_CHANNEL_MAP(Structure): + _fields_ = [ + ("channels", c_uint8), + ("map", c_int * PA_CHANNELS_MAX) + ] + + +class PA_CVOLUME(Structure): + _fields_ = [ + ("channels", c_uint8), + ("values", c_uint32 * PA_CHANNELS_MAX) + ] + + +class PA_PORT_INFO(Structure): + _fields_ = [ + ('name', c_char_p), + ('description', c_char_p), + ('priority', c_uint32), + ("available", c_int), + ] + + +class PA_SINK_INPUT_INFO(Structure): + _fields_ = [ + ("index", c_uint32), + ("name", c_char_p), + ("owner_module", c_uint32), + ("client", c_uint32), + ("sink", c_uint32), + ("sample_spec", PA_SAMPLE_SPEC), + ("channel_map", PA_CHANNEL_MAP), + ("volume", PA_CVOLUME), + ("buffer_usec", PA_USEC_T), + ("sink_usec", PA_USEC_T), + ("resample_method", c_char_p), + ("driver", c_char_p), + ("mute", c_int), + ("proplist", POINTER(PA_PROPLIST)) + ] + + +class PA_SINK_INFO(Structure): + _fields_ = [ + ("name", c_char_p), + ("index", c_uint32), + ("description", c_char_p), + ("sample_spec", PA_SAMPLE_SPEC), + ("channel_map", PA_CHANNEL_MAP), + ("owner_module", c_uint32), + ("volume", PA_CVOLUME), + ("mute", c_int), + ("monitor_source", c_uint32), + ("monitor_source_name", c_char_p), + ("latency", PA_USEC_T), + ("driver", c_char_p), + ("flags", c_int), + ("proplist", POINTER(PA_PROPLIST)), + ("configured_latency", PA_USEC_T), + ('base_volume', c_int), + ('state', c_int), + ('n_volume_steps', c_int), + ('card', c_uint32), + ('n_ports', c_uint32), + ('ports', POINTER(POINTER(PA_PORT_INFO))), + ('active_port', POINTER(PA_PORT_INFO)) + ] + + +class PA_SOURCE_OUTPUT_INFO(Structure): + _fields_ = [ + ("index", c_uint32), + ("name", c_char_p), + ("owner_module", c_uint32), + ("client", c_uint32), + ("source", c_uint32), + ("sample_spec", PA_SAMPLE_SPEC), + ("channel_map", PA_CHANNEL_MAP), + ("buffer_usec", PA_USEC_T), + ("source_usec", PA_USEC_T), + ("resample_method", c_char_p), + ("driver", c_char_p), + ("proplist", POINTER(PA_PROPLIST)), + ("corked", c_int), + ("volume", PA_CVOLUME), + ("mute", c_int), + ] + + +class PA_SOURCE_INFO(Structure): + _fields_ = [ + ("name", c_char_p), + ("index", c_uint32), + ("description", c_char_p), + ("sample_spec", PA_SAMPLE_SPEC), + ("channel_map", PA_CHANNEL_MAP), + ("owner_module", c_uint32), + ("volume", PA_CVOLUME), + ("mute", c_int), + ("monitor_of_sink", c_uint32), + ("monitor_of_sink_name", c_char_p), + ("latency", PA_USEC_T), + ("driver", c_char_p), + ("flags", c_int), + ("proplist", POINTER(PA_PROPLIST)), + ("configured_latency", PA_USEC_T), + ('base_volume', c_int), + ('state', c_int), + ('n_volume_steps', c_int), + ('card', c_uint32), + ('n_ports', c_uint32), + ('ports', POINTER(POINTER(PA_PORT_INFO))), + ('active_port', POINTER(PA_PORT_INFO)) + ] + + +class PA_CLIENT_INFO(Structure): + _fields_ = [ + ("index", c_uint32), + ("name", c_char_p), + ("owner_module", c_uint32), + ("driver", c_char_p) + ] + + +class PA_CARD_PROFILE_INFO(Structure): + _fields_ = [ + ('name', c_char_p), + ('description', c_char_p), + ('n_sinks', c_uint32), + ('n_sources', c_uint32), + ('priority', c_uint32), + ] + + +class PA_CARD_PROFILE_INFO2(Structure): + _fields_ = PA_CARD_PROFILE_INFO._fields_ + [('available', c_int)] + + +class PA_CARD_INFO(Structure): + _fields_ = [ + ('index', c_uint32), + ('name', c_char_p), + ('owner_module', c_uint32), + ('driver', c_char_p), + ('n_profiles', c_uint32), + ('profiles', POINTER(PA_CARD_PROFILE_INFO)), + ('active_profile', POINTER(PA_CARD_PROFILE_INFO)), + ('proplist', POINTER(PA_PROPLIST)), + ('n_ports', c_uint32), + ('ports', POINTER(POINTER(c_void_p))), + ('profiles2', POINTER(POINTER(PA_CARD_PROFILE_INFO2))), + ('active_profile2', POINTER(PA_CARD_PROFILE_INFO2)) + ] + + +class PA_SERVER_INFO(Structure): + _fields_ = [ + ('user_name', c_char_p), + ('host_name', c_char_p), + ('server_version', c_char_p), + ('server_name', c_char_p), + ('sample_spec', PA_SAMPLE_SPEC), + ('default_sink_name', c_char_p), + ('default_source_name', c_char_p), + ] + + +PA_STATE_CB_T = CFUNCTYPE(c_int, POINTER(PA_CONTEXT), c_void_p) +PA_CLIENT_INFO_CB_T = CFUNCTYPE(c_void_p, POINTER(PA_CONTEXT), POINTER(PA_CLIENT_INFO), c_int, c_void_p) +PA_SINK_INPUT_INFO_CB_T = CFUNCTYPE(c_int, POINTER(PA_CONTEXT), POINTER(PA_SINK_INPUT_INFO), c_int, c_void_p) +PA_SINK_INFO_CB_T = CFUNCTYPE(c_int, POINTER(PA_CONTEXT), POINTER(PA_SINK_INFO), c_int, c_void_p) +PA_SOURCE_OUTPUT_INFO_CB_T = CFUNCTYPE(c_int, POINTER(PA_CONTEXT), POINTER(PA_SOURCE_OUTPUT_INFO), c_int, c_void_p) +PA_SOURCE_INFO_CB_T = CFUNCTYPE(c_int, POINTER(PA_CONTEXT), POINTER(PA_SOURCE_INFO), c_int, c_void_p) +PA_CONTEXT_SUCCESS_CB_T = CFUNCTYPE(c_void_p, POINTER(PA_CONTEXT), c_int, c_void_p) +PA_CARD_INFO_CB_T = CFUNCTYPE(None, POINTER(PA_CONTEXT), POINTER(PA_CARD_INFO), c_int, c_void_p) +PA_SERVER_INFO_CB_T = CFUNCTYPE(None, POINTER(PA_CONTEXT), POINTER(PA_SERVER_INFO), c_void_p) +PA_CONTEXT_SUBSCRIBE_CB_T = CFUNCTYPE(c_void_p, POINTER(PA_CONTEXT), c_int, c_int, c_void_p) + +pa_threaded_mainloop_new = DLL.pa_threaded_mainloop_new +pa_threaded_mainloop_new.restype = POINTER(PA_THREADED_MAINLOOP) +pa_threaded_mainloop_new.argtypes = [] + +pa_threaded_mainloop_free = DLL.pa_threaded_mainloop_free +pa_threaded_mainloop_free.restype = c_void_p +pa_threaded_mainloop_free.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_start = DLL.pa_threaded_mainloop_start +pa_threaded_mainloop_start.restype = c_int +pa_threaded_mainloop_start.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_stop = DLL.pa_threaded_mainloop_stop +pa_threaded_mainloop_stop.restype = None +pa_threaded_mainloop_stop.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_lock = DLL.pa_threaded_mainloop_lock +pa_threaded_mainloop_lock.restype = None +pa_threaded_mainloop_lock.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_unlock = DLL.pa_threaded_mainloop_unlock +pa_threaded_mainloop_unlock.restype = None +pa_threaded_mainloop_unlock.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_wait = DLL.pa_threaded_mainloop_wait +pa_threaded_mainloop_wait.restype = None +pa_threaded_mainloop_wait.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_threaded_mainloop_signal = DLL.pa_threaded_mainloop_signal +pa_threaded_mainloop_signal.restype = None +pa_threaded_mainloop_signal.argtypes = [POINTER(PA_THREADED_MAINLOOP), c_int] + +pa_threaded_mainloop_get_api = DLL.pa_threaded_mainloop_get_api +pa_threaded_mainloop_get_api.restype = POINTER(PA_MAINLOOP_API) +pa_threaded_mainloop_get_api.argtypes = [POINTER(PA_THREADED_MAINLOOP)] + +pa_context_errno = DLL.pa_context_errno +pa_context_errno.restype = c_int +pa_context_errno.argtypes = [POINTER(PA_CONTEXT)] + +pa_context_new_with_proplist = DLL.pa_context_new_with_proplist +pa_context_new_with_proplist.restype = POINTER(PA_CONTEXT) +pa_context_new_with_proplist.argtypes = [POINTER(PA_MAINLOOP_API), c_char_p, POINTER(PA_PROPLIST)] + +pa_context_unref = DLL.pa_context_unref +pa_context_unref.restype = None +pa_context_unref.argtypes = [POINTER(PA_CONTEXT)] + +pa_context_set_state_callback = DLL.pa_context_set_state_callback +pa_context_set_state_callback.restype = None +pa_context_set_state_callback.argtypes = [POINTER(PA_CONTEXT), PA_STATE_CB_T, c_void_p] + +pa_context_connect = DLL.pa_context_connect +pa_context_connect.restype = c_int +pa_context_connect.argtypes = [POINTER(PA_CONTEXT), c_char_p, c_int, POINTER(c_int)] + +pa_context_get_state = DLL.pa_context_get_state +pa_context_get_state.restype = c_int +pa_context_get_state.argtypes = [POINTER(PA_CONTEXT)] + +pa_context_disconnect = DLL.pa_context_disconnect +pa_context_disconnect.restype = c_int +pa_context_disconnect.argtypes = [POINTER(PA_CONTEXT)] + +pa_operation_unref = DLL.pa_operation_unref +pa_operation_unref.restype = None +pa_operation_unref.argtypes = [POINTER(PA_OPERATION)] + +pa_context_subscribe = DLL.pa_context_subscribe +pa_context_subscribe.restype = POINTER(PA_OPERATION) +pa_context_subscribe.argtypes = [POINTER(PA_CONTEXT), c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_subscribe_callback = DLL.pa_context_set_subscribe_callback +pa_context_set_subscribe_callback.restype = None +pa_context_set_subscribe_callback.args = [POINTER(PA_CONTEXT), PA_CONTEXT_SUBSCRIBE_CB_T, c_void_p] + +pa_proplist_new = DLL.pa_proplist_new +pa_proplist_new.restype = POINTER(PA_PROPLIST) + +pa_proplist_sets = DLL.pa_proplist_sets +pa_proplist_sets.argtypes = [POINTER(PA_PROPLIST), c_char_p, c_char_p] + +pa_proplist_gets = DLL.pa_proplist_gets +pa_proplist_gets.restype = c_char_p +pa_proplist_gets.argtypes = [POINTER(PA_PROPLIST), c_char_p] + +pa_proplist_free = DLL.pa_proplist_free +pa_proplist_free.argtypes = [POINTER(PA_PROPLIST)] + +pa_context_get_sink_input_info_list = DLL.pa_context_get_sink_input_info_list +pa_context_get_sink_input_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_sink_input_info_list.argtypes = [POINTER(PA_CONTEXT), PA_SINK_INPUT_INFO_CB_T, c_void_p] + +pa_context_get_sink_info_list = DLL.pa_context_get_sink_info_list +pa_context_get_sink_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_sink_info_list.argtypes = [POINTER(PA_CONTEXT), PA_SINK_INFO_CB_T, c_void_p] + +pa_context_set_sink_mute_by_index = DLL.pa_context_set_sink_mute_by_index +pa_context_set_sink_mute_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_sink_mute_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_suspend_sink_by_index = DLL.pa_context_suspend_sink_by_index +pa_context_suspend_sink_by_index.restype = POINTER(PA_OPERATION) +pa_context_suspend_sink_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_sink_port_by_index = DLL.pa_context_set_sink_port_by_index +pa_context_set_sink_port_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_sink_port_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_char_p, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_sink_input_mute = DLL.pa_context_set_sink_input_mute +pa_context_set_sink_input_mute.restype = POINTER(PA_OPERATION) +pa_context_set_sink_input_mute.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_sink_volume_by_index = DLL.pa_context_set_sink_volume_by_index +pa_context_set_sink_volume_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_sink_volume_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, POINTER(PA_CVOLUME), PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_sink_input_volume = DLL.pa_context_set_sink_input_volume +pa_context_set_sink_input_volume.restype = POINTER(PA_OPERATION) +pa_context_set_sink_input_volume.argtypes = [POINTER(PA_CONTEXT), c_uint32, POINTER(PA_CVOLUME), PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_move_sink_input_by_index = DLL.pa_context_move_sink_input_by_index +pa_context_move_sink_input_by_index.restype = POINTER(PA_OPERATION) +pa_context_move_sink_input_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_uint32, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_default_sink = DLL.pa_context_set_default_sink +pa_context_set_default_sink.restype = POINTER(PA_OPERATION) +pa_context_set_default_sink.argtypes = [POINTER(PA_CONTEXT), c_char_p, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_kill_sink_input = DLL.pa_context_kill_sink_input +pa_context_kill_sink_input.restype = POINTER(PA_OPERATION) +pa_context_kill_sink_input.argtypes = [POINTER(PA_CONTEXT), c_uint32, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_kill_client = DLL.pa_context_kill_client +pa_context_kill_client.restype = POINTER(PA_OPERATION) +pa_context_kill_client.argtypes = [POINTER(PA_CONTEXT), c_uint32, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_get_source_output_info_list = DLL.pa_context_get_source_output_info_list +pa_context_get_source_output_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_source_output_info_list.argtypes = [POINTER(PA_CONTEXT), PA_SOURCE_OUTPUT_INFO_CB_T, c_void_p] + +pa_context_move_source_output_by_index = DLL.pa_context_move_source_output_by_index +pa_context_move_source_output_by_index.restype = POINTER(PA_OPERATION) +pa_context_move_source_output_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_uint32, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_source_output_volume = DLL.pa_context_set_source_output_volume +pa_context_set_source_output_volume.restype = POINTER(PA_OPERATION) +pa_context_set_source_output_volume.argtypes = [POINTER(PA_CONTEXT), c_uint32, POINTER(PA_CVOLUME), PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_source_output_mute = DLL.pa_context_set_source_output_mute +pa_context_set_source_output_mute.restype = POINTER(PA_OPERATION) +pa_context_set_source_output_mute.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_get_source_info_list = DLL.pa_context_get_source_info_list +pa_context_get_source_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_source_info_list.argtypes = [POINTER(PA_CONTEXT), PA_SOURCE_INFO_CB_T, c_void_p] + +pa_context_set_source_volume_by_index = DLL.pa_context_set_source_volume_by_index +pa_context_set_source_volume_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_source_volume_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, POINTER(PA_CVOLUME), PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_source_mute_by_index = DLL.pa_context_set_source_mute_by_index +pa_context_set_source_mute_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_source_mute_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_suspend_source_by_index = DLL.pa_context_suspend_source_by_index +pa_context_suspend_source_by_index.restype = POINTER(PA_OPERATION) +pa_context_suspend_source_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_source_port_by_index = DLL.pa_context_set_source_port_by_index +pa_context_set_source_port_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_source_port_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_char_p, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_set_default_source = DLL.pa_context_set_default_source +pa_context_set_default_source.restype = POINTER(PA_OPERATION) +pa_context_set_default_source.argtypes = [POINTER(PA_CONTEXT), c_char_p, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_kill_source_output = DLL.pa_context_kill_source_output +pa_context_kill_source_output.restype = POINTER(PA_OPERATION) +pa_context_kill_source_output.argtypes = [POINTER(PA_CONTEXT), c_uint32, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_get_client_info_list = DLL.pa_context_get_client_info_list +pa_context_get_client_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_client_info_list.argtypes = [POINTER(PA_CONTEXT), PA_CLIENT_INFO_CB_T, c_void_p] + +pa_context_get_card_info_list = DLL.pa_context_get_card_info_list +pa_context_get_card_info_list.restype = POINTER(PA_OPERATION) +pa_context_get_card_info_list.argtypes = [POINTER(PA_CONTEXT), PA_CARD_INFO_CB_T, c_void_p] + +pa_context_set_card_profile_by_index = DLL.pa_context_set_card_profile_by_index +pa_context_set_card_profile_by_index.restype = POINTER(PA_OPERATION) +pa_context_set_card_profile_by_index.argtypes = [POINTER(PA_CONTEXT), c_uint32, c_char_p, PA_CONTEXT_SUCCESS_CB_T, c_void_p] + +pa_context_get_server_info = DLL.pa_context_get_server_info +pa_context_get_server_info.restype = POINTER(PA_OPERATION) +pa_context_get_server_info.argtypes = [POINTER(PA_CONTEXT), PA_SERVER_INFO_CB_T, c_void_p] + +pa_get_library_version = DLL.pa_get_library_version +pa_get_library_version.restype = c_char_p +PA_MAJOR = int(pa_get_library_version().decode().split('.')[0]) + +# ^ bindings +######################################################################################### +# v lib + + +class DebugMixin(): + + def debug(self): + pprint(vars(self)) + + +class PulsePort(DebugMixin): + + def __init__(self, pa_port): + self.name = pa_port.name + self.description = pa_port.description + self.priority = pa_port.priority + self.available = getattr(pa_port, "available", 0) + if self.available == 1: # 1 off, 0 n/a, 2 on + self.description += b' / off' + + +class PulseServer(DebugMixin): + + def __init__(self, pa_server): + self.default_sink_name = pa_server.default_sink_name + self.default_source_name = pa_server.default_source_name + self.server_version = pa_server.server_version + + +class PulseCardProfile(DebugMixin): + + def __init__(self, pa_profile): + self.name = pa_profile.name + self.description = pa_profile.description + self.available = getattr(pa_profile, "available", 1) + if not self.available: + self.description += b' / off' + + +class PulseCard(DebugMixin): + + def __init__(self, pa_card): + self.name = pa_card.name + self.description = pa_proplist_gets(pa_card.proplist, b'device.description') + self.index = pa_card.index + self.driver = pa_card.driver + self.owner_module = pa_card.owner_module + self.n_profiles = pa_card.n_profiles + if PA_MAJOR >= 5: + self.profiles = [PulseCardProfile(pa_card.profiles2[n].contents) for n in range(self.n_profiles)] + self.active_profile = PulseCardProfile(pa_card.active_profile2[0]) + else: # fallback to legacy profile, for PA < 5.0 (March 2014) + self.profiles = [PulseCardProfile(pa_card.profiles[n]) for n in range(self.n_profiles)] + self.active_profile = PulseCardProfile(pa_card.active_profile[0]) + self.volume = type('volume', (object, ), {'channels': 1, 'values': [0, 0]}) + + def __str__(self): + return "Card-ID: {}, Name: {}".format(self.index, self.name.decode()) + + +class PulseClient(DebugMixin): + + def __init__(self, pa_client): + self.index = getattr(pa_client, "index", 0) + self.name = getattr(pa_client, "name", pa_client) + self.driver = getattr(pa_client, "driver", "default driver") + self.owner_module = getattr(pa_client, "owner_module", -1) + + def __str__(self): + return "Client-name: {}".format(self.name.decode()) + + +class Pulse(DebugMixin): + + def __init__(self, client_name='libpulse', server_name=None, reconnect=False): + self.error = None + self.data = [] + self.operation = None + self.connected = False + self.client_name = client_name.encode() + self.server_name = server_name + + self.pa_state_cb = PA_STATE_CB_T(self.state_cb) + self.pa_subscribe_cb = self.pa_dc_cb = lambda: None + self.pa_cbs = {'sink_input_list': PA_SINK_INPUT_INFO_CB_T(self.sink_input_list_cb), + 'source_output_list': PA_SOURCE_OUTPUT_INFO_CB_T(self.source_output_list_cb), + 'sink_list': PA_SINK_INFO_CB_T(self.sink_list_cb), + 'source_list': PA_SOURCE_INFO_CB_T(self.source_list_cb), + 'server': PA_SERVER_INFO_CB_T(self.server_cb), + 'card_list': PA_CARD_INFO_CB_T(self.card_list_cb), + 'client_list': PA_CLIENT_INFO_CB_T(self.client_list_cb), + 'success': PA_CONTEXT_SUCCESS_CB_T(self.context_success)} + self.mainloop = pa_threaded_mainloop_new() + self.mainloop_api = pa_threaded_mainloop_get_api(self.mainloop) + + proplist = pa_proplist_new() + pa_proplist_sets(proplist, b'application.id', self.client_name) + pa_proplist_sets(proplist, b'application.icon_name', b'audio-card') + self.context = pa_context_new_with_proplist(self.mainloop_api, self.client_name, proplist) + pa_context_set_state_callback(self.context, self.pa_state_cb, None) + pa_proplist_free(proplist) + + if pa_context_connect(self.context, self.server_name, 0, None) < 0 or self.error: + if not reconnect: sys.exit("Failed to connect to pulseaudio: Connection refused") + else: return + + pa_threaded_mainloop_lock(self.mainloop) + pa_threaded_mainloop_start(self.mainloop) + if self.error and reconnect: return + pa_threaded_mainloop_wait(self.mainloop) or pa_threaded_mainloop_unlock(self.mainloop) + if self.error and reconnect: return + elif self.error: sys.exit('Failed to connect to pulseaudio') + self.connected = True + + def wait_and_unlock(self): + pa_threaded_mainloop_wait(self.mainloop) + pa_threaded_mainloop_unlock(self.mainloop) + pa_operation_unref(self.operation) + + def reconnect(self): + if self.context: + pa_context_disconnect(self.context) + pa_context_unref(self.context) + if self.mainloop: + pa_threaded_mainloop_stop(self.mainloop) + pa_threaded_mainloop_free(self.mainloop) + self.__init__(self.client_name.decode(), self.server_name, reconnect=True) + + def unmute_stream(self, obj): + if type(obj) is PulseSinkInfo: + self.sink_mute(obj.index, 0) + elif type(obj) is PulseSinkInputInfo: + self.sink_input_mute(obj.index, 0) + elif type(obj) is PulseSourceInfo: + self.source_mute(obj.index, 0) + elif type(obj) is PulseSourceOutputInfo: + self.source_output_mute(obj.index, 0) + obj.mute = 0 + + def mute_stream(self, obj): + if type(obj) is PulseSinkInfo: + self.sink_mute(obj.index, 1) + elif type(obj) is PulseSinkInputInfo: + self.sink_input_mute(obj.index, 1) + elif type(obj) is PulseSourceInfo: + self.source_mute(obj.index, 1) + elif type(obj) is PulseSourceOutputInfo: + self.source_output_mute(obj.index, 1) + obj.mute = 1 + + def set_volume(self, obj, volume): + if type(obj) is PulseSinkInfo: + self.set_sink_volume(obj.index, volume) + elif type(obj) is PulseSinkInputInfo: + self.set_sink_input_volume(obj.index, volume) + elif type(obj) is PulseSourceInfo: + self.set_source_volume(obj.index, volume) + elif type(obj) is PulseSourceOutputInfo: + self.set_source_output_volume(obj.index, volume) + obj.volume = volume + + def change_volume_mono(self, obj, inc): + obj.volume.values = [v + inc for v in obj.volume.values] + self.set_volume(obj, obj.volume) + + def get_volume_mono(self, obj): + return int(sum(obj.volume.values) / len(obj.volume.values)) + + def fill_clients(self): + if not self.data: + return None + data, self.data = self.data, [] + clist = self.client_list() + for d in data: + for c in clist: + if c.index == d.client_id: + d.client = c + break + return data + + def state_cb(self, c, b): + state = pa_context_get_state(c) + if state == PA_CONTEXT_READY: + pa_threaded_mainloop_signal(self.mainloop, 0) + elif state == PA_CONTEXT_FAILED: + self.error = RuntimeError("Failed to complete action: {}, {}".format(state, pa_context_errno(c))) + self.connected = False + pa_threaded_mainloop_signal(self.mainloop, 0) + self.pa_dc_cb() + return 0 + + def _eof_cb(func): + def wrapper(self, c, info, eof, *args): + if eof: + pa_threaded_mainloop_signal(self.mainloop, 0) + return 0 + func(self, c, info, eof, *args) + return 0 + return wrapper + + def _action_sync(func): + def wrapper(self, *args): + if self.error: raise self.error + pa_threaded_mainloop_lock(self.mainloop) + try: + func(self, *args) + except Exception as e: + pa_threaded_mainloop_unlock(self.mainloop) + raise e + self.wait_and_unlock() + if func.__name__ in ('sink_input_list', 'source_output_list'): + self.data = self.fill_clients() + data, self.data = self.data, [] + return data or [] + return wrapper + + @_eof_cb + def card_list_cb(self, c, card_info, eof, userdata): + self.data.append(PulseCard(card_info[0])) + + @_eof_cb + def client_list_cb(self, c, client_info, eof, userdata): + self.data.append(PulseClient(client_info[0])) + + @_eof_cb + def sink_input_list_cb(self, c, sink_input_info, eof, userdata): + self.data.append(PulseSinkInputInfo(sink_input_info[0])) + + @_eof_cb + def sink_list_cb(self, c, sink_info, eof, userdata): + self.data.append(PulseSinkInfo(sink_info[0])) + + @_eof_cb + def source_output_list_cb(self, c, source_output_info, eof, userdata): + self.data.append(PulseSourceOutputInfo(source_output_info[0])) + + @_eof_cb + def source_list_cb(self, c, source_info, eof, userdata): + self.data.append(PulseSourceInfo(source_info[0])) + + def server_cb(self, c, server_info, userdata): + self.data = PulseServer(server_info[0]) + pa_threaded_mainloop_signal(self.mainloop, 0) + + def context_success(self, *_): + pa_threaded_mainloop_signal(self.mainloop, 0) + + def subscribe(self, cb): + self.pa_subscribe_cb, self.pa_dc_cb = PA_CONTEXT_SUBSCRIBE_CB_T(cb), cb + pa_context_set_subscribe_callback(self.context, self.pa_subscribe_cb, None) + pa_threaded_mainloop_lock(self.mainloop) + self.operation = pa_context_subscribe(self.context, PA_SUBSCRIPTION_MASK_ALL, self.pa_cbs['success'], None) + self.wait_and_unlock() + + @_action_sync + def sink_input_list(self): + self.operation = pa_context_get_sink_input_info_list(self.context, self.pa_cbs['sink_input_list'], None) + + @_action_sync + def source_output_list(self): + self.operation = pa_context_get_source_output_info_list(self.context, self.pa_cbs['source_output_list'], None) + + @_action_sync + def sink_list(self): + self.operation = pa_context_get_sink_info_list(self.context, self.pa_cbs['sink_list'], None) + + @_action_sync + def source_list(self): + self.operation = pa_context_get_source_info_list(self.context, self.pa_cbs['source_list'], None) + + @_action_sync + def get_server_info(self): + self.operation = pa_context_get_server_info(self.context, self.pa_cbs['server'], None) + + @_action_sync + def card_list(self): + self.operation = pa_context_get_card_info_list(self.context, self.pa_cbs['card_list'], None) + + @_action_sync + def client_list(self): + self.operation = pa_context_get_client_info_list(self.context, self.pa_cbs['client_list'], None) + + @_action_sync + def sink_input_mute(self, index, mute): + self.operation = pa_context_set_sink_input_mute(self.context, index, mute, self.pa_cbs['success'], None) + + @_action_sync + def sink_input_move(self, index, s_index): + self.operation = pa_context_move_sink_input_by_index(self.context, index, s_index, self.pa_cbs['success'], None) + + @_action_sync + def sink_mute(self, index, mute): + self.operation = pa_context_set_sink_mute_by_index(self.context, index, mute, self.pa_cbs['success'], None) + + @_action_sync + def set_sink_input_volume(self, index, vol): + self.operation = pa_context_set_sink_input_volume(self.context, index, vol.to_c(), self.pa_cbs['success'], None) + + @_action_sync + def set_sink_volume(self, index, vol): + self.operation = pa_context_set_sink_volume_by_index(self.context, index, vol.to_c(), self.pa_cbs['success'], None) + + @_action_sync + def sink_suspend(self, index, suspend): + self.operation = pa_context_suspend_sink_by_index(self.context, index, suspend, self.pa_cbs['success'], None) + + @_action_sync + def set_default_sink(self, name): + self.operation = pa_context_set_default_sink(self.context, name, self.pa_cbs['success'], None) + + @_action_sync + def kill_sink(self, index): + self.operation = pa_context_kill_sink_input(self.context, index, self.pa_cbs['success'], None) + + @_action_sync + def kill_client(self, index): + self.operation = pa_context_kill_client(self.context, index, self.pa_cbs['success'], None) + + @_action_sync + def set_sink_port(self, index, port): + self.operation = pa_context_set_sink_port_by_index(self.context, index, port, self.pa_cbs['success'], None) + + @_action_sync + def set_source_output_volume(self, index, vol): + self.operation = pa_context_set_source_output_volume(self.context, index, vol.to_c(), self.pa_cbs['success'], None) + + @_action_sync + def set_source_volume(self, index, vol): + self.operation = pa_context_set_source_volume_by_index(self.context, index, vol.to_c(), self.pa_cbs['success'], None) + + @_action_sync + def source_suspend(self, index, suspend): + self.operation = pa_context_suspend_source_by_index(self.context, index, suspend, self.pa_cbs['success'], None) + + @_action_sync + def set_default_source(self, name): + self.operation = pa_context_set_default_source(self.context, name, self.pa_cbs['success'], None) + + @_action_sync + def kill_source(self, index): + self.operation = pa_context_kill_source_output(self.context, index, self.pa_cbs['success'], None) + + @_action_sync + def set_source_port(self, index, port): + self.operation = pa_context_set_source_port_by_index(self.context, index, port, self.pa_cbs['success'], None) + + @_action_sync + def source_output_mute(self, index, mute): + self.operation = pa_context_set_source_output_mute(self.context, index, mute, self.pa_cbs['success'], None) + + @_action_sync + def source_mute(self, index, mute): + self.operation = pa_context_set_source_mute_by_index(self.context, index, mute, self.pa_cbs['success'], None) + + @_action_sync + def source_output_move(self, index, s_index): + self.operation = pa_context_move_source_output_by_index(self.context, index, s_index, self.pa_cbs['success'], None) + + @_action_sync + def set_card_profile(self, index, p_index): + self.operation = pa_context_set_card_profile_by_index(self.context, index, p_index, self.pa_cbs['success'], None) + + +class PulseSink(DebugMixin): + + def __init__(self, sink_info): + self.index = sink_info.index + self.name = sink_info.name + self.mute = sink_info.mute + self.volume = PulseVolume(sink_info.volume) + + +class PulseSinkInfo(PulseSink): + + def __init__(self, pa_sink_info): + PulseSink.__init__(self, pa_sink_info) + self.description = pa_sink_info.description + self.owner_module = pa_sink_info.owner_module + self.driver = pa_sink_info.driver + self.monitor_source = pa_sink_info.monitor_source + self.monitor_source_name = pa_sink_info.monitor_source_name + self.n_ports = pa_sink_info.n_ports + self.ports = [PulsePort(pa_sink_info.ports[i].contents) for i in range(self.n_ports)] + self.active_port = None + if self.n_ports: + self.active_port = PulsePort(pa_sink_info.active_port.contents) + + def __str__(self): + return "ID: sink-{}, Name: {}, Mute: {}, {}".format(self.index, self.description.decode(), self.mute, self.volume) + + +class PulseSinkInputInfo(PulseSink): + + def __init__(self, pa_sink_input_info): + PulseSink.__init__(self, pa_sink_input_info) + self.owner_module = pa_sink_input_info.owner_module + self.client = PulseClient(pa_sink_input_info.name) + self.client_id = pa_sink_input_info.client + self.sink = self.owner = pa_sink_input_info.sink + self.driver = pa_sink_input_info.driver + self.media_name = pa_proplist_gets(pa_sink_input_info.proplist, b'media.name') + + def __str__(self): + if self.client: + return "ID: sink-input-{}, Name: {}, Mute: {}, {}".format(self.index, self.client.name.decode(), self.mute, self.volume) + return "ID: sink-input-{}, Name: {}, Mute: {}".format(self.index, self.name.decode(), self.mute) + + +class PulseSource(DebugMixin): + + def __init__(self, source_info): + self.index = source_info.index + self.name = source_info.name + self.mute = source_info.mute + self.volume = PulseVolume(source_info.volume) + + +class PulseSourceInfo(PulseSource): + + def __init__(self, pa_source_info): + PulseSource.__init__(self, pa_source_info) + self.description = pa_source_info.description + self.owner_module = pa_source_info.owner_module + self.monitor_of_sink = pa_source_info.monitor_of_sink + self.monitor_of_sink_name = pa_source_info.monitor_of_sink_name + self.driver = pa_source_info.driver + self.n_ports = pa_source_info.n_ports + self.ports = [PulsePort(pa_source_info.ports[i].contents) for i in range(self.n_ports)] + self.active_port = None + if self.n_ports: + self.active_port = PulsePort(pa_source_info.active_port.contents) + + def __str__(self): + return "ID: source-{}, Name: {}, Mute: {}, {}".format(self.index, self.description.decode(), self.mute, self.volume) + + +class PulseSourceOutputInfo(PulseSource): + + def __init__(self, pa_source_output_info): + PulseSource.__init__(self, pa_source_output_info) + self.owner_module = pa_source_output_info.owner_module + self.client = PulseClient(pa_source_output_info.name) + self.client_id = pa_source_output_info.client + self.source = self.owner = pa_source_output_info.source + self.driver = pa_source_output_info.driver + self.application_id = pa_proplist_gets(pa_source_output_info.proplist, b'application.id') + + def __str__(self): + if self.client: + return "ID: source-output-{}, Name: {}, Mute: {}, {}".format(self.index, self.client.name.decode(), self.mute, self.volume) + return "ID: source-output-{}, Name: {}, Mute: {}".format(self.index, self.name.decode(), self.mute) + + +class PulseVolume(DebugMixin): + + def __init__(self, cvolume): + self.channels = cvolume.channels + self.values = [(round(x * 100 / PA_VOLUME_NORM)) for x in cvolume.values[:self.channels]] + self.cvolume = PA_CVOLUME() + self.cvolume.channels = self.channels + + def to_c(self): + self.values = list(map(lambda x: max(min(x, 150), 0), self.values)) + for x in range(self.channels): + self.cvolume.values[x] = round((self.values[x] * PA_VOLUME_NORM) / 100) + return self.cvolume + + def __str__(self): + return "Channels: {}, Volumes: {}".format(self.channels, [str(x) + "%" for x in self.values]) + + +# ^ lib +######################################################################################### +# v main + + +class Bar(): + # should be in correct order + LEFT, RIGHT, RLEFT, RRIGHT, CENTER, SUB, SLEFT, SRIGHT, NONE = range(9) + + def __init__(self, pa): + if type(pa) is str: + self.name = pa + return + if type(pa) in (PulseSinkInfo, PulseSourceInfo, PulseCard): + self.fullname = pa.description.decode() + else: + self.fullname = pa.client.name.decode() + self.name = re.sub(r'^ALSA plug-in \[|\]$', '', self.fullname.replace('|', ' ')) + for key in CFG.renames: + if key.match(self.name): + self.name = CFG.renames[key] + break + self.index = pa.index + self.owner = -1 + self.stream_index = -1 + self.media_name, self.media_name_wide, self.media_name_widths = '', False, [] + self.poll_data(pa, 0, 0) + self.maxsize = 150 + self.locked = True + + def poll_data(self, pa, owned, stream_index): + self.channels = pa.volume.channels + self.muted = getattr(pa, 'mute', False) + self.owned = owned + self.stream_index = stream_index + self.volume = pa.volume.values + if hasattr(pa, 'media_name'): + media_fullname = pa.media_name.decode().replace('\n', ' ') + media_name = ': {}'.format(media_fullname.replace('|', ' ')) + if media_fullname != self.fullname and media_name != self.media_name: + self.media_name, self.media_name_wide = media_name, False + if len(media_fullname) != len(pa.media_name): # contains multi-byte chars which might be wide + self.media_name_widths = [int(east_asian_width(c) == 'W') + 1 for c in media_name] + self.media_name_wide = 2 in self.media_name_widths + else: + self.media_name, self.media_name_wide = '', False + if type(pa) in (PulseSinkInputInfo, PulseSourceOutputInfo): + self.owner = pa.owner + self.pa = pa + + def mute_toggle(self): + PULSE.unmute_stream(self.pa) if self.muted else PULSE.mute_stream(self.pa) + + def lock_toggle(self): + self.locked = not self.locked + + def set(self, n, side): + vol = self.pa.volume + if self.locked: + for i, _ in enumerate(vol.values): + vol.values[i] = n + else: + vol.values[side] = n + PULSE.set_volume(self.pa, vol) + + def move(self, n, side): + vol = self.pa.volume + if self.locked: + for i, _ in enumerate(vol.values): + vol.values[i] += n + else: + vol.values[side] += n + PULSE.set_volume(self.pa, vol) + + +class Screen(): + DOWN = 1 + UP = -1 + SCROLL_UP = [getattr(curses, i, 0) for i in ['BUTTON4_PRESSED', 'BUTTON3_TRIPLE_CLICKED']] + SCROLL_DOWN = [getattr(curses, i, 0) for i in ['BUTTON5_PRESSED', 'A_LOW', 'A_BOLD', 'BUTTON4_DOUBLE_CLICKED']] + KEY_MOUSE = getattr(curses, 'KEY_MOUSE', 0) + DIGITS = list(map(ord, map(str, range(10)))) + SIDES = {Bar.LEFT: 'Left', Bar.RIGHT: 'Right', Bar.RLEFT: 'Rear Left', + Bar.RRIGHT: 'Rear Right', Bar.CENTER: 'Center', Bar.SUB: 'Subwoofer', + Bar.SLEFT: 'Side left', Bar.SRIGHT: 'Side right'} + SEQ_TO_KEY = {159: curses.KEY_F1, 160: curses.KEY_F2, 161: curses.KEY_F3, + 316: curses.KEY_SRIGHT, 317: curses.KEY_SLEFT, + 151: curses.KEY_HOME, 266: curses.KEY_HOME, + 149: curses.KEY_END, 269: curses.KEY_END} + + def __init__(self, color=2, mouse=True): + os.environ['ESCDELAY'] = '25' + self.screen = curses.initscr() + self.screen.nodelay(True) + self.screen.scrollok(1) + if mouse: + try: + curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.BUTTON1_CLICKED | self.KEY_MOUSE | + functools.reduce(operator.or_, list(self.SCROLL_UP)) | + functools.reduce(operator.or_, list(self.SCROLL_DOWN))) + except: + self.KEY_MOUSE = 0 + else: + self.KEY_MOUSE = 0 + try: + curses.curs_set(0) + except: # terminal doesn't support visibility requests + pass + self.screen.border(0) + self.screen.clear() + self.screen.refresh() + self.index = 0 + self.top_line_num = 0 + self.focus_line_num = 0 + self.lines, self.cols = curses.LINES - 2, curses.COLS - 1 + self.info, self.menu = str, str + self.mode_keys = self.get_mode_keys() + self.menu_titles = ['{} Output'.format(self.mode_keys[0]), + '{} Input'.format(self.mode_keys[1]), + '{} Cards'.format(self.mode_keys[2])] + self.data = [] + self.mode = {0: 1, 1: 0, 2: 0} + self.modes_data = [[[], 0, 0] for i in range(6)] + self.active_mode = 0 + self.old_mode = 0 + self.change_mode_allowed = True + self.n_lines = 0 + self.color_mode = color + if color in (1, 2) and curses.has_colors(): + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_GREEN, -1) + curses.init_pair(2, curses.COLOR_YELLOW, -1) + curses.init_pair(3, curses.COLOR_RED, -1) + self.green = curses.color_pair(1) + self.yellow = curses.color_pair(2) + self.red = curses.color_pair(3) + n = 7 if curses.COLORS < 256 else 67 + curses.init_pair(n, n - 1, -1) + self.muted_color = curses.color_pair(n) + if curses.COLORS < 256: + self.gray_gradient = [curses.A_NORMAL] * 3 + else: + try: + curses.init_pair(240, 240, -1) + curses.init_pair(243, 243, -1) + curses.init_pair(246, 246, -1) + self.gray_gradient = [curses.color_pair(240), + curses.color_pair(243), + curses.color_pair(246)] + except: + self.gray_gradient = [curses.A_NORMAL] * 3 + else: + # if term has colors start them regardless of --color to avoid weird backgrounds on some terminals + if curses.has_colors(): + curses.start_color() + curses.use_default_colors() + self.gray_gradient = [curses.A_NORMAL] * 3 + self.green = self.yellow = self.red = self.muted_color = curses.A_NORMAL + self.gradient = [self.green, self.yellow, self.red] + self.submenu_data = [] + self.submenu_width = 30 + self.submenu_show = False + self.submenu = curses.newwin(curses.LINES, 0, 0, 0) + self.helpwin_show = False + self.helpwin = curses.newwin(14, 50, 0, 0) + try: + self.helpwin.mvwin((curses.LINES // 2) - 7, (curses.COLS // 2) - 25) + except: + pass + self.selected = None + self.action = None + self.server_info = None + self.ev = threading.Event() + + def getch(self): + # blocking getch, can be 'interrupted' by ev.set + self.ev.wait() + self.ev.clear() + c = self.screen.getch() + if c == 27: # collect escape sequences as a single key + seq_sum = sum(takewhile(lambda x: x != -1, [self.screen.getch() for _ in range(5)])) + c = self.SEQ_TO_KEY.get(seq_sum, seq_sum + 128 if seq_sum else 27) + return c + + def pregetcher(self): + # because curses.getch doesn't work well with threads + while True: + select([sys.stdin], [], [], 10) + self.ev.set() + + def wake_cb(self, *_): + self.ev.set() + + def display_line(self, index, line, mod=curses.A_NORMAL, win=None): + shift, win = 0, win or self.screen + for i in line.split('\n'): + parts = i.rsplit('|') + head = ''.join(parts[:-1]) + tail = int(parts[-1] or 0) + try: + win.addstr(index, shift, head, tail | mod) + except: + win.addstr(min(curses.LINES - 1, index), min(curses.COLS - 1, shift), head, tail | mod) + shift += len(head) + + def change_mode(self, mode): + if not self.change_mode_allowed: + return + self.modes_data[self.active_mode][1] = self.focus_line_num + self.modes_data[self.active_mode][2] = self.top_line_num + self.old_mode = self.active_mode + self.mode = self.mode.fromkeys(self.mode, 0) + self.mode[mode] = 1 + self.focus_line_num = self.modes_data[mode][1] + self.top_line_num = self.modes_data[mode][2] + self.active_mode = mode + self.get_data() + + def cycle_mode(self, direction=1): + for mode, active in self.mode.items(): + if active == 1: + self.change_mode((mode + direction) % 3) + return + + def update_menu(self): + if self.change_mode_allowed: + self.menu = '{}|{}\n {}|{}\n {}|{}\n {:>{}}|{}'.format( + self.menu_titles[0], curses.A_BOLD if self.mode[0] else curses.A_DIM, + self.menu_titles[1], curses.A_BOLD if self.mode[1] else curses.A_DIM, + self.menu_titles[2], curses.A_BOLD if self.mode[2] else curses.A_DIM, + "? - help", self.cols - 30, curses.A_DIM) + else: + selected = 'output' if type(self.selected[0].pa) is PulseSinkInputInfo else 'input' + self.menu = "Select new {} device:|{}".format(selected, curses.A_NORMAL) + + def update_info(self): + focus, bottom = self.focus_line_num + self.top_line_num + 1, self.top_line_num + self.lines + try: + bar, side = self.data[focus - 1][0], self.data[focus - 1][1] + except IndexError: + self.focus_line_num, self.top_line_num = 0, 0 + for _ in range(len(self.data)): self.scroll(self.UP) + return + if side is Bar.NONE: + self.info = str + return + side = 'All' if bar.locked else 'Mono' if bar.channels == 1 else self.SIDES[side] + more = '↕' if bottom < self.n_lines and self.top_line_num > 0 else '↑' if self.top_line_num > 0 else '↓' if bottom < self.n_lines else ' ' + name = '{}: {}'.format(bar.name, side) + if len(name) > self.cols - 8: + name = '{}: {}'.format(bar.name[:self.cols - (10 + len(side))].strip(), side) + locked = '{}|{}'.format(CFG.style.info_locked, self.red) if bar.locked else '{}|{}'.format(CFG.style.info_unlocked, curses.A_DIM) + muted = '{}|{}'.format(CFG.style.info_muted, self.red) if bar.muted else '{}|{}'.format(CFG.style.info_unmuted, curses.A_DIM) + self.info = '{}\n {}\n {}|{}\n{:>{}}|0'.format(locked, muted, name, curses.A_NORMAL, more, self.cols - len(name) - 5) + + def run_mouse(self): + try: + _, x, y, _, c = curses.getmouse() + if c & curses.BUTTON1_CLICKED: + if y > 0: + top, bottom = self.top_line_num, len(self.data[self.top_line_num:self.top_line_num + self.lines]) - 1 + if y - 1 <= bottom: + self.focus_line_num = max(top, min(bottom, y - 1)) + else: + f1 = len(self.menu_titles[0]) + 1 # 1 is 'spacing' after the title + f2 = f1 + len(self.menu_titles[1]) + 2 + f3 = f2 + len(self.menu_titles[2]) + 3 + if x in range(0, f1): + self.change_mode(0) + elif x in range(f1, f2): + self.change_mode(1) + elif x in range(f2, f3): + self.change_mode(2) + return c + except curses.error: + return None + + def resize(self): + curses.COLS, curses.LINES = get_terminal_size() + curses.resizeterm(curses.LINES, curses.COLS) + self.submenu.resize(curses.LINES, self.submenu_width + 1) + if self.submenu_show: + self.submenu_show = False + self.focus_line_num = self.modes_data[5][1] + self.top_line_num = self.modes_data[5][2] + try: + self.helpwin.resize(14, 50) + self.helpwin.mvwin((curses.LINES // 2) - 7, (curses.COLS // 2) - 25) + except curses.error: + pass + self.helpwin_show = False + self.lines, self.cols = curses.LINES - 2, curses.COLS - 1 + self.ev.set() + + def terminate(self): + # if ^C pressed while sleeping in reconnect wrapper.restore won't be called + # so have to restore it manually here + self.screen.keypad(0) + curses.echo() + curses.nocbreak() + curses.endwin() + sys.exit() + + def reconnect(self): + self.focus_line_num = 0 + self.menu = self.info = str + self.data = [(Bar('PA - Connection refused.\nTrying to reconnect.'), Bar.NONE, 0)] + while not PULSE.connected: + self.display() + if self.screen.getch() in CFG.keys.quit: sys.exit() + PULSE.reconnect() + sleep(0.5) + PULSE.subscribe(self.wake_cb) + self.ev.set() + + def run(self, _): + signal.signal(signal.SIGINT, lambda s, f: self.terminate()) + signal.signal(signal.SIGTERM, lambda s, f: self.terminate()) + signal.signal(signal.SIGWINCH, lambda s, f: self.resize()) + threading.Thread(target=self.pregetcher, daemon=True).start() + PULSE.subscribe(self.wake_cb) + self.ev.set() + while True: + try: + if not self.submenu_show: + try: + self.get_data() + except RuntimeError: + self.reconnect() + except IndexError: + self.scroll(self.UP) + if self.helpwin_show: + self.display_helpwin() + self.run_helpwin() + continue + self.update_menu() + self.update_info() + self.display() + elif self.change_mode_allowed: + self.display_submenu() + self.run_submenu() + continue + except (curses.error, IndexError, ValueError) as e: + self.screen.erase() + self.screen.addstr("Terminal *might* be too small {}:{}\n".format(curses.LINES, curses.COLS)) + self.screen.addstr("{}\n{}\n".format(str(self.mode), str(e))) + self.screen.addstr(str(traceback.extract_tb(e.__traceback__))) + + c = self.getch() + if c == -1: continue + + focus = self.top_line_num + self.focus_line_num + bar, side = self.data[focus][0], self.data[focus][1] + + if c == self.KEY_MOUSE: + c = self.run_mouse() or c + + if c in CFG.keys.mode1: + self.change_mode(0) + elif c in CFG.keys.mode2: + self.change_mode(1) + elif c in CFG.keys.mode3: + self.change_mode(2) + elif c == ord('?'): + self.helpwin_show = True + elif c == ord('\n'): + if not self.submenu_show and self.change_mode_allowed and side != Bar.NONE: + self.selected = self.data[focus] + if type(self.selected[0].pa) in (PulseSinkInfo, PulseSourceInfo): + self.submenu_data = ['Suspend', 'Resume', 'Set as default'] + if self.selected[0].pa.n_ports: + self.submenu_data.append('Set port') + elif type(self.selected[0].pa) is PulseCard: + self.fill_submenu_pa(target='profile', off=0, hide=CFG.ui.hide_unavailable_profiles) + else: + self.submenu_data = ['Move', 'Kill'] + self.submenu_show = True + self.modes_data[5][0] = 0 + self.modes_data[5][1] = self.focus_line_num + self.modes_data[5][2] = self.top_line_num + self.focus_line_num = self.top_line_num = 0 + self.n_lines = len(self.submenu_data) + self.resize_submenu() + elif not self.change_mode_allowed: + self.submenu_show = False + self.change_mode_allowed = True + if self.action == 'Move': + if type(self.selected[0].pa) is PulseSinkInputInfo: + PULSE.sink_input_move(self.selected[0].index, self.data[focus][0].pa.index) + elif type(self.selected[0].pa) is PulseSourceOutputInfo: + PULSE.source_output_move(self.selected[0].index, self.data[focus][0].pa.index) + self.change_mode(self.old_mode) + self.focus_line_num = self.modes_data[5][1] + self.top_line_num = self.modes_data[5][2] + else: + self.change_mode(self.old_mode) + elif c in CFG.keys.next_mode: + self.cycle_mode() + elif c in CFG.keys.prev_mode: + self.cycle_mode(direction=-1) + elif c in CFG.keys.quit: + if not self.change_mode_allowed: + self.submenu_show = False + self.change_mode_allowed = True + self.change_mode(self.old_mode) + self.focus_line_num = self.modes_data[5][1] + self.top_line_num = self.modes_data[5][2] + else: + sys.exit() + + if side is Bar.NONE: + continue + + if c in CFG.keys.up: + if bar.locked: + if self.data[focus][1] == 0: + n = 1 + else: + n = self.data[focus][1] + 1 + for _ in range(n): self.scroll(self.UP) + else: + self.scroll(self.UP) + if not self.data[self.top_line_num + self.focus_line_num][0]: + self.scroll(self.UP) + elif c in CFG.keys.down: + if bar.locked: + if self.data[focus][1] == self.data[focus][3] - 1: + n = 1 + else: + n = ((self.data[focus][3] - 1) - self.data[focus][1]) + 1 + for _ in range(n): self.scroll(self.DOWN) + else: + self.scroll(self.DOWN) + if not self.data[self.top_line_num + self.focus_line_num][0]: + self.scroll(self.DOWN) + elif c in CFG.keys.top: + self.scroll_first() + elif c in CFG.keys.bottom: + self.scroll_last() + + elif c in CFG.keys.mute: + bar.mute_toggle() + elif c in CFG.keys.lock: + bar.lock_toggle() + elif c in CFG.keys.left or any([c & i for i in self.SCROLL_DOWN]): + bar.move(-CFG.general.step, side) + elif c in CFG.keys.right or any([c & i for i in self.SCROLL_UP]): + bar.move(CFG.general.step, side) + elif c in CFG.keys.left_big: + bar.move(-CFG.general.step_big, side) + elif c in CFG.keys.right_big: + bar.move(CFG.general.step_big, side) + elif c in self.DIGITS: + percent = int(chr(c)) * 10 + bar.set(100 if percent == 0 else percent, side) + + def fill_submenu_pa(self, target, off, hide): + self.submenu_data = [] + active = getattr(self.selected[0].pa, "active_" + target).description.decode() + for i in getattr(self.selected[0].pa, target + "s"): + description = i.description.decode() + if active == description: + self.submenu_data.append(' {}|{}'.format(description, self.green)) + else: + if hide and i.available == off: continue + self.submenu_data.append(' {}|{}'.format(description, curses.A_DIM if i.available == off else 0)) + + def build(self, target, devices, streams): + tmp = [] + index = 0 + for device in devices: + index += device.volume.channels + stream_index = device.volume.channels + tmp.append([device, device.volume.channels, index, stream_index]) + device_index = len(tmp) - 1 + for stream in streams: + if stream.owner == device.index: + index += stream.volume.channels + stream_index += stream.volume.channels + tmp.append([stream, -1, index, stream_index]) + tmp[device_index][1] += stream.volume.channels + tmp[-1][1] = tmp[device_index][1] + for s in tmp: + found = False + for i, data in enumerate(target): + if s[0].index == data[2] and type(s[0]) == type(data[0].pa): + found = True + data[0].poll_data(s[0], s[1], s[3]) + y = s[2] - (data[3] - data[1]) + target[i], target[y] = target[y], target[i] + if not found: + bar = Bar(s[0]) + bar.owned = s[1] + bar.stream_index = s[3] + for c in range(s[0].volume.channels): + target.append((bar, c, s[0].index, s[0].volume.channels)) + for i in reversed(range(len(target))): + data = target[i] + for s in tmp: + if s[0].index == data[2] and type(s[0]) == type(data[0].pa): + y = s[2] - (data[3] - data[1]) + target[i], target[y] = target[y], target[i] + break + else: + del target[i] + if self.focus_line_num + self.top_line_num >= i: + self.scroll(self.UP) + return target + + def add_spacers(self, f): + tmp = [] + l = len(f) + for i, s in enumerate(f): + tmp.append(s) + if s[0].stream_index == s[0].owned and s[1] == s[0].channels - 1 and i != l - 1: + tmp.append((None, -1, 0, 0)) + return tmp + + def get_data(self): + if self.mode[0]: + self.data = self.build(self.modes_data[0][0], PULSE.sink_list(), PULSE.sink_input_list()) + self.data = self.add_spacers(self.data) + elif self.mode[1]: + ids = (b'org.PulseAudio.pavucontrol', b'org.gnome.VolumeControl', b'org.kde.kmixd', b'pulsemixer') + source_output_list = [s for s in PULSE.source_output_list() if s.application_id not in ids] + self.data = self.build(self.modes_data[1][0], PULSE.source_list(), source_output_list) + self.data = self.add_spacers(self.data) + elif self.mode[2]: + self.data = self.build(self.modes_data[2][0], PULSE.card_list(), []) + elif type(self.selected[0].pa) is PulseSinkInputInfo: + self.data = self.build(self.modes_data[3][0], PULSE.sink_list(), []) + elif type(self.selected[0].pa) is PulseSourceOutputInfo: + self.data = self.build(self.modes_data[4][0], PULSE.source_list(), []) + self.server_info = PULSE.get_server_info() + self.n_lines = len(self.data) + if not self.n_lines: + self.focus_line_num = 0 + self.data.append((Bar('no data'), Bar.NONE, 0)) + if not self.data[self.top_line_num + self.focus_line_num][0]: + self.scroll(self.UP) + + def display(self): + self.screen.erase() + top = self.top_line_num + bottom = self.top_line_num + self.lines + self.display_line(0, self.menu) + for index, line in enumerate(self.data[top:bottom]): + bar, bartype = line[0], line[1] + if not bar: + self.screen.addstr(index + 1, 0, '', curses.A_DIM) + continue + elif bartype is Bar.NONE: + for i, name in enumerate(bar.name.split('\n')): + self.screen.addstr((self.lines // 2) + i, (self.cols // 2) - len(name) // 2, name, curses.A_DIM) + break + + # hightlight lines from same bar + same = [] + for i, v in enumerate(self.data[top:bottom]): + if v[0] is self.data[self.top_line_num + self.focus_line_num][0]: + same.append(v[0]) + + tree = ' ' + if bar.owner == -1 and bar.owned > bar.channels: + tree = ' │' + if bar.owner != -1: + tree = ' │' + if bartype == Bar.LEFT: + if bar.owner == -1: + tree = ' ' + if bar.owner != -1: + tree = ' ├─' + if bar.stream_index == bar.owned: + tree = ' └─' + if bar.channels != 1: + brackets = [CFG.style.bar_top_left, CFG.style.bar_top_right] + else: + brackets = [CFG.style.bar_left_mono, CFG.style.bar_right_mono] + elif bartype == bar.channels - 1: + if bar.stream_index == bar.owned: + tree = ' ' + brackets = [CFG.style.bar_bottom_left, CFG.style.bar_bottom_right] + else: + if bar.stream_index == bar.owned: + tree = ' ' + brackets = ['├', '┤'] + + # focus current lines + focus_hl, bracket_hl, arrow, gradient = 0, 0, CFG.style.arrow, self.gradient + if index == self.focus_line_num: + focus_hl = bracket_hl = curses.A_BOLD + arrow = CFG.style.arrow_focused + elif bar in same: + focus_hl = curses.A_BOLD + if bar.locked: + bracket_hl = curses.A_BOLD + arrow = CFG.style.arrow_locked + elif not bar.muted and self.color_mode != 2: + gradient = self.gray_gradient + + # highlight chosen sink/source or muted + if not self.change_mode_allowed and self.selected[0].owner == self.data[index][0].index: + bracket_hl = self.green | bracket_hl + if bar.muted: + focus_hl = focus_hl | self.muted_color + elif bar.muted: + bracket_hl = bracket_hl | self.red + focus_hl = focus_hl | self.muted_color + + off = 6 * (self.cols // (43 if self.cols <= 60 else 25)) - len(tree) + cols = self.cols - 31 - off - len(tree) + vol = list(CFG.style.bar_off * (cols - (cols % 3 != 0))) + n = int(len(vol) * bar.volume[bartype] / bar.maxsize) + if bar.muted: + vol[:n] = CFG.style.bar_on_muted * n + else: + vol[:n] = CFG.style.bar_on * n + vol = ''.join(vol) + if bartype is Bar.LEFT: + if bar.pa.name in (self.server_info.default_sink_name, self.server_info.default_source_name): + tree = CFG.style.default_stream + name = '{}{}'.format(bar.name, bar.media_name) + if bar.media_name_wide and len(bar.name) + sum(bar.media_name_widths) > 20 + off: + to_remove, widths = 0, [1] * len(bar.name) + bar.media_name_widths + while sum(widths) > 20 + off: + widths.pop() + to_remove += 1 + name = name[:-to_remove].strip() + '~' + elif len(name) > 20 + off: + name = name[:20 + off].strip() + '~' + line = '{:<{}}|{}\n {:<3}|{}\n '.format(name, 22 + off, focus_hl, + '' if type(bar.pa) is PulseCard else bar.volume[0], + focus_hl) + elif bartype is Bar.RIGHT: + line = '{:>{}}|{}\n {}|{}\n {:<3}|{}\n '.format( + '', 21 + off, self.red if bar.locked else curses.A_DIM, + '', self.red if bar.muted else curses.A_DIM, + bar.volume[bartype], focus_hl) + else: + line = '{:>{}}{:<3}|{}\n '.format('', 23 + off, bar.volume[bartype], focus_hl) + if type(bar.pa) is PulseCard: + volbar = '\n{}|0'.format(bar.pa.active_profile.description.decode()[:len(vol)]) + brackets = [' ', ' '] + else: + volbar = '' + for i, v in enumerate(re.findall('.{{{}}}'.format((len(vol) // 3)), vol)): + volbar += '\n{}|{}'.format(v, gradient[i] | focus_hl) + line += '{:>1}|{}\n{}|{}{}\n{}|{}\n{}|{}'.format(arrow, curses.A_BOLD, + brackets[0], bracket_hl, + volbar, + brackets[1], bracket_hl, + arrow, curses.A_BOLD) + self.display_line(index + 1, tree + "|0\n" + line) + self.display_line(self.lines + 1, self.info) + self.screen.refresh() + + def get_mode_keys(self): + return [re.compile(r'[()]|KEY_').sub('', curses.keyname(k[0]).decode('utf-8')) for k in [ + CFG.keys.mode1, CFG.keys.mode2, CFG.keys.mode3]] + + def display_helpwin(self): + doc = (('j k ↑ ↓', 'Navigation'), + ('h l ← →', 'Change volume'), + ('H L Shift← Shift→', 'Change volume by 10'), + ('1 2 3 .. 8 9 0', 'Set volume to 10%-100%'), + ('m', 'Mute/Unmute'), + ('Space', 'Lock/Unlock channels'), + ('Enter', 'Context menu'), + ('{} {} {}'.format(*self.mode_keys), 'Change modes'), + ('Tab Shift Tab', 'Next/Previous mode'), + ('Mouse click', 'Select device or mode'), + ('Mouse wheel', 'Volume change'), + ('Esc q', 'Quit')) + win_width, desc_maxlen = self.helpwin.getmaxyx()[1] - 4, max(len(x[1]) for x in doc) + self.helpwin.erase() + for i, s in enumerate(doc): + self.helpwin.addstr(i + 1, 2, s[0] + ' ' * (win_width - desc_maxlen - len(s[0])) + s[1]) + self.helpwin.border() + self.helpwin.refresh() + + def run_helpwin(self): + if self.getch() in CFG.keys.quit: + self.helpwin_show = False + + def resize_submenu(self): + key = lambda x: len(x.split('|')[0]) + self.submenu_width = min(self.cols + 1, max(30, len(max(self.submenu_data, key=key).split('|')[0]) + 3)) + self.submenu.resize(curses.LINES, self.submenu_width + 1) + + def display_submenu(self): + top = self.top_line_num + bottom = self.top_line_num + self.lines + 2 + self.submenu.erase() + self.submenu.vline(0, self.submenu_width, curses.ACS_VLINE, curses.LINES) + for index, line in enumerate(self.submenu_data[top:bottom]): + if index == self.focus_line_num: + focus_hl = curses.A_BOLD + arrow = CFG.style.arrow_focused + else: + focus_hl = curses.A_NORMAL + arrow = ' ' + if '|' in line: + self.display_line(index, ' {}|0\n'.format(arrow) + line, focus_hl, win=self.submenu) + else: + self.submenu.addstr(index, 1, arrow + ' ' + line, focus_hl) + self.submenu.refresh() + + def run_submenu(self): + c = self.getch() + if c in CFG.keys.quit: + self.submenu_show = False + self.focus_line_num = self.modes_data[5][1] + self.top_line_num = self.modes_data[5][2] + + elif c in CFG.keys.up: + self.scroll(self.UP, cycle=True) + elif c in CFG.keys.down: + self.scroll(self.DOWN, cycle=True) + elif c in CFG.keys.top: + self.scroll_first() + elif c in CFG.keys.bottom: + self.scroll_last() + + elif c == ord('\n'): + focus = self.focus_line_num + self.top_line_num + self.action = self.submenu_data[focus] + if self.action == 'Move': + if self.active_mode == 0: + self.change_mode(3) + elif self.active_mode == 1: + self.change_mode(4) + self.change_mode_allowed = self.submenu_show = False + return + elif self.action == 'Kill': + try: + PULSE.kill_client(self.selected[0].pa.client.index) + except: + if type(self.selected[0].pa) is PulseSinkInputInfo: + PULSE.kill_sink(self.selected[2]) + else: + PULSE.kill_source(self.selected[2]) + elif self.action == 'Suspend': + if type(self.selected[0].pa) is PulseSinkInfo: + PULSE.sink_suspend(self.selected[2], 1) + else: + PULSE.source_suspend(self.selected[2], 1) + elif self.action == 'Resume': + if type(self.selected[0].pa) is PulseSinkInfo: + PULSE.sink_suspend(self.selected[2], 0) + else: + PULSE.source_suspend(self.selected[2], 0) + elif self.action == 'Set as default': + if type(self.selected[0].pa) is PulseSinkInfo: + PULSE.set_default_sink(self.selected[0].pa.name) + else: + PULSE.set_default_source(self.selected[0].pa.name) + elif self.action == 'Set port': + self.fill_submenu_pa(target='port', off=1, hide=CFG.ui.hide_unavailable_ports) + self.focus_line_num = self.top_line_num = 0 + self.n_lines = len(self.submenu_data) + return + else: + index = self.selected[0].pa.index + description = self.action.rsplit('|')[0].strip() + get_name = lambda desc, l: next(filter(lambda x: x.description.decode() == desc, l)).name + if type(self.selected[0].pa) is PulseSinkInfo: + PULSE.set_sink_port(index, get_name(description, self.selected[0].pa.ports)) + elif type(self.selected[0].pa) is PulseSourceInfo: + PULSE.set_source_port(index, get_name(description, self.selected[0].pa.ports)) + elif type(self.selected[0].pa) is PulseCard: + PULSE.set_card_profile(index, get_name(description, self.selected[0].pa.profiles)) + self.change_mode_allowed = True + self.submenu_show = False + self.focus_line_num = self.modes_data[5][1] + self.top_line_num = self.modes_data[5][2] + + def scroll(self, n, cycle=False): + next_line_num = self.focus_line_num + n + + if n == self.UP and self.focus_line_num == 0 and self.top_line_num != 0: + self.top_line_num += self.UP + return + elif n == self.DOWN and next_line_num == self.lines and (self.top_line_num + self.lines) != self.n_lines: + self.top_line_num += self.DOWN + return + + if n == self.UP: + if self.top_line_num != 0 or self.focus_line_num != 0: + self.focus_line_num = next_line_num + elif cycle: + self.scroll_last() + elif n == self.DOWN and self.focus_line_num != self.lines: + if self.top_line_num + self.focus_line_num + 1 != self.n_lines: + self.focus_line_num = next_line_num + elif cycle: + self.scroll_first() + + def scroll_first(self): + for _ in range(self.n_lines): self.scroll(self.UP) + + def scroll_last(self): + for _ in range(self.n_lines): self.scroll(self.DOWN) + + +class Config(): + + def __init__(self): + + class General: + step = 1 + step_big = 10 + server = None + + self._more_keys = {'KEY_ESC': 27, 'KEY_TAB': 9, 'C': -96, 'M': 128} + class Keys: + up = [ord('k'), curses.KEY_UP, curses.KEY_PPAGE] + down = [ord('j'), curses.KEY_DOWN, curses.KEY_NPAGE] + left = [ord('h'), curses.KEY_LEFT] + right = [ord('l'), curses.KEY_RIGHT] + left_big = [ord('H'), curses.KEY_SLEFT] + right_big = [ord('L'), curses.KEY_SRIGHT] + top = [ord('g'), curses.KEY_HOME] + bottom = [ord('G'), curses.KEY_END] + mode1 = [curses.KEY_F1] + mode2 = [curses.KEY_F2] + mode3 = [curses.KEY_F3] + next_mode = [self._more_keys['KEY_TAB']] + prev_mode = [curses.KEY_BTAB] + mute = [ord('m')] + lock = [ord(' ')] + quit = [ord('q'), self._more_keys['KEY_ESC']] + + class UI: + hide_unavailable_profiles = False + hide_unavailable_ports = False + color = 2 + mouse = True + + class Style: + _bar_style = os.getenv('PULSEMIXER_BAR_STYLE', '┌╶┐╴└┘▮▯- ──').ljust(12, '?') + bar_top_left = _bar_style[0] + bar_left_mono = _bar_style[1] + bar_top_right = _bar_style[2] + bar_right_mono = _bar_style[3] + bar_bottom_left = _bar_style[4] + bar_bottom_right = _bar_style[5] + bar_on = _bar_style[6] + bar_on_muted = _bar_style[7] + bar_off = _bar_style[8] + arrow = _bar_style[9] + arrow_focused = _bar_style[10] + arrow_locked = _bar_style[11] + default_stream = '*' + info_locked = 'L' + info_unlocked = 'U' + info_muted = 'M' + info_unmuted = 'M' + + self.general = General() + self.keys = Keys() + self.ui = UI() + self.style = Style() + self.renames = {} + self.path = os.getenv("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")) + '/pulsemixer.cfg' + + def save(self): + default = ''' + ;; Goes into ~/.config/pulsemixer.cfg, $XDG_CONFIG_HOME respected + ;; Everything that starts with "#" or ";" is a comment + ;; For the option to take effect simply uncomment it + [general] + step = 1 + step-big = 10 + ; server = + [keys] + ;; To bind "special keys" such as arrows see "Key constant" table in + ;; https://docs.python.org/3/library/curses.html#constants + ; up = k, KEY_UP, KEY_PPAGE + ; down = j, KEY_DOWN, KEY_NPAGE + ; left = h, KEY_LEFT + ; right = l, KEY_RIGHT + ; left-big = H, KEY_SLEFT + ; right-big = L, KEY_SRIGHT + ; top = g, KEY_HOME + ; bottom = G, KEY_END + ; mode1 = KEY_F1 + ; mode2 = KEY_F2 + ; mode3 = KEY_F3 + ; next-mode = KEY_TAB + ; prev-mode = KEY_BTAB + ; mute = m + ; lock = ' ' ; 'space', quotes are stripped + ; quit = q, KEY_ESC + [ui] + ; hide-unavailable-profiles = no + ; hide-unavailable-ports = no + ; color = 2 ; same as --color, 0 no color, 1 color currently selected, 2 full-color + ; mouse = yes + [style] + ;; Pulsemixer will use these characters to draw interface + ;; Single characters only + ; bar-top-left = ┌ + ; bar-left-mono = ╶ + ; bar-top-right = ┐ + ; bar-right-mono = ╴ + ; bar-bottom-left = └ + ; bar-bottom-right = ┘ + ; bar-on = ▮ + ; bar-on-muted = ▯ + ; bar-off = - + ; arrow = ' ' + ; arrow-focused = ─ + ; arrow-locked = ─ + ; default-stream = * + ; info-locked = L + ; info-unlocked = U + ; info-muted = M ; 🔇 + ; info-unmuted = M ; 🔉 + [renames] + ;; Changes stream names in interactive mode, regular expression are supported + ;; https://docs.python.org/3/library/re.html#regular-expression-syntax + ; 'default name example' = 'new name' + ; '(?i)built-in .* audio' = 'Audio Controller' + ; 'AudioIPC Server' = 'Firefox' + ''' + directory = self.path.rsplit('/', 1)[0] + if not os.path.exists(directory): + os.makedirs(directory) + with open(self.path, 'w') as configfile: + configfile.write(dedent(default).strip()) + return self.path + + def load(self): + parser = ConfigParser(inline_comment_prefixes=('#', ';'), empty_lines_in_values=False) + parser.optionxform = str # keep case of keys, lowered() later + parser.NONSPACECRE = re.compile(r"") # ignore leading whitespace + if not parser.read(self.path): return self + if parser.has_section('renames'): + self.renames = {re.compile(k.strip('"\'') + r'\Z'):v.strip('"\'') for k, v in parser.items('renames')} + parser.remove_section('renames') + def getkeys(s, k): + keys = [] + for i in parser.get(s, k).strip(',').split(','): + i = i.strip().strip('"\'') # in case 'key' is encountered + if len(i) > 1: + if i.startswith(('C-', 'M-')): + mod, key = i.split('-') + key = self._more_keys[mod] + ord(key.lower()) + else: + key = getattr(curses, i, self._more_keys.get(i)) + else: + key = ord(i) + if key is None: raise Exception("module 'curses' has no attribute {}".format(i)) + keys.append(key) + return keys + get = {str: lambda s, k: parser.get(s, k).strip('"\''), + None.__class__: lambda s, k: parser.get(s, k).encode(), # server + list: getkeys, bool: parser.getboolean, + int: parser.getint, float: parser.getfloat} + for section in parser.sections(): + for key in parser[section]: + pykey = key.lower().replace('-', '_') + pyval = getattr(getattr(self, section.lower()), pykey) + val = get[type(pyval)](section, key) + setattr(getattr(self, section.lower()), pykey, val) + return self + + +PULSE = CFG = None + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], "hvl", + ["help", "version", "list", "list-sinks", "list-sources", "id=", + "set-volume=", "set-volume-all=", "change-volume=", "max-volume=", + "get-mute", "toggle-mute", "mute", "unmute", "get-volume", + "color=", "server=", "no-mouse", "create-config"]) + except getopt.GetoptError as e: + sys.exit("ERR: {}".format(e)) + assert args == [], sys.exit('ERR: {} not not recognized'.format(' '.join(args).strip())) + dopts = dict(opts) + + if '-h' in dopts or '--help' in dopts: + sys.exit(print(__doc__)) + if '-v' in dopts or '--version' in dopts: + sys.exit(print(VERSION)) + if '--create-config' in dopts: + try: + sys.exit(print(Config().save())) + except Exception as e: # permission denied and such + sys.exit('ERR: {}'.format(e)) + + global PULSE, CFG + try: + CFG = Config().load() + except Exception as e: + sys.exit('ERR: {}'.format(e)) + CFG.general.server = dopts.get('--server', '').encode() or CFG.general.server + CFG.ui.mouse = False if '--no-mouse' in dopts else CFG.ui.mouse + try: + CFG.ui.color = min(2, max(0, int(dopts.get('--color', '') or CFG.ui.color))) + except: + sys.exit('ERR: color must be a number') + signal.signal(signal.SIGINT, lambda s, f: sys.exit(1)) + PULSE = Pulse('pulsemixer', CFG.general.server) + + noninteractive_opts = dict(dopts) + noninteractive_opts.pop('--server', None) + noninteractive_opts.pop('--color', None) + noninteractive_opts.pop('--no-mouse', None) + if not noninteractive_opts: + if not sys.stdout.isatty(): sys.exit('ERR: output is not a tty-like device') + title = 'pulsemixer {}'.format(CFG.general.server.decode() if CFG.general.server else '') + print('\033]2;{}\007'.format(title.strip()), end='', flush=True) + curses.wrapper(Screen(CFG.ui.color, CFG.ui.mouse).run) + + sinks = PULSE.sink_list() + sink_inputs = PULSE.sink_input_list() + sources = PULSE.source_list() + source_outputs = PULSE.source_output_list() + server_info = PULSE.get_server_info() + streams = OrderedDict() + for k, v in (('sink-', sinks), ('sink-input-', sink_inputs), ('source-', sources), ('source-output-', source_outputs)): + for stream in v: streams[k + str(stream.index)] = stream + + check_n = lambda x, err: x.strip('+-').isdigit() or sys.exit('ERR: {} must be a number'.format(err)) + check_id = lambda x: x in streams or sys.exit('ERR: No such ID: ' + str(x)) + from_old_id = lambda index: next((k for k in streams if k.rsplit('-', 1)[-1] == index), index) + print_default = lambda x, y: print(x == y and ', Default' or '') + + if '--id' in dopts: + index = [i for i in opts if '--id' in i][0][1] + if index.isdigit(): index = from_old_id(index) + else: + index = 'sink-{}'.format([s.index for s in sinks if s.name == server_info.default_sink_name][0]) + check_id(index) + max_volume = 150 + + for opt, arg in opts: + if opt == '--id': + index = arg + if index.isdigit(): index = from_old_id(index) + check_id(index) + max_volume = 150 # reset for each new id + + elif opt in ('-l', '--list'): + for sink in sinks: + print("Sink:\t\t", sink, end='') + print_default(sink.name, server_info.default_sink_name) + for sink in sink_inputs: + print("Sink input:\t", sink) + for source in sources: + print("Source:\t\t", source, end='') + print_default(source.name, server_info.default_source_name) + for source in source_outputs: + print("Source output:\t", source) + + elif opt == '--list-sinks': + for sink in sinks: + print("Sink:\t\t", sink, end='') + print_default(sink.name, server_info.default_sink_name) + for sink in sink_inputs: + print("Sink input:\t", sink) + + elif opt == '--list-sources': + for source in sources: + print("Source:\t\t", source, end='') + print_default(source.name, server_info.default_source_name) + for source in source_outputs: + print("Source output:\t", source) + + elif opt == '--get-mute': + print(streams[index].mute) + + elif opt == '--mute': + PULSE.mute_stream(streams[index]) + + elif opt == '--unmute': + PULSE.unmute_stream(streams[index]) + + elif opt == '--toggle-mute': + PULSE.unmute_stream(streams[index]) if streams[index].mute else PULSE.mute_stream(streams[index]) + + elif opt == '--get-volume': + print(*streams[index].volume.values) + + elif opt == '--set-volume': + check_n(arg, err='volume') + vol = streams[index].volume + for i, _ in enumerate(vol.values): + vol.values[i] = int(arg) + PULSE.set_volume(streams[index], vol) + + elif opt == '--set-volume-all': + vol = streams[index].volume + arg = arg.strip(':').split(':') + if len(arg) != len(vol.values): + sys.exit("ERR: Specified volumes not equal to the number of channels in the stream") + for i, _ in enumerate(vol.values): + check_n(arg[i], err='volume') + vol.values[i] = int(arg[i]) + PULSE.set_volume(streams[index], vol) + + elif opt == '--change-volume': + check_n(arg, err='volume') + vol = streams[index].volume + for i, _ in enumerate(vol.values): + vol.values[i] = min(vol.values[i] + int(arg), max_volume) + PULSE.set_volume(streams[index], vol) + + elif opt == '--max-volume': + check_n(arg, err='max volume') + max_volume = int(arg) + vol = streams[index].volume + for i, _ in enumerate(vol.values): + vol.values[i] = min(vol.values[i], max_volume) + PULSE.set_volume(streams[index], vol) + + +if __name__ == '__main__': + main() diff --git a/Scripts/scriptfetch b/Scripts/scriptfetch new file mode 100755 index 0000000..2cbb362 --- /dev/null +++ b/Scripts/scriptfetch @@ -0,0 +1,57 @@ +#!/bin/sh + +#Informacion del sistema + +kernel="$(uname -r)" +os="$(awk -F'"' '/PRETTY/ {print ($2)}' /etc/os-release)" +cpu="$(lscpu | awk '/Nombre/ {print $4, $5, $7, $8, $9}')" +name="${USER:-`id -nu`}@${HOSTNAME:-`hostname`}" +sys="$(echo $(uname) | awk '{print toupper($0)}')" +st=$(df -h / | awk '/dev/{print $3" / "$2}') +mem=$(free -m | awk '/^Mem:/ {print $3 "MiB / " $2 "MiB"}') +shell=$(printf "$(basename $SHELL)" | awk '{print ($0)}') +init=$(awk '{print ($0)}' /proc/1/comm) +pkg="$(xbps-query -l | wc -l)" +manager=$(which xbps-query 2>/dev/null) && manager=${manager##*/} +wm="$(awk 'END {print $2}' "$HOME"/.xinitrc)" +tr="$(printf "$TERM" | awk '{print ($0)}')" +uptime="$(uptime -p | sed 's/up //')" +#iv="$(printf "$IMAGEVIEWER" | awk '{print toupper($0)}')" +#file="$(printf "$FILE" | awk '{print toupper($0)}')" +#ed="$(printf "$EDITOR" | awk '{print toupper($0)}')" +#br="$(printf "$BROWSER" | awk '{print toupper($0)}')" +#mn="$(printf "$MENU" | awk '{print toupper($0)}')" +#font="$(awk -F'"' '/font/ {print toupper($2)}' "${HOME}/.gtkrc-2.0")" + + +## Void-Linux +#echo " +#┌────────────────────────────────────────────────────┐ +#│ │ +# Host  ${name}${reset} +# ------- Os  ${os}${reset} +# _ \______ - Cpu  ${cpu}${reset} +# | \ ___ \ | Wm  ${wm}${reset} +# | | / \ | | Packages  ${pkg} (${manager})${reset} +# | | \___/ | | Kernel  ${kernel}${reset} +# | \______ \_| Shell  ${shell}${reset} +# -_______\ Init  ${init}${reset} +# Memory  ${mem}${reset} +# Uptime  ${uptime} +#│ │ +#└────────────────────────────────────────────────────┘ +#" +echo " +Host: ${name} +Os: ${os} +Kernel: ${kernel} +Init: ${init} +Pkgs: ${pkg} (${manager}) +Shell: ${shell} +Term: ${tr} +Wm: ${wm} +Cpu: ${cpu} +Ram: ${mem} +Store: ${st} +Uptime: ${uptime} +" diff --git a/Scripts/sctpad b/Scripts/sctpad new file mode 100755 index 0000000..4fafaf0 --- /dev/null +++ b/Scripts/sctpad @@ -0,0 +1,8 @@ +#!/bin/sh + +SCRATCHPAD_NAME=$1 +[ -z $SCRATCHPAD_NAME ] && SCRATCHPAD_NAME="sctpad" + +xdotool search --onlyvisible --classname $SCRATCHPAD_NAME windowunmap \ + || xdotool search --classname $SCRATCHPAD_NAME windowmap \ + || $1 & diff --git a/Scripts/tabn3 b/Scripts/tabn3 new file mode 100755 index 0000000..c9c82cb --- /dev/null +++ b/Scripts/tabn3 @@ -0,0 +1,5 @@ +#!/bin/sh + +tb=$(tabbed -c -d -r 2 st -w x) + +st -t "nnn - manejador de archivos" -n -e -w "$tb" nnn -d -D diff --git a/Scripts/udevil-mounter b/Scripts/udevil-mounter new file mode 100755 index 0000000..7b532e5 --- /dev/null +++ b/Scripts/udevil-mounter @@ -0,0 +1,62 @@ +#!/bin/sh + + +# Dependencias: udevil dmenu +# command to list devices lsblk -ln | awk '/part/ && !/sda/ && !/sdb/ && !/nvme0n1p3/ && !/nvme0n1p4/ {print $1}' // Using awk to filter all devices you don't want to unmount +ayuda() { + cat << EOF +Fork of usb.sh of @Tenshalito +https://git.disroot.org/tuxliban/scripts/src/branch/master/varios/usb +udevil-devices-mounter v0.2 (18/12/2021) +Script to manage external usb devices with devmon y udevil. +Use: +udevil-mounter [-muUh] +Opcions: + -m Mount External device + -u Umount External device + -U Umount last External device pluged-in + -h Show help +EOF +} + +dm="dmenu -l 5" + + + case $1 in + -m) + # Montar dispositivo extraible + usb="$(lsblk -ln | awk '/part/ && !/sda/ && !/sdb/ && !/nvme0n1p3/ && !/nvme0n1p4/ {print $1}' | $dm -p "Choose Device to Mount")" + devmon --sync --exec-on-drive "herbe ' Device "$usb" ' ' Mounted and Ready to use '" & + pkill -9 devmon && pkill -9 udevil + ;; + -u) + # Desmontar dispositivo + usb="$(lsblk -o name | awk '/part/ && !/sda/ && !/sdb/ && !/nvme0n1p3/ && !/nvme0n1p4/ {print $1}' | $dm -p "Choose Device to Unmount")" + devmon --unmount /dev/"$usb" && sleep 2; herbe 'Device "$usb" ' ' Unmounted and Safetly to Unplug ' + pkill -9 devmon && pkill -9 udevil + ;; + -U) + # Desmontar último pendrive insertado + devmon --unmount-recent && sleep 2; herbe "Last External Device Pluged-in" "Unmounted and Safetly to Unplug" + pkill -9 devmon && pkill -9 udevil + ;; + -h) + ayuda + ;; + *) + printf '%b' "\033[31;5mInvalid Option\033[0m\n" + printf '%b' "\033[37;2mOpcions:\033[0m\n" + printf '%b' "\033[32;1m-m: \033[36;2mMount External device\033[0m\\033[0m\n" + printf '%b' "\033[32;1m-u: \033[36;2mUmount External device\033[0m\\033[0m\n" + printf '%b' "\033[32;1m-U: \033[36;2mUmount last External device pluged-in\033[0m\\033[0m\n" + printf '%b' "\033[32;1m-h: \033[36;2mShow help\033[0m\\033[0m\n\n" + return + ;; + esac + +if [ ! -f /usr/bin/udevil ]; then + printf '%b' "\033[31;5m[ERROR]'udevil' can't be found\033[0m\n" + exit 0; +else + exit 0; +fi diff --git a/Scripts/vi-nuke b/Scripts/vi-nuke new file mode 100755 index 0000000..0c4ea8e --- /dev/null +++ b/Scripts/vi-nuke @@ -0,0 +1,79 @@ +#! /bin/sh + +FALLBACK_OPENER=xdg-open +entry="$1" +mime="$(file -ibL "$entry" )" +FNAME=$(basename "$entry") +ext="${FNAME##*.}" +if [ -n "$ext" ]; then + ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" +fi + + case "${ext}" in + ## Archive + a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ + rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zst|zip|rar|7z) + extract "$entry" + exit 0;; + + ## PDF + pdf) + "$BROWSER" "$entry" + exit 1;; + + ## Audio + aac|flac|m4a|mid|midi|mpa|mp2|mp3|ogg|wav|wma) + mpv --no-video --quiet "$entry" + exit 1;; + + ## Video + avi|mkv|mp4|gif) + devour mpv "$entry" + exit 1;; + + ## Image + png|jpg|jpeg|PNG|JPG|JPEG|svg) + display -resize '800x800!' "$entry" + exit 1;; + + ## Log files + log) + "$EDITOR" "$entry" + exit 0;; + + ## BitTorrent + torrenti|magnet) + aria2c -i "$entry" + exit 0;; + + ## OpenDocument + odt|ods|odp|sxw) + "$BROWSER" "$entry" + exit 0;; + + ## Markdown + md) + "$EDITOR" "$entry" + exit 0;; + + ## HTML + htm|html|xhtml) + ## Preview as text conversion + "$BROWSER" "$entry" + exit 0;; + + ## JSON + json) + jq --color-output . "$entry" + exit 0 ;; + + esac + + +case "$mime" in + *text*) + "$EDITOR" "$entry" + exit 0 ;; +esac + +$FALLBACK_OPENER "$entry"