feat: box to start wivrn when headset is wired (#117)
Some checks are pending
/ cargo-fmtcheck (push) Waiting to run
/ cargo-clippy (push) Waiting to run
/ cargo-test (push) Waiting to run
/ appimage (push) Waiting to run

This commit is contained in:
Gabriele Musco 2024-09-21 15:18:42 +02:00
parent 3a024cb9ab
commit 41e9af1676
No known key found for this signature in database
GPG key ID: 1068D795C80E51DE
4 changed files with 201 additions and 4 deletions

View file

@ -1,4 +1,4 @@
use super::alert::alert;
use super::{alert::alert, ADB_ERR_NO_DEV};
use crate::{
async_process::async_process,
depcheck::common::dep_adb,
@ -9,8 +9,6 @@ use gtk::prelude::*;
use relm4::{new_action_group, new_stateless_action, prelude::*};
use std::fs::remove_file;
const ADB_ERR_NO_DEV: &str = "no devices/emulators found";
const WIVRN_LATEST_RELEASE_APK_URL: &str =
"https://github.com/WiVRn/WiVRn/releases/latest/download/WiVRn-standard-release.apk";

View file

@ -10,6 +10,7 @@ use super::{
steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg},
steamvr_calibration_box::{SteamVrCalibrationBox, SteamVrCalibrationBoxMsg},
util::{limit_dropdown_width, warning_heading},
wivrn_wired_start_box::{WivrnWiredStartBox, WivrnWiredStartBoxInit, WivrnWiredStartBoxMsg},
};
use crate::{
config::Config,
@ -44,6 +45,8 @@ pub struct MainView {
#[tracker::do_not_track]
install_wivrn_box: AsyncController<InstallWivrnBox>,
#[tracker::do_not_track]
wivrn_wired_start_box: AsyncController<WivrnWiredStartBox>,
#[tracker::do_not_track]
steam_launch_options_box: Controller<SteamLaunchOptionsBox>,
#[tracker::do_not_track]
devices_box: Controller<DevicesBox>,
@ -364,6 +367,7 @@ impl SimpleComponent for MainView {
},
model.steam_launch_options_box.widget(),
model.wivrn_wired_start_box.widget(),
model.install_wivrn_box.widget(),
model.steamvr_calibration_box.widget(),
@ -494,6 +498,9 @@ impl SimpleComponent for MainView {
self.install_wivrn_box
.sender()
.emit(InstallWivrnBoxMsg::UpdateSelectedProfile(prof.clone()));
self.wivrn_wired_start_box
.sender()
.emit(WivrnWiredStartBoxMsg::UpdateSelectedProfile(prof.clone()));
}
Self::Input::UpdateProfiles(profiles, config) => {
self.set_profiles(profiles);
@ -789,6 +796,12 @@ impl SimpleComponent for MainView {
root_win: init.root_win.clone(),
})
.detach(),
wivrn_wired_start_box: WivrnWiredStartBox::builder()
.launch(WivrnWiredStartBoxInit {
selected_profile: init.selected_profile.clone(),
root_win: init.root_win.clone(),
})
.detach(),
devices_box: DevicesBox::builder().launch(()).detach(),
selected_profile: init.selected_profile.clone(),
profile_not_editable_dialog,

View file

@ -19,7 +19,9 @@ mod steamvr_calibration_box;
mod term_widget;
mod util;
mod wivrn_conf_editor;
pub mod wivrn_encoder_presets_win;
mod wivrn_encoder_presets_win;
mod wivrn_wired_start_box;
pub const SENDER_IO_ERR_MSG: &str = "relm4 sender i/o failed";
pub const ADW_DIALOG_WIDTH: i32 = 600;
pub const ADB_ERR_NO_DEV: &str = "no devices/emulators found";

View file

@ -0,0 +1,184 @@
use super::{alert::alert, ADB_ERR_NO_DEV};
use crate::{
async_process::async_process,
constants::APP_NAME,
depcheck::common::dep_adb,
profile::{Profile, XRServiceType},
};
use gtk::prelude::*;
use relm4::prelude::*;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum StartClientStatus {
Success,
Done(Option<String>),
InProgress,
}
#[tracker::track]
pub struct WivrnWiredStartBox {
selected_profile: Profile,
start_client_status: StartClientStatus,
#[tracker::do_not_track]
root_win: gtk::Window,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum WivrnWiredStartBoxMsg {
UpdateSelectedProfile(Profile),
/// prepares state for async action, calls DoStartClient
StartWivrnClient,
/// actually sends the adb commands to start the client
DoStartClient,
}
#[derive(Debug)]
pub struct WivrnWiredStartBoxInit {
pub selected_profile: Profile,
pub root_win: gtk::Window,
}
#[relm4::component(pub async)]
impl AsyncComponent for WivrnWiredStartBox {
type Init = WivrnWiredStartBoxInit;
type Input = WivrnWiredStartBoxMsg;
type Output = ();
type CommandOutput = ();
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 12,
add_css_class: "card",
add_css_class: "padded",
#[track = "model.changed(Self::selected_profile())"]
set_visible: model.selected_profile.xrservice_type == XRServiceType::Wivrn,
gtk::Label {
add_css_class: "heading",
set_hexpand: true,
set_xalign: 0.0,
set_label: "Start WiVRn Client (Wired)",
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Label {
add_css_class: "dim-label",
set_hexpand: true,
set_label: concat!(
"Start the WiVRn client on your Android headset. This only ",
"works in wired connection mode."
),
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
},
gtk::Button {
add_css_class: "suggested-action",
set_label: "Start WiVRn Client",
set_halign: gtk::Align::Start,
#[track = "model.changed(Self::start_client_status())"]
set_sensitive: model.start_client_status != StartClientStatus::InProgress,
connect_clicked[sender] => move |_| {
sender.input(Self::Input::StartWivrnClient)
},
},
gtk::Label {
add_css_class: "error",
set_xalign: 0.0,
set_wrap: true,
set_wrap_mode: gtk::pango::WrapMode::Word,
#[track = "model.changed(Self::start_client_status())"]
set_visible: matches!(&model.start_client_status, StartClientStatus::Done(Some(_))),
#[track = "model.changed(Self::start_client_status())"]
set_label: match &model.start_client_status {
StartClientStatus::Done(Some(err)) => err.as_str(),
_ => "",
},
},
}
}
async fn update(
&mut self,
message: Self::Input,
sender: AsyncComponentSender<Self>,
_root: &Self::Root,
) {
self.reset();
match message {
Self::Input::UpdateSelectedProfile(p) => self.set_selected_profile(p),
Self::Input::StartWivrnClient => {
if !dep_adb().check() {
alert("ADB is not installed", Some(&format!("Please install ADB on your computer to start the WiVRn client from {}.", APP_NAME)), Some(&self.root_win));
return;
}
self.set_start_client_status(StartClientStatus::InProgress);
sender.input(Self::Input::DoStartClient);
}
Self::Input::DoStartClient => {
let n_status = match async_process(
"sh",
Some(&[
"-c",
concat!(
"adb reverse tcp:9757 tcp:9757 && ",
"adb shell am force-stop org.meumeu.wivrn && ",
// wait for force-stop
"sleep 1 && ",
"adb shell am start -a android.intent.action.VIEW -d \"wivrn+tcp://127.0.0.1\" org.meumeu.wivrn"
)
]),
None
).await {
Ok(out) if out.exit_code == 0 =>
StartClientStatus::Success
,
Ok(out) => {
if out.stdout.contains(ADB_ERR_NO_DEV)
|| out.stderr.contains(ADB_ERR_NO_DEV)
{
StartClientStatus::Done(Some(
concat!(
"No devices connected. Make sure your headset is connected ",
"to this computer and USB debugging is turned on."
)
.into(),
))
} else {
eprintln!("Error: ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr);
StartClientStatus::Done(Some(
format!("ADB exited with code \"{}\"", out.exit_code)
))
}
},
Err(e) => {
eprintln!("Error: failed to run ADB: {e}");
StartClientStatus::Done(Some(
"Failed to run ADB".into()
))
},
};
self.set_start_client_status(n_status);
}
}
}
async fn init(
init: Self::Init,
root: Self::Root,
sender: AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> {
let model = Self {
selected_profile: init.selected_profile,
start_client_status: StartClientStatus::Done(None),
root_win: init.root_win,
tracker: 0,
};
let widgets = view_output!();
AsyncComponentParts { model, widgets }
}
}