#!/usr/bin/env bash # This file is part of The RetroPie Project # # The RetroPie Project is the legal property of its developers, whose names are # too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source. # # See the LICENSE.md file at the top-level directory of this distribution and # at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md # rp_module_id="bluetooth" rp_module_desc="Configure Bluetooth Devices" rp_module_section="config" function _update_hook_bluetooth() { # fix config location [[ -f "$configdir/bluetooth.cfg" ]] && mv "$configdir/bluetooth.cfg" "$configdir/all/bluetooth.cfg" local mode="$(_get_connect_mode)" # if user has set bluetooth connect mode to boot or background, make sure we # have the latest dependencies and update systemd script if [[ "$mode" != "default" ]]; then # make sure dependencies are up to date ! hasPackage "bluez-tools" && depends_bluetooth connect_mode_set_bluetooth "$mode" fi } function _get_connect_mode() { # get bluetooth config iniConfig "=" '"' "$configdir/all/bluetooth.cfg" iniGet "connect_mode" if [[ -n "$ini_value" ]]; then echo "$ini_value" else echo "default" fi } function depends_bluetooth() { local depends=(bluetooth python3-dbus python3-gi bluez-tools) if [[ "$__os_id" == "Raspbian" ]]; then depends+=(pi-bluetooth raspberrypi-sys-mods) fi getDepends "${depends[@]}" } function get_script_bluetooth() { name="$1" if ! which "$name"; then [[ "$name" == "bluez-test-input" ]] && name="bluez-test-device" name="$md_data/$name" fi echo "$name" } function _slowecho_bluetooth() { local line IFS=$'\n' for line in $(echo -e "${1}"); do echo -e "$line" sleep 1 done unset IFS } function bluez_cmd_bluetooth() { # create a named pipe & fd for input for bluetoothctl local fifo="$(mktemp -u)" mkfifo "$fifo" exec 3<>"$fifo" local line while true; do _slowecho_bluetooth "$1" >&3 # collect output for specified amount of time, then echo it while read -r line; do printf '%s\n' "$line" # (slow) reply to any optional challenges if [[ -n "$3" && "$line" =~ $3 ]]; then _slowecho_bluetooth "$4" >&3 fi done _slowecho_bluetooth "quit\n" >&3 break # read from bluetoothctl buffered line by line done < <(timeout "$2" stdbuf -oL bluetoothctl --agent=NoInputNoOutput <&3) exec 3>&- } function list_available_bluetooth() { local mac local name local info_text="\n\nSearching ..." declare -A paired=() declare -A found=() # get an asc array of paired mac addresses while read mac; read name; do paired+=(["$mac"]="$name") done < <(list_paired_bluetooth) # sixaxis: add USB pairing information [[ -n "$(lsmod | grep hid_sony)" ]] && info_text="Searching ...\n\nDualShock registration: while this text is visible, unplug the controller, press the PS/SHARE button, and then replug the controller." # sixaxis: configure workaround for PS3 when we detect one if cat /proc/bus/input/devices | grep Name | grep -qE "(Sony )?PLAY.*3" ; then _bonded_fix_bluetooth "add" systemctl -q is-active bluetooth.service && systemctl restart bluetooth.service fi dialog --backtitle "$__backtitle" --infobox "$info_text" 7 60 >/dev/tty if hasPackage bluez 5; then # sixaxis: reply to authorization challenge on USB cable connect while read mac; read name; do found+=(["$mac"]="$name") done < <(bluez_cmd_bluetooth "default-agent\nscan on" "15" "Authorize service$" "yes" >/dev/null; bluez_cmd_bluetooth "devices" "3" | grep "^Device " | cut -d" " -f2,3- | sed 's/ /\n/') else while read; read mac; read name; do found+=(["$mac"]="$name") done < <(hcitool scan --flush | tail -n +2 | sed 's/\t/\n/g') fi # display any found addresses that are not already paired for mac in "${!found[@]}"; do if [[ -z "${paired[$mac]}" ]]; then echo "$mac" echo "${found[$mac]}" fi done } function list_registered_bluetooth() { local line while read line; do if [[ "$line" =~ ^(.+)\ \((.+)\)$ ]]; then echo ${BASH_REMATCH[2]} echo ${BASH_REMATCH[1]} fi done < <(bt-device --list 2>/dev/null) } function _devices_grep_bluetooth() { declare -A devices=() local pattern="$1" local mac local name while read mac; read name; do if bt-device --info "$mac" 2>/dev/null | grep -q "$pattern"; then echo "$mac" echo "$name" fi done < <(list_registered_bluetooth) } function list_paired_bluetooth() { _devices_grep_bluetooth "Paired: 1" } function list_connected_bluetooth() { _devices_grep_bluetooth "Connected: 1" } function status_bluetooth() { local paired local connected local mac local name while read mac; read name; do paired+="$mac - $name\n" done < <(list_paired_bluetooth) [[ -z "$paired" ]] && paired="There are no paired devices" while read mac; read name; do connected+="$mac - $name\n" done < <(list_connected_bluetooth) [[ -z "$connected" ]] && connected="There are no connected devices" echo -e "Paired Devices:\n\n$paired\nConnected Devices:\n\n$connected" } function remove_device_bluetooth() { declare -A devices=() local mac local name local options=() # show paired devices first while read mac; read name; do devices+=(["$mac"]="$name") options+=("$mac" "$name") done < <(list_paired_bluetooth) # then list all other devices known while read mac; read name; do if [[ -z "${devices[$mac]}" ]]; then devices+=(["$mac"]="$name") options+=("$mac" "$name") fi done < <(list_registered_bluetooth) if [[ ${#devices[@]} -eq 0 ]] ; then printMsgs "dialog" "There are no devices to remove." else local cmd=(dialog --backtitle "$__backtitle" --menu "Please choose the bluetooth device you would like to remove" 22 76 16) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && return local out out=$(bt-device --remove "$choice" 2>&1) if [[ "$?" -eq 0 ]] ; then printMsgs "dialog" "Device ${devices[$choice]} removed" # remove Bluez workaround if it's the last PS3 device if [[ "${devices[$choice]}" == *PLAY*3* ]]; then list_paired_bluetooth | grep -qE "(Sony )?PLAY.*3" [[ ! $? -eq 0 ]] && _bonded_fix_bluetooth "remove" fi else printMsgs "dialog" "Error removing device:\n\n$out" fi fi } function pair_bluetooth() { declare -A devices=() local mac local name local options=() while read mac; read name; do devices+=(["$mac"]="$name") options+=("$mac" "$name") done < <(list_available_bluetooth) if [[ ${#devices[@]} -eq 0 ]] ; then printMsgs "dialog" "No devices were found. Ensure device is on and try again" return fi local cmd=(dialog --backtitle "$__backtitle" --menu "Please choose the bluetooth device you would like to connect to" 22 76 16) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && return mac="$choice" name="${devices[$choice]}" if [[ "$name" =~ "PLAYSTATION(R)3 Controller" ]]; then bt-device --disconnect="$mac" >/dev/null bt-device --set "$mac" Trusted 1 >/dev/null if [[ "$?" -eq 0 ]]; then printMsgs "dialog" "Successfully authenticated $name ($mac).\n\nYou can now remove the USB cable." else printMsgs "dialog" "Unable to authenticate $name ($mac).\n\nPlease try to pair the device again, making sure to follow the on-screen steps exactly." fi return fi local cmd=(dialog --backtitle "$__backtitle" --menu "Please choose the security mode - Try the first one, then second if that fails" 22 76 16) options=( 1 "DisplayYesNo" 2 "KeyboardDisplay" 3 "NoInputNoOutput" 4 "DisplayOnly" 5 "KeyboardOnly" ) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && return local mode="${options[choice*2-1]}" # create a named pipe & fd for input for bluez-simple-agent local fifo="$(mktemp -u)" mkfifo "$fifo" exec 3<>"$fifo" local line local pin local error="" local skip_connect=0 while read -r line; do case "$line" in "RequestPinCode"*) cmd=(dialog --nocancel --backtitle "$__backtitle" --menu "Please choose a pin" 22 76 16) options=( 1 "Pin 0000" 2 "Enter own Pin" ) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) pin="0000" if [[ "$choice" == "2" ]]; then pin=$(dialog --backtitle "$__backtitle" --inputbox "Please enter a pin" 10 60 2>&1 >/dev/tty) fi dialog --backtitle "$__backtitle" --infobox "Please enter pin $pin on your bluetooth device" 10 60 echo "$pin" >&3 # read "Enter PIN Code:" read -n 15 line ;; "RequestConfirmation"*) # read "Confirm passkey (yes/no): " echo "yes" >&3 read -n 26 line skip_connect=1 break ;; "DisplayPasskey"*|"DisplayPinCode"*) # extract key from end of line # DisplayPasskey (/org/bluez/1284/hci0/dev_01_02_03_04_05_06, 123456) [[ "$line" =~ ,\ (.+)\) ]] && pin=${BASH_REMATCH[1]} dialog --backtitle "$__backtitle" --infobox "Please enter pin $pin on your bluetooth device" 10 60 ;; "Creating device failed"*) error="$line" ;; esac # read from bluez-simple-agent buffered line by line done < <(stdbuf -oL $(get_script_bluetooth bluez-simple-agent) -c "$mode" hci0 "$mac" <&3) exec 3>&- rm -f "$fifo" if [[ "$skip_connect" -eq 1 ]]; then if hcitool con | grep -q "$mac"; then printMsgs "dialog" "Successfully paired and connected to $mac" return 0 else printMsgs "dialog" "Unable to connect to bluetooth device. Please try pairing with the commandline tool 'bluetoothctl'" return 1 fi fi if [[ -z "$error" ]]; then error=$(bt-device --set "$mac" Trusted 1 2>&1) if [[ "$?" -eq 0 ]] ; then return 0 fi fi printMsgs "dialog" "An error occurred connecting to the bluetooth device ($error)" return 1 } function udev_bluetooth() { declare -A devices=() local mac local name local options=() while read mac; read name; do devices+=(["$mac"]="$name") options+=("$mac" "$name") done < <(list_paired_bluetooth) if [[ ${#devices[@]} -eq 0 ]] ; then printMsgs "dialog" "There are no paired bluetooth devices." else local cmd=(dialog --backtitle "$__backtitle" --menu "Please choose the bluetooth device you would like to create a udev rule for" 22 76 16) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && return name="${devices[$choice]}" local config="/etc/udev/rules.d/99-bluetooth.rules" if ! grep -q "$name" "$config"; then local line="SUBSYSTEM==\"input\", ATTRS{name}==\"$name\", MODE=\"0666\", ENV{ID_INPUT_JOYSTICK}=\"1\"" addLineToFile "$line" "$config" printMsgs "dialog" "Added $line to $config\n\nPlease reboot for the configuration to take effect." else printMsgs "dialog" "An entry already exists for $name in $config" fi fi } function connect_bluetooth() { local mac local name while read mac; read name; do bt-device --connect "$mac" 2>/dev/null done < <(list_paired_bluetooth) } function connect_mode_gui_bluetooth() { local mode="$(_get_connect_mode)" [[ -z "$mode" ]] && mode="default" local cmd=(dialog --backtitle "$__backtitle" --default-item "$mode" --menu "Choose a connect mode" 22 76 16) local options=( default "Bluetooth stack default behaviour (recommended)" boot "Connect to devices once at boot" background "Force connecting to devices in the background" ) local choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -n "$choice" ]] && connect_mode_set_bluetooth "$choice" } function connect_mode_set_bluetooth() { local mode="$1" [[ -z "$mode" ]] && mode="default" local config="/etc/systemd/system/connect-bluetooth.service" case "$mode" in boot|background) mkdir -p "$md_inst" sed -e "s#CONFIGDIR#$configdir#" -e "s#ROOTDIR#$rootdir#" "$md_data/connect.sh" >"$md_inst/connect.sh" chmod a+x "$md_inst/connect.sh" cat > "$config" << _EOF_ [Unit] Description=Connect Bluetooth [Service] Type=simple ExecStart=nice -n19 "$md_inst/connect.sh" [Install] WantedBy=multi-user.target _EOF_ systemctl enable "$config" ;; default) if systemctl is-enabled connect-bluetooth 2>/dev/null | grep -q "enabled"; then systemctl disable "$config" fi rm -f "$config" rm -rf "$md_inst" ;; esac iniConfig "=" '"' "$configdir/all/bluetooth.cfg" iniSet "connect_mode" "$mode" chown "$__user":"$__group" "$configdir/all/bluetooth.cfg" } function gui_bluetooth() { addAutoConf "8bitdo_hack" 0 while true; do local connect_mode="$(_get_connect_mode)" local cmd=(dialog --backtitle "$__backtitle" --menu "Configure Bluetooth Devices" 22 76 16) local options=( P "Pair and Connect to Bluetooth Device" X "Remove Bluetooth Device" S "Show Paired & Connected Bluetooth Devices" U "Set up udev rule for Joypad (required for joypads from 8Bitdo etc)" C "Connect now to all paired devices" M "Configure bluetooth connect mode (currently: $connect_mode)" ) local atebitdo if getAutoConf 8bitdo_hack; then atebitdo=1 options+=(8 "8Bitdo mapping hack (ON - old firmware)") else atebitdo=0 options+=(8 "8Bitdo mapping hack (OFF - new firmware)") fi local choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) if [[ -n "$choice" ]]; then # temporarily restore Bluetooth stack (if needed) service sixad status &>/dev/null && sixad -r case "$choice" in P) pair_bluetooth ;; X) remove_device_bluetooth ;; S) printMsgs "dialog" "$(status_bluetooth)" ;; U) udev_bluetooth ;; C) connect_bluetooth ;; M) connect_mode_gui_bluetooth ;; 8) atebitdo="$((atebitdo ^ 1))" setAutoConf "8bitdo_hack" "$atebitdo" ;; esac else # restart sixad (if running) service sixad status &>/dev/null && service sixad restart && printMsgs "dialog" "NOTICE: The ps3controller driver was temporarily interrupted in order to allow compatibility with standard Bluetooth peripherals. Please re-pair your Dual Shock controller to continue (or disregard this message if currently using another controller)." break fi done } function _bonded_fix_bluetooth() { local mode=$1 # exit when we don't have an op mode (install/remove) or a config file [[ -z "$mode" ]] && return [[ ! -f "/etc/bluetooth/input.conf" ]] && return if [[ "$mode" == "add" ]]; then # configure the workaround iniConfig "=" '' "/etc/bluetooth/input.conf" iniSet "ClassicBondedOnly" "false" fi if [[ "$mode" == "remove" ]]; then # remove the workaround iniConfig "=" '' "/etc/bluetooth/input.conf" iniSet "ClassicBondedOnly" "true" fi }