feature: Improve device quirks support

This commit is contained in:
Tera << 8 2025-06-10 22:37:09 -04:00
parent 79c7846ecd
commit ad451ac014
Signed by: imterah
GPG key ID: 8FA7DD57BA6CEA37
6 changed files with 50 additions and 23 deletions

View file

@ -39,11 +39,11 @@ From there, you need to follow all the below steps if applicable to your current
1. First, you need to build the native version of raylib. To do that, go inside the `modules/raylib-python-cffi/raylib-c` directory. 1. First, you need to build the native version of raylib. To do that, go inside the `modules/raylib-python-cffi/raylib-c` directory.
2. Then, make the build directories and go into them: `mkdir -p build/out; cd build` 2. Then, make the build directories and go into them: `mkdir -p build/out; cd build`
3. Configure raylib: `cmake -DCUSTOMIZE_BUILD=ON -DSUPPORT_FILEFORMAT_JPG=ON -DSUPPORT_FILEFORMAT_FLAC=ON -DWITH_PIC=ON -DCMAKE_BUILD_TYPE=Release -DPLATFORM=DRM -DENABLE_WAYLAND_DRM_LEASING=ON -DSUPPORT_CLIPBOARD_IMAGE=ON -DCMAKE_INSTALL_PREFIX:PATH=$PWD/out ..` 3. Configure raylib: `cmake -DCUSTOMIZE_BUILD=ON -DSUPPORT_FILEFORMAT_JPG=ON -DSUPPORT_FILEFORMAT_FLAC=ON -DWITH_PIC=ON -DCMAKE_BUILD_TYPE=Release -DPLATFORM=DRM -DENABLE_WAYLAND_DRM_LEASING=ON -DSUPPORT_CLIPBOARD_IMAGE=ON -DBUILD_EXAMPLES=OFF -DSUPPORT_SSH_KEYBOARD_RPI=OFF -DDISABLE_EVDEV_INPUT=ON -DCMAKE_INSTALL_PREFIX:PATH=$PWD/out ..`
4. Finally, build and install raylib: `make install -j$(nproc)` 4. Finally, build and install raylib: `make install -j$(nproc)`
5. After that, you need to build the Python bindings. To do that, go to the `modules/raylib-python-cffi` directory. Assuming you did everything correctly, you should be able to go 2 directories back (`../..`) to get there. 5. After that, you need to build the Python bindings. To do that, go to the `modules/raylib-python-cffi` directory. Assuming you did everything correctly, you should be able to go 2 directories back (`../..`) to get there.
6. If you're on normal Linux and are not using Nix, do this command to build the package: `PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$PWD/raylib-c/build/out/lib64/pkgconfig/" ENABLE_WAYLAND_DRM_LEASING=YES RAYLIB_PLATFORM=DRM python3 setup.py bdist_wheel` 6. If you're on normal Linux and are not using Nix, do this command to build the package: `PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$PWD/raylib-c/build/out/lib64/pkgconfig/" ENABLE_WAYLAND_DRM_LEASING=YES RAYLIB_PLATFORM=DRM python3 setup.py bdist_wheel`
7. If you are using Nix/NixOS, do this command to build the package: `PKG_CONFIG_PATH_FOR_TARGET="$PKG_CONFIG_PATH_FOR_TARGET:$PWD/raylib-c/build/out/lib64/pkgconfig/" ENABLE_WAYLAND_DRM_LEASING=YES RAYLIB_PLATFORM=DRM python3 setup.py bdist_wheel; pip install dist/*.whl` 7. If you are using Nix/NixOS, do this command to build the package: `PKG_CONFIG_PATH_FOR_TARGET="$PKG_CONFIG_PATH_FOR_TARGET:$PWD/raylib-c/build/out/lib64/pkgconfig/" ENABLE_WAYLAND_DRM_LEASING=YES RAYLIB_PLATFORM=DRM python3 setup.py bdist_wheel`
8. Finally, install the package: `pip install dist/*.whl` 8. Finally, install the package: `pip install dist/*.whl`
### Building `PyEvdi` (Linux) ### Building `PyEvdi` (Linux)

View file

@ -4,9 +4,10 @@ from dataclasses import dataclass
import uuid import uuid
@dataclass @dataclass
class EvdiDisplaySpec: class UnrealXRDisplayMetadata:
edid: bytes edid: bytes
device_vendor: str device_vendor: str
device_quirks: dict[str, str | int]
max_width: int max_width: int
max_height: int max_height: int
max_refresh_rate: int max_refresh_rate: int

View file

