summaryrefslogtreecommitdiffstats
path: root/plugins/preview-tabbed
blob: 68d8d1ab5314f10213f829ba683f69dc7cf7543d (plain)
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env bash

# Description: tabbed/xembed based file previewer
#
# Dependencies:
#   - tabbed (https://tools.suckless.org/tabbed): xembed host
#   - xterm (or urxvt or st or alacritty) : xembed client for text-based preview
#   - mpv (https://mpv.io): xembed client for video/audio
#   - sxiv (https://github.com/muennich/sxiv) or,
#   - nsxiv (https://codeberg.org/nsxiv/nsxiv) : xembed client for images
#   - zathura (https://pwmt.org/projects/zathura): xembed client for PDF
#   - nnn's nuke plugin for text preview and fallback
#     nuke is a fallback for 'mpv', 'sxiv'/'nsxiv', and 'zathura', but has its
#     own dependencies, see the script for more information
#   - vim (or any editor/pager really)
#   - file
#   - mktemp
#   - xdotool (optional, to keep main window focused)
#
# Usage:
#   - Install the dependencies. Then set a NNN_FIFO
#     and set a key for the plugin, then start `nnn`:
#       $ NNN_FIFO=/tmp/nnn.fifo nnn
#   - Launch the plugin with the designated key from nnn
#
# Notes:
#   1. This plugin needs a "NNN_FIFO" to work. See man.
#   2. If the same NNN_FIFO is used in multiple nnn instances, there will be one
#      common preview window. With different FIFO paths, they will be independent.
#   3. This plugin only works on X, not on Wayland.
#
# 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`/`nsxiv`: image viewer
#     - `zathura`: PDF viewer
#     - but we always fallback to `nuke` plugin
#
# [1]: https://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 [ -n "$WAYLAND_DISPLAY" ] ; then
    echo "Wayland is not supported in preview-tabbed, this plugin could freeze your session!" >&2
    exit 1
fi

if type xterm >/dev/null 2>&1 ; then
    TERMINAL="xterm -into"
elif type urxvt >/dev/null 2>&1 ; then
    TERMINAL="urxvt -embed"
elif type st >/dev/null 2>&1 ; then
    TERMINAL="st -w"
elif type alacritty >/dev/null 2>&1 ; then
    TERMINAL="alacritty --embed"
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
}

sigint_kill () {
	kill_viewer
	kill "$TABBEDPID"
	exit 0
}

previewer_loop () {
    unset -v NNN_FIFO
    # mute from now
    exec >/dev/null 2>&1

    MAINWINDOW="$(xdotool getactivewindow)"

    start_tabbed
    trap sigint_kill SIGINT

    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 -bL --mime-type "$FILE")"

        case "$MIME" in
            video/*)
                if type mpv >/dev/null 2>&1 ; then
                    mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
                else
                    term_nuke "$XID" "$FILE"
                fi
                ;;
            audio/*)
                if type mpv >/dev/null 2>&1 ; then
                    mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
                else
                    term_nuke "$XID" "$FILE"
                fi
                ;;
            image/*)
                if type sxiv >/dev/null 2>&1 ; then
                    sxiv -ae "$XID" "$FILE" &
                elif type nsxiv >/dev/null 2>&1 ; then
                    nsxiv -ae "$XID" "$FILE" &
                else
                    term_nuke "$XID" "$FILE"
                fi
                ;;
            application/pdf)
                if type 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