1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
#!/usr/bin/env bash
# Description: tabbed/xembed based file previewer
#
# Note: This plugin needs a "NNN_FIFO" to work. See man.
#
# 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
# - mktemp
# - 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
#
# 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 which xterm >/dev/null 2>&1 ; then
TERMINAL="xterm -into"
elif which urxvt >/dev/null 2>&1 ; then
TERMINAL="urxvt -embed"
elif which 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
}
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
kill_viewer
MIME="$(file -b --mime-type "$FILE")"
case "$MIME" in
video/*)
if which mpv >/dev/null 2>&1 ; then
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
audio/*)
if which mpv >/dev/null 2>&1 ; then
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
image/*)
if which sxiv >/dev/null 2>&1 ; then
sxiv -e "$XID" "$FILE" &
else
term_nuke "$XID" "$FILE"
fi
;;
application/pdf)
if which 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
|