@ -2,13 +2,13 @@ import subprocess
import os import os
from libunreal.supported_devices import supported_devices from libunreal.supported_devices import supported_devices
from libunreal.edid import EvdiDisplaySpec from libunreal.edid import UnrealXRDisplayMetadata
import pyedid import pyedid
def upload_new_device_edid(display_spec: EvdiDisplaySpec, edid: bytes | bytearray): def upload_new_device_edid(display_spec: UnrealXRDisplayMetadata, edid: bytes | bytearray):
pass pass
def fetch_xr_glass_edid(allow_unsupported_devices) -> EvdiDisplaySpec: def fetch_xr_glass_edid(allow_unsupported_devices) -> UnrealXRDisplayMetadata:
# Scan for all VGA devices and their IDs # Scan for all VGA devices and their IDs
pci_device_comand = subprocess.run(["lspci"], capture_output=True) pci_device_comand = subprocess.run(["lspci"], capture_output=True)
@ -72,11 +72,11 @@ def fetch_xr_glass_edid(allow_unsupported_devices) -> EvdiDisplaySpec:
max_refresh = int(manufacturer_supported_devices[edid.name]["max_refresh"]) max_refresh = int(manufacturer_supported_devices[edid.name]["max_refresh"])
return EvdiDisplaySpec(raw_edid_file, edid.manufacturer_pnp_id, max_width, max_height, max_refresh, card_device, monitor.replace(f"{card_device}-", "")) return UnrealXRDisplayMetadata(raw_edid_file, edid.manufacturer_pnp_id, manufacturer_supported_devices[edid.name], max_width, max_height, max_refresh, card_device, monitor.replace(f"{card_device}-", ""))
raise ValueError("Could not find supported device. Check if the device is plugged in. If it is plugged in and working correctly, check the README or open an issue.") raise ValueError("Could not find supported device. Check if the device is plugged in. If it is plugged in and working correctly, check the README or open an issue.")
def upload_edid_firmware(display: EvdiDisplaySpec, fw: bytes | bytearray): def upload_edid_firmware(display: UnrealXRDisplayMetadata, fw: bytes | bytearray):
if display.linux_drm_connector == "" or display.linux_drm_card == "": if display.linux_drm_connector == "" or display.linux_drm_card == "":
raise ValueError("Linux DRM connector and/or Linux DRM card not specified!") raise ValueError("Linux DRM connector and/or Linux DRM card not specified!")

View file

@ -5,7 +5,9 @@ supported_devices: dict[str, dict[str, dict[str, str | int]]] = {
"Air": { "Air": {
"max_width": 1920, "max_width": 1920,
"max_height": 1080, "max_height": 1080,
"max_refresh": 120 "max_refresh": 120,
"sensor_init_delay": 10,
"z_vector_disabled": True,
} }
} }
} }

11
main.py
View file

