nnn/plugins/preview-tabbed
lvgx f47700a609
Add a tabbed/xembed based file previewer plugin (#552)
* Add a tabbed/xembed based file previewer plugin

This plugin is written in bash, because job control is not well
specified in POSIX sh (`jobs` can return anything).

We use `tabbed` [1] as a xembed [2] host, to have a single window
owning each previewer window.

Uses mpv, sxiv, zathura, and the nuke plugin.

[1]: http://tools.suckless.org/tabbed/
[2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html

* tabbed-preview: prevent focus steal with xdotool

* preview-tabbed: tabs->4 spaces

* preview-tabbed: add focus prevention timeout
2020-05-06 00:28:57 +05:30

198 lines
5.5 KiB
Bash
Executable file

#!/bin/bash
# Description: tabbed/xembed based file previewer
#
# Note: This plugin needs a "NNN_FIFO" to work. See man.
#
# Shell: Bash (job control is weakly specified in POSIX)
#
# 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): xembed client for images
# - zathura (https://pwmt.org/projects/zathura): xembed client for PDF documents
# - nnn's nuke plugin for text preview and fallback (should be in plugins directory)
# nuke is a fallback for 'mpv', 'sxiv', and 'zathura', but it has has its own
# dependencies, see the script itself
# - vim (or any editor/pager really)
# - file
# - xdotool (optional, to keep main window focused)
#
# How to use:
# First, install the dependencies. Then you need to set a NNN_FIFO path
# and set a key for the plugin, then start `nnn`:
#
# $ NNN_FIFO=/tmp/nnn.fifo nnn
#
# Then in `nnn`, launch the `preview-tabbed` plugin.
#
# If you provide the same NNN_FIFO to all nnn instances, there will be a
# single common preview window. I you provide different FIFO path, 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`: image viewer
# - `zathura`: PDF viewer
# - but we allways fallback to `nuke` plugin
#
# [1]: http://tools.suckless.org/tabbed/
# [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
XDOTOOL_TIMEOUT=2
PAGER=${PAGER:-"vim -R"}
NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke"
if which xterm ; then
TERMINAL="xterm -into"
elif which urxvt ; then
TERMINAL="urxvt -embed"
elif which st ; 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 %%)"
}
previewer_loop () {
unset -v NNN_FIFO
# mute from now
exec >/dev/null 2>&1
MAINWINDOW="$(xdotool getactivewindow)"
start_tabbed
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
if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then
kill "$VIEWERPID"
fi
MIME="$(file -b --mime-type "$FILE")"
case "$MIME" in
video/*)
if which mpv ; then
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
audio/*)
if which mpv ; then
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
image/*)
if which sxiv ; then
sxiv -e "$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
application/pdf)
if which zathura ; 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"
}
if [ ! -r "$NNN_FIFO" ] ; then
echo "Can't read \$NNN_FIFO ('$NNN_FIFO')"
exit 1
fi
previewer_loop < "$NNN_FIFO" &
disown