diff --git a/src/ui/app.rs b/src/ui/app.rs index 5e428b7..565ab1b 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -43,7 +43,7 @@ use crate::ui::build_window::{BuildWindowMsg, BuildWindowOutMsg}; use crate::ui::debug_view::DebugViewInit; use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg; use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg}; -use crate::xr_devices::XRDevices; +use crate::xr_devices::XRDevice; use crate::{stateless_action, withclones}; use gtk::prelude::*; use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup}; @@ -91,7 +91,7 @@ pub struct App { #[tracker::do_not_track] profiles: Vec, #[tracker::do_not_track] - xr_devices: XRDevices, + xr_devices: Vec, #[tracker::do_not_track] fbt_config_editor: Option>, #[tracker::do_not_track] @@ -164,7 +164,7 @@ impl App { return; }; self.debug_view.sender().emit(DebugViewMsg::ClearLog); - self.xr_devices = XRDevices::default(); + self.xr_devices = vec![]; if prof.can_start() { remove_file(&get_ipc_file_path(&prof.xrservice_type)) .is_err() @@ -225,7 +225,7 @@ impl App { self.main_view .sender() .emit(MainViewMsg::XRServiceActiveChanged(false, None)); - self.xr_devices = XRDevices::default(); + self.xr_devices = vec![]; } pub fn profiles_list(config: &Config) -> Vec { @@ -318,10 +318,13 @@ impl SimpleComponent for App { state.exit_status.is_none() && !state.stop_requested } { if let Some(monado) = self.libmonado.as_ref() { - self.xr_devices.merge(XRDevices::from_libmonado(monado)); + self.xr_devices = XRDevice::merge( + &self.xr_devices, + &XRDevice::from_libmonado(monado), + ); self.main_view .sender() - .emit(MainViewMsg::UpdateDevices(Some(self.xr_devices.clone()))); + .emit(MainViewMsg::UpdateDevices(self.xr_devices.clone())); } else { if let Some(so) = self.get_selected_profile().libmonado_so() { self.libmonado = libmonado_rs::Monado::create(so).ok(); @@ -338,23 +341,11 @@ impl SimpleComponent for App { match MonadoLog::new_from_str(row.as_str()) { None => {} Some(parsed) => { - // if parsed.func == "p_create_system" { - // match XRDevices::from_log_message(parsed.message.as_str()) { - // None => {} - // Some(devices) => { - // self.xr_devices.merge(devices.clone()); - // self.main_view.sender().emit(MainViewMsg::UpdateDevices( - // Some(self.xr_devices.clone()), - // )); - // break; - // } - // }; - // } else { - // self.xr_devices - // .search_log_for_generic_trackers(parsed.message.as_str()); - // } - self.xr_devices - .search_log_for_generic_trackers(parsed.message.as_str()); + if let Some(tracker) = + XRDevice::generic_tracker_from_log_row(parsed.message.as_str()) + { + self.xr_devices.push(tracker); + } } }; } @@ -389,7 +380,7 @@ impl SimpleComponent for App { self.start_xrservice(sender); } None => { - worker.stop(); + self.shutdown_xrservice(); self.restart_xrservice = true; } } @@ -668,7 +659,7 @@ impl SimpleComponent for App { profiles, xrservice_worker: None, build_worker: None, - xr_devices: XRDevices::default(), + xr_devices: vec![], fbt_config_editor: None, restart_xrservice: false, libmonado: None, diff --git a/src/ui/devices_box.rs b/src/ui/devices_box.rs index 5ab2491..7a33e3e 100644 --- a/src/ui/devices_box.rs +++ b/src/ui/devices_box.rs @@ -1,48 +1,43 @@ +use super::{ + alert::alert, + factories::device_row_factory::{DeviceRowModel, DeviceRowModelInit, DeviceRowState}, +}; use crate::{ file_builders::monado_config_v0::dump_generic_trackers, - xr_devices::{XRDevice, XRDevices}, + xr_devices::{XRDevice, XRDeviceType}, }; use adw::prelude::*; -use relm4::prelude::*; - -use super::alert::alert; +use relm4::{factory::FactoryVecDeque, prelude::*, Sender}; #[tracker::track] pub struct DevicesBox { - devices: Option, + devices: Vec, + + #[tracker::do_not_track] + device_rows: FactoryVecDeque, } #[derive(Debug)] pub enum DevicesBoxMsg { - UpdateDevices(Option), + UpdateDevices(Vec), DumpGenericTrackers, } impl DevicesBox { - fn get_dev(&self, key: XRDevice) -> Option { - match &self.devices { - None => None, - Some(devs) => match key { - XRDevice::Head => devs.head.clone(), - XRDevice::Left => devs.left.clone(), - XRDevice::Right => devs.right.clone(), - XRDevice::Gamepad => devs.gamepad.clone(), - XRDevice::Eyes => devs.eyes.clone(), - XRDevice::HandTrackingLeft => devs.hand_tracking_left.clone(), - XRDevice::HandTrackingRight => devs.hand_tracking_right.clone(), - XRDevice::GenericTracker => { - if devs.generic_trackers.is_empty() { - return None; - } else { - return Some(devs.generic_trackers.join(", ")); - } - } - }, - } - } + fn create_save_trackers_btn(sender: Sender) -> gtk::Button { + let btn = gtk::Button::builder() + .halign(gtk::Align::Center) + .valign(gtk::Align::Center) + .icon_name("document-save-symbolic") + .tooltip_text("Save current trackers") + .css_classes(["circular", "flat"]) + .build(); - fn get_dev_or_none(&self, key: XRDevice) -> String { - self.get_dev(key).unwrap_or("None".into()) + btn.connect_clicked(move |_| { + sender.emit(DevicesBoxMsg::DumpGenericTrackers); + }); + + btn } } @@ -60,147 +55,129 @@ impl SimpleComponent for DevicesBox { set_spacing: 12, set_margin_top: 12, #[track = "model.changed(Self::devices())"] - set_visible: model.devices.is_some(), - gtk::ListBox { - add_css_class: "boxed-list", - set_selection_mode: gtk::SelectionMode::None, - set_margin_all: 12, - // Head - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_icon_name: Some(match model.get_dev(XRDevice::Head) { - Some(name) => match name.as_str() { - "Simulated HMD" => "dialog-warning-symbolic", - _ => "emblem-ok-symbolic", - }, - None => "dialog-question-symbolic", - }), - #[track = "model.changed(Self::devices())"] - set_class_active: ("error", model.get_dev(XRDevice::Head).is_none()), - #[track = "model.changed(Self::devices())"] - set_class_active: ("warning", model.get_dev_or_none(XRDevice::Head) == "Simulated HMD"), - set_title: "Head", - #[track = "model.changed(Self::devices())"] - set_subtitle: match model.get_dev_or_none(XRDevice::Head).as_str() { - "Simulated HMD" => "No HMD detected (Simulated HMD)", - s => s, - }, - // TODO: status icon with popover - }, - // Left - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_icon_name: Some(match model.get_dev(XRDevice::Left) { - Some(_) => "emblem-ok-symbolic", - None => "dialog-question-symbolic", - }), - #[track = "model.changed(Self::devices())"] - set_class_active: ("error", model.get_dev(XRDevice::Left).is_none()), - set_title: "Left", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::Left).as_str(), - // TODO: status icon with popover - }, - // Right - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_icon_name: Some(match model.get_dev(XRDevice::Right) { - Some(_) => "emblem-ok-symbolic", - None => "dialog-question-symbolic", - }), - #[track = "model.changed(Self::devices())"] - set_class_active: ("error", model.get_dev(XRDevice::Right).is_none()), - set_title: "Right", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::Right).as_str(), - // TODO: status icon with popover - }, - // Gamepad - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_visible: model.get_dev(XRDevice::Gamepad).is_some(), - set_icon_name: Some("emblem-ok-symbolic"), - set_title: "Gamepad", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::Gamepad).as_str(), - }, - // Eyes - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_visible: model.get_dev(XRDevice::Eyes).is_some(), - set_icon_name: Some("emblem-ok-symbolic"), - set_title: "Eye Tracking", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::Eyes).as_str(), - }, - // Hand Tracking Left - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_visible: model.get_dev(XRDevice::HandTrackingLeft).is_some(), - set_icon_name: Some("emblem-ok-symbolic"), - set_title: "Hand Tracking Left", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::HandTrackingLeft).as_str(), - }, - // Hand Tracking Right - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_visible: model.get_dev(XRDevice::HandTrackingRight).is_some(), - set_icon_name: Some("emblem-ok-symbolic"), - set_title: "Hand Tracking Right", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::HandTrackingRight).as_str(), - }, - // Generic Trackers - adw::ActionRow { - #[track = "model.changed(Self::devices())"] - set_visible: model.get_dev(XRDevice::GenericTracker).is_some(), - set_icon_name: Some("emblem-ok-symbolic"), - set_title: "Generic Trackers", - #[track = "model.changed(Self::devices())"] - set_subtitle: model.get_dev_or_none(XRDevice::GenericTracker).as_str(), - add_suffix: save_trackers_btn = >k::Button { - set_halign: gtk::Align::Center, - set_valign: gtk::Align::Center, - set_icon_name: "document-save-symbolic", - set_tooltip_text: Some("Save current trackers"), - set_css_classes: &["circular", "flat"], - connect_clicked => move |_| { - sender.input(Self::Input::DumpGenericTrackers); - } - }, - }, - } + set_visible: !model.devices.is_empty(), + + append: &devices_listbox, } } - fn update(&mut self, message: Self::Input, _sender: ComponentSender) { + fn update(&mut self, message: Self::Input, sender: ComponentSender) { self.reset(); match message { Self::Input::UpdateDevices(devs) => { self.set_devices(devs); + let mut guard = self.device_rows.guard(); + guard.clear(); + if !self.devices.is_empty() { + let mut has_head = false; + let mut has_left = false; + let mut has_right = false; + let mut models: Vec = vec![]; + let mut generic: Vec<&XRDevice> = vec![]; + for dev in &self.devices { + match dev.dev_type { + XRDeviceType::Head => { + has_head = true; + let mut init = DeviceRowModelInit::from_xr_device(&dev); + if dev.name == "Simulated HMD" { + init.state = Some(DeviceRowState::Warning); + init.subtitle = Some("No HMD detected (Simulated HMD)".into()); + } + models.push(init); + } + XRDeviceType::Left => { + has_left = true; + models.push(DeviceRowModelInit::from_xr_device(&dev)); + } + XRDeviceType::Right => { + has_right = true; + models.push(DeviceRowModelInit::from_xr_device(&dev)); + } + XRDeviceType::GenericTracker => { + generic.push(dev); + } + _ => { + models.push(DeviceRowModelInit::from_xr_device(&dev)); + } + }; + } + if !generic.is_empty() { + models.push(DeviceRowModelInit { + title: Some(XRDeviceType::GenericTracker.to_string()), + subtitle: Some( + generic + .iter() + .map(|d| d.id.as_str()) + .collect::>() + .join(", "), + ), + suffix: Some( + Self::create_save_trackers_btn(sender.input_sender().clone()) + .upcast::(), + ), + ..Default::default() + }); + } + if !has_right { + models.push(DeviceRowModelInit::new_missing(XRDeviceType::Right)); + } + if !has_left { + models.push(DeviceRowModelInit::new_missing(XRDeviceType::Left)); + } + if !has_head { + models.push(DeviceRowModelInit::new_missing(XRDeviceType::Head)); + } + + models.sort_by(|m1, m2| { + let dt1 = XRDeviceType::from_display_str( + m1.title.as_ref().unwrap_or(&String::new()), + ); + let dt2 = XRDeviceType::from_display_str( + m2.title.as_ref().unwrap_or(&String::new()), + ); + dt1.cmp(&dt2) + }); + + for model in models { + guard.push_back(model); + } + } } Self::Input::DumpGenericTrackers => { - if let Some(devs) = self.devices.as_ref() { - let added = dump_generic_trackers(&devs.generic_trackers); - let multi_title = format!("Added {} new trackers", added); - let (title, msg) = match added { - 0 => ( - "No new trackers found", - "All the currently connected trackers are already present in your configuration" + let added = dump_generic_trackers( + &self + .devices + .iter() + .filter(|d| d.dev_type == XRDeviceType::GenericTracker) + .map(|d| d.id.clone()) + .collect::>(), + ); + let multi_title = format!("Added {} new trackers", added); + let (title, msg) = match added { + 0 => ( + "No new trackers found", + concat!( + "All the currently connected trackers ", + "are already present in your configuration" ), - 1 => ( - "Added 1 new tracker", - "Edit your configuration to make sure that all the trackers have the appropriate roles assigned" + ), + 1 => ( + "Added 1 new tracker", + concat!( + "Edit your configuration to make sure that ", + "all the trackers have the appropriate roles assigned" ), - _ => ( - multi_title.as_str(), - "Edit your configuration to make sure that all the trackers have the appropriate roles assigned" + ), + _ => ( + multi_title.as_str(), + concat!( + "Edit your configuration to make sure that ", + "all the trackers have the appropriate roles assigned" ), - }; - alert(title, Some(msg), None); - } + ), + }; + alert(title, Some(msg), None); } } } @@ -210,9 +187,19 @@ impl SimpleComponent for DevicesBox { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { + let devices_listbox = gtk::ListBox::builder() + .css_classes(["boxed-list"]) + .selection_mode(gtk::SelectionMode::None) + .margin_start(12) + .margin_end(12) + .margin_top(12) + .margin_bottom(12) + .build(); + let model = Self { tracker: 0, - devices: None, + devices: vec![], + device_rows: FactoryVecDeque::new(devices_listbox.clone(), sender.input_sender()), }; let widgets = view_output!(); diff --git a/src/ui/factories/device_row_factory.rs b/src/ui/factories/device_row_factory.rs new file mode 100644 index 0000000..5187bea --- /dev/null +++ b/src/ui/factories/device_row_factory.rs @@ -0,0 +1,124 @@ +use adw::prelude::*; +use relm4::prelude::*; + +use crate::{ + ui::devices_box::DevicesBoxMsg, + xr_devices::{XRDevice, XRDeviceType}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeviceRowState { + Ok, + Error, + Warning, +} + +impl Default for DeviceRowState { + fn default() -> Self { + Self::Ok + } +} + +impl DeviceRowState { + pub fn icon(&self) -> &str { + match &self { + Self::Ok => "emblem-ok-symbolic", + Self::Error => "dialog-question-symbolic", + Self::Warning => "dialog-warning-symbolic", + } + } + + pub fn class_name(&self) -> Option<&str> { + match &self { + Self::Ok => None, + Self::Error => Some("error"), + Self::Warning => Some("warning"), + } + } +} + +#[derive(Debug)] +pub struct DeviceRowModel { + title: String, + subtitle: String, + state: DeviceRowState, + suffix: Option, +} + +#[derive(Debug, Default)] +pub struct DeviceRowModelInit { + pub title: Option, + pub subtitle: Option, + pub state: Option, + pub suffix: Option, +} + +impl DeviceRowModelInit { + pub fn from_xr_device(d: &XRDevice) -> Self { + Self { + title: Some(d.dev_type.to_string()), + subtitle: Some(d.name.clone()), + ..Default::default() + } + } + + pub fn new_missing(t: XRDeviceType) -> Self { + DeviceRowModelInit { + title: Some(t.to_string()), + subtitle: Some("None".into()), + state: Some(DeviceRowState::Error), + ..Default::default() + } + } +} + +#[relm4::factory(pub)] +impl FactoryComponent for DeviceRowModel { + type Init = DeviceRowModelInit; + type Input = (); + type Output = (); + type CommandOutput = (); + type Widgets = DeviceRowModelWidgets; + type ParentInput = DevicesBoxMsg; + type ParentWidget = gtk::ListBox; + + view! { + root = adw::ActionRow { + // TODO: replace with flat button that spawns popover + add_prefix: icon = >k::Image { + set_icon_name: Some(self.state.icon()), + }, + set_title: self.title.as_str(), + set_subtitle: self.subtitle.as_str(), + } + } + + fn init_widgets( + &mut self, + _index: &Self::Index, + root: &Self::Root, + _returned_widget: &::ReturnedWidget, + _sender: FactorySender, + ) -> Self::Widgets { + let widgets = view_output!(); + + if let Some(suffix) = self.suffix.as_ref() { + widgets.root.add_suffix(suffix); + } + if let Some(cls) = self.state.class_name() { + widgets.root.add_css_class(cls); + widgets.icon.add_css_class(cls); + } + + widgets + } + + fn init_model(init: Self::Init, _index: &Self::Index, _sender: FactorySender) -> Self { + Self { + title: init.title.unwrap_or_default(), + subtitle: init.subtitle.unwrap_or_default(), + state: init.state.unwrap_or_default(), + suffix: init.suffix, + } + } +} diff --git a/src/ui/factories/mod.rs b/src/ui/factories/mod.rs index 41fe8c9..49b67a1 100644 --- a/src/ui/factories/mod.rs +++ b/src/ui/factories/mod.rs @@ -1,2 +1,3 @@ pub mod env_var_row_factory; pub mod tracker_role_group_factory; +pub mod device_row_factory; diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index 7254cf8..fa317c6 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -18,7 +18,7 @@ use crate::ui::app::{ }; use crate::ui::profile_editor::ProfileEditorInit; use crate::ui::util::{limit_dropdown_width, warning_heading}; -use crate::xr_devices::XRDevices; +use crate::xr_devices::XRDevice; use gtk::prelude::*; use relm4::adw::traits::MessageDialogExt; use relm4::adw::ResponseAppearance; @@ -66,7 +66,7 @@ pub enum MainViewMsg { DeleteProfile, DuplicateProfile, SaveProfile(Profile), - UpdateDevices(Option), + UpdateDevices(Vec), } #[derive(Debug)] @@ -462,7 +462,7 @@ impl SimpleComponent for MainView { Self::Input::XRServiceActiveChanged(active, profile) => { self.set_xrservice_active(active); if !active { - sender.input(Self::Input::UpdateDevices(None)); + sender.input(Self::Input::UpdateDevices(vec![])); } self.steam_launch_options_box .sender() diff --git a/src/xr_devices.rs b/src/xr_devices.rs index bc5e5f2..0aeda43 100644 --- a/src/xr_devices.rs +++ b/src/xr_devices.rs @@ -1,8 +1,9 @@ -use std::slice::Iter; -use crate::profile::Profile; +use std::{fmt::Display, slice::Iter}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum XRDevice { +const GENERIC_TRACKER_PREFIX: &str = "Found generic tracker device: "; + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum XRDeviceType { Head, Left, Right, @@ -13,7 +14,34 @@ pub enum XRDevice { GenericTracker, } -impl XRDevice { +impl Display for XRDeviceType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Head => "Head", + Self::Left => "Left", + Self::Right => "Right", + Self::Gamepad => "Gamepad", + Self::Eyes => "Eye Tracking", + Self::HandTrackingLeft => "Hand tracking left", + Self::HandTrackingRight => "Hand tracking right", + Self::GenericTracker => "Generic tracker", + }) + } +} + +impl PartialOrd for XRDeviceType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.as_number().cmp(&other.as_number())) + } +} + +impl Ord for XRDeviceType { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl XRDeviceType { pub fn iter() -> Iter<'static, Self> { [ Self::Head, @@ -40,58 +68,108 @@ impl XRDevice { Self::GenericTracker => "generic_tracker", } } -} -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct XRDevices { - pub head: Option, - pub left: Option, - pub right: Option, - pub gamepad: Option, - pub eyes: Option, - pub hand_tracking_left: Option, - pub hand_tracking_right: Option, - pub generic_trackers: Vec, -} - -const GENERIC_TRACKER_PREFIX: &str = "Found generic tracker device: "; - -impl XRDevices { - fn set_device(&mut self, dev: &XRDevice, name: String) { - match dev { - XRDevice::Head => self.head = Some(name), - XRDevice::Left => self.left = Some(name), - XRDevice::Right => self.right = Some(name), - XRDevice::Gamepad => self.gamepad = Some(name), - XRDevice::Eyes => self.eyes = Some(name), - XRDevice::HandTrackingLeft => self.hand_tracking_left = Some(name), - XRDevice::HandTrackingRight => self.hand_tracking_right = Some(name), - _ => {} + pub fn as_number(&self) -> u32 { + match self { + Self::Head => 0, + Self::Left => 1, + Self::Right => 2, + Self::Gamepad => 3, + Self::Eyes => 4, + Self::HandTrackingLeft => 5, + Self::HandTrackingRight => 6, + Self::GenericTracker => 7, } } - pub fn from_libmonado(monado: &libmonado_rs::Monado) -> Self { - let mut res = Self::default(); + pub fn from_monado_str(s: &str) -> Option { + match s { + "head" => Some(Self::Head), + "left" => Some(Self::Left), + "right" => Some(Self::Right), + "gamepad" => Some(Self::Gamepad), + "eyes" => Some(Self::Eyes), + "hand_tracking.left" => Some(Self::HandTrackingLeft), + "hand_tracking.right" => Some(Self::HandTrackingRight), + _ => None, + } + } + + pub fn from_display_str(s: &str) -> Self { + match s { + "Head" => Self::Head, + "Left" => Self::Left, + "Right" => Self::Right, + "Gamepad" => Self::Gamepad, + "Eye Tracking" => Self::Eyes, + "Hand tracking left" => Self::HandTrackingLeft, + "Hand tracking right" => Self::HandTrackingRight, + "Generic tracker" => Self::GenericTracker, + _ => Self::GenericTracker, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct XRDevice { + pub dev_type: XRDeviceType, + pub name: String, + pub id: String, + pub battery: f32, // battery percentage, from 0 to 1 maybe + // still need to implement it in monado & gui +} + +impl Default for XRDevice { + fn default() -> Self { + Self { + dev_type: XRDeviceType::GenericTracker, + name: String::default(), + id: String::default(), + battery: f32::default(), + } + } +} + +impl XRDevice { + pub fn generic_tracker_from_log_row(s: &str) -> Option { + if s.starts_with(GENERIC_TRACKER_PREFIX) { + let n_tracker = s.trim_start_matches(GENERIC_TRACKER_PREFIX); + return Some(Self { + dev_type: XRDeviceType::GenericTracker, + id: n_tracker.into(), + ..Default::default() + }); + } + None + } + + pub fn from_libmonado(monado: &libmonado_rs::Monado) -> Vec { + let mut res = vec![]; [ - XRDevice::Head, - XRDevice::Left, - XRDevice::Right, - XRDevice::Gamepad, - XRDevice::Eyes, + XRDeviceType::Head, + XRDeviceType::Left, + XRDeviceType::Right, + XRDeviceType::Gamepad, + XRDeviceType::Eyes, ] .iter() .for_each(|xrd| { if let Ok(dev) = monado.device_from_role(xrd.to_monado_str()) { - res.set_device(&xrd, dev.name); + res.push(Self { + name: dev.name, + id: dev.id.to_string(), + dev_type: xrd.clone(), + ..Default::default() + }) } }); res } - pub fn from_log_message(s: &str) -> Option { + pub fn from_log_message(s: &str) -> Vec { + let mut res = vec![]; let rows = s.split('\n'); let mut in_section = false; - let mut devs = Self::default(); for row in rows { if !in_section && row.starts_with("\tIn roles:") { in_section = true; @@ -103,64 +181,58 @@ impl XRDevices { } match row.trim().split(": ").collect::>()[..] { [_, ""] => {} - ["head", val] => devs.head = Some(val.to_string()), - ["left", val] => devs.left = Some(val.to_string()), - ["right", val] => devs.right = Some(val.to_string()), - ["gamepad", val] => devs.gamepad = Some(val.to_string()), - ["eyes", val] => devs.eyes = Some(val.to_string()), - ["hand_tracking.left", val] => devs.hand_tracking_left = Some(val.to_string()), - ["hand_tracking.right", val] => { - devs.hand_tracking_right = Some(val.to_string()) + [dev_type_s, name] => { + if let Some(xrdt) = XRDeviceType::from_monado_str(dev_type_s) { + res.push(Self { + dev_type: xrdt, + name: name.to_string(), + ..Default::default() + }); + } } _ => {} } } } - if in_section { - return Some(devs); - } - None + res } - pub fn merge(&mut self, new: Self) { - if new.head.is_some() { - self.head = new.head; + pub fn merge(old: &[Self], new: &[Self]) -> Vec { + if old.is_empty() { + return Vec::from(new); } - if new.left.is_some() { - self.left = new.left; - } - if new.right.is_some() { - self.right = new.right; - } - if new.gamepad.is_some() { - self.gamepad = new.gamepad; - } - if new.eyes.is_some() { - self.eyes = new.eyes; - } - if new.hand_tracking_left.is_some() { - self.hand_tracking_left = new.hand_tracking_left; - } - if new.hand_tracking_right.is_some() { - self.hand_tracking_right = new.hand_tracking_right; - } - if !new.generic_trackers.is_empty() { - self.generic_trackers.extend( - new.generic_trackers - .iter() - .filter(|t| !self.generic_trackers.contains(t)) - .cloned() - .collect::>(), - ); - } - } - - pub fn search_log_for_generic_trackers(&mut self, s: &str) { - if s.starts_with(GENERIC_TRACKER_PREFIX) { - let n_tracker = s.trim_start_matches(GENERIC_TRACKER_PREFIX); - if !self.generic_trackers.contains(&n_tracker.to_string()) { - self.generic_trackers.push(n_tracker.into()); + let new_dev_types = new + .iter() + .filter_map(|d| { + if d.dev_type == XRDeviceType::GenericTracker { + return None; + } + Some(d.dev_type) + }) + .collect::>(); + let mut res = old + .iter() + .filter(|d| !new_dev_types.contains(&d.dev_type)) + .map(Self::clone) + .collect::>(); + let old_tracker_ids = old + .iter() + .filter_map(|d| { + if d.dev_type == XRDeviceType::GenericTracker { + return Some(d.id.clone()); + } + None + }) + .collect::>(); + for n_dev in new { + if n_dev.dev_type == XRDeviceType::GenericTracker { + if !old_tracker_ids.contains(&n_dev.id) { + res.push(n_dev.clone()); + } + } else { + res.push(n_dev.clone()); } } + res } }