diff --git a/Cargo.toml b/Cargo.toml index 9f888e8..a192119 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ relm4 = { version = "0.6.0", features = [ ] } relm4-components = "0.6.0" relm4-icons = { version = "0.6.0", features = [ - "menu", "loupe" + "menu", "loupe", "copy" ] } serde = { version = "1.0.163", features = [ "derive" diff --git a/src/config.rs b/src/config.rs index bedb552..1a022d0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ fn config_file_path() -> String { fn default_config() -> Config { Config { // TODO: handle first start with no profile selected - selected_profile_name: "Demo profile".to_string(), + selected_profile_name: "".to_string(), debug_view_enabled: false, } } diff --git a/src/profile.rs b/src/profile.rs index 1e1a4a1..c554907 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -8,7 +8,7 @@ use crate::file_utils::get_writer; pub struct Profile { pub name: String, pub monado_path: String, - pub openovr_path: String, + pub opencomposite_path: String, pub libsurvive_path: Option, pub basalt_path: Option, pub mercury_path: Option, @@ -26,6 +26,16 @@ impl Display for Profile { } } +impl Profile { + pub fn get_steam_launch_options(&self) -> String { + let mut opts = vec![]; + opts.push(format!("XR_RUNTIME_JSON={prefix}/share/openxr/1/openxr_monado.json", prefix = self.prefix)); + opts.push(format!("PRESSURE_VESSEL_FILESYSTEMS_RW=$XDG_RUNTIME_DIR/monado_comp_ipc")); + opts.push("%command%".into()); + opts.join(" ") + } +} + pub fn load_profile(path: &String) -> Profile { let file = File::open(path).expect_dialog("Unable to open profile"); let reader = BufReader::new(file); @@ -48,7 +58,7 @@ mod tests { let profile = load_profile(&"./test/files/profile.json".to_string()); assert_eq!(profile.name, "Demo profile"); assert_eq!(profile.monado_path, "/home/user/monado"); - assert_eq!(profile.openovr_path, "/home/user/openovr"); + assert_eq!(profile.opencomposite_path, "/home/user/opencomposite"); assert_eq!(profile.prefix, "/home/user/rex2prefix"); assert_eq!( profile.libsurvive_path.as_deref(), @@ -76,7 +86,7 @@ mod tests { let p = Profile { name: "Demo profile".into(), monado_path: String::from("/home/user/monado"), - openovr_path: String::from("/home/user/openovr"), + opencomposite_path: String::from("/home/user/opencomposite"), libsurvive_path: Some(String::from("/home/user/libsurvive")), basalt_path: None, mercury_path: None, diff --git a/src/profiles/valve_index.rs b/src/profiles/valve_index.rs index fb6b7f1..3d0ae97 100644 --- a/src/profiles/valve_index.rs +++ b/src/profiles/valve_index.rs @@ -13,7 +13,7 @@ pub fn valve_index_profile() -> Profile { Profile { name: format!("Valve Index - {name} Default", name = APP_NAME), monado_path: format!("{data}/monado", data = data_dir), - openovr_path: format!("{data}/openovr", data = data_dir), + opencomposite_path: format!("{data}/opencomposite", data = data_dir), libsurvive_path: Some(format!("{data}/libsurvive", data = data_dir)), basalt_path: None, mercury_path: None, diff --git a/src/ui/app.rs b/src/ui/app.rs index 6c26653..5b87b70 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,11 +1,14 @@ -use std::time::Duration; - +use super::about_dialog::AboutDialog; +use super::build_window::BuildWindow; +use super::debug_view::{DebugView, DebugViewMsg}; +use super::main_view::MainViewMsg; use crate::builders::build_libsurvive::get_build_libsurvive_runner; use crate::builders::build_monado::get_build_monado_runner; +use crate::builders::build_opencomposite::get_build_opencomposite_runner; use crate::config::{get_config, save_config, Config}; use crate::constants::APP_NAME; use crate::dependencies::libsurvive_deps::get_missing_libsurvive_deps; -use crate::dependencies::monado_deps::{check_monado_deps, get_missing_monado_deps}; +use crate::dependencies::monado_deps::get_missing_monado_deps; use crate::profile::Profile; use crate::profiles::valve_index::valve_index_profile; use crate::runner::{Runner, RunnerStatus}; @@ -20,12 +23,7 @@ use relm4::adw::traits::MessageDialogExt; use relm4::gtk::glib; use relm4::{new_action_group, new_stateful_action, new_stateless_action, prelude::*}; use relm4::{ComponentParts, ComponentSender, SimpleComponent}; -use relm4_components::alert::{Alert, AlertSettings}; - -use super::about_dialog::AboutDialog; -use super::build_window::BuildWindow; -use super::debug_view::{DebugView, DebugViewMsg}; -use super::main_view::MainViewMsg; +use std::time::Duration; #[tracker::track] pub struct App { @@ -74,8 +72,10 @@ impl App { } pub fn get_selected_profile(&self) -> &Profile { - self.get_profile_by_name(&self.config.selected_profile_name) - .expect_dialog("Could not find selected profile") + match self.get_profile_by_name(&self.config.selected_profile_name) { + Some(profile) => profile, + None => self.profiles.get(0).expect_dialog("No profiles found"), + } } pub fn start_monado(&mut self) { @@ -152,7 +152,7 @@ impl SimpleComponent for App { self.build_window .sender() .emit(BuildWindowMsg::UpdateContent(pipeline.get_log())); - }, + } false => { self.build_window .sender() @@ -181,6 +181,11 @@ impl SimpleComponent for App { self.main_view .sender() .emit(MainViewMsg::MonadoActiveChanged(true)); + self.main_view + .sender() + .emit(MainViewMsg::SteamLaunchOptionsChanged( + self.get_selected_profile().get_steam_launch_options(), + )); } Some(runner) => match runner.status() { RunnerStatus::Running => { @@ -217,6 +222,8 @@ impl SimpleComponent for App { // runners.push(get_build_mercury_runner(profile.clone())); // } runners.push(get_build_monado_runner(profile.clone())); + // no listed deps for opencomp + runners.push(get_build_opencomposite_runner(profile.clone())); if !missing_deps.is_empty() { self.dependencies_dialog.set_body( missing_deps diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index 5816404..28e8d1a 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -1,16 +1,17 @@ -use gtk::prelude::*; -use relm4::prelude::*; -use relm4::{SimpleComponent, ComponentSender, ComponentParts}; -use relm4_icons::icon_name; use crate::config::Config; use crate::constants::APP_NAME; -use crate::ui::app::{AboutAction, DebugViewToggleAction, BuildProfileAction}; +use crate::ui::app::{AboutAction, BuildProfileAction, DebugViewToggleAction}; +use gtk::prelude::*; +use relm4::prelude::*; +use relm4::{ComponentParts, ComponentSender, SimpleComponent}; +use relm4_icons::icon_name; #[tracker::track] pub struct MainView { monado_active: bool, enable_debug_view: bool, profile_names: Vec, + steam_launch_options: String, } #[derive(Debug)] @@ -19,6 +20,7 @@ pub enum MainViewMsg { MonadoActiveChanged(bool), EnableDebugViewChanged(bool), UpdateProfileNames(Vec), + SteamLaunchOptionsChanged(String), } #[derive(Debug)] @@ -84,26 +86,98 @@ impl SimpleComponent for MainView { set_show_end_title_buttons: !model.enable_debug_view, }, }, - gtk::Box { - set_spacing: 12, - set_margin_all: 12, - gtk::Button { - add_css_class: "pill", - add_css_class: "suggested-action", - add_css_class: "destructive-action", - set_hexpand: true, - set_halign: gtk::Align::Center, - #[track = "model.changed(MainView::monado_active())"] - set_class_active: ("suggested-action", !model.monado_active), - #[track = "model.changed(MainView::monado_active())"] - set_label: match model.monado_active { - true => "Stop", - false => "Start", + gtk::ScrolledWindow { + set_hscrollbar_policy: gtk::PolicyType::Never, + set_hexpand: true, + set_vexpand: true, + gtk::Box { + set_spacing: 12, + set_margin_top: 12, + set_margin_bottom: 12, + set_orientation: gtk::Orientation::Vertical, + gtk::Button { + add_css_class: "pill", + add_css_class: "suggested-action", + add_css_class: "destructive-action", + set_hexpand: true, + set_halign: gtk::Align::Center, + #[track = "model.changed(MainView::monado_active())"] + set_class_active: ("suggested-action", !model.monado_active), + #[track = "model.changed(MainView::monado_active())"] + set_label: match model.monado_active { + true => "Stop", + false => "Start", + }, + connect_clicked[sender] => move |_| { + sender.input(MainViewMsg::StartStopClicked) + }, }, - connect_clicked[sender] => move |_| { - sender.input(MainViewMsg::StartStopClicked) + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_hexpand: true, + set_vexpand: false, + set_spacing: 12, + set_margin_top: 12, + set_margin_bottom: 12, + #[track = "model.changed(MainView::monado_active())"] + set_visible: model.monado_active, + gtk::Separator { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + }, + gtk::Label { + add_css_class: "heading", + set_hexpand: true, + set_xalign: 0.0, + set_margin_start: 12, + set_margin_end: 12, + set_label: "Steam Launch Options", + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + }, + gtk::Label { + add_css_class: "dim-label", + set_hexpand: true, + set_xalign: 0.0, + set_margin_start: 12, + set_margin_end: 12, + set_label: "Set this string in the launch options of Steam games, so that they can pick up Monado and OpenOVR correctly", + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + }, + gtk::Box { + set_margin_start: 12, + set_margin_end: 12, + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 6, + gtk::TextView { + add_css_class: "card", + set_hexpand: true, + set_vexpand: false, + set_monospace: true, + set_editable: false, + set_wrap_mode: gtk::WrapMode::Word, + set_left_margin: 6, + set_right_margin: 6, + set_top_margin: 6, + set_bottom_margin: 6, + set_size_request: (-1, 150), + #[wrap(Some)] + set_buffer: cmdbuf = >k::TextBuffer { + #[track = "model.changed(MainView::steam_launch_options())"] + set_text: model.steam_launch_options.as_str(), + } + }, + gtk::Button { + add_css_class: "flat", + add_css_class: "circular", + set_icon_name: icon_name::COPY, + set_vexpand: false, + set_valign: gtk::Align::Center, + }, + }, } - }, + } } } } @@ -114,7 +188,7 @@ impl SimpleComponent for MainView { match message { MainViewMsg::StartStopClicked => { sender.output(MainViewOutMsg::DoStartStopMonado); - }, + } MainViewMsg::MonadoActiveChanged(active) => { self.set_monado_active(active); } @@ -124,14 +198,20 @@ impl SimpleComponent for MainView { MainViewMsg::UpdateProfileNames(names) => { self.set_profile_names(names); } + MainViewMsg::SteamLaunchOptionsChanged(lo) => self.set_steam_launch_options(lo), } } - fn init(init: Self::Init, root: &Self::Root, sender: ComponentSender) -> ComponentParts { + fn init( + init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { let model = MainView { monado_active: false, enable_debug_view: init.config.debug_view_enabled, profile_names: vec![], + steam_launch_options: "".into(), tracker: 0, }; let widgets = view_output!(); diff --git a/test/files/profile.json b/test/files/profile.json index fe19b82..a4caf9f 100644 --- a/test/files/profile.json +++ b/test/files/profile.json @@ -1,7 +1,7 @@ { "name": "Demo profile", "monado_path": "/home/user/monado", - "openovr_path": "/home/user/openovr", + "opencomposite_path": "/home/user/opencomposite", "libsurvive_path": "/home/user/libsurvive", "basalt_path": null, "mercury_path": null,