diff --git a/src/runner.rs b/src/runner.rs index 4ddc8d3..1a04255 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -90,9 +90,9 @@ impl Runner { } } - pub fn xrservice_runner_from_profile(profile: Profile) -> Self { + pub fn xrservice_runner_from_profile(profile: &Profile) -> Self { Self::new( - Some(profile.environment), + Some(profile.environment.clone()), match profile.xrservice_type { XRServiceType::Monado => format!("{pfx}/bin/monado-service", pfx = profile.prefix), XRServiceType::Wivrn => format!("{pfx}/bin/wivrn-server", pfx = profile.prefix), @@ -253,7 +253,7 @@ mod tests { #[test] fn can_create_from_profile() { - Runner::xrservice_runner_from_profile(Profile::load_profile( + Runner::xrservice_runner_from_profile(&Profile::load_profile( &"./test/files/profile.json".to_string(), )); } diff --git a/src/ui/app.rs b/src/ui/app.rs index 9013556..ad5367c 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -19,6 +19,7 @@ use crate::dependencies::monado_deps::get_missing_monado_deps; use crate::dependencies::pkexec_dep::pkexec_dep; use crate::dependencies::wivrn_deps::get_missing_wivrn_deps; use crate::file_utils::setcap_cap_sys_nice_eip; +use crate::log_parser::MonadoLog; use crate::profile::{Profile, XRServiceType}; use crate::profiles::system_valve_index::system_valve_index_profile; use crate::profiles::valve_index::valve_index_profile; @@ -29,6 +30,7 @@ use crate::ui::build_window::BuildWindowMsg; 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 expect_dialog::ExpectDialog; use gtk::prelude::*; use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup}; @@ -71,6 +73,8 @@ pub struct App { build_pipeline: Option, #[tracker::do_not_track] profiles: Vec, + #[tracker::do_not_track] + devices_processed: bool, } #[derive(Debug)] @@ -85,6 +89,7 @@ pub enum Msg { RunSetCap, OpenLibsurviveSetup, Quit, + ProcessDevicesLog(Vec), } impl App { @@ -102,10 +107,15 @@ impl App { } pub fn start_xrservice(&mut self) { + let prof = self.get_selected_profile(); + self.devices_processed = match prof.xrservice_type { + XRServiceType::Monado => false, + XRServiceType::Wivrn => true, // no device from log in wivrn + }; self.debug_view .sender() .emit(DebugViewMsg::ClearLog); - let mut runner = Runner::xrservice_runner_from_profile(self.get_selected_profile().clone()); + let mut runner = Runner::xrservice_runner_from_profile(&prof); match runner.try_start() { Ok(_) => { self.xrservice_runner = Some(runner); @@ -189,6 +199,9 @@ impl SimpleComponent for App { Some(runner) => { let n_rows = runner.consume_rows(); if !n_rows.is_empty() { + if !self.devices_processed { + sender.input(Msg::ProcessDevicesLog(n_rows.clone())) + } self.debug_view .sender() .emit(DebugViewMsg::LogUpdated(n_rows)); @@ -245,6 +258,25 @@ impl SimpleComponent for App { }; self.main_view.sender().emit(MainViewMsg::ClockTicking); } + Msg::ProcessDevicesLog(rows) => { + for row in rows { + match MonadoLog::from_str(row.as_str()) { + None => {} + Some(parsed) => { + if parsed.func == "p_create_system" { + match XRDevices::from_log_message(parsed.message) { + None => {}, + Some(devices) => { + self.devices_processed = true; + self.main_view.sender().emit(MainViewMsg::UpdateDevices(Some(devices))); + break; + } + }; + } + } + }; + } + } Msg::EnableDebugViewChanged(val) => { self.set_enable_debug_view(val); self.config.debug_view_enabled = val; @@ -501,6 +533,7 @@ impl SimpleComponent for App { profiles, xrservice_runner: None, build_pipeline: None, + devices_processed: false, }; let widgets = view_output!(); diff --git a/src/ui/devices_box.rs b/src/ui/devices_box.rs new file mode 100644 index 0000000..2ae940c --- /dev/null +++ b/src/ui/devices_box.rs @@ -0,0 +1,253 @@ +use crate::xr_devices::{XRDevice, XRDevices}; +use gtk::prelude::*; +use relm4::prelude::*; + +#[tracker::track] +pub struct DevicesBox { + devices: Option, +} + +#[derive(Debug)] +pub enum DevicesBoxMsg { + UpdateDevices(Option), +} + +impl DevicesBox { + fn get_dev(&self, key: XRDevice) -> Option { + match &self.devices { + None => None, + Some(devs) => match key { + XRDevice::Head => devs.head.clone(), + XRDevice::Left => devs.left.clone(), + XRDevice::Right => devs.right.clone(), + XRDevice::Gamepad => devs.gamepad.clone(), + XRDevice::Eyes => devs.eyes.clone(), + XRDevice::HandTrackingLeft => devs.hand_tracking_left.clone(), + XRDevice::HandTrackingRight => devs.hand_tracking_right.clone(), + }, + } + } +} + +#[relm4::component(pub)] +impl SimpleComponent for DevicesBox { + type Init = (); + type Input = DevicesBoxMsg; + type Output = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_hexpand: true, + set_vexpand: false, + set_spacing: 12, + set_margin_top: 12, + #[track = "model.changed(Self::devices())"] + set_visible: model.devices.is_some(), + gtk::Separator { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + }, + // Head + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + gtk::Image { + #[track = "model.changed(Self::devices())"] + set_icon_name: Some(match model.get_dev(XRDevice::Head) { + Some(_) => "emblem-ok-symbolic", + None => "dialog-question-symbolic", + }), + #[track = "model.changed(Self::devices())"] + set_class_active: ("error", model.get_dev(XRDevice::Head).is_none()), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Head: {}", match model.get_dev(XRDevice::Head) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + #[track = "model.changed(Self::devices())"] + set_class_active: ("error", model.get_dev(XRDevice::Head).is_none()), + // TODO: status icon with popover + }, + }, + // Left + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + gtk::Image { + #[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()), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Left: {}", match model.get_dev(XRDevice::Left) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + #[track = "model.changed(Self::devices())"] + set_class_active: ("error", model.get_dev(XRDevice::Left).is_none()), + // TODO: status icon with popover + }, + }, + // Right + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + gtk::Image { + #[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()), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Right: {}", match model.get_dev(XRDevice::Right) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + #[track = "model.changed(Self::devices())"] + set_class_active: ("error", model.get_dev(XRDevice::Right).is_none()), + // TODO: status icon with popover + }, + }, + // Gamepad + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + #[track = "model.changed(Self::devices())"] + set_visible: model.get_dev(XRDevice::Gamepad).is_some(), + gtk::Image { + set_icon_name: Some("emblem-ok-symbolic"), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Gamepad: {}", match model.get_dev(XRDevice::Gamepad) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + }, + }, + // Eyes + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + #[track = "model.changed(Self::devices())"] + set_visible: model.get_dev(XRDevice::Eyes).is_some(), + gtk::Image { + set_icon_name: Some("emblem-ok-symbolic"), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Eye Tracking: {}", match model.get_dev(XRDevice::Eyes) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + }, + }, + // Hand Tracking Left + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + #[track = "model.changed(Self::devices())"] + set_visible: model.get_dev(XRDevice::HandTrackingLeft).is_some(), + gtk::Image { + set_icon_name: Some("emblem-ok-symbolic"), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Hand Tracking Left: {}", match model.get_dev(XRDevice::HandTrackingLeft) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + }, + }, + // Hand Tracking Right + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_spacing: 12, + set_margin_start: 12, + set_margin_end: 12, + #[track = "model.changed(Self::devices())"] + set_visible: model.get_dev(XRDevice::HandTrackingRight).is_some(), + gtk::Image { + set_icon_name: Some("emblem-ok-symbolic"), + }, + gtk::Label { + set_xalign: 0.0, + set_hexpand: true, + #[track = "model.changed(Self::devices())"] + set_label: format!("Hand Tracking Left: {}", match model.get_dev(XRDevice::HandTrackingRight) { + Some(v) => v.clone(), + None => "None".to_string(), + }).as_str(), + }, + }, + } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender) { + self.reset(); + + match message { + Self::Input::UpdateDevices(devs) => { + self.set_devices(devs); + } + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let model = Self { + tracker: 0, + devices: None, + }; + + let widgets = view_output!(); + + ComponentParts { model, widgets } + } +} diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index 31e71eb..6c43ed2 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -1,3 +1,4 @@ +use super::devices_box::{DevicesBox, DevicesBoxMsg}; use super::install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivrnBoxMsg}; use super::profile_editor::{ProfileEditor, ProfileEditorMsg, ProfileEditorOutMsg}; use super::runtime_switcher_box::{ @@ -11,6 +12,7 @@ use crate::ui::app::{ AboutAction, BuildProfileAction, DebugViewToggleAction, LibsurviveSetupAction, }; use crate::ui::profile_editor::ProfileEditorInit; +use crate::xr_devices::XRDevices; use gtk::prelude::*; use relm4::adw::traits::MessageDialogExt; use relm4::adw::ResponseAppearance; @@ -32,6 +34,8 @@ pub struct MainView { #[tracker::do_not_track] runtime_switcher_box: Controller, #[tracker::do_not_track] + devices_box: Controller, + #[tracker::do_not_track] profile_editor: Controller, #[tracker::do_not_track] profile_not_editable_dialog: adw::MessageDialog, @@ -54,6 +58,7 @@ pub enum MainViewMsg { DeleteProfile, DuplicateProfile, SaveProfile(Profile), + UpdateDevices(Option), } #[derive(Debug)] @@ -141,6 +146,7 @@ impl SimpleComponent for MainView { sender.input(MainViewMsg::StartStopClicked) }, }, + model.devices_box.widget(), model.runtime_switcher_box.widget(), model.steam_launch_options_box.widget(), model.install_wivrn_box.widget(), @@ -221,6 +227,9 @@ impl SimpleComponent for MainView { } Self::Input::XRServiceActiveChanged(active, profile) => { self.set_xrservice_active(active); + if !active { + sender.input(Self::Input::UpdateDevices(None)); + } self.steam_launch_options_box .sender() .emit(SteamLaunchOptionsBoxMsg::UpdateXRServiceActive(active)); @@ -299,6 +308,9 @@ impl SimpleComponent for MainView { self.selected_profile.create_duplicate(), )); } + Self::Input::UpdateDevices(devs) => { + self.devices_box.sender().emit(DevicesBoxMsg::UpdateDevices(devs)) + } } } @@ -376,6 +388,7 @@ impl SimpleComponent for MainView { .forward(sender.input_sender(), |message| match message { ProfileEditorOutMsg::SaveProfile(p) => Self::Input::SaveProfile(p), }), + devices_box: DevicesBox::builder().launch(()).detach(), selected_profile: init.selected_profile.clone(), profile_not_editable_dialog, profile_delete_confirm_dialog, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f721cc1..7b7a7a3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -10,3 +10,4 @@ pub mod runtime_switcher_box; pub mod profile_editor; pub mod factories; pub mod wivrn_conf_editor; +pub mod devices_box;