preview-tabbed (6885B)
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 or alacritty) : 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://codeberg.org/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 # 3. This plugin only works on X, not on Wayland. 31 # 32 # How it works: 33 # We use `tabbed` [1] as a xembed [2] host, to have a single window 34 # owning each previewer window. So each previewer must be a xembed client. 35 # For text previewers, this is not an issue, as there are a lot of 36 # xembed-able terminal emulator (we default to `xterm`, but examples are 37 # provided for `urxvt` and `st`). For graphic preview this can be trickier, 38 # but a few popular viewers are xembed-able, we use: 39 # - `mpv`: multimedia player, for video/audio preview 40 # - `sxiv`/`nsxiv`: image viewer 41 # - `zathura`: PDF viewer 42 # - but we always fallback to `nuke` plugin 43 # 44 # [1]: https://tools.suckless.org/tabbed/ 45 # [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html 46 # 47 # Shell: Bash (job control is weakly specified in POSIX) 48 # Author: Léo Villeveygoux 49 50 51 XDOTOOL_TIMEOUT=2 52 PAGER=${PAGER:-"vim -R"} 53 NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" 54 55 if [ -n "$WAYLAND_DISPLAY" ] ; then 56 echo "Wayland is not supported in preview-tabbed, this plugin could freeze your session!" >&2 57 exit 1 58 fi 59 60 if type xterm >/dev/null 2>&1 ; then 61 TERMINAL="xterm -into" 62 elif type urxvt >/dev/null 2>&1 ; then 63 TERMINAL="urxvt -embed" 64 elif type st >/dev/null 2>&1 ; then 65 TERMINAL="st -w" 66 elif type alacritty >/dev/null 2>&1 ; then 67 TERMINAL="alacritty --embed" 68 else 69 echo "No xembed term found" >&2 70 fi 71 72 if type xdg-user-dir >/dev/null 2>&1 ; then 73 PICTURES_DIR=$(xdg-user-dir PICTURES) 74 fi 75 76 term_nuke () { 77 # $1 -> $XID, $2 -> $FILE 78 $TERMINAL "$1" -e "$NUKE" "$2" & 79 } 80 81 start_tabbed () { 82 FIFO="$(mktemp -u)" 83 mkfifo "$FIFO" 84 85 tabbed > "$FIFO" & 86 87 jobs # Get rid of the "Completed" entries 88 89 TABBEDPID="$(jobs -p %%)" 90 91 if [ -z "$TABBEDPID" ] ; then 92 echo "Can't start tabbed" 93 exit 1 94 fi 95 96 read -r XID < "$FIFO" 97 98 rm -- "$FIFO" 99 } 100 101 get_viewer_pid () { 102 VIEWERPID="$(jobs -p %%)" 103 } 104 105 kill_viewer () { 106 if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then 107 kill "$VIEWERPID" 108 fi 109 } 110 111 sigint_kill () { 112 kill_viewer 113 kill "$TABBEDPID" 114 exit 0 115 } 116 117 previewer_loop () { 118 unset -v NNN_FIFO 119 # mute from now 120 exec >/dev/null 2>&1 121 122 MAINWINDOW="$(xdotool getactivewindow)" 123 124 start_tabbed 125 trap sigint_kill SIGINT 126 127 xdotool windowactivate "$MAINWINDOW" 128 129 # Bruteforce focus stealing prevention method, 130 # works well in floating window managers like XFCE 131 # but make interaction with the preview window harder 132 # (uncomment to use): 133 #xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & 134 135 while read -r FILE ; do 136 137 jobs # Get rid of the "Completed" entries 138 139 if ! jobs | grep tabbed ; then 140 break 141 fi 142 143 if [ ! -e "$FILE" ] ; then 144 continue 145 fi 146 147 kill_viewer 148 149 MIME="$(file -bL --mime-type "$FILE")" 150 151 case "$MIME" in 152 video/*) 153 if type mpv >/dev/null 2>&1 ; then 154 mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & 155 else 156 term_nuke "$XID" "$FILE" 157 fi 158 ;; 159 audio/*) 160 if type mpv >/dev/null 2>&1 ; then 161 mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & 162 else 163 term_nuke "$XID" "$FILE" 164 fi 165 ;; 166 image/*) 167 if type sxiv >/dev/null 2>&1 ; then 168 sxiv -ae "$XID" "$FILE" & 169 elif type nsxiv >/dev/null 2>&1 ; then 170 nsxiv -ae "$XID" "$FILE" & 171 else 172 term_nuke "$XID" "$FILE" 173 fi 174 ;; 175 application/pdf) 176 if type zathura >/dev/null 2>&1 ; then 177 zathura -e "$XID" "$FILE" & 178 else 179 term_nuke "$XID" "$FILE" 180 fi 181 ;; 182 inode/directory) 183 if [[ -n $PICTURES_DIR && "$FILE" == "$PICTURES_DIR"* ]] ; then 184 if type sxiv >/dev/null 2>&1 ; then 185 sxiv -te "$XID" "$FILE" & 186 elif type nsxiv >/dev/null 2>&1 ; then 187 nsxiv -te "$XID" "$FILE" & 188 else 189 $TERMINAL "$XID" -e nnn "$FILE" & 190 fi 191 else 192 $TERMINAL "$XID" -e nnn "$FILE" & 193 fi 194 ;; 195 text/*) 196 if [ -x "$NUKE" ] ; then 197 term_nuke "$XID" "$FILE" 198 else 199 # shellcheck disable=SC2086 200 $TERMINAL "$XID" -e $PAGER "$FILE" & 201 fi 202 ;; 203 *) 204 if [ -x "$NUKE" ] ; then 205 term_nuke "$XID" "$FILE" 206 else 207 $TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" & 208 fi 209 ;; 210 esac 211 get_viewer_pid 212 213 # following lines are not needed with the bruteforce xdotool method 214 ACTIVE_XID="$(xdotool getactivewindow)" 215 if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then 216 xdotool windowactivate "$MAINWINDOW" 217 else 218 timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & 219 fi 220 done 221 kill "$TABBEDPID" 222 kill_viewer 223 } 224 225 if [ ! -r "$NNN_FIFO" ] ; then 226 echo "Can't read \$NNN_FIFO ('$NNN_FIFO')" 227 exit 1 228 fi 229 230 previewer_loop < "$NNN_FIFO" & 231 disown