Merge branch 'feat/genericTrackersFromLibmonado' into 'main'

feat: list generic trackers from libmonado, remove log parsing

See merge request gabmus/envision!27
This commit is contained in:
GabMus 2024-06-15 20:11:38 +00:00
commit a081639a6f
14 changed files with 70 additions and 683 deletions

27
Cargo.lock generated
View file

@ -67,17 +67,17 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bindgen"
version = "0.68.1"
version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
dependencies = [
"bitflags 2.4.0",
"cexpr",
"clang-sys",
"itertools",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
@ -230,9 +230,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "dlopen2"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03"
checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6"
dependencies = [
"dlopen2_derive",
"libc",
@ -967,6 +967,15 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -1068,7 +1077,7 @@ dependencies = [
[[package]]
name = "libmonado-rs"
version = "0.1.0"
source = "git+https://github.com/technobaboo/libmonado-rs#d79d5bf11586b8010d8aa016097ebdb07f683c64"
source = "git+https://github.com/technobaboo/libmonado-rs?rev=e32e78c79ce9ec4a5a5de9eff30661c6c4307347#e32e78c79ce9ec4a5a5de9eff30661c6c4307347"
dependencies = [
"bindgen",
"cmake",
@ -1378,12 +1387,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.3.0"

View file

@ -18,7 +18,7 @@ lazy_static = "1.4.0"
libadwaita = { version = "0.6.0", features = [
"v1_4"
] }
libmonado-rs = { git = "https://github.com/technobaboo/libmonado-rs", version = "0.1.0" }
libmonado-rs = { git = "https://github.com/technobaboo/libmonado-rs", rev = "e32e78c79ce9ec4a5a5de9eff30661c6c4307347" }
rusb = "0.9.4"
nix = { version = "0.29.0", features = [
"fs",

View file

@ -1,5 +1,4 @@
pub mod active_runtime_json;
pub mod monado_autorun;
pub mod monado_config_v0;
pub mod openvrpaths_vrpath;
pub mod wivrn_config;

View file

@ -1,259 +0,0 @@
use crate::{
file_utils::{deserialize_file, get_writer_legacy},
paths::get_xdg_config_dir,
};
use serde::{Deserialize, Serialize};
use std::slice::Iter;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum XrtInputName {
#[serde(rename = "XRT_INPUT_GENERIC_TRACKER_POSE")]
XrtInputGenericTrackerPose,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum XrtTrackerRole {
#[serde(rename = "/user/vive_tracker_htcx/role/waist")]
ViveTrackerHtcxWaist,
#[serde(rename = "/user/vive_tracker_htcx/role/left_foot")]
ViveTrackerHtcxLeftFoot,
#[serde(rename = "/user/vive_tracker_htcx/role/right_foot")]
ViveTrackerHtcxRightFoot,
#[serde(rename = "/user/vive_tracker_htcx/role/handheld_object")]
ViveTrackerHtcxHandheldObject,
#[serde(rename = "/user/vive_tracker_htcx/role/left_shoulder")]
ViveTrackerHtcxLeftShoulder,
#[serde(rename = "/user/vive_tracker_htcx/role/right_shoulder")]
ViveTrackerHtcxRightShoulder,
#[serde(rename = "/user/vive_tracker_htcx/role/left_elbow")]
ViveTrackerHtcxLeftElbow,
#[serde(rename = "/user/vive_tracker_htcx/role/right_elbow")]
ViveTrackerHtcxRightElbow,
#[serde(rename = "/user/vive_tracker_htcx/role/left_knee")]
ViveTrackerHtcxLeftKnee,
#[serde(rename = "/user/vive_tracker_htcx/role/right_knee")]
ViveTrackerHtcxRightKnee,
#[serde(rename = "/user/vive_tracker_htcx/role/chest")]
ViveTrackerHtcxChest,
#[serde(rename = "/user/vive_tracker_htcx/role/camera")]
ViveTrackerHtcxCamera,
#[serde(rename = "/user/vive_tracker_htcx/role/keyboard")]
ViveTrackerHtcxKeyboard,
}
impl XrtTrackerRole {
pub fn to_picker_string(&self) -> &str {
match self {
Self::ViveTrackerHtcxWaist => "Waist",
Self::ViveTrackerHtcxLeftFoot => "Left foot",
Self::ViveTrackerHtcxRightFoot => "Right foot",
Self::ViveTrackerHtcxHandheldObject => "Handheld object",
Self::ViveTrackerHtcxLeftShoulder => "Left shoulder",
Self::ViveTrackerHtcxRightShoulder => "Right shoulder",
Self::ViveTrackerHtcxLeftElbow => "Left elbow",
Self::ViveTrackerHtcxRightElbow => "Right elbow",
Self::ViveTrackerHtcxLeftKnee => "Left knee",
Self::ViveTrackerHtcxRightKnee => "Right knee",
Self::ViveTrackerHtcxChest => "Chest",
Self::ViveTrackerHtcxCamera => "Camera",
Self::ViveTrackerHtcxKeyboard => "Keyboard",
}
}
pub fn from_picker_string(s: &str) -> Self {
match s.to_lowercase().trim() {
"waist" => Self::ViveTrackerHtcxWaist,
"left foot" => Self::ViveTrackerHtcxLeftFoot,
"right foot" => Self::ViveTrackerHtcxRightFoot,
"handheld object" => Self::ViveTrackerHtcxHandheldObject,
"left shoulder" => Self::ViveTrackerHtcxLeftShoulder,
"right shoulder" => Self::ViveTrackerHtcxRightShoulder,
"left elbow" => Self::ViveTrackerHtcxLeftElbow,
"right elbow" => Self::ViveTrackerHtcxRightElbow,
"left knee" => Self::ViveTrackerHtcxLeftKnee,
"right knee" => Self::ViveTrackerHtcxRightKnee,
"chest" => Self::ViveTrackerHtcxChest,
"camera" => Self::ViveTrackerHtcxCamera,
"keyboard" => Self::ViveTrackerHtcxKeyboard,
_ => Self::ViveTrackerHtcxWaist,
}
}
pub fn iter() -> Iter<'static, Self> {
[
Self::ViveTrackerHtcxWaist,
Self::ViveTrackerHtcxLeftFoot,
Self::ViveTrackerHtcxRightFoot,
Self::ViveTrackerHtcxHandheldObject,
Self::ViveTrackerHtcxLeftShoulder,
Self::ViveTrackerHtcxRightShoulder,
Self::ViveTrackerHtcxLeftElbow,
Self::ViveTrackerHtcxRightElbow,
Self::ViveTrackerHtcxLeftKnee,
Self::ViveTrackerHtcxRightKnee,
Self::ViveTrackerHtcxChest,
Self::ViveTrackerHtcxCamera,
Self::ViveTrackerHtcxKeyboard,
]
.iter()
}
pub fn to_number(&self) -> u32 {
match self {
Self::ViveTrackerHtcxWaist => 0,
Self::ViveTrackerHtcxLeftFoot => 1,
Self::ViveTrackerHtcxRightFoot => 2,
Self::ViveTrackerHtcxHandheldObject => 3,
Self::ViveTrackerHtcxLeftShoulder => 4,
Self::ViveTrackerHtcxRightShoulder => 5,
Self::ViveTrackerHtcxLeftElbow => 6,
Self::ViveTrackerHtcxRightElbow => 7,
Self::ViveTrackerHtcxLeftKnee => 8,
Self::ViveTrackerHtcxRightKnee => 9,
Self::ViveTrackerHtcxChest => 10,
Self::ViveTrackerHtcxCamera => 11,
Self::ViveTrackerHtcxKeyboard => 12,
}
}
pub fn from_number(n: &u32) -> Self {
match n {
0 => Self::ViveTrackerHtcxWaist,
1 => Self::ViveTrackerHtcxLeftFoot,
2 => Self::ViveTrackerHtcxRightFoot,
3 => Self::ViveTrackerHtcxHandheldObject,
4 => Self::ViveTrackerHtcxLeftShoulder,
5 => Self::ViveTrackerHtcxRightShoulder,
6 => Self::ViveTrackerHtcxLeftElbow,
7 => Self::ViveTrackerHtcxRightElbow,
8 => Self::ViveTrackerHtcxLeftKnee,
9 => Self::ViveTrackerHtcxRightKnee,
10 => Self::ViveTrackerHtcxChest,
11 => Self::ViveTrackerHtcxCamera,
12 => Self::ViveTrackerHtcxKeyboard,
_ => Self::ViveTrackerHtcxWaist,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TrackerRole {
pub device_serial: String,
pub role: XrtTrackerRole,
pub xrt_input_name: XrtInputName,
}
impl Default for TrackerRole {
fn default() -> Self {
Self {
device_serial: "".into(),
role: XrtTrackerRole::ViveTrackerHtcxWaist,
xrt_input_name: XrtInputName::XrtInputGenericTrackerPose,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MonadoConfigV0 {
#[serde(skip_serializing_if = "Option::is_none", rename = "$schema")]
_schema: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tracker_roles: Vec<TrackerRole>,
}
impl Default for MonadoConfigV0 {
fn default() -> Self {
Self {
_schema: Some(
"https://monado.pages.freedesktop.org/monado/config_v0.schema.json".to_string(),
),
tracker_roles: vec![],
}
}
}
impl MonadoConfigV0 {
pub fn has_tracker_serial(&self, serial: &str) -> bool {
self.tracker_roles.iter().any(|t| t.device_serial == serial)
}
pub fn get_next_free_xrt_tracker_role(&self) -> XrtTrackerRole {
XrtTrackerRole::iter()
.find(|role| !self.tracker_roles.iter().any(|et| et.role == **role))
.unwrap_or(&XrtTrackerRole::ViveTrackerHtcxHandheldObject)
.clone()
}
}
fn get_monado_config_v0_path() -> String {
format!(
"{config}/monado/config_v0.json",
config = get_xdg_config_dir()
)
}
fn get_monado_config_v0_from_path(path_s: &String) -> Option<MonadoConfigV0> {
deserialize_file(path_s)
}
pub fn get_monado_config_v0() -> MonadoConfigV0 {
get_monado_config_v0_from_path(&get_monado_config_v0_path())
.unwrap_or(MonadoConfigV0::default())
}
fn dump_monado_config_v0_to_path(config: &MonadoConfigV0, path_s: &String) {
let writer = get_writer_legacy(path_s);
serde_json::to_writer_pretty(writer, config).expect("Unable to save Monado config V0");
}
pub fn dump_monado_config_v0(config: &MonadoConfigV0) {
dump_monado_config_v0_to_path(config, &get_monado_config_v0_path());
}
pub fn dump_generic_trackers(trackers: &[String]) -> usize {
let mut conf = get_monado_config_v0();
let mut added: usize = 0;
trackers.iter().for_each(|serial| {
if !conf.has_tracker_serial(serial) {
conf.tracker_roles.push(TrackerRole {
device_serial: serial.to_string(),
role: conf.get_next_free_xrt_tracker_role(),
..TrackerRole::default()
});
added += 1;
}
});
if added > 0 {
dump_monado_config_v0(&conf);
}
added
}
#[cfg(test)]
mod tests {
use super::get_monado_config_v0_from_path;
use crate::file_builders::monado_config_v0::{XrtInputName, XrtTrackerRole};
#[test]
fn can_read_monado_config_v0() {
let conf = get_monado_config_v0_from_path(&"./test/files/monado_config_v0.json".into())
.expect("Couldn't find monado config v0");
assert_eq!(conf.tracker_roles.len(), 3);
assert_eq!(
conf.tracker_roles
.get(0)
.expect("could not get tracker role zero")
.role,
XrtTrackerRole::ViveTrackerHtcxWaist
);
assert_eq!(
conf.tracker_roles
.get(0)
.expect("could not get tracker role zero")
.xrt_input_name,
XrtInputName::XrtInputGenericTrackerPose
);
}
}

View file

@ -51,6 +51,7 @@ impl LinuxDistro {
let mut buf = String::new();
if reader.read_to_string(&mut buf).is_ok() {
buf = buf.trim().to_lowercase();
return Self::name_matcher(&buf);
}
}

View file

@ -4,11 +4,7 @@ use crate::{
profile::Profile,
};
use anyhow::bail;
use std::{
fs::read_to_string,
io::{Read, Write},
path::Path,
};
use std::{fs::read_to_string, io::Write, path::Path};
fn get_runtime_entrypoint_path() -> Option<String> {
let mut out = format!(

View file

@ -3,7 +3,6 @@ use super::alert::{alert, alert_w_widget};
use super::build_window::{BuildStatus, BuildWindow};
use super::cmdline_opts::CmdLineOpts;
use super::debug_view::{DebugView, DebugViewMsg};
use super::fbt_config_editor::{FbtConfigEditor, FbtConfigEditorInit, FbtConfigEditorMsg};
use super::job_worker::internal_worker::JobWorkerOut;
use super::job_worker::job::WorkerJob;
use super::job_worker::JobWorker;
@ -35,7 +34,6 @@ use crate::file_builders::openvrpaths_vrpath::{
};
use crate::file_utils::setcap_cap_sys_nice_eip;
use crate::linux_distro::LinuxDistro;
use crate::log_parser::MonadoLog;
use crate::paths::{get_data_dir, get_ipc_file_path};
use crate::profile::{Profile, XRServiceType};
use crate::stateless_action;
@ -100,8 +98,6 @@ pub struct App {
#[tracker::do_not_track]
xr_devices: Vec<XRDevice>,
#[tracker::do_not_track]
fbt_config_editor: Option<Controller<FbtConfigEditor>>,
#[tracker::do_not_track]
libmonado: Option<libmonado_rs::Monado>,
#[tracker::do_not_track]
@ -132,8 +128,6 @@ pub enum Msg {
OpenLibsurviveSetup,
SaveWinSize(i32, i32),
Quit,
ParseLog(Vec<String>),
ConfigFbt,
DebugOpenPrefix,
DebugOpenData,
OpenWivrnConfig,
@ -331,7 +325,6 @@ impl SimpleComponent for App {
match message {
Msg::OnServiceLog(rows) => {
if !rows.is_empty() {
sender.input(Msg::ParseLog(rows.clone()));
self.debug_view
.sender()
.emit(DebugViewMsg::LogUpdated(rows));
@ -387,20 +380,6 @@ impl SimpleComponent for App {
}
}
}
Msg::ParseLog(rows) => {
for row in rows {
match MonadoLog::new_from_str(row.as_str()) {
None => {}
Some(parsed) => {
if let Some(tracker) =
XRDevice::generic_tracker_from_log_row(parsed.message.as_str())
{
self.xr_devices.push(tracker);
}
}
};
}
}
Msg::EnableDebugViewChanged(val) => {
self.set_enable_debug_view(val);
self.config.debug_view_enabled = val;
@ -685,19 +664,6 @@ impl SimpleComponent for App {
))
.expect("Failed to present Libsurvive Setup Window");
}
Msg::ConfigFbt => {
self.fbt_config_editor = Some(
FbtConfigEditor::builder()
.launch(FbtConfigEditorInit {
root_win: self.app_win.clone().upcast::<gtk::Window>(),
})
.detach(),
);
self.fbt_config_editor
.as_ref()
.unwrap()
.emit(FbtConfigEditorMsg::Present);
}
Msg::SaveWinSize(w, h) => {
self.config.win_size = [w, h];
self.config.save();
@ -829,7 +795,6 @@ impl SimpleComponent for App {
autostart_worker: None,
build_worker: None,
xr_devices: vec![],
fbt_config_editor: None,
restart_xrservice: false,
libmonado: None,
wivrn_conf_editor: None,
@ -853,13 +818,6 @@ impl SimpleComponent for App {
sender.input_sender().emit(Msg::BuildProfile(true));
})
);
stateless_action!(
actions,
ConfigFbtAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::ConfigFbt);
})
);
{
let abd_sender = model.about_dialog.sender().clone();
stateless_action!(actions, AboutAction, move |_| {
@ -928,7 +886,6 @@ new_action_group!(pub AppActionGroup, "win");
new_stateless_action!(pub AboutAction, AppActionGroup, "about");
new_stateless_action!(pub BuildProfileAction, AppActionGroup, "buildprofile");
new_stateless_action!(pub BuildProfileCleanAction, AppActionGroup, "buildprofileclean");
new_stateless_action!(pub ConfigFbtAction, AppActionGroup, "configfbt");
new_stateless_action!(pub QuitAction, AppActionGroup, "quit");
new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool);

View file

@ -1,11 +1,5 @@
use super::{
alert::alert,
factories::device_row_factory::{DeviceRowModel, DeviceRowModelInit, DeviceRowState},
};
use crate::{
file_builders::monado_config_v0::dump_generic_trackers,
xr_devices::{XRDevice, XRDeviceRole},
};
use super::factories::device_row_factory::{DeviceRowModel, DeviceRowModelInit, DeviceRowState};
use crate::xr_devices::{XRDevice, XRDeviceRole};
use adw::prelude::*;
use relm4::{factory::AsyncFactoryVecDeque, prelude::*, Sender};
@ -20,25 +14,6 @@ pub struct DevicesBox {
#[derive(Debug)]
pub enum DevicesBoxMsg {
UpdateDevices(Vec<XRDevice>),
DumpGenericTrackers,
}
impl DevicesBox {
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();
btn.connect_clicked(move |_| {
sender.emit(DevicesBoxMsg::DumpGenericTrackers);
});
btn
}
}
#[relm4::component(pub)]
@ -106,13 +81,9 @@ impl SimpleComponent for DevicesBox {
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>(),
.map(|d| format!("{} ({})", d.name, d.index))
.collect::<Vec<String>>()
.join("\n"),
),
..Default::default()
});
@ -142,41 +113,6 @@ impl SimpleComponent for DevicesBox {
}
}
}
Self::Input::DumpGenericTrackers => {
let added = dump_generic_trackers(
&self
.devices
.iter()
.filter(|d| d.dev_type == XRDeviceRole::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",
concat!(
"All the currently connected trackers ",
"are already present in your configuration"
),
),
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(),
concat!(
"Edit your configuration to make sure that ",
"all the trackers have the appropriate roles assigned"
),
),
};
alert(title, Some(msg), None);
}
}
}

View file

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

View file

@ -1,106 +0,0 @@
use crate::{
file_builders::monado_config_v0::{TrackerRole, XrtTrackerRole},
ui::fbt_config_editor::FbtConfigEditorMsg,
ui::preference_rows::{combo_row, entry_row},
};
use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryComponent, prelude::*, AsyncFactorySender};
#[derive(Debug)]
pub struct TrackerRoleModel {
index: usize,
tracker_role: TrackerRole,
}
pub struct TrackerRoleModelInit {
pub index: usize,
pub tracker_role: Option<TrackerRole>,
}
#[derive(Debug)]
pub enum TrackerRoleModelMsg {
Changed(TrackerRole),
Delete,
}
#[derive(Debug)]
pub enum TrackerRoleModelOutMsg {
Changed(usize, TrackerRole),
Delete(usize),
}
#[relm4::factory(async pub)]
impl AsyncFactoryComponent for TrackerRoleModel {
type Init = TrackerRoleModelInit;
type Input = TrackerRoleModelMsg;
type Output = TrackerRoleModelOutMsg;
type CommandOutput = ();
type ParentWidget = adw::PreferencesPage;
view! {
root = adw::PreferencesGroup {
set_title: "Tracker",
#[wrap(Some)]
set_header_suffix: del_btn = &gtk::Button {
set_icon_name: "edit-delete-symbolic",
set_tooltip_text: Some("Delete Tracker"),
set_valign: gtk::Align::Center,
add_css_class: "flat",
add_css_class: "circular",
connect_clicked[sender] => move |_| {
sender.input(Self::Input::Delete);
}
},
add: {
let tr = self.tracker_role.clone();
&entry_row(
"Device serial",
self.tracker_role.device_serial.as_str(),
clone!(@strong sender => move |row| {
let mut ntr = tr.clone();
ntr.device_serial = row.text().to_string();
sender.input(Self::Input::Changed(ntr));
})
)
},
add: {
let tr = self.tracker_role.clone();
&combo_row("Tracker role", None, &tr.role.clone().to_picker_string(),
XrtTrackerRole::iter()
.map(XrtTrackerRole::to_picker_string)
.map(String::from)
.collect::<Vec<String>>(),
clone!(@strong sender => move |row| {
let mut ntr = tr.clone();
ntr.role = XrtTrackerRole::from_number(&row.selected());
sender.input(Self::Input::Changed(ntr));
})
)
},
}
}
async fn update(&mut self, message: Self::Input, sender: AsyncFactorySender<Self>) {
match message {
Self::Input::Changed(r) => {
self.tracker_role = r;
sender.output(Self::Output::Changed(self.index, self.tracker_role.clone()));
}
Self::Input::Delete => {
sender.output(Self::Output::Delete(self.index));
}
}
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
index: init.index,
tracker_role: init.tracker_role.unwrap_or_else(|| TrackerRole::default()),
}
}
}

View file

@ -1,173 +0,0 @@
use super::factories::tracker_role_group_factory::{TrackerRoleModel, TrackerRoleModelOutMsg};
use crate::{
file_builders::monado_config_v0::{
dump_monado_config_v0, get_monado_config_v0, MonadoConfigV0, TrackerRole,
},
ui::factories::tracker_role_group_factory::TrackerRoleModelInit,
};
use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
#[tracker::track]
pub struct FbtConfigEditor {
monado_config_v0: MonadoConfigV0,
#[tracker::do_not_track]
win: Option<adw::Window>,
#[tracker::do_not_track]
tracker_role_groups: AsyncFactoryVecDeque<TrackerRoleModel>,
}
#[derive(Debug)]
pub enum FbtConfigEditorMsg {
Present,
Save,
TrackerRoleNew,
TrackerRoleChanged(usize, TrackerRole),
TrackerRoleDeleted(usize),
Repopulate,
}
pub struct FbtConfigEditorInit {
pub root_win: gtk::Window,
}
#[relm4::component(pub)]
impl SimpleComponent for FbtConfigEditor {
type Init = FbtConfigEditorInit;
type Input = FbtConfigEditorMsg;
type Output = ();
view! {
#[name(win)]
adw::Window {
set_title: Some("Full Body Trackers"),
set_modal: true,
set_transient_for: Some(&init.root_win),
set_default_height: 500,
set_default_width: 600,
adw::ToolbarView {
set_top_bar_style: adw::ToolbarStyle::Flat,
add_top_bar: headerbar = &adw::HeaderBar {
set_vexpand: false,
set_hexpand: true,
add_css_class: "flat",
pack_start: &add_btn,
pack_end: &save_btn,
},
set_content: Some(&model.tracker_role_groups.widget().clone().upcast::<gtk::Widget>()),
},
}
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset();
match message {
Self::Input::Present => {
self.win.as_ref().unwrap().present();
}
Self::Input::Save => {
dump_monado_config_v0(&self.monado_config_v0);
self.win.as_ref().unwrap().close();
}
Self::Input::TrackerRoleChanged(index, n_role) => {
let role = self.monado_config_v0.tracker_roles.get_mut(index).unwrap();
role.device_serial = n_role.device_serial;
role.role = n_role.role;
role.xrt_input_name = n_role.xrt_input_name;
}
Self::Input::TrackerRoleDeleted(index) => {
self.monado_config_v0.tracker_roles.remove(index);
sender.input(Self::Input::Repopulate);
}
Self::Input::TrackerRoleNew => {
self.monado_config_v0
.tracker_roles
.push(TrackerRole::default());
self.tracker_role_groups
.guard()
.push_back(TrackerRoleModelInit {
index: self.monado_config_v0.tracker_roles.len() - 1,
tracker_role: None,
});
}
Self::Input::Repopulate => {
self.populate_tracker_roles();
}
}
}
fn init(
init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let page = adw::PreferencesPage::builder().build();
let add_btn = gtk::Button::builder()
.tooltip_text("Add Tracker")
.icon_name("list-add-symbolic")
.build();
let save_btn = gtk::Button::builder()
.label("Save")
.css_classes(["suggested-action"])
.build();
add_btn.connect_clicked(clone!(@strong sender => move |_| {
sender.input(Self::Input::TrackerRoleNew);
}));
save_btn.connect_clicked(clone!(@strong sender => move |_| {
sender.input(Self::Input::Save);
}));
let mut model = Self {
win: None,
tracker: 0,
monado_config_v0: get_monado_config_v0(),
tracker_role_groups: AsyncFactoryVecDeque::builder().launch(page).forward(
sender.input_sender(),
|msg| match msg {
TrackerRoleModelOutMsg::Changed(index, tracker_role) => {
Self::Input::TrackerRoleChanged(index, tracker_role.clone())
}
TrackerRoleModelOutMsg::Delete(index) => Self::Input::TrackerRoleDeleted(index),
},
),
};
model.populate_tracker_roles();
let widgets = view_output!();
model.win = Some(widgets.win.clone());
widgets.headerbar.pack_start(&add_btn);
widgets.headerbar.pack_end(&save_btn);
{
let win = widgets.win.clone();
let sc = gtk::ShortcutController::new();
sc.add_shortcut(gtk::Shortcut::new(
gtk::ShortcutTrigger::parse_string("Escape"),
Some(gtk::CallbackAction::new(move |_, _| {
win.close();
gtk::glib::Propagation::Proceed
})),
));
widgets.win.add_controller(sc);
}
ComponentParts { model, widgets }
}
}
impl FbtConfigEditor {
fn populate_tracker_roles(&mut self) {
let mut guard = self.tracker_role_groups.guard();
guard.clear();
for (i, v) in self.monado_config_v0.tracker_roles.iter().enumerate() {
guard.push_back(TrackerRoleModelInit {
index: i,
tracker_role: Some(v.clone()),
});
}
}
}

View file

@ -11,8 +11,7 @@ use crate::gpu_profile::{get_amd_gpu_power_profile, GpuPowerProfile};
use crate::profile::{LighthouseDriver, Profile, XRServiceType};
use crate::steamvr_utils::chaperone_info_exists;
use crate::ui::app::{
AboutAction, BuildProfileAction, BuildProfileCleanAction, ConfigFbtAction,
DebugViewToggleAction,
AboutAction, BuildProfileAction, BuildProfileCleanAction, DebugViewToggleAction,
};
use crate::ui::profile_editor::ProfileEditorInit;
use crate::ui::steamvr_calibration_box::SteamVrCalibrationBoxMsg;
@ -114,7 +113,6 @@ impl SimpleComponent for MainView {
"_Debug View" => DebugViewToggleAction,
"_Build Profile" => BuildProfileAction,
"C_lean Build Profile" => BuildProfileCleanAction,
"Configure Full Body _Tracking" => ConfigFbtAction,
},
section! {
"_About" => AboutAction,

View file

@ -7,7 +7,6 @@ pub mod cmdline_opts;
mod debug_view;
mod devices_box;
mod factories;
mod fbt_config_editor;
mod install_wivrn_box;
pub mod job_worker;
mod libsurvive_setup_window;

View file

@ -1,3 +1,4 @@
use libmonado_rs;
use std::{fmt::Display, slice::Iter};
const GENERIC_TRACKER_PREFIX: &str = "Found generic tracker device: ";
@ -105,7 +106,6 @@ impl XRDeviceRole {
Self::Chest,
Self::Camera,
Self::Keyboard,
Self::GenericTracker,
]
.iter()
}
@ -228,7 +228,8 @@ impl XRDeviceRole {
pub struct XRDevice {
pub dev_type: XRDeviceRole,
pub name: String,
pub id: String,
pub index: String,
pub serial: Option<String>,
pub battery: Option<f32>, // battery percentage, from 0 to 1 maybe
// still need to implement it in monado
}
@ -238,19 +239,21 @@ impl Default for XRDevice {
Self {
dev_type: XRDeviceRole::GenericTracker,
name: String::default(),
id: String::default(),
index: String::default(),
serial: None,
battery: Option::default(),
}
}
}
impl XRDevice {
#[deprecated]
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: XRDeviceRole::GenericTracker,
id: n_tracker.into(),
index: n_tracker.into(),
..Default::default()
});
}
@ -274,6 +277,7 @@ impl XRDevice {
pub fn from_libmonado(monado: &libmonado_rs::Monado) -> Vec<Self> {
let mut res = vec![];
let mut devs_with_role = vec![];
[
XRDeviceRole::Head,
XRDeviceRole::Left,
@ -299,17 +303,48 @@ impl XRDevice {
.iter()
.for_each(|xrd| {
if let Ok(dev) = monado.device_from_role(xrd.to_monado_str()) {
devs_with_role.push(dev.id);
// let serial = match dev.serial() {
// Ok(s) => {
// serials.push(s.clone());
// Some(s)
// }
// Err(e) => {
// eprintln!(
// "Warning: could not get serial for monado device {} ({}): {:#?}",
// dev.name, dev.id, e
// );
// None
// }
// };
res.push(Self {
name: dev.name,
id: dev.id.to_string(),
index: dev.id.to_string(),
// serial,
serial: None,
dev_type: xrd.clone(),
..Default::default()
})
}
});
if let Ok(all_devs) = monado.devices() {
all_devs
.into_iter()
.filter(|dev| !devs_with_role.contains(&dev.id))
.for_each(|dev_gt| {
res.push(Self {
name: dev_gt.name.clone(),
index: dev_gt.id.to_string(),
serial: dev_gt.serial().ok(),
dev_type: XRDeviceRole::GenericTracker,
..Default::default()
})
})
}
res
}
#[deprecated]
pub fn from_log_message(s: &str) -> Vec<Self> {
let mut res = vec![];
let rows = s.split('\n');
@ -359,19 +394,21 @@ impl XRDevice {
.filter(|d| !new_dev_types.contains(&d.dev_type))
.map(Self::clone)
.collect::<Vec<Self>>();
let old_tracker_ids = old
let old_tracker_serials = old
.iter()
.filter_map(|d| {
if d.dev_type == XRDeviceRole::GenericTracker {
return Some(d.id.clone());
return d.serial.clone();
}
None
})
.collect::<Vec<String>>();
for n_dev in new {
if n_dev.dev_type == XRDeviceRole::GenericTracker {
if !old_tracker_ids.contains(&n_dev.id) {
res.push(n_dev.clone());
if let Some(n_serial) = n_dev.serial.as_ref() {
if !old_tracker_serials.contains(n_serial) {
res.push(n_dev.clone());
}
}
} else {
res.push(n_dev.clone());