Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Issue #1289 ds4|5 disconnect #1484

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ RUN rm -f /etc/profile.d/toolbox.sh && \
systemctl enable bazzite-hardware-setup.service && \
systemctl enable tailscaled.service && \
systemctl enable dev-hugepages1G.mount && \
systemctl --global enable bazzite-bluetooth-ds4-ds5-workaround.service && \
systemctl --global enable bazzite-user-setup.service && \
systemctl --global enable podman.socket && \
systemctl --global enable systemd-tmpfiles-setup.service && \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Disconnect DS4 and DS5 controller on shortcut HOME + triangle
After=bluetooth.target
ConditionFileIsExecutable=/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround

[Service]
User=root
ExecCondition=/usr/bin/systemctl is-enabled bluetooth.service
ExecStart=/bin/bash /usr/libexec/bazzite-bluetooth-ds4-ds5-workaround
Restart=on-failure
RestartSec=5
TimeoutSec=5s

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/bin/bash

# Script Name: bazzite-bluetooth-ds4-ds5-workaround
# Author: https://github.com/thekk1
# Version: 1.5
#
# Related issues:
# https://github.com/ublue-os/bazzite/issues/1289
# https://github.com/ValveSoftware/steam-for-linux/issues/8678
#
# Description:
# This script monitors connected PS4 and PS5 gamepad devices and disconnects
# the Bluetooth connection if specific button combinations are detected (Home and Triangle buttons).
# The script handles background processes for each device and maintains a record of process IDs
# to ensure proper cleanup of processes associated with devices that are no longer present.
#
# This script should be removed if there is a native solution under one of the mentioned issues.
#
# Usage:
# ./bazzite-bluetooth-ds4-ds5-workaround [-d]
# -d: Enable debug mode

DEBUG=false
MONITOR_FILE="/tmp/monitored_devices.txt"
truncate -s 0 $MONITOR_FILE

# Check for the debug flag
while getopts "d" opt; do
case ${opt} in
d ) DEBUG=true ;;
\? ) echo "Usage: $0 [-d]" ;;
esac
done

# Function to find gamepad devices (PS4 and PS5)
find_gamepad_devices() {
local devices=()
local base_path="/sys/class/input"

for entry in "$base_path"/event*; do
local device_path="$entry/device"
local uevent_path="$device_path/uevent"

if [[ -f "$uevent_path" ]]; then
local devicename
devicename=$(grep "NAME" "$uevent_path" | cut -d'=' -f2 | tr -d '"')
if [[ "$devicename" == "Wireless Controller" || "$devicename" == "DualSense Wireless Controller" ]]; then
devices+=("$(basename "$entry")")
fi
fi
done
echo "${devices[@]}"
}

# Function to get MAC address from uevent file
get_mac_address_from_uevent() {
local device="$1"
local uniq_path="/sys/class/input/$device/device/uevent"

if [[ -f "$uniq_path" ]]; then
local mac_address
mac_address=$(grep "UNIQ" "$uniq_path" | cut -d'=' -f2 | tr -d '"')
echo "$mac_address"
else
echo ""
fi
}

# Function to monitor the gamepad and disconnect Bluetooth connection
monitor_gamepad() {
local device_path="$1"
local bluetooth_mac_address="$2"
local process_name="monitor_gamepad_$bluetooth_mac_address"

$DEBUG && echo "Starting monitoring on device: $device_path"

# Check if process is already running
if pgrep -f "$process_name" > /dev/null; then
$DEBUG && echo "Process for $bluetooth_mac_address is already running."
return
fi

# Define the command to disconnect all controllers
disconnect_cmd="for controller in \$(bluetoothctl list | awk '/Controller/ {print \$2}'); do echo -e \"select \$controller\ndisconnect $bluetooth_mac_address\n\" | bluetoothctl; done"

# Run the monitoring process with a specific process name
(cat "$device_path" 2>&1 | xxd -p -c 24 | awk -v mac="$bluetooth_mac_address" -v pname="$process_name" -v cmd="$disconnect_cmd" -v debug="$DEBUG" '
BEGIN { home_pressed=0; triangle_pressed=0 }
{
event_part=substr($0,33);
if(substr(event_part,1,4)=="0100") {
code=substr(event_part,5,4);
value=substr(event_part,9,2);
if(code=="3301" || code=="3c01") {
if(value=="01") {
if(code=="3301") home_pressed=1;
if(code=="3c01") triangle_pressed=1;
if(home_pressed && triangle_pressed) {
# Print a message before executing the disconnect command
if (debug == "true") {
print "Disconnect command issued for MAC address: " mac;
}
# Execute the disconnect command only once
system(cmd);
home_pressed=0;
triangle_pressed=0;
}
} else {
home_pressed=0;
triangle_pressed=0;
}
}
}
}' >&1) & echo $! > "/tmp/$process_name.pid" & disown

$DEBUG && echo "Started monitoring process for $bluetooth_mac_address with process name $process_name."
}

# Function to clean up processes
cleanup_processes() {
# List of currently monitored MAC addresses
local monitored_macs=$(cat "$MONITOR_FILE")

# Iterate through all monitored MAC addresses
while IFS= read -r mac; do
process_name="monitor_gamepad_$mac"
pid_file="/tmp/${process_name}.pid"

if [[ -z "$mac" ]]; then
continue
fi

if [[ -f "$pid_file" ]]; then
pid=$(cat "$pid_file")
if ! ps -p "$pid" > /dev/null; then
$DEBUG && echo "Stopping process for $mac as it's no longer running."
pkill -f "$process_name"
rm -f "$pid_file"
# Remove the MAC address from the monitor file
sed -i "/$mac/d" "$MONITOR_FILE"
fi
else
$DEBUG && echo "No PID file found for $mac."
fi
done <<< "$monitored_macs"
}

# Main loop of the script
while true; do
# Find currently connected gamepad devices
gamepad_devices=$(find_gamepad_devices)

# Get current MAC addresses from the monitor file
current_macs=$(cat "$MONITOR_FILE")

if [[ -z "$gamepad_devices" ]]; then
$DEBUG && echo "No PS4 or PS5 controllers found."
else
$DEBUG && echo "Found devices: $gamepad_devices"
# Convert the list of devices into an array
IFS=' ' read -r -a devices_array <<< "$gamepad_devices"

# Loop over the found devices
for device in "${devices_array[@]}"; do
device_path="/dev/input/$device"
mac_address=$(get_mac_address_from_uevent "$device")

if [[ -n "$mac_address" ]]; then
# Check if this device is already being monitored
if ! grep -q "$mac_address" "$MONITOR_FILE"; then
echo "$mac_address" >> "$MONITOR_FILE"
$DEBUG && echo "Monitoring device: $device_path"
monitor_gamepad "$device_path" "$mac_address"
fi
else
$DEBUG && echo "No MAC address found for $device."
fi
done
fi

# Clean up processes that are no longer needed
cleanup_processes

sleep 5 # Wait before searching for new devices
done