Skip to content

Instantly share code, notes, and snippets.

@b0o
Created March 8, 2021 10:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save b0o/61c9e1b8aaabd731578561e251519cc6 to your computer and use it in GitHub Desktop.
Save b0o/61c9e1b8aaabd731578561e251519cc6 to your computer and use it in GitHub Desktop.
#!/bin/bash
# rqrcp is a wrapper around 'qrcp receive' which improves desktop integration
# by adding support for copying file paths or contents to the clipboard,
# opening files in the default application, sending notifications, and using a
# GUI window to display the QR code.
#
# Copyright (C) 2021 Maddison Hellstrom <https://github.com/b0o>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -euo pipefail
if [[ ${BASH_VERSINFO[0]} -ge 5 || (${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 4) ]]; then
shopt -s inherit_errexit
fi
declare -g base
declare -g prog
base="$(realpath -e "${BASH_SOURCE[0]}")"
prog="$(basename "$base")"
declare -g version="v0.0.1"
declare -g authors=("2021 Maddison Hellstrom <github.com/b0o>")
declare -g repository="https://github.com/b0o/rqrcp"
declare -g issues="https://github.com/b0o/rqrcp/issues"
declare -g license="GPL-3.0-or-later"
declare -g license_url="https://www.gnu.org/licenses/gpl-3.0.txt"
# options
declare -gi open=0
declare -gi keep=0
declare -gi gui=0
declare -gi copy=0
declare -gi notify=0
declare -gi binary_stdout=0
declare -g mode="auto"
# state
declare -g tmp
declare -gi qrcp_pid=0
declare -g qrcp_pidfile=""
# gui subprocess state
declare -gi gui_pid=0
function usage() {
cat >&2 << EOF
Usage: $prog [opts] [dest]
$prog is a wrapper around 'qrcp receive' which improves desktop integration by
adding support for copying file paths or contents to the clipboard, opening
files in their default application, displaying notifications, and using a GUI
window to display the QR code.
Options:
-h Show help.
-v Show version.
-o Open the resulting file.
Requires xdg-open on Linux, open on MacOS.
-k Don't remove the temporary directory on exit.
-g Display QR code in a GUI window.
Currently only supported on Linux.
Requires feh, qrencode, and imagemagick.
-c Copy the resulting filepath or text to the clipboard.
Requires xclip under X11, pbcopy under MacOS, or wl-clipboard under
wayland.
-n Show a notification with the result.
Requires notify-send on Linux, osascript on MacOS.
-m <mode> Define how to handle the result. This will affect what is copied to
the clipboard and printed to stdout.
<mode> may be one of:
path Use the path of the resulting file
contents Use the contents of the resulting file
auto Use the contents of text files, the path of other files.
Default: auto
-b In "contents" mode, when stdout is a TTY and the file encoding is
binary, this option forces the file contents to be printed to
stdout.
-B Like -b, except this option forces binary files to never be printed
to stdout.
Caveats:
- $prog currently only supports receiving a single file.
About:
Repository: $repository
Issues: $issues
License: $license ($license_url)
Copyright: ${authors[0]}
$(printf ' %s\n' "${authors[@]:1}")
EOF
}
function cleanup() {
if [[ -d "$tmp" && $keep -eq 0 ]]; then
rm -r "$tmp"
fi
if [[ -e "$qrcp_pidfile" ]]; then
rm "$qrcp_pidfile"
fi
}
function _copy() {
if [[ $copy -eq 0 ]]; then
return 0
fi
local type="$1"
shift
case "$OSTYPE" in
linux-gnu)
if [[ -v WAYLAND_DISPLAY ]]; then
if [[ $# -ge 2 ]]; then
wl-copy -t "$type"
else
wl-copy "$*"
fi
elif [[ -v DISPLAY ]]; then
if [[ $# -ge 2 ]]; then
xclip -i
else
xclip -i <<< "$*"
fi
fi
;;
darwin)
if [[ $# -ge 2 ]]; then
pbcopy
else
pbcopy <<< "$*"
fi
;;
*)
echo "-c not supported on $OSTYPE" >&2
return 1
;;
esac
}
function _notify() {
if [[ $notify -eq 0 ]]; then
return 0
fi
local -i err=0
local opt OPTARG
local -i OPTIND
while getopts "e" opt "$@"; do
case "$opt" in
e)
err=1
;;
\?)
return 1
;;
esac
done
local title="$1"
shift
local msg
msg="$(head -1 <<< "$*")"
case "$OSTYPE" in
linux-gnu)
if [[ $err -eq 0 ]]; then
notify-send -t 1500 "$@"
else
notify-send -u critical -t 1500 "$@"
fi
;;
darwin)
if [[ $err -eq 0 ]]; then
title="Error: $title"
fi
osascript -e "display notification \"$msg\" with title \"$title\""
;;
*)
echo "-n not supported on $OSTYPE" >&2
return 1
;;
esac
}
function _open() {
if [[ $open -eq 0 ]]; then
return 0
fi
case "$OSTYPE" in
linux-gnu)
xdg-open "$@"
;;
darwin)
open "$@"
;;
*)
echo "-o not supported on $OSTYPE" >&2
return 1
;;
esac
}
function handle_result() {
if [[ $# -eq 0 ]]; then
echo "Nothing received" >&2
_notify "qrcp" "Nothing received"
return 0
fi
local result_file="$1"
local file_type file_encoding
file_type="$(file --mime-type -b "$result_file")"
file_encoding="$(file --mime-encoding -b "$result_file")"
local result=""
local -a result_arr=()
if [[ "$mode" == "auto" ]]; then
if [[ "$type" == "text/plain" ]]; then
mode="contents"
else
mode="path"
fi
fi
echo "Received $file_type" >&2
case "$mode" in
path)
echo "$result_file"
result="$result_file"
;;
contents)
if [[ "$file_encoding" == "binary" ]]; then
if [[ $binary_stdout -eq -1 || ($binary_stdout -eq 0 && -t 1) ]]; then
echo "Refusing to output binary file to terminal; use -b to force" >&2
else
cat "$result_file"
fi
else
cat "$result_file"
echo ""
mapfile -t result_arr < "$result_file"
result="$(printf '%s\n' "${result_arr[@]}")"
fi
;;
esac
if [[ -n "$result" ]]; then
_copy "$file_type" "$result"
else
_copy "$file_type" < "$result_file"
fi
_notify "qrcp" "Received $type${result:+: $result}"
_open "${result:-result_file}"
}
function cleanup_gui() {
if [[ $gui_pid -gt 1 ]] && ps $gui_pid &> /dev/null; then
kill $gui_pid
fi
if [[ $qrcp_pid -gt 1 ]] && ps $qrcp_pid &> /dev/null; then
kill $qrcp_pid
fi
}
function handle_gui() {
qrencode -o - -s 1 -- "$*" \
| convert - \
-scale 1000x1000 \
-gravity north \
-extent 1000x950 \
\( \
-size 900x250 \
-gravity Center \
-pointsize 48 \
caption:"$*" \
-extent 1000x300 \
\) \
-append png:- \
| feh --image-bg '#ffffff' \
--scale-down \
- &
gui_pid=$!
trap 'cleanup_gui' EXIT
wait
}
function _qrcp() {
if [[ $gui -eq 1 ]]; then
qrcp_pidfile="${tmp}.pid"
{
qrcp receive -o "$tmp" &
echo "$!" > "$qrcp_pidfile"
} | awk '
/^http/ && g != 1 {
g=1
"bash -c \"'"${BASH_SOURCE[0]}"' -G '"$qrcp_pidfile"' \\\"" $0 "\\\" &\necho \\$!\"" | getline pid
}
{
print $0
}
END {
system("ps " pid " &> /dev/null && kill " pid)
}' || {
_notify -e "qrcp" "Command failed"
return 1
}
else
qrcp receive -o "$tmp" >&2 || {
_notify -e "qrcp" "Command failed"
return 1
}
fi
}
function main() {
local -i handle_gui=0
local opt OPTARG
local -i OPTIND
while getopts "hvokgcnm:bBG:" opt "$@"; do
case "$opt" in
h)
usage
return 0
;;
v)
echo "$version"
return 0
;;
o)
open=1
;;
k)
keep=1
;;
g)
gui=1
;;
c)
copy=1
;;
n)
notify=1
;;
m)
case "$OPTARG" in
path)
mode="path"
;;
contents)
mode="contents"
;;
auto)
mode="auto"
;;
*)
echo "invalid mode: $OPTARG" >&2
return 1
;;
esac
;;
G)
handle_gui=1
qrcp_pid="$(head -1 "$OPTARG")"
;;
b)
binary_stdout=1
;;
B)
binary_stdout=-1
;;
\?)
return 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $handle_gui -eq 1 ]]; then
handle_gui "$@"
return $?
fi
local dest=""
if [[ $# -ge 1 ]]; then
dest="$(realpath -m "$1" 2> /dev/null)"
if ! [[ -d "$dest" || -d "$(dirname "$dest")" ]]; then
echo "No such file or directory: $1" >&2
return 1
fi
fi
tmp="$(mktemp --tmpdir -d qrcp-XXXXX)"
trap "cleanup" EXIT
if [[ $open -eq 1 && -z "$dest" ]]; then
keep=1
fi
_qrcp
local result_file
result_file="$(find "$tmp" -mindepth 1 -maxdepth 1 -type f | head -1)"
if [[ -z "$result_file" ]]; then
handle_result
return $?
fi
if [[ -n "$dest" ]]; then
mv --no-clobber "$result_file" "$dest"
if [[ -d "$dest" ]]; then
result_file="$dest/$(basename "$result_file")"
else
result_file="$dest"
fi
fi
local type
type="$(file --mime-type -b "$result_file")"
handle_result "$result_file"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment