feat!: refactor xr devices around vec

This commit is contained in:
Gabriele Musco 2023-09-17 20:44:30 +02:00
commit cd7fe1ac7b
No known key found for this signature in database
GPG key ID: 1068D795C80E51DE
6 changed files with 451 additions and 276 deletions

View file

@ -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<Profile>,
#[tracker::do_not_track]
xr_devices: XRDevices,
xr_devices: Vec<XRDevice>,
#[tracker::do_not_track]
fbt_config_editor: Option<Controller<FbtConfigEditor>>,
#[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<Profile> {
@ -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,

View file

@ -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<XRDevices>,
devices: Vec<XRDevice>,
#[tracker::do_not_track]
device_rows: FactoryVecDeque<DeviceRowModel>,
}
#[derive(Debug)]
pub enum DevicesBoxMsg {
UpdateDevices(Option<XRDevices>),
UpdateDevices(Vec<XRDevice>),
DumpGenericTrackers,
}
impl DevicesBox {
fn get_dev(&self, key: XRDevice) -> Option<String> {
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<DevicesBoxMsg>) -> 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,159 +55,151 @@ 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 = &gtk::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<Self>) {
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
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<DeviceRowModelInit> = 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::<Vec<&str>>()
.join(", "),
),
suffix: Some(
Self::create_save_trackers_btn(sender.input_sender().clone())
.upcast::<gtk::Widget>(),
),
..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 added = dump_generic_trackers(
&self
.devices
.iter()
.filter(|d| d.dev_type == XRDeviceType::GenericTracker)
.map(|d| d.id.clone())
.collect::<Vec<String>>(),
);
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"
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"
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"
concat!(
"Edit your configuration to make sure that ",
"all the trackers have the appropriate roles assigned"
),
),
};
alert(title, Some(msg), None);
}
}
}
}
fn init(
_init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
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!();

View file

@ -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<gtk::Widget>,
}
#[derive(Debug, Default)]
pub struct DeviceRowModelInit {
pub title: Option<String>,
pub subtitle: Option<String>,
pub state: Option<DeviceRowState>,
pub suffix: Option<gtk::Widget>,
}
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 = &gtk::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: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
_sender: FactorySender<Self>,
) -> 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 {
Self {
title: init.title.unwrap_or_default(),
subtitle: init.subtitle.unwrap_or_default(),
state: init.state.unwrap_or_default(),
suffix: init.suffix,
}
}
}

View file

@ -1,2 +1,3 @@
pub mod env_var_row_factory;
pub mod tracker_role_group_factory;
pub mod device_row_factory;

View file

@ -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<XRDevices>),
UpdateDevices(Vec<XRDevice>),
}
#[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()

View file

@ -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<std::cmp::Ordering> {
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<String>,
pub left: Option<String>,
pub right: Option<String>,
pub gamepad: Option<String>,
pub eyes: Option<String>,
pub hand_tracking_left: Option<String>,
pub hand_tracking_right: Option<String>,
pub generic_trackers: Vec<String>,
}
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<Self> {
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<Self> {
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<Self> {
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<Self> {
pub fn from_log_message(s: &str) -> Vec<Self> {
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::<Vec<&str>>()[..] {
[_, "<none>"] => {}
["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);
res
}
pub fn merge(old: &[Self], new: &[Self]) -> Vec<Self> {
if old.is_empty() {
return Vec::from(new);
}
let new_dev_types = new
.iter()
.filter_map(|d| {
if d.dev_type == XRDeviceType::GenericTracker {
return None;
}
Some(d.dev_type)
})
.collect::<Vec<XRDeviceType>>();
let mut res = old
.iter()
.filter(|d| !new_dev_types.contains(&d.dev_type))
.map(Self::clone)
.collect::<Vec<Self>>();
let old_tracker_ids = old
.iter()
.filter_map(|d| {
if d.dev_type == XRDeviceType::GenericTracker {
return Some(d.id.clone());
}
None
})
.collect::<Vec<String>>();
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());
}
pub fn merge(&mut self, new: Self) {
if new.head.is_some() {
self.head = new.head;
}
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::<Vec<String>>(),
);
}
}
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());
} else {
res.push(n_dev.clone());
}
}
res
}
}