mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-08-04 07:08:53 +00:00
feat: can install wivrn apk for either oculus or pico
This commit is contained in:
parent
a58fb365c9
commit
a6df8bacb9
5 changed files with 139 additions and 70 deletions
|
@ -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: 100000000,
|
bitrate: Some(100000000),
|
||||||
width: 1.0,
|
width: Some(1.0),
|
||||||
height: 1.0,
|
height: Some(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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,12 +347,12 @@ 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.get_selected_profile(),
|
||||||
|
));
|
||||||
|
self.main_view.sender().emit(MainViewMsg::UpdateProfiles(
|
||||||
self.profiles.clone(),
|
self.profiles.clone(),
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
))
|
))
|
||||||
|
@ -360,7 +360,7 @@ impl SimpleComponent for App {
|
||||||
}
|
}
|
||||||
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,9 +369,7 @@ 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()
|
|
||||||
.emit(MainViewMsg::UpdateProfiles(
|
|
||||||
self.profiles.clone(),
|
self.profiles.clone(),
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
))
|
))
|
||||||
|
@ -546,14 +544,12 @@ 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
|
|
||||||
.sender()
|
|
||||||
.emit(MainViewMsg::UpdateProfiles(
|
|
||||||
model.profiles.clone(),
|
model.profiles.clone(),
|
||||||
model.config.clone(),
|
model.config.clone(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -401,10 +401,10 @@ impl SimpleComponent for ProfileEditor {
|
||||||
)
|
)
|
||||||
.model(>k::StringList::new(
|
.model(>k::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(),
|
||||||
))
|
))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue