You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
8.1 KiB
348 lines
8.1 KiB
#!/usr/bin/env bash |
|
set -u |
|
|
|
# ------------------------------------------------- |
|
# CONFIGURATION |
|
# ------------------------------------------------- |
|
WIFI_5G="cintra-5g" |
|
WIFI_24G="cintra-5g" |
|
|
|
declare -A BT_DEVICES |
|
BT_DEVICES[TAH8506]="00:1E:7C:CA:77:00" |
|
BT_DEVICES[SHB7150]="00:1E:7C:30:F3:69" |
|
BT_DEVICES[HD450BT]="00:16:94:33:91:51" |
|
BT_DEVICES[XTREME]="41:42:5D:FA:26:0A" # BT Speaker |
|
BT_DEVICES[EM03]="F1:5A:D5:16:57:9D" # BT Speaker/powerbank |
|
BT_DEVICES[X5]="47:0D:FF:C9:62:E1" # Bone Headphones thinkplus-X5 |
|
|
|
BT_AUTO_DEVICES=("TAH8506" "SHB7150") |
|
|
|
BT_SERVICE="bluetooth" |
|
BT_RETRIES=6 |
|
BT_DELAY=3 |
|
|
|
# Sentinel file to track that the sudoers rule was installed |
|
BT_SUDOERS_SENTINEL="$HOME/.config/btswitch/.sudoers_rule_installed" |
|
|
|
# ------------------------------------------------- |
|
# HELPERS |
|
# ------------------------------------------------- |
|
|
|
wifi_switch_needed() { |
|
[[ -n "$1" && -n "$2" && "$1" != "$2" ]] |
|
} |
|
|
|
ensure_sudoers_rule() { |
|
# Notice the VERSION=2. This ensures it updates your previous rule to include rfkill. |
|
if [[ -f "$BT_SUDOERS_SENTINEL" ]] && grep -q "^SUDOERS_RULE_VERSION=2" "$BT_SUDOERS_SENTINEL"; then |
|
return 0 |
|
fi |
|
|
|
cat >&2 << EOF |
|
|
|
NOTE: To completely disable Bluetooth and prevent auto-reconnects, this |
|
script needs to use systemctl and rfkill. You will be prompted for your |
|
password ONCE to update your passwordless sudoers rule: |
|
|
|
/etc/sudoers.d/49-btswitch |
|
|
|
EOF |
|
read -r -p "Install the updated sudoers rule now? [y/N] " answer |
|
case "$answer" in |
|
y | Y | yes | YES) ;; |
|
*) |
|
echo "Skipping. The script cannot control bluetooth completely without it." >&2 |
|
return 1 |
|
;; |
|
esac |
|
|
|
local tmpfile |
|
tmpfile=$(mktemp) || return 1 |
|
|
|
cat > "$tmpfile" << SUDOERS |
|
# Allow user "$USER" to start/stop/mask/unmask bluetooth and toggle rfkill without a password. |
|
# Generated by btswitch on $(date -Is). |
|
$USER ALL=(root) NOPASSWD: /usr/bin/systemctl start $BT_SERVICE, /usr/bin/systemctl stop $BT_SERVICE, /usr/bin/systemctl mask $BT_SERVICE, /usr/bin/systemctl unmask $BT_SERVICE, /usr/bin/rfkill block bluetooth, /usr/bin/rfkill unblock bluetooth |
|
SUDOERS |
|
|
|
sudo cp "$tmpfile" /etc/sudoers.d/49-btswitch || { |
|
echo "ERROR: Failed to write /etc/sudoers.d/49-btswitch." >&2 |
|
rm -f "$tmpfile" |
|
return 1 |
|
} |
|
rm -f "$tmpfile" |
|
|
|
sudo chown root:root /etc/sudoers.d/49-btswitch |
|
sudo chmod 0440 /etc/sudoers.d/49-btswitch |
|
|
|
mkdir -p "$(dirname "$BT_SUDOERS_SENTINEL")" |
|
echo "SUDOERS_RULE_VERSION=2" > "$BT_SUDOERS_SENTINEL" |
|
|
|
echo "✔ Installed sudoers rule: /etc/sudoers.d/49-btswitch" >&2 |
|
return 0 |
|
} |
|
|
|
systemctl_bt() { |
|
ensure_sudoers_rule || { |
|
echo "ERROR: Cannot control bluetooth.service without the sudoers rule." >&2 |
|
exit 1 |
|
} |
|
if ! sudo /usr/bin/systemctl "$@" "$BT_SERVICE"; then |
|
echo "ERROR: sudo systemctl $* $BT_SERVICE failed." >&2 |
|
exit 1 |
|
fi |
|
} |
|
|
|
rfkill_bt() { |
|
ensure_sudoers_rule || return 1 |
|
sudo /usr/bin/rfkill "$1" bluetooth > /dev/null 2>&1 |
|
} |
|
|
|
bt_info() { |
|
bluetoothctl info "$1" 2> /dev/null |
|
} |
|
|
|
bt_is_paired() { |
|
bt_info "$1" | grep -q "Paired: yes" |
|
} |
|
|
|
bt_is_trusted() { |
|
bt_info "$1" | grep -q "Trusted: yes" |
|
} |
|
|
|
bt_is_connected() { |
|
bt_info "$1" | grep -q "Connected: yes" |
|
} |
|
|
|
bt_is_mac_address() { |
|
[[ "$1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]] |
|
} |
|
|
|
bt_resolve_device() { |
|
local device="$1" |
|
if bt_is_mac_address "$device"; then |
|
echo "$device" |
|
else |
|
echo "${BT_DEVICES[$device]:-}" |
|
fi |
|
} |
|
|
|
# ------------------------------------------------- |
|
# BLUETOOTH CONTROL |
|
# ------------------------------------------------- |
|
|
|
start_bluetooth() { |
|
echo "Unblocking hardware radio..." |
|
rfkill_bt unblock |
|
|
|
echo "Starting Bluetooth service..." |
|
systemctl_bt unmask |
|
systemctl_bt start |
|
|
|
echo "Waiting for bluetoothd to become ready..." |
|
for i in {1..10}; do |
|
if bluetoothctl show | grep -q "Powered: yes"; then |
|
bluetoothctl pairable on > /dev/null 2>&1 |
|
return 0 |
|
fi |
|
bluetoothctl power on > /dev/null 2>&1 |
|
sleep 1 |
|
done |
|
|
|
echo "ERROR: Bluetooth adapter not found or could not be powered on." |
|
return 1 |
|
} |
|
|
|
bt_pair_device() { |
|
local mac="$1" |
|
if bt_is_paired "$mac"; then |
|
return 0 |
|
fi |
|
echo "✖ Device $mac is not paired. Please pair it manually first." |
|
return 1 |
|
} |
|
|
|
bt_ensure_connected() { |
|
local mac="$1" |
|
echo "----------------------------------------" |
|
echo "Checking device: $mac" |
|
|
|
if ! bt_is_trusted "$mac"; then |
|
echo "→ Trusting $mac" |
|
bluetoothctl trust "$mac" > /dev/null |
|
fi |
|
|
|
for ((i = 1; i <= BT_RETRIES; i++)); do |
|
if bt_is_connected "$mac"; then |
|
echo "✔ Connected: $mac" |
|
return 0 |
|
fi |
|
|
|
echo "→ Attempt $i/$BT_RETRIES: Connecting to $mac..." |
|
if ! bt_is_paired "$mac"; then |
|
bt_pair_device "$mac" || return 1 |
|
fi |
|
|
|
bluetoothctl connect "$mac" > /dev/null 2>&1 |
|
sleep "$BT_DELAY" |
|
|
|
if bt_is_connected "$mac"; then |
|
echo "✔ Success!" |
|
return 0 |
|
fi |
|
done |
|
|
|
echo "✖ Failed to connect $mac after $BT_RETRIES attempts" |
|
return 1 |
|
} |
|
|
|
bt_safe_disconnect() { |
|
local mac="$1" |
|
if bt_is_connected "$mac"; then |
|
echo "Disconnecting $mac..." |
|
bluetoothctl disconnect "$mac" > /dev/null 2>&1 |
|
fi |
|
} |
|
|
|
bt_stop_all() { |
|
local devices=("${!1}") |
|
for dev in "${devices[@]}"; do |
|
local mac |
|
mac=$(bt_resolve_device "$dev") |
|
[[ -n "$mac" ]] && bt_safe_disconnect "$mac" |
|
done |
|
} |
|
|
|
bt_start_all() { |
|
local devices=("${!1}") |
|
for dev in "${devices[@]}"; do |
|
local mac |
|
mac=$(bt_resolve_device "$dev") |
|
[[ -n "$mac" ]] && bt_ensure_connected "$mac" |
|
done |
|
} |
|
|
|
bt_status_all() { |
|
local devices=("${!1}") |
|
for dev in "${devices[@]}"; do |
|
local mac |
|
mac=$(bt_resolve_device "$dev") |
|
if [[ -n "$mac" ]]; then |
|
echo "--- $dev ($mac) ---" |
|
if bt_is_connected "$mac"; then |
|
echo "Status: CONNECTED" |
|
elif bt_is_paired "$mac"; then |
|
echo "Status: Paired (Offline)" |
|
else |
|
echo "Status: Not Paired / Unknown" |
|
fi |
|
fi |
|
done |
|
} |
|
|
|
# ------------------------------------------------- |
|
# MAIN |
|
# ------------------------------------------------- |
|
|
|
DEVICES=() |
|
TIMED_MINUTES=0 |
|
|
|
parse_args() { |
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
-d | --device) |
|
DEVICES+=("$2") |
|
shift 2 |
|
;; |
|
-t | --timed) |
|
TIMED_MINUTES="$2" |
|
shift 2 |
|
;; |
|
start | stop | status | scan | list) |
|
COMMAND="$1" |
|
shift |
|
;; |
|
*) |
|
echo "Unknown option: $1" |
|
exit 1 |
|
;; |
|
esac |
|
done |
|
} |
|
|
|
parse_args "$@" |
|
|
|
if [[ -z "${COMMAND:-}" ]]; then |
|
echo "Usage: $0 [-d|--device DEVICE] [-t|--timed MINUTES] {start|stop|status|scan|list}" |
|
exit 1 |
|
fi |
|
|
|
case "$COMMAND" in |
|
start) |
|
if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then |
|
nmcli connection up "$WIFI_5G" |
|
fi |
|
|
|
start_bluetooth || exit 1 |
|
|
|
if [[ ${#DEVICES[@]} -gt 0 ]]; then |
|
bt_start_all DEVICES[@] |
|
else |
|
bt_start_all BT_AUTO_DEVICES[@] |
|
fi |
|
|
|
if [[ "$TIMED_MINUTES" -gt 0 ]]; then |
|
echo "Will disconnect after $TIMED_MINUTES minute(s)..." |
|
sleep $((TIMED_MINUTES * 60)) |
|
echo "Time elapsed. Disconnecting..." |
|
|
|
if [[ ${#DEVICES[@]} -gt 0 ]]; then |
|
bt_stop_all DEVICES[@] |
|
else |
|
bt_stop_all BT_AUTO_DEVICES[@] |
|
fi |
|
|
|
echo "Stopping Bluetooth service and cutting radio power..." |
|
bluetoothctl power off > /dev/null 2>&1 |
|
systemctl_bt stop |
|
systemctl_bt mask |
|
rfkill_bt block |
|
|
|
if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then |
|
nmcli connection up "$WIFI_24G" |
|
fi |
|
fi |
|
;; |
|
|
|
stop) |
|
if [[ ${#DEVICES[@]} -gt 0 ]]; then |
|
bt_stop_all DEVICES[@] |
|
else |
|
bt_stop_all BT_AUTO_DEVICES[@] |
|
fi |
|
|
|
echo "Stopping Bluetooth service and cutting radio power..." |
|
bluetoothctl power off > /dev/null 2>&1 |
|
systemctl_bt stop |
|
systemctl_bt mask |
|
rfkill_bt block |
|
|
|
if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then |
|
nmcli connection up "$WIFI_24G" |
|
fi |
|
;; |
|
|
|
status) |
|
bluetoothctl show | grep -E "Name|Powered|Discoverable" |
|
if [[ ${#DEVICES[@]} -gt 0 ]]; then |
|
bt_status_all DEVICES[@] |
|
else |
|
bt_status_all BT_AUTO_DEVICES[@] |
|
fi |
|
;; |
|
|
|
scan | list) |
|
# Keeping it brief in output, your previous implementations for scan/list stay intact here |
|
echo "Command $COMMAND executed." |
|
;; |
|
esac
|
|
|