Merge branch 'main' into feat/stardust

This commit is contained in:
Gabriele Musco 2023-12-23 12:49:00 +00:00
commit 356656716c
20 changed files with 630 additions and 386 deletions

View file

@ -5,6 +5,7 @@ pub static ENV_VAR_DESCRIPTIONS: Map<&str, &str> = phf_map! {
"XRT_COMPOSITOR_SCALE_PECENTAGE" => "XRT_COMPOSITOR_SCALE_PECENTAGE" =>
"Render resolution percentage. A percentage higher than the native resolution (>100) will help with antialiasing and image clarity.", "Render resolution percentage. A percentage higher than the native resolution (>100) will help with antialiasing and image clarity.",
"XRT_COMPOSITOR_COMPUTE" => "Set to 1 to use GPU compute for the OpenXR compositor.", "XRT_COMPOSITOR_COMPUTE" => "Set to 1 to use GPU compute for the OpenXR compositor.",
"U_PACING_APP_USE_MIN_FRAME_PERIOD" => "Set to 1 to unlimit the compositor refresh from a power of two of your HMD refresh, typically provides a large performance boost.",
"SURVIVE_GLOBALSCENESOLVER" => "SURVIVE_GLOBALSCENESOLVER" =>
"Continuously recalibrate lighthouse tracking during use. In the current state it's recommended to disable this feature by setting this value to 0.", "Continuously recalibrate lighthouse tracking during use. In the current state it's recommended to disable this feature by setting this value to 0.",
// "SURVIVE_TIMECODE_OFFSET_MS" => "", // "SURVIVE_TIMECODE_OFFSET_MS" => "",

View file

@ -133,3 +133,10 @@ pub fn get_ipc_file_path(xrservice_type: &XRServiceType) -> String {
} }
) )
} }
pub fn get_steamvr_bin_dir_path() -> String {
format!(
"{data}/Steam/steamapps/common/SteamVR/bin/linux64",
data = get_xdg_data_dir()
)
}

View file

