mirror of
https://github.com/lbgos/rift-s-linux.git
synced 2026-10-25 05:01:09 +01:00
inotify drops watches on moved or deleted directories and wait() drains the fd without parsing, so the remembered path set kept dead entries forever. Re-add watches every pass so watch mode recovers after a Steam reinstall instead of stalling until service restart. (cherry picked from commit 9cd08b0d7a4cb095f6bae516f42d141e1d2b0d64)
172 lines
6.7 KiB
Python
172 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Register the installed Rift S driver using SteamVR's own path registry tool."""
|
|
import argparse
|
|
import ctypes
|
|
import fcntl
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import select
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
DRIVER = Path('@CMAKE_INSTALL_FULL_DATADIR@/steamvr-monado')
|
|
|
|
|
|
def read_registry(path):
|
|
try:
|
|
value = json.loads(path.read_text())
|
|
return value if isinstance(value, dict) else {}
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
return {}
|
|
|
|
|
|
def steam_roots(home):
|
|
roots = [home / '.local/share/Steam', home / '.steam/steam', home / '.steam/root']
|
|
result = []
|
|
for root in roots:
|
|
root = root.resolve()
|
|
if root.is_dir() and root not in result:
|
|
result.append(root)
|
|
return result
|
|
|
|
|
|
def find_runtime(home, registry):
|
|
roots = steam_roots(home)
|
|
libraries = list(roots)
|
|
for root in roots:
|
|
folders = root / 'steamapps/libraryfolders.vdf'
|
|
if folders.is_file():
|
|
# Only library paths are needed; never read account or login files.
|
|
for path in re.findall(r'"path"\s*"((?:\\.|[^"\\])*)"', folders.read_text()):
|
|
library = Path(path.replace('\\\\', '\\').replace('\\"', '"'))
|
|
if library not in libraries:
|
|
libraries.append(library)
|
|
candidates = [Path(path) for path in registry.get('runtime', []) if isinstance(path, str)]
|
|
candidates += [path / 'steamapps/common/SteamVR' for path in libraries]
|
|
for runtime in candidates:
|
|
tool = runtime / 'bin/linux64/vrpathreg'
|
|
if tool.is_file() and os.access(tool, os.X_OK):
|
|
return tool, roots, libraries
|
|
return None, roots, libraries
|
|
|
|
|
|
def pathreg(tool, *arguments):
|
|
# Native vrpathreg can report paths. Do not copy those or subprocess environment to logs.
|
|
result = subprocess.run([str(tool), *arguments], stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL, timeout=20)
|
|
if result.returncode:
|
|
raise RuntimeError('SteamVR driver registration failed; will retry.')
|
|
|
|
|
|
def register(home, registry_path, uninstall=False):
|
|
registry = read_registry(registry_path)
|
|
tool, roots, libraries = find_runtime(home, registry)
|
|
if tool is None:
|
|
return False, roots, libraries
|
|
drivers = registry.get('external_drivers') or []
|
|
if uninstall:
|
|
if str(DRIVER) in drivers:
|
|
pathreg(tool, 'removedriver', str(DRIVER))
|
|
return True, roots, libraries
|
|
if not (DRIVER / 'driver.vrdrivermanifest').is_file():
|
|
raise RuntimeError('Installed Rift S driver manifest is missing.')
|
|
if str(DRIVER) in drivers:
|
|
return True, roots, libraries
|
|
# Bootstrap only missing native paths. Existing runtime/config/log paths remain intact.
|
|
if not registry.get('runtime'):
|
|
pathreg(tool, 'setthis')
|
|
root = roots[0] if roots else home / '.local/share/Steam'
|
|
if not registry.get('config'):
|
|
pathreg(tool, 'setconfig', str(root / 'config'))
|
|
if not registry.get('log'):
|
|
pathreg(tool, 'setlog', str(root / 'logs'))
|
|
pathreg(tool, 'adddriver', str(DRIVER))
|
|
return True, roots, libraries
|
|
|
|
|
|
class DirectoryEvents:
|
|
def __init__(self):
|
|
self.libc = ctypes.CDLL(None, use_errno=True)
|
|
self.libc.inotify_init1.argtypes = [ctypes.c_int]
|
|
self.libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32]
|
|
self.fd = self.libc.inotify_init1(os.O_NONBLOCK | os.O_CLOEXEC)
|
|
self.watches = set()
|
|
|
|
def watch(self, directories):
|
|
if self.fd < 0:
|
|
return
|
|
for directory in directories:
|
|
directory = directory.resolve()
|
|
while not directory.is_dir() and directory != directory.parent:
|
|
directory = directory.parent
|
|
# Re-add every pass: the kernel drops watches on moved or deleted
|
|
# directories (IN_IGNORED), which wait() drains unread. Skipping
|
|
# known paths here would leave watch mode deaf after a Steam
|
|
# reinstall until the service restarts.
|
|
mask = 0x100 | 0x80 | 0x8 | 0x400 | 0x800
|
|
if self.libc.inotify_add_watch(self.fd, os.fsencode(directory), mask) >= 0:
|
|
self.watches.add(directory)
|
|
|
|
def wait(self):
|
|
if self.fd < 0:
|
|
time.sleep(2)
|
|
elif select.select([self.fd], [], [], 2)[0]:
|
|
try:
|
|
os.read(self.fd, 65536)
|
|
except BlockingIOError:
|
|
pass
|
|
time.sleep(0.1)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('action', choices=['install', 'uninstall', 'watch'], nargs='?', default='install')
|
|
args = parser.parse_args()
|
|
if os.geteuid() == 0:
|
|
parser.error('Run in the Steam user session.')
|
|
home = Path.home()
|
|
config = Path(os.environ.get('XDG_CONFIG_HOME', home / '.config'))
|
|
registry_path = Path(os.environ.get('VR_PATHREG_OVERRIDE', config / 'openvr/openvrpaths.vrpath'))
|
|
state = Path(os.environ.get('XDG_STATE_HOME', home / '.local/state')) / 'monado-rifts'
|
|
state.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
events = DirectoryEvents()
|
|
last_error = None
|
|
with (state / 'registration.lock').open('w') as lock:
|
|
while True:
|
|
with_lock = False
|
|
try:
|
|
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
with_lock = True
|
|
done, roots, libraries = register(home, registry_path, args.action == 'uninstall')
|
|
last_error = None
|
|
except (OSError, RuntimeError, subprocess.TimeoutExpired) as error:
|
|
if args.action != 'watch':
|
|
raise SystemExit(str(error))
|
|
if last_error != str(error):
|
|
print(str(error), file=sys.stderr, flush=True)
|
|
last_error = str(error)
|
|
roots, libraries = steam_roots(home), []
|
|
done = False
|
|
finally:
|
|
if with_lock:
|
|
fcntl.flock(lock, fcntl.LOCK_UN)
|
|
if args.action != 'watch':
|
|
print('Rift S registered.' if done else 'Rift S registration will complete when SteamVR is installed.')
|
|
return
|
|
directories = [config / 'openvr', home / '.local/share/Steam', home / '.steam']
|
|
for root in roots:
|
|
directories.extend([root, root / 'steamapps'])
|
|
for library in libraries:
|
|
runtime = library / 'steamapps/common/SteamVR'
|
|
directories.extend([library, library / 'steamapps', library / 'steamapps/common',
|
|
runtime, runtime / 'bin', runtime / 'bin/linux64'])
|
|
events.watch(directories)
|
|
events.wait()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|