feat: can install wivrn apk for either oculus or pico

This commit is contained in:
Gabriele Musco 2023-06-29 18:18:58 +02:00
commit a6df8bacb9
No known key found for this signature in database
GPG key ID: 1068D795C80E51DE
5 changed files with 139 additions and 70 deletions

View file

@ -1,13 +1,46 @@
use expect_dialog::ExpectDialog; use std::{fmt::Display, slice::Iter};
use serde::{Serialize, Deserialize};
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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Encoder { pub enum Encoder {
X264, 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<Encoder> {
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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -15,81 +48,84 @@ pub enum Encoder {
pub enum Codec { pub enum Codec {
H264, H264,
H265, H265,
Avc,
Hevc,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WivrnConfEncoder { pub struct WivrnConfEncoder {
pub encoder: Encoder, pub encoder: Encoder,
pub codec: Codec, pub codec: Codec,
pub bitrate: u32, #[serde(skip_serializing_if = "Option::is_none")]
pub width: f32, pub bitrate: Option<u32>,
pub height: f32, #[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<i32>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WivrnConfig { pub struct WivrnConfig {
pub scale: [f32; 2], #[serde(skip_serializing_if = "Option::is_none")]
pub scale: Option<[f32; 2]>,
pub encoders: Vec<WivrnConfEncoder>, pub encoders: Vec<WivrnConfEncoder>,
} }
impl Default for WivrnConfig { impl Default for WivrnConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
scale: [0.8, 0.8], scale: Some([0.8, 0.8]),
encoders: vec![ encoders: vec![WivrnConfEncoder {
WivrnConfEncoder { encoder: Encoder::X264,
encoder: Encoder::X264, codec: Codec::H264,
codec: Codec::H264, bitrate: Some(100000000),
bitrate: 100000000, width: Some(1.0),
width: 1.0, height: Some(1.0),
height: 1.0, group: None,
} }],
],
} }
} }
} }
fn get_wivrn_config_path() -> String { fn get_wivrn_config_path() -> String {
format!( format!("{config}/wivrn/config.json", config = get_xdg_config_dir())
"{config}/wivrn/config.json",
config = get_xdg_config_dir()
)
} }
fn get_wivrn_config_from_path(path_s: &String) -> Option<WivrnConfig> { fn get_wivrn_config_from_path(path_s: &String) -> Option<WivrnConfig> {
deserialize_file(path_s) deserialize_file(path_s)
} }
pub fn get_wivrn_config() -> Option<WivrnConfig> { pub fn get_wivrn_config() -> WivrnConfig {
get_wivrn_config_from_path(&get_wivrn_config_path()) get_wivrn_config_from_path(&get_wivrn_config_path()).unwrap_or(WivrnConfig::default())
} }
fn dump_wivrn_config_to_path(config: &WivrnConfig, path_s: &String) { fn dump_wivrn_config_to_path(config: &WivrnConfig, path_s: &String) {
let writer = get_writer(path_s); let writer = get_writer(path_s);
serde_json::to_writer_pretty(writer, config) serde_json::to_writer_pretty(writer, config).expect_dialog("Unable to save WiVRn config");
.expect_dialog("Unable to save WiVRn config");
} }
pub fn dump_wivrn_config(config: &WivrnConfig, path_s: &String) { pub fn dump_wivrn_config(config: &WivrnConfig) {
dump_wivrn_config_to_path(config, path_s); dump_wivrn_config_to_path(config, &get_wivrn_config_path());
} }
#[cfg(test)] #[cfg(test)]
mod tests { 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; use super::get_wivrn_config_from_path;
#[test] #[test]
fn can_read_wivrn_config() { fn can_read_wivrn_config() {
let conf = get_wivrn_config_from_path(&"./test/files/wivrn_config.json".into()).expect("Couldn't find wivrn config"); let conf = get_wivrn_config_from_path(&"./test/files/wivrn_config.json".into())
assert_eq!(conf.scale, [0.8, 0.8]); .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.len(), 1);
assert_eq!(conf.encoders.get(0).unwrap().encoder, Encoder::X264); 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().codec, Codec::H264);
assert_eq!(conf.encoders.get(0).unwrap().bitrate, 100000000); assert_eq!(conf.encoders.get(0).unwrap().bitrate, Some(100000000));
assert_eq!(conf.encoders.get(0).unwrap().width, 1.0); assert_eq!(conf.encoders.get(0).unwrap().width, Some(1.0));
assert_eq!(conf.encoders.get(0).unwrap().height, 1.0); assert_eq!(conf.encoders.get(0).unwrap().height, Some(1.0));
} }
} }

View file