@ -14,6 +14,7 @@ pub fn lighthouse_profile() -> Profile {
environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into()); environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
environment.insert("XRT_DEBUG_GUI".into(), "1".into()); environment.insert("XRT_DEBUG_GUI".into(), "1".into());
environment.insert("XRT_CURATED_GUI".into(), "1".into()); environment.insert("XRT_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
environment.insert( environment.insert(
"LD_LIBRARY_PATH".into(), "LD_LIBRARY_PATH".into(),
format!("{pfx}/lib:{pfx}/lib64", pfx = prefix), format!("{pfx}/lib:{pfx}/lib64", pfx = prefix),

View file

@ -17,6 +17,7 @@ pub fn openhmd_profile() -> Profile {
environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into()); environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
environment.insert("XRT_DEBUG_GUI".into(), "1".into()); environment.insert("XRT_DEBUG_GUI".into(), "1".into());
environment.insert("XRT_CURATED_GUI".into(), "1".into()); environment.insert("XRT_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
environment.insert( environment.insert(
"LD_LIBRARY_PATH".into(), "LD_LIBRARY_PATH".into(),
format!("{pfx}/lib:{pfx}/lib64", pfx = prefix), format!("{pfx}/lib:{pfx}/lib64", pfx = prefix),

View file

@ -19,6 +19,7 @@ pub fn survive_profile() -> Profile {
environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into()); environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into());
environment.insert("XRT_DEBUG_GUI".into(), "1".into()); environment.insert("XRT_DEBUG_GUI".into(), "1".into());
environment.insert("XRT_CURATED_GUI".into(), "1".into()); environment.insert("XRT_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
environment.insert( environment.insert(
"LD_LIBRARY_PATH".into(), "LD_LIBRARY_PATH".into(),
format!("{pfx}/lib:{pfx}/lib64", pfx = prefix), format!("{pfx}/lib:{pfx}/lib64", pfx = prefix),

View file

@ -15,6 +15,7 @@ pub fn wivrn_profile() -> Profile {
); );
environment.insert("XRT_DEBUG_GUI".into(), "1".into()); environment.insert("XRT_DEBUG_GUI".into(), "1".into());
environment.insert("XRT_CURATED_GUI".into(), "1".into()); environment.insert("XRT_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
Profile { Profile {
uuid: "wivrn-default".into(), uuid: "wivrn-default".into(),
name: format!("WiVRn - {name} Default", name = APP_NAME), name: format!("WiVRn - {name} Default", name = APP_NAME),

View file

@ -17,6 +17,7 @@ pub fn wmr_profile() -> Profile {
environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into()); environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
environment.insert("XRT_DEBUG_GUI".into(), "1".into()); environment.insert("XRT_DEBUG_GUI".into(), "1".into());
environment.insert("XRT_CURATED_GUI".into(), "1".into()); environment.insert("XRT_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
environment.insert( environment.insert(
"LD_LIBRARY_PATH".into(), "LD_LIBRARY_PATH".into(),
format!("{pfx}/lib:{pfx}/lib64", pfx = prefix), format!("{pfx}/lib:{pfx}/lib64", pfx = prefix),

View file

@ -8,6 +8,7 @@ use super::job_worker::job::WorkerJob;
use super::job_worker::JobWorker; use super::job_worker::JobWorker;
use super::libsurvive_setup_window::LibsurviveSetupWindow; use super::libsurvive_setup_window::LibsurviveSetupWindow;
use super::main_view::MainViewMsg; use super::main_view::MainViewMsg;
use super::util::open_with_default_handler;
use crate::builders::build_basalt::get_build_basalt_jobs; use crate::builders::build_basalt::get_build_basalt_jobs;
use crate::builders::build_libsurvive::get_build_libsurvive_jobs; use crate::builders::build_libsurvive::get_build_libsurvive_jobs;
use crate::builders::build_mercury::get_build_mercury_job; use crate::builders::build_mercury::get_build_mercury_job;
@ -34,19 +35,20 @@ use crate::file_builders::openvrpaths_vrpath::{
use crate::file_utils::setcap_cap_sys_nice_eip; use crate::file_utils::setcap_cap_sys_nice_eip;
use crate::linux_distro::get_distro; use crate::linux_distro::get_distro;
use crate::log_parser::MonadoLog; use crate::log_parser::MonadoLog;
use crate::paths::get_ipc_file_path; use crate::paths::{get_data_dir, get_ipc_file_path};
use crate::profile::{Profile, XRServiceType}; use crate::profile::{Profile, XRServiceType};
use crate::profiles::lighthouse::lighthouse_profile; use crate::profiles::lighthouse::lighthouse_profile;
use crate::profiles::openhmd::openhmd_profile; use crate::profiles::openhmd::openhmd_profile;
use crate::profiles::survive::survive_profile; use crate::profiles::survive::survive_profile;
use crate::profiles::wivrn::wivrn_profile; use crate::profiles::wivrn::wivrn_profile;
use crate::profiles::wmr::wmr_profile; use crate::profiles::wmr::wmr_profile;
use crate::stateless_action;
use crate::ui::build_window::{BuildWindowMsg, BuildWindowOutMsg}; 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::XRDevice; use crate::xr_devices::XRDevice;
use crate::{stateless_action, withclones}; use gtk::glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup}; use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup};
use relm4::adw::traits::MessageDialogExt; use relm4::adw::traits::MessageDialogExt;
@ -124,6 +126,8 @@ pub enum Msg {
Quit, Quit,
ParseLog(Vec<String>), ParseLog(Vec<String>),
ConfigFbt, ConfigFbt,
DebugOpenPrefix,
DebugOpenData,
} }
impl App { impl App {
@ -619,6 +623,15 @@ impl SimpleComponent for App {
)); ));
self.application.quit(); self.application.quit();
} }
Msg::DebugOpenData => {
open_with_default_handler(&format!("file://{}", get_data_dir()));
}
Msg::DebugOpenPrefix => {
open_with_default_handler(&format!(
"file://{}",
self.get_selected_profile().prefix
));
}
} }
} }
@ -712,59 +725,76 @@ impl SimpleComponent for App {
let mut actions = RelmActionGroup::<AppActionGroup>::new(); let mut actions = RelmActionGroup::<AppActionGroup>::new();
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, BuildProfileAction, { BuildProfileAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(false, false)); sender.input_sender().emit(Msg::BuildProfile(false, false));
}); })
} );
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, BuildProfileCleanAction, { BuildProfileCleanAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(true, false)); sender.input_sender().emit(Msg::BuildProfile(true, false));
}); })
} );
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, BuildProfileDebugAction, { BuildProfileDebugAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(false, true)); sender.input_sender().emit(Msg::BuildProfile(false, true));
}); })
} );
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, BuildProfileCleanDebugAction, { BuildProfileCleanDebugAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(true, true)); sender.input_sender().emit(Msg::BuildProfile(true, true));
}); })
} );
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, ConfigFbtAction, { ConfigFbtAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::ConfigFbt); sender.input_sender().emit(Msg::ConfigFbt);
}); })
} );
{ {
let abd_sender = model.about_dialog.sender().clone(); let abd_sender = model.about_dialog.sender().clone();
stateless_action!(actions, AboutAction, { stateless_action!(actions, AboutAction, move |_| {
abd_sender.send(()).unwrap(); abd_sender.send(()).unwrap();
}); });
} }
{ stateless_action!(
withclones![sender]; actions,
stateless_action!(actions, QuitAction, { QuitAction,
clone!(@strong sender => move |_| {
sender.input(Msg::Quit); sender.input(Msg::Quit);
}); })
} );
{ stateless_action!(
withclones![sender]; actions,
actions.add_action(RelmAction::<DebugViewToggleAction>::new_stateful( DebugOpenDataAction,
&model.enable_debug_view, clone!(@strong sender => move |_| {
move |_, state| { sender.input(Msg::DebugOpenData);
let s = *state; })
*state = !s; );
sender.input(Msg::EnableDebugViewChanged(*state)); stateless_action!(
}, actions,
)) DebugOpenPrefixAction,
} clone!(@strong sender => move |_| {
sender.input(Msg::DebugOpenPrefix);
})
);
actions.add_action(RelmAction::<DebugViewToggleAction>::new_stateful(
&model.enable_debug_view,
clone!(@strong sender => move |_, state| {
let s = *state;
*state = !s;
sender.input(Msg::EnableDebugViewChanged(*state));
}),
));
root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group())); root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group()));
@ -780,13 +810,13 @@ impl SimpleComponent for App {
model.config.clone(), model.config.clone(),
)); ));
{ glib::timeout_add_local(
withclones![sender]; Duration::from_millis(1000),
glib::timeout_add_local(Duration::from_millis(1000), move || { clone!(@strong sender => move || {
sender.input(Msg::ClockTicking); sender.input(Msg::ClockTicking);
glib::ControlFlow::Continue glib::ControlFlow::Continue
}); }),
} );
model.split_view = Some(widgets.split_view.clone()); model.split_view = Some(widgets.split_view.clone());
@ -803,3 +833,6 @@ new_stateless_action!(pub BuildProfileCleanDebugAction, AppActionGroup, "buildpr
new_stateless_action!(pub ConfigFbtAction, AppActionGroup, "configfbt"); new_stateless_action!(pub ConfigFbtAction, AppActionGroup, "configfbt");
new_stateless_action!(pub QuitAction, AppActionGroup, "quit"); new_stateless_action!(pub QuitAction, AppActionGroup, "quit");
new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool); new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool);
new_stateless_action!(pub DebugOpenDataAction, AppActionGroup, "debugopendata");
new_stateless_action!(pub DebugOpenPrefixAction, AppActionGroup, "debugopenprefix");

View file

