mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 01:00:06 +02:00
Warn in Input Settings when SteamVR hasn't loaded the pointer driver
When SteamVR crashes, safe mode can block the newest add-on, and then it skips the ft_pointer driver at every start. The cursor still moves (the pointer helper draws it), but clicks, scrolling and mapped actions go through the driver's virtual controller, so none of them do anything, and nothing said why (#4). Input Settings now shows a warning at the top of every page with the fix: Manage Add-Ons in SteamVR's settings, unblock ft_pointer, restart SteamVR. It reads steamvr.vrsettings (~/.config/openvr/config on the Frame) for blocked_by_safe_mode, a disabled driver, or SteamVR's own safe mode. When none of those is set but SteamVR is running (the helper answers) and @ft_pointer doesn't, it says SteamVR runs without the driver: unblocked but not restarted yet, or not installed. The driver ignores the "ping" it sends. It checks at startup and every 30 minutes, since the driver only changes when SteamVR restarts. Fixes #4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b99fb97f32
commit
51e7e0fa85
2 files changed
+94
-7
No files matched your search
@@ -13,6 +13,10 @@ to the input relay over its control socket (@frametop_relay):
|
||||
- Gaze: the pointer's gaze mode (@ft_pointer_helper "gaze") and the gaze service
|
||||
(gaze/ft-gazed, @ft_gazed: status, forget, reload).
|
||||
- Bluetooth: paired devices, and re-applying the Bluetooth LE fixes after pairing.
|
||||
- A warning on every page when SteamVR won't load the ft_pointer driver (blocked after a
|
||||
crash, disabled, or SteamVR in safe mode) or hasn't loaded it (@ft_pointer doesn't answer
|
||||
while SteamVR runs): the cursor still moves, but no click lands. Checked at startup and
|
||||
every 30 minutes.
|
||||
Rules go to ~/.config/frametop-input.json and pointer settings to
|
||||
~/.config/frametop.conf; then the relay (and through it the helper) reloads.
|
||||
Launch with input-settings/ft-input-settings (host wrapper).
|
||||
@@ -36,6 +40,10 @@ CONF_PATH = os.path.expanduser("~/.config/frametop.conf")
|
||||
RELAY = "\0frametop_relay"
|
||||
HELPER = "\0ft_pointer_helper"
|
||||
GAZED = "\0ft_gazed"
|
||||
DRIVER = "\0ft_pointer" # the ft_pointer driver's control socket, bound while SteamVR has it loaded
|
||||
# SteamVR's settings; older installs keep them under Steam's config.
|
||||
VRSETTINGS_PATHS = [os.path.expanduser("~/.config/openvr/config/steamvr.vrsettings"),
|
||||
os.path.expanduser("~/.steam/steam/config/steamvr.vrsettings")]
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
GAZE_PROBE = os.path.join(REPO, "gaze", "probe", "ft-gazeprobe")
|
||||
BTN_MISC = 0x100
|
||||
@@ -157,6 +165,29 @@ def host(*cmd):
|
||||
return subprocess.CompletedProcess(cmd, 1, "", str(e))
|
||||
|
||||
|
||||
def driver_block():
|
||||
"""Why SteamVR won't load the ft_pointer driver: "blocked" (safe mode blocked it after a
|
||||
crash), "disabled" (turned off in Manage Add-Ons), "safemode" (SteamVR in safe mode, every
|
||||
add-on off) or "" (nothing stops it). SteamVR reads these at startup."""
|
||||
for path in VRSETTINGS_PATHS:
|
||||
if os.path.exists(path):
|
||||
settings = read_json(path)
|
||||
break
|
||||
else:
|
||||
return ""
|
||||
section = lambda name: settings.get(name) if isinstance(settings.get(name), dict) else {}
|
||||
if not isinstance(settings, dict):
|
||||
return ""
|
||||
driver = section("driver_ft_pointer")
|
||||
if driver.get("blocked_by_safe_mode") is True:
|
||||
return "blocked"
|
||||
if driver.get("enable") is False:
|
||||
return "disabled"
|
||||
if section("steamvr").get("enableSafeMode") is True:
|
||||
return "safemode"
|
||||
return ""
|
||||
|
||||
|
||||
class Backend(QObject):
|
||||
devicesChanged = Signal()
|
||||
mappingsChanged = Signal()
|
||||
@@ -164,6 +195,7 @@ class Backend(QObject):
|
||||
bluetoothChanged = Signal()
|
||||
controllersChanged = Signal()
|
||||
gazeChanged = Signal()
|
||||
driverChanged = Signal()
|
||||
activity = Signal(str) # device id
|
||||
captured = Signal(int, str) # code, name
|
||||
capturedController = Signal(str, str) # button, label
|
||||
@@ -184,6 +216,7 @@ class Backend(QObject):
|
||||
self._gaze_prev = None # the status before, for rates
|
||||
self._gaze_at = 0.0
|
||||
self._gaze_mode = None # the helper's gaze mode: True, False, None (no answer)
|
||||
self._driver_block = "" # set by _check_driver
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
self.sock.bind("") # autobind an abstract address the relay can reply to
|
||||
self.sock.setblocking(False)
|
||||
@@ -194,6 +227,11 @@ class Backend(QObject):
|
||||
self.rewatch = QTimer(interval=50000, timeout=lambda: self._send("watch 60"))
|
||||
self.rewatch.start()
|
||||
self.reload_timer = QTimer(singleShot=True, interval=400, timeout=lambda: self._send("reload"))
|
||||
# The driver only changes with a SteamVR restart, so a rare check is enough. The first one
|
||||
# waits a moment for the helper's vrstatus answer, which tells us SteamVR is up.
|
||||
self.driver_timer = QTimer(interval=30 * 60 * 1000, timeout=self._check_driver)
|
||||
self.driver_timer.start()
|
||||
QTimer.singleShot(3000, self._check_driver)
|
||||
self._refresh()
|
||||
self._send("watch 60")
|
||||
self.refreshBluetooth()
|
||||
@@ -225,6 +263,14 @@ class Backend(QObject):
|
||||
self._gaze_mode = None
|
||||
self.gazeChanged.emit()
|
||||
|
||||
def _check_driver(self):
|
||||
block = driver_block()
|
||||
if not block and self._vr and not self._send("ping", DRIVER):
|
||||
block = "unloaded" # SteamVR runs (the helper answers) without the driver
|
||||
if block != self._driver_block:
|
||||
self._driver_block = block
|
||||
self.driverChanged.emit()
|
||||
|
||||
def _read(self):
|
||||
while True:
|
||||
try:
|
||||
@@ -270,6 +316,13 @@ class Backend(QObject):
|
||||
self._capture_id = ""
|
||||
self.captured.emit(code, self.codeName(code))
|
||||
|
||||
# --- SteamVR driver ---
|
||||
@Property(str, notify=driverChanged)
|
||||
def driverBlock(self):
|
||||
"""driver_block(), or "unloaded": SteamVR runs without the driver (unblocked since it
|
||||
started, or not installed)."""
|
||||
return self._driver_block
|
||||
|
||||
# --- devices ---
|
||||
@Property(bool, notify=devicesChanged)
|
||||
def relayRunning(self):
|
||||
|
||||
+41
-7
@@ -25,6 +25,30 @@ Kirigami.ApplicationWindow {
|
||||
]
|
||||
}
|
||||
|
||||
// Why SteamVR has no ft_pointer driver, and how to get it back. Clicks, scrolling and mapped
|
||||
// actions all go through that driver, so every page shows this as (part of) its header.
|
||||
component DriverWarning: Kirigami.InlineMessage {
|
||||
readonly property string fix: "SteamVR Settings > Startup / Shutdown > Manage Add-Ons"
|
||||
readonly property string restart: ", then restart SteamVR (or reboot the headset)."
|
||||
visible: backend.driverBlock !== ""
|
||||
position: Kirigami.InlineMessage.Position.Header
|
||||
type: backend.driverBlock === "unloaded" ? Kirigami.MessageType.Warning : Kirigami.MessageType.Error
|
||||
text: ({
|
||||
blocked: "SteamVR blocked the Frametop pointer driver (ft_pointer) after a crash, so mouse clicks "
|
||||
+ "and scrolling do nothing (the cursor still moves). To fix it, open " + fix
|
||||
+ ", press Unblock next to ft_pointer" + restart,
|
||||
disabled: "The Frametop pointer driver (ft_pointer) is turned off in SteamVR, so mouse clicks and "
|
||||
+ "scrolling do nothing (the cursor still moves). To fix it, open " + fix
|
||||
+ ", turn ft_pointer on" + restart,
|
||||
safemode: "SteamVR is in safe mode, so it loads no add-ons, the Frametop pointer driver (ft_pointer) "
|
||||
+ "included: mouse clicks and scrolling do nothing (the cursor still moves). To fix it, turn "
|
||||
+ "safe mode off in SteamVR and check " + fix + restart,
|
||||
unloaded: "SteamVR is running without the Frametop pointer driver (ft_pointer), so mouse clicks and "
|
||||
+ "scrolling do nothing. If you just unblocked it, restart SteamVR (or reboot the headset). "
|
||||
+ "Otherwise check " + fix + ", or reinstall it with pointer/driver/install.sh."
|
||||
})[backend.driverBlock] || ""
|
||||
}
|
||||
|
||||
function show(page) {
|
||||
pageStack.clear()
|
||||
pageStack.push(page)
|
||||
@@ -47,13 +71,18 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.ScrollablePage {
|
||||
title: "Devices"
|
||||
|
||||
header: Kirigami.InlineMessage {
|
||||
visible: !backend.relayRunning || !backend.pointerMode
|
||||
position: Kirigami.InlineMessage.Position.Header
|
||||
type: backend.relayRunning ? Kirigami.MessageType.Information : Kirigami.MessageType.Error
|
||||
text: !backend.relayRunning
|
||||
? "The input relay isn't running (frametop-input-relay.service)."
|
||||
: "Pointer mode is off (POINTER=0): pointer devices act as a plain mouse."
|
||||
header: ColumnLayout {
|
||||
spacing: 0
|
||||
DriverWarning { Layout.fillWidth: true }
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
visible: !backend.relayRunning || !backend.pointerMode
|
||||
position: Kirigami.InlineMessage.Position.Header
|
||||
type: backend.relayRunning ? Kirigami.MessageType.Information : Kirigami.MessageType.Error
|
||||
text: !backend.relayRunning
|
||||
? "The input relay isn't running (frametop-input-relay.service)."
|
||||
: "Pointer mode is off (POINTER=0): pointer devices act as a plain mouse."
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
@@ -171,6 +200,7 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.ScrollablePage {
|
||||
id: bpage
|
||||
title: "Buttons"
|
||||
header: DriverWarning {}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: "Remove all"
|
||||
@@ -310,6 +340,7 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.ScrollablePage {
|
||||
id: cpage
|
||||
title: "Controllers"
|
||||
header: DriverWarning {}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: "Remove all"
|
||||
@@ -478,6 +509,7 @@ Kirigami.ApplicationWindow {
|
||||
id: pointerPage
|
||||
Kirigami.ScrollablePage {
|
||||
title: "Pointer"
|
||||
header: DriverWarning {}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: "Recenter"
|
||||
@@ -549,6 +581,7 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.ScrollablePage {
|
||||
id: gpage
|
||||
title: "Gaze"
|
||||
header: DriverWarning {}
|
||||
property var status: backend.gazeStatus
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
@@ -703,6 +736,7 @@ Kirigami.ApplicationWindow {
|
||||
id: bluetoothPage
|
||||
Kirigami.ScrollablePage {
|
||||
title: "Bluetooth"
|
||||
header: DriverWarning {}
|
||||
actions: [
|
||||
Kirigami.Action { text: "Refresh"; icon.name: "view-refresh"; onTriggered: backend.refreshBluetooth() },
|
||||
Kirigami.Action {
|
||||
|
||||
Reference in new issue
Block a user