Merge branch 'feat/integrate-libmonado' into 'main'

feat: integrate libmonado

See merge request gabmus/envision!13
This commit is contained in:
GabMus 2023-09-24 09:21:28 +00:00
commit 356e1152e8
15 changed files with 784 additions and 248 deletions

View file

@ -23,7 +23,11 @@ cargo:test:
- echo 'deb http://deb.debian.org/debian experimental main' > /etc/apt/sources.list.d/experimental.list - echo 'deb http://deb.debian.org/debian experimental main' > /etc/apt/sources.list.d/experimental.list
- apt-get update - apt-get update
- apt-get -t experimental install libgtk-4-dev libadwaita-1-dev libgtksourceview-5-dev libssl-dev -y - apt-get -t experimental install libgtk-4-dev libadwaita-1-dev libgtksourceview-5-dev libssl-dev -y
- apt-get install rust-all cargo meson ninja-build git desktop-file-utils gettext libjxl-dev file libusb-dev libusb-1.0-0-dev -y - apt-get install meson ninja-build git desktop-file-utils gettext libjxl-dev file libusb-dev libusb-1.0-0-dev curl -y
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh
- chmod +x /tmp/rustup.sh
- /tmp/rustup.sh -y
- source "$HOME/.cargo/env"
- rustc --version && cargo --version # Print version info for debugging - rustc --version && cargo --version # Print version info for debugging
- meson setup build -Dprefix="$PWD/build/localprefix" -Dprofile=development - meson setup build -Dprefix="$PWD/build/localprefix" -Dprofile=development
- ninja -C build - ninja -C build
@ -38,7 +42,11 @@ appimage:
- echo 'deb http://deb.debian.org/debian experimental main' > /etc/apt/sources.list.d/experimental.list - echo 'deb http://deb.debian.org/debian experimental main' > /etc/apt/sources.list.d/experimental.list
- apt-get update - apt-get update
- apt-get -t experimental install libgtk-4-dev libadwaita-1-dev libgtksourceview-5-dev libssl-dev -y - apt-get -t experimental install libgtk-4-dev libadwaita-1-dev libgtksourceview-5-dev libssl-dev -y
- apt-get install rust-all cargo meson ninja-build git desktop-file-utils gettext libjxl-dev file libusb-dev libusb-1.0-0-dev -y - apt-get install meson ninja-build git desktop-file-utils gettext libjxl-dev file libusb-dev libusb-1.0-0-dev curl -y
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh
- chmod +x /tmp/rustup.sh
- /tmp/rustup.sh -y
- source "$HOME/.cargo/env"
- bash ./dist/appimage/build_appimage.sh - bash ./dist/appimage/build_appimage.sh
artifacts: artifacts:
paths: paths:

204
Cargo.lock generated
View file

