chore: Remove legacy Python codebase
This commit is contained in:
parent
919d247934
commit
643fd3c61c
14 changed files with 24 additions and 1058 deletions
291
.gitignore
vendored
291
.gitignore
vendored
|
@ -1,281 +1,30 @@
|
||||||
# ---> Python
|
# ---> Go
|
||||||
# Byte-compiled / optimized / DLL files
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
__pycache__/
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
*.py[cod]
|
#
|
||||||
*$py.class
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
# C extensions
|
*.exe~
|
||||||
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
# Distribution / packaging
|
# Test binary, built with `go test -c`
|
||||||
.Python
|
*.test
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
# Usually these files are written by a python script from a template
|
*.out
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
# Dependency directories (remove the comment below to include it)
|
||||||
pip-log.txt
|
# vendor/
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Go workspace file
|
||||||
htmlcov/
|
go.work
|
||||||
.tox/
|
go.work.sum
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
# env file
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
#poetry.toml
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# Abstra
|
|
||||||
# Abstra is an AI-powered process automation framework.
|
|
||||||
# Ignore directories containing user credentials, local state, and settings.
|
|
||||||
# Learn more at https://abstra.io/docs
|
|
||||||
.abstra/
|
|
||||||
|
|
||||||
# Visual Studio Code
|
|
||||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
||||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
||||||
# you could uncomment the following to ignore the entire vscode folder
|
|
||||||
# .vscode/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
# ---> C
|
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
|
||||||
*.ilk
|
|
||||||
*.map
|
|
||||||
*.exp
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
*.lib
|
|
||||||
*.a
|
|
||||||
*.la
|
|
||||||
*.lo
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
# Debug files
|
|
||||||
*.dSYM/
|
|
||||||
*.su
|
|
||||||
*.idb
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
|
||||||
*.mod*
|
|
||||||
*.cmd
|
|
||||||
.tmp_versions/
|
|
||||||
modules.order
|
|
||||||
Module.symvers
|
|
||||||
Mkfile.old
|
|
||||||
dkms.conf
|
|
||||||
|
|
||||||
# ---> C++
|
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
|
|
||||||
# ---> UnrealXR
|
# ---> UnrealXR
|
||||||
|
# development dirs
|
||||||
data
|
data
|
||||||
drivers
|
|
||||||
|
|
9
.gitmodules
vendored
9
.gitmodules
vendored
|
@ -1,9 +0,0 @@
|
||||||
[submodule "modules/raylib-python-cffi"]
|
|
||||||
path = modules/raylib-python-cffi
|
|
||||||
url = https://git.terah.dev/UnrealXR/raylib-python-cffi.git
|
|
||||||
[submodule "modules/evdi"]
|
|
||||||
path = modules/evdi
|
|
||||||
url = https://github.com/DisplayLink/evdi.git
|
|
||||||
[submodule "modules/nreal-driver"]
|
|
||||||
path = modules/nreal-driver
|
|
||||||
url = https://git.terah.dev/UnrealXR/nrealAirLinuxDriver.git
|
|
|
@ -1,10 +0,0 @@
|
||||||
from libunreal.supported_devices import *
|
|
||||||
from libunreal.mcu_driver import *
|
|
||||||
from libunreal.edid import *
|
|
||||||
|
|
||||||
from sys import platform
|
|
||||||
|
|
||||||
if platform == "linux" or platform == "linux2":
|
|
||||||
from libunreal.linux import *
|
|
||||||
else:
|
|
||||||
raise OSError("Unsupported operating system")
|
|
|
@ -1,86 +0,0 @@
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UnrealXRDisplayMetadata:
|
|
||||||
edid: bytes
|
|
||||||
device_vendor: str
|
|
||||||
device_quirks: dict[str, str | int]
|
|
||||||
max_width: int
|
|
||||||
max_height: int
|
|
||||||
max_refresh_rate: int
|
|
||||||
linux_drm_card: str
|
|
||||||
linux_drm_connector: str
|
|
||||||
|
|
||||||
calculated_msft_payload_size = 22+4
|
|
||||||
|
|
||||||
def calculate_checksum(block):
|
|
||||||
checksum = (-sum(block[:-1])) & 0xFF
|
|
||||||
return checksum
|
|
||||||
|
|
||||||
def patch_edid_to_be_specialized(edid_data: bytes | bytearray) -> bytes | bytearray:
|
|
||||||
mutable_edid = bytearray(edid_data)
|
|
||||||
is_enhanced_mode = len(edid_data) > 128
|
|
||||||
|
|
||||||
found_extension_base = 0
|
|
||||||
extension_base_existed = False
|
|
||||||
|
|
||||||
if is_enhanced_mode:
|
|
||||||
for i in range(128, len(edid_data), 128):
|
|
||||||
if edid_data[i] == 0x02:
|
|
||||||
logger.warning("Detected existing ANSI CTA data section. Patching in place but untested! Please report any issues you discover")
|
|
||||||
|
|
||||||
if edid_data[i+1] != 0x03:
|
|
||||||
logger.warning("Incompatible version detected for ANSI CTA data section in EDID")
|
|
||||||
|
|
||||||
found_extension_base = i
|
|
||||||
extension_base_existed = True
|
|
||||||
|
|
||||||
if found_extension_base == 0:
|
|
||||||
found_extension_base = len(edid_data)
|
|
||||||
mutable_edid.extend([0]*128)
|
|
||||||
else:
|
|
||||||
mutable_edid.extend([0]*128)
|
|
||||||
found_extension_base = 128
|
|
||||||
|
|
||||||
generated_uuid = uuid.uuid4()
|
|
||||||
|
|
||||||
mutable_edid[found_extension_base] = 0x02
|
|
||||||
mutable_edid[found_extension_base+1] = 0x03
|
|
||||||
|
|
||||||
if extension_base_existed and mutable_edid[found_extension_base+2] != calculated_msft_payload_size and mutable_edid[found_extension_base+2] != 0:
|
|
||||||
# We try our best to move our data into place
|
|
||||||
current_base = mutable_edid[found_extension_base+2]
|
|
||||||
mutable_edid[found_extension_base+2] = calculated_msft_payload_size+1
|
|
||||||
|
|
||||||
mutable_edid[found_extension_base+4:found_extension_base+current_base-1] = [0]*(current_base-1)
|
|
||||||
mutable_edid[found_extension_base+calculated_msft_payload_size:found_extension_base+127] = mutable_edid[found_extension_base+current_base:found_extension_base+127]
|
|
||||||
else:
|
|
||||||
mutable_edid[found_extension_base+2] = calculated_msft_payload_size
|
|
||||||
|
|
||||||
if not extension_base_existed:
|
|
||||||
mutable_edid[126] += 1
|
|
||||||
mutable_edid[127] = calculate_checksum(mutable_edid[:128])
|
|
||||||
|
|
||||||
mutable_edid[found_extension_base+3] = 0 # We don't know any of these properties
|
|
||||||
|
|
||||||
# Implemented using https://learn.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension
|
|
||||||
# VST & Length
|
|
||||||
mutable_edid[found_extension_base+4] = 0x3 << 5 | 0x15 # 0x3: vendor specific tag; 0x15: length
|
|
||||||
# Assigned IEEE OUI
|
|
||||||
mutable_edid[found_extension_base+5] = 0x5C
|
|
||||||
mutable_edid[found_extension_base+6] = 0x12
|
|
||||||
mutable_edid[found_extension_base+7] = 0xCA
|
|
||||||
# Actual data
|
|
||||||
mutable_edid[found_extension_base+8] = 0x2 # Using version 0x2 for better compatibility
|
|
||||||
mutable_edid[found_extension_base+9] = 0x7 # Using VR tag for better compatibility even though it probably doesn't matter
|
|
||||||
mutable_edid[found_extension_base+10:found_extension_base+10+16] = generated_uuid.bytes
|
|
||||||
|
|
||||||
mutable_edid[found_extension_base+127] = calculate_checksum(mutable_edid[found_extension_base:found_extension_base+127])
|
|
||||||
|
|
||||||
if isinstance(edid_data, bytes):
|
|
||||||
return bytes(mutable_edid)
|
|
||||||
else:
|
|
||||||
return mutable_edid
|
|
|
@ -1,84 +0,0 @@
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
|
|
||||||
from libunreal.supported_devices import supported_devices
|
|
||||||
from libunreal.edid import UnrealXRDisplayMetadata
|
|
||||||
import pyedid
|
|
||||||
|
|
||||||
def upload_new_device_edid(display_spec: UnrealXRDisplayMetadata, edid: bytes | bytearray):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def fetch_xr_glass_edid(allow_unsupported_devices) -> UnrealXRDisplayMetadata:
|
|
||||||
# Scan for all VGA devices and their IDs
|
|
||||||
pci_device_comand = subprocess.run(["lspci"], capture_output=True)
|
|
||||||
|
|
||||||
if pci_device_comand.returncode != 0:
|
|
||||||
raise OSError("Failed to scan PCI devices")
|
|
||||||
|
|
||||||
pci_devices: list[str] = pci_device_comand.stdout.decode("utf-8").split("\n")
|
|
||||||
pci_devices = pci_devices[:-1]
|
|
||||||
|
|
||||||
vga_devices: list[str] = []
|
|
||||||
|
|
||||||
for pci_device in pci_devices:
|
|
||||||
if "VGA compatible controller:" in pci_device:
|
|
||||||
vga_devices.append(pci_device[:pci_device.index(" ")])
|
|
||||||
|
|
||||||
# Attempt to find any XR glasses
|
|
||||||
for vga_device in vga_devices:
|
|
||||||
card_devices = list(os.listdir(f"/sys/devices/pci0000:00/0000:{vga_device}/drm/"))
|
|
||||||
|
|
||||||
for card_device in card_devices:
|
|
||||||
if "card" not in card_device:
|
|
||||||
continue
|
|
||||||
|
|
||||||
monitors = list(os.listdir(f"/sys/devices/pci0000:00/0000:{vga_device}/drm/{card_device}/"))
|
|
||||||
|
|
||||||
for monitor in monitors:
|
|
||||||
if card_device not in monitor:
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(f"/sys/devices/pci0000:00/0000:{vga_device}/drm/{card_device}/{monitor}/edid", "rb") as edid:
|
|
||||||
raw_edid_file = edid.read()
|
|
||||||
|
|
||||||
if len(raw_edid_file) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
edid = pyedid.parse_edid(raw_edid_file)
|
|
||||||
|
|
||||||
for manufacturer, manufacturer_supported_devices in supported_devices.items():
|
|
||||||
if edid.manufacturer_pnp_id == manufacturer and (edid.name in manufacturer_supported_devices or allow_unsupported_devices):
|
|
||||||
max_width = 0
|
|
||||||
max_height = 0
|
|
||||||
max_refresh = 0
|
|
||||||
|
|
||||||
for resolution in edid.resolutions:
|
|
||||||
if resolution[0] > max_width and resolution[1] > max_height:
|
|
||||||
max_width = resolution[0]
|
|
||||||
max_height = resolution[1]
|
|
||||||
|
|
||||||
max_refresh = max(max_refresh, int(resolution[2]))
|
|
||||||
|
|
||||||
if max_width == 0 or max_height == 0:
|
|
||||||
if "max_width" not in manufacturer_supported_devices[edid.name] or "max_height" not in manufacturer_supported_devices[edid.name]:
|
|
||||||
raise ValueError("Couldn't determine maximum width and height, and the maximum width and height isn't defined in the device quirks section")
|
|
||||||
|
|
||||||
max_width = int(manufacturer_supported_devices[edid.name]["max_width"])
|
|
||||||
max_height = int(manufacturer_supported_devices[edid.name]["max_height"])
|
|
||||||
|
|
||||||
if max_refresh == 0:
|
|
||||||
if "max_refresh" not in manufacturer_supported_devices[edid.name]:
|
|
||||||
raise ValueError("Couldn't determine maximum refresh rate, and the maximum refresh rate isn't defined in the device quirks section")
|
|
||||||
|
|
||||||
max_refresh = int(manufacturer_supported_devices[edid.name]["max_refresh"])
|
|
||||||
|
|
||||||
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.")
|
|
||||||
|
|
||||||
def upload_edid_firmware(display: UnrealXRDisplayMetadata, fw: bytes | bytearray):
|
|
||||||
if display.linux_drm_connector == "" or display.linux_drm_card == "":
|
|
||||||
raise ValueError("Linux DRM connector and/or Linux DRM card not specified!")
|
|
||||||
|
|
||||||
with open(f"/sys/kernel/debug/dri/{display.linux_drm_card.replace("card", "")}/{display.linux_drm_connector}/edid_override", "wb") as kernel_edid:
|
|
||||||
kernel_edid.write(fw)
|
|
|
@ -1,145 +0,0 @@
|
||||||
from tempfile import TemporaryDirectory
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from os import path, environ
|
|
||||||
from typing import Callable
|
|
||||||
from loguru import logger
|
|
||||||
from shutil import which
|
|
||||||
from time import sleep
|
|
||||||
from enum import Enum
|
|
||||||
import subprocess
|
|
||||||
import threading
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import signal
|
|
||||||
import atexit
|
|
||||||
import os
|
|
||||||
|
|
||||||
class MCUCommandTypes(Enum):
|
|
||||||
ROLL = 0
|
|
||||||
PITCH = 1
|
|
||||||
YAW = 2
|
|
||||||
GENERIC_MESSAGE = 3
|
|
||||||
BRIGHTNESS_UP = 4
|
|
||||||
BRIGHTNESS_DOWN = 5
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MCUCallbackWrapper:
|
|
||||||
OnRollUpdate: Callable[[float], None]
|
|
||||||
OnPitchUpdate: Callable[[float], None]
|
|
||||||
OnYawUpdate: Callable[[float], None]
|
|
||||||
OnTextMessageRecieved: Callable[[str], None]
|
|
||||||
OnBrightnessUp: Callable[[int], None]
|
|
||||||
OnBrightnessDown: Callable[[int], None]
|
|
||||||
|
|
||||||
vendor_to_driver_table: dict[str, str] = {
|
|
||||||
"MRG": "xreal_ar_driver",
|
|
||||||
}
|
|
||||||
|
|
||||||
def find_executable_path_from_driver_name(driver_name) -> str:
|
|
||||||
# First try the normal driver path
|
|
||||||
try:
|
|
||||||
driver_path = path.join("drivers", driver_name)
|
|
||||||
|
|
||||||
file = open(driver_path)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
return driver_path
|
|
||||||
except OSError:
|
|
||||||
# Then search the system path
|
|
||||||
driver_path = which(driver_name)
|
|
||||||
|
|
||||||
if driver_path == "":
|
|
||||||
raise OSError("Could not find driver executable in driver directory or in PATH")
|
|
||||||
|
|
||||||
return driver_path
|
|
||||||
|
|
||||||
def start_mcu_event_listener(driver_vendor: str, events: MCUCallbackWrapper):
|
|
||||||
driver_executable = find_executable_path_from_driver_name(vendor_to_driver_table[driver_vendor])
|
|
||||||
|
|
||||||
created_temp_dir = TemporaryDirectory()
|
|
||||||
sock_path = path.join(created_temp_dir.name, "mcu_socket")
|
|
||||||
|
|
||||||
def on_socket_event(sock: socket.socket):
|
|
||||||
while True:
|
|
||||||
message_type = sock.recv(1)
|
|
||||||
|
|
||||||
if message_type[0] == MCUCommandTypes.ROLL.value:
|
|
||||||
roll_data = sock.recv(4)
|
|
||||||
roll_value = struct.unpack("!f", roll_data)[0]
|
|
||||||
|
|
||||||
if not isinstance(roll_value, float):
|
|
||||||
logger.warning("Expected roll value to be a float but got other type instead")
|
|
||||||
continue
|
|
||||||
|
|
||||||
events.OnRollUpdate(roll_value)
|
|
||||||
elif message_type[0] == MCUCommandTypes.PITCH.value:
|
|
||||||
pitch_data = sock.recv(4)
|
|
||||||
pitch_value = struct.unpack("!f", pitch_data)[0]
|
|
||||||
|
|
||||||
if not isinstance(pitch_value, float):
|
|
||||||
logger.warning("Expected pitch value to be a float but got other type instead")
|
|
||||||
continue
|
|
||||||
|
|
||||||
events.OnPitchUpdate(pitch_value)
|
|
||||||
elif message_type[0] == MCUCommandTypes.YAW.value:
|
|
||||||
yaw_data = sock.recv(4)
|
|
||||||
yaw_value = struct.unpack("!f", yaw_data)[0]
|
|
||||||
|
|
||||||
if not isinstance(yaw_value, float):
|
|
||||||
logger.warning("Expected yaw value to be a float but got other type instead")
|
|
||||||
continue
|
|
||||||
|
|
||||||
events.OnYawUpdate(yaw_value)
|
|
||||||
elif message_type[0] == MCUCommandTypes.GENERIC_MESSAGE.value:
|
|
||||||
length_bytes = sock.recv(4)
|
|
||||||
|
|
||||||
msg_len = struct.unpack("!I", length_bytes)[0]
|
|
||||||
msg_bytes = sock.recv(msg_len)
|
|
||||||
|
|
||||||
msg = msg_bytes.decode("utf-8", errors="replace")
|
|
||||||
events.OnTextMessageRecieved(msg)
|
|
||||||
elif message_type[0] == MCUCommandTypes.BRIGHTNESS_UP.value:
|
|
||||||
brightness_bytes = sock.recv(1)
|
|
||||||
events.OnBrightnessUp(int.from_bytes(brightness_bytes, byteorder='big'))
|
|
||||||
elif message_type[0] == MCUCommandTypes.BRIGHTNESS_DOWN.value:
|
|
||||||
brightness_bytes = sock.recv(1)
|
|
||||||
events.OnBrightnessDown(int.from_bytes(brightness_bytes, byteorder='big'))
|
|
||||||
else:
|
|
||||||
logger.warning(f"Unknown message type recieved: {str(message_type[0])}")
|
|
||||||
|
|
||||||
def start_socket_handout():
|
|
||||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
||||||
server.bind(sock_path)
|
|
||||||
server.listen(1)
|
|
||||||
|
|
||||||
sock: socket.socket | None = None
|
|
||||||
|
|
||||||
while True:
|
|
||||||
sock, _ = server.accept()
|
|
||||||
|
|
||||||
threaded_connection_processing = threading.Thread(target=on_socket_event, args=(sock,), daemon=True)
|
|
||||||
threaded_connection_processing.start()
|
|
||||||
|
|
||||||
created_temp_dir.cleanup()
|
|
||||||
|
|
||||||
def spawn_child_process():
|
|
||||||
custom_env = environ.copy()
|
|
||||||
custom_env["UNREALXR_NREAL_DRIVER_SOCK"] = sock_path
|
|
||||||
|
|
||||||
process = subprocess.Popen([driver_executable], env=custom_env)
|
|
||||||
|
|
||||||
def kill_child():
|
|
||||||
if process.pid is None:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
os.kill(process.pid, signal.SIGTERM)
|
|
||||||
|
|
||||||
atexit.register(kill_child)
|
|
||||||
|
|
||||||
threaded_socket_handout = threading.Thread(target=start_socket_handout, daemon=True)
|
|
||||||
threaded_socket_handout.start()
|
|
||||||
|
|
||||||
sleep(0.01) # Give the socket server time to initialize
|
|
||||||
|
|
||||||
threaded_child_process = threading.Thread(target=spawn_child_process, daemon=True)
|
|
||||||
threaded_child_process.start()
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Sourced from "https://uefi.org/uefi-pnp-export"
|
|
||||||
supported_devices: dict[str, dict[str, dict[str, str | int]]] = {
|
|
||||||
"MRG": {
|
|
||||||
# Quirks section
|
|
||||||
"Air": {
|
|
||||||
"max_width": 1920,
|
|
||||||
"max_height": 1080,
|
|
||||||
"max_refresh": 120,
|
|
||||||
"sensor_init_delay": 10,
|
|
||||||
"z_vector_disabled": True,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
188
main.py
188
main.py
|
@ -1,188 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from sys import platform
|
|
||||||
import logging
|
|
||||||
import atexit
|
|
||||||
import json
|
|
||||||
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 loguru import logger
|
|
||||||
import PyEvdi
|
|
||||||
import pyedid
|
|
||||||
import pyray
|
|
||||||
import time
|
|
||||||
|
|
||||||
from render import render_loop
|
|
||||||
import libunreal
|
|
||||||
|
|
||||||
default_configuration: dict[str, str | int] = {
|
|
||||||
"display_angle": 45,
|
|
||||||
"display_spacing": 1,
|
|
||||||
"display_count": 3,
|
|
||||||
"allow_unsupported_devices": False,
|
|
||||||
"allow_unsupported_vendors": False,
|
|
||||||
"override_default_edid": False,
|
|
||||||
"edid_override_path": "/file/here",
|
|
||||||
"override_width": 0,
|
|
||||||
"override_height": 0,
|
|
||||||
"override_refresh_rate": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize_configuration():
|
|
||||||
pass
|
|
||||||
|
|
||||||
used_cards: list[int] = []
|
|
||||||
|
|
||||||
def find_suitable_evdi_card() -> int:
|
|
||||||
for i in range(20):
|
|
||||||
if PyEvdi.check_device(i) == PyEvdi.AVAILABLE and i not in used_cards:
|
|
||||||
used_cards.append(i)
|
|
||||||
return i
|
|
||||||
|
|
||||||
PyEvdi.add_device()
|
|
||||||
|
|
||||||
for i in range(20):
|
|
||||||
if PyEvdi.check_device(i) == PyEvdi.AVAILABLE and i not in used_cards:
|
|
||||||
used_cards.append(i)
|
|
||||||
return i
|
|
||||||
|
|
||||||
raise ValueError("Failed to allocate virtual display device")
|
|
||||||
|
|
||||||
@logger.catch
|
|
||||||
def main():
|
|
||||||
configuration = {}
|
|
||||||
|
|
||||||
logger.info("Loading configuration")
|
|
||||||
config_dir = os.environ["UNREALXR_CONFIG_PATH"] if "UNREALXR_CONFIG_PATH" in os.environ else ""
|
|
||||||
data_dir = os.environ["UNREALXR_DATA_PATH"] if "UNREALXR_DATA_PATH" in os.environ else ""
|
|
||||||
|
|
||||||
# Use OS defaults if we weren't overriden in env
|
|
||||||
if config_dir == "":
|
|
||||||
config_dir = user_config_dir("UnrealXR", "Tera")
|
|
||||||
|
|
||||||
if data_dir == "":
|
|
||||||
data_dir = user_data_dir("UnrealXR", "Tera")
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.stat(data_dir)
|
|
||||||
except OSError:
|
|
||||||
os.makedirs(data_dir)
|
|
||||||
|
|
||||||
# Read config and create it if it doesn't exist
|
|
||||||
config_path = os.path.join(config_dir, "config.json")
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.stat(config_path)
|
|
||||||
|
|
||||||
with open(config_path, "r") as config_file:
|
|
||||||
configuration = json.load(config_file)
|
|
||||||
except OSError:
|
|
||||||
try:
|
|
||||||
os.makedirs(config_dir)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
with open(config_path, "w") as config_file:
|
|
||||||
json.dump(default_configuration, config_file, indent=4)
|
|
||||||
|
|
||||||
configuration = default_configuration
|
|
||||||
|
|
||||||
# Set unbound values (ie. if user is using an older version)
|
|
||||||
for key, default_value in default_configuration.items():
|
|
||||||
if key not in configuration:
|
|
||||||
# Add quotes if we're a string
|
|
||||||
value = default_value
|
|
||||||
|
|
||||||
if isinstance(value, str):
|
|
||||||
value = f'"{value}"'
|
|
||||||
|
|
||||||
logger.warning(f"Setting unbound key '{key}' with default value {value}. You might want to define this! If not, this warning can be safely ignored.")
|
|
||||||
configuration[key] = default_value
|
|
||||||
|
|
||||||
# Initialize logging to files
|
|
||||||
logger.add(os.path.join(data_dir, "unrealxr.log"), format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")
|
|
||||||
logger.info("Loaded configuration")
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
raise OSError("You are not running as root! Running as root is necessary to talk to the EVDI service")
|
|
||||||
|
|
||||||
# Get the display EDID
|
|
||||||
logger.info("Attempting to read display EDID file")
|
|
||||||
edid: libunreal.UnrealXRDisplayMetadata | None = None
|
|
||||||
|
|
||||||
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
|
|
||||||
with open(configuration["edid_override_path"], "rb") as edid_file:
|
|
||||||
edid_file = edid_file.read()
|
|
||||||
parsed_edid_file = pyedid.parse_edid(edid_file)
|
|
||||||
|
|
||||||
max_width = int(configuration["override_width"])
|
|
||||||
max_height = int(configuration["override_height"])
|
|
||||||
max_refresh = int(configuration["override_refresh_rate"])
|
|
||||||
|
|
||||||
if configuration["override_width"] == 0 or configuration["override_height"] == 0 or configuration["override_refresh_rate"] == 0:
|
|
||||||
for resolution in parsed_edid_file.resolutions:
|
|
||||||
if configuration["override_width"] == 0 or configuration["override_height"] == 0:
|
|
||||||
if resolution[0] > max_width and resolution[1] > max_height:
|
|
||||||
max_width = resolution[0]
|
|
||||||
max_height = resolution[1]
|
|
||||||
|
|
||||||
if configuration["override_refresh_rate"] == 0:
|
|
||||||
max_refresh = max(max_refresh, int(resolution[2]))
|
|
||||||
|
|
||||||
if max_width == 0 or max_height == 0:
|
|
||||||
raise ValueError("Could not determine maximum width and/or height from EDID file, and the width and/or height overrides aren't set!")
|
|
||||||
|
|
||||||
if max_refresh == 0:
|
|
||||||
raise ValueError("Could not determine maximum refresh rate from EDID file, and the refresh rate overrides aren't set!")
|
|
||||||
|
|
||||||
edid = libunreal.UnrealXRDisplayMetadata(edid_file, parsed_edid_file.name if parsed_edid_file.name else "", {}, max_width, max_height, max_refresh, "", "")
|
|
||||||
else:
|
|
||||||
edid = libunreal.fetch_xr_glass_edid(configuration["allow_unsupported_devices"])
|
|
||||||
|
|
||||||
assert(edid is not None)
|
|
||||||
logger.info("Got EDID file")
|
|
||||||
|
|
||||||
if platform == "linux" or platform == "linux2":
|
|
||||||
# TODO: implement EDID patching for overridden displays
|
|
||||||
logger.info("Patching EDID firmware")
|
|
||||||
patched_edid = libunreal.patch_edid_to_be_specialized(edid.edid)
|
|
||||||
libunreal.upload_edid_firmware(edid, patched_edid)
|
|
||||||
|
|
||||||
def unload_custom_fw():
|
|
||||||
with open(f"/sys/kernel/debug/dri/{edid.linux_drm_card.replace("card", "")}/{edid.linux_drm_connector}/edid_override", "w") as kernel_edid:
|
|
||||||
kernel_edid.write("reset")
|
|
||||||
|
|
||||||
logger.info("Please unplug and plug in your XR device to restore it back to normal settings.")
|
|
||||||
|
|
||||||
atexit.register(unload_custom_fw)
|
|
||||||
input("Press the Enter key to continue loading after you unplug and plug in your XR device.")
|
|
||||||
|
|
||||||
# Raylib gets confused if there's multiple dri devices so we initialize the window before anything
|
|
||||||
logger.info("Initializing XR headset")
|
|
||||||
pyray.set_target_fps(edid.max_refresh_rate*2) # we need more headroom...
|
|
||||||
pyray.init_window(edid.max_width, edid.max_height, "UnrealXR")
|
|
||||||
|
|
||||||
logger.info("Initializing virtual displays")
|
|
||||||
cards = []
|
|
||||||
|
|
||||||
for i in range(int(configuration["display_count"])):
|
|
||||||
suitable_card_id = find_suitable_evdi_card()
|
|
||||||
card = PyEvdi.Card(suitable_card_id)
|
|
||||||
card.connect(edid.edid, len(edid.edid), edid.max_width*edid.max_height, edid.max_width*edid.max_height*edid.max_refresh_rate)
|
|
||||||
cards.append(card)
|
|
||||||
|
|
||||||
logger.debug(f"Initialized card #{str(i+1)}")
|
|
||||||
|
|
||||||
atexit.register(lambda: card.close())
|
|
||||||
|
|
||||||
logger.info("Initialized displays. Entering rendering loop")
|
|
||||||
render_loop(edid, configuration, cards)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("Welcome to UnrealXR!\n")
|
|
||||||
main()
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 3673a4b34d386921fc323ddbd2ef0e000022e2d4
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 36ad789c9b5893653c523d4af6e24120ec19ab33
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 7c982c0d4e2773739f00eb91a2d2cbe87eb7c76d
|
|
235
render.py
235
render.py
|
@ -1,235 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from io import BufferedWriter
|
|
||||||
from sys import int_info
|
|
||||||
from typing import Union
|
|
||||||
import ctypes
|
|
||||||
import time
|
|
||||||
import math
|
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
from raylib import rl
|
|
||||||
import PyEvdi
|
|
||||||
import pyray
|
|
||||||
|
|
||||||
from libunreal import UnrealXRDisplayMetadata, MCUCallbackWrapper, start_mcu_event_listener
|
|
||||||
|
|
||||||
vertical_size = 0.0
|
|
||||||
horizontal_sizing_constant = 1
|
|
||||||
|
|
||||||
previous_pitch = 0.0
|
|
||||||
previous_yaw = 0.0
|
|
||||||
previous_roll = 0.0
|
|
||||||
|
|
||||||
current_pitch = 0.0
|
|
||||||
current_yaw = 0.0
|
|
||||||
current_roll = 0.0
|
|
||||||
|
|
||||||
has_gotten_pitch_callback_before = False
|
|
||||||
has_gotten_yaw_callback_before = False
|
|
||||||
has_gotten_roll_callback_before = False
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RectMetadata:
|
|
||||||
card: PyEvdi.Card
|
|
||||||
buffer_ptr: pyray.ffi.CData | None
|
|
||||||
texture: Union[pyray.Texture, None]
|
|
||||||
model: Union[pyray.Model, None]
|
|
||||||
angle: int
|
|
||||||
relative_position: int
|
|
||||||
|
|
||||||
def pitch_callback(new_pitch: float):
|
|
||||||
global current_pitch
|
|
||||||
global previous_pitch
|
|
||||||
global has_gotten_pitch_callback_before
|
|
||||||
|
|
||||||
if not has_gotten_pitch_callback_before:
|
|
||||||
has_gotten_pitch_callback_before = True
|
|
||||||
previous_pitch = new_pitch
|
|
||||||
current_pitch = new_pitch
|
|
||||||
else:
|
|
||||||
previous_pitch = current_pitch
|
|
||||||
current_pitch = new_pitch
|
|
||||||
|
|
||||||
def yaw_callback(new_yaw: float):
|
|
||||||
global current_yaw
|
|
||||||
global previous_yaw
|
|
||||||
global has_gotten_yaw_callback_before
|
|
||||||
|
|
||||||
if not has_gotten_yaw_callback_before:
|
|
||||||
has_gotten_yaw_callback_before = True
|
|
||||||
previous_yaw = new_yaw
|
|
||||||
current_yaw = new_yaw
|
|
||||||
else:
|
|
||||||
previous_yaw = current_yaw
|
|
||||||
current_yaw = new_yaw
|
|
||||||
|
|
||||||
def roll_callback(new_roll: float):
|
|
||||||
global current_roll
|
|
||||||
global previous_roll
|
|
||||||
global has_gotten_roll_callback_before
|
|
||||||
|
|
||||||
if not has_gotten_roll_callback_before:
|
|
||||||
has_gotten_roll_callback_before = True
|
|
||||||
previous_roll = new_roll
|
|
||||||
roll = new_roll
|
|
||||||
else:
|
|
||||||
previous_roll = current_roll
|
|
||||||
current_roll = new_roll
|
|
||||||
|
|
||||||
def text_message(message: str):
|
|
||||||
logger.debug(f"Got message from AR's MCU: {message}")
|
|
||||||
|
|
||||||
def stub_brightness_function(brightness: int):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def find_max_vertical_size(fovy_deg: float, distance: float) -> float:
|
|
||||||
fovy_rad = math.radians(fovy_deg)
|
|
||||||
return 2 * distance * math.tan(fovy_rad / 2)
|
|
||||||
|
|
||||||
def find_optimal_horizonal_res(vertical_display_res: int, horizontal_display_res: int) -> float:
|
|
||||||
aspect_ratio = horizontal_display_res/vertical_display_res
|
|
||||||
horizontal_size = vertical_size * aspect_ratio
|
|
||||||
horizontal_size = horizontal_size * horizontal_sizing_constant
|
|
||||||
|
|
||||||
return horizontal_size
|
|
||||||
|
|
||||||
def render_loop(display_metadata: UnrealXRDisplayMetadata, config: dict[str, str | int], cards: list[PyEvdi.Card]):
|
|
||||||
global vertical_size
|
|
||||||
global core_mesh
|
|
||||||
logger.info("Starting sensor event listener")
|
|
||||||
|
|
||||||
mcu_callbacks = MCUCallbackWrapper(roll_callback, pitch_callback, yaw_callback, text_message, stub_brightness_function, stub_brightness_function)
|
|
||||||
start_mcu_event_listener(display_metadata.device_vendor, mcu_callbacks)
|
|
||||||
|
|
||||||
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):
|
|
||||||
time.sleep(0.01)
|
|
||||||
|
|
||||||
logger.info("Initialized sensors")
|
|
||||||
|
|
||||||
camera = pyray.Camera3D()
|
|
||||||
camera.fovy = 45.0
|
|
||||||
|
|
||||||
vertical_size = find_max_vertical_size(camera.fovy, 5.0)
|
|
||||||
|
|
||||||
camera.position = pyray.Vector3(0.0, vertical_size/2, 5.0)
|
|
||||||
camera.target = pyray.Vector3(0.0, vertical_size/2, 0.0)
|
|
||||||
camera.up = pyray.Vector3(0.0, 1.0, 0.0)
|
|
||||||
camera.projection = pyray.CameraProjection.CAMERA_PERSPECTIVE
|
|
||||||
|
|
||||||
core_mesh = pyray.gen_mesh_plane(find_optimal_horizonal_res(display_metadata.max_height, display_metadata.max_width), vertical_size, 1, 1)
|
|
||||||
|
|
||||||
movement_vector = pyray.Vector3()
|
|
||||||
look_vector = pyray.Vector3()
|
|
||||||
|
|
||||||
has_z_vector_disabled_quirk = False
|
|
||||||
has_sensor_init_delay_quirk = False
|
|
||||||
sensor_init_start_time = time.time()
|
|
||||||
|
|
||||||
if "z_vector_disabled" in display_metadata.device_quirks and bool(display_metadata.device_quirks["z_vector_disabled"]):
|
|
||||||
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
|
|
||||||
|
|
||||||
rects: list[RectMetadata] = []
|
|
||||||
|
|
||||||
if int(config["display_count"]) >= 2:
|
|
||||||
display_angle = int(config["display_angle"])
|
|
||||||
display_spacing = int(config["display_spacing"])
|
|
||||||
total_displays = int(config["display_count"])
|
|
||||||
|
|
||||||
highest_possible_angle_on_both_sides = (total_displays-1)*display_angle
|
|
||||||
highest_possible_pixel_spacing_on_both_sides = (total_displays-1)*display_spacing
|
|
||||||
|
|
||||||
for i in range(total_displays):
|
|
||||||
current_angle = (-highest_possible_angle_on_both_sides)+(display_angle*i)
|
|
||||||
current_display_spacing = (-highest_possible_pixel_spacing_on_both_sides)+(display_spacing*i)
|
|
||||||
|
|
||||||
rect_metadata = RectMetadata(cards[i], None, None, None, current_angle, current_display_spacing)
|
|
||||||
|
|
||||||
has_acquired_fb = False
|
|
||||||
|
|
||||||
def fb_acquire_handler(evdi_buffer: PyEvdi.Buffer):
|
|
||||||
nonlocal has_acquired_fb
|
|
||||||
|
|
||||||
if has_acquired_fb:
|
|
||||||
return
|
|
||||||
|
|
||||||
has_acquired_fb = True
|
|
||||||
logger.info(f"Acquired buffer for card #{i+1} with ID {evdi_buffer.id}")
|
|
||||||
|
|
||||||
address = ctypes.pythonapi.PyCapsule_GetPointer
|
|
||||||
address.restype = ctypes.c_void_p
|
|
||||||
address.argtypes = [ctypes.py_object, ctypes.c_char_p]
|
|
||||||
|
|
||||||
buffer_void_ptr = address(evdi_buffer.bytes, None)
|
|
||||||
rect_metadata.buffer_ptr = pyray.ffi.cast("void *", buffer_void_ptr)
|
|
||||||
|
|
||||||
pyray_image = pyray.Image()
|
|
||||||
|
|
||||||
pyray_image.data = rect_metadata.buffer_ptr
|
|
||||||
pyray_image.width = display_metadata.max_width
|
|
||||||
pyray_image.height = display_metadata.max_height
|
|
||||||
pyray_image.mipmaps = 1
|
|
||||||
pyray_image.format = pyray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
|
|
||||||
|
|
||||||
rect_metadata.texture = pyray.load_texture_from_image(pyray_image)
|
|
||||||
rect_metadata.model = pyray.load_model_from_mesh(core_mesh)
|
|
||||||
|
|
||||||
pyray.set_material_texture(rect_metadata.model.materials[0], pyray.MaterialMapIndex.MATERIAL_MAP_ALBEDO, rect_metadata.texture)
|
|
||||||
|
|
||||||
cards[i].acquire_framebuffer_handler = fb_acquire_handler
|
|
||||||
cards[i].handle_events(1000)
|
|
||||||
|
|
||||||
rects.append(rect_metadata)
|
|
||||||
|
|
||||||
while not pyray.window_should_close():
|
|
||||||
if has_sensor_init_delay_quirk:
|
|
||||||
if time.time() - sensor_init_start_time >= int(display_metadata.device_quirks["sensor_init_delay"]):
|
|
||||||
# Unset the quirk state
|
|
||||||
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
|
|
||||||
|
|
||||||
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.clear_background(pyray.BLACK)
|
|
||||||
pyray.begin_mode_3d(camera)
|
|
||||||
|
|
||||||
for rect_count in range(len(rects)):
|
|
||||||
rect = rects[rect_count]
|
|
||||||
|
|
||||||
if rect.buffer_ptr is None or rect.texture is None or rect.model is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
cards[rect_count].handle_events(1)
|
|
||||||
pyray.update_texture(rect.texture, rect.buffer_ptr)
|
|
||||||
|
|
||||||
pyray.draw_model_ex(
|
|
||||||
rect.model,
|
|
||||||
pyray.Vector3(0, vertical_size/2, 0),
|
|
||||||
pyray.Vector3(1, 0, 0), # rotate around X to make it vertical
|
|
||||||
90,
|
|
||||||
pyray.Vector3(1, 1, 1),
|
|
||||||
pyray.WHITE
|
|
||||||
)
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
pyray.end_mode_3d()
|
|
||||||
pyray.end_drawing()
|
|
||||||
|
|
||||||
logger.info("Goodbye!")
|
|
||||||
pyray.close_window()
|
|
|
@ -1,7 +0,0 @@
|
||||||
setuptools==80.9.0
|
|
||||||
pybind11==2.13.6
|
|
||||||
pyedid==1.0.3
|
|
||||||
loguru==0.7.3
|
|
||||||
platformdirs==4.3.8
|
|
||||||
cffi=1.17.1
|
|
||||||
wheel=0.45.1
|
|
11
shell.nix
11
shell.nix
|
@ -3,9 +3,12 @@
|
||||||
}: pkgs.mkShell {
|
}: pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
# Runtime dependencies
|
# Runtime dependencies
|
||||||
python3
|
|
||||||
pciutils
|
pciutils
|
||||||
|
|
||||||
|
# UnrealXR build dependencies
|
||||||
|
go
|
||||||
|
gopls
|
||||||
|
|
||||||
# evdi build dependencies
|
# evdi build dependencies
|
||||||
libdrm
|
libdrm
|
||||||
linuxHeaders
|
linuxHeaders
|
||||||
|
@ -37,11 +40,5 @@
|
||||||
mkdir -p "$PWD/data/config" "$PWD/data/data"
|
mkdir -p "$PWD/data/config" "$PWD/data/data"
|
||||||
export UNREALXR_CONFIG_PATH="$PWD/data/config"
|
export UNREALXR_CONFIG_PATH="$PWD/data/config"
|
||||||
export UNREALXR_DATA_PATH="$PWD/data/data"
|
export UNREALXR_DATA_PATH="$PWD/data/data"
|
||||||
|
|
||||||
if [ ! -d ".venv" ]; then
|
|
||||||
python3 -m venv .venv
|
|
||||||
fi
|
|
||||||
|
|
||||||
source .venv/bin/activate
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue