mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2025-12-09 22:48:00 -08:00
191 lines
5.8 KiB
Bash
Executable File
191 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Capture a region with slurp+grim. If AnkiConnect is available, attach the
|
|
# JPEG to the newest note; otherwise copy a PNG to the clipboard.
|
|
|
|
set -euo pipefail
|
|
|
|
ANKI_CONNECT_PORT="${ANKI_CONNECT_PORT:-8765}"
|
|
PICTURE_FIELD="${PICTURE_FIELD:-Picture}"
|
|
QUALITY="${QUALITY:-90}"
|
|
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/screenshot-anki"
|
|
ANKI_URL="http://localhost:${ANKI_CONNECT_PORT}"
|
|
HOSTNAME_SHORT="$(hostname -s 2>/dev/null || hostname)"
|
|
HYPRLAND_GEOMETRY_FILTER='"\\(.at[0]),\\(.at[1]) \\(.size[0])x\\(.size[1])"'
|
|
REQUIREMENTS=(slurp grim wl-copy xdotool curl jq rofi)
|
|
ROFI_THEME_STR='listview {columns: 2; lines: 3;} window {width: 45%;}'
|
|
ROFI_THEME="$HOME/.config/rofi/launchers/type-2/style-2.rasi"
|
|
CAPTURE_MODE=""
|
|
|
|
notify() {
|
|
if command -v notify-send >/dev/null 2>&1; then
|
|
notify-send "$@"
|
|
fi
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
notify "Missing dependency" "$1 is required"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
wiggle_mouse() {
|
|
# Avoid disappearing cursor on some compositors
|
|
xdotool mousemove_relative 1 1
|
|
xdotool mousemove_relative -- -1 -1
|
|
}
|
|
|
|
drain_enter_key() {
|
|
# Release lingering Enter press from launching via rofi so it
|
|
# doesn't reach the next focused window (e.g., a game).
|
|
xdotool keyup Return 2>/dev/null || true
|
|
xdotool keyup KP_Enter 2>/dev/null || true
|
|
}
|
|
|
|
capture_region() {
|
|
local fmt="$1" quality="$2" output="$3"
|
|
local geometry
|
|
geometry=$(slurp)
|
|
if [[ -z "$geometry" ]]; then
|
|
notify "Screenshot cancelled" "No region selected"
|
|
exit 1
|
|
fi
|
|
if [[ "$fmt" == "jpeg" ]]; then
|
|
grim -g "$geometry" -t jpeg -q "$quality" "$output"
|
|
else
|
|
grim -g "$geometry" -t png "$output"
|
|
fi
|
|
}
|
|
|
|
capture_current_window() {
|
|
local fmt="$1" quality="$2" output="$3" geometry
|
|
|
|
if [[ "$HOSTNAME_SHORT" != "luna" && "$HOSTNAME_SHORT" != "lapis" ]]; then
|
|
notify "Window capture unavailable" "Falling back to region selection"
|
|
capture_region "$fmt" "$quality" "$output"
|
|
return
|
|
fi
|
|
|
|
geometry=$(hyprctl -j activewindow 2>/dev/null | jq -r "$HYPRLAND_GEOMETRY_FILTER" 2>/dev/null || true)
|
|
if [[ -z "$geometry" || "$geometry" == "null" ]]; then
|
|
notify "Window capture failed" "Falling back to region selection"
|
|
capture_region "$fmt" "$quality" "$output"
|
|
return
|
|
fi
|
|
|
|
if [[ "$fmt" == "jpeg" ]]; then
|
|
grim -g "$geometry" -t jpeg -q "$quality" "$output"
|
|
else
|
|
grim -g "$geometry" -t png "$output"
|
|
fi
|
|
}
|
|
|
|
choose_capture_mode() {
|
|
local selection
|
|
selection=$(printf "%s\n%s\n" "Region (slurp)" "Current window (Hyprland)" |
|
|
rofi -dmenu -i \
|
|
-p "Capture mode" \
|
|
-mesg "Select capture target" \
|
|
-no-custom \
|
|
-no-lazy-grab \
|
|
-location 0 -yoffset 30 -xoffset 30 \
|
|
-theme "$ROFI_THEME" \
|
|
-theme-str "$ROFI_THEME_STR" \
|
|
-window-title "screenshot-anki")
|
|
|
|
if [[ -z "$selection" ]]; then
|
|
notify "Screenshot cancelled" "No capture mode selected"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$selection" == "Current window (Hyprland)" ]]; then
|
|
if [[ "$HOSTNAME_SHORT" != "luna" && "$HOSTNAME_SHORT" != "lapis" ]]; then
|
|
notify "Window capture unavailable" "Using region instead (host: $HOSTNAME_SHORT)"
|
|
CAPTURE_MODE="region"
|
|
else
|
|
CAPTURE_MODE="window"
|
|
fi
|
|
else
|
|
CAPTURE_MODE="region"
|
|
fi
|
|
}
|
|
|
|
copy_to_clipboard() {
|
|
local file="$1"
|
|
if ! wl-copy <"$file"; then
|
|
notify "Error copying screenshot" "wl-copy failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
get_newest_note_id() {
|
|
local response
|
|
response=$(curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' \
|
|
-d '{"action":"findNotes","version":6,"params":{"query":"is:new"}}')
|
|
jq -r '.result[-1] // empty' <<<"$response"
|
|
}
|
|
|
|
update_note_with_image() {
|
|
local note_id="$1" image_path="$2" filename="$3"
|
|
local payload
|
|
payload=$(jq -n --argjson noteId "$note_id" --arg field "$PICTURE_FIELD" \
|
|
--arg path "$image_path" --arg filename "$filename" '
|
|
{action:"updateNoteFields",version:6,
|
|
params:{note:{id:$noteId,fields:{($field):""},
|
|
picture:[{path:$path,filename:$filename,fields:[$field]}]}}}')
|
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
|
}
|
|
|
|
open_note_in_browser() {
|
|
local note_id="$1"
|
|
local payload
|
|
payload=$(jq -n --argjson noteId "$note_id" '
|
|
{action:"guiBrowse",version:6,params:{query:("nid:" + ($noteId|tostring))}}')
|
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
|
}
|
|
|
|
main() {
|
|
local requirements=("${REQUIREMENTS[@]}")
|
|
for cmd in "${requirements[@]}"; do
|
|
require_cmd "$cmd"
|
|
done
|
|
|
|
mkdir -p "$CACHE_DIR"
|
|
local timestamp base newest_note image_path
|
|
timestamp=$(date +%s)
|
|
base="$CACHE_DIR/$timestamp"
|
|
|
|
drain_enter_key
|
|
choose_capture_mode
|
|
|
|
if [[ "$CAPTURE_MODE" == "window" ]]; then
|
|
require_cmd hyprctl
|
|
fi
|
|
|
|
wiggle_mouse
|
|
newest_note=$(get_newest_note_id)
|
|
|
|
local capture_fn="capture_region"
|
|
if [[ "$CAPTURE_MODE" == "window" ]]; then
|
|
capture_fn="capture_current_window"
|
|
fi
|
|
|
|
if [[ -n "$newest_note" ]]; then
|
|
image_path="$base.jpg"
|
|
"$capture_fn" "jpeg" "$QUALITY" "$image_path"
|
|
update_note_with_image "$newest_note" "$image_path" "paste-$timestamp.jpg"
|
|
open_note_in_browser "$newest_note"
|
|
notify -i "$image_path" "Screenshot Taken" "Added to Anki note"
|
|
rm -f "$image_path"
|
|
else
|
|
image_path="$base.png"
|
|
"$capture_fn" "png" "" "$image_path"
|
|
copy_to_clipboard "$image_path"
|
|
notify -i "$image_path" "Screenshot Taken" "Copied to clipboard"
|
|
rm -f "$image_path"
|
|
fi
|
|
}
|
|
|
|
main "$@"
|