From 74dc90303499d32e0fccc8d6b0625cc853343dc6 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 12:13:17 +0800 Subject: [PATCH 1/6] fix(launcher): :bug: Restore Launcher --- .gitignore | 4 +- Cargo.lock | 61 ++- alvr/launcher/Cargo.toml | 4 +- alvr/launcher/src/actions.rs | 348 +++++++++++++++ alvr/launcher/src/main.rs | 812 +---------------------------------- alvr/launcher/src/ui.rs | 391 +++++++++++++++++ 6 files changed, 826 insertions(+), 794 deletions(-) create mode 100644 alvr/launcher/src/actions.rs create mode 100644 alvr/launcher/src/ui.rs diff --git a/.gitignore b/.gitignore index 9a7f83cf22..e8c1d036cb 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,6 @@ node_modules # The .gitignore for android is project folder. # flatpak -.flatpak* \ No newline at end of file +.flatpak* + +ALVR-Launcher \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a5272e906b..b15f536471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,10 +327,12 @@ dependencies = [ "alvr_gui_common", "anyhow", "eframe", + "flate2", "futures-util", "open", "reqwest 0.12.0", "serde_json", + "tar", "tokio", "zip", ] @@ -2049,6 +2051,18 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + [[package]] name = "flate2" version = "1.0.28" @@ -2516,6 +2530,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -2691,7 +2724,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2714,6 +2747,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.3", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -4262,7 +4296,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", @@ -4301,6 +4335,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", + "h2 0.4.3", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -4978,6 +5013,17 @@ dependencies = [ "libc", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -6358,6 +6404,17 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys 0.4.13", + "rustix 0.38.32", +] + [[package]] name = "xcursor" version = "0.3.5" diff --git a/alvr/launcher/Cargo.toml b/alvr/launcher/Cargo.toml index c54e92f125..67239f8ef5 100644 --- a/alvr/launcher/Cargo.toml +++ b/alvr/launcher/Cargo.toml @@ -11,9 +11,11 @@ alvr_gui_common.workspace = true anyhow = "1" eframe = "0.26" +flate2 = "1" futures-util = "0.3.28" open = "5" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json", "http2"] } serde_json = "1" +tar = "0.4" tokio = { version = "1", features = ["rt-multi-thread"] } zip = "0.6" diff --git a/alvr/launcher/src/actions.rs b/alvr/launcher/src/actions.rs new file mode 100644 index 0000000000..5ed030f3b5 --- /dev/null +++ b/alvr/launcher/src/actions.rs @@ -0,0 +1,348 @@ +use crate::{ + InstallationInfo, Progress, ReleaseChannelsInfo, ReleaseInfo, UiMessage, WorkerMessage, +}; +use alvr_common::anyhow::Result; +use anyhow::bail; +use flate2::read::GzDecoder; +use futures_util::StreamExt; +use std::{ + env, + fs::{self, File}, + io::{Cursor, Write}, + path::PathBuf, + process::Command, + sync::mpsc::{Receiver, Sender}, +}; + +#[cfg(not(windows))] +const ADB_EXECUTABLE: &str = "adb"; +#[cfg(windows)] +const ADB_EXECUTABLE: &str = "adb.exe"; + +#[cfg(not(windows))] +const PLATFORM_TOOLS_DL_LINK: &str = + "https://dl.google.com/android/repository/platform-tools-latest-linux.zip"; +#[cfg(windows)] +const PLATFORM_TOOLS_DL_LINK: &str = + "https://dl.google.com/android/repository/platform-tools-latest-windows.zip"; + +const APK_NAME: &str = "client.apk"; + +pub fn installations_dir() -> PathBuf { + data_dir().join("installations") +} + +pub fn worker(rx: Receiver, tx: Sender) { + tokio::runtime::Runtime::new() + .expect("Failed to create tokio runtime") + .block_on(async { + let client = reqwest::Client::builder() + .user_agent("ALVR-Launcher") + .build() + .unwrap(); + let version_data = match fetch_all_releases(&client).await { + Ok(data) => data, + Err(why) => { + eprintln!("Error fetching version data: {}", why); + return; + } + }; + + tx.send(WorkerMessage::ReleaseChannelsInfo(version_data)) + .unwrap(); + + loop { + let message = match rx.recv() { + Ok(message) => message, + Err(e) => { + eprintln!("receiver error {e}"); + break; + } + }; + match message { + UiMessage::Quit => return, + UiMessage::Install(release) => match install(&tx, release, &client).await { + Ok(_) => tx.send(WorkerMessage::Done).unwrap(), + Err(why) => tx.send(WorkerMessage::Error(why.to_string())).unwrap(), + }, + UiMessage::InstallClient(release) => { + match install_apk(&tx, release, &client).await { + Ok(_) => tx.send(WorkerMessage::Done).unwrap(), + Err(why) => tx.send(WorkerMessage::Error(why.to_string())).unwrap(), + } + } + } + } + }); +} + +async fn fetch_all_releases(client: &reqwest::Client) -> Result { + Ok(ReleaseChannelsInfo { + stable: fetch_releases_for_repo( + client, + "https://api.github.com/repos/alvr-org/ALVR/releases", + ) + .await?, + nightly: fetch_releases_for_repo( + client, + "https://api.github.com/repos/alvr-org/ALVR-nightly/releases", + ) + .await?, + }) +} + +async fn fetch_releases_for_repo(client: &reqwest::Client, url: &str) -> Result> { + let response: serde_json::Value = client.get(url).send().await?.json().await?; + + let mut releases = Vec::new(); + for value in response.as_array().unwrap() { + releases.push(ReleaseInfo { + tag: value["tag_name"].as_str().unwrap().to_string(), + assets: value["assets"] + .as_array() + .unwrap() + .iter() + .map(|value| { + ( + value["name"].as_str().unwrap().to_string(), + value["browser_download_url"].as_str().unwrap().to_string(), + ) + }) + .collect(), + }) + } + Ok(releases) +} + +pub fn get_release(release_info: &ReleaseChannelsInfo, version: &str) -> Option { + release_info + .stable + .iter() + .find(|release| release.tag == version) + .cloned() + .or_else(|| { + release_info + .nightly + .iter() + .find(|release| release.tag == version) + .cloned() + }) +} + +async fn install_apk( + worker_message_sender: &Sender, + release: ReleaseInfo, + client: &reqwest::Client, +) -> anyhow::Result<()> { + worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + msg: "Starting install".to_string(), + progress: 0.0, + })) + .unwrap(); + + let installation_dir = installations_dir().join(&release.tag); + + let apk_path = installation_dir.clone().join(APK_NAME); + + if !apk_path.exists() { + let apk_buffer = download( + worker_message_sender, + "Downloading Client APK", + release + .assets + .get("alvr_client_android.apk") + .ok_or(anyhow::anyhow!("Unable to determine download URL"))?, + client, + ) + .await?; + + let mut file = File::create(&apk_path)?; + file.write_all(&apk_buffer)?; + } + + worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + msg: "Installing APK".to_string(), + progress: 0.0, + })) + .unwrap(); + + let res = match Command::new(ADB_EXECUTABLE) + .arg("install") + .arg("-r") + .arg(&apk_path) + .output() + { + Ok(res) => res, + Err(_) => { + let adb_path = data_dir().join("platform-tools").join(ADB_EXECUTABLE); + + if !adb_path.exists() { + let mut buffer = Cursor::new( + download( + worker_message_sender, + "Downloading Android Platform Tools", + PLATFORM_TOOLS_DL_LINK, + client, + ) + .await?, + ); + + zip::ZipArchive::new(&mut buffer)?.extract(&data_dir())?; + } + + worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + msg: "Installing APK".to_string(), + progress: 0.0, + })) + .unwrap(); + + Command::new(adb_path) + .arg("install") + .arg("-r") + .arg(&apk_path) + .output()? + } + }; + if res.status.success() { + Ok(()) + } else { + Err(anyhow::anyhow!( + "ADB install failed: {}", + String::from_utf8_lossy(&res.stderr) + )) + } +} + +async fn download( + worker_message_sender: &Sender, + msg: &str, + url: &str, + client: &reqwest::Client, +) -> anyhow::Result> { + let res = client.get(url).send().await?; + let total_size = res.content_length(); + let mut stream = res.bytes_stream(); + let mut buffer = Vec::new(); + while let Some(item) = stream.next().await { + buffer.extend(item?); + + match total_size { + Some(total_size) => worker_message_sender + .send(WorkerMessage::ProgressUpdate(Progress { + msg: msg.to_string(), + progress: buffer.len() as f32 / total_size as f32, + })) + .unwrap(), + None => worker_message_sender + .send(WorkerMessage::ProgressUpdate(Progress { + msg: format!("{} (Progress unavailable)", msg), + progress: 0.5, + })) + .unwrap(), + } + } + + Ok(buffer) +} + +async fn install( + tx: &Sender, + release: ReleaseInfo, + client: &reqwest::Client, +) -> anyhow::Result<()> { + tx.send(WorkerMessage::ProgressUpdate(Progress { + msg: "Starting install".to_string(), + progress: 0.0, + })) + .unwrap(); + + let file_name = if cfg!(windows) { + "alvr_streamer_windows.zip" + } else { + "alvr_streamer_linux.tar.gz" + }; + + let url = release + .assets + .get(file_name) + .ok_or(anyhow::anyhow!("Unable to determine download link"))?; + + let buffer = download(tx, "Downloading Streamer", url, client).await?; + + let installation_dir = installations_dir().join(&release.tag); + + fs::create_dir_all(&installation_dir)?; + + let mut buffer = Cursor::new(buffer); + if cfg!(windows) { + zip::ZipArchive::new(&mut buffer)?.extract(&installation_dir)?; + } else { + tar::Archive::new(&mut GzDecoder::new(&mut buffer)).unpack(&installation_dir)?; + } + + Ok(()) +} + +pub fn data_dir() -> PathBuf { + if cfg!(target_os = "linux") { + PathBuf::from(env::var("HOME").expect("Failed to determine home directory")) + .join(".local/share/ALVR-Launcher") + } else { + env::current_dir() + .expect("Unable to determine executable directory") + .join("ALVR-Launcher") + } +} + +pub fn get_installations() -> Vec { + match fs::read_dir(installations_dir()) { + Ok(entries) => entries + .into_iter() + .filter_map(|entry| { + entry + .ok() + .filter(|entry| match entry.file_type() { + Ok(file_type) => file_type.is_dir(), + Err(why) => { + eprintln!("Failed to read entry file type: {}", why); + false + } + }) + .map(|entry| { + let mut apk_path = entry.path(); + apk_path.push(APK_NAME); + InstallationInfo { + version: entry.file_name().to_string_lossy().to_string(), + is_apk_downloaded: apk_path.exists(), + } + }) + }) + .collect(), + Err(why) => { + eprintln!("Failed to read versions dir: {}", why); + Vec::new() + } + } +} + +pub fn launch_dashboard(version: &str) -> Result<()> { + let installation_dir = installations_dir().join(version); + + let dashboard_path = if cfg!(windows) { + installation_dir.join("ALVR Dashboard.exe") + } else if cfg!(target_os = "linux") { + installation_dir.join("bin/alvr_alvr_dashboard") + } else { + bail!("Unsupported platform") + }; + + Command::new(dashboard_path).spawn()?; + + Ok(()) +} + +pub fn delete_installation(version: &str) -> Result<()> { + fs::remove_dir_all(installations_dir().join(version))?; + + Ok(()) +} diff --git a/alvr/launcher/src/main.rs b/alvr/launcher/src/main.rs index 2eb4f7e692..f80126d782 100644 --- a/alvr/launcher/src/main.rs +++ b/alvr/launcher/src/main.rs @@ -1,568 +1,48 @@ -#[cfg(target_os = "linux")] -use std::os::unix::prelude::PermissionsExt; -use std::{ - collections::BTreeMap, - env, - fs::{self, File, Permissions}, - io::{Cursor, Write}, - mem, - path::{Path, PathBuf}, - process::Command, - sync::mpsc::{self, Receiver, Sender}, - thread, -}; +mod actions; +mod ui; -use eframe::{ - egui::{ - Button, CentralPanel, ComboBox, Context, Frame, Grid, Layout, ProgressBar, ViewportCommand, - Window, - }, - emath::{Align, Align2}, - epaint::Color32, -}; -use futures_util::StreamExt; - -#[cfg(not(target_os = "windows"))] -const DASHBOARD_PATHS: &[&str] = &["ALVR-x86_64.AppImage", "bin/alvr_dashboard"]; -#[cfg(target_os = "windows")] -const DASHBOARD_PATHS: &[&str] = &["ALVR Dashboard.exe"]; - -#[cfg(not(target_os = "windows"))] -const ADB_EXECUTABLE: &str = "adb"; -#[cfg(target_os = "windows")] -const ADB_EXECUTABLE: &str = "adb.exe"; - -#[cfg(not(target_os = "windows"))] -const PLATFORM_TOOLS_DL_LINK: &str = - "https://dl.google.com/android/repository/platform-tools-latest-linux.zip"; -#[cfg(target_os = "windows")] -const PLATFORM_TOOLS_DL_LINK: &str = - "https://dl.google.com/android/repository/platform-tools-latest-windows.zip"; - -const VERSIONS_SUBDIR: &str = "versions"; - -const APK_NAME: &str = "client.apk"; - -trait Extended

