Merge branch 'feat/steamvr_quick_cal' into 'main'

feat: steamvr quick calibration in main view

See merge request gabmus/envision!23
This commit is contained in:
GabMus 2023-12-22 08:16:24 +00:00
commit e212c449cf
4 changed files with 240 additions and 1 deletions

View file

@ -125,3 +125,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

@ -3,6 +3,7 @@ use super::devices_box::{DevicesBox, DevicesBoxMsg};
use super::install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivrnBoxMsg};
use super::profile_editor::{ProfileEditor, ProfileEditorMsg, ProfileEditorOutMsg};
use super::steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg};
use super::steamvr_calibration_box::SteamVrCalibrationBox;
use crate::config::Config;
use crate::constants::APP_NAME;
use crate::file_utils::mount_has_nosuid;
@ -17,6 +18,7 @@ use crate::ui::app::{
BuildProfileDebugAction, ConfigFbtAction, DebugViewToggleAction,
};
use crate::ui::profile_editor::ProfileEditorInit;
use crate::ui::steamvr_calibration_box::SteamVrCalibrationBoxMsg;
use crate::ui::util::{limit_dropdown_width, warning_heading};
use crate::xr_devices::XRDevice;
use gtk::prelude::*;
@ -47,6 +49,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,
}
@ -334,6 +338,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,
@ -368,7 +373,7 @@ impl SimpleComponent for MainView {
set_label: "Calibrate",
set_halign: gtk::Align::Start,
connect_clicked[sender] => move |_| {
sender.output(Self::Output::OpenLibsurviveSetup).expect("Sender outut failed");
sender.output(Self::Output::OpenLibsurviveSetup).expect("Sender output failed");
}
},
},
@ -465,6 +470,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![]));
}
@ -492,6 +500,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()));
@ -630,6 +643,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,
@ -647,6 +667,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,
tracker: 0,
};

View file

@ -15,5 +15,6 @@ pub mod main_view;
pub mod preference_rows;
pub mod profile_editor;
pub mod steam_launch_options_box;
mod steamvr_calibration_box;
pub mod util;
pub mod wivrn_conf_editor;

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 }
}
}