@ -347,20 +347,20 @@ impl SimpleComponent for App {
.collect(); .collect();
self.config.save(); self.config.save();
self.profiles = Self::profiles_list(&self.config); self.profiles = Self::profiles_list(&self.config);
self.main_view.sender().emit(MainViewMsg::UpdateSelectedProfile(
self.get_selected_profile()
));
self.main_view self.main_view
.sender() .sender()
.emit(MainViewMsg::UpdateProfiles( .emit(MainViewMsg::UpdateSelectedProfile(
self.profiles.clone(), self.get_selected_profile(),
self.config.clone(), ));
)) self.main_view.sender().emit(MainViewMsg::UpdateProfiles(
self.profiles.clone(),
self.config.clone(),
))
} }
} }
Msg::SaveProfile(prof) => { Msg::SaveProfile(prof) => {
match self.profiles.iter().position(|p| p.uuid == prof.uuid) { match self.profiles.iter().position(|p| p.uuid == prof.uuid) {
None => {}, None => {}
Some(index) => { Some(index) => {
self.profiles.remove(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.profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
self.config.set_profiles(&self.profiles); self.config.set_profiles(&self.profiles);
self.config.save(); self.config.save();
self.main_view self.main_view.sender().emit(MainViewMsg::UpdateProfiles(
.sender() self.profiles.clone(),
.emit(MainViewMsg::UpdateProfiles( self.config.clone(),
self.profiles.clone(), ))
self.config.clone(),
))
} }
Msg::RunSetCap => { Msg::RunSetCap => {
if !check_dependency(pkexec_dep()) { if !check_dependency(pkexec_dep()) {
@ -546,17 +544,15 @@ impl SimpleComponent for App {
actions.add_action(libsurvive_setup_action); actions.add_action(libsurvive_setup_action);
root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group())); root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group()));
model model
.application .application
.set_accelerators_for_action::<QuitAction>(&["<Control>q"]); .set_accelerators_for_action::<QuitAction>(&["<Control>q"]);
model model.main_view.sender().emit(MainViewMsg::UpdateProfiles(
.main_view model.profiles.clone(),
.sender() model.config.clone(),
.emit(MainViewMsg::UpdateProfiles( ));
model.profiles.clone(),
model.config.clone(),
));
let timer_sender = sender.clone(); let timer_sender = sender.clone();
glib::timeout_add_local(Duration::from_millis(1000), move || { glib::timeout_add_local(Duration::from_millis(1000), move || {

View file

@ -38,7 +38,7 @@ impl FactoryComponent for EntryModel {
view! { view! {
root = adw::EntryRow { root = adw::EntryRow {
set_title: &self.name, set_title: &self.name,
set_text: &self.value, set_text: &self.value,
add_suffix: clear_btn = &gtk::Button { add_suffix: clear_btn = &gtk::Button {
set_icon_name: "edit-clear-symbolic", set_icon_name: "edit-clear-symbolic",

View file

@ -8,7 +8,12 @@ use crate::{
runner::{Runner, RunnerStatus}, runner::{Runner, RunnerStatus},
}; };
use gtk::prelude::*; 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; use std::thread::JoinHandle;
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@ -34,7 +39,7 @@ pub struct InstallWivrnBox {
pub enum InstallWivrnBoxMsg { pub enum InstallWivrnBoxMsg {
ClockTicking, ClockTicking,
UpdateSelectedProfile(Profile), UpdateSelectedProfile(Profile),
DownloadWivrn, DownloadWivrn(String),
InstallWivrnApk, InstallWivrnApk,
} }
@ -50,6 +55,15 @@ impl SimpleComponent for InstallWivrnBox {
type Input = InstallWivrnBoxMsg; type Input = InstallWivrnBoxMsg;
type Output = (); type Output = ();
menu! {
install_wivrn_menu: {
section! {
"For _Oculus" => WivrnApkOculusAction,
"For _Pico" => WivrnApkPicoAction,
}
}
}
view! { view! {
gtk::Box { gtk::Box {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
@ -89,7 +103,7 @@ impl SimpleComponent for InstallWivrnBox {
set_wrap: true, set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word, set_wrap_mode: gtk::pango::WrapMode::Word,
}, },
gtk::Button { gtk::MenuButton {
add_css_class: "suggested-action", add_css_class: "suggested-action",
set_label: "Install WiVRn", set_label: "Install WiVRn",
set_margin_start: 12, set_margin_start: 12,
@ -100,9 +114,7 @@ impl SimpleComponent for InstallWivrnBox {
InstallWivrnStatus::InProgress => false, InstallWivrnStatus::InProgress => false,
_ => true, _ => true,
}, },
connect_clicked[sender] => move |_| { set_menu_model: Some(&install_wivrn_menu),
sender.input(Self::Input::DownloadWivrn);
},
}, },
gtk::Label { gtk::Label {
add_css_class: "error", add_css_class: "error",
@ -174,15 +186,12 @@ impl SimpleComponent for InstallWivrnBox {
}; };
} }
} }
Self::Input::DownloadWivrn => { Self::Input::DownloadWivrn(link) => {
if !check_dependency(adb_dep()) { if !check_dependency(adb_dep()) {
self.adb_missing_dialog.present(); self.adb_missing_dialog.present();
} else { } else {
self.set_install_wivrn_status(InstallWivrnStatus::InProgress); self.set_install_wivrn_status(InstallWivrnStatus::InProgress);
self.download_thread = Some(download_file( self.download_thread = Some(download_file(link, wivrn_apk_download_path()));
"https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-oculus-release.apk".into(),
wivrn_apk_download_path()
));
} }
} }
Self::Input::InstallWivrnApk => { Self::Input::InstallWivrnApk => {
@ -221,6 +230,34 @@ impl SimpleComponent for InstallWivrnBox {
let widgets = view_output!(); let widgets = view_output!();
let mut actions = RelmActionGroup::<InstallWivrnActionGroup>::new();
let apk_oculus_action = {
let oculus_sender = sender.clone();
RelmAction::<WivrnApkOculusAction>::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::<WivrnApkPicoAction>::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 } ComponentParts { model, widgets }
} }
} }
new_action_group!(pub InstallWivrnActionGroup, "installwivrn");
new_stateless_action!(pub WivrnApkOculusAction, InstallWivrnActionGroup, "apkoculus");
new_stateless_action!(pub WivrnApkPicoAction, InstallWivrnActionGroup, "apkpico");

View file

@ -401,10 +401,10 @@ impl SimpleComponent for ProfileEditor {
) )
.model(&gtk::StringList::new( .model(&gtk::StringList::new(
XRServiceType::iter() XRServiceType::iter()
.map(|st| st.to_string()) .map(XRServiceType::to_string)
.collect::<Vec<String>>() .collect::<Vec<String>>()
.iter() .iter()
.map(|s| s.as_str()) .map(String::as_str)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.as_slice(), .as_slice(),
)) ))