Skip to content

Commit

Permalink
feat: power management
Browse files Browse the repository at this point in the history
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 <iam@lach.pw>
  • Loading branch information
CertainLach committed Sep 7, 2022
1 parent 02b25f1 commit 01baba9
Show file tree
Hide file tree
Showing 21 changed files with 1,333 additions and 217 deletions.
868 changes: 804 additions & 64 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ Current implementation of driver intercepts some calls between SteamVR and commo
- HMD image - standard interface (DP) used, however there are some things missing in kernel, see patches below
- Audio output - standard interface used
- Front facing camera - works, with minor noise/distortion, standard interface (UVC) used
- Lighthouse power management
- Headset, controllers, Vive tracker (tracking) - part of original driver
- Headset/controllers firmware updates - part of original driver

### TODO

- 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
- 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
- Focus knob overlay (Some third-party may work though). Focusing does work, but there is no visual helper.
- 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
- Front facing camera noise - can be solved with some kernel tinkering (UVC driver)
Expand Down Expand Up @@ -58,9 +59,9 @@ Latest version of driver [automatically patches](https://github.com/CertainLach/

In `steamvr.vrsettings`:

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

Reconfigures helmet before startup
Reconfigures helmet to specified resolution/framerate before startup

- 0 - 2448x1224 90fps
- 1 - 2448x1224 120fps
Expand All @@ -71,17 +72,17 @@ Reconfigures helmet before startup

Similar to original vive console utility

`vivepro2.brightness`: `1-130`
`vivepro2.brightness`: `1-130`, 130 by default

Display brightness

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

`vivepro2.noiseCancel`: `true/false`
`vivepro2.noiseCancel`: `true/false`, disabled by default

Toggle built-in microphone noise canceling

Similar option exists in vive console,
Similar option exists in vive console

## Required kernel patches

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

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

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)

## Donate

I dont have enough motivation making this thing work for everyone/adding features everyone wants/needs
Expand Down
2 changes: 2 additions & 0 deletions bin/driver-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ tracing-subscriber = "0.3.9"
cppvtbl = "0.2.0"
real_c_string = "1.0.0"

valve-pm = { path = "../../crates/valve-pm" }
vive-hid = { path = "../../crates/vive-hid" }
lens-client = { path = "../../crates/lens-client" }
lens-protocol = { path = "../../crates/lens-protocol" }
openvr = { path = "../../crates/openvr" }
tokio = { version = "1.21.0", features = ["rt", "rt-multi-thread"] }

