protobuf parser, read metrics, move async executor to wlx_common

This commit is contained in:
Aleksander 2026-02-24 19:05:29 +01:00
parent 41c6f43a3c
commit 0f6f344c97
29 changed files with 612 additions and 51 deletions

80
Cargo.lock generated
View File

@ -2855,8 +2855,7 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libmonado" name = "libmonado"
version = "1.6.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/oo8dev/libmonado-rs.git?rev=fc39940a64dea2df080a0d2c974c7d651006241f#fc39940a64dea2df080a0d2c974c7d651006241f"
checksum = "0df3183760954d877b894d42f5c6954837d8a7d7bd5f042409c4175afeaf24ea"
dependencies = [ dependencies = [
"dlopen2", "dlopen2",
"flagset", "flagset",
@ -3108,6 +3107,12 @@ dependencies = [
"pxfm", "pxfm",
] ]
[[package]]
name = "multimap"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.14" version = "0.2.14"
@ -3905,6 +3910,17 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap 2.12.1",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.12.1" version = "0.12.1"
@ -4167,6 +4183,64 @@ dependencies = [
"syn 2.0.113", "syn 2.0.113",
] ]
[[package]]
name = "prost"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-build"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
dependencies = [
"heck 0.5.0",
"itertools 0.14.0",
"log",
"multimap",
"petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn 2.0.113",
"tempfile",
]
[[package]]
name = "prost-derive"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
dependencies = [
"anyhow",
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.113",
]
[[package]]
name = "prost-types"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
dependencies = [
"prost",
]
[[package]]
name = "prost_build"
version = "0.1.0"
dependencies = [
"prost-build",
]
[[package]] [[package]]
name = "pure-rust-locales" name = "pure-rust-locales"
version = "0.8.2" version = "0.8.2"
@ -6293,6 +6367,7 @@ dependencies = [
"mint", "mint",
"openxr", "openxr",
"ovr_overlay", "ovr_overlay",
"prost",
"pure-rust-locales", "pure-rust-locales",
"regex", "regex",
"rosc", "rosc",
@ -6305,6 +6380,7 @@ dependencies = [
"slotmap", "slotmap",
"smallvec", "smallvec",
"smithay", "smithay",
"smol",
"strum", "strum",
"sysinfo", "sysinfo",
"thiserror 2.0.17", "thiserror 2.0.17",

View File

@ -30,6 +30,7 @@ members = [
"dash-frontend", "dash-frontend",
"wayvr-ipc", "wayvr-ipc",
"wayvrctl", "wayvrctl",
"scripts/prost_build",
] ]
resolver = "3" resolver = "3"
@ -38,6 +39,7 @@ vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
smol = "2.0.2"
glam = { version = "0.30.9", features = ["mint", "serde"] } glam = { version = "0.30.9", features = ["mint", "serde"] }
clap = { version = "4.5.53", features = ["derive"] } clap = { version = "4.5.53", features = ["derive"] }
xdg = "3.0.0" xdg = "3.0.0"

View File

@ -23,7 +23,7 @@ strum.workspace = true
chrono = "0.4.42" chrono = "0.4.42"
keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" } keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" }
smol = "2.0.2" smol = { workspace = true }
hyper = { version = "1.8.1", features = ["client", "http1", "http2"] } hyper = { version = "1.8.1", features = ["client", "http1", "http2"] }
http-body-util = "0.1.3" http-body-util = "0.1.3"
async-native-tls = "0.5.0" async-native-tls = "0.5.0"

View File

@ -17,6 +17,7 @@ use wgui::{
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
}; };
use wlx_common::{ use wlx_common::{
async_executor::AsyncExecutor,
audio, audio,
dash_interface::{BoxDashInterface, RecenterMode}, dash_interface::{BoxDashInterface, RecenterMode},
locale::WayVRLangProvider, locale::WayVRLangProvider,
@ -29,7 +30,6 @@ use crate::{
util::{ util::{
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams}, popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
toast_manager::ToastManager, toast_manager::ToastManager,
various::AsyncExecutor,
}, },
views, views,
}; };

View File

@ -1,8 +1,8 @@
use anyhow::Context; use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use wlx_common::cache_dir; use wlx_common::{async_executor::AsyncExecutor, cache_dir};
use crate::util::{http_client, steam_utils::AppID, various::AsyncExecutor}; use crate::util::{http_client, steam_utils::AppID};
pub struct CoverArt { pub struct CoverArt {
// can be empty in case if data couldn't be fetched (use a fallback image then) // can be empty in case if data couldn't be fetched (use a fallback image then)

View File

@ -13,8 +13,7 @@ use smol::{net::TcpStream, prelude::*};
use std::convert::TryInto; use std::convert::TryInto;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use wlx_common::async_executor::AsyncExecutor;
use crate::util::various::AsyncExecutor;
pub struct HttpClientResponse { pub struct HttpClientResponse {
pub data: Vec<u8>, pub data: Vec<u8>,
} }

View File

@ -4,5 +4,4 @@ pub mod pactl_wrapper;
pub mod popup_manager; pub mod popup_manager;
pub mod steam_utils; pub mod steam_utils;
pub mod toast_manager; pub mod toast_manager;
pub mod various;
pub mod wgui_simple; pub mod wgui_simple;

View File

@ -25,11 +25,11 @@ use wgui::{
util::WLength, util::WLength,
}, },
}; };
use wlx_common::async_executor::AsyncExecutor;
use crate::util::{ use crate::util::{
cached_fetcher::{self, CoverArt}, cached_fetcher::{self, CoverArt},
steam_utils::{self, AppID}, steam_utils::{self, AppID},
various::AsyncExecutor,
}; };
pub struct ViewCommon { pub struct ViewCommon {

View File

@ -5,7 +5,6 @@ use crate::{
util::{ util::{
cached_fetcher::{self, CoverArt}, cached_fetcher::{self, CoverArt},
steam_utils::{self, AppID, AppManifest}, steam_utils::{self, AppID, AppManifest},
various::AsyncExecutor,
}, },
views::game_cover, views::game_cover,
}; };
@ -19,6 +18,7 @@ use wgui::{
task::Tasks, task::Tasks,
widget::{ConstructEssentials, label::WidgetLabel}, widget::{ConstructEssentials, label::WidgetLabel},
}; };
use wlx_common::async_executor::AsyncExecutor;
#[derive(Clone)] #[derive(Clone)]
enum Task { enum Task {

View File

@ -13,6 +13,7 @@ use wgui::{
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
}, },
}; };
use wlx_common::async_executor::AsyncExecutor;
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks}, frontend::{FrontendTask, FrontendTasks},
@ -20,7 +21,6 @@ use crate::{
cached_fetcher::CoverArt, cached_fetcher::CoverArt,
popup_manager::{MountPopupParams, PopupHandle}, popup_manager::{MountPopupParams, PopupHandle},
steam_utils::{self, AppID, SteamUtils}, steam_utils::{self, AppID, SteamUtils},
various::AsyncExecutor,
}, },
views::{self, game_cover, game_launcher}, views::{self, game_cover, game_launcher},
}; };

View File

@ -0,0 +1,7 @@
[package]
name = "prost_build"
version = "0.1.0"
edition = "2024"
[dependencies]
prost-build = "0.14.3"

View File

@ -0,0 +1,5 @@
use std::io::Result;
fn main() -> Result<()> {
prost_build::compile_protos(&["src/monado_metrics.proto"], &["src/"])?;
Ok(())
}

View File

