diff --git a/src/cmd_runner.rs b/src/cmd_runner.rs index c6a16da..598433f 100644 --- a/src/cmd_runner.rs +++ b/src/cmd_runner.rs @@ -7,7 +7,7 @@ use nix::{ }; use crate::{ - file_utils::get_writer, + file_utils::get_writer_legacy, profile::{Profile, XRServiceType}, runner::{Runner, RunnerStatus}, }; @@ -172,7 +172,7 @@ impl CmdRunner { } fn save_log(path_s: String, log: &[String]) -> Result<(), std::io::Error> { - let mut writer = get_writer(&path_s); + let mut writer = get_writer_legacy(&path_s); let log_s = log.concat(); writer.write_all(log_s.as_ref()) } diff --git a/src/config.rs b/src/config.rs index 6542839..aa0fdc6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use crate::{ constants::CMD_NAME, device_prober::get_xr_usb_devices, - file_utils::get_writer, + file_utils::get_writer_legacy, paths::get_config_dir, profile::Profile, profiles::{ @@ -78,7 +78,7 @@ impl Config { } fn save_to_path(&self, path_s: &String) -> Result<(), serde_json::Error> { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, self) } diff --git a/src/downloader.rs b/src/downloader.rs index 5e1d3f8..a5e201d 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -1,4 +1,4 @@ -use crate::{constants::APP_ID, file_utils::get_writer}; +use crate::{constants::APP_ID, file_utils::get_writer_legacy}; use reqwest::{ header::{HeaderMap, USER_AGENT}, Method, @@ -32,7 +32,7 @@ pub fn download_file(url: String, path: String) -> JoinHandle Result<(), serde_json::Error> { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, active_runtime) } diff --git a/src/file_builders/monado_autorun.rs b/src/file_builders/monado_autorun.rs index e60ae80..b2a49bb 100644 --- a/src/file_builders/monado_autorun.rs +++ b/src/file_builders/monado_autorun.rs @@ -1,5 +1,5 @@ use crate::{ - file_utils::{deserialize_file, get_writer}, + file_utils::{deserialize_file, get_writer_legacy}, paths::get_xdg_config_dir, }; use serde::{Deserialize, Serialize}; @@ -45,7 +45,7 @@ pub fn get_monado_autorun_config() -> MonadoAutorunConfig { } fn dump_monado_autorun_config_to_path(config: &MonadoAutorunConfig, path_s: &String) { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, config).expect("Unable to save Monado Autorun config"); } diff --git a/src/file_builders/monado_config_v0.rs b/src/file_builders/monado_config_v0.rs index 5418f21..cec5fe8 100644 --- a/src/file_builders/monado_config_v0.rs +++ b/src/file_builders/monado_config_v0.rs @@ -1,5 +1,5 @@ use crate::{ - file_utils::{deserialize_file, get_writer}, + file_utils::{deserialize_file, get_writer_legacy}, paths::get_xdg_config_dir, }; use serde::{Deserialize, Serialize}; @@ -202,7 +202,7 @@ pub fn get_monado_config_v0() -> MonadoConfigV0 { } fn dump_monado_config_v0_to_path(config: &MonadoConfigV0, path_s: &String) { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, config).expect("Unable to save Monado config V0"); } diff --git a/src/file_builders/openvrpaths_vrpath.rs b/src/file_builders/openvrpaths_vrpath.rs index bc59a33..7f83ca4 100644 --- a/src/file_builders/openvrpaths_vrpath.rs +++ b/src/file_builders/openvrpaths_vrpath.rs @@ -1,5 +1,5 @@ use crate::{ - file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, + file_utils::{copy_file, deserialize_file, get_writer_legacy, set_file_readonly}, paths::{get_backup_dir, get_xdg_config_dir, get_xdg_data_dir}, profile::Profile, }; @@ -67,7 +67,7 @@ fn dump_openvrpaths_to_path( ovr_paths: &OpenVrPaths, path_s: &String, ) -> Result<(), serde_json::Error> { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, ovr_paths) } diff --git a/src/file_builders/wivrn_config.rs b/src/file_builders/wivrn_config.rs index 4150043..c516ba1 100644 --- a/src/file_builders/wivrn_config.rs +++ b/src/file_builders/wivrn_config.rs @@ -1,5 +1,5 @@ use crate::{ - file_utils::{deserialize_file, get_writer}, + file_utils::{deserialize_file, get_writer_legacy}, paths::get_xdg_config_dir, }; use serde::{Deserialize, Serialize}; @@ -136,7 +136,7 @@ pub fn get_wivrn_config() -> WivrnConfig { } fn dump_wivrn_config_to_path(config: &WivrnConfig, path_s: &String) { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, config).expect("Unable to save WiVRn config"); } diff --git a/src/file_utils.rs b/src/file_utils.rs index f79cca5..4a6dcc7 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -9,7 +9,23 @@ use std::{ path::Path, }; -pub fn get_writer(path_s: &String) -> BufWriter { +pub fn get_writer(path_s: &str) -> anyhow::Result> { + let path = Path::new(path_s); + if let Some(parent) = path.parent() { + if !parent.is_dir() { + create_dir_all(parent)?; + } + }; + let file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path)?; + Ok(BufWriter::new(file)) +} + +#[deprecated] +pub fn get_writer_legacy(path_s: &str) -> BufWriter { let path = Path::new(path_s); if let Some(parent) = path.parent() { if !parent.is_dir() { @@ -52,7 +68,7 @@ pub fn deserialize_file(path_s: &String) -> Opti } } -pub fn set_file_readonly(path_s: &String, readonly: bool) -> Result<(), std::io::Error> { +pub fn set_file_readonly(path_s: &str, readonly: bool) -> Result<(), std::io::Error> { let path = Path::new(&path_s); if !path.is_file() { println!("WARN: trying to set readonly on a file that does not exist"); @@ -81,7 +97,7 @@ pub fn rm_rf(path_s: &String) { } } -pub fn copy_file(source_s: &String, dest_s: &String) { +pub fn copy_file(source_s: &str, dest_s: &str) { let source = Path::new(source_s); let dest = Path::new(dest_s); if let Some(parent) = dest.parent() { diff --git a/src/main.rs b/src/main.rs index 547a747..a4498a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use relm4::{ gtk::{self, gdk, gio, glib, prelude::*}, MessageBroker, RelmApp, }; +use steam_linux_runtime_injector::restore_runtime_entrypoint; use ui::app::{App, AppInit}; pub mod adb; @@ -37,6 +38,7 @@ pub mod profile; pub mod profiles; pub mod runner; pub mod runner_pipeline; +pub mod steam_linux_runtime_injector; pub mod steamvr_utils; pub mod ui; pub mod xr_devices; @@ -60,6 +62,7 @@ fn restore_steam_xr_files() { }; } } + restore_runtime_entrypoint(); } fn main() -> Result<()> { diff --git a/src/profile.rs b/src/profile.rs index 3e24ffc..547986c 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -1,5 +1,5 @@ use crate::{ - file_utils::get_writer, + file_utils::get_writer_legacy, paths::{get_data_dir, get_ipc_file_path, BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX}, }; use serde::{Deserialize, Serialize}; @@ -288,14 +288,19 @@ impl Default for Profile { } impl Profile { - pub fn get_steam_launch_options(&self) -> String { + pub fn get_env_vars(&self) -> Vec { + if self.can_be_built { + return vec![ + "PRESSURE_VESSEL_FILESYSTEMS_RW=\"$XDG_RUNTIME_DIR/wivrn_comp_ipc:$XDG_RUNTIME_DIR/monado_comp_ipc\"".into(), + ]; + } vec![ // format!( // "VR_OVERRIDE={opencomp}/build", // opencomp = self.opencomposite_path, // ), format!( - "XR_RUNTIME_JSON={prefix}/share/openxr/1/openxr_{runtime}.json", + "XR_RUNTIME_JSON=\"{prefix}/share/openxr/1/openxr_{runtime}.json\"", prefix = match self.prefix.as_str() { SYSTEM_PREFIX => BWRAP_SYSTEM_PREFIX, other => other, @@ -306,12 +311,16 @@ impl Profile { } ), format!( - "PRESSURE_VESSEL_FILESYSTEMS_RW={path}", + "PRESSURE_VESSEL_FILESYSTEMS_RW=\"{path}\"", path = get_ipc_file_path(&self.xrservice_type), ), - "%command%".into(), ] - .join(" ") + } + + pub fn get_steam_launch_options(&self) -> String { + let mut vars = self.get_env_vars(); + vars.push("%command%".into()); + vars.join(" ") } pub fn get_survive_cli_path(&self) -> Option { @@ -329,7 +338,7 @@ impl Profile { } pub fn dump_profile(&self, path_s: &String) { - let writer = get_writer(path_s); + let writer = get_writer_legacy(path_s); serde_json::to_writer_pretty(writer, self).expect("Could not write profile") } diff --git a/src/steam_linux_runtime_injector.rs b/src/steam_linux_runtime_injector.rs new file mode 100644 index 0000000..083eede --- /dev/null +++ b/src/steam_linux_runtime_injector.rs @@ -0,0 +1,80 @@ +use crate::{ + file_utils::{copy_file, get_writer}, + paths::{get_backup_dir, get_home_dir, get_xdg_data_dir}, + profile::Profile, +}; +use anyhow::bail; +use std::{ + fs::read_to_string, + io::{Read, Write}, + path::Path, +}; + +fn get_runtime_entrypoint_path() -> Option { + let mut out = format!( + "{home}/.steam/steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point", + home = get_home_dir(), + ); + + if Path::new(&out).is_file() { + return Some(out); + } + + out = format!( + "{data}/Steam/ubuntu12_64/steam-runtime-sniper/_v2-entry-point", + data = get_xdg_data_dir() + ); + + if Path::new(&out).is_file() { + return Some(out); + } + + None +} + +fn get_backup_runtime_entrypoint_location() -> String { + format!("{backup}/_v2-entry-point.bak", backup = get_backup_dir()) +} + +fn backup_runtime_entrypoint(path: &str) { + copy_file(&path, &get_backup_runtime_entrypoint_location()); +} + +pub fn restore_runtime_entrypoint() { + if let Some(path) = get_runtime_entrypoint_path() { + let backup = get_backup_runtime_entrypoint_location(); + if Path::new(&backup).is_file() { + copy_file(&backup, &path); + } + } +} + +fn append_to_runtime_entrypoint(data: &str, path: &str) -> anyhow::Result<()> { + let existing = read_to_string(path)?; + let new = existing.replace( + "exec \"${here}/${run}\" \"$@\"\nexit 125", + &format!("\n\n# envision\n{data}\n\nexec \"${{here}}/${{run}}\" \"$@\"\nexit 125"), + ); + let mut writer = get_writer(path)?; + writer.write(&new.as_bytes())?; + Ok(()) +} + +pub fn set_runtime_entrypoint_launch_opts_from_profile(profile: &Profile) -> anyhow::Result<()> { + restore_runtime_entrypoint(); + if let Some(dest) = get_runtime_entrypoint_path() { + backup_runtime_entrypoint(&dest); + append_to_runtime_entrypoint( + &profile + .get_env_vars() + .iter() + .map(|ev| "export ".to_string() + ev) + .collect::>() + .join("\n"), + &dest, + )?; + + return Ok(()); + } + bail!("Could not find valid runtime entrypoint"); +} diff --git a/src/ui/app.rs b/src/ui/app.rs index 6ec89e9..7e9c4cb 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -39,6 +39,9 @@ use crate::log_parser::MonadoLog; use crate::paths::{get_data_dir, get_ipc_file_path}; use crate::profile::{Profile, XRServiceType}; use crate::stateless_action; +use crate::steam_linux_runtime_injector::{ + restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile, +}; use crate::ui::build_window::{BuildWindowMsg, BuildWindowOutMsg}; use crate::ui::debug_view::{DebugViewInit, DebugViewOutMsg}; use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg; @@ -199,7 +202,7 @@ impl App { debug, ); worker.start(); - if let Some(autostart_cmd) = prof.autostart_command { + if let Some(autostart_cmd) = &prof.autostart_command { let autostart_worker = JobWorker::new_with_timer( Duration::from_secs(5), WorkerJob::new_cmd( @@ -222,6 +225,8 @@ impl App { .emit(MainViewMsg::XRServiceActiveChanged( true, Some(self.get_selected_profile()), + // show launch opts only if setting the runtime entrypoint fails + set_runtime_entrypoint_launch_opts_from_profile(&prof).is_err(), )); self.debug_view .sender() @@ -265,10 +270,9 @@ impl App { worker.stop(); } self.libmonado = None; - self.restore_openxr_openvr_files(); self.main_view .sender() - .emit(MainViewMsg::XRServiceActiveChanged(false, None)); + .emit(MainViewMsg::XRServiceActiveChanged(false, None, false)); self.debug_view .sender() .emit(DebugViewMsg::XRServiceActiveChanged(false)); @@ -334,9 +338,11 @@ impl SimpleComponent for App { } } Msg::OnServiceExit(code) => { + self.restore_openxr_openvr_files(); + restore_runtime_entrypoint(); self.main_view .sender() - .emit(MainViewMsg::XRServiceActiveChanged(false, None)); + .emit(MainViewMsg::XRServiceActiveChanged(false, None, false)); self.debug_view .sender() .emit(DebugViewMsg::XRServiceActiveChanged(false)); diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index eda27e0..d43d252 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -55,7 +55,7 @@ pub enum MainViewMsg { ClockTicking, StartStopClicked, RestartXRService, - XRServiceActiveChanged(bool, Option), + XRServiceActiveChanged(bool, Option, bool), EnableDebugViewChanged(bool), UpdateProfiles(Vec, Config), SetSelectedProfile(u32), @@ -450,7 +450,7 @@ impl SimpleComponent for MainView { .output(Self::Output::RestartXRService) .expect("Sender output failed"); } - Self::Input::XRServiceActiveChanged(active, profile) => { + Self::Input::XRServiceActiveChanged(active, profile, show_launch_opts) => { self.set_xrservice_active(active); self.steamvr_calibration_box .sender() @@ -458,9 +458,9 @@ impl SimpleComponent for MainView { if !active { sender.input(Self::Input::UpdateDevices(vec![])); } - self.steam_launch_options_box - .sender() - .emit(SteamLaunchOptionsBoxMsg::UpdateXRServiceActive(active)); + self.steam_launch_options_box.sender().emit( + SteamLaunchOptionsBoxMsg::UpdateXRServiceActive(show_launch_opts), + ); match profile { None => {} Some(prof) => {