diff --git a/system_files/desktop/shared/etc/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules b/system_files/desktop/shared/etc/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules new file mode 100644 index 0000000000..a10f5a0cba --- /dev/null +++ b/system_files/desktop/shared/etc/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules @@ -0,0 +1,4 @@ +# DO NOT TOUCH ME +# This file is here in order to disable /usr/lib/udev/rules.d/80-bazzite-ps-controller-sleep.rules +# Read /usr/lib/systemd/system/bazzite-bluetooth-ds4-ds5-workaround@.service +# Delete me if you want to enable workaround \ No newline at end of file diff --git a/system_files/desktop/shared/usr/lib/systemd/system/bazzite-bluetooth-ds4-ds5-workaround@.service b/system_files/desktop/shared/usr/lib/systemd/system/bazzite-bluetooth-ds4-ds5-workaround@.service new file mode 100644 index 0000000000..86a3376f58 --- /dev/null +++ b/system_files/desktop/shared/usr/lib/systemd/system/bazzite-bluetooth-ds4-ds5-workaround@.service @@ -0,0 +1,31 @@ +# By Bazzite +# This workaround allows for DualShock/Dualsense controllers of disconnecting +# when pressing HOME + TRIANGLE +# +# Files composing workaround: +# +# - /usr/lib/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules +# Activates the service with controller MAC as parameter. +# +# - /usr/lib/systemd/system/bazzite-bluetooth-ds4-ds5-workaround@.service +# Allows to start binary that handles keycode reading. +# +# - /usr/libexec/bazzite-bluetooth-ds4-ds5-workaround +# Reads keycodes and disconnects controllers when HOME + TRIANGLE is read. +# +# - /etc/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules +# Empty file that acts as a deactivator of the workaround + +[Unit] +Description=Disconnect DS4 and DS5 controller on shortcut HOME + triangle +After=bluetooth.target +Requires=bluetooth.target +ConditionFileIsExecutable=/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround + +[Service] +User=root +Type=simple +CPUQuota=25% +ExecStart=/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround %I +Restart=on-failure +RestartSec=5 diff --git a/system_files/desktop/shared/usr/lib/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules b/system_files/desktop/shared/usr/lib/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules new file mode 100644 index 0000000000..e2e6c07fe8 --- /dev/null +++ b/system_files/desktop/shared/usr/lib/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules @@ -0,0 +1 @@ +ACTION=="add", SUBSYSTEMS=="input", ATTRS{name}=="*Wireless Controller", RUN+="/usr/bin/systemctl start --no-block --runtime bazzite-bluetooth-ds4-ds5-workaround@$attr{uniq}.service" \ No newline at end of file diff --git a/system_files/desktop/shared/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround b/system_files/desktop/shared/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround new file mode 100755 index 0000000000..f88e115781 --- /dev/null +++ b/system_files/desktop/shared/usr/libexec/bazzite-bluetooth-ds4-ds5-workaround @@ -0,0 +1,144 @@ +#!/usr/bin/python + +""" +# Script Name: bazzite-bluetooth-ds4-ds5-workaround +# Related issues: +# https://github.com/ublue-os/bazzite/issues/1289 +# https://github.com/ValveSoftware/steam-for-linux/issues/8678 +# +# Original version written by https://github.com/thekk1, see https://github.com/ublue-os/bazzite/pull/1416 +# +# 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). +# +# This script should be removed if there is a native solution under one of the mentioned issues. +# +# Usage: +# ./bazzite-bluetooth-ds4-ds5-workaround + +""" +# See https://gist.github.com/noohgnas/7c896791d437e51122c59fe9576a1bcf for reference +import subprocess +import sys +from typing import cast + +import evdev +from evdev import InputDevice, InputEvent + + +class KeyStatus: + def __init__(self) -> None: + self.home: bool = False + self.triangle: bool = False + self.other: set[int] = set() + + +GAMEPAD_KEYS = { + "HOME": [ + 316, # Dualshock + ], + "TRIANGLE": [ + 307, # Dualshock + ], +} + +EV_KEY = 1 + + +def trigger_callback(device_mac: str): + """Action to realize when HOME + TRIANGLE is held at the same time""" + # Get bluetooth adapters + p = subprocess.Popen( + ["/usr/bin/bluetoothctl", "list"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + p.wait() + adapt_strlines = p.stdout + if adapt_strlines is None: + print("ERROR: No adapters detected") + return + + for adap in adapt_strlines: + print(f"DEBUG: {adap}") + adap = adap.strip().split(" ", maxsplit=2)[1] + p2 = subprocess.Popen( + ["/usr/bin/bash", "-c", f"""echo -e "select {adap}\ndisconnect {device_mac}" | bluetoothctl """], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + text=True, + ) + p2.wait() + + +def get_device(mac: str) -> InputDevice | None: + """Obtain the /dev/input/eventX path for a bluetooth + Returns: + str: path address + """ + res: str | None = None + mac = mac.lower() + devices = [evdev.InputDevice(d) for d in evdev.list_devices()] + for device in [d for d in devices if d.uniq]: + if mac == str(device.uniq).lower(): + res = device + break + return res + + +def main(argv: list[str]): + if len(argv[1:]) != 1: + print("ERROR: Missing argument", file=sys.stderr) + exit(1) + + device_mac = argv[1] + if not device_mac or device_mac == "": + print("ERROR: Missing device bluetooth mac address", file=sys.stderr) + exit(0) + + global keys_status + keys_status = KeyStatus() + + gamepad = get_device(device_mac) + if gamepad is None: + print("ERROR: Incorrect device bluetooth mac address", file=sys.stderr) + exit(69) # systemd-analyze exit-status UNAVAILABLE + + for event in gamepad.read_loop(): + event = cast(InputEvent, event) + + code = event.code + value = event.value + if event.type == 1: + if code in GAMEPAD_KEYS["HOME"]: + keys_status.home = value == 1 + elif code in GAMEPAD_KEYS["TRIANGLE"]: + keys_status.triangle = value == 1 + else: + ( + keys_status.other.add(code) + if value == 1 + else keys_status.other.remove(code) + ) + + # print( + # f"home: {keys_status.home}, triangle: {keys_status.triangle}, other: {keys_status.other}", + # ) + if ( + value == 1 + and keys_status.home + and keys_status.triangle + and len(keys_status.other) <= 0 + ): + #### Here we manage what happens when the HOME TRIANGLE is pressed + print("HOME + TRIANGLE detected") + trigger_callback(device_mac) + + +if __name__ == "__main__": + try: + main(sys.argv) + except KeyboardInterrupt as _: + pass diff --git a/system_files/desktop/shared/usr/share/ublue-os/just/80-bazzite.just b/system_files/desktop/shared/usr/share/ublue-os/just/80-bazzite.just index a266c95059..517d6f2c35 100644 --- a/system_files/desktop/shared/usr/share/ublue-os/just/80-bazzite.just +++ b/system_files/desktop/shared/usr/share/ublue-os/just/80-bazzite.just @@ -342,3 +342,100 @@ bazzite-cli: benchmark: echo 'Running a 1 minute benchmark ...' cd /tmp && stress-ng --matrix 0 -t 1m --times + +# Enable/disable HOME + TRIANGLE controller shutdown option for DualShock/DualSense +toggle-ds45-workaround ACTION="": + #!/usr/bin/bash + + set -eo pipefail + source /usr/lib/ujust/ujust.sh + + OPTION={{ ACTION }} + PLACEHOLDER=/etc/udev/rules.d/80-bazzite-bluetooth-ds4-ds5-workaround.rules + WORKAROUND_NAME="bazzite-bluetooth-ds4-ds5-workaround" + + function is_enabled() { + if [[ ! -e $PLACEHOLDER ]]; then + return 0 + fi + return 1 + } + + function enable_workaround() { + # Check if the placeholder rule exists and if does, delete it + if is_enabled ; then + echo "$WORKAROUND_NAME is already enabled" + return + fi + sudo rm -v "$PLACEHOLDER" + echo "$WORKAROUND_NAME was enabled" + } + + function disable_workaround() { + # Set placeholder to disable workaround + if ! is_enabled; then + echo "$WORKAROUND_NAME is already disabled" + return + fi + sudo cp -v /usr/$PLACEHOLDER $PLACEHOLDER || { : | sudo tee $PLACEHOLDER; } + echo "$WORKAROUND_NAME was disabled" + } + + function show_help() { + echo "Usage: ujust enable-ds45-workaround