#!/bin/sh

# sudo pacman -S --needed sway grim slurp wf-recorder wl-clipboard jq ffmpeg libnotify libpulse procps-ng python-gobject gtk3 gtk-layer-shell

[ -z "$WAYLAND_DISPLAY" ] && echo "ERROR: Wayland session required" && exit 1

# screenshot directory
screenshot_directory="$HOME/Pictures/screenshots"
videos_directory="$HOME/Videos"
audio_directory="$HOME/Music"

mkdir -p "$screenshot_directory"
mkdir -p "$videos_directory"
mkdir -p "$audio_directory"

filename=$(date '+%Y-%m-%d_%H-%M-%S_pc') #2025-08-10_18-53-58

# h264_nvenc libx264rgb
VIDEO_CODEC="libx264rgb"
FRAMERATE=60
BORDER_WIDTH=2
BORDER_COLOR="#7F8C8D"

desktop="$(pactl get-default-sink).monitor"
output="$(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name')"

window_boxes(){
    swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | "\(.rect.x + .window_rect.x),\(.rect.y + .window_rect.y) \(.window_rect.width)x\(.window_rect.height)"'
}

slurp(){
    window_boxes | command slurp -c "#FFFFFFFF" -d "$@"
}

select_area(){
    python3 - "$BORDER_WIDTH" "$BORDER_COLOR" "$(window_boxes)" << 'EOF'
import subprocess
import sys
import threading
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("GtkLayerShell", "0.1")
gi.require_foreign("cairo")
import cairo
from gi.repository import Gtk, Gdk, GLib, GtkLayerShell

width = int(sys.argv[1])
color = Gdk.RGBA()
color.parse(sys.argv[2])

boxes = []
for line in sys.argv[3].splitlines():
    try:
        position, size = line.split(" ")
        box_x, box_y = map(int, position.split(","))
        box_w, box_h = map(int, size.split("x"))
        boxes.append((box_x, box_y, box_w, box_h))
    except ValueError:
        pass

state = {"start": None, "dragged": False, "selection": None, "size": None, "pending": None, "result": None}
condition = threading.Condition()
windows = []

def redraw():
    for window in windows:
        window.queue_draw()

def box_at(px, py):
    found = [b for b in boxes if b[0] <= px < b[0] + b[2] and b[1] <= py < b[1] + b[3]]
    return min(found, key=lambda b: b[2] * b[3]) if found else None

def set_selection(selection):
    if selection == state["selection"]:
        return
    state["selection"] = selection
    state["size"] = None
    if selection:
        with condition:
            state["pending"] = selection
            condition.notify()
    redraw()

def apply_size(selection, size):
    if selection == state["selection"]:
        state["size"] = size
        redraw()
    return False

def measure():
    while True:
        with condition:
            while state["pending"] is None:
                condition.wait()
            selection = state["pending"]
            state["pending"] = None
        x, y, w, h = selection
        size = 0
        if w > 0 and h > 0:
            try:
                size = len(subprocess.run(["grim", "-g", f"{x},{y} {w}x{h}", "-"], capture_output=True, check=True).stdout)
            except (subprocess.CalledProcessError, OSError):
                size = None
        GLib.idle_add(apply_size, selection, size)

def format_size(size):
    if size is None:
        return "..."
    if size >= 1000000000:
        decimal = f"{size / 1000000000:.2f} GB"
    elif size >= 1000000:
        decimal = f"{size / 1000000:.1f} MB"
    else:
        decimal = f"{size / 1000:.1f} KB"
    if size >= 1073741824:
        binary = f"{size / 1073741824:.2f} GiB"
    elif size >= 1048576:
        binary = f"{size / 1048576:.1f} MiB"
    else:
        binary = f"{size / 1024:.1f} KiB"
    return f"{decimal} ({binary})"

def draw(widget, cr, geometry):
    cr.set_operator(cairo.OPERATOR_SOURCE)
    cr.set_source_rgba(0, 0, 0, 0)
    cr.paint()
    cr.set_operator(cairo.OPERATOR_OVER)

    selection = state["selection"]
    cr.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
    cr.set_source_rgba(0, 0, 0, 0.25)
    cr.rectangle(0, 0, geometry.width, geometry.height)
    if selection:
        cr.rectangle(selection[0] - geometry.x, selection[1] - geometry.y, selection[2], selection[3])
    cr.fill()

    if not selection:
        return True

    x, y, w, h = selection
    x -= geometry.x
    y -= geometry.y

    Gdk.cairo_set_source_rgba(cr, color)
    cr.set_line_width(width)
    cr.rectangle(x - width / 2, y - width / 2, w + width, h + width)
    cr.stroke()

    if not (0 <= x + w - 1 < geometry.width and 0 <= y + h - 1 < geometry.height):
        return True

    text = f"{w}x{h}  {format_size(state['size'])}"
    cr.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
    cr.set_font_size(14)
    ascent, descent = cr.font_extents()[:2]
    padding = 6
    box_width = cr.text_extents(text).x_advance + padding * 2
    box_height = ascent + descent + padding * 2

    box_x = min(max(x + w + width - box_width, 0), geometry.width - box_width)
    box_y = y + h + width + 4
    if box_y + box_height > geometry.height:
        box_y = max(y - width - 4 - box_height, 0)

    cr.set_source_rgba(0, 0, 0, 0.6)
    cr.rectangle(box_x, box_y, box_width, box_height)
    cr.fill()
    cr.set_source_rgba(1, 1, 1, 1)
    cr.move_to(box_x + padding, box_y + padding + ascent)
    cr.show_text(text)
    return True

def finish():
    selection = state["selection"]
    state["start"] = None
    if not selection or selection[2] <= 0 or selection[3] <= 0:
        return
    state["result"] = selection
    Gtk.main_quit()

def press(widget, event, geometry):
    if event.button == 3:
        Gtk.main_quit()
        return True
    if event.button != 1:
        return True
    state["start"] = (int(event.x) + geometry.x, int(event.y) + geometry.y)
    state["dragged"] = False
    return True

def motion(widget, event, geometry):
    px = int(event.x) + geometry.x
    py = int(event.y) + geometry.y
    if state["start"]:
        sx, sy = state["start"]
        if state["dragged"] or abs(px - sx) > 2 or abs(py - sy) > 2:
            state["dragged"] = True
            set_selection((min(sx, px), min(sy, py), abs(px - sx), abs(py - sy)))
    else:
        set_selection(box_at(px, py))
    return True

def release(widget, event, geometry):
    if event.button != 1 or not state["start"]:
        return True
    if not state["dragged"]:
        set_selection(box_at(int(event.x) + geometry.x, int(event.y) + geometry.y))
    finish()
    return True

def key(widget, event):
    if event.keyval == Gdk.KEY_Escape:
        Gtk.main_quit()
    return True

threading.Thread(target=measure, daemon=True).start()

display = Gdk.Display.get_default()
for index in range(display.get_n_monitors()):
    monitor = display.get_monitor(index)
    geometry = monitor.get_geometry()

    window = Gtk.Window()
    GtkLayerShell.init_for_window(window)
    GtkLayerShell.set_namespace(window, "region-select")
    GtkLayerShell.set_layer(window, GtkLayerShell.Layer.OVERLAY)
    GtkLayerShell.set_monitor(window, monitor)
    GtkLayerShell.set_exclusive_zone(window, -1)
    for edge in (GtkLayerShell.Edge.TOP, GtkLayerShell.Edge.BOTTOM, GtkLayerShell.Edge.LEFT, GtkLayerShell.Edge.RIGHT):
        GtkLayerShell.set_anchor(window, edge, True)
    try:
        GtkLayerShell.set_keyboard_mode(window, GtkLayerShell.KeyboardMode.EXCLUSIVE)
    except AttributeError:
        GtkLayerShell.set_keyboard_interactivity(window, True)

    window.set_app_paintable(True)
    window.set_visual(window.get_screen().get_rgba_visual())
    window.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.KEY_PRESS_MASK)

    window.connect("draw", draw, geometry)
    window.connect("button-press-event", press, geometry)
    window.connect("motion-notify-event", motion, geometry)
    window.connect("button-release-event", release, geometry)
    window.connect("key-press-event", key)
    window.connect("destroy", Gtk.main_quit)
    window.show_all()
    window.get_window().set_cursor(Gdk.Cursor.new_from_name(display, "crosshair"))
    windows.append(window)

