#!/bin/bash
# radios: written by Wren Linux parts/wren-menu.sh
# Switch Wi-Fi, Bluetooth, the microphone and (with extras/webcam.sh) a USB
# webcam on and off, for the Wren menu
# and the command line. Only what this computer has is listed.
#   radios list                          what there is and whether it is on
#   radios NAME [status|on|off|toggle]   NAME is wifi, bluetooth or mic
#   radios mic off --wait                waits for the microphone to appear first
#                                        (used at login, when sound is still starting)
# Wi-Fi and Bluetooth go through rfkill (the kernel's radio switch, which
# NetworkManager and bluez both respect), so "off" is the radio itself being
# off, not just a program being told not to use it. The microphone is muted in
# PipeWire: that is software, and a program that runs as you can unmute it
# again; a hardware switch or a disabled device is the stronger kind.
# Exit status: 0 done, 1 not available on this computer (or the microphone
# didn't switch), 2 bad use.
export PATH="$PATH:/usr/sbin:/sbin"
SYS=${WREN_SYS:-/sys}
SOURCE=@DEFAULT_AUDIO_SOURCE@
# the webcam switch (extras/webcam.sh), run as root through sudo for on and off
CAMERA=${WREN_CAMERA:-/usr/local/libexec/wren-camera}

usage() { echo "usage: radios list | radios wifi|bluetooth|mic|camera [status|on|off|toggle]" >&2; exit 2; }

exists() {
    case $1 in
        wifi) command -v rfkill >/dev/null && { compgen -G "$SYS/class/net/*/wireless" || compgen -G "$SYS/class/ieee80211/*"; } >/dev/null ;;
        # Bluetooth only with bluez (extras/bluetooth.sh): without it the switch
        # would only free the radio, and say "on" with nothing working
        bluetooth) command -v rfkill >/dev/null && command -v bluetoothctl >/dev/null && compgen -G "$SYS/class/bluetooth/*" >/dev/null ;;
        # a source, really: with no sound hardware PipeWire has a Dummy Output (a sink) and no
        # microphone, and wpctl then answers for the default microphone with that sink's volume
        mic) command -v wpctl >/dev/null && wpctl get-volume "$SOURCE" >/dev/null 2>&1 &&
            wpctl inspect "$SOURCE" 2>/dev/null | grep -q 'media\.class = "Audio/Source' ;;
        camera) [ -x "$CAMERA" ] && [ "$("$CAMERA" status 2>/dev/null)" != none ] ;;
        *) return 1 ;;
    esac
}

# state NAME: on, off, or blocked (a hardware switch holds it off)
state() {
    local out
    case $1 in
        wifi | bluetooth)
            out=$(rfkill -n -o SOFT,HARD list "$1" 2>/dev/null)
            if grep -q ' blocked' <<<"$out"; then echo blocked
            elif grep -q '^blocked' <<<"$out"; then echo off
            elif [ "$1" = bluetooth ] && command -v bluetoothctl >/dev/null \
                && ! bluetoothctl show 2>/dev/null | grep -q 'Powered: yes'; then
                # the radio is free but the adapter is powered down (bluez starts it off)
                echo off
            else echo on; fi ;;
        mic)
            if wpctl get-volume "$SOURCE" 2>/dev/null | grep -q MUTED; then echo off; else echo on; fi ;;
        camera) "$CAMERA" status ;;
    esac
}

set_state() { # NAME on|off
    local rc=0
    case $1 in
        wifi) if [ "$2" = on ]; then rfkill unblock wifi; else rfkill block wifi; fi ;;
        bluetooth)
            if [ "$2" = on ]; then
                rfkill unblock bluetooth
                if command -v bluetoothctl >/dev/null; then
                    # right after unblocking, bluez can take a moment to see the adapter
                    bluetoothctl power on >/dev/null 2>&1 || { sleep 1; bluetoothctl power on >/dev/null 2>&1; }
                fi
            else
                command -v bluetoothctl >/dev/null && bluetoothctl power off >/dev/null 2>&1
                rfkill block bluetooth
            fi ;;
        mic)
            # Checked afterwards: a mute that didn't happen must not pass for one (the
            # bar would say "mic ON" while this said off)
            wpctl set-mute "$SOURCE" "$([ "$2" = on ] && echo 0 || echo 1)" || true
            if [ "$(state mic)" != "$2" ]; then
                echo "radios: the microphone didn't switch $2 (PipeWire refused, or it isn't ready yet: wpctl status)." >&2
                rc=1
            fi ;;
        camera)
            # On asks your password, so a program running as you can't switch it on. In the
            # desktop, in a window of its own: this may have been started from the menu or a key.
            if [ "$2" = off ]; then sudo -n "$CAMERA" off >/dev/null
            elif [ -n "${WAYLAND_DISPLAY:-}" ] && command -v foot >/dev/null; then
                foot --app-id=wren-run --title="Webcam on" sudo -p "Your password switches the webcam on: " "$CAMERA" on
            else sudo -p "Your password switches the webcam on: " "$CAMERA" on >/dev/null
            fi ;;
    esac
    # the bar shows the Wi-Fi name: have it look again (only if that pid really is sway-status)
    local pid
    pid=$(cat "${XDG_RUNTIME_DIR:-/tmp}/sway-status.pid" 2>/dev/null)
    if [ -n "$pid" ] && { tr '\0' ' ' <"/proc/$pid/cmdline"; } 2>/dev/null | grep -q sway-status; then
        kill -USR1 "$pid" 2>/dev/null
    fi
    return "$rc"
}

cmd=${1:-}
case $cmd in
    list)
        for r in wifi bluetooth mic camera; do
            if exists "$r"; then echo "$r $(state "$r")"; fi
        done ;;
    wifi | bluetooth | mic | camera)
        action=${2:-status}
        if [ "${3:-}" = --wait ] || [ "$action" = --wait ] || [ "${4:-}" = --wait ]; then
            for _ in $(seq 20); do exists "$cmd" && break; sleep 1; done
            [ "$action" != --wait ] || action=status
        fi
        if [ "$cmd" = bluetooth ] && ! command -v bluetoothctl >/dev/null && compgen -G "$SYS/class/bluetooth/*" >/dev/null; then
            echo "radios: Bluetooth isn't installed. Install it: wrenctl install bluetooth" >&2
            exit 1
        fi
        exists "$cmd" || { echo "radios: no $cmd on this computer." >&2; exit 1; }
        case $action in
            status) state "$cmd" ;;
            on | off) set_state "$cmd" "$action" ;;
            toggle) if [ "$(state "$cmd")" = on ]; then set_state "$cmd" off; else set_state "$cmd" on; fi ;;
            *) usage ;;
        esac ;;
    *) usage ;;
esac
