|
@@ -109,11 +109,12 @@ def _first_usage_page(fd):
|
|
|
|
|
|
|
|
|
|
|
|
|
class HidDev:
|
|
class HidDev:
|
|
|
- def __init__(self, node, vid, pid, serial):
|
|
|
|
|
|
|
+ def __init__(self, node, vid, pid, serial, vendor_str = "Valve"):
|
|
|
self.node = node
|
|
self.node = node
|
|
|
self.vid = vid
|
|
self.vid = vid
|
|
|
self.pid = pid
|
|
self.pid = pid
|
|
|
self.serial = serial
|
|
self.serial = serial
|
|
|
|
|
+ self.vendor_str = vendor_str
|
|
|
self.fd = os.open(node, os.O_RDWR)
|
|
self.fd = os.open(node, os.O_RDWR)
|
|
|
self.usage_page = _first_usage_page(self.fd)
|
|
self.usage_page = _first_usage_page(self.fd)
|
|
|
|
|
|
|
@@ -179,8 +180,19 @@ def enumerate_devices():
|
|
|
# If these don't match, this is not a Puck slot, but some other random HID interface.
|
|
# If these don't match, this is not a Puck slot, but some other random HID interface.
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
|
|
+ # If we're here, this is a Puck. Let's detect and display if it's a Valve Puck or an OpenPuck.
|
|
|
|
|
+ # OpenPuck can be detected by the OpenPuck mouse wake interface.
|
|
|
|
|
+
|
|
|
|
|
+ devnode = iface_dir.parent
|
|
|
|
|
+ device_vendor = "Valve"
|
|
|
|
|
+
|
|
|
|
|
+ for subnode in glob.glob(str(devnode) + "/" + devnode.name + ":*/interface"):
|
|
|
|
|
+ txt = Path(subnode).read_text().strip()
|
|
|
|
|
+ if "OpenPuck" in txt:
|
|
|
|
|
+ device_vendor = "OpenPuck"
|
|
|
|
|
+
|
|
|
try:
|
|
try:
|
|
|
- dev = HidDev(node, vid, pid, serial)
|
|
|
|
|
|
|
+ dev = HidDev(node, vid, pid, serial, device_vendor)
|
|
|
except PermissionError:
|
|
except PermissionError:
|
|
|
print("permission denied on %s (run with sudo or install the udev rule)" % node, file=sys.stderr)
|
|
print("permission denied on %s (run with sudo or install the udev rule)" % node, file=sys.stderr)
|
|
|
continue
|
|
continue
|
|
@@ -214,7 +226,7 @@ def read_slots(puck_list):
|
|
|
for dev in sorted(puck_list, key=nodesort):
|
|
for dev in sorted(puck_list, key=nodesort):
|
|
|
if last_dev is not None and dev.serial != last_dev.serial:
|
|
if last_dev is not None and dev.serial != last_dev.serial:
|
|
|
# This is a new puck, append the old puck and all its slots to the list.
|
|
# This is a new puck, append the old puck and all its slots to the list.
|
|
|
- pucks.append({"serial": last_dev.serial, "slots": slots})
|
|
|
|
|
|
|
+ pucks.append({"serial": last_dev.serial, "slots": slots, "vendor": last_dev.vendor_str})
|
|
|
slots = []
|
|
slots = []
|
|
|
idx = 0
|
|
idx = 0
|
|
|
try:
|
|
try:
|
|
@@ -230,7 +242,7 @@ def read_slots(puck_list):
|
|
|
last_dev = dev
|
|
last_dev = dev
|
|
|
idx = idx + 1
|
|
idx = idx + 1
|
|
|
|
|
|
|
|
- pucks.append({"serial": dev.serial, "slots": slots})
|
|
|
|
|
|
|
+ pucks.append({"serial": dev.serial, "slots": slots, "vendor": dev.vendor_str})
|
|
|
return pucks
|
|
return pucks
|
|
|
|
|
|
|
|
|
|
|
|
@@ -387,21 +399,65 @@ def write_controller_slot(slot=0, puck_serial=None, pkey=None):
|
|
|
ctrl.set_feature(1, 0x95, bytes([0x52, 0xAF, 0x27, 0xA4]))
|
|
ctrl.set_feature(1, 0x95, bytes([0x52, 0xAF, 0x27, 0xA4]))
|
|
|
print("paired. controller rebooting into wireless — move the puck to your host.")
|
|
print("paired. controller rebooting into wireless — move the puck to your host.")
|
|
|
|
|
|
|
|
|
|
+def get_controller_pairings():
|
|
|
|
|
+ devs = enumerate_devices()
|
|
|
|
|
+ ctrls = sorted(devs["ctrl"], key=nodesort)
|
|
|
|
|
+
|
|
|
|
|
+ if not ctrls:
|
|
|
|
|
+ return []
|
|
|
|
|
+
|
|
|
|
|
+ controllers = []
|
|
|
|
|
+
|
|
|
|
|
+ for c in ctrls:
|
|
|
|
|
+ slots = []
|
|
|
|
|
+ i = 0
|
|
|
|
|
+
|
|
|
|
|
+ for bond_str in [b"esb/bond\x00", b"esb/bond_2\x00"]:
|
|
|
|
|
+ c.set_feature(1, 0xED, bond_str)
|
|
|
|
|
+ bond = c.get_feature(1)
|
|
|
|
|
+
|
|
|
|
|
+ if bond[0] == 0x01 and bond[1] == 0xed and bond[2] > 0:
|
|
|
|
|
+ bond_len = bond[2]
|
|
|
|
|
+ bond_key = bond[3:3+8]
|
|
|
|
|
+ bond_name = bond[11:11+(bond_len-8)]
|
|
|
|
|
+ slots.append({"idx": i, "used": True, "key": bond_key.hex(), "serial": bond_name.decode("latin1", "replace")})
|
|
|
|
|
+ else:
|
|
|
|
|
+ slots.append({"idx": i, "used": False})
|
|
|
|
|
+
|
|
|
|
|
+ i = i + 1
|
|
|
|
|
|
|
|
|
|
+ controllers.append({"serial": c.read_serial(1), "slots": slots})
|
|
|
|
|
+
|
|
|
|
|
+ return controllers
|
|
|
|
|
|
|
|
|
|
|
|
|
def cmd_list():
|
|
def cmd_list():
|
|
|
devs = enumerate_devices()
|
|
devs = enumerate_devices()
|
|
|
pucks = sorted(devs["puck"], key=nodesort)
|
|
pucks = sorted(devs["puck"], key=nodesort)
|
|
|
ctrls = sorted(devs["ctrl"], key=nodesort)
|
|
ctrls = sorted(devs["ctrl"], key=nodesort)
|
|
|
- print("controllers:", [(d.node, "%04X" % d.pid, d.read_serial(1)) for d in ctrls] or "(none)")
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if ctrls:
|
|
|
|
|
+ cs = get_controller_pairings()
|
|
|
|
|
+
|
|
|
|
|
+ for c in cs:
|
|
|
|
|
+ print(f"Controller: {c['serial']}")
|
|
|
|
|
+
|
|
|
|
|
+ for s in c['slots']:
|
|
|
|
|
+ if s['used']:
|
|
|
|
|
+ print(" slot %d: %s (key %s)" % (s["idx"], s["serial"], s["key"]))
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(" slot %d: Unused" % (s["idx"]))
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("No controller connected.")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
if pucks:
|
|
if pucks:
|
|
|
ps = read_slots(pucks)
|
|
ps = read_slots(pucks)
|
|
|
for puck in ps:
|
|
for puck in ps:
|
|
|
slots = puck['slots']
|
|
slots = puck['slots']
|
|
|
|
|
|
|
|
- print(f"Puck: {puck['serial']}")
|
|
|
|
|
|
|
+ print(f"Puck: {puck['serial']} ({puck['vendor']})")
|
|
|
|
|
|
|
|
for s in slots:
|
|
for s in slots:
|
|
|
if s["used"]:
|
|
if s["used"]:
|
|
@@ -409,7 +465,7 @@ def cmd_list():
|
|
|
else:
|
|
else:
|
|
|
print(" slot %d: Empty" % (s["idx"]))
|
|
print(" slot %d: Empty" % (s["idx"]))
|
|
|
else:
|
|
else:
|
|
|
- print("puck: (none)")
|
|
|
|
|
|
|
+ print("No Puck connected.")
|
|
|
|
|
|
|
|
|
|
|
|
|
def steam_key_check(key):
|
|
def steam_key_check(key):
|