@ -0,0 +1,174 @@
syntax = "proto3";
package monado_metrics;
message Version
{
uint32 major = 1;
uint32 minor = 2;
}
message SessionFrame
{
//! Which session this frame belongs to.
int64 session_id = 1;
//! ID of frame.
int64 frame_id = 2;
//! How long we thought the frame would take.
uint64 predicted_frame_time_ns = 3;
//! When we predicted the app should wake up.
uint64 predicted_wake_up_time_ns = 4;
//! When the client's GPU work should have completed.
uint64 predicted_gpu_done_time_ns = 5;
//! When we predicted this frame to be shown.
uint64 predicted_display_time_ns = 6;
//! The selected display period.
uint64 predicted_display_period_ns = 7;
/*!
* When the app told us to display this frame, can be different
* then the predicted display time so we track that separately.
*/
uint64 display_time_ns = 8;
//! When the predict call was made (inside of xrWaitFrame).
uint64 when_predicted_ns = 9;
//! When the waiting thread was woken up.
uint64 when_wait_woke_ns = 10;
//! When xrBeginFrame was called.
uint64 when_begin_ns = 11;
//! When the layer information was delivered, (inside of xrEndFrame).
uint64 when_delivered_ns = 12;
//! When the scheduled GPU work was completed.
uint64 when_gpu_done_ns = 13;
//! Was this frame discarded.
bool discarded = 14;
}
message Used
{
//! Owning session of the frame.
int64 session_id = 1;
//! Which session frame was used by the compositor.
int64 session_frame_id = 2;
//! The system frame that the session was used by.
int64 system_frame_id = 3;
//! When the frame was latched.
uint64 when_ns = 4;
}
message SystemFrame
{
//! ID of frame.
int64 frame_id = 1;
//! Projected pixels to photon time.
uint64 predicted_display_time_ns = 2;
//! Current period of displaying of frames.
uint64 predicted_display_period_ns = 3;
//! When the compositor should hand pixels to display hardware.
uint64 desired_present_time_ns = 4;
//! The time that the compositor should wake up.
uint64 wake_up_time_ns = 5;
uint64 present_slop_ns = 6;
}
/*!
* Out of band info about a GPU timing from a system compositor.
*/
message SystemGpuInfo
{
//! ID of frame.
int64 frame_id = 1;
//! Start of GPU work.
uint64 gpu_start_ns = 2;
//! End of GPU work.
uint64 gpu_end_ns = 3;
//! When the information was gathered.
uint64 when_ns = 4;
}
/*!
* Information that comes from the vulkan present timing.
*/
message SystemPresentInfo
{
//! ID of frame.
int64 frame_id = 1;
//! The total expected time for the compositor to complete a frame.
uint64 expected_comp_time_ns = 2;
//! The time we predicted the compositor to wake up time.
uint64 predicted_wake_up_time_ns = 3;
//! Predicted time for completion of the GPU work.
uint64 predicted_done_time_ns = 4;
//! Predicted display time.
uint64 predicted_display_time_ns = 5;
//! When was this frame predicted.
uint64 when_predict_ns = 6;
//! When we last woke up the compositor after its equivalent of wait_frame.
uint64 when_woke_ns = 7;
//! When the compositor started rendering a frame
uint64 when_began_ns = 8;
//! When the compositor finished rendering a frame
uint64 when_submitted_ns = 9;
//! When new frame timing info was last added.
uint64 when_infoed_ns = 10;
//! When we wanted to start presenting.
uint64 desired_present_time_ns = 11;
//! The slop used for this frame.
uint64 present_slop_ns = 12;
//! Margin of GPU to present time.
uint64 present_margin_ns = 13;
//! When the present time actually happened.
uint64 actual_present_time_ns = 14;
//! The earliest we could have presented.
uint64 earliest_present_time_ns = 15;
}
message Record
{
oneof record {
Version version = 1;
SessionFrame session_frame = 2;
Used used = 3;
SystemFrame system_frame = 4;
SystemGpuInfo system_gpu_info = 5;
SystemPresentInfo system_present_info = 6;
};
}

View File

@ -58,7 +58,8 @@ interprocess = { version = "2.2.3" }
json = { version = "0.12.4", optional = true } json = { version = "0.12.4", optional = true }
json5 = "1.3.0" json5 = "1.3.0"
libc = "0.2.178" libc = "0.2.178"
libmonado = { version = "1.6.0", optional = true } smol = { workspace = true }
libmonado = { git = "https://github.com/oo8dev/libmonado-rs.git", rev = "fc39940a64dea2df080a0d2c974c7d651006241f", optional = true }
log-panics = { version = "2.1.0", features = ["with-backtrace"] } log-panics = { version = "2.1.0", features = ["with-backtrace"] }
mint = "0.5.9" mint = "0.5.9"
openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true } openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true }
@ -88,6 +89,7 @@ xcb = { version = "1.6.0", optional = true, features = [
"as-raw-xcb-connection", "as-raw-xcb-connection",
] } ] }
xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros
prost = { version = "0.14.3", optional = true }
[build-dependencies] [build-dependencies]
regex.workspace = true regex.workspace = true
@ -95,7 +97,7 @@ regex.workspace = true
[features] [features]
default = ["openvr", "openxr", "osc", "x11", "wayland"] default = ["openvr", "openxr", "osc", "x11", "wayland"]
openvr = ["dep:ovr_overlay", "dep:json"] openvr = ["dep:ovr_overlay", "dep:json"]
openxr = ["dep:openxr", "dep:libmonado"] openxr = ["dep:openxr", "dep:libmonado", "dep:prost"]
osc = ["dep:rosc"] osc = ["dep:rosc"]
x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"] x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"]
wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"] wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]