@ -70,6 +70,29 @@ version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]]
name = "bindgen"
version = "0.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
dependencies = [
"bitflags 2.4.0",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.33",
"which",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.2.0" version = "0.2.0"
@ -150,6 +173,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.15.5" version = "0.15.5"
@ -166,6 +198,35 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
dependencies = [
"cc",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -182,6 +243,35 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "dlopen2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03"
dependencies = [
"dlopen2_derive",
"libc",
"once_cell",
"winapi",
]
[[package]]
name = "dlopen2_derive"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.33" version = "0.8.33"
@ -199,7 +289,9 @@ dependencies = [
"gettext-rs", "gettext-rs",
"git2", "git2",
"gtk4", "gtk4",
"lazy_static",
"libadwaita", "libadwaita",
"libmonado-rs",
"libusb", "libusb",
"nix", "nix",
"phf", "phf",
@ -257,6 +349,12 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "flagset"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779"
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.11.0" version = "0.11.0"
@ -585,6 +683,12 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "gobject-sys" name = "gobject-sys"
version = "0.18.0" version = "0.18.0"
@ -747,6 +851,15 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "home"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "0.2.9"
@ -884,6 +997,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libadwaita" name = "libadwaita"
version = "0.5.2" version = "0.5.2"
@ -936,6 +1055,29 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libmonado-rs"
version = "0.1.0"
source = "git+https://github.com/technobaboo/libmonado-rs#3b3f098cb131843ee90f078e26362fcefe02b822"
dependencies = [
"bindgen",
"cmake",
"convert_case",
"dlopen2",
"flagset",
"semver",
]
[[package]] [[package]]
name = "libssh2-sys" name = "libssh2-sys"
version = "0.3.0" version = "0.3.0"
@ -1057,6 +1199,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -1117,6 +1265,16 @@ dependencies = [
"pin-utils", "pin-utils",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -1240,6 +1398,12 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.0" version = "2.3.0"
@ -1311,6 +1475,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "prettyplease"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
"syn 2.0.33",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@ -1514,6 +1688,12 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -1638,6 +1818,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@ -1977,6 +2163,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.1" version = "2.4.1"
@ -2107,6 +2299,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View file

@ -14,9 +14,11 @@ git2 = "0.17.2"
gtk4 = { version = "0.7.2", features = [ gtk4 = { version = "0.7.2", features = [
"v4_10", "v4_10",
] } ] }
lazy_static = "1.4.0"
libadwaita = { version = "0.5.2", features = [ libadwaita = { version = "0.5.2", features = [
"v1_3" "v1_3"
] } ] }
libmonado-rs = { git = "https://github.com/technobaboo/libmonado-rs", version = "0.1.0" }
libusb = "0.3.0" libusb = "0.3.0"
nix = { version = "0.26.4", features = [ nix = { version = "0.26.4", features = [
"fs" "fs"

View file

@ -33,6 +33,12 @@ cd envision
./dist/appimage/build_appimage.sh ./dist/appimage/build_appimage.sh
``` ```
# Feature flags
|Env var|Values|Default|
|---|---|---|
|`ENVISION_FF_USE_LIBMONADO`|`1`: enabled; `0`: disabled|`0`|
# Common issues # Common issues
## NOSUID with systemd-homed ## NOSUID with systemd-homed

View file

@ -30,6 +30,7 @@ pub mod func_runner;
pub mod gpu_profile; pub mod gpu_profile;
pub mod log_level; pub mod log_level;
pub mod log_parser; pub mod log_parser;
pub mod monado_utils;
pub mod paths; pub mod paths;
pub mod profile; pub mod profile;
pub mod profiles; pub mod profiles;

11
src/monado_utils.rs Normal file
View file

@ -0,0 +1,11 @@
use crate::profile::Profile;
pub fn get_devs(prof: &Profile) {
if let Ok(monado) = libmonado_rs::Monado::create(prof.libmonado_so().unwrap()) {
if let Ok(devs) = monado.devices() {
for dev in devs {
println!(">>> {}", dev.name);
}
}
}
}

View file

@ -350,6 +350,23 @@ impl Profile {
pub fn can_start(&self) -> bool { pub fn can_start(&self) -> bool {
Path::new(&self.xrservice_binary()).is_file() Path::new(&self.xrservice_binary()).is_file()
} }
pub fn libmonado_so(&self) -> Option<String> {
let mut res = format!("{}/lib/libmonado.so", self.prefix);
if Path::new(&res).is_file() {
return Some(res);
}
res = format!("{}/lib64/libmonado.so", self.prefix);
if Path::new(&res).is_file() {
return Some(res);
}
None
}
pub fn has_libmonado(&self) -> bool {
self.libmonado_so().is_some()
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -3,6 +3,7 @@ use super::alert::alert;
use super::build_window::{BuildStatus, BuildWindow}; use super::build_window::{BuildStatus, BuildWindow};
use super::debug_view::{DebugView, DebugViewMsg}; use super::debug_view::{DebugView, DebugViewMsg};
use super::fbt_config_editor::{FbtConfigEditor, FbtConfigEditorInit, FbtConfigEditorMsg}; use super::fbt_config_editor::{FbtConfigEditor, FbtConfigEditorInit, FbtConfigEditorMsg};
use super::feature_flags::FF_LIBMONADO_DEVICE_ENUMERATION_ENABLED;
use super::job_worker::internal_worker::JobWorkerOut; use super::job_worker::internal_worker::JobWorkerOut;
use super::job_worker::job::WorkerJob; use super::job_worker::job::WorkerJob;
use super::job_worker::JobWorker; use super::job_worker::JobWorker;
@ -43,7 +44,7 @@ use crate::ui::build_window::{BuildWindowMsg, BuildWindowOutMsg};
use crate::ui::debug_view::DebugViewInit; use crate::ui::debug_view::DebugViewInit;
use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg; use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg;
use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg}; use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg};
use crate::xr_devices::XRDevices; use crate::xr_devices::XRDevice;
use crate::{stateless_action, withclones}; use crate::{stateless_action, withclones};
use gtk::prelude::*; use gtk::prelude::*;
use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup}; use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup};
@ -91,9 +92,11 @@ pub struct App {
#[tracker::do_not_track] #[tracker::do_not_track]
profiles: Vec<Profile>, profiles: Vec<Profile>,
#[tracker::do_not_track] #[tracker::do_not_track]
xr_devices: XRDevices, xr_devices: Vec<XRDevice>,
#[tracker::do_not_track] #[tracker::do_not_track]
fbt_config_editor: Option<Controller<FbtConfigEditor>>, fbt_config_editor: Option<Controller<FbtConfigEditor>>,
#[tracker::do_not_track]
libmonado: Option<libmonado_rs::Monado>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -162,7 +165,7 @@ impl App {
return; return;
}; };
self.debug_view.sender().emit(DebugViewMsg::ClearLog); self.debug_view.sender().emit(DebugViewMsg::ClearLog);
self.xr_devices = XRDevices::default(); self.xr_devices = vec![];
if prof.can_start() { if prof.can_start() {
remove_file(&get_ipc_file_path(&prof.xrservice_type)) remove_file(&get_ipc_file_path(&prof.xrservice_type))
.is_err() .is_err()
@ -218,11 +221,12 @@ impl App {
if let Some(worker) = self.xrservice_worker.as_ref() { if let Some(worker) = self.xrservice_worker.as_ref() {
worker.stop(); worker.stop();
} }
self.libmonado = None;
self.restore_openxr_openvr_files(); self.restore_openxr_openvr_files();
self.main_view self.main_view
.sender() .sender()
.emit(MainViewMsg::XRServiceActiveChanged(false, None)); .emit(MainViewMsg::XRServiceActiveChanged(false, None));
self.xr_devices = XRDevices::default(); self.xr_devices = vec![];
} }
pub fn profiles_list(config: &Config) -> Vec<Profile> { pub fn profiles_list(config: &Config) -> Vec<Profile> {
@ -309,26 +313,49 @@ impl SimpleComponent for App {
} }
Msg::ClockTicking => { Msg::ClockTicking => {
self.main_view.sender().emit(MainViewMsg::ClockTicking); self.main_view.sender().emit(MainViewMsg::ClockTicking);
if *FF_LIBMONADO_DEVICE_ENUMERATION_ENABLED {
if let Some(w) = self.xrservice_worker.as_ref() {
if {
let state = w.state.lock().unwrap();
state.exit_status.is_none() && !state.stop_requested
} {
if let Some(monado) = self.libmonado.as_ref() {
self.xr_devices = XRDevice::merge(
&self.xr_devices,
&XRDevice::from_libmonado(monado),
);
self.main_view
.sender()
.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();
if self.libmonado.is_some() {
sender.input(Msg::ClockTicking);
}
}
}
}
}
}
} }
Msg::ParseLog(rows) => { Msg::ParseLog(rows) => {
for row in rows { for row in rows {
match MonadoLog::new_from_str(row.as_str()) { match MonadoLog::new_from_str(row.as_str()) {
None => {} None => {}
Some(parsed) => { Some(parsed) => {
if parsed.func == "p_create_system" { if !*FF_LIBMONADO_DEVICE_ENUMERATION_ENABLED
match XRDevices::from_log_message(parsed.message.as_str()) { && parsed.func == "p_create_system"
None => {} {
Some(devices) => { let n_devs = XRDevice::from_log_message(parsed.message.as_str());
self.xr_devices.merge(devices.clone()); self.xr_devices = XRDevice::merge(&self.xr_devices, &n_devs);
self.main_view.sender().emit(MainViewMsg::UpdateDevices( self.main_view
Some(self.xr_devices.clone()), .sender()
)); .emit(MainViewMsg::UpdateDevices(self.xr_devices.clone()));
break; } else if let Some(tracker) =
} XRDevice::generic_tracker_from_log_row(parsed.message.as_str())
}; {
} else { self.xr_devices.push(tracker);
self.xr_devices
.search_log_for_generic_trackers(parsed.message.as_str());
} }
} }
}; };
@ -364,7 +391,7 @@ impl SimpleComponent for App {
self.start_xrservice(sender); self.start_xrservice(sender);
} }
None => { None => {
worker.stop(); self.shutdown_xrservice();
self.restart_xrservice = true; self.restart_xrservice = true;
} }
} }
@ -643,9 +670,10 @@ impl SimpleComponent for App {
profiles, profiles,
xrservice_worker: None, xrservice_worker: None,
build_worker: None, build_worker: None,
xr_devices: XRDevices::default(), xr_devices: vec![],
fbt_config_editor: None, fbt_config_editor: None,
restart_xrservice: false, restart_xrservice: false,
libmonado: None,
}; };
let widgets = view_output!(); let widgets = view_output!();

View file

@ -1,48 +1,43 @@
use super::{
alert::alert,
factories::device_row_factory::{DeviceRowModel, DeviceRowModelInit, DeviceRowState},
};
use crate::{ use crate::{
file_builders::monado_config_v0::dump_generic_trackers, file_builders::monado_config_v0::dump_generic_trackers,
xr_devices::{XRDevice, XRDevices}, xr_devices::{XRDevice, XRDeviceType},
}; };
use adw::prelude::*; use adw::prelude::*;
use relm4::prelude::*; use relm4::{factory::FactoryVecDeque, prelude::*, Sender};
use super::alert::alert;
#[tracker::track] #[tracker::track]
pub struct DevicesBox { pub struct DevicesBox {
devices: Option<XRDevices>, devices: Vec<XRDevice>,
#[tracker::do_not_track]
device_rows: FactoryVecDeque<DeviceRowModel>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum DevicesBoxMsg { pub enum DevicesBoxMsg {
UpdateDevices(Option<XRDevices>), UpdateDevices(Vec<XRDevice>),
DumpGenericTrackers, DumpGenericTrackers,
} }
impl DevicesBox { impl DevicesBox {
fn get_dev(&self, key: XRDevice) -> Option<String> { fn create_save_trackers_btn(sender: Sender<DevicesBoxMsg>) -> gtk::Button {
match &self.devices { let btn = gtk::Button::builder()
None => None, .halign(gtk::Align::Center)
Some(devs) => match key { .valign(gtk::Align::Center)
XRDevice::Head => devs.head.clone(), .icon_name("document-save-symbolic")
XRDevice::Left => devs.left.clone(), .tooltip_text("Save current trackers")
XRDevice::Right => devs.right.clone(), .css_classes(["circular", "flat"])
XRDevice::Gamepad => devs.gamepad.clone(), .build();
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 get_dev_or_none(&self, key: XRDevice) -> String { btn.connect_clicked(move |_| {
self.get_dev(key).unwrap_or("None".into()) sender.emit(DevicesBoxMsg::DumpGenericTrackers);
});
btn
} }
} }
@ -60,159 +55,151 @@ impl SimpleComponent for DevicesBox {
set_spacing: 12, set_spacing: 12,
set_margin_top: 12, set_margin_top: 12,
#[track = "model.changed(Self::devices())"] #[track = "model.changed(Self::devices())"]
set_visible: model.devices.is_some(), set_visible: !model.devices.is_empty(),
gtk::ListBox {
add_css_class: "boxed-list", append: &devices_listbox,
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);
}
},
},
}
} }
} }
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) { fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset(); self.reset();
match message { match message {
Self::Input::UpdateDevices(devs) => { Self::Input::UpdateDevices(devs) => {
self.set_devices(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 => { Self::Input::DumpGenericTrackers => {
if let Some(devs) = self.devices.as_ref() { let added = dump_generic_trackers(
let added = dump_generic_trackers(&devs.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 multi_title = format!("Added {} new trackers", added);
let (title, msg) = match added { let (title, msg) = match added {
0 => ( 0 => (
"No new trackers found", "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 => ( 1 => (
"Added 1 new tracker", "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(), 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); alert(title, Some(msg), None);
} }
} }
} }
}
fn init( fn init(
_init: Self::Init, _init: Self::Init,
root: &Self::Root, root: &Self::Root,
sender: ComponentSender<Self>, sender: ComponentSender<Self>,
) -> ComponentParts<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 { let model = Self {
tracker: 0, tracker: 0,
devices: None, devices: vec![],
device_rows: FactoryVecDeque::new(devices_listbox.clone(), sender.input_sender()),
}; };
let widgets = view_output!(); 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 device_row_factory;
pub mod env_var_row_factory; pub mod env_var_row_factory;
pub mod tracker_role_group_factory; pub mod tracker_role_group_factory;

11
src/ui/feature_flags.rs Normal file
View file

@ -0,0 +1,11 @@
use lazy_static::lazy_static;
use std::env;
fn get_ff_libmonado_device_enumeration_enabled() -> bool {
env::var("ENVISION_FF_USE_LIBMONADO").unwrap_or_default() == "1"
}
lazy_static! {
pub static ref FF_LIBMONADO_DEVICE_ENUMERATION_ENABLED: bool =
get_ff_libmonado_device_enumeration_enabled();
}

View file

@ -18,7 +18,7 @@ use crate::ui::app::{
}; };
use crate::ui::profile_editor::ProfileEditorInit; use crate::ui::profile_editor::ProfileEditorInit;
use crate::ui::util::{limit_dropdown_width, warning_heading}; use crate::ui::util::{limit_dropdown_width, warning_heading};
use crate::xr_devices::XRDevices; use crate::xr_devices::XRDevice;
use gtk::prelude::*; use gtk::prelude::*;
use relm4::adw::traits::MessageDialogExt; use relm4::adw::traits::MessageDialogExt;
use relm4::adw::ResponseAppearance; use relm4::adw::ResponseAppearance;
@ -66,7 +66,7 @@ pub enum MainViewMsg {
DeleteProfile, DeleteProfile,
DuplicateProfile, DuplicateProfile,
SaveProfile(Profile), SaveProfile(Profile),
UpdateDevices(Option<XRDevices>), UpdateDevices(Vec<XRDevice>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -462,7 +462,7 @@ impl SimpleComponent for MainView {
Self::Input::XRServiceActiveChanged(active, profile) => { Self::Input::XRServiceActiveChanged(active, profile) => {
self.set_xrservice_active(active); self.set_xrservice_active(active);
if !active { if !active {
sender.input(Self::Input::UpdateDevices(None)); sender.input(Self::Input::UpdateDevices(vec![]));
} }
self.steam_launch_options_box self.steam_launch_options_box
.sender() .sender()

View file

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

View file

@ -1,5 +1,9 @@
#[derive(Debug, Clone, PartialEq, Eq)] use std::{fmt::Display, slice::Iter};
pub enum XRDevice {
const GENERIC_TRACKER_PREFIX: &str = "Found generic tracker device: ";
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum XRDeviceType {
Head, Head,
Left, Left,
Right, Right,
@ -10,25 +14,162 @@ pub enum XRDevice {
GenericTracker, GenericTracker,
} }
#[derive(Debug, Default, Clone, PartialEq, Eq)] impl Display for XRDeviceType {
pub struct XRDevices { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pub head: Option<String>, f.write_str(match self {
pub left: Option<String>, Self::Head => "Head",
pub right: Option<String>, Self::Left => "Left",
pub gamepad: Option<String>, Self::Right => "Right",
pub eyes: Option<String>, Self::Gamepad => "Gamepad",
pub hand_tracking_left: Option<String>, Self::Eyes => "Eye Tracking",
pub hand_tracking_right: Option<String>, Self::HandTrackingLeft => "Hand tracking left",
pub generic_trackers: Vec<String>, Self::HandTrackingRight => "Hand tracking right",
Self::GenericTracker => "Generic tracker",
})
}
} }
const GENERIC_TRACKER_PREFIX: &str = "Found generic tracker device: "; impl PartialOrd for XRDeviceType {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.as_number().cmp(&other.as_number()))
}
}
impl XRDevices { impl Ord for XRDeviceType {
pub fn from_log_message(s: &str) -> Option<Self> { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl XRDeviceType {
pub fn iter() -> Iter<'static, Self> {
[
Self::Head,
Self::Left,
Self::Right,
Self::Gamepad,
Self::Eyes,
Self::HandTrackingLeft,
Self::HandTrackingRight,
Self::GenericTracker,
]
.iter()
}
pub fn to_monado_str(&self) -> &str {
match self {
Self::Head => "head",
Self::Left => "left",
Self::Right => "right",
Self::Gamepad => "gamepad",
Self::Eyes => "eyes",
Self::HandTrackingLeft => "hand_tracking.left",
Self::HandTrackingRight => "hand_tracking.right",
Self::GenericTracker => "generic_tracker",
}
}
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_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![];
[
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.push(Self {
name: dev.name,
id: dev.id.to_string(),
dev_type: xrd.clone(),
..Default::default()
})
}
});
res
}
pub fn from_log_message(s: &str) -> Vec<Self> {
let mut res = vec![];
let rows = s.split('\n'); let rows = s.split('\n');
let mut in_section = false; let mut in_section = false;
let mut devs = Self::default();
for row in rows { for row in rows {
if !in_section && row.starts_with("\tIn roles:") { if !in_section && row.starts_with("\tIn roles:") {
in_section = true; in_section = true;
@ -40,64 +181,58 @@ impl XRDevices {
} }
match row.trim().split(": ").collect::<Vec<&str>>()[..] { match row.trim().split(": ").collect::<Vec<&str>>()[..] {
[_, "<none>"] => {} [_, "<none>"] => {}
["head", val] => devs.head = Some(val.to_string()), [dev_type_s, name] => {
["left", val] => devs.left = Some(val.to_string()), if let Some(xrdt) = XRDeviceType::from_monado_str(dev_type_s) {
["right", val] => devs.right = Some(val.to_string()), res.push(Self {
["gamepad", val] => devs.gamepad = Some(val.to_string()), dev_type: xrdt,
["eyes", val] => devs.eyes = Some(val.to_string()), name: name.to_string(),
["hand_tracking.left", val] => devs.hand_tracking_left = Some(val.to_string()), ..Default::default()
["hand_tracking.right", val] => { });
devs.hand_tracking_right = Some(val.to_string()) }
} }
_ => {} _ => {}
} }
} }
} }
if in_section { res
return Some(devs); }
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 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());
} }
} else {
pub fn merge(&mut self, new: Self) { res.push(n_dev.clone());
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());
} }
} }
res
} }
} }