diff options
author | Ryan Pavlik <ryan@ryanpavlik.com> | 2020-04-08 10:12:12 -0500 |
---|---|---|
committer | Ryan Pavlik <ryan@ryanpavlik.com> | 2020-04-08 10:12:12 -0500 |
commit | 86ef909e8c9d626db93e3188be9265a2e5844d0b (patch) | |
tree | f1682737dd18ffdbe19747367ab85d9d16a1d00c |
Import xr-hardware_0.2.1.orig.tar.bz2
[dgit import orig xr-hardware_0.2.1.orig.tar.bz2]
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .gitlab-ci.yml | 37 | ||||
-rw-r--r-- | 70-xrhardware-hwdb.rules | 25 | ||||
-rw-r--r-- | 70-xrhardware.hwdb | 115 | ||||
-rw-r--r-- | 70-xrhardware.rules | 137 | ||||
-rw-r--r-- | LICENSE.txt | 23 | ||||
-rw-r--r-- | Makefile | 48 | ||||
-rw-r--r-- | README.md | 145 | ||||
-rwxr-xr-x | make-hwdb-file.py | 16 | ||||
-rwxr-xr-x | make-hwdb-rules-file.py | 14 | ||||
-rwxr-xr-x | make-udev-rules.py | 19 | ||||
-rwxr-xr-x | test-db.py | 34 | ||||
-rw-r--r-- | xrhardware/__init__.py | 5 | ||||
-rw-r--r-- | xrhardware/db.py | 57 | ||||
-rw-r--r-- | xrhardware/device.py | 108 | ||||
-rw-r--r-- | xrhardware/generate.py | 48 |
16 files changed, 834 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb9e7e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +.vscode/ +.pc/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..0bb2f65 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,37 @@ +# Copyright 2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +variables: + UPSTREAM_REPO: monado/utilities/xr-hardware + + DEBIAN_TAG: 2020-01-22.1 + DEBIAN_VERSION: buster + DEBIAN_CONTAINER_IMAGE: "$CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG" + +include: + - project: 'wayland/ci-templates' + ref: b7030c2cd0d6ccc5f6d4f8299bafa4daa9240d71 + file: '/templates/debian.yml' + +stages: + - container_prep + - test + +debian:container_prep: + extends: .debian@container-ifnot-exists + stage: container_prep + variables: + GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image + # a list of packages to install + DEBIAN_DEBS: 'python3 python3-attr python3-flake8 git make' + + +test-db-and-generated: + stage: test + image: $DEBIAN_CONTAINER_IMAGE + script: + - make test + - make clean + - make all + # Fail the build if files are out of date. + - git diff --quiet diff --git a/70-xrhardware-hwdb.rules b/70-xrhardware-hwdb.rules new file mode 100644 index 0000000..0818410 --- /dev/null +++ b/70-xrhardware-hwdb.rules @@ -0,0 +1,25 @@ +# Do not edit this file - generated by make-hwdb-rules-file.py +# SPDX-License-Identifier: BSL-1.0 + +# 70-xrhardware-hwdb.rules + +# Must be located at a number smaller than 73 for uaccess to work right! + +# Skip if a remove +ACTION=="remove", GOTO="xrhardware_end" + + +# This rules file must be used with the corresponding hwdb file + + +# Exit if we didn't find one +ENV{ID_xrhardware}!="1", GOTO="xrhardware_end" + +# XR devices with serial ports aren't modems, modem-manager +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="usb", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Make friendly symlinks for XR USB-Serial devices. +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="tty", SYMLINK+="ttyUSB.$env{ID_xrhardware_USBSERIAL_NAME}" + +LABEL="xrhardware_end" + diff --git a/70-xrhardware.hwdb b/70-xrhardware.hwdb new file mode 100644 index 0000000..ac2b5b9 --- /dev/null +++ b/70-xrhardware.hwdb @@ -0,0 +1,115 @@ +# Do not edit this file - generated by make-hwdb-file.py +# SPDX-License-Identifier: BSL-1.0 + +# This hwdb file must be used with the corresponding rules file. +# Razer Hydra +usb:v1532p0300* + ID_xrhardware=1 + +# HTC Vive +usb:v0bb4p2c87* + ID_xrhardware=1 + +# HTC Vive Pro +usb:v0bb4p0309* + ID_xrhardware=1 + +# Valve Watchman Dongle +usb:v28dep2101* + ID_xrhardware=1 + +# Valve Index Controller +usb:v28dep2300* + ID_xrhardware=1 + +# Valve Receiver for Lighthouse - HTC Vive +usb:v28dep2000* + ID_xrhardware=1 + +# OSVR HDK +usb:v1532p0b00* + ID_xrhardware=1 + ID_xrhardware_USBSERIAL_NAME=OSVRHDK + +# OSVR HDK Camera +usb:v0bdap57e8* + ID_xrhardware=1 + +# Sensics zSight +usb:v16d0p0515* + ID_xrhardware=1 + ID_xrhardware_USBSERIAL_NAME=zSight + +# NOLO CV1 +usb:v0483p5750* + ID_xrhardware=1 + +# Oculus Rift (DK1) +usb:v2833p0001* + ID_xrhardware=1 + +# Oculus Rift (DK2) +usb:v2833p0021* + ID_xrhardware=1 + +# Oculus Rift (DK2) +usb:v2833p2021* + ID_xrhardware=1 + +# Oculus Rift (CV1) +usb:v2833p0031* + ID_xrhardware=1 + +# Samsung GearVR (Gen1) +usb:v04e8pa500* + ID_xrhardware=1 + +# 3Glasses-D3V1 +usb:v2b1cp0200* + ID_xrhardware=1 + +# 3Glasses-D3V2 +usb:v2b1cp0201* + ID_xrhardware=1 + +# 3Glasses-D3C +usb:v2b1cp0202* + ID_xrhardware=1 + +# 3Glasses-D2C +usb:v2b1cp0203* + ID_xrhardware=1 + +# 3Glasses-S1V5 +usb:v2b1cp0100* + ID_xrhardware=1 + +# 3Glasses-S1V8 +usb:v2b1cp0101* + ID_xrhardware=1 + +# Sony PlayStation VR +usb:v054cp09af* + ID_xrhardware=1 + +# Sony PlayStation Move Motion Controller CECH-ZCM1 +usb:v054cp03d5* +bluetooth:v054cp03d5* + ID_xrhardware=1 + +# Sony PlayStation Move Motion Controller CECH-ZCM2 +usb:v054cp0c5e* +bluetooth:v054cp0c5e* + ID_xrhardware=1 + +# LG 360 VR R-100 +usb:v1004p6374* + ID_xrhardware=1 + +# Microsoft HoloLens Sensors +usb:v045ep0659* + ID_xrhardware=1 + +# Samsung Odyssey+ sensors +usb:v04e8p7312* + ID_xrhardware=1 diff --git a/70-xrhardware.rules b/70-xrhardware.rules new file mode 100644 index 0000000..3422ca5 --- /dev/null +++ b/70-xrhardware.rules @@ -0,0 +1,137 @@ +# Do not edit this file - generated by make-udev-rules.py +# SPDX-License-Identifier: BSL-1.0 + +# 70-xrhardware.rules + +# Must be located at a number smaller than 73 for uaccess to work right! + +# Skip if a remove +ACTION=="remove", GOTO="xrhardware_end" + + +# BEGIN DEVICE LIST # +##################### +# Razer Hydra - USB +ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0300", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# HTC Vive - USB +ATTRS{idVendor}=="0bb4", ATTRS{idProduct}=="2c87", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# HTC Vive Pro - USB +ATTRS{idVendor}=="0bb4", ATTRS{idProduct}=="0309", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Valve Watchman Dongle - USB +ATTRS{idVendor}=="28de", ATTRS{idProduct}=="2101", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Valve Index Controller - USB +ATTRS{idVendor}=="28de", ATTRS{idProduct}=="2300", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Valve Receiver for Lighthouse - HTC Vive - USB +ATTRS{idVendor}=="28de", ATTRS{idProduct}=="2000", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# OSVR HDK - USB +ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0b00", TAG+="uaccess", ENV{ID_xrhardware}="1", ENV{ID_xrhardware_USBSERIAL_NAME}="OSVRHDK" + + +# OSVR HDK Camera - USB +ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="57e8", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Sensics zSight - USB +ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0515", TAG+="uaccess", ENV{ID_xrhardware}="1", ENV{ID_xrhardware_USBSERIAL_NAME}="zSight" + + +# NOLO CV1 - USB +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5750", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Oculus Rift (DK1) - USB +ATTRS{idVendor}=="2833", ATTRS{idProduct}=="0001", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Oculus Rift (DK2) - USB +ATTRS{idVendor}=="2833", ATTRS{idProduct}=="0021", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Oculus Rift (DK2) - USB +ATTRS{idVendor}=="2833", ATTRS{idProduct}=="2021", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Oculus Rift (CV1) - USB +ATTRS{idVendor}=="2833", ATTRS{idProduct}=="0031", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Samsung GearVR (Gen1) - USB +ATTRS{idVendor}=="04e8", ATTRS{idProduct}=="a500", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-D3V1 - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0200", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-D3V2 - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0201", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-D3C - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0202", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-D2C - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0203", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-S1V5 - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0100", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# 3Glasses-S1V8 - USB +ATTRS{idVendor}=="2b1c", ATTRS{idProduct}=="0101", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Sony PlayStation VR - USB +ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09af", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Sony PlayStation Move Motion Controller CECH-ZCM1 - Bluetooth, USB +ATTRS{idVendor}=="054c", ATTRS{idProduct}=="03d5", TAG+="uaccess", ENV{ID_xrhardware}="1" +KERNELS=="0005:054C:03D5.*", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Sony PlayStation Move Motion Controller CECH-ZCM2 - Bluetooth, USB +ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0c5e", TAG+="uaccess", ENV{ID_xrhardware}="1" +KERNELS=="0005:054C:0C5E.*", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# LG 360 VR R-100 - USB +ATTRS{idVendor}=="1004", ATTRS{idProduct}=="6374", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Microsoft HoloLens Sensors - USB +ATTRS{idVendor}=="045e", ATTRS{idProduct}=="0659", TAG+="uaccess", ENV{ID_xrhardware}="1" + + +# Samsung Odyssey+ sensors - USB +ATTRS{idVendor}=="04e8", ATTRS{idProduct}=="7312", TAG+="uaccess", ENV{ID_xrhardware}="1" + +################### +# END DEVICE LIST # + + +# Exit if we didn't find one +ENV{ID_xrhardware}!="1", GOTO="xrhardware_end" + +# XR devices with serial ports aren't modems, modem-manager +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="usb", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Make friendly symlinks for XR USB-Serial devices. +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="tty", SYMLINK+="ttyUSB.$env{ID_xrhardware_USBSERIAL_NAME}" + +LABEL="xrhardware_end" + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c725b9 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +#!/usr/bin/env make -f +# Copyright 2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> + +RULES_FILE := 70-xrhardware.rules +HWDB_FILE := 70-xrhardware.hwdb +HWDB_RULES_FILE := 70-xrhardware-hwdb.rules + +INSTALL ?= install + +PYTHON ?= python3 + +all: $(RULES_FILE) $(HWDB_FILE) $(HWDB_RULES_FILE) +.PHONY: all + +test: + $(PYTHON) test-db.py + $(PYTHON) -m flake8 . +.PHONY: test + +$(RULES_FILE): make-udev-rules.py xrhardware/db.py xrhardware/generate.py + $(PYTHON) $< > $@ +$(HWDB_FILE): make-hwdb-file.py xrhardware/db.py xrhardware/generate.py + $(PYTHON) $< > $@ +$(HWDB_RULES_FILE): make-hwdb-rules-file.py xrhardware/generate.py + $(PYTHON) $< > $@ + +clean: + -rm -f $(RULES_FILE) $(HWDB_FILE) $(HWDB_RULES_FILE) +.PHONY: clean + +# Currently does nothing, since the generated files get committed. +clean_package: +.PHONY: clean_package + +RULES_DIR ?= /etc/udev/rules.d + +install: $(RULES_FILE) + $(INSTALL) -d $(DESTDIR)$(RULES_DIR)/ + $(INSTALL) -m 644 -D $(RULES_FILE) $(DESTDIR)$(RULES_DIR)/ +.PHONY: install + +install_package: $(RULES_FILE) + $(INSTALL) -d $(DESTDIR)$(PREFIX)/lib/udev/rules.d + $(INSTALL) -m 644 -D $(RULES_FILE) $(DESTDIR)$(PREFIX)/lib/udev/rules.d/ +.PHONY: install_package + diff --git a/README.md b/README.md new file mode 100644 index 0000000..664554f --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# XR Hardware Rules + +A simple set of scripts to generate udev rules for user access to XR (VR and AR) +hardware devices. + +Packages are developed over at +<https://salsa.debian.org/rpavlik-guest/xr-hardware> rather than this repo, in +an attempt to do the Debian stuff correctly by the book. + +## "Build" requirements (to re-generate files) + +You'll need Python 3 and the `python3-attr` Debian package or equivalent +(`pip3 install attr`, etc.) + +## Quick start: "build" and use + +Once you have the deps installed, run: + +```sh +make + +sudo make install # to install udev rules to /etc/udev/rules.d +``` + +`DESTDIR` is obeyed, but packagers should really see the dedicated section for +them below. + +You'll need to at least re-plug your devices to get this to register for now. + +Note that you still might see something like this in a simple `ls -l` view: + +``` +crw-rw----+ 1 root root 246, 2 Jan 22 12:00 /dev/hidraw2 +``` + +This doesn't mean it failed to work: the `+` means there's an ACL attached in +addition to the normal Unix permissions. Running `getfacl` on that device node +will reveal: + +``` +# file: dev/hidraw2 +# owner: root +# group: root +user::rw- +user:YOUR_USERNAME_HERE:rw- +group::--- +mask::rw- +other::--- +``` + +and you should be able to use the device without sudo. If this is not your +experience, file an issue. + +## Makefile targets + +Right now we're generating the hwdb files as well as the shorter associated +rules file, but not doing anything with it in the makefile. + +If Python 3.x is not named `python3` you can override the `PYTHON` variable when +running `make` to tell it the correct name for your system. + +### `all` or one of the generated files + +This generates the file(s), if out of date. + +### `test` + +Runs some very minimal consistency checks on the database. +Also runs `flake8` so you'll need `python3-flake8` or something like that. + +### `clean` + +Removes the generated files. Since these are committed to the repo, this will +"dirty" the repo. + +### `install` + +Probably run with `sudo`. Will install by default to `/etc/udev/rules.d`: +override `RULES_DIR` if you want them somewhere else. Obeys `DESTDIR` but not +`PREFIX`. + +### For distro packages only + +If you're a distro packager, use slightly different targets: + +- Use `clean_package` instead of `clean`. (This is currently a no-op to avoid + dirtying the repo.) +- Use `install_package` instead of `install` (installs to `/lib/udev/rules.d`, + obeying both `DESTDIR` and `PREFIX`) + +## Contributing + +The device database is in `xrhardware/db.py` so that's the most likely file +to be modified. + +Plug your device in or connect it, and look at dmesg. + +### Bluetooth + +You'll see something like this at the end (example is a first-generation PS Move controller): + +``` +[541976.201331] input: Motion Controller as /devices/pci0000:00/0000:00:01.3/0000:02:00.0/usb1/1-12/1-12:1.0/bluetooth/hci0/hci0:1/0005:054C:03D5.001A/input/input62 +[541976.201426] sony 0005:054C:03D5.001A: input,hidraw6: BLUETOOTH HID v0.01 Joystick [Motion Controller] on fc:01:7c:a3:cf:06 +``` + +the HID "VID/PID" are in that long path, in the level that starts with `0005` +(`0005:054C:03D5.001A` in our example): + +- 0005 means Bluetooth +- 054C:03D5 are the vendor and product ID, respectively + +(They're also in the log "prefix" on the line starting with "sony" - the driver name.) + +If your device also can be used over USB, check to see if these values match +between the two. If they do, you can just put `bluetooth=True` (the `usb=True` +is implied by default) in the Device entry. Otherwise, be sure to add +`usb=False` as well as a second device entry for the USB identity of the device. + +### USB + +You'll see something like this at the end (example is still a first-generation PS Move controller): + +``` +[542674.705441] usb 3-1.2.1: USB disconnect, device number 77 +[544022.781910] usb 3-1.2.1: new full-speed USB device number 78 using xhci_hcd +[544022.888540] usb 3-1.2.1: New USB device found, idVendor=054c, idProduct=03d5, bcdDevice= 2.20 +[544022.888541] usb 3-1.2.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0 +[544022.888542] usb 3-1.2.1: Product: Motion Controller +[544022.888543] usb 3-1.2.1: Manufacturer: Sony Computer Entertainment +[544022.966925] input: Sony Computer Entertainment Motion Controller as /devices/pci0000:00/0000:00:07.1/0000:0b:00.3/usb3/3-1/3-1.2/3-1.2.1/3-1.2.1:1.0/0003:054C:03D5.001D/input/input65 +[544022.967017] sony 0003:054C:03D5.001D: input,hidraw5: USB HID v1.10 Joystick [Sony Computer Entertainment Motion Controller] on usb-0000:0b:00.3-1.2.1/input0 +``` + +Here, the VID and PID are nicely called out: + +- `idVendor=054c` +- `idProduct=03d5` + +Note that here, this device also can use Bluetooth, in which case it shows up +with the same VID/PID but on a different bus. + +## License + +Boost Software License 1.0 diff --git a/make-hwdb-file.py b/make-hwdb-file.py new file mode 100755 index 0000000..d9b67d0 --- /dev/null +++ b/make-hwdb-file.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""Make a hwdb file. Requires an associated .rules file.""" + +from xrhardware.db import get_devices +from xrhardware.generate import generate_file_contents + +if __name__ == "__main__": + all_entries = '\n\n'.join(d.make_hwdb_entry() for d in get_devices()) + contents = '\n'.join(( + "# This hwdb file must be used with the corresponding rules file.", + all_entries + )) + print(generate_file_contents(__file__, contents)) diff --git a/make-hwdb-rules-file.py b/make-hwdb-rules-file.py new file mode 100755 index 0000000..ed29526 --- /dev/null +++ b/make-hwdb-rules-file.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""Make a .rules file for use with a hwdb file.""" + +from xrhardware.generate import generate_rules_file_contents + +if __name__ == "__main__": + + print(generate_rules_file_contents( + __file__, + "70-xrhardware-hwdb.rules", + '# This rules file must be used with the corresponding hwdb file')) diff --git a/make-udev-rules.py b/make-udev-rules.py new file mode 100755 index 0000000..d2812a4 --- /dev/null +++ b/make-udev-rules.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""Make a standalone .rules file.""" +from xrhardware.db import get_devices +from xrhardware.generate import generate_rules_file_contents + +if __name__ == "__main__": + all_rules = '\n\n'.join(d.make_commented_rule() for d in get_devices()) + contents = '\n'.join(( + '# BEGIN DEVICE LIST #', + '#####################', + all_rules, + '###################', + '# END DEVICE LIST #', + )) + print(generate_rules_file_contents( + __file__, "70-xrhardware.rules", contents)) diff --git a/test-db.py b/test-db.py new file mode 100755 index 0000000..e8f4814 --- /dev/null +++ b/test-db.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""XR Hardware device database consistency test.""" +from xrhardware.db import get_devices + +if __name__ == "__main__": + print("Testing device database entries' consistency and uniqueness") + known = set() + import re + hexstring = re.compile(r"[0-9a-fA-F]{4}") + for dev in get_devices(): + + print("Checking entry for:", dev.extended_description) + + # Duplicate ID? + for dev_id in dev.yield_hwdb_identification(): + assert(dev_id not in known) + known.add(dev_id) + + # Must be at least one of these + assert(dev.usb or dev.bluetooth) + + # VID/PID must fit the expected format + if dev.pid: + assert(hexstring.match(dev.pid)) + # prefer lowercase for compatibility + assert(dev.pid == dev.pid.lower()) + if dev.vid: + assert(hexstring.match(dev.vid)) + # prefer lowercase for compatibility + assert(dev.vid == dev.vid.lower()) + print("Success!") diff --git a/xrhardware/__init__.py b/xrhardware/__init__.py new file mode 100644 index 0000000..5c5820f --- /dev/null +++ b/xrhardware/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 -i +# Copyright 2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""XR hardware database and associated utilities.""" diff --git a/xrhardware/db.py b/xrhardware/db.py new file mode 100644 index 0000000..4c6a396 --- /dev/null +++ b/xrhardware/db.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 -i +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""XR Hardware device database.""" + +from .device import Device + +# Please make sure VID/PID are lowercase. + + +def get_devices(): + """Return the entire device database in one really large tuple.""" + return ( + Device("Razer Hydra", "1532", "0300"), + Device("HTC Vive", "0bb4", "2c87"), + Device("HTC Vive Pro", "0bb4", "0309"), + Device("Valve Watchman Dongle", "28de", "2101"), + Device("Valve Index Controller", "28de", "2300"), + # Name might be wrong. + Device("Valve Receiver for Lighthouse - HTC Vive", "28de", "2000"), + # also PID 2220 (lighthouse fpga rx), 2300 (lhr vive pro?) + Device("OSVR HDK", "1532", "0b00", usb_serial_name="OSVRHDK"), + Device("OSVR HDK Camera", "0bda", "57e8"), + Device("Sensics zSight", "16d0", "0515", usb_serial_name="zSight"), + + Device("NOLO CV1", "0483", "5750"), + + Device("Oculus Rift (DK1)", "2833", "0001"), + Device("Oculus Rift (DK2)", "2833", "0021"), + Device("Oculus Rift (DK2)", "2833", "2021"), + Device("Oculus Rift (CV1)", "2833", "0031"), + Device("Samsung GearVR (Gen1)", "04e8", "a500"), + + Device("3Glasses-D3V1", "2b1c", "0200"), + Device("3Glasses-D3V2", "2b1c", "0201"), + Device("3Glasses-D3C", "2b1c", "0202"), + Device("3Glasses-D2C", "2b1c", "0203"), + Device("3Glasses-S1V5", "2b1c", "0100"), + Device("3Glasses-S1V8", "2b1c", "0101"), + + Device("Sony PlayStation VR", "054c", "09af"), + + Device("Sony PlayStation Move Motion Controller CECH-ZCM1", + "054c", "03d5", bluetooth=True, usb=True), + Device("Sony PlayStation Move Motion Controller CECH-ZCM2", + "054c", "0c5e", bluetooth=True, usb=True), + # TODO Duplicate of NOLO CV1? + # Device("Deepoon", "0483", "5750"), + + Device("LG 360 VR R-100", "1004", "6374"), + + Device("Microsoft HoloLens Sensors", "045e", "0659"), + Device("Samsung Odyssey+ sensors", "04e8", "7312"), + # Pretends to be a DK1... + # Device("VR-Tek WVR1", "2833", "0001"), + ) diff --git a/xrhardware/device.py b/xrhardware/device.py new file mode 100644 index 0000000..865ac64 --- /dev/null +++ b/xrhardware/device.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 -i +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""XR Hardware device database element type.""" + +import attr + + +@attr.s +class Device: + """An XR hardware device that we should permit access to.""" + + description = attr.ib() + vid = attr.ib(default=None) + pid = attr.ib(default=None) + usb_serial_name = attr.ib(default=None) + usb = attr.ib(default=True) + bluetooth = attr.ib(default=False) + + def get_properties_to_set(self): + """Generate the udev properties to set for this device.""" + yield ('ID_xrhardware', 1) + + if self.usb_serial_name: + yield ('ID_xrhardware_USBSERIAL_NAME', self.usb_serial_name) + + def yield_hwdb_identification(self): + """Compute identification for hwdb recognition of this device.""" + if not self.vid: + raise RuntimeError( + "Can't make a hwdb entry for something without a vid!") + # usb: and bluetooth: prefix come later + parts = [] + parts.append('v%s' % self.vid) + if self.pid: + parts.append('p%s' % self.pid) + parts.append('*') + identifier_suffix = ''.join(parts) + if self.usb: + yield 'usb:' + identifier_suffix + if self.bluetooth: + yield 'bluetooth:' + identifier_suffix + + def make_hwdb_entry(self): + """Return a hwdb entry for this device.""" + lines = ['# ' + self.description] + lines.extend(list(self.yield_hwdb_identification())) + for k, v in self.get_properties_to_set(): + lines.append(' %s=%s' % (k, v)) + return '\n'.join(lines) + + @property + def extended_description(self): + """Return the description augmented with the interface types.""" + interfaces = [] + if self.bluetooth: + interfaces.append("Bluetooth") + if self.usb: + interfaces.append("USB") + return "%s - %s" % (self.description, ", ".join(interfaces)) + + def make_commented_rule(self): + """Return a comment and udev rule for this device.""" + return '\n'.join(( + "# %s" % self.extended_description, + self.make_rule(), + "")) + + def yield_rule_condition_lists(self): + """Yield the udev rule conditions (as lists) to select this device.""" + if self.usb: + parts = [] + if self.vid: + parts.append('ATTRS{idVendor}=="%s"' % self.vid) + if self.pid: + parts.append('ATTRS{idProduct}=="%s"' % self.pid) + yield parts + + if self.bluetooth: + yield ['KERNELS=="0005:%s:%s.*"' % + (self.vid.upper(), self.pid.upper())] + # Bluetooth devices don't get idVendor and idProduct, + # but they do get this which is pretty similar. + # However, these fail to trigger the rule properly for the PS Move + # at least. + # + # parts = ['ATTRS{id/bustype}=="0005"'] + # if self.vid: + # parts.append('ATTRS{id/vendor}=="%s"' % self.vid) + # if self.pid: + # parts.append('ATTRS{id/product}=="%s"' % self.pid) + # yield parts + + def yield_rule_conditions(self): + """Yield the udev rule conditions to select this device.""" + for conditions in self.yield_rule_condition_lists(): + yield ", ".join(conditions) + + def make_rule(self): + """Return a udev rule for this device.""" + rules = [] + for condition in self.yield_rule_conditions(): + parts = [condition, 'TAG+="uaccess"'] + parts.extend('ENV{%s}="%s"' % (k, v) + for k, v in self.get_properties_to_set()) + rules.append(', '.join(parts)) + return "\n".join(rules) diff --git a/xrhardware/generate.py b/xrhardware/generate.py new file mode 100644 index 0000000..ca29ea0 --- /dev/null +++ b/xrhardware/generate.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Collabora, Ltd +# SPDX-License-Identifier: BSL-1.0 +# Author: Ryan Pavlik <ryan.pavlik@collabora.com> +"""XR Hardware generation helper code.""" + +# pylint: disable=E501 +_SHARED_PREFIX = """# Do not edit this file - generated by {script} +# SPDX-License-Identifier: BSL-1.0 +""" + + +_RULES_TEMPLATE_PREFIX = """ +# Must be located at a number smaller than 73 for uaccess to work right! + +# Skip if a remove +ACTION=="remove", GOTO="xrhardware_end" + +""" + +# Must be before 73 to have uaccess do what it should! +_RULES_TEMPLATE_SUFFIX = """ + +# Exit if we didn't find one +ENV{ID_xrhardware}!="1", GOTO="xrhardware_end" + +# XR devices with serial ports aren't modems, modem-manager +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="usb", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Make friendly symlinks for XR USB-Serial devices. +ENV{ID_xrhardware_USBSERIAL_NAME}!="", SUBSYSTEM=="tty", SYMLINK+="ttyUSB.$env{ID_xrhardware_USBSERIAL_NAME}" + +LABEL="xrhardware_end" +""" # noqa: E501 # it's too long because that's what the template is. + + +def generate_file_contents(script, body): + """Return generated file contents for the given script file and body.""" + from pathlib import Path + script = str(Path(script).name) + shared_prefix = _SHARED_PREFIX.format(script=script) + return '\n'.join((shared_prefix, body)) + + +def generate_rules_file_contents(script, fn, body): + """Return the body surrounded by the rules template.""" + parts = ['# ' + fn, _RULES_TEMPLATE_PREFIX, body, _RULES_TEMPLATE_SUFFIX] + return generate_file_contents(script, "\n".join(parts)) |