#!/bin/bash set -u readonly PANAMA_OSD_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" strict_delivery() { [[ ${PANAMA_OSD_STRICT:-false} == true || ${PANAMA_OSD_STRICT:-false} == 1 ]] } show_progress() { if ! qs ipc call osd progress "$1" "$2" 100 "$3" >/dev/null 2>&1; then strict_delivery && return 1 fi return 0 } show_message() { if ! qs ipc call osd message "$1" "$2" >/dev/null 2>&1; then strict_delivery && return 1 fi return 0 } volume_state() { local target="$1" output level percent muted=false output="$(wpctl get-volume "$target" 2>/dev/null)" || return 1 if [[ $output =~ Volume:[[:space:]]*([0-9]+([.][0-9]+)?) ]]; then level="${BASH_REMATCH[1]}" else return 1 fi [[ $output == *"[MUTED]"* ]] && muted=true percent="$(awk -v value="$level" 'BEGIN { printf "%d", value * 100 + 0.5 }')" printf '%s %s\n' "$percent" "$muted" } show_volume() { local target="$1" kind="$2" state percent muted label state="$(volume_state "$target")" || return 0 read -r percent muted <<<"$state" if [[ $muted == true ]]; then show_progress "${kind}-muted" "$percent" "Muted" else label="${percent}%" show_progress "$kind" "$percent" "$label" fi } adjust_volume() { local action="${1:-}" step="${2:-6}" target="@DEFAULT_AUDIO_SINK@" case "$action" in up) wpctl set-volume -l 1 "$target" "${step}%+" || return ;; down) wpctl set-volume "$target" "${step}%-" || return ;; toggle) wpctl set-mute "$target" toggle || return ;; *) printf 'Usage: panama-osd volume up|down|toggle [step]\n' >&2; return 2 ;; esac show_volume "$target" volume } adjust_microphone() { local action="${1:-}" step="${2:-6}" target="@DEFAULT_AUDIO_SOURCE@" case "$action" in up) wpctl set-volume -l 1 "$target" "${step}%+" || return ;; down) wpctl set-volume "$target" "${step}%-" || return ;; toggle) wpctl set-mute "$target" toggle || return ;; *) printf 'Usage: panama-osd microphone up|down|toggle [step]\n' >&2; return 2 ;; esac show_volume "$target" microphone } brightness_percent() { local output="$1" percent percent="$(awk -F, 'NR == 1 { value=$4; gsub(/%/, "", value); print value }' <<<"$output")" [[ $percent =~ ^[0-9]+$ ]] || return 1 printf '%s\n' "$percent" } brightness_error() { local detail="$1" label="External brightness unavailable" if [[ $detail == *udev* || $detail == *accessible* || $detail == *permission* ]]; then label="Brightness needs permission" fi show_message dialog-warning-symbolic "$label" || true if command -v notify-send >/dev/null 2>&1; then notify-send --app-name=Panama --icon=display-brightness-symbolic \ "Brightness unavailable" "$detail" >/dev/null 2>&1 || true fi } discover_ddc_bus() { local helper="$1" cache_file="$2" list_json focused selected error bus connector command -v jq >/dev/null 2>&1 || { brightness_error "jq is required to discover DDC/CI displays." return 1 } list_json="$("$helper" list 2>/dev/null)" || { brightness_error "The external brightness helper could not inspect connected displays." return 1 } if ! jq -e 'type == "object" and (.displays | type == "array")' >/dev/null 2>&1 <<<"$list_json"; then brightness_error "The external brightness helper returned invalid display information." return 1 fi error="$(jq -r '.error // empty' <<<"$list_json")" if [[ -n $error ]]; then brightness_error "$error" return 1 fi focused="$(hyprctl -j monitors 2>/dev/null \ | jq -r '.[] | select(.focused == true) | .name' 2>/dev/null \ | head -n1)" selected="$(jq -r --arg connector "$focused" ' ([.displays[] | select(.connector == $connector)][0] // .displays[0] // empty) | [.bus, .connector] | @tsv ' <<<"$list_json")" IFS=$'\t' read -r bus connector <<<"$selected" if [[ ! $bus =~ ^[0-9]+$ ]]; then brightness_error "No connected monitor exposes DDC/CI brightness control." return 1 fi umask 077 printf '%s\t%s\n' "$bus" "$connector" >"$cache_file" printf '%s\n' "$bus" } adjust_ddc_brightness() { local action="$1" step="$2" local helper="${PANAMA_OSD_BRIGHTNESS_HELPER:-$PANAMA_OSD_SCRIPT_DIR/panama-brightness}" local runtime_dir="${PANAMA_OSD_RUNTIME_DIR:-${XDG_RUNTIME_DIR:-/tmp}/panama-osd-${UID}}" local cache_file="$runtime_dir/brightness-bus" lock_file="$runtime_dir/brightness.lock" local bus="" connector="" current target lock_fd [[ -x $helper ]] || { brightness_error "The external brightness helper is not installed." return 0 } mkdir -p "$runtime_dir" || return 0 chmod 700 "$runtime_dir" 2>/dev/null || true exec {lock_fd}>"$lock_file" || return 0 # DDC transactions on one I2C bus cannot safely overlap. A short wait also # sheds an excessive key-repeat backlog instead of replaying it seconds later. flock -w 2 "$lock_fd" || return 0 if [[ -r $cache_file ]]; then IFS=$'\t' read -r bus connector <"$cache_file" || true [[ $bus =~ ^[0-9]+$ ]] || bus="" fi if [[ -n $bus ]]; then current="$("$helper" get "$bus" 2>/dev/null)" || current="" if [[ ! $current =~ ^[0-9]+$ ]]; then : >"$cache_file" bus="" fi fi if [[ -z $bus ]]; then bus="$(discover_ddc_bus "$helper" "$cache_file")" || return 0 current="$("$helper" get "$bus" 2>/dev/null)" || current="" fi if [[ ! $current =~ ^[0-9]+$ ]]; then brightness_error "The selected monitor stopped responding over DDC/CI." return 0 fi if [[ $action == up ]]; then target=$(( current + step )) else target=$(( current - step )) fi (( target > 100 )) && target=100 (( target < 0 )) && target=0 if ! "$helper" set "$bus" "$target" >/dev/null 2>&1; then brightness_error "The selected monitor did not accept the brightness change." return 0 fi show_progress brightness "$target" "${target}%" } adjust_brightness() { local action="${1:-}" step="${2:-5}" output percent [[ $step =~ ^[0-9]+$ ]] || { printf 'Usage: panama-osd brightness up|down [step]\n' >&2 return 2 } case "$action" in up|down) ;; *) printf 'Usage: panama-osd brightness up|down [step]\n' >&2; return 2 ;; esac # Laptop panels expose a kernel backlight class and remain the fastest, # most reliable path. Desktops fall through to DDC/CI monitor control. output="$(brightnessctl -m -c backlight 2>/dev/null)" || output="" if percent="$(brightness_percent "$output")"; then if [[ $action == up ]]; then brightnessctl -e4 -n2 -c backlight set "${step}%+" >/dev/null || return 0 else brightnessctl -e4 -n2 -c backlight set "${step}%-" >/dev/null || return 0 fi output="$(brightnessctl -m -c backlight 2>/dev/null)" || return 0 percent="$(brightness_percent "$output")" || return 0 show_progress brightness "$percent" "${percent}%" return fi adjust_ddc_brightness "$action" "$step" } media_action() { local action="${1:-}" kind label fallback case "$action" in play-pause) playerctl play-pause || return if [[ $(playerctl status 2>/dev/null) == "Playing" ]]; then kind="media-play" fallback="Playing" else kind="media-pause" fallback="Paused" fi ;; next) playerctl next || return; kind="media-next"; fallback="Next track" ;; previous) playerctl previous || return; kind="media-previous"; fallback="Previous track" ;; stop) playerctl stop || return; kind="media-stop"; fallback="Stopped" ;; *) printf 'Usage: panama-osd media play-pause|next|previous|stop\n' >&2; return 2 ;; esac label="$(playerctl metadata --format '{{ title }} — {{ artist }}' 2>/dev/null)" [[ -n $label ]] || label="$fallback" show_message "$kind" "$label" } case "${1:-}" in volume) shift; adjust_volume "$@" ;; microphone) shift; adjust_microphone "$@" ;; brightness) shift; adjust_brightness "$@" ;; media) shift; media_action "$@" ;; message) shift kind="${1:-}" [[ -n $kind && $# -ge 2 ]] || { printf 'Usage: panama-osd message ICON LABEL\n' >&2 exit 2 } shift show_message "$kind" "$*" ;; *) printf 'Usage: panama-osd volume|microphone|brightness|media|message ACTION [value]\n' >&2 exit 2 ;; esac