Gtk.main()

for window in windows:
    window.destroy()
display.flush()

if not state["result"]:
    sys.exit(1)

x, y, w, h = state["result"]
print(f"{x},{y} {w}x{h}")
EOF
}

show_border(){
    python3 - "$1" "$BORDER_WIDTH" "$BORDER_COLOR" "$2" << 'EOF' &
import os
import sys
import time
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("GtkLayerShell", "0.1")
gi.require_foreign("cairo")
import cairo
from gi.repository import Gtk, Gdk, GLib, GtkLayerShell

position, size = sys.argv[1].split(" ")
x, y = map(int, position.split(","))
w, h = map(int, size.split("x"))
width = int(sys.argv[2])
color = Gdk.RGBA()
color.parse(sys.argv[3])
path = sys.argv[4]
start = time.monotonic()

monitor = Gdk.Display.get_default().get_monitor_at_point(x, y)
geometry = monitor.get_geometry()
x -= geometry.x
y -= geometry.y

window = Gtk.Window()
GtkLayerShell.init_for_window(window)
GtkLayerShell.set_namespace(window, "region-border")
GtkLayerShell.set_layer(window, GtkLayerShell.Layer.OVERLAY)
GtkLayerShell.set_monitor(window, monitor)
GtkLayerShell.set_exclusive_zone(window, -1)
for edge in (GtkLayerShell.Edge.TOP, GtkLayerShell.Edge.BOTTOM, GtkLayerShell.Edge.LEFT, GtkLayerShell.Edge.RIGHT):
    GtkLayerShell.set_anchor(window, edge, True)

window.set_app_paintable(True)
window.set_visual(window.get_screen().get_rgba_visual())

def draw(widget, cr):
    cr.set_operator(cairo.OPERATOR_SOURCE)
    cr.set_source_rgba(0, 0, 0, 0)
    cr.paint()
    cr.set_operator(cairo.OPERATOR_OVER)
    Gdk.cairo_set_source_rgba(cr, color)
    cr.set_line_width(width)
    cr.rectangle(x - width / 2, y - width / 2, w + width, h + width)
    cr.stroke()

    try:
        size_bytes = os.path.getsize(path)
    except OSError:
        size_bytes = 0

    if size_bytes >= 1000000000:
        decimal = f"{size_bytes / 1000000000:.2f} GB"
    elif size_bytes >= 1000000:
        decimal = f"{size_bytes / 1000000:.1f} MB"
    else:
        decimal = f"{size_bytes / 1000:.1f} KB"

    if size_bytes >= 1073741824:
        binary = f"{size_bytes / 1073741824:.2f} GiB"
    elif size_bytes >= 1048576:
        binary = f"{size_bytes / 1048576:.1f} MiB"
    else:
        binary = f"{size_bytes / 1024:.1f} KiB"

    elapsed = int(time.monotonic() - start)
    hours, rest = divmod(elapsed, 3600)
    minutes, seconds = divmod(rest, 60)
    timer = f"{hours}:{minutes:02}:{seconds:02}" if hours else f"{minutes:02}:{seconds:02}"

    text = f"{w}x{h}  {decimal} ({binary})  {timer}"

    cr.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
    cr.set_font_size(14)
    ascent, descent = cr.font_extents()[:2]
    padding = 6
    box_width = cr.text_extents(text).x_advance + padding * 2
    box_height = ascent + descent + padding * 2

    box_x = min(max(x + w + width - box_width, 0), geometry.width - box_width)
    box_y = y + h + width + 4
    if box_y + box_height > geometry.height:
        box_y = max(y - width - 4 - box_height, 0)

    cr.set_source_rgba(0, 0, 0, 0.6)
    cr.rectangle(box_x, box_y, box_width, box_height)
    cr.fill()
    cr.set_source_rgba(1, 1, 1, 1)
    cr.move_to(box_x + padding, box_y + padding + ascent)
    cr.show_text(text)
    return True

def tick():
    window.queue_draw()
    return True

window.connect("draw", draw)
window.connect("destroy", Gtk.main_quit)
window.show_all()
window.input_shape_combine_region(cairo.Region())
GLib.timeout_add(500, tick)
Gtk.main()
EOF
    border_pid=$!
}