@ -1,9 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from sys import platform from sys import platform
import logging
import atexit import atexit
import json import json
import os import os
# Silence pyray init messages
raylib_python_logger = logging.getLogger("raylib")
raylib_python_logger.setLevel(logging.ERROR)
from platformdirs import user_data_dir, user_config_dir from platformdirs import user_data_dir, user_config_dir
from loguru import logger from loguru import logger
import PyEvdi import PyEvdi
@ -16,6 +21,7 @@ import libunreal
default_configuration: dict[str, str | int] = { default_configuration: dict[str, str | int] = {
"display_angle": 45, "display_angle": 45,
"display_pixel_spacing": 45,
"display_count": 3, "display_count": 3,
"allow_unsupported_devices": False, "allow_unsupported_devices": False,
"allow_unsupported_vendors": False, "allow_unsupported_vendors": False,
@ -106,7 +112,7 @@ def main():
# Get the display EDID # Get the display EDID
logger.info("Attempting to read display EDID file") logger.info("Attempting to read display EDID file")
edid: libunreal.EvdiDisplaySpec | None = None edid: libunreal.UnrealXRDisplayMetadata | None = None
if configuration["override_default_edid"] or configuration["allow_unsupported_vendors"]: if configuration["override_default_edid"] or configuration["allow_unsupported_vendors"]:
# We need to parse it to get the maximum width, height, and refresh rate for EVDI's calculations # We need to parse it to get the maximum width, height, and refresh rate for EVDI's calculations
@ -134,7 +140,7 @@ def main():
if max_refresh == 0: if max_refresh == 0:
raise ValueError("Could not determine maximum refresh rate from EDID file, and the refresh rate overrides aren't set!") raise ValueError("Could not determine maximum refresh rate from EDID file, and the refresh rate overrides aren't set!")
edid = libunreal.EvdiDisplaySpec(edid_file, parsed_edid_file.name if parsed_edid_file.name else "", max_width, max_height, max_refresh, "", "") edid = libunreal.UnrealXRDisplayMetadata(edid_file, parsed_edid_file.name if parsed_edid_file.name else "", {}, max_width, max_height, max_refresh, "", "")
else: else:
edid = libunreal.fetch_xr_glass_edid(configuration["allow_unsupported_devices"]) edid = libunreal.fetch_xr_glass_edid(configuration["allow_unsupported_devices"])
@ -176,6 +182,7 @@ def main():
logger.info("Initialized displays. Entering rendering loop") logger.info("Initialized displays. Entering rendering loop")
render_loop(edid, cards) render_loop(edid, cards)
if __name__ == "__main__": if __name__ == "__main__":
print("Welcome to UnrealXR!\n") print("Welcome to UnrealXR!\n")
main() main()

View file

@ -1,11 +1,11 @@
from time import sleep import time
import math import math
from loguru import logger from loguru import logger
import PyEvdi import PyEvdi
import pyray import pyray
from libunreal import EvdiDisplaySpec, MCUCallbackWrapper, start_mcu_event_listener from libunreal import UnrealXRDisplayMetadata, MCUCallbackWrapper, start_mcu_event_listener
previous_pitch = 0.0 previous_pitch = 0.0
previous_yaw = 0.0 previous_yaw = 0.0
@ -64,7 +64,7 @@ def text_message(message: str):
def stub_brightness_function(brightness: int): def stub_brightness_function(brightness: int):
pass pass
def render_loop(display_metadata: EvdiDisplaySpec, cards: list[PyEvdi.Card]): def render_loop(display_metadata: UnrealXRDisplayMetadata, cards: list[PyEvdi.Card]):
logger.info("Starting sensor event listener") logger.info("Starting sensor event listener")
mcu_callbacks = MCUCallbackWrapper(roll_callback, pitch_callback, yaw_callback, text_message, stub_brightness_function, stub_brightness_function) mcu_callbacks = MCUCallbackWrapper(roll_callback, pitch_callback, yaw_callback, text_message, stub_brightness_function, stub_brightness_function)
@ -73,7 +73,7 @@ def render_loop(display_metadata: EvdiDisplaySpec, cards: list[PyEvdi.Card]):
logger.info("Beginning sensor initialization. Awaiting first sensor update") logger.info("Beginning sensor initialization. Awaiting first sensor update")
while (not has_gotten_pitch_callback_before) or (not has_gotten_yaw_callback_before) or (not has_gotten_roll_callback_before): while (not has_gotten_pitch_callback_before) or (not has_gotten_yaw_callback_before) or (not has_gotten_roll_callback_before):
sleep(0.01) time.sleep(0.01)
logger.info("Initialized sensors") logger.info("Initialized sensors")
@ -89,17 +89,34 @@ def render_loop(display_metadata: EvdiDisplaySpec, cards: list[PyEvdi.Card]):
movement_vector = pyray.Vector3() movement_vector = pyray.Vector3()
look_vector = pyray.Vector3() look_vector = pyray.Vector3()
logger.error("QUIRK: Waiting 10 seconds before reading sensors due to sensor drift bugs") has_z_vector_disabled_quirk = False
sleep(10) has_sensor_init_delay_quirk = False
logger.error("Continuing...") sensor_init_start_time = time.time()
if "z_vector_disabled" in display_metadata.device_quirks:
logger.warning("QUIRK: The Z vector has been disabled for your specific device")
has_z_vector_disabled_quirk = True
if "sensor_init_delay" in display_metadata.device_quirks:
logger.warning(f"QUIRK: Waiting {str(display_metadata.device_quirks["sensor_init_delay"])} second(s) before reading sensors")
logger.warning("|| MOVEMENT WILL NOT BE OPERATIONAL DURING THIS TIME. ||")
sensor_init_start_time = time.time()
has_sensor_init_delay_quirk = True
while not pyray.window_should_close(): while not pyray.window_should_close():
look_vector.x = (current_yaw-previous_yaw)*6.5 if has_sensor_init_delay_quirk:
look_vector.y = (current_pitch-previous_pitch)*6.5 if time.time() - sensor_init_start_time >= int(display_metadata.device_quirks["sensor_init_delay"]):
# the Z vector is more trouble than its worth so it just doesn't get accounted for... # Unset the quirk state
#look_vector.z = (current_roll-previous_roll)*6.5 logger.info("Movement is now enabled.")
has_sensor_init_delay_quirk = False
else:
look_vector.x = (current_yaw-previous_yaw)*6.5
look_vector.y = (current_pitch-previous_pitch)*6.5
pyray.update_camera_pro(camera, movement_vector, look_vector, 0.0) if not has_z_vector_disabled_quirk:
look_vector.z = (current_roll-previous_roll)*6.5
pyray.update_camera_pro(camera, movement_vector, look_vector, 0.0)
pyray.begin_drawing() pyray.begin_drawing()
pyray.clear_background(pyray.BLACK) pyray.clear_background(pyray.BLACK)