aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/preview-tabbed
blob: 9fefee144448ac194657493344257928fb57d159 (plain) (blame)
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
#!/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 %%)"
}

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 >/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"
}

if [ ! -r "$NNN_FIFO" ] ; then
    echo "Can't read \$NNN_FIFO ('$NNN_FIFO')"
    exit 1
fi

previewer_loop < "$NNN_FIFO" &
disown