diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e6004b9..d32c0ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ cargo:clippy: RUSTFLAGS: "-Dwarnings" script: - apt-get update - - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev curl -y + - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev libopenxr-dev curl -y - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh - chmod +x /tmp/rustup.sh - /tmp/rustup.sh -y @@ -38,7 +38,7 @@ cargo:test: stage: check script: - apt-get update - - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev curl -y + - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev libopenxr-dev curl -y - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh - chmod +x /tmp/rustup.sh - /tmp/rustup.sh -y @@ -55,7 +55,7 @@ appimage: stage: deploy script: - apt-get update - - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev curl -y + - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev libopenxr-dev curl -y - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh - chmod +x /tmp/rustup.sh - /tmp/rustup.sh -y diff --git a/Cargo.lock b/Cargo.lock index 53e06ed..87de428 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,7 @@ checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.4", ] [[package]] @@ -278,6 +278,7 @@ dependencies = [ "libadwaita", "libmonado-rs", "nix", + "openxr", "phf", "phf_macros", "relm4", @@ -1075,6 +1076,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + [[package]] name = "libmonado-rs" version = "0.1.0" @@ -1254,6 +1265,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nix" version = "0.29.0" @@ -1374,6 +1391,25 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "openxr" +version = "0.18.0" +source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76" +dependencies = [ + "libc", + "libloading 0.8.5", + "ndk-context", + "openxr-sys", +] + +[[package]] +name = "openxr-sys" +version = "0.10.0" +source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76" +dependencies = [ + "libc", +] + [[package]] name = "pango" version = "0.19.5" diff --git a/Cargo.toml b/Cargo.toml index b5bf147..bc54d88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,6 @@ tracker = "0.2.1" uuid = { version = "1.8.0", features = ["v4", "fast-rng"] } vte4 = { version = "0.7.1", features = ["v0_72"] } xdg = "2.5.2" +openxr = { git = "https://github.com/galister/openxrs", rev = "af4a55d", features = [ + "linked", +] } diff --git a/src/main.rs b/src/main.rs index 262dc3f..a36fdb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ pub mod linux_distro; pub mod log_level; pub mod log_parser; pub mod monado_utils; +pub mod openxr_prober; pub mod paths; pub mod profile; pub mod profiles; diff --git a/src/openxr_prober.rs b/src/openxr_prober.rs new file mode 100644 index 0000000..96a04b3 --- /dev/null +++ b/src/openxr_prober.rs @@ -0,0 +1,39 @@ +// gracefully stolen from https://github.com/galister/wlx-overlay-s/blob/main/src/backend/openxr/helpers.rs#L9 +// original code licensed gpl 3 +use openxr as xr; + +use crate::constants::CMD_NAME; + +pub fn is_openxr_ready() -> bool { + let entry = xr::Entry::linked(); + + if entry.enumerate_extensions().is_err() { + return false; + }; + + let enabled_extensions = xr::ExtensionSet::default(); + + let layers = []; + + let Ok(xr_instance) = entry.create_instance( + &xr::ApplicationInfo { + application_name: &format!("{}-openxr-prober", CMD_NAME), + application_version: 0, + engine_name: CMD_NAME, + engine_version: 0, + }, + &enabled_extensions, + &layers, + ) else { + return false; + }; + + if xr_instance + .system(xr::FormFactor::HEAD_MOUNTED_DISPLAY) + .is_err() + { + return false; + }; + + true +} diff --git a/src/ui/app.rs b/src/ui/app.rs index a028849..fc69c47 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -4,7 +4,7 @@ use super::build_window::{BuildStatus, BuildWindow}; use super::cmdline_opts::CmdLineOpts; use super::debug_view::{DebugView, DebugViewMsg}; use super::job_worker::internal_worker::JobWorkerOut; -use super::job_worker::job::WorkerJob; +use super::job_worker::job::{FuncWorkerOut, WorkerJob}; use super::job_worker::JobWorker; use super::libsurvive_setup_window::LibsurviveSetupWindow; use super::main_view::MainViewMsg; @@ -35,6 +35,7 @@ use crate::file_builders::openvrpaths_vrpath::{ use crate::file_utils::setcap_cap_sys_nice_eip; use crate::is_appimage::IS_APPIMAGE; use crate::linux_distro::LinuxDistro; +use crate::openxr_prober::is_openxr_ready; use crate::paths::{get_data_dir, get_ipc_file_path}; use crate::profile::{Profile, XRServiceType}; use crate::stateless_action; @@ -111,6 +112,9 @@ pub struct App { wivrn_appimage_warn_shown: bool, #[tracker::do_not_track] configure_wivrn_action: gtk::gio::SimpleAction, + + #[tracker::do_not_track] + openxr_prober_worker: Option, } #[derive(Debug)] @@ -139,6 +143,9 @@ pub enum Msg { DebugCopyEnvVars, OpenWivrnConfig, HandleCommandLine(CmdLineOpts), + StartProber, + OnProberExit(bool), + NoOp, } impl App { @@ -203,23 +210,6 @@ impl App { debug, ); worker.start(); - if let Some(autostart_cmd) = &prof.autostart_command { - let autostart_worker = JobWorker::new_with_timer( - Duration::from_secs(5), - WorkerJob::new_cmd( - Some(prof.environment.clone()), - "sh".into(), - Some(vec!["-c".into(), autostart_cmd.clone()]), - ), - sender.input_sender(), - |msg| match msg { - JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows), - JobWorkerOut::Exit(code) => Msg::OnAutostartExit(code), - }, - ); - autostart_worker.start(); - self.autostart_worker = Some(autostart_worker) - } self.xrservice_worker = Some(worker); self.main_view .sender() @@ -233,6 +223,7 @@ impl App { .sender() .emit(DebugViewMsg::XRServiceActiveChanged(true)); self.set_inhibit_session(true); + sender.input(Msg::StartProber); } else { alert( "Failed to start profile", @@ -245,6 +236,24 @@ impl App { } } + pub fn run_autostart(&mut self, sender: ComponentSender) { + let prof = self.get_selected_profile(); + if let Some(autostart_cmd) = &prof.autostart_command { + let mut jobs = VecDeque::new(); + jobs.push_back(WorkerJob::new_cmd( + Some(prof.environment.clone()), + "sh".into(), + Some(vec!["-c".into(), autostart_cmd.clone()]), + )); + let autostart_worker = JobWorker::new(jobs, sender.input_sender(), |msg| match msg { + JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows), + JobWorkerOut::Exit(code) => Msg::OnAutostartExit(code), + }); + autostart_worker.start(); + self.autostart_worker = Some(autostart_worker); + } + } + pub fn restore_openxr_openvr_files(&self) { restore_runtime_entrypoint(); if let Err(e) = set_current_active_runtime_to_steam() { @@ -264,6 +273,11 @@ impl App { } pub fn shutdown_xrservice(&mut self) { + if let Some(w) = self.openxr_prober_worker.as_ref() { + w.stop(); + // this can cause threads to remain hanging... + self.openxr_prober_worker = None; + } self.set_inhibit_session(false); if let Some(worker) = self.xrservice_worker.as_ref() { worker.stop(); @@ -331,6 +345,7 @@ impl SimpleComponent for App { self.reset(); match message { + Msg::NoOp => {} Msg::OnServiceLog(rows) => { if !rows.is_empty() { self.debug_view @@ -727,6 +742,50 @@ impl SimpleComponent for App { self.skip_depcheck = true; } } + Msg::StartProber => { + if self + .openxr_prober_worker + .as_ref() + .is_some_and(|w| w.exit_code().is_none()) + { + eprintln!(">>>>>>>>>>>>>>> prober already running!"); + // prober already running + return; + } + let worker = JobWorker::new_with_timer( + Duration::from_millis(500), + WorkerJob::new_func(Box::new(move || { + println!(">>>>>>>>>>>>>>> probing now <<<<<<<<<<<<<<<<<<"); + let ready = is_openxr_ready(); + println!(">>>>>>>>>>>>>>> ready? {}", ready); + FuncWorkerOut { + success: ready, + ..Default::default() + } + })), + sender.input_sender(), + |msg| match msg { + JobWorkerOut::Exit(code) => Self::Input::OnProberExit(code == 0), + _ => Self::Input::NoOp, + }, + ); + worker.start(); + self.openxr_prober_worker = Some(worker); + } + Msg::OnProberExit(success) => { + self.main_view + .sender() + .emit(MainViewMsg::UpdateXrServiceReady(success)); + if success { + self.run_autostart(sender.clone()); + } else if self + .xrservice_worker + .as_ref() + .is_some_and(|w| w.exit_code().is_none()) + { + sender.input(Msg::StartProber); + } + } } } @@ -875,6 +934,7 @@ impl SimpleComponent for App { skip_depcheck: false, wivrn_appimage_warn_shown: false, configure_wivrn_action, + openxr_prober_worker: None, }; let widgets = view_output!(); diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index a3b4f68..b51e29e 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -8,7 +8,7 @@ use crate::config::Config; use crate::dependencies::common::dep_pkexec; use crate::file_utils::mount_has_nosuid; use crate::gpu_profile::{get_amd_gpu_power_profile, GpuPowerProfile}; -use crate::profile::{LighthouseDriver, Profile}; +use crate::profile::{LighthouseDriver, Profile, XRServiceType}; use crate::steamvr_utils::chaperone_info_exists; use crate::ui::app::{ AboutAction, BuildProfileAction, BuildProfileCleanAction, ConfigureWivrnAction, @@ -48,6 +48,7 @@ pub struct MainView { steamvr_calibration_box: Controller, #[tracker::do_not_track] root_win: gtk::Window, + xrservice_ready: bool, } #[derive(Debug)] @@ -67,6 +68,7 @@ pub enum MainViewMsg { DuplicateProfile, SaveProfile(Profile), UpdateDevices(Vec), + UpdateXrServiceReady(bool), } #[derive(Debug)] @@ -180,6 +182,32 @@ impl SimpleComponent for MainView { }, }, }, + adw::Bin { + #[track = "model.changed(Self::xrservice_active())"] + set_visible: model.xrservice_active, + add_css_class: "card", + gtk::Label { + #[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready())"] + set_label: if model.xrservice_ready { + "Service ready, you can launch XR apps" + } else { + match model.selected_profile.xrservice_type { + XRServiceType::Monado => + "Starting…", + XRServiceType::Wivrn => + "Starting, please connect your client device…", + } + }, + set_margin_all: 18, + add_css_class: "heading", + add_css_class: "success", + add_css_class: "warning", + #[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready())"] + set_class_active: ("warning", !model.xrservice_ready), + set_wrap: true, + set_justify: gtk::Justification::Center, + }, + }, model.devices_box.widget(), gtk::Box { set_orientation: gtk::Orientation::Vertical, @@ -424,6 +452,9 @@ impl SimpleComponent for MainView { .expect("Sender output failed"); } Self::Input::XRServiceActiveChanged(active, profile, show_launch_opts) => { + if !active { + self.set_xrservice_ready(false); + } self.set_xrservice_active(active); self.steamvr_calibration_box .sender() @@ -536,10 +567,14 @@ impl SimpleComponent for MainView { ); } } - Self::Input::UpdateDevices(devs) => self - .devices_box - .sender() - .emit(DevicesBoxMsg::UpdateDevices(devs)), + Self::Input::UpdateDevices(devs) => { + self.devices_box + .sender() + .emit(DevicesBoxMsg::UpdateDevices(devs)); + } + Self::Input::UpdateXrServiceReady(ready) => { + self.set_xrservice_ready(ready); + } } } @@ -621,6 +656,7 @@ impl SimpleComponent for MainView { root_win: init.root_win.clone(), steamvr_calibration_box, profile_editor: None, + xrservice_ready: false, tracker: 0, }; let widgets = view_output!();