diff --git a/src/file_builders/wivrn_config.rs b/src/file_builders/wivrn_config.rs index a1515f6..e7fcc38 100644 --- a/src/file_builders/wivrn_config.rs +++ b/src/file_builders/wivrn_config.rs @@ -1,13 +1,46 @@ -use expect_dialog::ExpectDialog; -use serde::{Serialize, Deserialize}; +use std::{fmt::Display, slice::Iter}; -use crate::file_utils::{get_xdg_config_dir, deserialize_file, get_writer}; +use expect_dialog::ExpectDialog; +use serde::{Deserialize, Serialize}; + +use crate::file_utils::{deserialize_file, get_writer, get_xdg_config_dir}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Encoder { X264, - X265, + Nvenc, + Vaapi, +} + +impl Display for Encoder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::X264 => "x264", + Self::Nvenc => "NVEnc", + Self::Vaapi => "VAAPI", + }) + } +} + +impl Encoder { + pub fn iter() -> Iter<'static, Encoder> { + [Self::X264, Self::Nvenc, Self::Vaapi].iter() + } + + pub fn as_vec() -> Vec { + vec![ + Self::X264, Self::Nvenc, Self::Vaapi + ] + } + + pub fn as_number(&self) -> u32 { + match self { + Self::X264 => 0, + Self::Nvenc => 1, + Self::Vaapi => 2, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -15,81 +48,84 @@ pub enum Encoder { pub enum Codec { H264, H265, + Avc, + Hevc, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct WivrnConfEncoder { pub encoder: Encoder, pub codec: Codec, - pub bitrate: u32, - pub width: f32, - pub height: f32, + #[serde(skip_serializing_if = "Option::is_none")] + pub bitrate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub width: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub height: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct WivrnConfig { - pub scale: [f32; 2], + #[serde(skip_serializing_if = "Option::is_none")] + pub scale: Option<[f32; 2]>, pub encoders: Vec, } impl Default for WivrnConfig { fn default() -> Self { Self { - scale: [0.8, 0.8], - encoders: vec![ - WivrnConfEncoder { - encoder: Encoder::X264, - codec: Codec::H264, - bitrate: 100000000, - width: 1.0, - height: 1.0, - } - ], + scale: Some([0.8, 0.8]), + encoders: vec![WivrnConfEncoder { + encoder: Encoder::X264, + codec: Codec::H264, + bitrate: Some(100000000), + width: Some(1.0), + height: Some(1.0), + group: None, + }], } } } fn get_wivrn_config_path() -> String { - format!( - "{config}/wivrn/config.json", - config = get_xdg_config_dir() - ) + format!("{config}/wivrn/config.json", config = get_xdg_config_dir()) } fn get_wivrn_config_from_path(path_s: &String) -> Option { deserialize_file(path_s) } -pub fn get_wivrn_config() -> Option { - get_wivrn_config_from_path(&get_wivrn_config_path()) +pub fn get_wivrn_config() -> WivrnConfig { + get_wivrn_config_from_path(&get_wivrn_config_path()).unwrap_or(WivrnConfig::default()) } fn dump_wivrn_config_to_path(config: &WivrnConfig, path_s: &String) { let writer = get_writer(path_s); - serde_json::to_writer_pretty(writer, config) - .expect_dialog("Unable to save WiVRn config"); + serde_json::to_writer_pretty(writer, config).expect_dialog("Unable to save WiVRn config"); } -pub fn dump_wivrn_config(config: &WivrnConfig, path_s: &String) { - dump_wivrn_config_to_path(config, path_s); +pub fn dump_wivrn_config(config: &WivrnConfig) { + dump_wivrn_config_to_path(config, &get_wivrn_config_path()); } #[cfg(test)] mod tests { - use crate::file_builders::wivrn_config::{Encoder, Codec}; + use crate::file_builders::wivrn_config::{Codec, Encoder}; use super::get_wivrn_config_from_path; - #[test] fn can_read_wivrn_config() { - let conf = get_wivrn_config_from_path(&"./test/files/wivrn_config.json".into()).expect("Couldn't find wivrn config"); - assert_eq!(conf.scale, [0.8, 0.8]); + let conf = get_wivrn_config_from_path(&"./test/files/wivrn_config.json".into()) + .expect("Couldn't find wivrn config"); + assert_eq!(conf.scale, Some([0.8, 0.8])); assert_eq!(conf.encoders.len(), 1); assert_eq!(conf.encoders.get(0).unwrap().encoder, Encoder::X264); assert_eq!(conf.encoders.get(0).unwrap().codec, Codec::H264); - assert_eq!(conf.encoders.get(0).unwrap().bitrate, 100000000); - assert_eq!(conf.encoders.get(0).unwrap().width, 1.0); - assert_eq!(conf.encoders.get(0).unwrap().height, 1.0); + assert_eq!(conf.encoders.get(0).unwrap().bitrate, Some(100000000)); + assert_eq!(conf.encoders.get(0).unwrap().width, Some(1.0)); + assert_eq!(conf.encoders.get(0).unwrap().height, Some(1.0)); } } diff --git a/src/ui/app.rs b/src/ui/app.rs index bed15bf..64737bb 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -347,20 +347,20 @@ impl SimpleComponent for App { .collect(); self.config.save(); self.profiles = Self::profiles_list(&self.config); - self.main_view.sender().emit(MainViewMsg::UpdateSelectedProfile( - self.get_selected_profile() - )); self.main_view .sender() - .emit(MainViewMsg::UpdateProfiles( - self.profiles.clone(), - self.config.clone(), - )) + .emit(MainViewMsg::UpdateSelectedProfile( + self.get_selected_profile(), + )); + self.main_view.sender().emit(MainViewMsg::UpdateProfiles( + self.profiles.clone(), + self.config.clone(), + )) } } Msg::SaveProfile(prof) => { match self.profiles.iter().position(|p| p.uuid == prof.uuid) { - None => {}, + None => {} Some(index) => { self.profiles.remove(index); } @@ -369,12 +369,10 @@ impl SimpleComponent for App { self.profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name)); self.config.set_profiles(&self.profiles); self.config.save(); - self.main_view - .sender() - .emit(MainViewMsg::UpdateProfiles( - self.profiles.clone(), - self.config.clone(), - )) + self.main_view.sender().emit(MainViewMsg::UpdateProfiles( + self.profiles.clone(), + self.config.clone(), + )) } Msg::RunSetCap => { if !check_dependency(pkexec_dep()) { @@ -546,17 +544,15 @@ impl SimpleComponent for App { actions.add_action(libsurvive_setup_action); root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group())); + model .application .set_accelerators_for_action::(&["q"]); - model - .main_view - .sender() - .emit(MainViewMsg::UpdateProfiles( - model.profiles.clone(), - model.config.clone(), - )); + model.main_view.sender().emit(MainViewMsg::UpdateProfiles( + model.profiles.clone(), + model.config.clone(), + )); let timer_sender = sender.clone(); glib::timeout_add_local(Duration::from_millis(1000), move || { diff --git a/src/ui/factories/entry_row_factory.rs b/src/ui/factories/entry_row_factory.rs index 278120d..f153346 100644 --- a/src/ui/factories/entry_row_factory.rs +++ b/src/ui/factories/entry_row_factory.rs @@ -38,7 +38,7 @@ impl FactoryComponent for EntryModel { view! { root = adw::EntryRow { - set_title: &self.name, + set_title: &self.name, set_text: &self.value, add_suffix: clear_btn = >k::Button { set_icon_name: "edit-clear-symbolic", diff --git a/src/ui/install_wivrn_box.rs b/src/ui/install_wivrn_box.rs index cfcffa2..0b96f2d 100644 --- a/src/ui/install_wivrn_box.rs +++ b/src/ui/install_wivrn_box.rs @@ -8,7 +8,12 @@ use crate::{ runner::{Runner, RunnerStatus}, }; use gtk::prelude::*; -use relm4::{prelude::*, adw::traits::MessageDialogExt}; +use relm4::{ + actions::{ActionGroupName, RelmAction, RelmActionGroup}, + adw::traits::MessageDialogExt, + new_action_group, new_stateless_action, + prelude::*, +}; use std::thread::JoinHandle; #[derive(PartialEq, Eq, Debug, Clone)] @@ -34,7 +39,7 @@ pub struct InstallWivrnBox { pub enum InstallWivrnBoxMsg { ClockTicking, UpdateSelectedProfile(Profile), - DownloadWivrn, + DownloadWivrn(String), InstallWivrnApk, } @@ -50,6 +55,15 @@ impl SimpleComponent for InstallWivrnBox { type Input = InstallWivrnBoxMsg; type Output = (); + menu! { + install_wivrn_menu: { + section! { + "For _Oculus" => WivrnApkOculusAction, + "For _Pico" => WivrnApkPicoAction, + } + } + } + view! { gtk::Box { set_orientation: gtk::Orientation::Vertical, @@ -89,7 +103,7 @@ impl SimpleComponent for InstallWivrnBox { set_wrap: true, set_wrap_mode: gtk::pango::WrapMode::Word, }, - gtk::Button { + gtk::MenuButton { add_css_class: "suggested-action", set_label: "Install WiVRn", set_margin_start: 12, @@ -100,9 +114,7 @@ impl SimpleComponent for InstallWivrnBox { InstallWivrnStatus::InProgress => false, _ => true, }, - connect_clicked[sender] => move |_| { - sender.input(Self::Input::DownloadWivrn); - }, + set_menu_model: Some(&install_wivrn_menu), }, gtk::Label { add_css_class: "error", @@ -174,15 +186,12 @@ impl SimpleComponent for InstallWivrnBox { }; } } - Self::Input::DownloadWivrn => { + Self::Input::DownloadWivrn(link) => { if !check_dependency(adb_dep()) { self.adb_missing_dialog.present(); } else { self.set_install_wivrn_status(InstallWivrnStatus::InProgress); - self.download_thread = Some(download_file( - "https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-oculus-release.apk".into(), - wivrn_apk_download_path() - )); + self.download_thread = Some(download_file(link, wivrn_apk_download_path())); } } Self::Input::InstallWivrnApk => { @@ -221,6 +230,34 @@ impl SimpleComponent for InstallWivrnBox { let widgets = view_output!(); + let mut actions = RelmActionGroup::::new(); + + let apk_oculus_action = { + let oculus_sender = sender.clone(); + RelmAction::::new_stateless(move |_| { + oculus_sender.input(Self::Input::DownloadWivrn("https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-oculus-release.apk".into())); + }) + }; + + let apk_pico_action = { + let pico_sender = sender.clone(); + RelmAction::::new_stateless(move |_| { + pico_sender.input(Self::Input::DownloadWivrn("https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-pico-release.apk".into())); + }) + }; + + actions.add_action(apk_oculus_action); + actions.add_action(apk_pico_action); + + root.insert_action_group( + InstallWivrnActionGroup::NAME, + Some(&actions.into_action_group()), + ); + ComponentParts { model, widgets } } } + +new_action_group!(pub InstallWivrnActionGroup, "installwivrn"); +new_stateless_action!(pub WivrnApkOculusAction, InstallWivrnActionGroup, "apkoculus"); +new_stateless_action!(pub WivrnApkPicoAction, InstallWivrnActionGroup, "apkpico"); diff --git a/src/ui/profile_editor.rs b/src/ui/profile_editor.rs index c205cd5..521bee7 100644 --- a/src/ui/profile_editor.rs +++ b/src/ui/profile_editor.rs @@ -401,10 +401,10 @@ impl SimpleComponent for ProfileEditor { ) .model(>k::StringList::new( XRServiceType::iter() - .map(|st| st.to_string()) + .map(XRServiceType::to_string) .collect::>() .iter() - .map(|s| s.as_str()) + .map(String::as_str) .collect::>() .as_slice(), ))