{ - fn extended(self, path: P) -> Self; -} - -impl

Extended

for PathBuf -where - P: AsRef, -{ - fn extended(mut self, path: P) -> Self { - self.push(path); - self - } -} - -#[derive(PartialEq, Eq, Copy, Clone)] -enum InstallationType { - #[cfg(target_os = "linux")] - AppImage, - Archive, -} - -impl ToString for InstallationType { - fn to_string(&self) -> String { - match self { - #[cfg(target_os = "linux")] - Self::AppImage => "AppImage", - Self::Archive => "Archive", - } - .to_string() - } -} - -impl Default for InstallationType { - #[allow(unreachable_code)] - fn default() -> Self { - #[cfg(target_os = "linux")] - return Self::AppImage; - - Self::Archive - } -} +use std::{collections::BTreeMap, sync::mpsc, thread}; +use ui::Launcher; #[derive(Clone)] -struct Release { +struct ReleaseInfo { tag: String, assets: BTreeMap, } -impl Release { - async fn fetch_releases(client: &reqwest::Client, url: &str) -> anyhow::Result> { - let response: serde_json::Value = client.get(url).send().await?.json().await?; - - let mut releases = Vec::new(); - for value in response.as_array().unwrap() { - releases.push(Self { - tag: value["tag_name"].as_str().unwrap().to_string(), - assets: value["assets"] - .as_array() - .unwrap() - .iter() - .map(|value| { - ( - value["name"].as_str().unwrap().to_string(), - value["browser_download_url"].as_str().unwrap().to_string(), - ) - }) - .collect(), - }) - } - Ok(releases) - } -} - -struct ReleaseData { - stable: Vec, - nightly: Vec, -} - -impl ReleaseData { - async fn fetch(client: &reqwest::Client) -> anyhow::Result { - Ok(Self { - stable: Release::fetch_releases( - client, - "https://api.github.com/repos/alvr-org/ALVR/releases", - ) - .await?, - nightly: Release::fetch_releases( - client, - "https://api.github.com/repos/alvr-org/ALVR-nightly/releases", - ) - .await?, - }) - } - - fn get_release(&self, version: &str) -> Option<&Release> { - self.stable - .iter() - .find(|release| release.tag == version) - .or_else(|| self.nightly.iter().find(|release| release.tag == version)) - } -} - -enum State { - Default, - Installing(Progress), - Error(String), +struct Progress { + msg: String, + progress: f32, } -enum WorkerMsg { - VersionData(ReleaseData), +enum WorkerMessage { + ReleaseChannelsInfo(ReleaseChannelsInfo), ProgressUpdate(Progress), Done, Error(String), } -struct Progress { - msg: String, - progress: f32, -} - -enum GuiMsg { - Install { - installation_type: InstallationType, - release: Release, - }, - InstallClient(Release), +enum UiMessage { + Install(ReleaseInfo), + InstallClient(ReleaseInfo), Quit, } -enum Popup { - None, - Delete(String), - Edit(String), - Version(VersionPopup), -} - -impl Default for Popup { - fn default() -> Self { - Self::None - } -} - -#[derive(Clone, PartialEq, Eq)] -enum Version { - Stable(String), - Nightly(String), -} - -impl Version { - fn inner(&self) -> &String { - match self { - Version::Stable(version) => version, - Version::Nightly(version) => version, - } - } +struct ReleaseChannelsInfo { + stable: Vec, + nightly: Vec, } -impl ToString for Version { - fn to_string(&self) -> String { - match self { - Version::Stable(version) => { - format!("Stable {}", version) - } - Version::Nightly(version) => { - format!("Nightly {}", version) - } - } - } -} - -struct VersionPopup { - version: Version, - installation_type: InstallationType, -} - -struct Launcher { - rx: Receiver, - tx: Sender, - state: State, - installations: Vec, - version_data: Option, - popup: Popup, -} - -struct Installation { - version: String, - apk_downloaded: bool, -} - -impl Launcher { - fn new(cc: &eframe::CreationContext, rx: Receiver, tx: Sender) -> Self { - alvr_gui_common::theme::set_theme(&cc.egui_ctx); - - Self { - rx, - tx, - state: State::Default, - installations: get_installations(), - version_data: None, - popup: Popup::None, - } - } - - fn version_popup(&mut self, ctx: &Context, mut version_popup: VersionPopup) -> Popup { - Window::new("Add version") - .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - let version_data = self.version_data.as_ref().unwrap(); - let (channel, version_str, versions): (&str, String, Vec) = - match version_popup.version.clone() { - Version::Stable(version) => ( - "Stable", - version, - version_data - .stable - .iter() - .map(|release| Version::Stable(release.tag.clone())) - .collect(), - ), - Version::Nightly(version) => ( - "Nightly", - version, - version_data - .nightly - .iter() - .map(|release| Version::Nightly(release.tag.clone())) - .collect(), - ), - }; - Grid::new("add-version-grid").num_columns(2).show(ui, |ui| { - ui.label("Channel"); - - ui.with_layout(Layout::right_to_left(Align::Min), |ui| { - ComboBox::from_id_source("channel") - .selected_text(channel) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut version_popup.version, - Version::Stable( - self.version_data.as_ref().unwrap().stable[0].tag.clone(), - ), - "Stable", - ); - ui.selectable_value( - &mut version_popup.version, - Version::Nightly( - self.version_data.as_ref().unwrap().nightly[0].tag.clone(), - ), - "Nightly", - ); - }) - }); - ui.end_row(); - - ui.label("Version"); - ui.with_layout(Layout::right_to_left(Align::Min), |ui| { - ComboBox::from_id_source("version") - .selected_text(version_str) - .show_ui(ui, |ui| { - for version in versions { - ui.selectable_value( - &mut version_popup.version, - version.clone(), - version.inner(), - ); - } - }) - }); - ui.end_row(); - - ui.label("Installation Type"); - ui.with_layout(Layout::right_to_left(Align::Min), |ui| { - ComboBox::from_id_source("type") - .selected_text(version_popup.installation_type.to_string()) - .show_ui(ui, |ui| { - #[cfg(target_os = "linux")] - ui.selectable_value( - &mut version_popup.installation_type, - InstallationType::AppImage, - InstallationType::AppImage.to_string(), - ); - ui.selectable_value( - &mut version_popup.installation_type, - InstallationType::Archive, - InstallationType::Archive.to_string(), - ); - }) - }); - ui.end_row(); - }); - ui.columns(2, |ui| { - if ui[0].button("Cancel").clicked() { - return Popup::None; - } - - if ui[1].button("Install").clicked() { - self.tx - .send(GuiMsg::Install { - installation_type: version_popup.installation_type, - release: match &version_popup.version { - Version::Stable(version) => version_data - .stable - .iter() - .find(|release| release.tag == *version) - .unwrap() - .clone(), - Version::Nightly(version) => version_data - .nightly - .iter() - .find(|release| release.tag == *version) - .unwrap() - .clone(), - }, - }) - .unwrap(); - return Popup::None; - } - - Popup::Version(version_popup) - }) - }) - .unwrap() - .inner - .unwrap() - } - - fn edit_popup(&self, ctx: &Context, version: String) -> Popup { - Window::new("Edit version") - .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - ui.with_layout(Layout::top_down_justified(Align::Center), |ui| { - if ui.button("Delete version").clicked() { - return Popup::Delete(version); - }; - if ui.button("Close").clicked() { - return Popup::None; - } - - Popup::Edit(version) - }) - .inner - }) - .unwrap() - .inner - .unwrap() - } - - fn delete_popup(&mut self, ctx: &Context, version: String) -> Popup { - Window::new("Are you sure?") - .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.label(format!("This will permanently delete version {}", version)); - }); - ui.columns(2, |ui| { - if ui[0].button("Cancel").clicked() { - return Popup::None; - } - if ui[1].button("Delete version").clicked() { - let mut path = data_dir(); - path.push(version); - fs::remove_dir_all(path).expect("Failed to delete version"); - - self.installations = get_installations(); - - return Popup::None; - } - Popup::Delete(version) - }) - }) - .unwrap() - .inner - .unwrap() - } -} - -impl eframe::App for Launcher { - fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { - while let Ok(msg) = self.rx.try_recv() { - match msg { - WorkerMsg::VersionData(data) => self.version_data = Some(data), - WorkerMsg::ProgressUpdate(progress) => { - self.state = State::Installing(progress); - } - WorkerMsg::Done => { - // Refresh installations - self.installations = get_installations(); - self.state = State::Default; - } - WorkerMsg::Error(why) => self.state = State::Error(why), - } - } - - CentralPanel::default().show(ctx, |ui| match &self.state { - State::Default => { - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.label("ALVR Launcher"); - ui.label(match &self.version_data { - Some(data) => format!("Latest stable release: {}", data.stable[0].tag), - None => "Fetching latest release...".to_string(), - }); - - for installation in &self.installations { - let path = data_dir() - .extended(VERSIONS_SUBDIR) - .extended(&installation.version); - - Frame::group(ui.style()) - .fill(alvr_gui_common::theme::SECTION_BG) - .show(ui, |ui| { - Grid::new(&installation.version) - .num_columns(2) - .show(ui, |ui| { - ui.label(&installation.version); - ui.with_layout(Layout::right_to_left(Align::Min), |ui| { - if ui.button("Launch").clicked() { - for exec in DASHBOARD_PATHS { - let path = path.clone().extended(exec); - - if Command::new(&path).spawn().is_ok() { - break; - } - } - self.tx.send(GuiMsg::Quit).unwrap(); - ctx.send_viewport_cmd(ViewportCommand::Close); - } - if ui - .add_enabled( - self.version_data.is_some() - && self - .version_data - .as_ref() - .unwrap() - .get_release(&installation.version) - .is_some() - || installation.apk_downloaded, - Button::new("Install APK"), - ) - .clicked() - { - self.tx - .send(GuiMsg::InstallClient( - self.version_data - .as_ref() - .unwrap() - .get_release(&installation.version) - .unwrap() - .clone(), - )) - .unwrap(); - }; - if ui.button("Open directory").clicked() { - open::that_in_background(path); - } - if ui.button("Edit").clicked() { - self.popup = - Popup::Edit(installation.version.clone()); - } - }) - }) - }); - } - - if ui - .add_enabled(self.version_data.is_some(), Button::new("Add version")) - .clicked() - { - self.popup = Popup::Version(VersionPopup { - version: Version::Stable( - self.version_data.as_ref().unwrap().stable[0].tag.clone(), - ), - installation_type: InstallationType::default(), - }); - } - - let popup = match mem::take(&mut self.popup) { - Popup::Version(version_popup) => self.version_popup(ctx, version_popup), - Popup::Edit(version) => self.edit_popup(ctx, version), - Popup::Delete(version) => self.delete_popup(ctx, version), - Popup::None => Popup::None, - }; - self.popup = popup; - }); - } - State::Installing(progress) => { - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.label(&progress.msg); - ui.add(ProgressBar::new(progress.progress).animate(true)); - }); - } - State::Error(why) => { - let why = why.clone(); // Avoid borrowing issues with the closure for the layout - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.colored_label(Color32::LIGHT_RED, "Error!"); - ui.label(why); - - if ui.button("Close").clicked() { - self.state = State::Default; - } - }); - } - }); - - if ctx.input(|i| i.viewport().close_requested()) { - self.tx.send(GuiMsg::Quit).unwrap(); - } - } +struct InstallationInfo { + pub version: String, + is_apk_downloaded: bool, } fn main() { - let (worker_tx, gui_rx) = mpsc::channel::(); - let (gui_tx, worker_rx) = mpsc::channel::(); + let (worker_tx, gui_rx) = mpsc::channel::(); + let (gui_tx, worker_rx) = mpsc::channel::(); - let worker_handle = thread::spawn(|| worker(worker_rx, worker_tx)); + let worker_handle = thread::spawn(|| actions::worker(worker_rx, worker_tx)); eframe::run_native( "ALVR Launcher", @@ -575,251 +55,3 @@ fn main() { worker_handle.join().unwrap(); } - -fn worker(rx: Receiver, tx: Sender) { - tokio::runtime::Runtime::new() - .expect("Failed to create tokio runtime") - .block_on(async { - let client = reqwest::Client::builder() - .user_agent("ALVR-Launcher") - .build() - .unwrap(); - let version_data = match ReleaseData::fetch(&client).await { - Ok(data) => data, - Err(why) => { - eprintln!("Error fetching version data: {}", why); - return; - } - }; - - tx.send(WorkerMsg::VersionData(version_data)).unwrap(); - - loop { - match rx.recv().unwrap() { - GuiMsg::Quit => return, - GuiMsg::Install { - installation_type, - release, - } => match install(&tx, installation_type, release, &client).await { - Ok(_) => tx.send(WorkerMsg::Done).unwrap(), - Err(why) => tx.send(WorkerMsg::Error(why.to_string())).unwrap(), - }, - GuiMsg::InstallClient(release) => { - match install_apk(&tx, release, &client).await { - Ok(_) => tx.send(WorkerMsg::Done).unwrap(), - Err(why) => tx.send(WorkerMsg::Error(why.to_string())).unwrap(), - } - } - } - } - }); -} - -async fn install_apk( - tx: &Sender, - release: Release, - client: &reqwest::Client, -) -> anyhow::Result<()> { - tx.send(WorkerMsg::ProgressUpdate(Progress { - msg: "Starting install".to_string(), - progress: 0.0, - })) - .unwrap(); - - let installation_dir = data_dir().extended(VERSIONS_SUBDIR).extended(&release.tag); - - let apk_path = installation_dir.clone().extended(APK_NAME); - - if !apk_path.exists() { - let apk_buffer = download( - tx, - "Downloading Client APK", - release - .assets - .get("alvr_client_android.apk") - .ok_or(anyhow::anyhow!("Unable to determine download URL"))?, - client, - ) - .await?; - - let mut file = File::create(&apk_path)?; - file.write_all(&apk_buffer)?; - } - - tx.send(WorkerMsg::ProgressUpdate(Progress { - msg: "Installing APK".to_string(), - progress: 0.0, - })) - .unwrap(); - - let res = match Command::new(ADB_EXECUTABLE) - .arg("install") - .arg("-r") - .arg(&apk_path) - .output() - { - Ok(res) => res, - Err(_) => { - let adb_path = data_dir() - .extended("platform-tools") - .extended(ADB_EXECUTABLE); - - if !adb_path.exists() { - let mut buffer = Cursor::new( - download( - tx, - "Downloading Android Platform Tools", - PLATFORM_TOOLS_DL_LINK, - client, - ) - .await?, - ); - - zip::ZipArchive::new(&mut buffer)?.extract(&data_dir())?; - } - - tx.send(WorkerMsg::ProgressUpdate(Progress { - msg: "Installing APK".to_string(), - progress: 0.0, - })) - .unwrap(); - - Command::new(adb_path) - .arg("install") - .arg("-r") - .arg(&apk_path) - .output()? - } - }; - if res.status.success() { - Ok(()) - } else { - Err(anyhow::anyhow!( - "ADB install failed: {}", - String::from_utf8_lossy(&res.stderr) - )) - } -} - -async fn download( - tx: &Sender, - msg: &str, - url: &str, - client: &reqwest::Client, -) -> anyhow::Result> { - let res = client.get(url).send().await?; - let total_size = res.content_length(); - let mut stream = res.bytes_stream(); - let mut buffer = Vec::new(); - while let Some(item) = stream.next().await { - buffer.extend(item?); - - match total_size { - Some(total_size) => tx - .send(WorkerMsg::ProgressUpdate(Progress { - msg: msg.to_string(), - progress: buffer.len() as f32 / total_size as f32, - })) - .unwrap(), - None => tx - .send(WorkerMsg::ProgressUpdate(Progress { - msg: format!("{} (Progress unavailable)", msg), - progress: 0.5, - })) - .unwrap(), - } - } - - Ok(buffer) -} - -async fn install( - tx: &Sender, - installation_type: InstallationType, - release: Release, - client: &reqwest::Client, -) -> anyhow::Result<()> { - tx.send(WorkerMsg::ProgressUpdate(Progress { - msg: "Starting install".to_string(), - progress: 0.0, - })) - .unwrap(); - - let url = match installation_type { - #[cfg(target_os = "linux")] - InstallationType::AppImage => release.assets.get("ALVR-x86_64.AppImage"), - #[cfg(not(target_os = "windows"))] - InstallationType::Archive => release.assets.get("alvr_streamer_linux.tar.gz"), - #[cfg(target_os = "windows")] - InstallationType::Archive => release.assets.get("alvr_streamer_windows.zip"), - } - .ok_or(anyhow::anyhow!("Unable to determine download link"))? - .clone(); - - let buffer = download(tx, "Downloading Streamer", &url, client).await?; - - let mut installation_dir = data_dir().extended(VERSIONS_SUBDIR).extended(&release.tag); - - fs::create_dir_all(&installation_dir)?; - - match installation_type { - #[cfg(target_os = "linux")] - InstallationType::AppImage => { - installation_dir.push("ALVR-x86_64.AppImage"); - let mut file = File::create(&installation_dir)?; - - file.write_all(&buffer)?; - file.set_permissions(Permissions::from_mode(0o755))?; - } - #[cfg(not(target_os = "windows"))] - InstallationType::Archive => todo!(), - #[cfg(target_os = "windows")] - InstallationType::Archive => { - let mut buffer = Cursor::new(buffer); - zip::ZipArchive::new(&mut buffer)?.extract(&installation_dir)?; - } - } - Ok(()) -} - -fn data_dir() -> PathBuf { - if cfg!(target_os = "linux") { - PathBuf::from(env::var("HOME").expect("Failed to determine home directory")) - .extended(".local/share/ALVR-Launcher") - } else { - env::current_dir() - .expect("Unable to determine executable directory") - .extended("ALVR-Launcher") - } -} - -fn get_installations() -> Vec { - match fs::read_dir(data_dir().extended(VERSIONS_SUBDIR)) { - Ok(entries) => entries - .into_iter() - .filter_map(|entry| { - entry - .ok() - .filter(|entry| match entry.file_type() { - Ok(file_type) => file_type.is_dir(), - Err(why) => { - eprintln!("Failed to read entry file type: {}", why); - false - } - }) - .map(|entry| { - let mut apk_path = entry.path(); - apk_path.push(APK_NAME); - Installation { - version: entry.file_name().to_string_lossy().to_string(), - apk_downloaded: apk_path.exists(), - } - }) - }) - .collect(), - Err(why) => { - eprintln!("Failed to read versions dir: {}", why); - Vec::new() - } - } -} diff --git a/alvr/launcher/src/ui.rs b/alvr/launcher/src/ui.rs new file mode 100644 index 0000000000..eec507c385 --- /dev/null +++ b/alvr/launcher/src/ui.rs @@ -0,0 +1,391 @@ +use crate::{actions, InstallationInfo, Progress, ReleaseChannelsInfo, UiMessage, WorkerMessage}; +use eframe::{ + egui::{ + Button, CentralPanel, ComboBox, Context, Frame, Grid, Layout, ProgressBar, ViewportCommand, + Window, + }, + emath::{Align, Align2}, + epaint::Color32, +}; +use std::{ + mem, + sync::mpsc::{Receiver, Sender}, +}; + +enum State { + Default, + Installing(Progress), + Error(String), +} + +#[derive(Default)] +enum Popup { + #[default] + None, + DeleteInstallation(String), + EditVersion(String), + Version(VersionPopup), +} + +#[derive(Clone, PartialEq, Eq)] +enum Version { + Stable(String), + Nightly(String), +} + +impl Version { + fn inner(&self) -> &String { + match self { + Version::Stable(version) => version, + Version::Nightly(version) => version, + } + } +} + +impl ToString for Version { + fn to_string(&self) -> String { + match self { + Version::Stable(version) => { + format!("Stable {}", version) + } + Version::Nightly(version) => { + format!("Nightly {}", version) + } + } + } +} + +struct VersionPopup { + version: Version, +} + +pub struct Launcher { + rx: Receiver, + tx: Sender, + state: State, + release_channels_info: Option, + installations: Vec, + popup: Popup, +} + +impl Launcher { + pub fn new( + cc: &eframe::CreationContext, + rx: Receiver, + tx: Sender, + ) -> Self { + alvr_gui_common::theme::set_theme(&cc.egui_ctx); + + Self { + rx, + tx, + state: State::Default, + release_channels_info: None, + installations: actions::get_installations(), + popup: Popup::None, + } + } + + fn version_popup(&mut self, ctx: &Context, mut version_popup: VersionPopup) -> Popup { + Window::new("Add version") + .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + let release_channels_info = self.release_channels_info.as_ref().unwrap(); + let (channel, version_str, versions): (&str, String, Vec) = + match version_popup.version.clone() { + Version::Stable(version) => ( + "Stable", + version, + release_channels_info + .stable + .iter() + .map(|release| Version::Stable(release.tag.clone())) + .collect(), + ), + Version::Nightly(version) => ( + "Nightly", + version, + release_channels_info + .nightly + .iter() + .map(|release| Version::Nightly(release.tag.clone())) + .collect(), + ), + }; + Grid::new("add-version-grid").num_columns(2).show(ui, |ui| { + ui.label("Channel"); + + ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + ComboBox::from_id_source("channel") + .selected_text(channel) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut version_popup.version, + Version::Stable( + self.release_channels_info.as_ref().unwrap().stable[0] + .tag + .clone(), + ), + "Stable", + ); + ui.selectable_value( + &mut version_popup.version, + Version::Nightly( + self.release_channels_info.as_ref().unwrap().nightly[0] + .tag + .clone(), + ), + "Nightly", + ); + }) + }); + ui.end_row(); + + ui.label("Version"); + ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + ComboBox::from_id_source("version") + .selected_text(version_str) + .show_ui(ui, |ui| { + for version in versions { + ui.selectable_value( + &mut version_popup.version, + version.clone(), + version.inner(), + ); + } + }) + }); + ui.end_row(); + }); + ui.columns(2, |ui| { + if ui[0].button("Cancel").clicked() { + return Popup::None; + } + + if ui[1].button("Install").clicked() { + self.tx + .send(UiMessage::Install(match &version_popup.version { + Version::Stable(version) => release_channels_info + .stable + .iter() + .find(|release| release.tag == *version) + .unwrap() + .clone(), + Version::Nightly(version) => release_channels_info + .nightly + .iter() + .find(|release| release.tag == *version) + .unwrap() + .clone(), + })) + .unwrap(); + return Popup::None; + } + + Popup::Version(version_popup) + }) + }) + .unwrap() + .inner + .unwrap() + } + + fn edit_popup(&self, ctx: &Context, version: String) -> Popup { + Window::new("Edit version") + .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.with_layout(Layout::top_down_justified(Align::Center), |ui| { + if ui.button("Delete version").clicked() { + return Popup::DeleteInstallation(version); + }; + if ui.button("Close").clicked() { + return Popup::None; + } + + Popup::EditVersion(version) + }) + .inner + }) + .unwrap() + .inner + .unwrap() + } + + fn delete_popup(&mut self, ctx: &Context, version: String) -> Popup { + Window::new("Are you sure?") + .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.with_layout(Layout::top_down(Align::Center), |ui| { + ui.label(format!("This will permanently delete version {}", version)); + }); + ui.columns(2, |ui| { + if ui[0].button("Cancel").clicked() { + return Popup::None; + } + if ui[1].button("Delete version").clicked() { + if let Err(e) = actions::delete_installation(&version) { + self.state = State::Error(format!("Failed to delete version: {e}")); + } + + self.installations = actions::get_installations(); + + return Popup::None; + } + Popup::DeleteInstallation(version) + }) + }) + .unwrap() + .inner + .unwrap() + } +} + +impl eframe::App for Launcher { + fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { + while let Ok(msg) = self.rx.try_recv() { + match msg { + WorkerMessage::ReleaseChannelsInfo(data) => self.release_channels_info = Some(data), + WorkerMessage::ProgressUpdate(progress) => { + self.state = State::Installing(progress); + } + WorkerMessage::Done => { + // Refresh installations + self.installations = actions::get_installations(); + self.state = State::Default; + } + WorkerMessage::Error(e) => self.state = State::Error(e), + } + } + + CentralPanel::default().show(ctx, |ui| match &self.state { + State::Default => { + ui.with_layout(Layout::top_down(Align::Center), |ui| { + ui.label("ALVR Launcher"); + ui.label(match &self.release_channels_info { + Some(data) => format!("Latest stable release: {}", data.stable[0].tag), + None => "Fetching latest release...".to_string(), + }); + + for installation in &self.installations { + let path = actions::installations_dir().join(&installation.version); + + Frame::group(ui.style()) + .fill(alvr_gui_common::theme::SECTION_BG) + .show(ui, |ui| { + Grid::new(&installation.version) + .num_columns(2) + .show(ui, |ui| { + ui.label(&installation.version); + ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + if ui.button("Launch").clicked() { + match actions::launch_dashboard( + &installation.version, + ) { + Ok(()) => { + self.tx.send(UiMessage::Quit).unwrap(); + ctx.send_viewport_cmd( + ViewportCommand::Close, + ); + } + Err(e) => { + self.state = State::Error(e.to_string()); + } + } + } + + if ui + .add_enabled( + self.release_channels_info.is_some() + && actions::get_release( + self.release_channels_info + .as_ref() + .unwrap(), + &installation.version, + ) + .is_some() + || installation.is_apk_downloaded, + Button::new("Install APK"), + ) + .clicked() + { + self.tx + .send(UiMessage::InstallClient( + actions::get_release( + self.release_channels_info + .as_ref() + .unwrap(), + &installation.version, + ) + .unwrap() + .clone(), + )) + .unwrap(); + }; + if ui.button("Open directory").clicked() { + open::that_in_background(path); + } + if ui.button("Edit").clicked() { + self.popup = Popup::EditVersion( + installation.version.clone(), + ); + } + }) + }) + }); + } + + if ui + .add_enabled( + self.release_channels_info.is_some(), + Button::new("Add version"), + ) + .clicked() + { + self.popup = Popup::Version(VersionPopup { + version: Version::Stable( + self.release_channels_info.as_ref().unwrap().stable[0] + .tag + .clone(), + ), + }); + } + + let popup = match mem::take(&mut self.popup) { + Popup::Version(version_popup) => self.version_popup(ctx, version_popup), + Popup::EditVersion(version) => self.edit_popup(ctx, version), + Popup::DeleteInstallation(version) => self.delete_popup(ctx, version), + Popup::None => Popup::None, + }; + self.popup = popup; + }); + } + State::Installing(progress) => { + ui.with_layout(Layout::top_down(Align::Center), |ui| { + ui.label(&progress.msg); + ui.add(ProgressBar::new(progress.progress).animate(true)); + }); + } + State::Error(e) => { + let e = e.clone(); // Avoid borrowing issues with the closure for the layout + ui.with_layout(Layout::top_down(Align::Center), |ui| { + ui.colored_label(Color32::LIGHT_RED, "Error!"); + ui.label(e); + + if ui.button("Close").clicked() { + self.state = State::Default; + } + }); + } + }); + + if ctx.input(|i| i.viewport().close_requested()) { + self.tx.send(UiMessage::Quit).unwrap(); + } + } +} From 5b4d21f5c06c76189ce107eb96d0529306636b5a Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 17:59:29 +0800 Subject: [PATCH 2/6] Fix linux dashboard path; more refactoring --- alvr/launcher/src/actions.rs | 142 +++++++++++++------------- alvr/launcher/src/main.rs | 21 ++-- alvr/launcher/src/ui.rs | 190 +++++++++++++++++++---------------- 3 files changed, 187 insertions(+), 166 deletions(-) diff --git a/alvr/launcher/src/actions.rs b/alvr/launcher/src/actions.rs index 5ed030f3b5..6724f74224 100644 --- a/alvr/launcher/src/actions.rs +++ b/alvr/launcher/src/actions.rs @@ -1,7 +1,7 @@ use crate::{ InstallationInfo, Progress, ReleaseChannelsInfo, ReleaseInfo, UiMessage, WorkerMessage, }; -use alvr_common::anyhow::Result; +use alvr_common::{anyhow::Result, ToAny}; use anyhow::bail; use flate2::read::GzDecoder; use futures_util::StreamExt; @@ -19,9 +19,12 @@ const ADB_EXECUTABLE: &str = "adb"; #[cfg(windows)] const ADB_EXECUTABLE: &str = "adb.exe"; -#[cfg(not(windows))] +#[cfg(target_os = "linux")] const PLATFORM_TOOLS_DL_LINK: &str = "https://dl.google.com/android/repository/platform-tools-latest-linux.zip"; +#[cfg(target_os = "macos")] +const PLATFORM_TOOLS_DL_LINK: &str = + "https://dl.google.com/android/repository/platform-tools-latest-macos.zip"; #[cfg(windows)] const PLATFORM_TOOLS_DL_LINK: &str = "https://dl.google.com/android/repository/platform-tools-latest-windows.zip"; @@ -32,7 +35,10 @@ pub fn installations_dir() -> PathBuf { data_dir().join("installations") } -pub fn worker(rx: Receiver, tx: Sender) { +pub fn worker( + ui_message_receiver: Receiver, + worker_message_sender: Sender, +) { tokio::runtime::Runtime::new() .expect("Failed to create tokio runtime") .block_on(async { @@ -42,35 +48,34 @@ pub fn worker(rx: Receiver, tx: Sender) { .unwrap(); let version_data = match fetch_all_releases(&client).await { Ok(data) => data, - Err(why) => { - eprintln!("Error fetching version data: {}", why); + Err(e) => { + eprintln!("Error fetching version data: {}", e); return; } }; - tx.send(WorkerMessage::ReleaseChannelsInfo(version_data)) + worker_message_sender + .send(WorkerMessage::ReleaseChannelsInfo(version_data)) .unwrap(); loop { - let message = match rx.recv() { - Ok(message) => message, - Err(e) => { - eprintln!("receiver error {e}"); - break; - } + let Ok(message) = ui_message_receiver.recv() else { + return; }; - match message { + let res = match message { UiMessage::Quit => return, - UiMessage::Install(release) => match install(&tx, release, &client).await { - Ok(_) => tx.send(WorkerMessage::Done).unwrap(), - Err(why) => tx.send(WorkerMessage::Error(why.to_string())).unwrap(), - }, - UiMessage::InstallClient(release) => { - match install_apk(&tx, release, &client).await { - Ok(_) => tx.send(WorkerMessage::Done).unwrap(), - Err(why) => tx.send(WorkerMessage::Error(why.to_string())).unwrap(), - } + UiMessage::InstallServer(release) => { + install_server(&worker_message_sender, release, &client).await + } + UiMessage::InstallClient(release_info) => { + install_apk(&worker_message_sender, release_info, &client).await } + }; + match res { + Ok(()) => worker_message_sender.send(WorkerMessage::Done).unwrap(), + Err(e) => worker_message_sender + .send(WorkerMessage::Error(e.to_string())) + .unwrap(), } } }); @@ -95,18 +100,18 @@ async fn fetch_releases_for_repo(client: &reqwest::Client, url: &str) -> Result< let response: serde_json::Value = client.get(url).send().await?.json().await?; let mut releases = Vec::new(); - for value in response.as_array().unwrap() { + for value in response.as_array().to_any()? { releases.push(ReleaseInfo { - tag: value["tag_name"].as_str().unwrap().to_string(), + version: value["tag_name"].as_str().to_any()?.to_string(), assets: value["assets"] .as_array() - .unwrap() + .to_any()? .iter() - .map(|value| { - ( - value["name"].as_str().unwrap().to_string(), - value["browser_download_url"].as_str().unwrap().to_string(), - ) + .filter_map(|value| { + Some(( + value["name"].as_str()?.to_string(), + value["browser_download_url"].as_str()?.to_string(), + )) }) .collect(), }) @@ -114,17 +119,20 @@ async fn fetch_releases_for_repo(client: &reqwest::Client, url: &str) -> Result< Ok(releases) } -pub fn get_release(release_info: &ReleaseChannelsInfo, version: &str) -> Option { - release_info +pub fn get_release( + release_channels_info: &ReleaseChannelsInfo, + version: &str, +) -> Option { + release_channels_info .stable .iter() - .find(|release| release.tag == version) + .find(|release| release.version == version) .cloned() .or_else(|| { - release_info + release_channels_info .nightly .iter() - .find(|release| release.tag == version) + .find(|release| release.version == version) .cloned() }) } @@ -135,12 +143,11 @@ async fn install_apk( client: &reqwest::Client, ) -> anyhow::Result<()> { worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { - msg: "Starting install".to_string(), + message: "Starting install".to_string(), progress: 0.0, - })) - .unwrap(); + }))?; - let installation_dir = installations_dir().join(&release.tag); + let installation_dir = installations_dir().join(&release.version); let apk_path = installation_dir.clone().join(APK_NAME); @@ -161,10 +168,9 @@ async fn install_apk( } worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { - msg: "Installing APK".to_string(), + message: "Installing APK".to_string(), progress: 0.0, - })) - .unwrap(); + }))?; let res = match Command::new(ADB_EXECUTABLE) .arg("install") @@ -191,10 +197,9 @@ async fn install_apk( } worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { - msg: "Installing APK".to_string(), + message: "Installing APK".to_string(), progress: 0.0, - })) - .unwrap(); + }))?; Command::new(adb_path) .arg("install") @@ -215,7 +220,7 @@ async fn install_apk( async fn download( worker_message_sender: &Sender, - msg: &str, + message: &str, url: &str, client: &reqwest::Client, ) -> anyhow::Result> { @@ -227,34 +232,31 @@ async fn download( buffer.extend(item?); match total_size { - Some(total_size) => worker_message_sender - .send(WorkerMessage::ProgressUpdate(Progress { - msg: msg.to_string(), + Some(total_size) => { + worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + message: message.to_string(), progress: buffer.len() as f32 / total_size as f32, - })) - .unwrap(), - None => worker_message_sender - .send(WorkerMessage::ProgressUpdate(Progress { - msg: format!("{} (Progress unavailable)", msg), - progress: 0.5, - })) - .unwrap(), + }))? + } + None => worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + message: format!("{} (Progress unavailable)", message), + progress: 0.5, + }))?, } } Ok(buffer) } -async fn install( - tx: &Sender, +async fn install_server( + worker_message_sender: &Sender, release: ReleaseInfo, client: &reqwest::Client, ) -> anyhow::Result<()> { - tx.send(WorkerMessage::ProgressUpdate(Progress { - msg: "Starting install".to_string(), + worker_message_sender.send(WorkerMessage::ProgressUpdate(Progress { + message: "Starting install".to_string(), progress: 0.0, - })) - .unwrap(); + }))?; let file_name = if cfg!(windows) { "alvr_streamer_windows.zip" @@ -267,9 +269,9 @@ async fn install( .get(file_name) .ok_or(anyhow::anyhow!("Unable to determine download link"))?; - let buffer = download(tx, "Downloading Streamer", url, client).await?; + let buffer = download(worker_message_sender, "Downloading Streamer", url, client).await?; - let installation_dir = installations_dir().join(&release.tag); + let installation_dir = installations_dir().join(&release.version); fs::create_dir_all(&installation_dir)?; @@ -303,8 +305,8 @@ pub fn get_installations() -> Vec { .ok() .filter(|entry| match entry.file_type() { Ok(file_type) => file_type.is_dir(), - Err(why) => { - eprintln!("Failed to read entry file type: {}", why); + Err(e) => { + eprintln!("Failed to read entry file type: {}", e); false } }) @@ -318,8 +320,8 @@ pub fn get_installations() -> Vec { }) }) .collect(), - Err(why) => { - eprintln!("Failed to read versions dir: {}", why); + Err(e) => { + eprintln!("Failed to read versions dir: {}", e); Vec::new() } } @@ -331,7 +333,7 @@ pub fn launch_dashboard(version: &str) -> Result<()> { let dashboard_path = if cfg!(windows) { installation_dir.join("ALVR Dashboard.exe") } else if cfg!(target_os = "linux") { - installation_dir.join("bin/alvr_alvr_dashboard") + installation_dir.join("alvr_streamer_linux/bin/alvr_dashboard") } else { bail!("Unsupported platform") }; diff --git a/alvr/launcher/src/main.rs b/alvr/launcher/src/main.rs index f80126d782..5ce50ea47f 100644 --- a/alvr/launcher/src/main.rs +++ b/alvr/launcher/src/main.rs @@ -6,12 +6,12 @@ use ui::Launcher; #[derive(Clone)] struct ReleaseInfo { - tag: String, + version: String, assets: BTreeMap, } struct Progress { - msg: String, + message: String, progress: f32, } @@ -23,7 +23,7 @@ enum WorkerMessage { } enum UiMessage { - Install(ReleaseInfo), + InstallServer(ReleaseInfo), InstallClient(ReleaseInfo), Quit, } @@ -39,17 +39,24 @@ struct InstallationInfo { } fn main() { - let (worker_tx, gui_rx) = mpsc::channel::(); - let (gui_tx, worker_rx) = mpsc::channel::(); + let (worker_message_sender, worker_message_receiver) = mpsc::channel::(); + let (ui_message_sender, ui_message_receiver) = mpsc::channel::(); - let worker_handle = thread::spawn(|| actions::worker(worker_rx, worker_tx)); + let worker_handle = + thread::spawn(|| actions::worker(ui_message_receiver, worker_message_sender)); eframe::run_native( "ALVR Launcher", eframe::NativeOptions { ..Default::default() }, - Box::new(move |cc| Box::new(Launcher::new(cc, gui_rx, gui_tx))), + Box::new(move |cc| { + Box::new(Launcher::new( + cc, + worker_message_receiver, + ui_message_sender, + )) + }), ) .expect("Failed to run eframe"); diff --git a/alvr/launcher/src/ui.rs b/alvr/launcher/src/ui.rs index eec507c385..6b09385e32 100644 --- a/alvr/launcher/src/ui.rs +++ b/alvr/launcher/src/ui.rs @@ -28,31 +28,15 @@ enum Popup { } #[derive(Clone, PartialEq, Eq)] -enum Version { - Stable(String), - Nightly(String), +enum ReleaseChannelType { + Stable, + Nightly, } -impl Version { - fn inner(&self) -> &String { - match self { - Version::Stable(version) => version, - Version::Nightly(version) => version, - } - } -} - -impl ToString for Version { - fn to_string(&self) -> String { - match self { - Version::Stable(version) => { - format!("Stable {}", version) - } - Version::Nightly(version) => { - format!("Nightly {}", version) - } - } - } +#[derive(Clone, PartialEq, Eq)] +struct Version { + version: String, + release_channel: ReleaseChannelType, } struct VersionPopup { @@ -60,8 +44,8 @@ struct VersionPopup { } pub struct Launcher { - rx: Receiver, - tx: Sender, + worker_message_receiver: Receiver, + ui_message_sender: Sender, state: State, release_channels_info: Option, installations: Vec, @@ -71,14 +55,14 @@ pub struct Launcher { impl Launcher { pub fn new( cc: &eframe::CreationContext, - rx: Receiver, - tx: Sender, + worker_message_receiver: Receiver, + ui_message_sender: Sender, ) -> Self { alvr_gui_common::theme::set_theme(&cc.egui_ctx); Self { - rx, - tx, + worker_message_receiver, + ui_message_sender, state: State::Default, release_channels_info: None, installations: actions::get_installations(), @@ -92,25 +76,32 @@ impl Launcher { .resizable(false) .collapsible(false) .show(ctx, |ui| { + // Safety: unwrap is safe because the "Add release" button is available after populating the release_channels_info. let release_channels_info = self.release_channels_info.as_ref().unwrap(); let (channel, version_str, versions): (&str, String, Vec) = - match version_popup.version.clone() { - Version::Stable(version) => ( + match version_popup.version.release_channel.clone() { + ReleaseChannelType::Stable => ( "Stable", - version, + version_popup.version.version.clone(), release_channels_info .stable .iter() - .map(|release| Version::Stable(release.tag.clone())) + .map(|release| Version { + version: release.version.clone(), + release_channel: ReleaseChannelType::Stable, + }) .collect(), ), - Version::Nightly(version) => ( + ReleaseChannelType::Nightly => ( "Nightly", - version, + version_popup.version.version.clone(), release_channels_info .nightly .iter() - .map(|release| Version::Nightly(release.tag.clone())) + .map(|release| Version { + version: release.version.clone(), + release_channel: ReleaseChannelType::Nightly, + }) .collect(), ), }; @@ -123,20 +114,30 @@ impl Launcher { .show_ui(ui, |ui| { ui.selectable_value( &mut version_popup.version, - Version::Stable( - self.release_channels_info.as_ref().unwrap().stable[0] - .tag + Version { + version: self + .release_channels_info + .as_ref() + .unwrap() + .stable[0] + .version .clone(), - ), + release_channel: ReleaseChannelType::Stable, + }, "Stable", ); ui.selectable_value( &mut version_popup.version, - Version::Nightly( - self.release_channels_info.as_ref().unwrap().nightly[0] - .tag + Version { + version: self + .release_channels_info + .as_ref() + .unwrap() + .nightly[0] + .version .clone(), - ), + release_channel: ReleaseChannelType::Nightly, + }, "Nightly", ); }) @@ -152,7 +153,7 @@ impl Launcher { ui.selectable_value( &mut version_popup.version, version.clone(), - version.inner(), + version.version, ); } }) @@ -165,21 +166,27 @@ impl Launcher { } if ui[1].button("Install").clicked() { - self.tx - .send(UiMessage::Install(match &version_popup.version { - Version::Stable(version) => release_channels_info - .stable - .iter() - .find(|release| release.tag == *version) - .unwrap() - .clone(), - Version::Nightly(version) => release_channels_info - .nightly - .iter() - .find(|release| release.tag == *version) - .unwrap() - .clone(), - })) + self.ui_message_sender + .send(UiMessage::InstallServer( + match &version_popup.version.release_channel { + ReleaseChannelType::Stable => release_channels_info + .stable + .iter() + .find(|release| { + release.version == version_popup.version.version + }) + .unwrap() + .clone(), + ReleaseChannelType::Nightly => release_channels_info + .nightly + .iter() + .find(|release| { + release.version == version_popup.version.version + }) + .unwrap() + .clone(), + }, + )) .unwrap(); return Popup::None; } @@ -248,7 +255,7 @@ impl Launcher { impl eframe::App for Launcher { fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { - while let Ok(msg) = self.rx.try_recv() { + while let Ok(msg) = self.worker_message_receiver.try_recv() { match msg { WorkerMessage::ReleaseChannelsInfo(data) => self.release_channels_info = Some(data), WorkerMessage::ProgressUpdate(progress) => { @@ -268,7 +275,7 @@ impl eframe::App for Launcher { ui.with_layout(Layout::top_down(Align::Center), |ui| { ui.label("ALVR Launcher"); ui.label(match &self.release_channels_info { - Some(data) => format!("Latest stable release: {}", data.stable[0].tag), + Some(data) => format!("Latest stable release: {}", data.stable[0].version), None => "Fetching latest release...".to_string(), }); @@ -288,7 +295,9 @@ impl eframe::App for Launcher { &installation.version, ) { Ok(()) => { - self.tx.send(UiMessage::Quit).unwrap(); + self.ui_message_sender + .send(UiMessage::Quit) + .unwrap(); ctx.send_viewport_cmd( ViewportCommand::Close, ); @@ -299,34 +308,36 @@ impl eframe::App for Launcher { } } + let release_info = self + .release_channels_info + .as_ref() + .and_then(|info| { + actions::get_release( + info, + &installation.version, + ) + }); if ui .add_enabled( - self.release_channels_info.is_some() - && actions::get_release( - self.release_channels_info - .as_ref() - .unwrap(), - &installation.version, - ) - .is_some() + release_info.is_some() || installation.is_apk_downloaded, Button::new("Install APK"), ) .clicked() { - self.tx - .send(UiMessage::InstallClient( - actions::get_release( - self.release_channels_info - .as_ref() - .unwrap(), - &installation.version, - ) - .unwrap() - .clone(), - )) - .unwrap(); + if let Some(release_info) = release_info { + self.ui_message_sender + .send(UiMessage::InstallClient( + release_info, + )) + .unwrap(); + } else { + self.state = State::Error( + "Failed to get release info".to_string(), + ); + } }; + if ui.button("Open directory").clicked() { open::that_in_background(path); } @@ -348,11 +359,12 @@ impl eframe::App for Launcher { .clicked() { self.popup = Popup::Version(VersionPopup { - version: Version::Stable( - self.release_channels_info.as_ref().unwrap().stable[0] - .tag + version: Version { + version: self.release_channels_info.as_ref().unwrap().stable[0] + .version .clone(), - ), + release_channel: ReleaseChannelType::Stable, + }, }); } @@ -367,7 +379,7 @@ impl eframe::App for Launcher { } State::Installing(progress) => { ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.label(&progress.msg); + ui.label(&progress.message); ui.add(ProgressBar::new(progress.progress).animate(true)); }); } @@ -385,7 +397,7 @@ impl eframe::App for Launcher { }); if ctx.input(|i| i.viewport().close_requested()) { - self.tx.send(UiMessage::Quit).unwrap(); + self.ui_message_sender.send(UiMessage::Quit).unwrap(); } } } From 2cc5b1db1bbaa4ccabd3f9c45127dde110120ace Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 18:07:36 +0800 Subject: [PATCH 3/6] Update egui; update launcher Cargo.toml --- Cargo.lock | 34 +++++++++---------- alvr/client_mock/Cargo.toml | 2 +- alvr/dashboard/Cargo.toml | 2 +- .../components/settings_controls/number.rs | 2 +- alvr/dashboard/src/dashboard/mod.rs | 4 +-- alvr/gui_common/Cargo.toml | 2 +- alvr/launcher/Cargo.toml | 18 +++++++--- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b15f536471..1a93a89c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,7 +321,7 @@ dependencies = [ [[package]] name = "alvr_launcher" -version = "0.1.0" +version = "21.0.0-dev00" dependencies = [ "alvr_common", "alvr_gui_common", @@ -1677,18 +1677,18 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfe80b1890e1a8cdbffc6044d6872e814aaf6011835a2a5e2db0e5c5c4ef4e" +checksum = "fb152797942f72b84496eb2ebeff0060240e0bf55096c4525ffa22dd54722d86" dependencies = [ "bytemuck", ] [[package]] name = "eframe" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c456c1bb6d13bf68b780257484703d750c70a23ff891ba35f4d6e23a4dbdf26f" +checksum = "3bcc8e06df6f0a6cf09a3247ff7e85fdfffc28dda4fe5561e05314bf7618a918" dependencies = [ "bytemuck", "cocoa", @@ -1720,9 +1720,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180f595432a5b615fc6b74afef3955249b86cfea72607b40740a4cd60d5297d0" +checksum = "6d1b8cc14b0b260aa6bd124ef12c8a94f57ffe8e40aa970f3db710c21bb945f3" dependencies = [ "accesskit", "ahash", @@ -1733,9 +1733,9 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f2d75e1e70228e7126f828bac05f9fe0e7ea88e9660c8cebe609bb114c61d4" +checksum = "04ee072f7cbd9e03ae4028db1c4a8677fbb4efc4b62feee6563763a6f041c88d" dependencies = [ "bytemuck", "document-features", @@ -1751,9 +1751,9 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4d44f8d89f70d4480545eb2346b76ea88c3022e9f4706cebc799dbe8b004a2" +checksum = "3733435d6788c760bb98ce4cb1b8b7a2d953a3a7b421656ba8b3e014019be3d0" dependencies = [ "accesskit_winit", "arboard", @@ -1768,9 +1768,9 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e3be8728b4c59493dbfec041c657e6725bdeafdbd49aef3f1dbb9e551fa01" +checksum = "f933e9e64c4d074c78ce71785a5778f648453c2b2a3efd28eea189dac3f19c28" dependencies = [ "bytemuck", "egui", @@ -1790,9 +1790,9 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "emath" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6916301ecf80448f786cdf3eb51d9dbdd831538732229d49119e2d4312eaaf09" +checksum = "555a7cbfcc52c81eb5f8f898190c840fa1c435f67f30b7ef77ce7cf6b7dcd987" dependencies = [ "bytemuck", ] @@ -1877,9 +1877,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b9fdf617dd7f58b0c8e6e9e4a1281f730cde0831d40547da446b2bb76a47af" +checksum = "bd63c37156e949bda80f7e39cc11508bc34840aecf52180567e67cdb2bf1a5fe" dependencies = [ "ab_glyph", "ahash", diff --git a/alvr/client_mock/Cargo.toml b/alvr/client_mock/Cargo.toml index b7fb9d95ce..20d2b7eacf 100644 --- a/alvr/client_mock/Cargo.toml +++ b/alvr/client_mock/Cargo.toml @@ -12,6 +12,6 @@ alvr_client_core.workspace = true alvr_packets.workspace = true alvr_session.workspace = true -eframe = "0.26" +eframe = "0.27" env_logger = "0.11" rand = "0.8" diff --git a/alvr/dashboard/Cargo.toml b/alvr/dashboard/Cargo.toml index a569d86dd5..dbdc41c9c3 100644 --- a/alvr/dashboard/Cargo.toml +++ b/alvr/dashboard/Cargo.toml @@ -16,7 +16,7 @@ alvr_gui_common.workspace = true bincode = "1" chrono = "0.4" -eframe = "0.26" +eframe = "0.27" env_logger = "0.11" ico = "0.3" rand = "0.8" diff --git a/alvr/dashboard/src/dashboard/components/settings_controls/number.rs b/alvr/dashboard/src/dashboard/components/settings_controls/number.rs index cd96819146..1f0e41f9d9 100644 --- a/alvr/dashboard/src/dashboard/components/settings_controls/number.rs +++ b/alvr/dashboard/src/dashboard/components/settings_controls/number.rs @@ -115,7 +115,7 @@ impl Control { }; if response.drag_started() || response.gained_focus() { self.editing_value_f64 = Some(session_value) - } else if response.drag_released() || response.lost_focus() { + } else if response.drag_stopped() || response.lost_focus() { request = get_request(&self.nesting_info, *editing_value_mut, self.ty); *session_fragment = to_json_value(*editing_value_mut, self.ty); diff --git a/alvr/dashboard/src/dashboard/mod.rs b/alvr/dashboard/src/dashboard/mod.rs index c607595bf1..8fdffe9ba1 100644 --- a/alvr/dashboard/src/dashboard/mod.rs +++ b/alvr/dashboard/src/dashboard/mod.rs @@ -10,9 +10,7 @@ use alvr_events::EventType; use alvr_gui_common::theme; use alvr_packets::{PathValuePair, ServerRequest}; use alvr_session::SessionConfig; -use eframe::egui::{ - self, style::Margin, Align, CentralPanel, Frame, Layout, RichText, SidePanel, Stroke, -}; +use eframe::egui::{self, Align, CentralPanel, Frame, Layout, Margin, RichText, SidePanel, Stroke}; use std::{ collections::BTreeMap, ops::Deref, diff --git a/alvr/gui_common/Cargo.toml b/alvr/gui_common/Cargo.toml index 38ea17b0c2..6f9cd93488 100644 --- a/alvr/gui_common/Cargo.toml +++ b/alvr/gui_common/Cargo.toml @@ -9,4 +9,4 @@ license.workspace = true [dependencies] alvr_common.workspace = true -egui = "0.26" +egui = "0.27" diff --git a/alvr/launcher/Cargo.toml b/alvr/launcher/Cargo.toml index 67239f8ef5..163d20dcea 100644 --- a/alvr/launcher/Cargo.toml +++ b/alvr/launcher/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "alvr_launcher" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,11 +13,16 @@ alvr_common.workspace = true alvr_gui_common.workspace = true anyhow = "1" -eframe = "0.26" -flate2 = "1" +eframe = "0.27" +flate2 = "1.0.18" futures-util = "0.3.28" open = "5" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json", "http2"] } +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", + "stream", + "json", + "http2", +] } serde_json = "1" tar = "0.4" tokio = { version = "1", features = ["rt-multi-thread"] } From fdbbc42f5b061bd1463ae0c9e301b4cb1f5e2219 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 18:12:21 +0800 Subject: [PATCH 4/6] Reorder version entry buttons --- alvr/launcher/src/ui.rs | 47 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/alvr/launcher/src/ui.rs b/alvr/launcher/src/ui.rs index 6b09385e32..00221dd61a 100644 --- a/alvr/launcher/src/ui.rs +++ b/alvr/launcher/src/ui.rs @@ -290,22 +290,14 @@ impl eframe::App for Launcher { .show(ui, |ui| { ui.label(&installation.version); ui.with_layout(Layout::right_to_left(Align::Min), |ui| { - if ui.button("Launch").clicked() { - match actions::launch_dashboard( - &installation.version, - ) { - Ok(()) => { - self.ui_message_sender - .send(UiMessage::Quit) - .unwrap(); - ctx.send_viewport_cmd( - ViewportCommand::Close, - ); - } - Err(e) => { - self.state = State::Error(e.to_string()); - } - } + if ui.button("Edit").clicked() { + self.popup = Popup::EditVersion( + installation.version.clone(), + ); + } + + if ui.button("Open directory").clicked() { + open::that_in_background(path); } let release_info = self @@ -338,13 +330,22 @@ impl eframe::App for Launcher { } }; - if ui.button("Open directory").clicked() { - open::that_in_background(path); - } - if ui.button("Edit").clicked() { - self.popup = Popup::EditVersion( - installation.version.clone(), - ); + if ui.button("Launch").clicked() { + match actions::launch_dashboard( + &installation.version, + ) { + Ok(()) => { + self.ui_message_sender + .send(UiMessage::Quit) + .unwrap(); + ctx.send_viewport_cmd( + ViewportCommand::Close, + ); + } + Err(e) => { + self.state = State::Error(e.to_string()); + } + } } }) }) From 631c57e3f7b979f61cbea0b7ea218bf0cf45e3ad Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 18:29:05 +0800 Subject: [PATCH 5/6] Allow client downgrades --- alvr/launcher/src/actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alvr/launcher/src/actions.rs b/alvr/launcher/src/actions.rs index 6724f74224..295e507d69 100644 --- a/alvr/launcher/src/actions.rs +++ b/alvr/launcher/src/actions.rs @@ -174,7 +174,7 @@ async fn install_apk( let res = match Command::new(ADB_EXECUTABLE) .arg("install") - .arg("-r") + .arg("-d") .arg(&apk_path) .output() { From 42c1213bfe7b2a59d04bad610c4c7653a445edf6 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Tue, 2 Apr 2024 19:24:46 +0800 Subject: [PATCH 6/6] Add icon and tweak UI --- .gitignore | 2 +- Cargo.lock | 2 ++ alvr/launcher/Cargo.toml | 6 ++++-- alvr/launcher/build.rs | 9 +++++++++ alvr/launcher/src/main.rs | 17 ++++++++++++++++- alvr/launcher/src/ui.rs | 7 ++++--- 6 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 alvr/launcher/build.rs diff --git a/.gitignore b/.gitignore index e8c1d036cb..2596e92804 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,4 @@ node_modules # flatpak .flatpak* -ALVR-Launcher \ No newline at end of file +ALVR-Launcher diff --git a/Cargo.lock b/Cargo.lock index 1a93a89c21..c550098e44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,11 +329,13 @@ dependencies = [ "eframe", "flate2", "futures-util", + "ico", "open", "reqwest 0.12.0", "serde_json", "tar", "tokio", + "winres", "zip", ] diff --git a/alvr/launcher/Cargo.toml b/alvr/launcher/Cargo.toml index 163d20dcea..479151d705 100644 --- a/alvr/launcher/Cargo.toml +++ b/alvr/launcher/Cargo.toml @@ -6,8 +6,6 @@ rust-version.workspace = true authors.workspace = true license.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] alvr_common.workspace = true alvr_gui_common.workspace = true @@ -16,6 +14,7 @@ anyhow = "1" eframe = "0.27" flate2 = "1.0.18" futures-util = "0.3.28" +ico = "0.3" open = "5" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", @@ -27,3 +26,6 @@ serde_json = "1" tar = "0.4" tokio = { version = "1", features = ["rt-multi-thread"] } zip = "0.6" + +[target.'cfg(windows)'.build-dependencies] +winres = "0.1" diff --git a/alvr/launcher/build.rs b/alvr/launcher/build.rs new file mode 100644 index 0000000000..c53ad611cb --- /dev/null +++ b/alvr/launcher/build.rs @@ -0,0 +1,9 @@ +#[cfg(windows)] +fn main() { + let mut resource = winres::WindowsResource::new(); + resource.set_icon("../dashboard/resources/dashboard.ico"); + resource.compile().unwrap(); +} + +#[cfg(not(windows))] +fn main() {} diff --git a/alvr/launcher/src/main.rs b/alvr/launcher/src/main.rs index 5ce50ea47f..fd9aae9a9d 100644 --- a/alvr/launcher/src/main.rs +++ b/alvr/launcher/src/main.rs @@ -1,7 +1,9 @@ mod actions; mod ui; -use std::{collections::BTreeMap, sync::mpsc, thread}; +use eframe::egui::{IconData, ViewportBuilder}; +use ico::IconDir; +use std::{collections::BTreeMap, io::Cursor, sync::mpsc, thread}; use ui::Launcher; #[derive(Clone)] @@ -45,9 +47,22 @@ fn main() { let worker_handle = thread::spawn(|| actions::worker(ui_message_receiver, worker_message_sender)); + let ico = IconDir::read(Cursor::new(include_bytes!( + "../../dashboard/resources/dashboard.ico" + ))) + .unwrap(); + let image = ico.entries().first().unwrap().decode().unwrap(); + eframe::run_native( "ALVR Launcher", eframe::NativeOptions { + viewport: ViewportBuilder::default() + .with_inner_size((700.0, 400.0)) + .with_icon(IconData { + rgba: image.rgba_data().to_owned(), + width: image.width(), + height: image.height(), + }), ..Default::default() }, Box::new(move |cc| { diff --git a/alvr/launcher/src/ui.rs b/alvr/launcher/src/ui.rs index 00221dd61a..4d8b791074 100644 --- a/alvr/launcher/src/ui.rs +++ b/alvr/launcher/src/ui.rs @@ -1,8 +1,8 @@ use crate::{actions, InstallationInfo, Progress, ReleaseChannelsInfo, UiMessage, WorkerMessage}; use eframe::{ egui::{ - Button, CentralPanel, ComboBox, Context, Frame, Grid, Layout, ProgressBar, ViewportCommand, - Window, + self, Button, CentralPanel, ComboBox, Context, Frame, Grid, Layout, ProgressBar, RichText, + ViewportCommand, Window, }, emath::{Align, Align2}, epaint::Color32, @@ -273,7 +273,7 @@ impl eframe::App for Launcher { CentralPanel::default().show(ctx, |ui| match &self.state { State::Default => { ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.label("ALVR Launcher"); + ui.label(RichText::new("ALVR Launcher").size(25.0).strong()); ui.label(match &self.release_channels_info { Some(data) => format!("Latest stable release: {}", data.stable[0].version), None => "Fetching latest release...".to_string(), @@ -284,6 +284,7 @@ impl eframe::App for Launcher { Frame::group(ui.style()) .fill(alvr_gui_common::theme::SECTION_BG) + .inner_margin(egui::vec2(10.0, 5.0)) .show(ui, |ui| { Grid::new(&installation.version) .num_columns(2)