Skip to content

Commit 01baba9

Browse files
committed
feat: power management
I'm not sure why power management is still not supported on linux, it was pretty easy to implement, and I'm not sure if I missing something besides correct status display inside of steamvr GUI Anyway, now this feature just works, you should have any bluetooth adapter present in system (Builtin vive is not always detected for me), and then configure power management using standard GUI Fixes: ValveSoftware/SteamVR-for-Linux#320 Signed-off-by: Yaroslav Bolyukin <[email protected]>
1 parent 02b25f1 commit 01baba9

21 files changed

+1333
-217
lines changed

Cargo.lock

+804-64
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ Current implementation of driver intercepts some calls between SteamVR and commo
1515
- HMD image - standard interface (DP) used, however there are some things missing in kernel, see patches below
1616
- Audio output - standard interface used
1717
- Front facing camera - works, with minor noise/distortion, standard interface (UVC) used
18+
- Lighthouse power management
1819
- Headset, controllers, Vive tracker (tracking) - part of original driver
1920
- Headset/controllers firmware updates - part of original driver
2021

2122
### TODO
2223

23-
- Configuration utilities - most of reconfiguration (resolution, noise cancelation, brightness) abilities are already reverse-engineered, they just aren't easly configurable, some GUI utility should be written
24+
- Configuration utilities - most of reconfiguration (resolution, noise cancelation, brightness, lighthouse power management) abilities are already reverse-engineered, they just aren't easly configurable, some GUI utility should be written
2425
- Focus knob overlay (Some third-party may work though). Focusing does work, but there is no visual helper.
2526
- Audio output is not targeted to correct device yet (You need to manually switch it every time), it should be possible to implement this feature in this driver however
2627
- Front facing camera noise - can be solved with some kernel tinkering (UVC driver)
@@ -58,9 +59,9 @@ Latest version of driver [automatically patches](https://github.com/CertainLach/
5859

5960
In `steamvr.vrsettings`:
6061

61-
`vivepro2.resolution`: `0-5`
62+
`vivepro2.resolution`: `0-5`, 0 by default, to make it most compatible with every hardware
6263

63-
Reconfigures helmet before startup
64+
Reconfigures helmet to specified resolution/framerate before startup
6465

6566
- 0 - 2448x1224 90fps
6667
- 1 - 2448x1224 120fps
@@ -71,17 +72,17 @@ Reconfigures helmet before startup
7172

7273
Similar to original vive console utility
7374

74-
`vivepro2.brightness`: `1-130`
75+
`vivepro2.brightness`: `1-130`, 130 by default
7576

7677
Display brightness
7778

7879
Original vive console seems to fade from 0 to 130 on start, and then no longer touch this setting
7980

80-
`vivepro2.noiseCancel`: `true/false`
81+
`vivepro2.noiseCancel`: `true/false`, disabled by default
8182

8283
Toggle built-in microphone noise canceling
8384

84-
Similar option exists in vive console,
85+
Similar option exists in vive console
8586

8687
## Required kernel patches
8788

@@ -102,6 +103,8 @@ boot.kernelPatches = vivepro2-linux-driver.kernelPatches;
102103

103104
If you use arch btw, then you can use this kernel package with all required patches applied (i have not tested it, and can't provide any guarantees about contents of this repo): https://github.com/santeri3700/vive-pro-2-on-linux
104105

106+
I don't recommend using other distributions, because it will be harder, because of usage of bleeding-edge kernel, but it should work, and I will fix any issues with them (I.e I have fixed usage of this driver on ubuntu)
107+
105108
## Donate
106109

107110
I dont have enough motivation making this thing work for everyone/adding features everyone wants/needs

bin/driver-proxy/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ tracing-subscriber = "0.3.9"
1414
cppvtbl = "0.2.0"
1515
real_c_string = "1.0.0"
1616

17+
valve-pm = { path = "../../crates/valve-pm" }
1718
vive-hid = { path = "../../crates/vive-hid" }
1819
lens-client = { path = "../../crates/lens-client" }
1920
lens-protocol = { path = "../../crates/lens-protocol" }
2021
openvr = { path = "../../crates/openvr" }
22+
tokio = { version = "1.21.0", features = ["rt", "rt-multi-thread"] }
2123

2224
[lib]
2325
crate-type = ["cdylib"]

bin/driver-proxy/src/hmd.rs renamed to bin/driver-proxy/src/driver/hmd.rs

-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ impl IVRDisplayComponent for HmdDisplay {
140140
#[impl_vtables(ITrackedDeviceServerDriver)]
141141
pub struct HmdDriver {
142142
// pub steam: Rc<SteamDevice>,
143-
// pub vive: Rc<ViveDevice>,
144143
pub lens: Rc<RefCell<Client>>,
145144
pub real: &'static VtableRef<ITrackedDeviceServerDriverVtable>,
146145
pub mode: Mode,

bin/driver-proxy/src/driver/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod camera;
2+
pub mod hmd;
3+
pub mod server_tracked_provider;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use std::{
2+
cell::{Cell, RefCell},
3+
os::raw::c_char,
4+
sync::Mutex,
5+
};
6+
7+
use crate::{
8+
driver_context::{try_init_driver_context, DRIVER_CONTEXT},
9+
factory::{get_hmd_driver_factory, TOKIO_RUNTIME},
10+
setting,
11+
settings::Setting,
12+
try_vr,
13+
};
14+
use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables};
15+
use once_cell::sync::Lazy;
16+
use tokio::task::LocalSet;
17+
use tracing::info;
18+
use valve_pm::{StationCommand, StationControl, StationState};
19+
20+
use crate::openvr::{
21+
EVRInitError, IServerTrackedDeviceProvider, IServerTrackedDeviceProviderVtable,
22+
IServerTrackedDeviceProvider_Version, IVRDriverContextVtable,
23+
};
24+
25+
// (name ":" "BS2" ":" "0"/"1") ** ","
26+
const BASE_STATIONS: Setting<String> = setting!("driver_lighthouse", "PowerManagedBaseStations2");
27+
// 0 - disabled
28+
// 1 - sleep
29+
// 2 - standby
30+
const POWER_MANAGEMENT: Setting<i32> = setting!("steamvr", "basestationPowerManagement");
31+
32+
#[impl_vtables(IServerTrackedDeviceProvider)]
33+
pub struct ServerTrackedProvider {
34+
real: &'static VtableRef<IServerTrackedDeviceProviderVtable>,
35+
stations: Mutex<Vec<StationControl>>,
36+
standby_state: Mutex<StationState>,
37+
}
38+
impl IServerTrackedDeviceProvider for ServerTrackedProvider {
39+
fn Init(
40+
&self,
41+
pDriverContext: *const cppvtbl::VtableRef<IVRDriverContextVtable>,
42+
) -> EVRInitError {
43+
let _ = try_init_driver_context(unsafe { &*pDriverContext });
44+
let context = DRIVER_CONTEXT.get().expect("context just initialized");
45+
46+
let power_management = POWER_MANAGEMENT.get();
47+
*self.standby_state.lock().expect("lock") = match power_management {
48+
0 => StationState::Unknown,
49+
2 => StationState::Standby,
50+
_ => StationState::Sleeping,
51+
};
52+
53+
if *self.standby_state.lock().expect("lock") != StationState::Unknown {
54+
let _runtime = TOKIO_RUNTIME.enter();
55+
let stations = BASE_STATIONS.get();
56+
let stations: Vec<_> = stations
57+
.split(",")
58+
.filter(|s| !s.is_empty())
59+
.filter_map(|line| {
60+
let mut parts = line.split(":");
61+
let name = parts.next()?;
62+
let _bs2 = parts.next()?;
63+
let enabled = parts.next()?;
64+
65+
if enabled == "1" {
66+
Some(name.to_owned())
67+
} else {
68+
None
69+
}
70+
})
71+
.map(|name| StationControl::new(name.to_owned(), StationState::On))
72+
.collect();
73+
info!("enabled power management for {} stations", stations.len());
74+
self.stations.lock().expect("lock").extend(stations);
75+
};
76+
77+
self.real.Init(
78+
VtableRef::into_raw(HasVtable::<IVRDriverContextVtable>::get(&context)) as *const _,
79+
)
80+
}
81+
82+
fn Cleanup(&self) {
83+
self.real.Cleanup();
84+
info!("disconnecting from base stations");
85+
let _runtime = TOKIO_RUNTIME.enter();
86+
let localset = LocalSet::new();
87+
for station in self.stations.lock().expect("lock").drain(..) {
88+
localset.spawn_local(station.finish());
89+
}
90+
TOKIO_RUNTIME.block_on(localset);
91+
}
92+
93+
fn GetInterfaceVersions(&self) -> *const *const c_char {
94+
self.real.GetInterfaceVersions()
95+
}
96+
97+
fn RunFrame(&self) {
98+
self.real.RunFrame()
99+
}
100+
101+
fn ShouldBlockStandbyMode(&self) -> bool {
102+
false
103+
}
104+
105+
fn EnterStandby(&self) {
106+
self.real.EnterStandby();
107+
info!("making station standby");
108+
for station in self.stations.lock().expect("lock").iter_mut() {
109+
station.send(StationCommand::SetState(
110+
*self.standby_state.lock().expect("lock"),
111+
))
112+
}
113+
}
114+
115+
fn LeaveStandby(&self) {
116+
self.real.LeaveStandby();
117+
info!("waking up base stations");
118+
for station in self.stations.lock().expect("lock").iter_mut() {
119+
station.send(StationCommand::SetState(StationState::On))
120+
}
121+
}
122+
}
123+
124+
pub static SERVER_TRACKED_DEVICE_PROVIDER: Lazy<WithVtables<ServerTrackedProvider>> =
125+
Lazy::new(|| {
126+
info!("intializing server tracker provider");
127+
let real = unsafe {
128+
let factory = get_hmd_driver_factory().expect("factory should exist");
129+
try_vr!(factory(IServerTrackedDeviceProvider_Version))
130+
.expect("failed to obtain tracked device provider from factory")
131+
};
132+
133+
WithVtables::new(ServerTrackedProvider {
134+
real: unsafe { VtableRef::from_raw(real as *const _) },
135+
stations: Mutex::new(vec![]),
136+
standby_state: Mutex::new(StationState::Unknown),
137+
})
138+
});

bin/driver-proxy/src/factory.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ use std::{
44
ptr::null,
55
};
66

7-
use crate::{Error, Result};
7+
use crate::{server_tracked_provider::SERVER_TRACKED_DEVICE_PROVIDER, Error, Result};
88
use cppvtbl::{HasVtable, VtableRef};
99
use libloading::{Library, Symbol};
10-
use once_cell::sync::OnceCell;
10+
use once_cell::sync::{Lazy, OnceCell};
11+
use tokio::runtime::Runtime;
1112
use tracing::info;
1213

13-
use crate::{
14-
openvr::{
15-
EVRInitError, IServerTrackedDeviceProviderVtable, IServerTrackedDeviceProvider_Version,
16-
},
17-
server_tracked_provider::get_server_tracked_provider,
14+
use crate::openvr::{
15+
EVRInitError, IServerTrackedDeviceProviderVtable, IServerTrackedDeviceProvider_Version,
1816
};
1917

2018
pub type HmdDriverFactory =
@@ -33,6 +31,9 @@ pub fn get_hmd_driver_factory() -> Result<&'static Symbol<'static, HmdDriverFact
3331
})
3432
}
3533

34+
pub static TOKIO_RUNTIME: Lazy<Runtime> =
35+
Lazy::new(|| Runtime::new().expect("tokio init should not fail"));
36+
3637
fn HmdDriverFactory_impl(iface: *const c_char) -> Result<*const c_void> {
3738
// May be already installed
3839
if tracing_subscriber::fmt().without_time().try_init().is_ok() {
@@ -44,10 +45,9 @@ fn HmdDriverFactory_impl(iface: *const c_char) -> Result<*const c_void> {
4445
info!("requested interface: {ifacen:?}");
4546

4647
if ifacen == unsafe { CStr::from_ptr(IServerTrackedDeviceProvider_Version) } {
47-
let provider = get_server_tracked_provider()?;
4848
Ok(
4949
VtableRef::into_raw(HasVtable::<IServerTrackedDeviceProviderVtable>::get(
50-
&provider,
50+
&SERVER_TRACKED_DEVICE_PROVIDER,
5151
)) as *const _ as *const c_void,
5252
)
5353
} else {

bin/driver-proxy/src/lib.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
#![feature(never_type, try_blocks)]
1+
#![feature(never_type, try_blocks, thread_local)]
22
#![allow(non_snake_case)]
33

44
#[macro_use]
55
extern crate openvr;
66

7-
mod driver_context;
8-
mod driver_host;
7+
/// Wrappers for things, returned from original driver
8+
mod driver;
9+
pub use driver::{camera, hmd, server_tracked_provider};
10+
11+
/// Wrappers for things, passed from vrserver to original driver
12+
mod server;
13+
pub use server::{driver_context, driver_host};
14+
915
#[macro_use]
1016
mod error;
11-
mod camera;
1217
mod factory;
13-
mod hmd;
14-
mod server_tracked_provider;
1518
#[macro_use]
1619
mod settings;
1720
mod log;

bin/driver-proxy/src/driver_context.rs renamed to bin/driver-proxy/src/server/driver_context.rs

+11-22
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
use std::{
22
ffi::{c_void, CStr},
33
os::raw::c_char,
4-
ptr::null_mut,
54
};
65

7-
use crate::{
8-
driver_host::get_driver_host, openvr::IVRServerDriverHostVtable, try_vr, vr_result, Error,
9-
Result,
10-
};
6+
use crate::{driver_host::DRIVER_HOST, openvr::IVRServerDriverHostVtable, try_vr, Result};
117
use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables};
128
use once_cell::sync::OnceCell;
139
use tracing::info;
@@ -36,13 +32,8 @@ impl IVRDriverContext for DriverContext {
3632
let name = unsafe { CStr::from_ptr(pchInterfaceVersion) };
3733
info!("get generic interface {name:?}");
3834
if name == unsafe { CStr::from_ptr(IVRServerDriverHost_Version) } {
39-
vr_result!(
40-
result,
41-
get_driver_host().map(|host| {
42-
VtableRef::into_raw(HasVtable::<IVRServerDriverHostVtable>::get(host)) as *mut _
43-
}),
44-
null_mut()
45-
)
35+
VtableRef::into_raw(HasVtable::<IVRServerDriverHostVtable>::get(&*DRIVER_HOST))
36+
as *mut _
4637
} else {
4738
self.real.GetGenericInterface(pchInterfaceVersion, result)
4839
}
@@ -53,17 +44,15 @@ impl IVRDriverContext for DriverContext {
5344
}
5445
}
5546

56-
static DRIVER_CONTEXT: OnceCell<WithVtables<DriverContext>> = OnceCell::new();
57-
pub fn try_init_driver_context(real: &'static VtableRef<IVRDriverContextVtable>) -> Result<(), ()> {
47+
pub static DRIVER_CONTEXT: OnceCell<WithVtables<DriverContext>> = OnceCell::new();
48+
49+
pub fn try_init_driver_context(real: &'static VtableRef<IVRDriverContextVtable>) {
5850
if DRIVER_CONTEXT.get().is_some() {
59-
return Ok(());
51+
return;
6052
}
61-
let context = WithVtables::new(DriverContext { real });
62-
DRIVER_CONTEXT.set(context).map_err(|_| ())
63-
}
64-
65-
pub fn get_driver_context() -> Result<&'static WithVtables<DriverContext>> {
53+
let new_ctx = WithVtables::new(DriverContext { real });
6654
DRIVER_CONTEXT
67-
.get()
68-
.ok_or_else(|| Error::Internal("driver context is not initialized yet"))
55+
.set(new_ctx)
56+
.map_err(|_| ())
57+
.expect("context is not set");
6958
}

0 commit comments

Comments
 (0)