-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
114 additions
and
67 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,23 +10,23 @@ | |
# * mode: full|selected|focused | ||
# | ||
# ENVIRONMENT VARIABLES: | ||
# * SCREENSHOT_DOCKER_HOST: set to prefer using docker for image optimization | ||
# * LAINTOOLS_SS_DOCKER_HOST: set to prefer using docker for image optimization | ||
# Examples: `unix:///var/run/docker.sock` for local execution | ||
# `ssh://[email protected]` for remote | ||
# | ||
# DEPENDENCIES: | ||
# * Interpreter: POSIX shell + coreutils | ||
# * Screenshot tool: | ||
# * menyoki + xdotool + (hacksaw | xrectsel | slop) | ||
# * shotgun + xdotool + (hacksaw | xrectsel | slop) | ||
# * maim + xdotool | ||
# * escrotum + xdotool | ||
# * mss + xdotool + (hacksaw | xrectsel | slop) | ||
# * menyoki + (xdotool | xprop) + (hacksaw | xrectsel | slop) | ||
# * shotgun + (xdotool | xprop) + (hacksaw | xrectsel | slop) | ||
# * maim + (xdotool | xprop) | ||
# * escrotum + (xdotool | xprop) | ||
# * mss + (xdotool | xprop) + (hacksaw | xrectsel | slop) | ||
# * scrot | ||
# * (graphicsmagick | imagemagick) + xdotool | ||
# * (graphicsmagick | imagemagick) + (xdotool | xprop) | ||
# * Clipboard tool [opt]: xclip | xsel | ||
# * Image lossless optimizer [opt]: oxipng | ect | leanify | pngout | optipng | pngcrush | advpng | jpegoptim | jpegtran | ||
# * OCR tool [opt]: tesseract + tesseract-ocr-rus | easyocr | ||
# * Image lossless optimizer [opt]: oxipng | ect | leanify | pngout | optipng | pngcrush | advpng | ||
# * OCR tool [opt]: tesseract [+ tesseract-ocr-*] | easyocr | ||
# * Locks manager [opt]: flock | ||
# | ||
|
||
|
@@ -35,32 +35,78 @@ set -o errexit # exit on fail | |
set -o nounset # exit on undeclared variable | ||
# set -o xtrace # trace execution | ||
|
||
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} | ||
|
||
SCREENSHOT_DIR=${SCREENSHOT_DIR:-${HOME}/screenshots} | ||
LAINTOOLS_SS_DIR=${LAINTOOLS_SS_DIR:-${HOME}/screenshots} | ||
save_date=$(date '+%s_%Y.%m.%d_%H:%M:%S') | ||
SCREENSHOT_NAME=${SCREENSHOT_NAME:-${save_date}.png} | ||
SCREENSHOT_PATH=${SCREENSHOT_PATH:-${SCREENSHOT_DIR}/${SCREENSHOT_NAME}} | ||
SCREENSHOT_OCR_LANGS=${SCREENSHOT_OCR_LANGS:-eng+rus} | ||
LAINTOOLS_SS_NAME=${LAINTOOLS_SS_NAME:-${save_date}.png} | ||
LAINTOOLS_SS_PATH=${LAINTOOLS_SS_PATH:-${LAINTOOLS_SS_DIR}/${LAINTOOLS_SS_NAME}} | ||
LAINTOOLS_SS_OCR_LANGS=${LAINTOOLS_SS_OCR_LANGS:-eng+rus} | ||
|
||
LAINTOOLS_DRBTT_STORAGE_PATH=${LAINTOOLS_DRBTT_STORAGE_PATH:-$XDG_DATA_HOME/LAINTOOLS/drbtt_events.duckdb} | ||
|
||
# We expect word splitting here | ||
# shellcheck disable=2046 | ||
get_size() { set -- $(ls -dn "$1") && printf %s "$5"; } | ||
has() { command -v "$@" >/dev/null; } | ||
has_focus_window_getter() { has xdotool || has xprop sed; } | ||
has_selection_getter() { has hacksaw || has xrectsel || has slop; } | ||
was_optimized() { printf "%s\n" "${SCREENSHOT_NAME}" >> "${SCREENSHOT_DIR}/.optimized"; } | ||
was_optimized() { printf "%s\n" "${LAINTOOLS_SS_NAME}" >> "${LAINTOOLS_SS_DIR}/.optimized"; } | ||
|
||
get_focused_window() { | ||
get_focused_window_id() { | ||
if has xdotool; then | ||
xdotool getwindowfocus -f | ||
elif has xprop sed; then | ||
printf '%d\n' "$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)" | ||
xprop -notype -root 32i ' $0' _NET_ACTIVE_WINDOW | cut -d ' ' -f 2 | ||
else | ||
printf "Internal error!\n" >&2 | ||
exit 1 | ||
fi | ||
} | ||
|
||
init_storage() { | ||
mkdir -p "$(dirname "${LAINTOOLS_DRBTT_STORAGE_PATH}")" | ||
duckdb "${LAINTOOLS_DRBTT_STORAGE_PATH}" " | ||
CREATE TABLE IF NOT EXISTS events( | ||
timestamp TIMESTAMPTZ PRIMARY KEY, | ||
instance VARCHAR, | ||
class VARCHAR, | ||
name VARCHAR, | ||
cursor_x USMALLINT, | ||
cursor_y USMALLINT, | ||
); | ||
" | ||
} | ||
|
||
sql_escape() { | ||
# src: https://github.com/larkery/zsh-histdb/blob/30797f0c50c31c8d8de32386970c5d480e5ab35d/sqlite-history.zsh#L24 | ||
# print -r -- ${${@//\'/\'\'}//$'\x00'} | ||
printf %s "${1}" |sed -e 's/\\/\\\\/g' -e "s/'/\\\'/g" | ||
} | ||
|
||
snapshot_event() ( | ||
TIMESTAMP="$(date --utc --iso-8601=seconds)" | ||
eval "$(xprop -id "$(get_focused_window_id)" -notype '=$0\n' WM_NAME '=$1\nWM_INSTANCE=$0\n' WM_CLASS)" | ||
eval "$(xdotool getmouselocation --shell)" | ||
duckdb "${LAINTOOLS_DRBTT_STORAGE_PATH}" " | ||
INSERT INTO events VALUES ( | ||
'$(sql_escape "${TIMESTAMP}")', | ||
'$(sql_escape "${WM_INSTANCE}")', | ||
'$(sql_escape "${WM_CLASS}")', | ||
'$(sql_escape "${WM_NAME}")', | ||
$(sql_escape "${X}"), | ||
$(sql_escape "${Y}"), | ||
); | ||
" | ||
) | ||
|
||
drbttd() { | ||
init_storage | ||
while sleep 1; do | ||
snapshot_event || true | ||
done | ||
} | ||
|
||
usage() { | ||
printf "\ | ||
Usage: screenshot COMMAND | ||
|
@@ -102,9 +148,9 @@ make_screenshot() { | |
fi | ||
) | ||
case $1 in | ||
'full' ) menyoki -q capture --root png save -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) menyoki -q capture --root --size "${selection}" png save -- "${SCREENSHOT_PATH}" ;; | ||
'focused') menyoki -q capture --focus png save -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) menyoki -q capture --root png save -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) menyoki -q capture --root --size "${selection}" png save -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') menyoki -q capture --focus png save -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has_selection_getter && has shotgun; then | ||
|
@@ -119,23 +165,23 @@ make_screenshot() { | |
) | ||
# shellcheck disable=SC2086 | ||
case $1 in | ||
'full' ) shotgun -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) shotgun ${selection} -- "${SCREENSHOT_PATH}" ;; | ||
'focused') shotgun --id "$(get_focused_window)" -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) shotgun -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) shotgun ${selection} -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') shotgun --id "$(get_focused_window_id)" -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has maim; then | ||
case $1 in | ||
'full' ) maim -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) maim --select -- "${SCREENSHOT_PATH}" ;; | ||
'focused') maim --window "$(get_focused_window)" -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) maim -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) maim --select -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') maim --window "$(get_focused_window_id)" -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has escrotum; then | ||
case $1 in | ||
'full' ) escrotum -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) escrotum --select -- "${SCREENSHOT_PATH}" ;; | ||
'focused') escrotum --xid "$(get_focused_window)" -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) escrotum -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) escrotum --select -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') escrotum --xid "$(get_focused_window_id)" -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has_selection_getter && has mss; then | ||
|
@@ -149,30 +195,30 @@ make_screenshot() { | |
fi | ||
) | ||
case $1 in | ||
'full' ) mss --output "${SCREENSHOT_PATH}" ;; | ||
'select' ) mss --coordinates "${selection}" --output "${SCREENSHOT_PATH}" ;; | ||
'focused') mss --coordinates "$(get_focused_window)" --output "${SCREENSHOT_PATH}" ;; | ||
'full' ) mss --output "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) mss --coordinates "${selection}" --output "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') mss --coordinates "$(get_focused_window_id)" --output "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has scrot; then | ||
case $1 in | ||
'full' ) scrot --multidisp -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) scrot --select -- "${SCREENSHOT_PATH}" ;; | ||
'focused') scrot --focused -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) scrot --multidisp -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) scrot --select -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') scrot --focused -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has gm; then | ||
case $1 in | ||
'full' ) gm import -window root "${SCREENSHOT_PATH}" ;; | ||
'select' ) gm import "${SCREENSHOT_PATH}" ;; | ||
'focused') gm import -window "$(get_focused_window)" "${SCREENSHOT_PATH}" ;; | ||
'full' ) gm import -window root "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) gm import "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') gm import -window "$(get_focused_window_id)" "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
elif has_focus_window_getter && has import; then | ||
case $1 in | ||
'full' ) import -window root -- "${SCREENSHOT_PATH}" ;; | ||
'select' ) import -- "${SCREENSHOT_PATH}" ;; | ||
'focused') import -window "$(get_focused_window)" -- "${SCREENSHOT_PATH}" ;; | ||
'full' ) import -window root -- "${LAINTOOLS_SS_PATH}" ;; | ||
'select' ) import -- "${LAINTOOLS_SS_PATH}" ;; | ||
'focused') import -window "$(get_focused_window_id)" -- "${LAINTOOLS_SS_PATH}" ;; | ||
esac | ||
|
||
else | ||
|
@@ -187,21 +233,21 @@ make_screenshot() { | |
|
||
copy_to_clipboard() { | ||
if has xclip; then | ||
printf %s "${SCREENSHOT_PATH}" | xclip -in -selection primary | ||
xclip -selection clipboard -target image/png <"${SCREENSHOT_PATH}" | ||
printf %s "${LAINTOOLS_SS_PATH}" | xclip -in -selection primary | ||
xclip -selection clipboard -target image/png <"${LAINTOOLS_SS_PATH}" | ||
|
||
elif has xsel; then | ||
printf %s "${SCREENSHOT_PATH}" | xsel --input --primary | ||
printf %s "${SCREENSHOT_PATH}" | xsel --input --clipboard | ||
printf %s "${LAINTOOLS_SS_PATH}" | xsel --input --primary | ||
printf %s "${LAINTOOLS_SS_PATH}" | xsel --input --clipboard | ||
fi | ||
} | ||
|
||
|
||
# https://wiki.archlinux.org/title/List_of_applications/Multimedia#Console | ||
optimize_image() { | ||
if [ -n "${SCREENSHOT_DOCKER_HOST:-}" ] && docker info >/dev/null 2>&1; then | ||
if [ -n "${LAINTOOLS_SS_DOCKER_HOST:-}" ] && docker info >/dev/null 2>&1; then | ||
( | ||
export DOCKER_HOST=${SCREENSHOT_DOCKER_HOST} | ||
export DOCKER_HOST=${LAINTOOLS_SS_DOCKER_HOST} | ||
# Create a container that would auto-destroy in 10 minutes | ||
container_id=$( | ||
docker run --rm -d \ | ||
|
@@ -211,43 +257,43 @@ optimize_image() { | |
# Destroy container on exit from brackets | ||
trap 'docker rm -f "${container_id}" >/dev/null; trap - EXIT; exit' EXIT INT HUP | ||
# Copy file to container, process it, and copy back | ||
docker cp "${SCREENSHOT_PATH}" "${container_id}:/${SCREENSHOT_NAME}" | ||
docker exec -ti "${container_id}" ect -9 "/${SCREENSHOT_NAME}" | ||
docker container cp "${container_id}:/${SCREENSHOT_NAME}" "${SCREENSHOT_PATH}" | ||
docker cp "${LAINTOOLS_SS_PATH}" "${container_id}:/${LAINTOOLS_SS_NAME}" | ||
docker exec -ti "${container_id}" ect -9 "/${LAINTOOLS_SS_NAME}" | ||
docker container cp "${container_id}:/${LAINTOOLS_SS_NAME}" "${LAINTOOLS_SS_PATH}" | ||
|
||
was_optimized | ||
) | ||
|
||
elif has oxipng; then | ||
oxipng --quiet --opt max --threads 1 -- "${SCREENSHOT_PATH}" | ||
oxipng --quiet --opt max --threads 1 -- "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
elif has etc; then | ||
ect -9 "${SCREENSHOT_PATH}" | ||
ect -9 "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
elif has leanify; then | ||
leanify --iteration 100 -- "${SCREENSHOT_PATH}" | ||
leanify --iteration 100 -- "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
elif has pngout; then | ||
pngout "${SCREENSHOT_PATH}" "/tmp/${SCREENSHOT_NAME}" || [ $? = 2 ] | ||
mv "/tmp/${SCREENSHOT_NAME}" "${SCREENSHOT_PATH}" | ||
pngout "${LAINTOOLS_SS_PATH}" "/tmp/${LAINTOOLS_SS_NAME}" || [ $? = 2 ] | ||
mv "/tmp/${LAINTOOLS_SS_NAME}" "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
elif has optipng; then | ||
optipng -o7 -strip all -- "${SCREENSHOT_PATH}" | ||
optipng -o7 -strip all -- "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
elif has pngcrush; then | ||
pngcrush -brute -reduce "${SCREENSHOT_PATH}" "/tmp/${SCREENSHOT_NAME}" | ||
if [ "$(get_size "/tmp/${SCREENSHOT_NAME}")" -lt "$(get_size "${SCREENSHOT_PATH}")" ]; then | ||
mv "/tmp/${SCREENSHOT_NAME}" "${SCREENSHOT_PATH}" | ||
pngcrush -brute -reduce "${LAINTOOLS_SS_PATH}" "/tmp/${LAINTOOLS_SS_NAME}" | ||
if [ "$(get_size "/tmp/${LAINTOOLS_SS_NAME}")" -lt "$(get_size "${LAINTOOLS_SS_PATH}")" ]; then | ||
mv "/tmp/${LAINTOOLS_SS_NAME}" "${LAINTOOLS_SS_PATH}" | ||
fi | ||
was_optimized | ||
|
||
elif has advpng; then | ||
advpng --recompress --shrink-insane --iter=100 -- "${SCREENSHOT_PATH}" | ||
advpng --recompress --shrink-insane --iter=100 -- "${LAINTOOLS_SS_PATH}" | ||
was_optimized | ||
|
||
fi | ||
|
@@ -257,23 +303,23 @@ optimize_image() { | |
ocr_image() { | ||
if has tesseract; then | ||
tesseract \ | ||
-l "${SCREENSHOT_OCR_LANGS}" \ | ||
-l "${LAINTOOLS_SS_OCR_LANGS}" \ | ||
--psm 1 \ | ||
"${SCREENSHOT_PATH}" \ | ||
"${SCREENSHOT_DIR}/${save_date}" | ||
"${LAINTOOLS_SS_PATH}" \ | ||
"${LAINTOOLS_SS_DIR}/${save_date}" | ||
elif has easyocr; then | ||
easyocr \ | ||
--lang $(printf %s "${SCREENSHOT_OCR_LANGS}" |sed -e 's/[a-z]+/ /g' -e 's/[a-z]$//g') \ | ||
--detail 0 --file "${SCREENSHOT_PATH}" > "${SCREENSHOT_DIR}/${save_date}.txt" | ||
--lang $(printf %s "${LAINTOOLS_SS_OCR_LANGS}" |sed -e 's/[a-z]+/ /g' -e 's/[a-z]$//g') \ | ||
--detail 0 --file "${LAINTOOLS_SS_PATH}" > "${LAINTOOLS_SS_DIR}/${save_date}.txt" | ||
fi | ||
} | ||
|
||
|
||
main() { | ||
check_user_input "$@" | ||
mkdir -p "${SCREENSHOT_DIR}" | ||
mkdir -p "${LAINTOOLS_SS_DIR}" | ||
make_screenshot "$@" | ||
printf '%s\n' "${SCREENSHOT_PATH}" | ||
printf '%s\n' "${LAINTOOLS_SS_PATH}" | ||
copy_to_clipboard | ||
( | ||
# Execute under lock | ||
|
@@ -286,4 +332,5 @@ main() { | |
) 9>"/var/lock/screenshot-$(id -u).lock" | ||
} | ||
|
||
main "$@" | ||
# main "$@" | ||
drbttd |