-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontroller-led
More file actions
executable file
·125 lines (115 loc) · 5.4 KB
/
Copy pathcontroller-led
File metadata and controls
executable file
·125 lines (115 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env bash
# controller-led — DualSense LED helper for the controller-manager daemon.
#
# controller-led lightbar-raw <hidrawN> <r> <g> <b> # lightbar via raw HID
# controller-led <ledname> <r> <g> <b> # lightbar via LED class
# controller-led player <inputN> <1-4> # white player-LED pattern
#
# The lightbar is driven with a RAW HID output report (lightbar-raw). Unlike a
# kernel LED-class write, a raw colour report also clears the DualSense firmware
# "light out" latch, so the colour reaches the hardware even after a raw-HID
# consumer (Steam Input) has latched the pad dark — the LED-class write updates
# the sysfs node but leaves such a latched lightbar physically off. The old
# LED-class path (<ledname>) is kept for reference/compat. The white player LEDs
# go through the LED class, which drives them reliably.
#
# Safety: only a *:rgb:indicator / *:white:player-N LED or a /dev/hidrawN node
# that belongs to a Sony (054C) HID device is ever written, so this can't be
# aimed at unrelated LEDs/devices (keyboard, NIC, …). Meant to be invoked
# through a tightly-scoped NOPASSWD sudoers rule.
set -euo pipefail
# HID_ID=BUS:0000VVVV:0000PPPP -> extract the 4-digit vendor; must be Sony.
sony_ok() {
local ue="$1"
[[ -r "$ue" ]] || { echo "cannot read $ue" >&2; return 1; }
local vid
vid="$(sed -n 's/^HID_ID=[0-9A-Fa-f]*:0*\([0-9A-Fa-f]\{4\}\):.*/\1/p' "$ue" | tr 'a-f' 'A-F')"
[[ "$vid" == "054C" ]] || { echo "vendor '$vid' not Sony — refused" >&2; return 1; }
}
if [[ "${1:-}" == "player" ]]; then
prefix="${2:-}"; n="${3:-}"
[[ "$prefix" =~ ^input[0-9]+$ ]] || { echo "bad prefix '$prefix'" >&2; exit 2; }
[[ "$n" =~ ^[1-4]$ ]] || { echo "bad player '$n'" >&2; exit 2; }
base="/sys/class/leds/$prefix:white:player-1"
[[ -d "$base" ]] || { echo "$base absent, nothing to do"; exit 0; }
sony_ok "$base/device/uevent" || exit 3
# PS5-authentic patterns: the lit-LED count equals the player number.
case "$n" in
1) pat=(0 0 1 0 0) ;;
2) pat=(0 1 0 1 0) ;;
3) pat=(1 0 1 0 1) ;;
4) pat=(1 1 0 1 1) ;;
esac
for i in 1 2 3 4 5; do
dir="/sys/class/leds/$prefix:white:player-$i"
[[ -d "$dir" ]] && echo "${pat[$((i - 1))]}" > "$dir/brightness"
done
echo "controller-led: $prefix player -> $n"
exit 0
fi
if [[ "${1:-}" == "lightbar-raw" ]]; then
node="${2:-}"; r="${3:-}"; g="${4:-}"; b="${5:-}"
# A plain /dev/hidrawN node (no path traversal, no other class).
[[ "$node" =~ ^/dev/hidraw[0-9]+$ ]] || { echo "bad hidraw '$node'" >&2; exit 2; }
# Node already gone (controller disconnected mid-write) — nothing to do.
[[ -e "$node" ]] || { echo "$node absent, nothing to do"; exit 0; }
for v in "$r" "$g" "$b"; do
{ [[ "$v" =~ ^[0-9]+$ ]] && (( v <= 255 )); } || { echo "bad channel '$v'" >&2; exit 2; }
done
ue="/sys/class/hidraw/$(basename "$node")/device/uevent"
sony_ok "$ue" || exit 3
# Bus from HID_ID=BUS:0000VVVV:0000PPPP — 0005 Bluetooth (report 0x31 + CRC),
# 0003 USB (report 0x02, no CRC): the two wire formats differ.
bus="$(sed -n 's/^HID_ID=\([0-9A-Fa-f]*\):.*/\1/p' "$ue")"
if python3 - "$node" "$bus" "$r" "$g" "$b" <<'PY'
import sys, zlib
node = sys.argv[1]
bt = int(sys.argv[2], 16) == 5 # BUS_BLUETOOTH
r, g, b = (int(x) & 0xff for x in sys.argv[3:6])
# DualSense output_report_common. valid_flag1 bit2 = LIGHTBAR_CONTROL sets the
# colour; valid_flag2 bit1 = LIGHTBAR_SETUP_CONTROL_ENABLE with lightbar_setup=0
# ("normal", not LIGHT_OUT=2) re-enables the bar. Both are needed: some DualSense
# firmwares light up on LIGHTBAR_CONTROL alone, others ignore a plain colour
# while latched and only obey it together with the setup flag (field-observed
# across two pads, 2026-07-13). Sending both clears the "light out" latch and
# sets the colour on either firmware.
common = bytearray(47)
common[1] = 0x04 # valid_flag1: LIGHTBAR_CONTROL
common[38] = 0x02 # valid_flag2: LIGHTBAR_SETUP_CONTROL_ENABLE
common[41] = 0x00 # lightbar_setup: 0 = normal/on
common[44], common[45], common[46] = r, g, b
if bt: # BT: 0x31, seq_tag, tag, CRC32 tail
rep = bytearray(78)
rep[0], rep[1], rep[2] = 0x31, 0x00, 0x10
rep[3:3 + 47] = common
crc = zlib.crc32(bytes([0xA2]) + bytes(rep[0:74])) & 0xffffffff # seed 0xA2
rep[74:78] = crc.to_bytes(4, "little")
else: # USB: 0x02, no CRC
rep = bytearray(48)
rep[0] = 0x02
rep[1:1 + 47] = common
with open(node, "wb") as f:
f.write(bytes(rep))
PY
then
echo "controller-led: lightbar-raw $node -> $r $g $b (bus $bus)"
exit 0
fi
echo "lightbar-raw: write to $node failed" >&2
exit 1
fi
led="${1:-}"; r="${2:-}"; g="${3:-}"; b="${4:-}"
# Name only (never a path) — refuse anything but a plain rgb-indicator led name.
[[ "$led" =~ ^[A-Za-z0-9_.:-]+:rgb:indicator$ ]] || { echo "bad led '$led'" >&2; exit 2; }
dir="/sys/class/leds/$led"
# Node already gone (e.g. controller disconnected mid-write) — nothing to do.
[[ -d "$dir" ]] || { echo "$dir absent, nothing to do"; exit 0; }
# Channels must be integers 0-255.
for v in "$r" "$g" "$b"; do
{ [[ "$v" =~ ^[0-9]+$ ]] && (( v <= 255 )); } || { echo "bad channel '$v'" >&2; exit 2; }
done
sony_ok "$dir/device/uevent" || exit 3
printf '%s' "$r $g $b" > "$dir/multi_intensity"
echo 255 > "$dir/brightness"
echo "controller-led: $led -> $r $g $b"
exit 0