screenshot_selected_area(){
    geometry="$(select_area)" || exit 1
    grim -g "$geometry" "$screenshot_directory/$filename.png"
    wl-copy < "$screenshot_directory/$filename.png"
    notify-send -i "$screenshot_directory/$filename.png" "Screenshot" "Area screenshot taken"
}

screenshot_full_screen(){
    grim "$screenshot_directory/$filename.png"
    wl-copy < "$screenshot_directory/$filename.png"
    notify-send -i "$screenshot_directory/$filename.png" "Screenshot" "Full screen screenshot taken"
}

video_selected_area(){
    geometry="$(slurp)" || exit 1
    show_border "$geometry" "$videos_directory/$filename.mp4"
    wf-recorder -g "$geometry" -c "$VIDEO_CODEC" -r "$FRAMERATE" -f "$videos_directory/$filename.mp4"
    kill "$border_pid"
    grim -g "$geometry" -t png "/tmp/$filename.png"
    notify-send -i "/tmp/$filename.png" "Video" "Area video taken"
}

video_full_screen(){
    wf-recorder -o "$output" -c "$VIDEO_CODEC" -r "$FRAMERATE" -f "$videos_directory/$filename.mp4"
    ffmpeg -i "$videos_directory/$filename.mp4" -ss 00:00:01.000 -vframes 1 "/tmp/$filename.png" 2>/dev/null
    notify-send -i "/tmp/$filename.png" "Video" "Full screen video taken"
}

