#!/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 "$@"