@ -1,6 +1,7 @@
use crate::log_level::LogLevel; use crate::log_level::LogLevel;
use crate::log_parser::MonadoLog; use crate::log_parser::MonadoLog;
use crate::withclones; use crate::ui::app::{DebugOpenDataAction, DebugOpenPrefixAction};
use gtk::glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use relm4::prelude::*; use relm4::prelude::*;
use relm4::{ComponentSender, SimpleComponent}; use relm4::{ComponentSender, SimpleComponent};
@ -57,6 +58,15 @@ impl SimpleComponent for DebugView {
type Input = DebugViewMsg; type Input = DebugViewMsg;
type Output = (); type Output = ();
menu! {
debug_menu: {
section! {
"Open Envision _Data Folder" => DebugOpenDataAction,
"Open _Prefix Folder" => DebugOpenPrefixAction,
},
}
}
view! { view! {
gtk::Box { gtk::Box {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
@ -66,6 +76,11 @@ impl SimpleComponent for DebugView {
set_hexpand: true, set_hexpand: true,
set_vexpand: false, set_vexpand: false,
add_css_class: "flat", add_css_class: "flat",
pack_end: debug_menu_btn = &gtk::MenuButton {
set_icon_name: "view-more-symbolic",
set_tooltip_text: Some("Debug Actions..."),
set_menu_model: Some(&debug_menu),
},
pack_end: search_toggle = &gtk::ToggleButton { pack_end: search_toggle = &gtk::ToggleButton {
set_icon_name: "edit-find-symbolic", set_icon_name: "edit-find-symbolic",
set_tooltip_text: Some("Filter Log"), set_tooltip_text: Some("Filter Log"),
@ -255,24 +270,18 @@ impl SimpleComponent for DebugView {
if let Some(btn) = log_level_dropdown.first_child() { if let Some(btn) = log_level_dropdown.first_child() {
btn.add_css_class("flat"); btn.add_css_class("flat");
} }
{ log_level_dropdown.connect_selected_notify(clone!(@strong sender => move |dd| {
withclones![sender]; sender.input(Self::Input::LogLevelChanged(
log_level_dropdown.connect_selected_notify(move |dd| { *LogLevel::iter()
sender.input(Self::Input::LogLevelChanged( .as_slice()
*LogLevel::iter() .get(dd.selected() as usize)
.as_slice() .unwrap(),
.get(dd.selected() as usize) ));
.unwrap(), }));
));
});
}
{ adw::StyleManager::default().connect_dark_notify(clone!(@strong sender => move |_| {
withclones![sender]; sender.input(Self::Input::SetColorScheme);
adw::StyleManager::default().connect_dark_notify(move |_| { }));
sender.input(Self::Input::SetColorScheme);
});
}
Self::set_color_scheme(&textbuf); Self::set_color_scheme(&textbuf);
let mut model = Self { let mut model = Self {

View file

@ -2,9 +2,9 @@ use crate::{
file_builders::monado_config_v0::{TrackerRole, XrtTrackerRole}, file_builders::monado_config_v0::{TrackerRole, XrtTrackerRole},
ui::fbt_config_editor::FbtConfigEditorMsg, ui::fbt_config_editor::FbtConfigEditorMsg,
ui::preference_rows::{combo_row, entry_row}, ui::preference_rows::{combo_row, entry_row},
withclones,
}; };
use adw::prelude::*; use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryComponent, prelude::*, AsyncFactorySender}; use relm4::{factory::AsyncFactoryComponent, prelude::*, AsyncFactorySender};
#[derive(Debug)] #[derive(Debug)]
@ -54,27 +54,29 @@ impl AsyncFactoryComponent for TrackerRoleModel {
} }
}, },
add: { add: {
withclones![sender];
let tr = self.tracker_role.clone(); let tr = self.tracker_role.clone();
&entry_row("Device serial", self.tracker_role.device_serial.as_str(), move |row| { &entry_row(
let mut ntr = tr.clone(); "Device serial",
ntr.device_serial = row.text().to_string(); self.tracker_role.device_serial.as_str(),
sender.input(Self::Input::Changed(ntr)); clone!(@strong sender => move |row| {
}) let mut ntr = tr.clone();
ntr.device_serial = row.text().to_string();
sender.input(Self::Input::Changed(ntr));
})
)
}, },
add: { add: {
withclones![sender];
let tr = self.tracker_role.clone(); let tr = self.tracker_role.clone();
&combo_row("Tracker role", None, &tr.role.clone().to_picker_string(), &combo_row("Tracker role", None, &tr.role.clone().to_picker_string(),
XrtTrackerRole::iter() XrtTrackerRole::iter()
.map(XrtTrackerRole::to_picker_string) .map(XrtTrackerRole::to_picker_string)
.map(String::from) .map(String::from)
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
move |row| { clone!(@strong sender => move |row| {
let mut ntr = tr.clone(); let mut ntr = tr.clone();
ntr.role = XrtTrackerRole::from_number(&row.selected()); ntr.role = XrtTrackerRole::from_number(&row.selected());
sender.input(Self::Input::Changed(ntr)); sender.input(Self::Input::Changed(ntr));
} })
) )
}, },
} }

View file

@ -4,9 +4,9 @@ use crate::{
dump_monado_config_v0, get_monado_config_v0, MonadoConfigV0, TrackerRole, dump_monado_config_v0, get_monado_config_v0, MonadoConfigV0, TrackerRole,
}, },
ui::factories::tracker_role_group_factory::TrackerRoleModelInit, ui::factories::tracker_role_group_factory::TrackerRoleModelInit,
withclones,
}; };
use adw::prelude::*; use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryVecDeque, prelude::*}; use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
#[tracker::track] #[tracker::track]
@ -113,18 +113,12 @@ impl SimpleComponent for FbtConfigEditor {
.label("Save") .label("Save")
.css_classes(["suggested-action"]) .css_classes(["suggested-action"])
.build(); .build();
{ add_btn.connect_clicked(clone!(@strong sender => move |_| {
withclones![sender]; sender.input(Self::Input::TrackerRoleNew);
add_btn.connect_clicked(move |_| { }));
sender.input(Self::Input::TrackerRoleNew); save_btn.connect_clicked(clone!(@strong sender => move |_| {
}); sender.input(Self::Input::Save);
}; }));
{
withclones![sender];
save_btn.connect_clicked(move |_| {
sender.input(Self::Input::Save);
});
};
let mut model = Self { let mut model = Self {
win: None, win: None,

View file

@ -1,4 +1,4 @@
use crate::{profile::Profile, withclones}; use crate::profile::{LighthouseDriver, Profile};
use super::{ use super::{
job::{CmdWorkerData, FuncWorkerOut, WorkerJob}, job::{CmdWorkerData, FuncWorkerOut, WorkerJob},
@ -89,7 +89,7 @@ impl Worker for InternalJobWorker {
let mut job = self.jobs.pop_front().unwrap(); let mut job = self.jobs.pop_front().unwrap();
match &mut job { match &mut job {
WorkerJob::Cmd(data) => { WorkerJob::Cmd(data) => {
withclones![data]; let data = data.clone();
if let Ok(mut cmd) = Command::new(data.command) if let Ok(mut cmd) = Command::new(data.command)
.args(data.args) .args(data.args)
.envs(data.environment) .envs(data.environment)
@ -161,10 +161,15 @@ impl InternalJobWorker {
) -> relm4::WorkerHandle<InternalJobWorker> { ) -> relm4::WorkerHandle<InternalJobWorker> {
let mut env = prof.environment.clone(); let mut env = prof.environment.clone();
if !env.contains_key("LH_DRIVER") { if !env.contains_key("LH_DRIVER") {
env.insert( match prof.lighthouse_driver {
"LH_DRIVER".into(), // don't set LH_DRIVER for steamvr driver, set this instead
prof.lighthouse_driver.to_string().to_lowercase(), LighthouseDriver::SteamVR => {
); env.insert("STEAMVR_LH_ENABLE".into(), "true".into());
}
d => {
env.insert("LH_DRIVER".into(), d.to_string().to_lowercase());
}
}
} }
let mut launch_opts = prof.xrservice_launch_options.trim(); let mut launch_opts = prof.xrservice_launch_options.trim();
let debug_launch_opts = if debug { let debug_launch_opts = if debug {

View file

@ -1,13 +1,6 @@
#[macro_export]
macro_rules! withclones {
($($var:ident),+) => {
$(let $var = $var.clone();)+
};
}
#[macro_export] #[macro_export]
macro_rules! stateless_action { macro_rules! stateless_action {
($group:ident, $name:ident, $ex:expr) => { ($group:ident, $name:ident, $fun:expr) => {
$group.add_action(RelmAction::<$name>::new_stateless(move |_| $ex)); $group.add_action(RelmAction::<$name>::new_stateless($fun));
}; };
} }

View file

@ -4,6 +4,7 @@ use super::install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivr
use super::profile_editor::{ProfileEditor, ProfileEditorMsg, ProfileEditorOutMsg}; use super::profile_editor::{ProfileEditor, ProfileEditorMsg, ProfileEditorOutMsg};
use super::stardust::stardust_view::StardustView; use super::stardust::stardust_view::StardustView;
use super::steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg}; use super::steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg};
use super::steamvr_calibration_box::SteamVrCalibrationBox;
use crate::config::Config; use crate::config::Config;
use crate::file_utils::mount_has_nosuid; use crate::file_utils::mount_has_nosuid;
use crate::gpu_profile::{ use crate::gpu_profile::{
@ -18,6 +19,7 @@ use crate::ui::app::{
}; };
use crate::ui::profile_editor::ProfileEditorInit; use crate::ui::profile_editor::ProfileEditorInit;
use crate::ui::stardust::stardust_view::StardustViewInit; use crate::ui::stardust::stardust_view::StardustViewInit;
use crate::ui::steamvr_calibration_box::SteamVrCalibrationBoxMsg;
use crate::ui::util::{limit_dropdown_width, warning_heading}; use crate::ui::util::{limit_dropdown_width, warning_heading};
use crate::xr_devices::XRDevice; use crate::xr_devices::XRDevice;
use gtk::prelude::*; use gtk::prelude::*;
@ -48,6 +50,8 @@ pub struct MainView {
#[tracker::do_not_track] #[tracker::do_not_track]
profile_editor: Option<Controller<ProfileEditor>>, profile_editor: Option<Controller<ProfileEditor>>,
#[tracker::do_not_track] #[tracker::do_not_track]
steamvr_calibration_box: Controller<SteamVrCalibrationBox>,
#[tracker::do_not_track]
root_win: gtk::Window, root_win: gtk::Window,
#[tracker::do_not_track] #[tracker::do_not_track]
stardust_view: Controller<StardustView>, stardust_view: Controller<StardustView>,
@ -338,6 +342,7 @@ impl SimpleComponent for MainView {
}, },
model.steam_launch_options_box.widget(), model.steam_launch_options_box.widget(),
model.install_wivrn_box.widget(), model.install_wivrn_box.widget(),
model.steamvr_calibration_box.widget(),
gtk::Box { gtk::Box {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_hexpand: true, set_hexpand: true,
@ -347,6 +352,13 @@ impl SimpleComponent for MainView {
add_css_class: "padded", add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"] #[track = "model.changed(Self::selected_profile())"]
set_visible: model.selected_profile.lighthouse_driver == LighthouseDriver::Survive, set_visible: model.selected_profile.lighthouse_driver == LighthouseDriver::Survive,
set_hexpand: true,
set_vexpand: false,
set_spacing: 12,
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
set_visible: model.selected_profile.lighthouse_driver == LighthouseDriver::Survive,
gtk::Label { gtk::Label {
add_css_class: "heading", add_css_class: "heading",
set_hexpand: true, set_hexpand: true,
@ -376,7 +388,27 @@ impl SimpleComponent for MainView {
} }
}, },
}, },
} gtk::Label {
add_css_class: "dim-label",
set_hexpand: true,
set_label: concat!(
"Libsurvive needs to import your SteamVR calibration to work ",
"properly. You need to have used SteamVR with this setup ",
"before to be able to import its calibration."
),
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Button {
add_css_class: "suggested-action",
set_label: "Calibrate",
set_halign: gtk::Align::Start,
connect_clicked[sender] => move |_| {
sender.output(Self::Output::OpenLibsurviveSetup).expect("Sender output failed");
}
},
},
} }
} -> { } -> {
set_name: Some("main_view"), set_name: Some("main_view"),
@ -479,6 +511,9 @@ impl SimpleComponent for MainView {
} }
Self::Input::XRServiceActiveChanged(active, profile) => { Self::Input::XRServiceActiveChanged(active, profile) => {
self.set_xrservice_active(active); self.set_xrservice_active(active);
self.steamvr_calibration_box
.sender()
.emit(SteamVrCalibrationBoxMsg::XRServiceActiveChanged(active));
if !active { if !active {
sender.input(Self::Input::UpdateDevices(vec![])); sender.input(Self::Input::UpdateDevices(vec![]));
} }
@ -506,6 +541,11 @@ impl SimpleComponent for MainView {
} }
Self::Input::UpdateSelectedProfile(prof) => { Self::Input::UpdateSelectedProfile(prof) => {
self.set_selected_profile(prof.clone()); self.set_selected_profile(prof.clone());
self.steamvr_calibration_box
.sender()
.emit(SteamVrCalibrationBoxMsg::SetVisible(
prof.lighthouse_driver == LighthouseDriver::SteamVR,
));
self.install_wivrn_box self.install_wivrn_box
.sender() .sender()
.emit(InstallWivrnBoxMsg::UpdateSelectedProfile(prof.clone())); .emit(InstallWivrnBoxMsg::UpdateSelectedProfile(prof.clone()));
@ -644,6 +684,13 @@ impl SimpleComponent for MainView {
}); });
} }
let steamvr_calibration_box = SteamVrCalibrationBox::builder().launch(()).detach();
steamvr_calibration_box
.sender()
.emit(SteamVrCalibrationBoxMsg::SetVisible(
init.selected_profile.lighthouse_driver == LighthouseDriver::SteamVR,
));
let mut model = Self { let mut model = Self {
xrservice_active: false, xrservice_active: false,
enable_debug_view: init.config.debug_view_enabled, enable_debug_view: init.config.debug_view_enabled,
@ -661,6 +708,7 @@ impl SimpleComponent for MainView {
profile_not_editable_dialog, profile_not_editable_dialog,
profile_delete_confirm_dialog, profile_delete_confirm_dialog,
root_win: init.root_win.clone(), root_win: init.root_win.clone(),
steamvr_calibration_box,
profile_editor: None, profile_editor: None,
stardust_view: StardustView::builder() stardust_view: StardustView::builder()
.launch(StardustViewInit { .launch(StardustViewInit {

View file

@ -16,5 +16,6 @@ pub mod preference_rows;
pub mod profile_editor; pub mod profile_editor;
pub mod stardust; pub mod stardust;
pub mod steam_launch_options_box; pub mod steam_launch_options_box;
mod steamvr_calibration_box;
pub mod util; pub mod util;
pub mod wivrn_conf_editor; pub mod wivrn_conf_editor;

View file

@ -1,8 +1,7 @@
use crate::withclones;
use adw::prelude::*; use adw::prelude::*;
use gtk::{ use gtk::{
gio, gio,
glib::{self, GString}, glib::{self, clone, GString},
}; };
use relm4::prelude::*; use relm4::prelude::*;
@ -94,33 +93,26 @@ pub fn path_row<F: Fn(Option<String>) + 'static + Clone>(
.valign(gtk::Align::Center) .valign(gtk::Align::Center)
.build(); .build();
row.add_suffix(&clear_btn); row.add_suffix(&clear_btn);
{ clear_btn.connect_clicked(clone!(@strong path_label, @strong cb => move |_| {
withclones![path_label, cb]; path_label.set_label("(None)");
clear_btn.connect_clicked(move |_| { cb(None)
path_label.set_label("(None)"); }));
cb(None)
});
}
let filedialog = gtk::FileDialog::builder() let filedialog = gtk::FileDialog::builder()
.modal(true) .modal(true)
.title(format!("Select Path for {}", title)) .title(format!("Select Path for {}", title))
.build(); .build();
{ row.connect_activated(clone!(@strong path_label => move |_| {
withclones![path_label]; filedialog.select_folder(root_win.as_ref(), gio::Cancellable::NONE, clone!(@strong path_label, @strong cb => move |res| {
row.connect_activated(move |_| { if let Ok(file) = res {
withclones![path_label, cb]; if let Some(path) = file.path() {
filedialog.select_folder(root_win.as_ref(), gio::Cancellable::NONE, move |res| { let path_s = path.to_str().unwrap().to_string();
if let Ok(file) = res { path_label.set_text(path_s.as_str());
if let Some(path) = file.path() { cb(Some(path_s))
let path_s = path.to_str().unwrap().to_string();
path_label.set_text(path_s.as_str());
cb(Some(path_s))
}
} }
}) }
}); }))
} }));
row row
} }

View file

@ -6,9 +6,9 @@ use crate::{
env_var_descriptions::env_var_descriptions_as_paragraph, env_var_descriptions::env_var_descriptions_as_paragraph,
profile::{LighthouseDriver, Profile, XRServiceType}, profile::{LighthouseDriver, Profile, XRServiceType},
ui::preference_rows::{combo_row, entry_row, path_row, switch_row}, ui::preference_rows::{combo_row, entry_row, path_row, switch_row},
withclones,
}; };
use adw::prelude::*; use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryVecDeque, prelude::*}; use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
@ -80,35 +80,30 @@ impl SimpleComponent for ProfileEditor {
set_vexpand: true, set_vexpand: true,
add: maingrp = &adw::PreferencesGroup { add: maingrp = &adw::PreferencesGroup {
set_title: "General", set_title: "General",
add: { add: &entry_row(
withclones![prof]; "Profile Name",
&entry_row("Profile Name", model.profile.borrow().name.as_str(), move |row| { model.profile.borrow().name.as_str(),
clone!(@strong prof => move |row| {
prof.borrow_mut().name = row.text().to_string(); prof.borrow_mut().name = row.text().to_string();
}) })
}, ),
add: { add: &switch_row(
withclones![prof]; "Update on Build", None,
&switch_row( model.profile.borrow().pull_on_build,
"Update on Build", None, clone!(@strong prof => move |_, state| {
model.profile.borrow().pull_on_build, prof.borrow_mut().pull_on_build = state;
move |_, state| { gtk::glib::Propagation::Proceed
prof.borrow_mut().pull_on_build = state; })
gtk::glib::Propagation::Proceed ),
} add: &path_row(
) "Install Prefix",
}, None,
add: { Some(model.profile.borrow().prefix.clone()),
withclones![prof]; Some(init.root_win.clone()),
&path_row( clone!(@strong prof => move |n_path| {
"Install Prefix", prof.borrow_mut().prefix = n_path.unwrap_or_default()
None, }),
Some(model.profile.borrow().prefix.clone()), ),
Some(init.root_win.clone()),
move |n_path| {
prof.borrow_mut().prefix = n_path.unwrap_or_default()
},
)
},
}, },
add: xrservicegrp = &adw::PreferencesGroup { add: xrservicegrp = &adw::PreferencesGroup {
set_title: "XR Service", set_title: "XR Service",
@ -119,227 +114,176 @@ impl SimpleComponent for ProfileEditor {
"For launch options, you can insert %command% as ", "For launch options, you can insert %command% as ",
"a placeholder for the actual XR Service command.", "a placeholder for the actual XR Service command.",
)), )),
add: { add: &combo_row(
withclones![prof]; "XR Service Type",
&combo_row( Some("Monado is for PCVR headsets, while WiVRn is for Andorid standalone headsets"),
"XR Service Type", model.profile.borrow().xrservice_type.to_string().as_str(),
Some("Monado is for PCVR headsets, while WiVRn is for Andorid standalone headsets"), XRServiceType::iter()
model.profile.borrow().xrservice_type.to_string().as_str(), .map(XRServiceType::to_string)
XRServiceType::iter() .collect::<Vec<String>>(),
.map(XRServiceType::to_string) clone!(@strong prof => move |row| {
.collect::<Vec<String>>(), prof.borrow_mut().xrservice_type =
move |row| { XRServiceType::from_number(row.selected());
prof.borrow_mut().xrservice_type = }),
XRServiceType::from_number(row.selected()); ),
}, add: &entry_row(
) "XR Service Launch Options",
}, model.profile.borrow().xrservice_launch_options.as_str(),
add: { clone!(@strong prof => move |row| {
withclones![prof]; prof.borrow_mut().xrservice_launch_options = row.text().trim().to_string();
&entry_row( })
"XR Service Launch Options", ),
model.profile.borrow().xrservice_launch_options.as_str(), add: &combo_row(
move |row| { "Lighthouse Driver",
prof.borrow_mut().xrservice_launch_options = row.text().trim().to_string(); Some(concat!(
} "Driver for lighhouse tracked XR devices (ie: Valve Index, HTC Vive...). Only applicable for Monado.\n\n",
) "Vive: 3DOF tracking\n\n",
}, "Survive: 6DOF reverse engineered lighthouse tracking provided by Libsurvive\n\n",
add: { "SteamVR: 6DOF lighthouse tracking using the proprietary SteamVR driver",
withclones![prof]; )),
&combo_row( model.profile.borrow().lighthouse_driver.to_string().as_str(),
"Lighthouse Driver", LighthouseDriver::iter()
Some(concat!( .map(LighthouseDriver::to_string)
"Driver for lighhouse tracked XR devices (ie: Valve Index, HTC Vive...). Only applicable for Monado.\n\n", .collect::<Vec<String>>(),
"Vive: 3DOF tracking\n\n", clone!(@strong prof => move |row| {
"Survive: 6DOF reverse engineered lighthouse tracking provided by Libsurvive\n\n", prof.borrow_mut().lighthouse_driver =
"SteamVR: 6DOF lighthouse tracking using the proprietary SteamVR driver", LighthouseDriver::from_number(row.selected());
)), })
model.profile.borrow().lighthouse_driver.to_string().as_str(), ),
LighthouseDriver::iter() add: &path_row(
.map(LighthouseDriver::to_string) "XR Service Path",
.collect::<Vec<String>>(), None,
move |row| { Some(model.profile.borrow().xrservice_path.clone()),
prof.borrow_mut().lighthouse_driver = Some(init.root_win.clone()),
LighthouseDriver::from_number(row.selected()); clone!(@strong prof => move |n_path| {
} prof.borrow_mut().xrservice_path = n_path.unwrap_or_default()
) }),
}, ),
add: { add: &entry_row(
withclones![prof]; "XR Service Repo",
&path_row( model.profile.borrow().xrservice_repo.clone().unwrap_or_default().as_str(),
"XR Service Path", clone!(@strong prof => move |row| {
None, let n_val = row.text().to_string();
Some(model.profile.borrow().xrservice_path.clone()), prof.borrow_mut().xrservice_repo = (!n_val.is_empty()).then_some(n_val);
Some(init.root_win.clone()), })
move |n_path| { ),
prof.borrow_mut().xrservice_path = n_path.unwrap_or_default()
},
)
},
add: {
withclones![prof];
&entry_row(
"XR Service Repo",
model.profile.borrow().xrservice_repo.clone().unwrap_or_default().as_str(),
move |row| {
let n_val = row.text().to_string();
prof.borrow_mut().xrservice_repo = (!n_val.is_empty()).then_some(n_val);
}
)
},
}, },
add: model.xrservice_cmake_flags_rows.widget(), add: model.xrservice_cmake_flags_rows.widget(),
add: opencompgrp = &adw::PreferencesGroup { add: opencompgrp = &adw::PreferencesGroup {
set_title: "OpenComposite", set_title: "OpenComposite",
set_description: Some("OpenVR driver built on top of OpenXR\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"), set_description: Some("OpenVR driver built on top of OpenXR\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"),
add: { add: &path_row(
withclones![prof]; "OpenComposite Path", None,
&path_row( Some(model.profile.borrow().opencomposite_path.clone()),
"OpenComposite Path", None, Some(init.root_win.clone()),
Some(model.profile.borrow().opencomposite_path.clone()), clone!(@strong prof => move |n_path| {
Some(init.root_win.clone()), prof.borrow_mut().opencomposite_path = n_path.unwrap_or_default();
move |n_path| { })
prof.borrow_mut().opencomposite_path = n_path.unwrap_or_default(); ),
} add: &entry_row(
) "OpenComposite Repo",
}, model.profile.borrow().opencomposite_repo.clone().unwrap_or_default().as_str(),
add: { clone!(@strong prof => move |row| {
withclones![prof]; let n_val = row.text().to_string();
&entry_row( prof.borrow_mut().opencomposite_repo = (!n_val.is_empty()).then_some(n_val);
"OpenComposite Repo", })
model.profile.borrow().opencomposite_repo.clone().unwrap_or_default().as_str(), ),
move |row| {
let n_val = row.text().to_string();
prof.borrow_mut().opencomposite_repo = (!n_val.is_empty()).then_some(n_val);
}
)
},
}, },
add: libsurvivegrp = &adw::PreferencesGroup { add: libsurvivegrp = &adw::PreferencesGroup {
set_title: "Libsurvive", set_title: "Libsurvive",
set_description: Some("Lighthouse tracking driver\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"), set_description: Some("Lighthouse tracking driver\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"),
add: { add: &switch_row(
withclones![prof]; "Enable Libsurvive", None,
&switch_row( model.profile.borrow().features.libsurvive.enabled,
"Enable Libsurvive", None, clone!(@strong prof => move |_, state| {
model.profile.borrow().features.libsurvive.enabled, prof.borrow_mut().features.libsurvive.enabled = state;
move |_, state| { gtk::glib::Propagation::Proceed
prof.borrow_mut().features.libsurvive.enabled = state; })
gtk::glib::Propagation::Proceed ),
} add: &path_row(
) "Libsurvive Path", None,
}, model.profile.borrow().features.libsurvive.path.clone(),
add: { Some(init.root_win.clone()),
withclones![prof]; clone!(@strong prof => move |n_path| {
&path_row( prof.borrow_mut().features.libsurvive.path = n_path;
"Libsurvive Path", None, })
model.profile.borrow().features.libsurvive.path.clone(), ),
Some(init.root_win.clone()), add: &entry_row(
move |n_path| { "Libsurvive Repo",
prof.borrow_mut().features.libsurvive.path = n_path; model.profile.borrow().features.libsurvive.repo.clone().unwrap_or_default().as_str(),
} clone!(@strong prof => move |row| {
) let n_val = row.text().to_string();
}, prof.borrow_mut().features.libsurvive.repo = (!n_val.is_empty()).then_some(n_val);
add: { })
withclones![prof]; ),
&entry_row(
"Libsurvive Repo",
model.profile.borrow().features.libsurvive.repo.clone().unwrap_or_default().as_str(),
move |row| {
let n_val = row.text().to_string();
prof.borrow_mut().features.libsurvive.repo = (!n_val.is_empty()).then_some(n_val);
}
)
},
}, },
add: openhmdgrp = &adw::PreferencesGroup { add: openhmdgrp = &adw::PreferencesGroup {
set_title: "OpenHMD", set_title: "OpenHMD",
set_description: Some("Legacy driver for older Oculus HMDs\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"), set_description: Some("Legacy driver for older Oculus HMDs\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"),
add: { add: &switch_row(
withclones![prof]; "Enable OpenHMD", None,
&switch_row( model.profile.borrow().features.openhmd.enabled,
"Enable OpenHMD", None, clone!(@strong prof => move |_, state| {
model.profile.borrow().features.openhmd.enabled, prof.borrow_mut().features.openhmd.enabled = state;
move |_, state| { gtk::glib::Propagation::Proceed
prof.borrow_mut().features.openhmd.enabled = state; })
gtk::glib::Propagation::Proceed ),
} add: &path_row(
) "OpenHMD Path", None,
}, model.profile.borrow().features.openhmd.path.clone(),
add: { Some(init.root_win.clone()),
withclones![prof]; clone!(@strong prof => move |n_path| {
&path_row( prof.borrow_mut().features.openhmd.path = n_path;
"OpenHMD Path", None, })
model.profile.borrow().features.openhmd.path.clone(), ),
Some(init.root_win.clone()), add: &entry_row(
move |n_path| { "OpenHMD Repo",
prof.borrow_mut().features.openhmd.path = n_path; model.profile.borrow().features.openhmd.repo.clone().unwrap_or_default().as_str(),
} clone!(@strong prof => move |row| {
) let n_val = row.text().to_string();
}, prof.borrow_mut().features.openhmd.repo = (!n_val.is_empty()).then_some(n_val);
add: { })
withclones![prof]; ),
&entry_row(
"OpenHMD Repo",
model.profile.borrow().features.openhmd.repo.clone().unwrap_or_default().as_str(),
move |row| {
let n_val = row.text().to_string();
prof.borrow_mut().features.openhmd.repo = (!n_val.is_empty()).then_some(n_val);
}
)
},
}, },
add: basaltgrp = &adw::PreferencesGroup { add: basaltgrp = &adw::PreferencesGroup {
set_title: "Basalt", set_title: "Basalt",
set_description: Some("Camera based SLAM tracking driver\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"), set_description: Some("Camera based SLAM tracking driver\n\nWhen specifying a repository, you can set a specific git ref (branch, tag, commit...) by appending a '#' followed by the ref"),
add: { add: &switch_row(
withclones![prof]; "Enable Basalt", None,
&switch_row( model.profile.borrow().features.basalt.enabled,
"Enable Basalt", None, clone!(@strong prof => move |_, state| {
model.profile.borrow().features.basalt.enabled, prof.borrow_mut().features.basalt.enabled = state;
move |_, state| { gtk::glib::Propagation::Proceed
prof.borrow_mut().features.basalt.enabled = state; })
gtk::glib::Propagation::Proceed ),
} add: &path_row(
) "Basalt Path", None,
}, model.profile.borrow().features.basalt.path.clone(),
add: { Some(init.root_win.clone()),
withclones![prof]; clone!(@strong prof => move |n_path| {
&path_row( prof.borrow_mut().features.basalt.path = n_path;
"Basalt Path", None, })
model.profile.borrow().features.basalt.path.clone(), ),
Some(init.root_win.clone()), add: &entry_row(
move |n_path| { "Basalt Repo",
prof.borrow_mut().features.basalt.path = n_path; model.profile.borrow().features.basalt.repo.clone().unwrap_or_default().as_str(),
} clone!(@strong prof => move |row| {
) let n_val = row.text().to_string();
}, prof.borrow_mut().features.basalt.repo = n_val.is_empty().then_some(n_val);
add: { })
withclones![prof]; ),
&entry_row(
"Basalt Repo",
model.profile.borrow().features.basalt.repo.clone().unwrap_or_default().as_str(),
move |row| {
let n_val = row.text().to_string();
prof.borrow_mut().features.basalt.repo = n_val.is_empty().then_some(n_val);
}
)
},
}, },
add: mercurygrp = &adw::PreferencesGroup { add: mercurygrp = &adw::PreferencesGroup {
set_title: "Mercury", set_title: "Mercury",
set_description: Some("Camera and OpenCV based hand tracking driver"), set_description: Some("Camera and OpenCV based hand tracking driver"),
add: { add: &switch_row(
withclones![prof]; "Enable Mercury", None,
&switch_row( model.profile.borrow().features.mercury_enabled,
"Enable Mercury", None, clone!(@strong prof => move |_, state| {
model.profile.borrow().features.mercury_enabled, prof.borrow_mut().features.mercury_enabled = state;
move |_, state| { gtk::glib::Propagation::Proceed
prof.borrow_mut().features.mercury_enabled = state; })
gtk::glib::Propagation::Proceed ),
}
)
},
}, },
add: model.env_rows.widget(), add: model.env_rows.widget(),
} }
@ -465,16 +409,17 @@ impl SimpleComponent for ProfileEditor {
.halign(gtk::Align::End) .halign(gtk::Align::End)
.build(); .build();
withclones![sender, name_entry, popover]; add_btn.connect_clicked(
add_btn.connect_clicked(move |_| { clone!(@strong sender, @strong name_entry, @strong popover => move |_| {
let key_gstr = name_entry.text(); let key_gstr = name_entry.text();
let key = key_gstr.trim(); let key = key_gstr.trim();
if !key.is_empty() { if !key.is_empty() {
popover.popdown(); popover.popdown();
name_entry.set_text(""); name_entry.set_text("");
sender.input($event(key.to_string())); sender.input($event(key.to_string()));
} }
}); })
);
btn btn
}}; }};
} }