video_selected_area_audio(){
    geometry="$(slurp)" || exit 1
    show_border "$geometry" "$videos_directory/$filename.mp4"
    wf-recorder -g "$geometry" -c "$VIDEO_CODEC" -r "$FRAMERATE" --audio="$desktop" -f "$videos_directory/$filename.mp4"
    kill "$border_pid"
    grim -g "$geometry" -t png "/tmp/$filename.png"
    notify-send -i "/tmp/$filename.png" "Video" "Area video with desktop audio taken"
}

video_full_screen_audio(){
    wf-recorder -o "$output" -c "$VIDEO_CODEC" -r "$FRAMERATE" --audio="$desktop" -f "$videos_directory/$filename.mp4"
    ffmpeg -i "$videos_directory/$filename.mp4" -ss 00:00:01.000 -vframes 1 "/tmp/$filename.png" 2>/dev/null
    notify-send -i "/tmp/$filename.png" "Video" "Full screen video with desktop audio taken"
}

stop(){
    pkill -fxn '(/\S+)*ffmpeg\s.*\spulse\s.*'
    pkill -fxn '(/\S+)*wf-recorder\s.*'
    pkill -fxn '(/\S+)*pw-record\s.*'
    exit 1
}

help(){
cat << EOF
-h | -help | --help
-s | -stop
-sa | -screenshot_selected_area
-sf | -screenshot_full_screen
-va | -video_selected_area
-vf | -video_full_screen
-vaad | -video_selected_area_audio
-vfad | -video_full_screen_audio
EOF
}

main(){
while [ "$1" != "" ]; do
    case "$1" in
        -h|-help|--help)
            help
            exit 0
            ;;
        -s|-stop)
            stop
            ;;
        -sa|-screenshot_selected_area)
            screenshot_selected_area
            ;;
        -sf|-screenshot_full_screen)
            screenshot_full_screen
            ;;
        -va|-video_selected_area)
            video_selected_area
            ;;
        -vf|-video_full_screen)
            video_full_screen
            ;;
        -vaad|-video_selected_area_audio)
            video_selected_area_audio
            ;;
        -vfad|-video_full_screen_audio)
            video_full_screen_audio
            ;;
        *)
            echo "ERROR: unknown parameter $1"
            help
            exit 1
            ;;
    esac
    shift
done
}

main "$@"