View File

@ -24,7 +24,7 @@ impl InputBlocker {
} }
pub fn update(&mut self, app: &mut AppState) { pub fn update(&mut self, app: &mut AppState) {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return; // monado not available return; // monado not available
}; };
@ -50,7 +50,7 @@ impl InputBlocker {
} else { } else {
trace!("Unblocking input"); trace!("Unblocking input");
} }
self.block_inputs(monado, should_block_inputs, should_block_poses); self.block_inputs(&mut monado.ipc, should_block_inputs, should_block_poses);
} }
self.inputs_blocked_last_frame = should_block_inputs; self.inputs_blocked_last_frame = should_block_inputs;

View File

@ -272,7 +272,7 @@ impl OpenXrInputSource {
} }
pub fn update_devices(app: &mut AppState) -> bool { pub fn update_devices(app: &mut AppState) -> bool {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return false; // monado not available return false; // monado not available
}; };
@ -297,7 +297,7 @@ impl OpenXrInputSource {
let mut seen = Vec::<u32>::with_capacity(32); let mut seen = Vec::<u32>::with_capacity(32);
for (mnd_role, wlx_role) in roles { for (mnd_role, wlx_role) in roles {
let device = monado.device_from_role(mnd_role); let device = monado.ipc.device_from_role(mnd_role);
if let Ok(mut device) = device if let Ok(mut device) = device
&& !seen.contains(&device.index) && !seen.contains(&device.index)
{ {
@ -305,7 +305,7 @@ impl OpenXrInputSource {
Self::update_device_battery_status(&mut device, wlx_role, &mut app.input_state); Self::update_device_battery_status(&mut device, wlx_role, &mut app.input_state);
} }
} }
if let Ok(devices) = monado.devices() { if let Ok(devices) = monado.ipc.devices() {
for mut device in devices { for mut device in devices {
if !seen.contains(&device.index) { if !seen.contains(&device.index) {
let role = if device.name_id >= 4 && device.name_id <= 8 { let role = if device.name_id >= 4 && device.name_id <= 8 {

View File

@ -35,6 +35,7 @@ mod blocker;
mod helpers; mod helpers;
mod input; mod input;
mod lines; mod lines;
pub mod monado_state;
mod overlay; mod overlay;
mod playspace; mod playspace;
mod skybox; mod skybox;
@ -85,15 +86,18 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut delete_queue = vec![]; let mut delete_queue = vec![];
app.monado_init(); app.monado_state_init();
let mut playspace = app.monado.as_mut().and_then(|m| { let mut playspace = app.monado_state.as_mut().and_then(|m| {
playspace::PlayspaceMover::new(m) playspace::PlayspaceMover::new(&mut m.ipc)
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}")) .map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
.ok() .ok()
}); });
let mut blocker = app.monado.as_ref().map(blocker::InputBlocker::new); let mut blocker = app
.monado_state
.as_ref()
.map(|m| blocker::InputBlocker::new(&m.ipc));
let (session, mut frame_wait, mut frame_stream) = unsafe { let (session, mut frame_wait, mut frame_stream) = unsafe {
let raw_session = helpers::create_overlay_session( let raw_session = helpers::create_overlay_session(
@ -211,7 +215,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
} }
} }
if app.monado.is_some() && next_device_update <= Instant::now() { if app.monado_state.is_some() && next_device_update <= Instant::now() {
let changed = OpenXrInputSource::update_devices(&mut app); let changed = OpenXrInputSource::update_devices(&mut app);
if changed { if changed {
overlays.devices_changed(&mut app)?; overlays.devices_changed(&mut app)?;
@ -403,6 +407,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
futures.wait()?; futures.wait()?;
// End rendering // End rendering
if let Some(monado_state) = &mut app.monado_state {
monado_state.update();
}
// Layer composition // Layer composition
let mut layers = vec![]; let mut layers = vec![];
if !main_session_visible && let Some(skybox) = skybox.as_mut() { if !main_session_visible && let Some(skybox) = skybox.as_mut() {
@ -499,8 +507,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
watch.config.active_state.as_mut().unwrap().transform = watch_transform; watch.config.active_state.as_mut().unwrap().transform = watch_transform;
} // main_loop } // main_loop
if let (Some(blocker), Some(monado)) = (blocker, app.monado.as_mut()) { if let (Some(blocker), Some(monado)) = (blocker, app.monado_state.as_mut()) {
blocker.unblock(monado); blocker.unblock(&mut monado.ipc);
} }
overlays.persist_layout(&mut app); overlays.persist_layout(&mut app);

View File

@ -0,0 +1,25 @@
use crate::subsystem::monado_metrics::{self, metrics_fd::MonadoMetricsFd};
pub struct MonadoState {
pub ipc: libmonado::Monado,
pub metrics: Option<MonadoMetricsFd>,
}
impl MonadoState {
pub fn new() -> anyhow::Result<Self> {
let mut ipc = libmonado::Monado::auto_connect().map_err(|s| anyhow::anyhow!("{s}"))?;
let metrics = monado_metrics::metrics_fd::MonadoMetricsFd::new(&mut ipc)?;
Ok(Self {
ipc,
metrics: Some(metrics),
})
}
pub fn update(&mut self) {
if let Some(metrics) = &mut self.metrics {
metrics.update();
}
}
}

View File

@ -44,19 +44,19 @@ impl PlayspaceMover {
} }
pub fn handle_task(&mut self, app: &mut AppState, task: PlayspaceTask) { pub fn handle_task(&mut self, app: &mut AppState, task: PlayspaceTask) {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return; // monado not available return; // monado not available
}; };
match task { match task {
PlayspaceTask::FixFloor => { PlayspaceTask::FixFloor => {
self.fix_floor(&app.input_state, monado); self.fix_floor(&app.input_state, &mut monado.ipc);
} }
PlayspaceTask::Reset => { PlayspaceTask::Reset => {
self.reset_offset(monado); self.reset_offset(&mut monado.ipc);
} }
PlayspaceTask::Recenter => { PlayspaceTask::Recenter => {
self.recenter(&app.input_state, monado); self.recenter(&app.input_state, &mut monado.ipc);
} }
} }
} }
@ -66,7 +66,7 @@ impl PlayspaceMover {
overlays: &mut OverlayWindowManager<OpenXrOverlayData>, overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
app: &mut AppState, app: &mut AppState,
) { ) {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return; // monado not available return; // monado not available
}; };
@ -74,7 +74,7 @@ impl PlayspaceMover {
if pointer.now.space_reset { if pointer.now.space_reset {
if !pointer.before.space_reset { if !pointer.before.space_reset {
log::info!("Space reset"); log::info!("Space reset");
self.reset_offset(monado); self.reset_offset(&mut monado.ipc);
} }
return; return;
} }
@ -111,7 +111,7 @@ impl PlayspaceMover {
data.pose *= space_transform; data.pose *= space_transform;
data.hand_pose = new_hand; data.hand_pose = new_hand;
apply_offset(data.pose, monado); apply_offset(data.pose, &mut monado.ipc);
self.rotate = Some(data); self.rotate = Some(data);
} else { } else {
for (i, pointer) in app.input_state.pointers.iter().enumerate() { for (i, pointer) in app.input_state.pointers.iter().enumerate() {
@ -167,7 +167,7 @@ impl PlayspaceMover {
data.pose.translation += relative_pos; data.pose.translation += relative_pos;
data.hand_pose = new_hand; data.hand_pose = new_hand;
apply_offset(data.pose, monado); apply_offset(data.pose, &mut monado.ipc);
self.drag = Some(data); self.drag = Some(data);
} else { } else {
for (i, pointer) in app.input_state.pointers.iter().enumerate() { for (i, pointer) in app.input_state.pointers.iter().enumerate() {

View File

@ -471,11 +471,11 @@ impl DashInterface<AppState> for DashInterfaceLive {
&mut self, &mut self,
app: &mut AppState, app: &mut AppState,
) -> anyhow::Result<Vec<dash_interface::MonadoClient>> { ) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return Ok(Vec::new()); // no monado available return Ok(Vec::new()); // no monado available
}; };
let clients = monado_list_clients_filtered(monado)?; let clients = monado_list_clients_filtered(&mut monado.ipc)?;
let mut res = Vec::<dash_interface::MonadoClient>::new(); let mut res = Vec::<dash_interface::MonadoClient>::new();
@ -499,29 +499,29 @@ impl DashInterface<AppState> for DashInterfaceLive {
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
fn monado_client_focus(&mut self, app: &mut AppState, name: &str) -> anyhow::Result<()> { fn monado_client_focus(&mut self, app: &mut AppState, name: &str) -> anyhow::Result<()> {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return Ok(()); // no monado available return Ok(()); // no monado available
}; };
monado_client_focus(monado, name) monado_client_focus(&mut monado.ipc, name)
} }
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
fn monado_brightness_get(&mut self, app: &mut AppState) -> Option<f32> { fn monado_brightness_get(&mut self, app: &mut AppState) -> Option<f32> {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return None; return None;
}; };
monado_get_brightness(monado) monado_get_brightness(&mut monado.ipc)
} }
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
fn monado_brightness_set(&mut self, app: &mut AppState, brightness: f32) -> Option<()> { fn monado_brightness_set(&mut self, app: &mut AppState, brightness: f32) -> Option<()> {
let Some(monado) = &mut app.monado else { let Some(monado) = &mut app.monado_state else {
return None; return None;
}; };
monado_set_brightness(monado, brightness).ok() monado_set_brightness(&mut monado.ipc, brightness).ok()
} }
#[cfg(not(feature = "openxr"))] #[cfg(not(feature = "openxr"))]

View File

@ -9,6 +9,7 @@ use wgui::{
drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex, drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
renderer_vk::context::SharedContext as WSharedContext, renderer_vk::context::SharedContext as WSharedContext,
}; };
use wlx_common::async_executor::AsyncExecutor;
use wlx_common::locale::WayVRLangProvider; use wlx_common::locale::WayVRLangProvider;
use wlx_common::{ use wlx_common::{
audio, audio,
@ -18,7 +19,10 @@ use wlx_common::{
overlays::{ToastDisplayMethod, ToastTopic}, overlays::{ToastDisplayMethod, ToastTopic},
}; };
#[cfg(feature = "openxr")]
use crate::backend;
use crate::backend::wayvr::WvrServerState; use crate::backend::wayvr::WvrServerState;
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
use crate::subsystem::osc::OscSender; use crate::subsystem::osc::OscSender;
@ -34,6 +38,7 @@ use crate::{
pub struct AppState { pub struct AppState {
pub session: AppSession, pub session: AppSession,
pub tasks: TaskContainer, pub tasks: TaskContainer,
pub executor: AsyncExecutor,
pub gfx: Arc<WGfx>, pub gfx: Arc<WGfx>,
pub gfx_extras: WGfxExtras, pub gfx_extras: WGfxExtras,
@ -67,7 +72,7 @@ pub struct AppState {
pub wvr_server: Option<WvrServerState>, pub wvr_server: Option<WvrServerState>,
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
pub monado: Option<libmonado::Monado>, pub monado_state: Option<backend::openxr::monado_state::MonadoState>,
} }
#[allow(unused_mut)] #[allow(unused_mut)]
@ -152,9 +157,12 @@ impl AppState {
let lang_provider = WayVRLangProvider::from_config(&session.config); let lang_provider = WayVRLangProvider::from_config(&session.config);
let executor = Rc::new(smol::LocalExecutor::new());
Ok(Self { Ok(Self {
session, session,
tasks, tasks,
executor,
gfx, gfx,
gfx_extras, gfx_extras,
hid_provider, hid_provider,
@ -184,17 +192,25 @@ impl AppState {
wvr_server, wvr_server,
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
monado: None, monado_state: None,
}) })
} }
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
pub fn monado_init(&mut self) { pub fn monado_state_init(&mut self) {
use crate::backend::openxr::monado_state::MonadoState;
log::debug!("Connecting to Monado IPC"); log::debug!("Connecting to Monado IPC");
self.monado = None; // stop connection first self.monado_state = None; // stop connection first
self.monado = libmonado::Monado::auto_connect()
.map_err(|e| log::warn!("Will not use libmonado: {e}")) match MonadoState::new() {
.ok(); Ok(m) => {
self.monado_state = Some(m);
}
Err(e) => {
log::error!("Will not use libmonado: {e:?}");
}
}
} }
} }

View File

@ -5,3 +5,6 @@ pub mod notifications;
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
pub mod osc; pub mod osc;
#[cfg(feature = "openxr")]
pub mod monado_metrics;

View File

@ -0,0 +1,10 @@
`proto.rs` can be generated via prost_build tool.
To generate a new version of protobuf file:
```
cd scripts/prost_build
OUT_DIR=your_directory cargo run
```
new protobuf files will be generated in `your_directory` directory.

View File

@ -0,0 +1,52 @@
use std::{
io::Read,
os::{fd::AsFd, unix::net::UnixStream},
};
use crate::subsystem::monado_metrics::proto;
pub struct MonadoMetricsFd {
stream_reader: UnixStream,
stream_writer: UnixStream,
}
impl MonadoMetricsFd {
pub fn new(monado: &mut libmonado::Monado) -> anyhow::Result<Self> {
let (stream_reader, stream_writer) = std::os::unix::net::UnixStream::pair()?;
stream_writer.set_nonblocking(true)?;
stream_reader.set_nonblocking(true)?;
monado.push_metrics_fd(stream_writer.as_fd(), true)?;
Ok(Self {
stream_reader,
stream_writer,
})
}
fn parse_message(&self, record: proto::Record) {
log::debug!("metrics message: {record:?}");
}
// called every frame
pub fn update(&mut self) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(byte_count) = self.stream_reader.read(&mut buf) {
if byte_count == 0 {
debug_assert!(false);
break;
}
let res: Result<proto::Record, _> = prost::Message::decode_length_delimited(&buf[..]);
match res {
Ok(record) => {
self.parse_message(record);
}
Err(e) => {
log::error!("decode error: {e}");
}
}
}
}
}

View File

@ -0,0 +1,2 @@
pub mod metrics_fd;
pub mod proto;

View File

@ -0,0 +1,180 @@
// This file is @generated by prost-build.
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Version {
#[prost(uint32, tag = "1")]
pub major: u32,
#[prost(uint32, tag = "2")]
pub minor: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SessionFrame {
/// ! Which session this frame belongs to.
#[prost(int64, tag = "1")]
pub session_id: i64,
/// ! ID of frame.
#[prost(int64, tag = "2")]
pub frame_id: i64,
/// ! How long we thought the frame would take.
#[prost(uint64, tag = "3")]
pub predicted_frame_time_ns: u64,
/// ! When we predicted the app should wake up.
#[prost(uint64, tag = "4")]
pub predicted_wake_up_time_ns: u64,
/// ! When the client's GPU work should have completed.
#[prost(uint64, tag = "5")]
pub predicted_gpu_done_time_ns: u64,
/// ! When we predicted this frame to be shown.
#[prost(uint64, tag = "6")]
pub predicted_display_time_ns: u64,
/// ! The selected display period.
#[prost(uint64, tag = "7")]
pub predicted_display_period_ns: u64,
/// !
/// When the app told us to display this frame, can be different
/// then the predicted display time so we track that separately.
#[prost(uint64, tag = "8")]
pub display_time_ns: u64,
/// ! When the predict call was made (inside of xrWaitFrame).
#[prost(uint64, tag = "9")]
pub when_predicted_ns: u64,
/// ! When the waiting thread was woken up.
#[prost(uint64, tag = "10")]
pub when_wait_woke_ns: u64,
/// ! When xrBeginFrame was called.
#[prost(uint64, tag = "11")]
pub when_begin_ns: u64,
/// ! When the layer information was delivered, (inside of xrEndFrame).
#[prost(uint64, tag = "12")]
pub when_delivered_ns: u64,
/// ! When the scheduled GPU work was completed.
#[prost(uint64, tag = "13")]
pub when_gpu_done_ns: u64,
/// ! Was this frame discarded.
#[prost(bool, tag = "14")]
pub discarded: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Used {
/// ! Owning session of the frame.
#[prost(int64, tag = "1")]
pub session_id: i64,
/// ! Which session frame was used by the compositor.
#[prost(int64, tag = "2")]
pub session_frame_id: i64,
/// ! The system frame that the session was used by.
#[prost(int64, tag = "3")]
pub system_frame_id: i64,
/// ! When the frame was latched.
#[prost(uint64, tag = "4")]
pub when_ns: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemFrame {
/// ! ID of frame.
#[prost(int64, tag = "1")]
pub frame_id: i64,
/// ! Projected pixels to photon time.
#[prost(uint64, tag = "2")]
pub predicted_display_time_ns: u64,
/// ! Current period of displaying of frames.
#[prost(uint64, tag = "3")]
pub predicted_display_period_ns: u64,
/// ! When the compositor should hand pixels to display hardware.
#[prost(uint64, tag = "4")]
pub desired_present_time_ns: u64,
/// ! The time that the compositor should wake up.
#[prost(uint64, tag = "5")]
pub wake_up_time_ns: u64,
#[prost(uint64, tag = "6")]
pub present_slop_ns: u64,
}
/// !
/// Out of band info about a GPU timing from a system compositor.
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemGpuInfo {
/// ! ID of frame.
#[prost(int64, tag = "1")]
pub frame_id: i64,
/// ! Start of GPU work.
#[prost(uint64, tag = "2")]
pub gpu_start_ns: u64,
/// ! End of GPU work.
#[prost(uint64, tag = "3")]
pub gpu_end_ns: u64,
/// ! When the information was gathered.
#[prost(uint64, tag = "4")]
pub when_ns: u64,
}
/// !
/// Information that comes from the vulkan present timing.
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemPresentInfo {
/// ! ID of frame.
#[prost(int64, tag = "1")]
pub frame_id: i64,
/// ! The total expected time for the compositor to complete a frame.
#[prost(uint64, tag = "2")]
pub expected_comp_time_ns: u64,
/// ! The time we predicted the compositor to wake up time.
#[prost(uint64, tag = "3")]
pub predicted_wake_up_time_ns: u64,
/// ! Predicted time for completion of the GPU work.
#[prost(uint64, tag = "4")]
pub predicted_done_time_ns: u64,
/// ! Predicted display time.
#[prost(uint64, tag = "5")]
pub predicted_display_time_ns: u64,
/// ! When was this frame predicted.
#[prost(uint64, tag = "6")]
pub when_predict_ns: u64,
/// ! When we last woke up the compositor after its equivalent of wait_frame.
#[prost(uint64, tag = "7")]
pub when_woke_ns: u64,
/// ! When the compositor started rendering a frame
#[prost(uint64, tag = "8")]
pub when_began_ns: u64,
/// ! When the compositor finished rendering a frame
#[prost(uint64, tag = "9")]
pub when_submitted_ns: u64,
/// ! When new frame timing info was last added.
#[prost(uint64, tag = "10")]
pub when_infoed_ns: u64,
/// ! When we wanted to start presenting.
#[prost(uint64, tag = "11")]
pub desired_present_time_ns: u64,
/// ! The slop used for this frame.
#[prost(uint64, tag = "12")]
pub present_slop_ns: u64,
/// ! Margin of GPU to present time.
#[prost(uint64, tag = "13")]
pub present_margin_ns: u64,
/// ! When the present time actually happened.
#[prost(uint64, tag = "14")]
pub actual_present_time_ns: u64,
/// ! The earliest we could have presented.
#[prost(uint64, tag = "15")]
pub earliest_present_time_ns: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Record {
#[prost(oneof = "record::Record", tags = "1, 2, 3, 4, 5, 6")]
pub record: ::core::option::Option<record::Record>,
}
/// Nested message and enum types in `Record`.
pub mod record {
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Record {
#[prost(message, tag = "1")]
Version(super::Version),
#[prost(message, tag = "2")]
SessionFrame(super::SessionFrame),
#[prost(message, tag = "3")]
Used(super::Used),
#[prost(message, tag = "4")]
SystemFrame(super::SystemFrame),
#[prost(message, tag = "5")]
SystemGpuInfo(super::SystemGpuInfo),
#[prost(message, tag = "6")]
SystemPresentInfo(super::SystemPresentInfo),
}
}

View File

@ -22,7 +22,7 @@ strum.workspace = true
xdg.workspace = true xdg.workspace = true
chrono = "0.4.42" chrono = "0.4.42"
smol = "2.0.2" smol = { workspace = true }
wgui = { path = "../wgui/" } wgui = { path = "../wgui/" }
rodio = { version = "0.21.1", default-features = false, features = [ rodio = { version = "0.21.1", default-features = false, features = [
"playback", "playback",

View File

@ -1,4 +1,5 @@
pub mod astr_containers; pub mod astr_containers;
pub mod async_executor;
pub mod audio; pub mod audio;
pub mod cache_dir; pub mod cache_dir;
pub mod common; pub mod common;