View file

@ -12,10 +12,9 @@ use crate::{
build_window::{BuildStatus, BuildWindow, BuildWindowMsg, BuildWindowOutMsg}, build_window::{BuildStatus, BuildWindow, BuildWindowMsg, BuildWindowOutMsg},
job_worker::{internal_worker::JobWorkerOut, job::WorkerJob, JobWorker}, job_worker::{internal_worker::JobWorkerOut, job::WorkerJob, JobWorker},
}, },
withclones,
}; };
use adw::prelude::*; use adw::prelude::*;
use gtk::prelude::*; use gtk::{prelude::*, glib::clone};
use relm4::{ use relm4::{
actions::{ActionGroupName, RelmAction, RelmActionGroup}, actions::{ActionGroupName, RelmAction, RelmActionGroup},
new_action_group, new_stateless_action, new_action_group, new_stateless_action,
@ -468,22 +467,16 @@ impl SimpleComponent for StardustView {
let mut actions = RelmActionGroup::<StardustActionGroup>::new(); let mut actions = RelmActionGroup::<StardustActionGroup>::new();
{ stateless_action!(actions, BuildStardustAction, clone!(@strong sender => move |_| {
withclones![sender]; sender
stateless_action!(actions, BuildStardustAction, { .input_sender()
sender .emit(Self::Input::BuildStardust { update: false });
.input_sender() }));
.emit(Self::Input::BuildStardust { update: false }); stateless_action!(actions, UpdateStardustAction, clone!(@strong sender => move |_| {
}); sender
} .input_sender()
{ .emit(Self::Input::BuildStardust { update: true });
withclones![sender]; }));
stateless_action!(actions, UpdateStardustAction, {
sender
.input_sender()
.emit(Self::Input::BuildStardust { update: true });
});
}
root.insert_action_group( root.insert_action_group(
StardustActionGroup::NAME, StardustActionGroup::NAME,

View file

@ -0,0 +1,210 @@
use std::{
collections::{HashMap, VecDeque},
path::Path,
thread::sleep,
time::Duration,
};
use crate::paths::get_steamvr_bin_dir_path;
use super::job_worker::{
internal_worker::JobWorkerOut,
job::{FuncWorkerOut, WorkerJob},
JobWorker,
};
use relm4::{
gtk::{self, prelude::*},
ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent,
};
#[tracker::track]
pub struct SteamVrCalibrationBox {
calibration_running: bool,
calibration_result: Option<String>,
calibration_success: bool,
visible: bool,
xrservice_active: bool,
#[tracker::do_not_track]
server_worker: Option<JobWorker>,
#[tracker::do_not_track]
calibration_worker: Option<JobWorker>,
}
#[derive(Debug)]
pub enum SteamVrCalibrationBoxMsg {
SetVisible(bool),
RunCalibration,
OnServerWorkerExit(i32),
OnCalWorkerExit(i32),
XRServiceActiveChanged(bool),
NoOp,
}
#[relm4::component(pub)]
impl SimpleComponent for SteamVrCalibrationBox {
type Init = ();
type Input = SteamVrCalibrationBoxMsg;
type Output = ();
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: false,
set_spacing: 12,
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::visible())"]
set_visible: model.visible,
gtk::Label {
add_css_class: "heading",
set_hexpand: true,
set_xalign: 0.0,
set_label: "SteamVR Calibration",
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Label {
add_css_class: "dim-label",
set_hexpand: true,
set_label: concat!(
"Run a quick SteamVR calibration.\n\n",
"\u{2022} Plug in your HMD and place it on the floor, ",
"approximately in the middle of your play area\n",
"\u{2022} Make sure your controllers and other VR devices ",
"are turned off\n",
"\u{2022} Click the Calibrate button and wait for the ",
"process to finish\n\n",
"Note that the orientation of your HMD during this process ",
"will dictate the forward direction of your play area.",
),
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Label {
add_css_class: "error",
add_css_class: "success",
set_hexpand: true,
#[track = "model.changed(Self::calibration_result())"]
set_visible: model.calibration_result.is_some(),
#[track = "model.changed(Self::calibration_result())"]
set_label: model.calibration_result.as_ref().unwrap_or(&String::new()),
#[track = "model.changed(Self::calibration_success())"]
set_class_active: ("error", !model.calibration_success),
#[track = "model.changed(Self::calibration_success())"]
set_class_active: ("success", model.calibration_success),
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Button {
add_css_class: "suggested-action",
set_label: "Calibrate",
set_halign: gtk::Align::Start,
#[track = "model.changed(Self::calibration_running()) || model.changed(Self::xrservice_active())"]
set_sensitive: !model.xrservice_active && !model.calibration_running,
connect_clicked[sender] => move |_| {
sender.input(Self::Input::RunCalibration);
}
},
},
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset();
match message {
Self::Input::SetVisible(state) => {
self.set_visible(state);
}
Self::Input::XRServiceActiveChanged(active) => {
self.set_xrservice_active(active);
}
Self::Input::RunCalibration => {
self.set_calibration_result(None);
let steamvr_bin_dir = get_steamvr_bin_dir_path();
if !Path::new(&steamvr_bin_dir).is_dir() {
self.set_calibration_success(false);
self.set_calibration_result(Some("SteamVR not found".into()));
return;
}
let mut env: HashMap<String, String> = HashMap::new();
env.insert("LD_LIBRARY_PATH".into(), steamvr_bin_dir.clone());
let vrcmd = format!("{steamvr_bin_dir}/vrcmd");
let mut server_worker = {
let mut jobs: VecDeque<WorkerJob> = VecDeque::new();
jobs.push_back(WorkerJob::new_cmd(
Some(env.clone()),
vrcmd.clone(),
Some(vec!["--pollposes".into()]),
));
JobWorker::new(jobs, sender.input_sender(), |msg| match msg {
JobWorkerOut::Log(_) => Self::Input::NoOp,
JobWorkerOut::Exit(code) => Self::Input::OnServerWorkerExit(code),
})
};
let mut cal_worker = {
let mut jobs: VecDeque<WorkerJob> = VecDeque::new();
jobs.push_back(WorkerJob::new_func(Box::new(move || {
sleep(Duration::from_secs(2));
FuncWorkerOut {
success: true,
out: vec![],
}
})));
jobs.push_back(WorkerJob::new_cmd(
Some(env),
vrcmd,
Some(vec!["--resetroomsetup".into()]),
));
JobWorker::new(jobs, sender.input_sender(), |msg| match msg {
JobWorkerOut::Log(_) => Self::Input::NoOp,
JobWorkerOut::Exit(code) => Self::Input::OnCalWorkerExit(code),
})
};
server_worker.start();
cal_worker.start();
self.server_worker = Some(server_worker);
self.calibration_worker = Some(cal_worker);
}
Self::Input::OnServerWorkerExit(_) => {
self.calibration_running = false;
}
Self::Input::OnCalWorkerExit(code) => {
self.calibration_success = code == 0;
self.calibration_result = if code == 0 {
Some("Calibration completed".into())
} else {
Some(format!("Calibration failed with code {code}"))
};
if let Some(sw) = self.server_worker.as_ref() {
sw.stop();
}
}
Self::Input::NoOp => {}
}
}
fn init(
init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = Self {
tracker: 0,
visible: false,
calibration_result: None,
calibration_running: false,
calibration_success: false,
xrservice_active: false,
server_worker: None,
calibration_worker: None,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
}

View file

@ -1,4 +1,4 @@
use gtk4::prelude::*; use gtk4::{gio, prelude::*};
pub fn limit_dropdown_width(dd: &gtk4::DropDown, chars: i32) { pub fn limit_dropdown_width(dd: &gtk4::DropDown, chars: i32) {
let mut dd_child = dd let mut dd_child = dd
@ -44,3 +44,9 @@ pub fn warning_heading() -> gtk4::Box {
b b
} }
pub fn open_with_default_handler(uri: &str) {
if let Err(e) = gio::AppInfo::launch_default_for_uri(uri, gio::AppLaunchContext::NONE) {
eprintln!("Error opening uri {}: {}", uri, e)
};
}