]> Sergey Matveev's repositories - nnn.git/blob - plugins/preview-tabbed
Add alacritty as xembed client for text-based preview
[nnn.git] / plugins / preview-tabbed
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
73 term_nuke () {
74     # $1 -> $XID, $2 -> $FILE
75     $TERMINAL "$1" -e "$NUKE" "$2" &
76 }
77
78 start_tabbed () {
79     FIFO="$(mktemp -u)"
80     mkfifo "$FIFO"
81
82     tabbed > "$FIFO" &
83
84     jobs # Get rid of the "Completed" entries
85
86     TABBEDPID="$(jobs -p %%)"
87
88     if [ -z "$TABBEDPID" ] ; then
89         echo "Can't start tabbed"
90         exit 1
91     fi
92
93     read -r XID < "$FIFO"
94
95     rm "$FIFO"
96 }
97
98 get_viewer_pid () {
99         VIEWERPID="$(jobs -p %%)"
100 }
101
102 kill_viewer () {
103         if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then
104             kill "$VIEWERPID"
105         fi
106 }
107
108 sigint_kill () {
109         kill_viewer
110         kill "$TABBEDPID"
111         exit 0
112 }
113
114 previewer_loop () {
115     unset -v NNN_FIFO
116     # mute from now
117     exec >/dev/null 2>&1
118
119     MAINWINDOW="$(xdotool getactivewindow)"
120
121     start_tabbed
122     trap sigint_kill SIGINT
123
124     xdotool windowactivate "$MAINWINDOW"
125
126     # Bruteforce focus stealing prevention method,
127     # works well in floating window managers like XFCE
128     # but make interaction with the preview window harder
129     # (uncomment to use):
130     #xdotool behave "$XID" focus windowactivate "$MAINWINDOW" &
131
132     while read -r FILE ; do
133
134         jobs # Get rid of the "Completed" entries
135
136         if ! jobs | grep tabbed ; then
137             break
138         fi
139
140         if [ ! -e "$FILE" ] ; then
141             continue
142         fi
143
144         kill_viewer
145
146         MIME="$(file -bL --mime-type "$FILE")"
147
148         case "$MIME" in
149             video/*)
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             audio/*)
157                 if type mpv >/dev/null 2>&1 ; then
158                     mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
159                 else
160                     term_nuke "$XID" "$FILE"
161                 fi
162                 ;;
163             image/*)
164                 if type sxiv >/dev/null 2>&1 ; then
165                     sxiv -ae "$XID" "$FILE" &
166                 elif type nsxiv >/dev/null 2>&1 ; then
167                     nsxiv -ae "$XID" "$FILE" &
168                 else
169                     term_nuke "$XID" "$FILE"
170                 fi
171                 ;;
172             application/pdf)
173                 if type zathura >/dev/null 2>&1 ; then
174                     zathura -e "$XID" "$FILE" &
175                 else
176                     term_nuke "$XID" "$FILE"
177                 fi
178                 ;;
179             inode/directory)
180                 $TERMINAL "$XID" -e nnn "$FILE" &
181                 ;;
182             text/*)
183                 if [ -x "$NUKE" ] ; then
184                     term_nuke "$XID" "$FILE"
185                 else
186                     # shellcheck disable=SC2086
187                     $TERMINAL "$XID" -e $PAGER "$FILE" &
188                 fi
189                 ;;
190             *)
191                 if [ -x "$NUKE" ] ; then
192                     term_nuke "$XID" "$FILE"
193                 else
194                     $TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" &
195                 fi
196                 ;;
197         esac
198         get_viewer_pid
199
200         # following lines are not needed with the bruteforce xdotool method
201         ACTIVE_XID="$(xdotool getactivewindow)"
202         if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then
203             xdotool windowactivate "$MAINWINDOW"
204         else
205             timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" &
206         fi
207     done
208     kill "$TABBEDPID"
209     kill_viewer
210 }
211
212 if [ ! -r "$NNN_FIFO" ] ; then
213     echo "Can't read \$NNN_FIFO ('$NNN_FIFO')"
214     exit 1
215 fi
216
217 previewer_loop < "$NNN_FIFO" &
218 disown