From f47700a609cf1e17c9a796c26da3885c880255d4 Mon Sep 17 00:00:00 2001 From: lvgx Date: Tue, 5 May 2020 20:58:57 +0200 Subject: [PATCH] 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 --- plugins/README.md | 1 + plugins/preview-tabbed | 197 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100755 plugins/preview-tabbed diff --git a/plugins/README.md b/plugins/README.md index 22083f1f..4ae5bd23 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -60,6 +60,7 @@ Plugins are installed to `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`. | pdfread | Read a PDF or text file aloud | sh | pdftotext, mpv,
pico2wave | | pdfview | View PDF file in `$PAGER` | sh | pdftotext/
mupdf-tools | | picker | Pick files and list one per line (to pipe) | sh | nnn | +| preview-tabbed | `tabbed`/xembed based file previewer | bash | _see in-file docs_ | | pskill | Fuzzy list by name and kill process or zombie | sh | fzf/fzy, ps,
sudo/doas | | renamer | Batch rename selection or files in dir | sh | [qmv](https://www.nongnu.org/renameutils/)/[vidir](https://joeyh.name/code/moreutils/) | | ringtone | Create a variable bitrate mp3 ringtone from file | sh | date, ffmpeg | diff --git a/plugins/preview-tabbed b/plugins/preview-tabbed new file mode 100755 index 00000000..654ffca7 --- /dev/null +++ b/plugins/preview-tabbed @@ -0,0 +1,197 @@ +#!/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