Added hybrid mode to bring back overlay dragging after regression

This commit is contained in:
Juan Arteaga Carmona committed 2026-10-04 22:46:55 +02:00
1 parent 7c9029e4ca
commit c581a70c89
6 files changed
+55 -17

No files matched your search

+10 -8
View File
@@ -15,7 +15,7 @@ An experimental SteamVR driver that lets you control the **Steam Frame's laser p
> - **It runs inside `vrserver`.** A bug in the driver can crash SteamVR. On the Frame, SteamVR is your whole session, so the headset can end up showing nothing until you remove the driver (see [Recovery](#recovery)).
> - **It grabs your mouse.** While the laser mode is on, the driver takes exclusive control of the mouse (`EVIOCGRAB`), and nothing else receives its input.
> - **It relies on undocumented behaviour.** It depends on SteamVR internals that Valve can change in any update: the compositor's `lasermouse` action set and Frame-specific bindings.
> - **No warranty.** Use it at your own risk. Read the code first; it's a single ~400-line file: [`src/driver.cpp`](src/driver.cpp).
> - **No warranty.** Use it at your own risk. Read the code first; it's a single ~580-line file: [`src/driver.cpp`](src/driver.cpp).
## What it does
@@ -26,7 +26,7 @@ This driver adds a **virtual controller** to SteamVR:
- **Direction:** aimed by mouse movement.
- **Buttons:** your mouse buttons.
The compositor then treats it like any other laser-pointing hand.
The compositor then treats it like any other laser-pointing controller.
| Mouse | Laser mode OFF (default) | Laser mode ON |
|---|---|---|
@@ -38,7 +38,7 @@ The compositor then treats it like any other laser-pointing hand.
When laser mode turns on, the ray starts where you're looking. When it's off, the virtual device stays connected but parks its ray pointing at the sky with every button released, so your real controllers keep the laser.
Since 0.5.0 the virtual device registers as a **stylus** (`/user/stylus`), not as a hand, so it doesn't take your real left or right controller's slot. This is untested on the headset; see the development log.
Since 0.5.1 the virtual device is a **stylus** (`/user/stylus`) while laser mode is off, so it doesn't take your real controllers' slots. While laser mode is on it switches to the **left hand** (`activeRole`), because SteamVR only lets hands drag overlays. The owner tested this switch with 0.5.1 and reports it works (see the development log).
## Requirements
- A Steam Frame (aarch64, SteamOS VR variant) with SteamVR at `/opt/steamvr`.
@@ -58,7 +58,7 @@ cmake -S . -B build -G Ninja && cmake --build build
Check that it loaded:
```sh
grep -a 'mouselaser:' ~/.local/share/Steam/logs/vrserver.txt | tail
# expect: "version 0.5.0-experimental", "activated as device N, role 5", "using /dev/input/eventX (<your mouse>)"
# expect: "version 0.5.1-experimental", "activated as device N, role 5", "using /dev/input/eventX (<your mouse>)"
```
### Optional: offline wheel test
@@ -81,7 +81,8 @@ Add any of these to `~/.config/openvr/config/steamvr.vrsettings` under a `"drive
| `sensitivity` | `0.05` | Degrees of ray rotation per mouse count. |
| `toggleButton` | `276` | evdev key code of the toggle: 276 = BTN_EXTRA (forward), 275 = BTN_SIDE (back). |
| `backButton` | `275` | evdev key code that sends the laser Back action while laser mode is on (275 = BTN_SIDE). `-1` disables it. |
| `role` | `5` | 5 = stylus (its own `/user/stylus` path, stays connected, doesn't collide with real controllers). 1 = left hand, 2 = right hand: these share the slot with that real controller and disconnect while laser mode is off (pre-0.5.0 behaviour). |
| `role` | `5` | Role while laser mode is off. 5 = stylus (its own `/user/stylus` path, stays connected, doesn't collide with real controllers). 1 = left hand, 2 = right hand: these share the slot with that real controller and disconnect while off (pre-0.5.0 behaviour). |
| `activeRole` | `1` | Role while laser mode is on. 1 = left hand, 2 = right hand: needed to drag overlays (a stylus drag follows your head). `0` keeps `role`. While on, the real controller of that hand loses its laser. |
| `deviceNameFilter` | `""` | Substring of the evdev mouse name. Empty means the first device with REL_X/REL_Y and BTN_LEFT. |
| `originOffsetY` | `-0.08` | Ray origin height relative to the HMD, in metres. |
| `invertY` | `false` | Invert vertical aim. |
@@ -115,12 +116,13 @@ If SteamVR won't come up properly after installing, there are four options:
| Kernel | `6.18.0-gfbdbca41fd45` |
| SteamVR | as installed at `/opt/steamvr` on 2026-10-01 |
| Mouse | MCHOSE G3 A (2.4 GHz, has BTN_SIDE/BTN_EXTRA) |
| Result | Loads, toggles, aims and clicks across overlays. Reconnects after the mouse sleeps or replugs. The wheel scrolls Steam UI lists like the thumbstick: 0.2.0 worked but felt a bit choppy. 0.3.0's smooth mode was confirmed working by the owner. |
| Result | Loads, toggles, aims and clicks across overlays. Reconnects after the mouse sleeps or replugs. The wheel scrolls Steam UI lists like the thumbstick: 0.2.0 worked but felt a bit choppy. 0.3.0's smooth mode was confirmed working by the owner. 0.5.1 (stylus while off, left hand while on) reported working by the owner, including quick off/on toggles. |
## Known limitations
- The ray starts just below your head (`originOffsetY`) and points where you aim, so you see the beam almost end-on. Expect to rely mostly on the cursor dot on overlays.
- With `role` 1 or 2, a **real controller holding the same hand role** loses its laser. The default stylus role (5) is meant to avoid this, but that hasn't been tested on the headset yet.
- The driver logs some SteamVR events (`event N device M`) as diagnostics while the stylus role is being tested.
- While laser mode is on, the **real controller of the `activeRole` hand loses its laser** (the left one by default; confirmed by the owner). It gets it back when laser mode is switched off. Set `activeRole: 2` to borrow the right hand instead. With `role` 1 or 2 this happens whenever the device is connected.
- With a stylus role active (`activeRole: 0`), the mouse laser can aim and click but **can't drag overlays**: they follow your head. SteamVR's compositor only drags with hands or the head.
- The driver logs some SteamVR events (`event N device M`) as diagnostics, including a burst of `event 111` lines at startup.
- **Wheel feel is approximate.** A wheel isn't a stick: it only sends notches. Smooth mode only *simulates* a held stick, so it still won't feel exactly like a real thumbstick. Tune it with the `wheel*` settings.
- Settings are only read when SteamVR starts.
- **Back doesn't reach the KDE desktop.** SteamVR's laser Back goes to SteamVR overlays. Steam's UI handles it, but gamescope doesn't pass it on to the windows it hosts (a real controller's B behaves the same). With laser mode off, the side buttons don't work in KDE either, because the nested KWin's X11 backend drops X buttons 8 and up. See [docs/steam-frame-background.md](docs/steam-frame-background.md).
+9 -1
View File
@@ -58,7 +58,7 @@ The broader notes from that session (hardware, display pipeline, KDE scale, ultr
14. **Back button, 0.4.0** (2026-10-04). The mouse's back side button (BTN_SIDE, setting `backButton`) is now bound to the compositor's `/actions/lasermouse/in/back`. That is the same action as the right Frame controller's B; right A is `home`. The owner reports Back works in the Steam UI and not on the KDE overlay. Investigating KDE separately (see `steam-frame-background.md`, input path) showed two causes: gamescope doesn't forward SteamVR Back to its windows, and KWin's X11 nested backend drops X buttons 8 and up even with laser mode off. The driver can't fix either one, and nothing was added for it.
15. **Stylus role, 0.5.0** (2026-10-04). Untested on the headset.
15. **Stylus role, 0.5.0** (2026-10-04). Tested in entry 17: aiming and clicking work, dragging overlays doesn't.
- Problems: (a) pressing the toggle sometimes grabbed the mouse but the laser flashed briefly or never showed, leaving neither the KDE cursor nor the laser; (b) the real left controller's laser conflicted with the virtual device.
- An earlier session tried 0.4.1 (delay before pressing the grip), 0.4.2 (stay connected) and 0.4.3 (parked valid pose while off), all as the **left hand**. The owner reverted them from git. Its capture finding is kept: a device whose pose goes invalid or disconnects is dropped as the compositor's laser pointer until a new "user interaction started", so a quick off/on fails. Staying connected as the left hand caused conflict (b).
- Change: `role` now defaults to **Stylus (5)**, whose user path is `/user/stylus` and which is never a hand. The bindings repeat every hand entry for `/user/stylus` (the HMD's own binding shows the compositor laser accepts pose sources that aren't hands). With a role other than a hand, the device stays connected with a valid pose, parked straight up while off. Hand roles keep the old disconnect-while-off behaviour. The profile's binding UI mode is `single_device`.
@@ -67,7 +67,15 @@ The broader notes from that session (hardware, display pipeline, KDE scale, ultr
16. **SteamVR primer** (2026-10-04). Added `docs/steamvr-primer.md`, an accessible overview for developers new to SteamVR. It gathers the parent exploration notes (01–09), this repo's docs and new checks on the headset. New facts recorded there: `frame_hmd` and `frame_controller` are resource-only, and the `cv` driver adds the headset and both controllers. Also the full list of compositor action sets, the controller roles and their user paths, and the `vrcmd` options.
17. **0.5.0 result: stylus can't drag overlays** (2026-10-04, owner test). With role Stylus the laser aims and clicks, but dragging the dashboard or a floating overlay makes it follow the **head** instead of the mouse. With a hand role, dragging works. Cause, from `strings vrcompositor`: the only device paths the compositor knows are `/user/hand/left`, `/user/hand/right` and `/user/head`. Overlay dragging (`CGrabTransform`) attaches to one of those devices, so a `/user/stylus` pointer falls back to the head. The driver can't change this.
18. **Hybrid role, 0.5.1** (2026-10-04). The owner chose to try this over reverting. The device registers as a stylus (`role: 5`) and, while laser mode is on, sets its `Prop_ControllerRoleHint_Int32` to `activeRole` (default 1, left hand) so it can drag overlays; it goes back to stylus when switched off. The log shows `role N` on each change, and SteamVR should emit `event 108` (role changed). Things to check: whether the role change takes effect live, whether the laser comes up straight away after it, whether dragging follows the mouse, and what happens to the real left controller's laser while laser mode is on. If the role switch fails, revert to a plain hand role and document the trade-offs.
- **On-headset test: working** (owner, after a reboot, 22:26 on 2026-10-04). The log shows `role 1` on every switch-on and `role 5` on every switch-off. SteamVR follows each with `event 108` (role changed), so it applies `Prop_ControllerRoleHint_Int32` changes live. A quick off/on (22:30:06, 0.5 s apart) kept the laser.
- Observation: SteamVR also sends `event 100`/`101` (TrackedDeviceActivated/Deactivated) for our device on every toggle, even though it stays connected. This was already the case with 0.5.0. We don't know what it means, and it causes no visible problem.
- The owner confirmed the real **left controller loses its laser while laser mode is on**, as expected: the device holds the left-hand slot. It is fine with laser mode off. `activeRole: 2` moves this to the right hand.
## State at hand-off
- The driver is registered from this repo (`<repo>/driver/mouselaser`). The old prototype registration has been removed.
- Current version: 0.5.1-experimental, tested on the headset (entry 18).
- Nothing is vendored. The build depends on SteamVR's bundled header.
- Licensed MIT (see `LICENSE`). SPDX tags are on the sources.
+3 -1
View File
@@ -13,7 +13,7 @@ vrserver
└─ RunFrame() ── pose + input components, every vrserver frame
vrcompositor
└─ uses resources/input/vrcompositor_bindings_mouselaser.json (via the profile's default_bindings)
/actions/lasermouse/in/Pointer ← /user/stylus/pose/raw (or /user/hand/{left,right} for hand roles) → the laser
/actions/lasermouse/in/Pointer ← /user/hand/left/pose/raw while on (activeRole), /user/stylus/... while off → the laser
```
### Why `alwaysActivate: true`
@@ -39,6 +39,8 @@ vrcompositor
- **hand (1/2):** `deviceIsConnected = false` and `poseIsValid = false`, so the real controller of that hand gets its slot back. A connected device with a hand role would compete with it.
### Role and user path
Since 0.5.1 the role changes with laser mode. The device registers with `role` (default stylus, 5). On each toggle, `RunFrame` sets `Prop_ControllerRoleHint_Int32` to `activeRole` (default left hand, 1) when switching on, or back to `role` when switching off, and logs `role N`. This is needed because dragging overlays only works with a hand (see below and the primer). SteamVR applies the change live: each switch is followed by `event 108` (role changed), and the owner reports laser and dragging working with 0.5.1.
SteamVR gives each controller a `/user/...` path based on its role. Hand roles share `/user/hand/left|right` with the real controllers. `TrackedControllerRole_Stylus` (5) maps to `/user/stylus`, which `IsRoleAllowedAsHand()` rules out of hand selection. Treadmill (4) maps to `/user/treadmill`. OptOut (3) has no path unless a tracker role is assigned in SteamVR. The compositor's laser accepts pose sources that aren't hands (the Frame HMD binds `/user/head/pose/raw` to `lasermouse/in/pointer`), so the bindings repeat every hand entry for `/user/stylus`.
### Event diagnostics
+8 -5
View File
@@ -212,7 +212,7 @@ A controller declares a **role**, and the role decides its **user path**. User p
`IsRoleAllowedAsHand()` in the header returns true only for Invalid, LeftHand and RightHand. Other user paths SteamVR knows about include `/user/head`, `/user/hand/secondary`, `/user/keyboard`, `/user/waist`, `/user/foot/...`, `/user/knee/...` and the Vive tracker role paths.
**Why this mattered for us:** up to 0.4.x, `mouselaser` registered as the **left hand**. Whenever it was connected it competed with your real left controller for `/user/hand/left`, and one of them lost its laser. 0.5.0 registers as a **stylus**, which has its own slot.
**Why this mattered for us:** up to 0.4.x, `mouselaser` registered as the **left hand**. Whenever it was connected it competed with your real left controller for `/user/hand/left`, and one of them lost its laser. 0.5.0 registered as a **stylus**, which has its own slot, but a stylus can't drag overlays (section 8). Since 0.5.1 it is a stylus while laser mode is off and switches to the left hand while it's on. **The role hint can be changed while SteamVR runs:** update `Prop_ControllerRoleHint_Int32`, and SteamVR re-assigns roles and sends `TrackedDeviceRoleChanged` (108).
### Activity events
SteamVR tracks whether each device is in use and sends events about it. Ones we've seen and care about:
@@ -224,7 +224,7 @@ SteamVR tracks whether each device is in use and sends events about it. Ones we'
| `TrackedDeviceRoleChanged` | 108 | Hand assignments changed. |
| `PropertyChanged` | 111 | A device property changed. Many at startup. |
These matter because **the compositor uses them to decide which device drives the laser** (section 8). A driver can see them with `VRServerDriverHost()->PollNextEvent()`. 0.5.0 logs them as `mouselaser: event N device M`.
These matter because **the compositor uses them to decide which device drives the laser** (section 8). A driver can see them with `VRServerDriverHost()->PollNextEvent()`. Since 0.5.0 the driver logs them as `mouselaser: event N device M`. One unexplained observation: our device gets 100/101 on every toggle even though it stays connected.
---
@@ -297,6 +297,9 @@ This is the least documented part. It's all inferred from behaviour, logs and st
- **Observed (0.4.x captures):** a device that **disconnects or reports an invalid pose is dropped as the pointer device**, and isn't picked up again until SteamVR sees a new "user interaction started". That took about 10 s of quiet. A quick off/on toggle therefore left the mouse grabbed with no laser. That's why 0.5.0 keeps the device connected with a valid pose (parked pointing at the sky) while laser mode is off.
- The compositor also has settings like `modalGamepadAndLaser` and `laserMouseDebugging` in SteamVR's default settings. We haven't experimented with them.
### Dragging overlays needs a hand (or the head)
The laser's *aim* comes from the `Pointer` action, which any user path can feed. **Grabbing and moving** an overlay (the dashboard, floating windows) is done by the compositor's scene graph (`CGrabTransform`), which attaches the overlay to a *device*. The only device paths in `vrcompositor` are `/user/hand/left`, `/user/hand/right` and `/user/head`. A `/user/stylus` pointer can aim and click, but a drag follows the **head**. Tested with 0.5.0, see the development log, entry 17. That's why 0.5.1 becomes a hand while laser mode is on.
### Other ways into the laser (considered, not used)
- **The `lasermouse` mailbox.** SteamVR has an internal message bus served by vrserver as a WebSocket on `ws://127.0.0.1:27062` (local only). The compositor listens on a mailbox called `lasermouse` with messages such as `dump_laser_overlays`, `force_activate_laser_mouse` and `remote_laser_mouse_events`. The last one is how **VRLink** (PC↔headset streaming) sends laser input. Its payload is an undocumented protobuf, so it's a dead end without reverse engineering. Protocol details are in [steam-frame-background.md](steam-frame-background.md).
- **Gamescope flags** (`--mouse-sensitivity`, `--force-grab-cursor`). These only change behaviour *inside* one overlay.
@@ -344,7 +347,7 @@ mouse ─► /dev/input/event5 (evdev)
With all of the above, the driver is short. Details are in [how-it-works.md](how-it-works.md).
1. **Provider** (`alwaysActivate` driver) adds one device: class `Controller`, controller type `mouselaser`, role **Stylus** (since 0.5.0).
1. **Provider** (`alwaysActivate` driver) adds one device: class `Controller`, controller type `mouselaser`. Its role is **Stylus** while laser mode is off and **left hand** while it's on (0.5.1, to allow dragging overlays).
2. **Mouse thread** finds the first evdev device with relative X/Y and a left button. The forward side button toggles laser mode and `EVIOCGRAB`.
3. **Every frame** (`RunFrame`):
- Position = HMD position, a little lower (`originOffsetY`).
@@ -363,7 +366,7 @@ With all of the above, the driver is short. Details are in [how-it-works.md](how
| **Bump the version string on every change.** | It's the only quick way to know which build is running. We once lost track: git was reverted but the old `.so` was still in place, and the log showed `0.4.3` while the source said `0.4.0`. The `.so` is gitignored, so reverting git doesn't touch it. |
| **A crash in the driver takes SteamVR down.** | The driver runs inside `vrserver`. Keep SSH/RDP ready, know the [recovery steps](../README.md#recovery), and never let a joinable `std::thread` be destroyed (that calls `std::terminate`). |
| **Don't disconnect or invalidate the pointer device to "turn it off".** | The compositor drops it as the laser pointer, and only takes it back after a new user interaction (about 10 s of quiet). |
| **Don't register as a hand unless you want to replace that hand.** | Hand slots are exclusive; the real controller loses its laser. |
| **Don't register as a hand unless you want to replace that hand.** | Hand slots are exclusive; the real controller loses its laser. If you need hand-only features, such as dragging overlays, switch the role hint to a hand only while you need it. |
| **Saved user bindings beat the driver's defaults.** | If a binding change "does nothing", look for a saved copy. |
| **Benign log noise:** `Driver mouselaser has no suitable devices`, and `steam.client (mouselaser) has no configured binding`. | The first is logged because our driver provides no HMD; the device is added right after. The second, because only compositor bindings are shipped (Steam's own Frame binding is haptics only anyway). |
| **SteamVR adds the driver name to log lines itself.** | Logging `"mouselaser: ..."` yourself gives `mouselaser: mouselaser: ...`. |
@@ -447,7 +450,7 @@ XDG_RUNTIME_DIR=/run/user/1000 systemctl --user status gamescope-session
---
## 15. Open questions
- **Does the compositor accept a `/user/stylus` pointer?** 0.5.0's first run shows it toggling and receiving interaction events, but the on-headset verdict is pending. See [development-log.md](development-log.md).
- **Stylus role:** the compositor accepts a `/user/stylus` pointer for aiming and clicking, but not for dragging overlays (see section 8). Switching role at runtime (stylus while off, hand while on) works (0.5.1). The cost: while laser mode is on, the real left controller loses its laser, because the mouse holds the left-hand slot.
- **Exactly how the compositor picks the pointer device.** Is it interaction events, `SwitchLaserHand`, or something else? The 0.5.0 event logging is meant to answer this.
- **Is there a driver-side signal for "the laser is up"?** It would let the driver grab the mouse only when the laser actually works.
- **The mailbox.** Does it need a `?secret=`, and what does `dump_laser_overlays` return?
@@ -4,6 +4,7 @@
"sensitivity": 0.05,
"toggleButton": 276,
"role": 5,
"activeRole": 1,
"deviceNameFilter": "",
"originOffsetY": -0.08,
"invertY": false,
+24 -2
View File
@@ -6,7 +6,8 @@
// mouse is grabbed (EVIOCGRAB) so gamescope stops seeing it; while inactive the device
// stays connected (so the compositor keeps it as a pointer source), but parks its ray
// pointing at the sky and releases every button.
// It registers as a stylus, not a hand, so it never takes a real controller's hand slot.
// It is a stylus while off, so it never takes a real controller's hand slot, and switches to
// a hand role while on, because the compositor only drags overlays with hands (or the head).
//
// EXPERIMENTAL and AI-generated ("vibecoded"); see README.md before relying on it.
@@ -33,7 +34,7 @@
using namespace vr;
static const char *k_section = "driver_mouselaser";
static const char *k_version = "0.5.0-experimental";
static const char *k_version = "0.5.1-experimental";
static void Log(const char *fmt, ...)
{
@@ -60,6 +61,9 @@ struct Settings
// Stylus (5) gets its own /user/stylus path. A hand role (1/2) shares the slot with the
// real controller of that hand, so the device disconnects while off (pre-0.5.0 behaviour).
int role = TrackedControllerRole_Stylus;
// Role while laser mode is on (0 = keep `role`). The compositor attaches dragged overlays
// to /user/hand/{left,right} or /user/head only, so a stylus drag follows the head.
int activeRole = TrackedControllerRole_LeftHand;
std::string nameFilter; // substring of the evdev name; empty = first mouse found
float originOffsetY = -0.08f; // metres, world space, relative to the HMD
bool invertY = false;
@@ -88,6 +92,8 @@ struct Settings
if (err == VRSettingsError_None) backButton = i;
i = s->GetInt32(k_section, "role", &err);
if (err == VRSettingsError_None) role = i;
i = s->GetInt32(k_section, "activeRole", &err);
if (err == VRSettingsError_None) activeRole = i;
char buf[256] = {};
s->GetString(k_section, "deviceNameFilter", buf, sizeof(buf), &err);
if (err == VRSettingsError_None) nameFilter = buf;
@@ -359,12 +365,14 @@ public:
{
m_id = id;
PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(id);
m_container = c;
VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, "Mouse Laser (virtual)");
VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, "frame-unboundedMouse-vibed");
VRProperties()->SetStringProperty(c, Prop_ControllerType_String, "mouselaser");
VRProperties()->SetStringProperty(c, Prop_InputProfilePath_String, "{mouselaser}/input/mouselaser_profile.json");
VRProperties()->SetStringProperty(c, Prop_RenderModelName_String, "{mouselaser}mouselaser_none");
VRProperties()->SetInt32Property(c, Prop_ControllerRoleHint_Int32, m_settings.role);
m_currentRole = m_settings.role;
VRProperties()->SetBoolProperty(c, Prop_DeviceProvidesBatteryStatus_Bool, false);
VRDriverInput()->CreateBooleanComponent(c, "/input/trigger/click", &m_trigger);
@@ -421,6 +429,17 @@ public:
// invalid is dropped as the laser pointer until a new "user interaction" (about 10 s of
// quiet), so a quick off/on would grab the mouse with no laser.
bool present = active || IsRoleAllowedAsHand(ETrackedControllerRole(m_settings.role));
if (active != m_wasActive)
{
m_wasActive = active;
int role = active && m_settings.activeRole > 0 ? m_settings.activeRole : m_settings.role;
if (role != m_currentRole)
{
m_currentRole = role;
VRProperties()->SetInt32Property(m_container, Prop_ControllerRoleHint_Int32, role);
Log("role %d\n", role);
}
}
if (m_mouse.toggled.exchange(false) && active && hmd.bPoseIsValid)
{
// Start the ray where the user is looking.
@@ -479,6 +498,9 @@ private:
Settings m_settings;
MouseReader m_mouse;
uint32_t m_id = k_unTrackedDeviceIndexInvalid;
PropertyContainerHandle_t m_container = k_ulInvalidPropertyContainer;
bool m_wasActive = false;
int m_currentRole = 0;
std::mutex m_poseMutex;
DriverPose_t m_pose = {};
float m_yaw = 0, m_pitch = 0;