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" =>
"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.",
"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" =>
"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" => "",

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_DEBUG_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(
"LD_LIBRARY_PATH".into(),
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_DEBUG_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(
"LD_LIBRARY_PATH".into(),
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("XRT_DEBUG_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(
"LD_LIBRARY_PATH".into(),
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_CURATED_GUI".into(), "1".into());
environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into());
Profile {
uuid: "wivrn-default".into(),
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_DEBUG_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(
"LD_LIBRARY_PATH".into(),
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::libsurvive_setup_window::LibsurviveSetupWindow;
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_libsurvive::get_build_libsurvive_jobs;
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::linux_distro::get_distro;
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::profiles::lighthouse::lighthouse_profile;
use crate::profiles::openhmd::openhmd_profile;
use crate::profiles::survive::survive_profile;
use crate::profiles::wivrn::wivrn_profile;
use crate::profiles::wmr::wmr_profile;
use crate::stateless_action;
use crate::ui::build_window::{BuildWindowMsg, BuildWindowOutMsg};
use crate::ui::debug_view::DebugViewInit;
use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg;
use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg};
use crate::xr_devices::XRDevice;
use crate::{stateless_action, withclones};
use gtk::glib::clone;
use gtk::prelude::*;
use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup};
use relm4::adw::traits::MessageDialogExt;
@ -124,6 +126,8 @@ pub enum Msg {
Quit,
ParseLog(Vec<String>),
ConfigFbt,
DebugOpenPrefix,
DebugOpenData,
}
impl App {
@ -619,6 +623,15 @@ impl SimpleComponent for App {
));
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();
{
withclones![sender];
stateless_action!(actions, BuildProfileAction, {
stateless_action!(
actions,
BuildProfileAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(false, false));
});
}
{
withclones![sender];
stateless_action!(actions, BuildProfileCleanAction, {
})
);
stateless_action!(
actions,
BuildProfileCleanAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(true, false));
});
}
{
withclones![sender];
stateless_action!(actions, BuildProfileDebugAction, {
})
);
stateless_action!(
actions,
BuildProfileDebugAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(false, true));
});
}
{
withclones![sender];
stateless_action!(actions, BuildProfileCleanDebugAction, {
})
);
stateless_action!(
actions,
BuildProfileCleanDebugAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::BuildProfile(true, true));
});
}
{
withclones![sender];
stateless_action!(actions, ConfigFbtAction, {
})
);
stateless_action!(
actions,
ConfigFbtAction,
clone!(@strong sender => move |_| {
sender.input_sender().emit(Msg::ConfigFbt);
});
}
})
);
{
let abd_sender = model.about_dialog.sender().clone();
stateless_action!(actions, AboutAction, {
stateless_action!(actions, AboutAction, move |_| {
abd_sender.send(()).unwrap();
});
}
{
withclones![sender];
stateless_action!(actions, QuitAction, {
stateless_action!(
actions,
QuitAction,
clone!(@strong sender => move |_| {
sender.input(Msg::Quit);
});
}
{
withclones![sender];
actions.add_action(RelmAction::<DebugViewToggleAction>::new_stateful(
&model.enable_debug_view,
move |_, state| {
let s = *state;
*state = !s;
sender.input(Msg::EnableDebugViewChanged(*state));
},
))
}
})
);
stateless_action!(
actions,
DebugOpenDataAction,
clone!(@strong sender => move |_| {
sender.input(Msg::DebugOpenData);
})
);
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()));
@ -780,13 +810,13 @@ impl SimpleComponent for App {
model.config.clone(),
));
{
withclones![sender];
glib::timeout_add_local(Duration::from_millis(1000), move || {
glib::timeout_add_local(
Duration::from_millis(1000),
clone!(@strong sender => move || {
sender.input(Msg::ClockTicking);
glib::ControlFlow::Continue
});
}
}),
);
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 QuitAction, AppActionGroup, "quit");
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_parser::MonadoLog;
use crate::withclones;
use crate::ui::app::{DebugOpenDataAction, DebugOpenPrefixAction};
use gtk::glib::clone;
use gtk::prelude::*;
use relm4::prelude::*;
use relm4::{ComponentSender, SimpleComponent};
@ -57,6 +58,15 @@ impl SimpleComponent for DebugView {
type Input = DebugViewMsg;
type Output = ();
menu! {
debug_menu: {
section! {
"Open Envision _Data Folder" => DebugOpenDataAction,
"Open _Prefix Folder" => DebugOpenPrefixAction,
},
}
}
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
@ -66,6 +76,11 @@ impl SimpleComponent for DebugView {
set_hexpand: true,
set_vexpand: false,
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 {
set_icon_name: "edit-find-symbolic",
set_tooltip_text: Some("Filter Log"),
@ -255,24 +270,18 @@ impl SimpleComponent for DebugView {
if let Some(btn) = log_level_dropdown.first_child() {
btn.add_css_class("flat");
}
{
withclones![sender];
log_level_dropdown.connect_selected_notify(move |dd| {
sender.input(Self::Input::LogLevelChanged(
*LogLevel::iter()
.as_slice()
.get(dd.selected() as usize)
.unwrap(),
));
});
}
log_level_dropdown.connect_selected_notify(clone!(@strong sender => move |dd| {
sender.input(Self::Input::LogLevelChanged(
*LogLevel::iter()
.as_slice()
.get(dd.selected() as usize)
.unwrap(),
));
}));
{
withclones![sender];
adw::StyleManager::default().connect_dark_notify(move |_| {
sender.input(Self::Input::SetColorScheme);
});
}
adw::StyleManager::default().connect_dark_notify(clone!(@strong sender => move |_| {
sender.input(Self::Input::SetColorScheme);
}));
Self::set_color_scheme(&textbuf);
let mut model = Self {

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use crate::{profile::Profile, withclones};
use crate::profile::{LighthouseDriver, Profile};
use super::{
job::{CmdWorkerData, FuncWorkerOut, WorkerJob},
@ -89,7 +89,7 @@ impl Worker for InternalJobWorker {
let mut job = self.jobs.pop_front().unwrap();
match &mut job {
WorkerJob::Cmd(data) => {
withclones![data];
let data = data.clone();
if let Ok(mut cmd) = Command::new(data.command)
.args(data.args)
.envs(data.environment)
@ -161,10 +161,15 @@ impl InternalJobWorker {
) -> relm4::WorkerHandle<InternalJobWorker> {
let mut env = prof.environment.clone();
if !env.contains_key("LH_DRIVER") {
env.insert(
"LH_DRIVER".into(),
prof.lighthouse_driver.to_string().to_lowercase(),
);
match prof.lighthouse_driver {
// don't set LH_DRIVER for steamvr driver, set this instead
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 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_rules! stateless_action {
($group:ident, $name:ident, $ex:expr) => {
$group.add_action(RelmAction::<$name>::new_stateless(move |_| $ex));
($group:ident, $name:ident, $fun:expr) => {
$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::stardust::stardust_view::StardustView;
use super::steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg};
use super::steamvr_calibration_box::SteamVrCalibrationBox;
use crate::config::Config;
use crate::file_utils::mount_has_nosuid;
use crate::gpu_profile::{
@ -18,6 +19,7 @@ use crate::ui::app::{
};
use crate::ui::profile_editor::ProfileEditorInit;
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::xr_devices::XRDevice;
use gtk::prelude::*;
@ -48,6 +50,8 @@ pub struct MainView {
#[tracker::do_not_track]
profile_editor: Option<Controller<ProfileEditor>>,
#[tracker::do_not_track]
steamvr_calibration_box: Controller<SteamVrCalibrationBox>,
#[tracker::do_not_track]
root_win: gtk::Window,
#[tracker::do_not_track]
stardust_view: Controller<StardustView>,
@ -338,6 +342,7 @@ impl SimpleComponent for MainView {
},
model.steam_launch_options_box.widget(),
model.install_wivrn_box.widget(),
model.steamvr_calibration_box.widget(),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
@ -347,6 +352,13 @@ impl SimpleComponent for MainView {
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
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 {
add_css_class: "heading",
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"),
@ -479,6 +511,9 @@ impl SimpleComponent for MainView {
}
Self::Input::XRServiceActiveChanged(active, profile) => {
self.set_xrservice_active(active);
self.steamvr_calibration_box
.sender()
.emit(SteamVrCalibrationBoxMsg::XRServiceActiveChanged(active));
if !active {
sender.input(Self::Input::UpdateDevices(vec![]));
}
@ -506,6 +541,11 @@ impl SimpleComponent for MainView {
}
Self::Input::UpdateSelectedProfile(prof) => {
self.set_selected_profile(prof.clone());
self.steamvr_calibration_box
.sender()
.emit(SteamVrCalibrationBoxMsg::SetVisible(
prof.lighthouse_driver == LighthouseDriver::SteamVR,
));
self.install_wivrn_box
.sender()
.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 {
xrservice_active: false,
enable_debug_view: init.config.debug_view_enabled,
@ -661,6 +708,7 @@ impl SimpleComponent for MainView {
profile_not_editable_dialog,
profile_delete_confirm_dialog,
root_win: init.root_win.clone(),
steamvr_calibration_box,
profile_editor: None,
stardust_view: StardustView::builder()
.launch(StardustViewInit {

View file

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

View file

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

View file

@ -6,9 +6,9 @@ use crate::{
env_var_descriptions::env_var_descriptions_as_paragraph,
profile::{LighthouseDriver, Profile, XRServiceType},
ui::preference_rows::{combo_row, entry_row, path_row, switch_row},
withclones,
};
use adw::prelude::*;
use gtk::glib::clone;
use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
use std::{cell::RefCell, rc::Rc};
@ -80,35 +80,30 @@ impl SimpleComponent for ProfileEditor {
set_vexpand: true,
add: maingrp = &adw::PreferencesGroup {
set_title: "General",
add: {
withclones![prof];
&entry_row("Profile Name", model.profile.borrow().name.as_str(), move |row| {
add: &entry_row(
"Profile Name",
model.profile.borrow().name.as_str(),
clone!(@strong prof => move |row| {
prof.borrow_mut().name = row.text().to_string();
})
},
add: {
withclones![prof];
&switch_row(
"Update on Build", None,
model.profile.borrow().pull_on_build,
move |_, state| {
prof.borrow_mut().pull_on_build = state;
gtk::glib::Propagation::Proceed
}
)
},
add: {
withclones![prof];
&path_row(
"Install Prefix",
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: &switch_row(
"Update on Build", None,
model.profile.borrow().pull_on_build,
clone!(@strong prof => move |_, state| {
prof.borrow_mut().pull_on_build = state;
gtk::glib::Propagation::Proceed
})
),
add: &path_row(
"Install Prefix",
None,
Some(model.profile.borrow().prefix.clone()),
Some(init.root_win.clone()),
clone!(@strong prof => move |n_path| {
prof.borrow_mut().prefix = n_path.unwrap_or_default()
}),
),
},
add: xrservicegrp = &adw::PreferencesGroup {
set_title: "XR Service",
@ -119,227 +114,176 @@ impl SimpleComponent for ProfileEditor {
"For launch options, you can insert %command% as ",
"a placeholder for the actual XR Service command.",
)),
add: {
withclones![prof];
&combo_row(
"XR Service Type",
Some("Monado is for PCVR headsets, while WiVRn is for Andorid standalone headsets"),
model.profile.borrow().xrservice_type.to_string().as_str(),
XRServiceType::iter()
.map(XRServiceType::to_string)
.collect::<Vec<String>>(),
move |row| {
prof.borrow_mut().xrservice_type =
XRServiceType::from_number(row.selected());
},
)
},
add: {
withclones![prof];
&entry_row(
"XR Service Launch Options",
model.profile.borrow().xrservice_launch_options.as_str(),
move |row| {
prof.borrow_mut().xrservice_launch_options = row.text().trim().to_string();
}
)
},
add: {
withclones![prof];
&combo_row(
"Lighthouse Driver",
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",
"SteamVR: 6DOF lighthouse tracking using the proprietary SteamVR driver",
)),
model.profile.borrow().lighthouse_driver.to_string().as_str(),
LighthouseDriver::iter()
.map(LighthouseDriver::to_string)
.collect::<Vec<String>>(),
move |row| {
prof.borrow_mut().lighthouse_driver =
LighthouseDriver::from_number(row.selected());
}
)
},
add: {
withclones![prof];
&path_row(
"XR Service Path",
None,
Some(model.profile.borrow().xrservice_path.clone()),
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: &combo_row(
"XR Service Type",
Some("Monado is for PCVR headsets, while WiVRn is for Andorid standalone headsets"),
model.profile.borrow().xrservice_type.to_string().as_str(),
XRServiceType::iter()
.map(XRServiceType::to_string)
.collect::<Vec<String>>(),
clone!(@strong prof => move |row| {
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(),
clone!(@strong prof => move |row| {
prof.borrow_mut().xrservice_launch_options = row.text().trim().to_string();
})
),
add: &combo_row(
"Lighthouse Driver",
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",
"SteamVR: 6DOF lighthouse tracking using the proprietary SteamVR driver",
)),
model.profile.borrow().lighthouse_driver.to_string().as_str(),
LighthouseDriver::iter()
.map(LighthouseDriver::to_string)
.collect::<Vec<String>>(),
clone!(@strong prof => move |row| {
prof.borrow_mut().lighthouse_driver =
LighthouseDriver::from_number(row.selected());
})
),
add: &path_row(
"XR Service Path",
None,
Some(model.profile.borrow().xrservice_path.clone()),
Some(init.root_win.clone()),
clone!(@strong prof => move |n_path| {
prof.borrow_mut().xrservice_path = n_path.unwrap_or_default()
}),
),
add: &entry_row(
"XR Service Repo",
model.profile.borrow().xrservice_repo.clone().unwrap_or_default().as_str(),
clone!(@strong prof => 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: opencompgrp = &adw::PreferencesGroup {
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"),
add: {
withclones![prof];
&path_row(
"OpenComposite Path", None,
Some(model.profile.borrow().opencomposite_path.clone()),
Some(init.root_win.clone()),
move |n_path| {
prof.borrow_mut().opencomposite_path = n_path.unwrap_or_default();
}
)
},
add: {
withclones![prof];
&entry_row(
"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: &path_row(
"OpenComposite Path", None,
Some(model.profile.borrow().opencomposite_path.clone()),
Some(init.root_win.clone()),
clone!(@strong prof => 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(),
clone!(@strong prof => 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 {
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"),
add: {
withclones![prof];
&switch_row(
"Enable Libsurvive", None,
model.profile.borrow().features.libsurvive.enabled,
move |_, state| {
prof.borrow_mut().features.libsurvive.enabled = state;
gtk::glib::Propagation::Proceed
}
)
},
add: {
withclones![prof];
&path_row(
"Libsurvive Path", None,
model.profile.borrow().features.libsurvive.path.clone(),
Some(init.root_win.clone()),
move |n_path| {
prof.borrow_mut().features.libsurvive.path = n_path;
}
)
},
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: &switch_row(
"Enable Libsurvive", None,
model.profile.borrow().features.libsurvive.enabled,
clone!(@strong prof => move |_, state| {
prof.borrow_mut().features.libsurvive.enabled = state;
gtk::glib::Propagation::Proceed
})
),
add: &path_row(
"Libsurvive Path", None,
model.profile.borrow().features.libsurvive.path.clone(),
Some(init.root_win.clone()),
clone!(@strong prof => move |n_path| {
prof.borrow_mut().features.libsurvive.path = n_path;
})
),
add: &entry_row(
"Libsurvive Repo",
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: openhmdgrp = &adw::PreferencesGroup {
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"),
add: {
withclones![prof];
&switch_row(
"Enable OpenHMD", None,
model.profile.borrow().features.openhmd.enabled,
move |_, state| {
prof.borrow_mut().features.openhmd.enabled = state;
gtk::glib::Propagation::Proceed
}
)
},
add: {
withclones![prof];
&path_row(
"OpenHMD Path", None,
model.profile.borrow().features.openhmd.path.clone(),
Some(init.root_win.clone()),
move |n_path| {
prof.borrow_mut().features.openhmd.path = n_path;
}
)
},
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: &switch_row(
"Enable OpenHMD", None,
model.profile.borrow().features.openhmd.enabled,
clone!(@strong prof => move |_, state| {
prof.borrow_mut().features.openhmd.enabled = state;
gtk::glib::Propagation::Proceed
})
),
add: &path_row(
"OpenHMD Path", None,
model.profile.borrow().features.openhmd.path.clone(),
Some(init.root_win.clone()),
clone!(@strong prof => move |n_path| {
prof.borrow_mut().features.openhmd.path = n_path;
})
),
add: &entry_row(
"OpenHMD Repo",
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: basaltgrp = &adw::PreferencesGroup {
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"),
add: {
withclones![prof];
&switch_row(
"Enable Basalt", None,
model.profile.borrow().features.basalt.enabled,
move |_, state| {
prof.borrow_mut().features.basalt.enabled = state;
gtk::glib::Propagation::Proceed
}
)
},
add: {
withclones![prof];
&path_row(
"Basalt Path", None,
model.profile.borrow().features.basalt.path.clone(),
Some(init.root_win.clone()),
move |n_path| {
prof.borrow_mut().features.basalt.path = n_path;
}
)
},
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: &switch_row(
"Enable Basalt", None,
model.profile.borrow().features.basalt.enabled,
clone!(@strong prof => move |_, state| {
prof.borrow_mut().features.basalt.enabled = state;
gtk::glib::Propagation::Proceed
})
),
add: &path_row(
"Basalt Path", None,
model.profile.borrow().features.basalt.path.clone(),
Some(init.root_win.clone()),
clone!(@strong prof => move |n_path| {
prof.borrow_mut().features.basalt.path = n_path;
})
),
add: &entry_row(
"Basalt Repo",
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: mercurygrp = &adw::PreferencesGroup {
set_title: "Mercury",
set_description: Some("Camera and OpenCV based hand tracking driver"),
add: {
withclones![prof];
&switch_row(
"Enable Mercury", None,
model.profile.borrow().features.mercury_enabled,
move |_, state| {
prof.borrow_mut().features.mercury_enabled = state;
gtk::glib::Propagation::Proceed
}
)
},
add: &switch_row(
"Enable Mercury", None,
model.profile.borrow().features.mercury_enabled,
clone!(@strong prof => move |_, state| {
prof.borrow_mut().features.mercury_enabled = state;
gtk::glib::Propagation::Proceed
})
),
},
add: model.env_rows.widget(),
}
@ -465,16 +409,17 @@ impl SimpleComponent for ProfileEditor {
.halign(gtk::Align::End)
.build();
withclones![sender, name_entry, popover];
add_btn.connect_clicked(move |_| {
let key_gstr = name_entry.text();
let key = key_gstr.trim();
if !key.is_empty() {
popover.popdown();
name_entry.set_text("");
sender.input($event(key.to_string()));
}
});
add_btn.connect_clicked(
clone!(@strong sender, @strong name_entry, @strong popover => move |_| {
let key_gstr = name_entry.text();
let key = key_gstr.trim();
if !key.is_empty() {
popover.popdown();
name_entry.set_text("");
sender.input($event(key.to_string()));
}
})
);
btn
}};
}

View file

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