preview-tabbed (6056B)
1 #!/usr/bin/env bash 2 3 # Description: tabbed/xembed based file previewer 4 # 5 # Dependencies: 6 # - tabbed (https://tools.suckless.org/tabbed): xembed host 7 # - xterm (or urxvt or st) : xembed client for text-based preview 8 # - mpv (https://mpv.io): xembed client for video/audio 9 # - sxiv (https://github.com/muennich/sxiv) or, 10 # - nsxiv (https://github.com/nsxiv/nsxiv) : xembed client for images 11 # - zathura (https://pwmt.org/projects/zathura): xembed client for PDF 12 # - nnn's nuke plugin for text preview and fallback 13 # nuke is a fallback for 'mpv', 'sxiv'/'nsxiv', and 'zathura', but has its 14 # own dependencies, see the script for more information 15 # - vim (or any editor/pager really) 16 # - file 17 # - mktemp 18 # - xdotool (optional, to keep main window focused) 19 # 20 # Usage: 21 # - Install the dependencies. Then set a NNN_FIFO 22 # and set a key for the plugin, then start `nnn`: 23 # $ NNN_FIFO=/tmp/nnn.fifo nnn 24 # - Launch the plugin with the designated key from nnn 25 # 26 # Notes: 27 # 1. This plugin needs a "NNN_FIFO" to work. See man. 28 # 2. If the same NNN_FIFO is used in multiple nnn instances, there will be one 29 # common preview window. With different FIFO paths, they will be independent. 30 # 31 # How it works: 32 # We use `tabbed` [1] as a xembed [2] host, to have a single window 33 # owning each previewer window. So each previewer must be a xembed client. 34 # For text previewers, this is not an issue, as there are a lot of 35 # xembed-able terminal emulator (we default to `xterm`, but examples are 36 # provided for `urxvt` and `st`). For graphic preview this can be trickier, 37 # but a few popular viewers are xembed-able, we use: 38 # - `mpv`: multimedia player, for video/audio preview 39 # - `sxiv`/`nsxiv`: image viewer 40 # - `zathura`: PDF viewer 41 # - but we always fallback to `nuke` plugin 42 # 43 # [1]: https://tools.suckless.org/tabbed/ 44 # [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html 45 # 46 # Shell: Bash (job control is weakly specified in POSIX) 47 # Author: Léo Villeveygoux 48 49 50 XDOTOOL_TIMEOUT=2 51 PAGER=${PAGER:-"vim -R"} 52 NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" 53 54 55 if type xterm >/dev/null 2>&1 ; then 56 TERMINAL="xterm -into" 57 elif type urxvt >/dev/null 2>&1 ; then 58 TERMINAL="urxvt -embed" 59 elif type st >/dev/null 2>&1 ; then 60 TERMINAL="st -w" 61 else 62 echo "No xembed term found" >&2 63 fi 64 65 66 term_nuke () { 67 # $1 -> $XID, $2 -> $FILE 68 $TERMINAL "$1" -e "$NUKE" "$2" & 69 } 70 71 start_tabbed () { 72 FIFO="$(mktemp -u)" 73 mkfifo "$FIFO" 74 75 tabbed > "$FIFO" & 76 77 jobs # Get rid of the "Completed" entries 78 79 TABBEDPID="$(jobs -p %%)" 80 81 if [ -z "$TABBEDPID" ] ; then 82 echo "Can't start tabbed" 83 exit 1 84 fi 85 86 read -r XID < "$FIFO" 87 88 rm "$FIFO" 89 } 90 91 get_viewer_pid () { 92 VIEWERPID="$(jobs -p %%)" 93 } 94 95 kill_viewer () { 96 if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then 97 kill "$VIEWERPID" 98 fi 99 } 100 101 sigint_kill () { 102 kill_viewer 103 kill "$TABBEDPID" 104 exit 0 105 } 106 107 previewer_loop () { 108 unset -v NNN_FIFO 109 # mute from now 110 exec >/dev/null 2>&1 111 112 MAINWINDOW="$(xdotool getactivewindow)" 113 114 start_tabbed 115 trap sigint_kill SIGINT 116 117 xdotool windowactivate "$MAINWINDOW" 118 119 # Bruteforce focus stealing prevention method, 120 # works well in floating window managers like XFCE 121 # but make interaction with the preview window harder 122 # (uncomment to use): 123 #xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & 124 125 while read -r FILE ; do 126 127 jobs # Get rid of the "Completed" entries 128 129 if ! jobs | grep tabbed ; then 130 break 131 fi 132 133 if [ ! -e "$FILE" ] ; then 134 continue 135 fi 136 137 kill_viewer 138 139 MIME="$(file -bL --mime-type "$FILE")" 140 141 case "$MIME" in 142 video/*) 143 if type mpv >/dev/null 2>&1 ; then 144 mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & 145 else 146 term_nuke "$XID" "$FILE" 147 fi 148 ;; 149 audio/*) 150 if type mpv >/dev/null 2>&1 ; then 151 mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & 152 else 153 term_nuke "$XID" "$FILE" 154 fi 155 ;; 156 image/*) 157 if type sxiv >/dev/null 2>&1 ; then 158 sxiv -ae "$XID" "$FILE" & 159 elif type nsxiv >/dev/null 2>&1 ; then 160 nsxiv -ae "$XID" "$FILE" & 161 else 162 term_nuke "$XID" "$FILE" 163 fi 164 ;; 165 application/pdf) 166 if type zathura >/dev/null 2>&1 ; then 167 zathura -e "$XID" "$FILE" & 168 else 169 term_nuke "$XID" "$FILE" 170 fi 171 ;; 172 inode/directory) 173 $TERMINAL "$XID" -e nnn "$FILE" & 174 ;; 175 text/*) 176 if [ -x "$NUKE" ] ; then 177 term_nuke "$XID" "$FILE" 178 else 179 # shellcheck disable=SC2086 180 $TERMINAL "$XID" -e $PAGER "$FILE" & 181 fi 182 ;; 183 *) 184 if [ -x "$NUKE" ] ; then 185 term_nuke "$XID" "$FILE" 186 else 187 $TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" & 188 fi 189 ;; 190 esac 191 get_viewer_pid 192 193 # following lines are not needed with the bruteforce xdotool method 194 ACTIVE_XID="$(xdotool getactivewindow)" 195 if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then 196 xdotool windowactivate "$MAINWINDOW" 197 else 198 timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & 199 fi 200 done 201 kill "$TABBEDPID" 202 kill_viewer 203 } 204 205 if [ ! -r "$NNN_FIFO" ] ; then 206 echo "Can't read \$NNN_FIFO ('$NNN_FIFO')" 207 exit 1 208 fi 209 210 previewer_loop < "$NNN_FIFO" & 211 disown