summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Pavlik <ryan@ryanpavlik.com>2020-04-08 10:12:12 -0500
committerRyan Pavlik <ryan@ryanpavlik.com>2020-04-08 10:12:12 -0500
commit86ef909e8c9d626db93e3188be9265a2e5844d0b (patch)
treef1682737dd18ffdbe19747367ab85d9d16a1d00c
Import xr-hardware_0.2.1.orig.tar.bz2
[dgit import orig xr-hardware_0.2.1.orig.tar.bz2]
-rw-r--r--.gitignore3
-rw-r--r--.gitlab-ci.yml37
-rw-r--r--70-xrhardware-hwdb.rules25
-rw-r--r--70-xrhardware.hwdb115
-rw-r--r--70-xrhardware.rules137
-rw-r--r--LICENSE.txt23
-rw-r--r--Makefile48
-rw-r--r--README.md145
-rwxr-xr-xmake-hwdb-file.py16
-rwxr-xr-xmake-hwdb-rules-file.py14
-rwxr-xr-xmake-udev-rules.py19
-rwxr-xr-xtest-db.py34
-rw-r--r--xrhardware/__init__.py5
-rw-r--r--xrhardware/db.py57
-rw-r--r--xrhardware/device.py108
-rw-r--r--xrhardware/generate.py48
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))