diff --git a/README.md b/README.md index 615545c..0f4684d 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,20 @@ reinstall_dotfiles - ✅ **WSL** (Windows Subsystem for Linux) - ✅ **macOS** (shell and tool configs, package installation not automated) +## Corsair headset auto-switch + +`bin/corsair-headset-switch` is a systemd user service that switches the default +PipeWire sink to the Corsair VOID Elite when the headset powers on and restores +the previous sink when it powers off. It depends on `headsetcontrol` (installed +by `setup.sh`). After symlinking, enable it once: + +```bash +systemctl --user daemon-reload +systemctl --user enable --now corsair-headset.service +``` + +Logs: `journalctl --user -u corsair-headset -f`. + ## 📝 License Personal dotfiles - use at your own discretion. diff --git a/bin/corsair-headset-switch b/bin/corsair-headset-switch new file mode 100755 index 0000000..e071164 --- /dev/null +++ b/bin/corsair-headset-switch @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -uo pipefail + +readonly VOID_SINK_PATTERN='VOID_ELITE' +readonly POLL_INTERVAL=2 + +parse_headset_state() { + local json status + json="$(cat)" + [[ -n "${json}" ]] || { echo unknown; return; } + status="$(jq -r '.devices[0].battery.status // empty' <<<"${json}" 2>/dev/null)" + case "${status}" in + BATTERY_AVAILABLE | BATTERY_CHARGING) echo on ;; + BATTERY_UNAVAILABLE | BATTERY_TIMEOUT) echo off ;; + *) echo unknown ;; + esac +} + +headset_state() { + local json + json="$(headsetcontrol -o json 2>/dev/null)" || { echo unknown; return; } + parse_headset_state <<<"${json}" +} + +void_sink() { + pactl list short sinks | awk -v p="${VOID_SINK_PATTERN}" '$0 ~ p { print $2; exit }' +} + +log() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*"; } + +main() { + command -v headsetcontrol >/dev/null || { echo 'headsetcontrol not found' >&2; exit 1; } + command -v jq >/dev/null || { echo 'jq not found' >&2; exit 1; } + command -v pactl >/dev/null || { echo 'pactl not found' >&2; exit 1; } + + local prev current sink saved_default='' + prev="$(headset_state)" + [[ "${prev}" == unknown ]] && prev=off + log "baseline headset state: ${prev}" + + while true; do + current="$(headset_state)" + case "${current}" in + on) + if [[ "${prev}" == off ]]; then + sink="$(void_sink)" + if [[ -n "${sink}" ]]; then + saved_default="$(pactl get-default-sink)" + pactl set-default-sink "${sink}" + log "headset on -> default sink = ${sink} (was ${saved_default})" + else + log 'headset on but VOID sink not found; skipping' + fi + fi + prev=on + ;; + off) + if [[ "${prev}" == on ]]; then + if [[ -n "${saved_default}" ]] \ + && pactl list short sinks | awk '{print $2}' | grep -qxF "${saved_default}"; then + pactl set-default-sink "${saved_default}" + log "headset off -> restored default sink = ${saved_default}" + else + log 'headset off but no valid previous sink to restore' + fi + fi + prev=off + ;; + *) : ;; + esac + sleep "${POLL_INTERVAL}" + done +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main +fi diff --git a/setup.sh b/setup.sh index 0585019..417f6ed 100755 --- a/setup.sh +++ b/setup.sh @@ -47,6 +47,7 @@ init_links() { move_link .config/spotifyd/spotifyd.conf media/spotifyd.conf move_link .config/fontconfig fontconfig move_link .config/ngrok/ngrok.yml web/ngrok.yml + move_link .config/systemd/user/corsair-headset.service systemd/corsair-headset.service echo "Symlinking complete. Backups stored in ${SCRIPT_PATH}/backups." return @@ -67,7 +68,7 @@ install_packages() { return fi - if grep --quiet 'ID=manjaro' /etc/os-release; then + if is_arch; then # shellcheck disable=2119 install_arch_packages post_install_packages diff --git a/shell/functions b/shell/functions index 7f3cf23..b0a8a74 100644 --- a/shell/functions +++ b/shell/functions @@ -8,6 +8,50 @@ section_start() { echo "${1}" } +detect_aur_helper() { + local h + for h in paru pamac yay; do + if command -v "${h}" &>/dev/null; then + AUR_HELPER="${h}" + return 0 + fi + done + echo 'No supported AUR helper (paru/pamac/yay) found' >&2 + return 1 +} + +arch_install() { + [[ -n "${AUR_HELPER:-}" ]] || detect_aur_helper || return 1 + local cmd + case "${AUR_HELPER}" in + paru | yay) cmd=("${AUR_HELPER}" -S --noconfirm --needed "$@") ;; + pamac) cmd=(sudo pamac install --no-confirm --no-upgrade "$@") ;; + *) + echo "Unknown AUR helper: ${AUR_HELPER}" >&2 + return 1 + ;; + esac + if [[ -n "${DRY:-}" ]]; then + echo "${cmd[*]}" + return 0 + fi + "${cmd[@]}" +} + +enable_aur() { + [[ -n "${AUR_HELPER:-}" ]] || detect_aur_helper || return 1 + [[ "${AUR_HELPER}" == pamac ]] || return 0 + [[ -n "${DRY:-}" ]] && return 0 + sudo sed -Ei '/EnableAUR/s/^#//' /etc/pamac.conf +} + +is_arch() { + local id id_like + id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")" + id_like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")" + [[ "${id}" == arch ]] || [[ " ${id_like} " == *' arch '* ]] +} + reinstall_dotfiles() { rm -rf ~/.{local/bin,npm,nvm,zinit,go/bin,config/composer/vendor,nvim,config/nvim} cd ~/.dotfiles && ./setup.sh @@ -15,7 +59,7 @@ reinstall_dotfiles() { install_arch_packages() { echo 'Enabling the AUR...' - sudo sed -Ei '/EnableAUR/s/^#//' /etc/pamac.conf + enable_aur PACKAGES=( # General @@ -90,6 +134,7 @@ install_arch_packages() { tldr git jq + headsetcontrol ripgrep exa fd @@ -101,11 +146,7 @@ install_arch_packages() { bat ) - # shellcheck disable=2046 - sudo pamac install --no-confirm --no-upgrade $( - IFS=' ' - echo "${PACKAGES[*]}" - ) "$@" + arch_install "${PACKAGES[@]}" "$@" } install_wsl_packages() { @@ -393,7 +434,7 @@ post_install_packages() { echo 'Installing `spotify-tui' mkdir "${HOME}/.rustup" rustup default stable - pamac install --no-confirm --no-upgrade spotify-tui + arch_install spotify-tui } post_setup_arch_services() { @@ -403,7 +444,7 @@ post_setup_arch_services() { } setup_arch_system_emoji_support() { - sudo pamac install noto-fonts-emoji + arch_install noto-fonts-emoji sudo cp ~/.dotfiles/templates/fonts-local.conf /etc/fonts/local.conf } diff --git a/systemd/corsair-headset.service b/systemd/corsair-headset.service new file mode 100644 index 0000000..ed18837 --- /dev/null +++ b/systemd/corsair-headset.service @@ -0,0 +1,12 @@ +[Unit] +Description=Corsair VOID Elite auto-switch +After=pipewire.service + +[Service] +Type=simple +ExecStart=%h/.dotfiles/bin/corsair-headset-switch +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target