[lib]
crate-type = ["cdylib"]
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ impl IVRDisplayComponent for HmdDisplay {
#[impl_vtables(ITrackedDeviceServerDriver)]
pub struct HmdDriver {
// pub steam: Rc<SteamDevice>,
// pub vive: Rc<ViveDevice>,
pub lens: Rc<RefCell<Client>>,
pub real: &'static VtableRef<ITrackedDeviceServerDriverVtable>,
pub mode: Mode,
Expand Down
3 changes: 3 additions & 0 deletions bin/driver-proxy/src/driver/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod camera;
pub mod hmd;
pub mod server_tracked_provider;
138 changes: 138 additions & 0 deletions bin/driver-proxy/src/driver/server_tracked_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::{
cell::{Cell, RefCell},
os::raw::c_char,
sync::Mutex,
};

use crate::{
driver_context::{try_init_driver_context, DRIVER_CONTEXT},
factory::{get_hmd_driver_factory, TOKIO_RUNTIME},
setting,
settings::Setting,
try_vr,
};
use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables};
use once_cell::sync::Lazy;
use tokio::task::LocalSet;
use tracing::info;
use valve_pm::{StationCommand, StationControl, StationState};

use crate::openvr::{
EVRInitError, IServerTrackedDeviceProvider, IServerTrackedDeviceProviderVtable,
IServerTrackedDeviceProvider_Version, IVRDriverContextVtable,
};

// (name ":" "BS2" ":" "0"/"1") ** ","
const BASE_STATIONS: Setting<String> = setting!("driver_lighthouse", "PowerManagedBaseStations2");
// 0 - disabled
// 1 - sleep
// 2 - standby
const POWER_MANAGEMENT: Setting<i32> = setting!("steamvr", "basestationPowerManagement");

#[impl_vtables(IServerTrackedDeviceProvider)]
pub struct ServerTrackedProvider {
real: &'static VtableRef<IServerTrackedDeviceProviderVtable>,
stations: Mutex<Vec<StationControl>>,
standby_state: Mutex<StationState>,
}
impl IServerTrackedDeviceProvider for ServerTrackedProvider {
fn Init(
&self,
pDriverContext: *const cppvtbl::VtableRef<IVRDriverContextVtable>,
) -> EVRInitError {
let _ = try_init_driver_context(unsafe { &*pDriverContext });
let context = DRIVER_CONTEXT.get().expect("context just initialized");

let power_management = POWER_MANAGEMENT.get();
*self.standby_state.lock().expect("lock") = match power_management {
0 => StationState::Unknown,
2 => StationState::Standby,
_ => StationState::Sleeping,
};

if *self.standby_state.lock().expect("lock") != StationState::Unknown {
let _runtime = TOKIO_RUNTIME.enter();
let stations = BASE_STATIONS.get();
let stations: Vec<_> = stations
.split(",")
.filter(|s| !s.is_empty())
.filter_map(|line| {
let mut parts = line.split(":");
let name = parts.next()?;
let _bs2 = parts.next()?;
let enabled = parts.next()?;

if enabled == "1" {
Some(name.to_owned())
} else {
None
}
})
.map(|name| StationControl::new(name.to_owned(), StationState::On))
.collect();
info!("enabled power management for {} stations", stations.len());
self.stations.lock().expect("lock").extend(stations);
};

self.real.Init(
VtableRef::into_raw(HasVtable::<IVRDriverContextVtable>::get(&context)) as *const _,
)
}

fn Cleanup(&self) {
self.real.Cleanup();
info!("disconnecting from base stations");
let _runtime = TOKIO_RUNTIME.enter();
let localset = LocalSet::new();
for station in self.stations.lock().expect("lock").drain(..) {
localset.spawn_local(station.finish());
}
TOKIO_RUNTIME.block_on(localset);
}

fn GetInterfaceVersions(&self) -> *const *const c_char {
self.real.GetInterfaceVersions()
}

fn RunFrame(&self) {
self.real.RunFrame()
}

fn ShouldBlockStandbyMode(&self) -> bool {
false
}

fn EnterStandby(&self) {
self.real.EnterStandby();
info!("making station standby");
for station in self.stations.lock().expect("lock").iter_mut() {
station.send(StationCommand::SetState(
*self.standby_state.lock().expect("lock"),
))
}
}

fn LeaveStandby(&self) {
self.real.LeaveStandby();
info!("waking up base stations");
for station in self.stations.lock().expect("lock").iter_mut() {
station.send(StationCommand::SetState(StationState::On))
}
}
}

pub static SERVER_TRACKED_DEVICE_PROVIDER: Lazy<WithVtables<ServerTrackedProvider>> =
Lazy::new(|| {
info!("intializing server tracker provider");
let real = unsafe {
let factory = get_hmd_driver_factory().expect("factory should exist");
try_vr!(factory(IServerTrackedDeviceProvider_Version))
.expect("failed to obtain tracked device provider from factory")
};

WithVtables::new(ServerTrackedProvider {
real: unsafe { VtableRef::from_raw(real as *const _) },
stations: Mutex::new(vec![]),
standby_state: Mutex::new(StationState::Unknown),
})
});
18 changes: 9 additions & 9 deletions bin/driver-proxy/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ use std::{
ptr::null,
};

use crate::{Error, Result};
use crate::{server_tracked_provider::SERVER_TRACKED_DEVICE_PROVIDER, Error, Result};
use cppvtbl::{HasVtable, VtableRef};
use libloading::{Library, Symbol};
use once_cell::sync::OnceCell;
use once_cell::sync::{Lazy, OnceCell};
use tokio::runtime::Runtime;
use tracing::info;

use crate::{
openvr::{
EVRInitError, IServerTrackedDeviceProviderVtable, IServerTrackedDeviceProvider_Version,
},
server_tracked_provider::get_server_tracked_provider,
use crate::openvr::{
EVRInitError, IServerTrackedDeviceProviderVtable, IServerTrackedDeviceProvider_Version,
};

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

pub static TOKIO_RUNTIME: Lazy<Runtime> =
Lazy::new(|| Runtime::new().expect("tokio init should not fail"));

fn HmdDriverFactory_impl(iface: *const c_char) -> Result<*const c_void> {
// May be already installed
if tracing_subscriber::fmt().without_time().try_init().is_ok() {
Expand All @@ -44,10 +45,9 @@ fn HmdDriverFactory_impl(iface: *const c_char) -> Result<*const c_void> {
info!("requested interface: {ifacen:?}");

if ifacen == unsafe { CStr::from_ptr(IServerTrackedDeviceProvider_Version) } {
let provider = get_server_tracked_provider()?;
Ok(
VtableRef::into_raw(HasVtable::<IServerTrackedDeviceProviderVtable>::get(
&provider,
&SERVER_TRACKED_DEVICE_PROVIDER,
)) as *const _ as *const c_void,
)
} else {
Expand Down
15 changes: 9 additions & 6 deletions bin/driver-proxy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#![feature(never_type, try_blocks)]
#![feature(never_type, try_blocks, thread_local)]
#![allow(non_snake_case)]

#[macro_use]
extern crate openvr;

mod driver_context;
mod driver_host;
/// Wrappers for things, returned from original driver
mod driver;
pub use driver::{camera, hmd, server_tracked_provider};

/// Wrappers for things, passed from vrserver to original driver
mod server;
pub use server::{driver_context, driver_host};

#[macro_use]
mod error;
mod camera;
mod factory;
mod hmd;
mod server_tracked_provider;
#[macro_use]
mod settings;
mod log;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use std::{
ffi::{c_void, CStr},
os::raw::c_char,
ptr::null_mut,
};

use crate::{
driver_host::get_driver_host, openvr::IVRServerDriverHostVtable, try_vr, vr_result, Error,
Result,
};
use crate::{driver_host::DRIVER_HOST, openvr::IVRServerDriverHostVtable, try_vr, Result};
use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables};
use once_cell::sync::OnceCell;
use tracing::info;
Expand Down Expand Up @@ -36,13 +32,8 @@ impl IVRDriverContext for DriverContext {
let name = unsafe { CStr::from_ptr(pchInterfaceVersion) };
info!("get generic interface {name:?}");
if name == unsafe { CStr::from_ptr(IVRServerDriverHost_Version) } {
vr_result!(
result,
get_driver_host().map(|host| {
VtableRef::into_raw(HasVtable::<IVRServerDriverHostVtable>::get(host)) as *mut _
}),
null_mut()
)
VtableRef::into_raw(HasVtable::<IVRServerDriverHostVtable>::get(&*DRIVER_HOST))
as *mut _
} else {
self.real.GetGenericInterface(pchInterfaceVersion, result)
}
Expand All @@ -53,17 +44,15 @@ impl IVRDriverContext for DriverContext {
}
}

static DRIVER_CONTEXT: OnceCell<WithVtables<DriverContext>> = OnceCell::new();
pub fn try_init_driver_context(real: &'static VtableRef<IVRDriverContextVtable>) -> Result<(), ()> {
pub static DRIVER_CONTEXT: OnceCell<WithVtables<DriverContext>> = OnceCell::new();

pub fn try_init_driver_context(real: &'static VtableRef<IVRDriverContextVtable>) {
if DRIVER_CONTEXT.get().is_some() {
return Ok(());
return;
}
let context = WithVtables::new(DriverContext { real });
DRIVER_CONTEXT.set(context).map_err(|_| ())
}

pub fn get_driver_context() -> Result<&'static WithVtables<DriverContext>> {
let new_ctx = WithVtables::new(DriverContext { real });
DRIVER_CONTEXT
.get()
.ok_or_else(|| Error::Internal("driver context is not initialized yet"))
.set(new_ctx)
.map_err(|_| ())
.expect("context is not set");
}
Loading

0 comments on commit 01baba9

Please sign in to comment.