Created
March 8, 2021 10:37
-
-
Save b0o/61c9e1b8aaabd731578561e251519cc6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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