
Using a PS3 Navigation Controller on Arch Linux
Everything here runs on Arch Linux with X11 and dwm:
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
Everything here runs on Arch Linux with X11 and dwm:
I needed to install Bluetooth utilities, Python, the Python input-device library, xdotool, evtest and triggerhappy.
Install the required packages
sudo pacman -S bluez bluez-utils python python-evdev xdotool evtest triggerhappy
Enable Bluetooth and start it immediately
sudo systemctl enable --now bluetooth
Power on the PS3 Navigation Controller and open the Bluetooth control utility:
Inside bluetoothctl, run the following commands:
Enable the Bluetooth adapter
power on
Enable the Bluetooth agent
agent on
Make the agent the default
default-agent
Search for nearby Bluetooth devices
scan on
Once the controller appears, use its Bluetooth address in place of CONTROLLER_MAC:
Pair with the controller
pair CONTROLLER_MAC
Trust the controller for future connections
trust CONTROLLER_MAC
Connect to the controller
connect CONTROLLER_MAC
Stop scanning for devices
scan off
Exit the Bluetooth shell
exit
Linux assigns input devices names such as /dev/input/event19. That number can change after reconnecting the controller or rebooting, so hardcoding it in scripts is not ideal.
Instead, create a udev rule that gives the controller a predictable symlink:
Open a new udev rule file
sudo vim /etc/udev/rules.d/99-navigation-controller.rules
Create a stable symlink for the PS3 Navigation Controller
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Navigation Controller", ATTRS{uniq}=="CONTROLLER_MAC", SYMLINK+="input/navigation-controller"
Reload udev rules
sudo udevadm control --reload-rules
Apply the updated rules to connected devices
sudo udevadm trigger
The analog stick reports its position through Linux input events. A Python script reads those events and uses xdotool to move the mouse pointer. I like keeping my custom scripts inside .config/scripts and store them inside my dotfiles repository.
#!/usr/bin/env python3
Read Linux input events
import evdev
Run xdotool commands
import subprocess
Wait before retrying unavailable devices
import time
Use the stable udev symlink
DEVICE = "/dev/input/navigation-controller"
Center position of the analog stick
CENTER_X = 131 CENTER_Y = 128
Ignore small movements around the center
DEADZONE = 15
Maximum number of pixels moved per update
MAX_SPEED = 8
def move_mouse(x, y):
Calculate displacement from the stick's center
dx = x - CENTER_X dy = y - CENTER_Y
Ignore horizontal movement inside the deadzone
if abs(dx) <= DEADZONE: dx = 0
Ignore vertical movement inside the deadzone
if abs(dy) <= DEADZONE: dy = 0
Do nothing when the stick is centered
if dx == 0 and dy == 0: return
Convert stick displacement into mouse movement
mx = int(dx / (128 - DEADZONE) * MAX_SPEED) my = int(dy / (128 - DEADZONE) * MAX_SPEED)
Ensure small horizontal movements still move the pointer
if mx == 0 and dx: mx = 1 if dx > 0 else -1
Ensure small vertical movements still move the pointer
if my == 0 and dy: my = 1 if dy > 0 else -1
Move the pointer relative to its current position
subprocess.run( ["xdotool", "mousemove_relative", "--", str(mx), str(my)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, )
Keep the script running across controller disconnections
while True:
Wait until the controller device becomes available
while True: try: dev = evdev.InputDevice(DEVICE) break except (FileNotFoundError, OSError): time.sleep(3)
Initialize the stick position
x = CENTER_X y = CENTER_Y
try:
Process incoming input events
for event in dev.read_loop():
Only handle analog-axis events
if event.type == evdev.ecodes.EV_ABS:
Update the horizontal stick position
if event.code == evdev.ecodes.ABS_X: x = event.value
Update the vertical stick position
elif event.code == evdev.ecodes.ABS_Y: y = event.value
Move the mouse using the latest stick position
move_mouse(x, y)
except (OSError, IOError):
Close the device after a disconnection
dev.close()
Wait briefly before attempting to reconnect
time.sleep(1)
Now that the analog stick will act as a mouse we can
First of all edit the triggerhappy service:
[Unit] After=bluetooth.target
[Service] ExecStart= ExecStart=/usr/sbin/thd --triggers /etc/triggerhappy/triggers.d/ --device /dev/input/navigation-controller Restart=always RestartSec=3
Make sure you paste the code above between:
Editing /etc/systemd/system/triggerhappy.service.d/override.conf
Anything between here and the comment below will become the contents of the drop-in file
Edits below this comment will be discarded
Together, these settings make the controller listener start after bluetooth is started, use the intended configuration and input device, and recover automatically if the daemon stops. Enable the service with:
Reload systemd unit definitions and overrides
sudo systemctl daemon-reload
Enable triggerhappy at boot and start it now
sudo systemctl enable --now triggerhappy
The analog stick handles mouse movement, but the physical buttons are handled by triggerhappy. I chose the X button to open Netflix.com in Firefox and make it full screen and the O button to open HBOMax.com.
#!/bin/sh
Select the active X display
export DISPLAY=:0
Tell X11 where the user's authentication file is
export XAUTHORITY=/home/sava/.Xauthority
Connect to the user's D-Bus session
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
Open Netflix in a new Firefox window
firefox --new-window https://www.netflix.com/ &
Wait for Firefox to begin opening the window
sleep 1
Toggle Firefox fullscreen
xdotool key F11
Of couse, the HBOMax one the same but the URL is changed when launching firefox.
If you want to bind a different button or simply figure out which button corresponds to which Linux input event, evtest is your friend.
Start monitoring controller input events
sudo evtest /dev/input/navigation-controller
Now press and release the buttons you want to identify. The output will show the event name, code, and value. For example here's what happens when you press the Up arrow on the DPAD:
Event: time 1791890030.090573, -------------- SYN_REPORT ------------ Event: time 1791890030.126617, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90005 Event: time 1791890030.126617, type 1 (EV_KEY), code 544 (BTN_DPAD_UP), value 0 Event: time 1791890030.126617, -------------- SYN_REPORT ------------ Event: time 1791890030.202585, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90005 Event: time 1791890030.202585, type 1 (EV_KEY), code 544 (BTN_DPAD_UP), value 1
Launch Netflix as the sava user when the X button is pressed
BTN_GAMEPAD 1 sudo -u sava /home/sava/.config/scripts/netflix.sh
Launch HBO Max as the sava user when the O button is pressed
BTN_B 1 sudo -u sava /home/sava/.config/scripts/hbomax.sh
Send Super+Q to dwm to close the focused window when the PS button is pressed
BTN_MODE 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key super+q
Send the media Play/Pause key when the L1 button is pressed
BTN_TL 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key XF86AudioPlay
Simulate a right mouse click when the L2 button is pressed
BTN_TL2 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool click 3
Simulate a left mouse click when the analog stick is pressed (L3)
BTN_THUMBL 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool click 1
Send the Left arrow key when the D-pad is pressed left
BTN_DPAD_LEFT 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key Left
Send the Right arrow key when the D-pad is pressed right
BTN_DPAD_RIGHT 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key Right
Send the Up arrow key when the D-pad is pressed up
BTN_DPAD_UP 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key Up
Send the Down arrow key when the D-pad is pressed down
BTN_DPAD_DOWN 1 DISPLAY=:0 XAUTHORITY=/home/sava/.Xauthority xdotool key Down
and start the navigation controller "mouse" script at login:
~/.config/scripts/ps3-mouse.py & exec dwm
And there you have it. If you chose to use a navigation controller yourself and use my triggerhappy config:
It's a fun way to repurpose old hardware and make an Arch Linux machine more comfortable to use from the couch. 🛋️ + 🎮 = 📽 + 🍿
If this tutorial saved you time, sanity, or a mild existential crisis... consider fueling future chaos with a small donation:
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。