1mod space;
2mod sys;
3
4pub use semver::Version;
5pub use space::*;
6pub use sys::ClientState;
7pub use sys::MndProperty;
8pub use sys::MndResult;
9
10use dlopen2::wrapper::Container;
11use flagset::FlagSet;
12use semver::VersionReq;
13use serde::Deserialize;
14use std::env;
15use std::ffi::c_char;
16use std::ffi::CStr;
17use std::ffi::CString;
18use std::ffi::OsStr;
19use std::fmt::Debug;
20use std::fs;
21use std::path::PathBuf;
22use std::ptr;
23use std::vec;
24use sys::MndRootPtr;
25use sys::MonadoApi;
26
27fn crate_api_version() -> VersionReq {
28 VersionReq::parse("^1.3.0").unwrap()
29}
30fn get_api_version(api: &Container<MonadoApi>) -> Version {
31 let mut major = 0;
32 let mut minor = 0;
33 let mut patch = 0;
34 unsafe { api.mnd_api_get_version(&mut major, &mut minor, &mut patch) };
35
36 Version::new(major as u64, minor as u64, patch as u64)
37}
38
39#[derive(Debug, Clone, Deserialize)]
40struct RuntimeJSON {
41 runtime: RuntimeInfo,
42}
43#[derive(Debug, Clone, Deserialize)]
44struct RuntimeInfo {
45 #[serde(rename = "library_path")]
46 _library_path: PathBuf,
47 #[serde(rename = "MND_libmonado_path")]
48 libmonado_path: Option<PathBuf>,
49}
50
51#[derive(Debug, Clone, Copy)]
52pub struct BatteryStatus {
53 pub present: bool,
54 pub charging: bool,
55 pub charge: f32,
56}
57
58#[derive(Debug, Clone, Copy)]
59pub enum DeviceRole {
60 Head,
61 Eyes,
62 Left,
63 Right,
64 Gamepad,
65 HandTrackingLeft,
66 HandTrackingRight,
67}
68
69impl From<DeviceRole> for &'static str {
70 fn from(value: DeviceRole) -> Self {
71 match value {
72 DeviceRole::Head => "head",
73 DeviceRole::Eyes => "eyes",
74 DeviceRole::Left => "left",
75 DeviceRole::Right => "right",
76 DeviceRole::Gamepad => "gamepad",
77 DeviceRole::HandTrackingLeft => "hand-tracking-left",
78 DeviceRole::HandTrackingRight => "hand-tracking-right",
79 }
80 }
81}
82
83pub struct Monado {
84 api: Container<MonadoApi>,
85 root: MndRootPtr,
86}
87impl Monado {
88 pub fn auto_connect() -> Result<Self, String> {
89 if let Ok(libmonado_path) = env::var("LIBMONADO_PATH") {
90 match fs::metadata(&libmonado_path) {
91 Ok(metadata) if metadata.is_file() => {
92 return Self::create(libmonado_path).map_err(|e| format!("{e:?}"))
93 }
94 _ => return Err("LIBMONADO_PATH does not point to a valid file".into()),
95 }
96 }
97
98 let override_runtime = std::env::var_os("XDG_RUNTIME_JSON").map(PathBuf::from);
99 let possible_config_files = xdg::BaseDirectories::new()
100 .ok()
101 .into_iter()
102 .flat_map(|b| b.find_config_files("openxr/1/active_runtime.json"));
103 let override_runtime = override_runtime
104 .into_iter()
105 .chain(possible_config_files)
106 .find_map(|p| {
107 Some((
108 serde_json::from_str::<RuntimeJSON>(&std::fs::read_to_string(&p).ok()?).ok()?,
109 p,
110 ))
111 });
112
113 let Some((runtime_json, mut runtime_path)) = override_runtime else {
114 return Err("Couldn't find the actively running runtime".to_string());
115 };
116 runtime_path.pop();
117 let Some(libmonado_path) = runtime_json.runtime.libmonado_path else {
118 return Err("Couldn't find libmonado path in active runtime json".to_string());
119 };
120
121 let path = runtime_path.join(libmonado_path);
122 Self::create(path).map_err(|e| format!("{e:?}"))
123 }
124 pub fn create<S: AsRef<OsStr>>(libmonado_so: S) -> Result<Self, MndResult> {
125 let api = unsafe { Container::<MonadoApi>::load(libmonado_so) }
126 .map_err(|_| MndResult::ErrorConnectingFailed)?;
127 if !crate_api_version().matches(&get_api_version(&api)) {
128 return Err(MndResult::ErrorInvalidVersion);
129 }
130 let mut root = std::ptr::null_mut();
131 unsafe {
132 api.mnd_root_create(&mut root).to_result()?;
133 }
134 Ok(Monado { api, root })
135 }
136
137 pub fn get_api_version(&self) -> Version {
138 get_api_version(&self.api)
139 }
140 pub fn recenter_local_spaces(&self) -> Result<(), MndResult> {
141 unsafe {
142 self.api
143 .mnd_root_recenter_local_spaces(self.root)
144 .to_result()
145 }
146 }
147
148 pub fn clients(&self) -> Result<impl IntoIterator<Item = Client<'_>>, MndResult> {
149 unsafe {
150 self.api
151 .mnd_root_update_client_list(self.root)
152 .to_result()?
153 };
154 let mut count = 0;
155 unsafe {
156 self.api
157 .mnd_root_get_number_clients(self.root, &mut count)
158 .to_result()?
159 };
160 let mut clients: Vec<Option<Client>> = vec::from_elem(None, count as usize);
161 for (index, client) in clients.iter_mut().enumerate() {
162 let mut id = 0;
163 unsafe {
164 self.api
165 .mnd_root_get_client_id_at_index(self.root, index as u32, &mut id)
166 .to_result()?
167 };
168 client.replace(Client { monado: self, id });
169 }
170 Ok(clients.into_iter().flatten())
171 }
172
173 fn device_index_from_role_str(&self, role_name: &str) -> Result<u32, MndResult> {
174 let c_name = CString::new(role_name).unwrap();
175 let mut index = -1;
176
177 unsafe {
178 self.api
179 .mnd_root_get_device_from_role(self.root, c_name.as_ptr(), &mut index)
180 .to_result()?
181 };
182 if index == -1 {
183 return Err(MndResult::ErrorInvalidValue);
184 }
185 Ok(index as u32)
186 }
187
188 fn device_from_role_str<'m>(&'m self, role_name: &str) -> Result<Device<'m>, MndResult> {
194 let index = self.device_index_from_role_str(role_name)?;
195 let mut c_name: *const c_char = std::ptr::null_mut();
196 let mut name_id = 0;
197 unsafe {
198 self.api
199 .mnd_root_get_device_info(self.root, index, &mut name_id, &mut c_name)
200 .to_result()?
201 };
202 let name = unsafe {
203 CStr::from_ptr(c_name)
204 .to_str()
205 .map_err(|_| MndResult::ErrorInvalidValue)?
206 .to_owned()
207 };
208
209 Ok(Device {
210 monado: self,
211 index,
212 name_id,
213 name,
214 })
215 }
216
217 pub fn device_index_from_role(&self, role: DeviceRole) -> Result<u32, MndResult> {
218 self.device_index_from_role_str(role.into())
219 }
220
221 pub fn device_from_role(&self, role: DeviceRole) -> Result<Device<'_>, MndResult> {
222 self.device_from_role_str(role.into())
223 }
224
225 pub fn devices(&self) -> Result<impl IntoIterator<Item = Device<'_>>, MndResult> {
226 let mut count = 0;
227 unsafe {
228 self.api
229 .mnd_root_get_device_count(self.root, &mut count)
230 .to_result()?
231 };
232 let mut devices: Vec<Option<Device>> = vec::from_elem(None, count as usize);
233 for (index, device) in devices.iter_mut().enumerate() {
234 let index = index as u32;
235 let mut name_id = 0;
236 let mut c_name: *const c_char = std::ptr::null_mut();
237 unsafe {
238 self.api
239 .mnd_root_get_device_info(self.root, index, &mut name_id, &mut c_name)
240 .to_result()?
241 };
242 let name = unsafe {
243 CStr::from_ptr(c_name)
244 .to_str()
245 .map_err(|_| MndResult::ErrorInvalidValue)?
246 .to_owned()
247 };
248 device.replace(Device {
249 monado: self,
250 index,
251 name_id,
252 name,
253 });
254 }
255 Ok(devices.into_iter().flatten())
256 }
257}
258impl Drop for Monado {
259 fn drop(&mut self) {
260 unsafe { self.api.mnd_root_destroy(&mut self.root) }
261 }
262}
263
264#[derive(Clone)]
265pub struct Client<'m> {
266 monado: &'m Monado,
267 id: u32,
268}
269impl Client<'_> {
270 pub fn name(&mut self) -> Result<String, MndResult> {
271 let mut string = std::ptr::null();
272 unsafe {
273 self.monado
274 .api
275 .mnd_root_get_client_name(self.monado.root, self.id, &mut string)
276 .to_result()?
277 };
278 let c_string = unsafe { CStr::from_ptr(string) };
279 c_string
280 .to_str()
281 .map_err(|_| MndResult::ErrorInvalidValue)
282 .map(ToString::to_string)
283 }
284 pub fn state(&mut self) -> Result<FlagSet<ClientState>, MndResult> {
285 let mut state = 0;
286 unsafe {
287 self.monado
288 .api
289 .mnd_root_get_client_state(self.monado.root, self.id, &mut state)
290 .to_result()?
291 };
292 Ok(unsafe { FlagSet::new_unchecked(state) })
293 }
294 pub fn set_primary(&mut self) -> Result<(), MndResult> {
295 unsafe {
296 self.monado
297 .api
298 .mnd_root_set_client_primary(self.monado.root, self.id)
299 .to_result()
300 }
301 }
302 pub fn set_focused(&mut self) -> Result<(), MndResult> {
303 unsafe {
304 self.monado
305 .api
306 .mnd_root_set_client_focused(self.monado.root, self.id)
307 .to_result()
308 }
309 }
310 pub fn set_io_active(&mut self, active: bool) -> Result<(), MndResult> {
311 let state = self.state()?;
312 if state.contains(ClientState::ClientIoActive) != active {
313 unsafe {
314 self.monado
315 .api
316 .mnd_root_toggle_client_io_active(self.monado.root, self.id)
317 .to_result()?;
318 }
319 }
320 Ok(())
321 }
322}
323
324#[derive(Clone)]
325pub struct Device<'m> {
326 monado: &'m Monado,
327 pub index: u32,
328 pub name_id: u32,
330 pub name: String,
331}
332impl Device<'_> {
333 pub fn battery_status(&self) -> Result<BatteryStatus, MndResult> {
334 let mut present: bool = Default::default();
335 let mut charging: bool = Default::default();
336 let mut charge: f32 = Default::default();
337 unsafe {
338 self.monado
339 .api
340 .mnd_root_get_device_battery_status(
341 self.monado.root,
342 self.index,
343 &mut present,
344 &mut charging,
345 &mut charge,
346 )
347 .to_result()?;
348 }
349 Ok(BatteryStatus {
350 present,
351 charging,
352 charge,
353 })
354 }
355 pub fn serial(&self) -> Result<String, MndResult> {
356 self.get_info_string(MndProperty::PropertySerialString)
357 }
358 pub fn get_info_bool(&self, property: MndProperty) -> Result<bool, MndResult> {
359 let mut value: bool = Default::default();
360 unsafe {
361 self.monado
362 .api
363 .mnd_root_get_device_info_bool(self.monado.root, self.index, property, &mut value)
364 .to_result()?
365 }
366 Ok(value)
367 }
368 pub fn get_info_u32(&self, property: MndProperty) -> Result<u32, MndResult> {
369 let mut value: u32 = Default::default();
370 unsafe {
371 self.monado
372 .api
373 .mnd_root_get_device_info_u32(self.monado.root, self.index, property, &mut value)
374 .to_result()?
375 }
376 Ok(value)
377 }
378 pub fn get_info_i32(&self, property: MndProperty) -> Result<i32, MndResult> {
379 let mut value: i32 = Default::default();
380 unsafe {
381 self.monado
382 .api
383 .mnd_root_get_device_info_i32(self.monado.root, self.index, property, &mut value)
384 .to_result()?
385 }
386 Ok(value)
387 }
388 pub fn get_info_f32(&self, property: MndProperty) -> Result<f32, MndResult> {
389 let mut value: f32 = Default::default();
390 unsafe {
391 self.monado
392 .api
393 .mnd_root_get_device_info_float(self.monado.root, self.index, property, &mut value)
394 .to_result()?
395 }
396 Ok(value)
397 }
398 pub fn get_info_string(&self, property: MndProperty) -> Result<String, MndResult> {
399 let mut cstr_ptr = ptr::null_mut();
400
401 unsafe {
402 self.monado
403 .api
404 .mnd_root_get_device_info_string(
405 self.monado.root,
406 self.index,
407 property,
408 &mut cstr_ptr,
409 )
410 .to_result()?
411 }
412
413 unsafe { Ok(CStr::from_ptr(cstr_ptr).to_string_lossy().to_string()) }
414 }
415}
416impl Debug for Device<'_> {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 f.debug_struct("Device")
419 .field("id", &self.name_id)
420 .field("name", &self.name)
421 .finish()
422 }
423}
424
425#[test]
426fn test_dump_info() {
427 let monado = Monado::auto_connect().unwrap();
428 dbg!(monado.get_api_version());
429 println!();
430
431 for mut client in monado.clients().unwrap() {
432 dbg!(client.name().unwrap(), client.state().unwrap());
433 println!();
434 }
435 for device in monado.devices().unwrap() {
436 let _ = dbg!(device.name_id, &device.name, device.serial());
437 println!();
438 }
439 for tracking_origin in monado.tracking_origins().unwrap() {
440 dbg!(
441 tracking_origin.id,
442 &tracking_origin.name,
443 tracking_origin.get_offset().unwrap()
444 );
445 println!();
446 }
447}