diff --git a/bootstrap.windows.toml b/bootstrap.windows.toml index 6f30831..0b37c15 100644 --- a/bootstrap.windows.toml +++ b/bootstrap.windows.toml @@ -1,2 +1,2 @@ name = "yuzu" -target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v9.toml" +target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v10.toml" diff --git a/config.windows.v10.toml b/config.windows.v10.toml new file mode 100644 index 0000000..cbf23bd --- /dev/null +++ b/config.windows.v10.toml @@ -0,0 +1,58 @@ +installing_message = "Reminder: yuzu is an experimental emulator. Stuff will break!" +hide_advanced = true + +[authentication] +# Base64 encoded version of the public key for validating the JWT token. Must be in DER format +pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB" +# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure +# "patreonInfo": { "linked": false, "activeSubscription": false } +# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download +auth_url = "https://api.yuzu-emu.org/jwt/installer/" + [authentication.validation] + iss = "citra-core" + aud = "installer" + +[[packages]] +name = "yuzu Early Access" +description = "Preview release with the newest features for the supporters." +icon = "thicc_logo_installer__ea_shadow.png" +requires_authorization = true +# puts a "new" ribbon the package select +is_new = true + [packages.extended_description] + no_action_description = "Thank you for your support!" + # Displayed when the package has no authentication for the user + need_authentication_description = "Click here to sign in with your yuzu account for Early Access" + # Displayed when the package has an authentication, but the user has not linked their account + need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details" + # Displayed when the package has an authentication, but the user has not linked their account + need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details" + # Displayed when the package has an authentication, but the user has not linked their account + need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details" + + [packages.source] + name = "patreon" + match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$" + [packages.source.config] + repo = "earlyaccess" + [[packages.shortcuts]] + name = "yuzu Early Access" + relative_path = "yuzu-windows-msvc-early-access/yuzu.exe" + description = "Launch yuzu Early Access" + + +[[packages]] +name = "yuzu" +description = "Includes frequent updates to yuzu with all the latest reviewed and tested features." +icon = "thicc_logo_installer_shadow.png" +default = true + [packages.source] + name = "github" + match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$" + [packages.source.config] + repo = "yuzu-emu/yuzu-mainline" + [[packages.shortcuts]] + name = "yuzu" + relative_path = "yuzu-windows-msvc/yuzu.exe" + description = "Launch yuzu" + diff --git a/config.windows.v9.toml b/config.windows.v9.toml index 1db13a8..518261a 100644 --- a/config.windows.v9.toml +++ b/config.windows.v9.toml @@ -1,5 +1,6 @@ installing_message = "Reminder: yuzu is an experimental emulator. Stuff will break!" hide_advanced = true +new_tool = "https://github.com/yuzu-emu/liftinstall/releases/download/1.8/yuzu_install.exe" [authentication] # Base64 encoded version of the public key for validating the JWT token. Must be in DER format diff --git a/src/config.rs b/src/config.rs index ef3e91f..5b08d59 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,23 @@ pub struct PackageShortcut { pub name: String, pub relative_path: String, pub description: String, + #[serde(default)] + pub has_desktop_shortcut: bool, +} + +/// Extra description for authentication and authorization state for a package +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct PackageExtendedDescription { + #[serde(default)] + pub no_action_description: Option, + #[serde(default)] + pub need_authentication_description: Option, + #[serde(default)] + pub need_link_description: Option, + #[serde(default)] + pub need_subscription_description: Option, + #[serde(default)] + pub need_reward_tier_description: Option, } /// Describes a overview of a individual package. @@ -32,6 +49,8 @@ pub struct PackageShortcut { pub struct PackageDescription { pub name: String, pub description: String, + #[serde(default)] + pub icon: Option, pub default: Option, pub source: PackageSource, #[serde(default)] @@ -41,13 +60,7 @@ pub struct PackageDescription { #[serde(default)] pub is_new: Option, #[serde(default)] - pub need_authentication_description: Option, - #[serde(default)] - pub need_link_description: Option, - #[serde(default)] - pub need_subscription_description: Option, - #[serde(default)] - pub need_reward_tier_description: Option, + pub extended_description: Option, } /// Configuration for validating the JWT token diff --git a/src/frontend/rest/assets.rs b/src/frontend/rest/assets.rs index 51679c4..679b50a 100644 --- a/src/frontend/rest/assets.rs +++ b/src/frontend/rest/assets.rs @@ -34,7 +34,10 @@ pub fn file_from_string(file_path: &str) -> Option<(String, &'static [u8])> { file_path, "/index.html", "/favicon.ico", - "/img/logo.png", + "/img/light_mode_installer_logo.png", + "/img/dark_mode_installer_logo.png", + "/thicc_logo_installer__ea_shadow.png", + "/thicc_logo_installer_shadow.png", "/img/how-to-open.png", "/css/app.css", "/css/chunk-vendors.css", diff --git a/src/frontend/rest/services/authentication.rs b/src/frontend/rest/services/authentication.rs index 4364640..46e0efd 100644 --- a/src/frontend/rest/services/authentication.rs +++ b/src/frontend/rest/services/authentication.rs @@ -138,7 +138,7 @@ pub fn validate_token( }; // Configure validation for audience and issuer if the configuration provides it - let validation = match validation { + let mut validation = match validation { Some(v) => { let mut valid = Validation::new(Algorithm::RS256); valid.iss = v.iss; @@ -149,7 +149,8 @@ pub fn validate_token( } None => Validation::default(), }; - + validation.validate_exp = false; + validation.validate_nbf = false; // Verify the JWT token decode::(&body, pub_key.as_slice(), &validation) .map(|tok| tok.claims) diff --git a/src/frontend/rest/services/exit.rs b/src/frontend/rest/services/exit.rs index d99b5b1..2ddeeb2 100644 --- a/src/frontend/rest/services/exit.rs +++ b/src/frontend/rest/services/exit.rs @@ -2,18 +2,15 @@ //! //! The /api/exit closes down the application. -use frontend::rest::services::default_future; -use frontend::rest::services::Future; -use frontend::rest::services::Request; -use frontend::rest::services::Response; -use frontend::rest::services::WebService; +use frontend::rest::services::Future as InternalFuture; +use frontend::rest::services::{default_future, Request, Response, WebService}; use hyper::header::ContentType; use hyper::StatusCode; use std::process::exit; -pub fn handle(service: &WebService, _req: Request) -> Future { +pub fn handle(service: &WebService, _req: Request) -> InternalFuture { match service.get_framework_write().shutdown() { Ok(_) => { exit(0); diff --git a/src/frontend/rest/services/install.rs b/src/frontend/rest/services/install.rs index 4b42bbf..f2cbeaa 100644 --- a/src/frontend/rest/services/install.rs +++ b/src/frontend/rest/services/install.rs @@ -28,12 +28,17 @@ pub fn handle(service: &WebService, req: Request) -> Future { let mut to_install = Vec::new(); let mut path: Option = None; + let mut install_desktop_shortcut= false; // Transform results into just an array of stuff to install for (key, value) in &results { if key == "path" { path = Some(value.to_owned()); continue; + } else if key == "installDesktopShortcut" { + info!("Found installDesktopShortcut {:?}", value); + install_desktop_shortcut = value == "true"; + continue; } if value == "true" { @@ -55,7 +60,7 @@ pub fn handle(service: &WebService, req: Request) -> Future { framework.set_install_dir(&path); } - if let Err(v) = framework.install(to_install, &sender, new_install) { + if let Err(v) = framework.install(to_install, &sender, new_install, install_desktop_shortcut) { error!("Install error occurred: {:?}", v); if let Err(v) = sender.send(InstallMessage::Error(v)) { error!("Failed to send install error: {:?}", v); diff --git a/src/frontend/rest/services/mod.rs b/src/frontend/rest/services/mod.rs index 390eb3c..db108d4 100644 --- a/src/frontend/rest/services/mod.rs +++ b/src/frontend/rest/services/mod.rs @@ -136,7 +136,7 @@ impl Service for WebService { (Method::Get, "/api/config") => config::handle(self, req), (Method::Get, "/api/dark-mode") => dark_mode::handle(self, req), (Method::Get, "/api/default-path") => default_path::handle(self, req), - (Method::Get, "/api/exit") => exit::handle(self, req), + (Method::Post, "/api/exit") => exit::handle(self, req), (Method::Get, "/api/packages") => packages::handle(self, req), (Method::Get, "/api/installation-status") => installation_status::handle(self, req), (Method::Post, "/api/check-auth") => authentication::handle(self, req), diff --git a/src/installer.rs b/src/installer.rs index d72ba5c..4738b4e 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -179,6 +179,7 @@ impl InstallerFramework { items: Vec, messages: &Sender, fresh_install: bool, + create_desktop_shortcuts: bool, ) -> Result<(), String> { info!( "Framework: Installing {:?} to {:?}", @@ -207,6 +208,7 @@ impl InstallerFramework { items, uninstall_items, fresh_install, + create_desktop_shortcuts, }); let mut tree = DependencyTree::build(task); diff --git a/src/main.rs b/src/main.rs index 2643c3c..b0956a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,11 +65,13 @@ mod tasks; use installer::InstallerFramework; use logging::LoggingErrors; +use std::path::PathBuf; use clap::App; use clap::Arg; use config::BaseAttributes; +use std::process::{Command, Stdio, exit}; static RAW_CONFIG: &'static str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml")); @@ -106,12 +108,12 @@ fn main() { info!("{} installer", app_name); - // Handle self-updating if needed let current_exe = std::env::current_exe().log_expect("Current executable could not be found"); let current_path = current_exe .parent() .log_expect("Parent directory of executable could not be found"); + // Handle self-updating if needed self_update::perform_swap(¤t_exe, matches.value_of("swap")); if let Some(new_matches) = self_update::check_args(reinterpret_app, current_path) { matches = new_matches; @@ -119,15 +121,42 @@ fn main() { self_update::cleanup(current_path); // Load in metadata + setup the installer framework + let mut fresh_install = false; let metadata_file = current_path.join("metadata.json"); let mut framework = if metadata_file.exists() { info!("Using pre-existing metadata file: {:?}", metadata_file); InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata") } else { info!("Starting fresh install"); + fresh_install = true; InstallerFramework::new(config) }; + // check for existing installs if we are running as a fresh install + let installed_path = PathBuf::from(framework.get_default_path().unwrap()); + if fresh_install && installed_path.join("metadata.json").exists() { + info!("Existing install detected! Trying to launch this install instead"); + // Generate installer path + let platform_extension = if cfg!(windows) { + "maintenancetool.exe" + } else { + "maintenancetool" + }; + let existing = installed_path.join(platform_extension).into_os_string().into_string(); + if existing.is_ok() { + info!("Launching {:?}", existing); + let success = Command::new(existing.unwrap()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn(); + if success.is_ok() { + exit(0); + } else { + error!("Unable to start existing yuzu maintenance tool. Launching old one instead"); + } + } + } + let is_launcher = if let Some(string) = matches.value_of("launcher") { framework.is_launcher = true; framework.launcher_path = Some(string.to_string()); diff --git a/src/native/interop.cpp b/src/native/interop.cpp index 7121614..37b49c5 100644 --- a/src/native/interop.cpp +++ b/src/native/interop.cpp @@ -43,7 +43,8 @@ extern "C" int saveShortcut( const wchar_t *description, const wchar_t *path, const wchar_t *args, - const wchar_t *workingDir) + const wchar_t *workingDir, + const wchar_t *exePath) { const char *errStr = NULL; HRESULT h; @@ -82,6 +83,9 @@ extern "C" int saveShortcut( shellLink->SetDescription(description); if (path != NULL) shellLink->SetPath(path); + // default to using the first icon in the exe (usually correct) + if (exePath != NULL) + shellLink->SetIconLocation(exePath, 0); if (args != NULL) shellLink->SetArguments(args); if (workingDir != NULL) @@ -95,6 +99,10 @@ extern "C" int saveShortcut( goto err; } + // Notify that a new shortcut was created using the shell api + SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcutPath, NULL); + SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, shortcutPath, NULL); + persistFile->Release(); shellLink->Release(); CoUninitialize(); @@ -160,3 +168,15 @@ extern "C" HRESULT getSystemFolder(wchar_t *out_path) } return result; } + +extern "C" HRESULT getDesktopFolder(wchar_t *out_path) +{ + PWSTR path = NULL; + HRESULT result = SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &path); + if (result == S_OK) + { + wcscpy_s(out_path, MAX_PATH + 1, path); + CoTaskMemFree(path); + } + return result; +} diff --git a/src/native/mod.rs b/src/native/mod.rs index b138870..967b7a9 100644 --- a/src/native/mod.rs +++ b/src/native/mod.rs @@ -30,7 +30,7 @@ mod natives { HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ, }; - use widestring::U16CString; + use widestring::{U16CString}; extern "C" { pub fn saveShortcut( @@ -39,6 +39,7 @@ mod natives { path: *const winapi::ctypes::wchar_t, args: *const winapi::ctypes::wchar_t, workingDir: *const winapi::ctypes::wchar_t, + exePath: *const winapi::ctypes::wchar_t, ) -> ::std::os::raw::c_int; pub fn isDarkThemeActive() -> ::std::os::raw::c_uint; @@ -49,6 +50,28 @@ mod natives { ) -> ::std::os::raw::c_int; pub fn getSystemFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT; + + pub fn getDesktopFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT; + } + + // Needed here for Windows interop + #[allow(unsafe_code)] + pub fn create_desktop_shortcut( + name: &str, + description: &str, + target: &str, + args: &str, + working_dir: &str, + exe_path: &str, + ) -> Result { + let mut cmd_path = [0u16; MAX_PATH + 1]; + let _result = unsafe { getDesktopFolder(cmd_path.as_mut_ptr()) }; + let source_path = format!( + "{}\\{}.lnk", + String::from_utf16_lossy(&cmd_path[..count_u16(&cmd_path)]).as_str(), + name + ); + create_shortcut_inner(source_path, name, description, target, args, working_dir, exe_path) } // Needed here for Windows interop @@ -59,12 +82,27 @@ mod natives { target: &str, args: &str, working_dir: &str, + exe_path: &str, ) -> Result { let source_file = format!( "{}\\Microsoft\\Windows\\Start Menu\\Programs\\{}.lnk", env::var("APPDATA").log_expect("APPDATA is bad, apparently"), name ); + create_shortcut_inner(source_file, name, description, target, args, working_dir, exe_path) + } + + // Needed here for Windows interop + #[allow(unsafe_code)] + fn create_shortcut_inner( + source_file: String, + _name: &str, + description: &str, + target: &str, + args: &str, + working_dir: &str, + exe_path: &str, + ) -> Result { info!("Generating shortcut @ {:?}", source_file); @@ -78,6 +116,8 @@ mod natives { U16CString::from_str(args).log_expect("Error while converting to wchar_t"); let native_working_dir = U16CString::from_str(working_dir).log_expect("Error while converting to wchar_t"); + let native_exe_path = + U16CString::from_str(exe_path).log_expect("Error while converting to wchar_t"); let shortcutResult = unsafe { saveShortcut( @@ -86,6 +126,7 @@ mod natives { native_target.as_ptr(), native_args.as_ptr(), native_working_dir.as_ptr(), + native_exe_path.as_ptr(), ) }; @@ -98,6 +139,17 @@ mod natives { } } + fn count_u16(u16str: &[u16]) -> usize { + let mut pos = 0; + for x in u16str.iter() { + if *x == 0 { + break; + } + pos += 1; + } + pos + } + /// Cleans up the installer pub fn burn_on_exit(app_name: &str) { let current_exe = env::current_exe().log_expect("Current executable could not be found"); @@ -127,20 +179,13 @@ mod natives { let spawn_result: i32 = unsafe { let mut cmd_path = [0u16; MAX_PATH + 1]; let result = getSystemFolder(cmd_path.as_mut_ptr()); - let mut pos = 0; - for x in cmd_path.iter() { - if *x == 0 { - break; - } - pos += 1; - } if result != winapi::shared::winerror::S_OK { return; } spawnDetached( U16CString::from_str( - format!("{}\\cmd.exe", String::from_utf16_lossy(&cmd_path[..pos])).as_str(), + format!("{}\\cmd.exe", String::from_utf16_lossy(&cmd_path[..count_u16(&cmd_path)])).as_str(), ) .log_expect("Unable to convert string to wchar_t") .as_ptr(), diff --git a/src/tasks/check_authorization.rs b/src/tasks/check_authorization.rs index de72a87..575bc76 100644 --- a/src/tasks/check_authorization.rs +++ b/src/tasks/check_authorization.rs @@ -22,7 +22,7 @@ impl Task for CheckAuthorizationTask { ) -> Result { assert_eq!(input.len(), 1); - let params = input.pop().log_expect("Should have input from resolver!"); + let params = input.pop().log_expect("Check Authorization Task should have input from resolver!"); let (version, file) = match params { TaskParamType::File(v, f) => Ok((v, f)), _ => Err("Unexpected TaskParamType in CheckAuthorization: {:?}"), diff --git a/src/tasks/download_pkg.rs b/src/tasks/download_pkg.rs index 205599a..6741568 100644 --- a/src/tasks/download_pkg.rs +++ b/src/tasks/download_pkg.rs @@ -24,7 +24,7 @@ impl Task for DownloadPackageTask { ) -> Result { assert_eq!(input.len(), 1); - let file = input.pop().log_expect("Should have input from resolver!"); + let file = input.pop().log_expect("Download Package Task should have input from resolver!"); let (version, file, auth) = match file { TaskParamType::Authentication(v, f, auth) => (v, f, auth), _ => return Err("Unexpected param type to download package".to_string()), diff --git a/src/tasks/install.rs b/src/tasks/install.rs index 2fcc3fb..88f7fbe 100644 --- a/src/tasks/install.rs +++ b/src/tasks/install.rs @@ -8,6 +8,7 @@ use tasks::install_global_shortcut::InstallGlobalShortcutsTask; use tasks::install_pkg::InstallPackageTask; use tasks::save_executable::SaveExecutableTask; use tasks::uninstall_pkg::UninstallPackageTask; +use tasks::launch_installed_on_exit::LaunchOnExitTask; use tasks::Task; use tasks::TaskDependency; @@ -19,6 +20,7 @@ pub struct InstallTask { pub items: Vec, pub uninstall_items: Vec, pub fresh_install: bool, + pub create_desktop_shortcuts: bool, } impl Task for InstallTask { @@ -60,7 +62,7 @@ impl Task for InstallTask { for item in &self.items { elements.push(TaskDependency::build( TaskOrdering::Pre, - Box::new(InstallPackageTask { name: item.clone() }), + Box::new(InstallPackageTask { name: item.clone(), create_desktop_shortcuts: self.create_desktop_shortcuts }), )); } @@ -74,6 +76,11 @@ impl Task for InstallTask { TaskOrdering::Pre, Box::new(InstallGlobalShortcutsTask {}), )); + + elements.push(TaskDependency::build( + TaskOrdering::Post, + Box::new(LaunchOnExitTask {}) + )) } elements diff --git a/src/tasks/install_desktop_shortcut.rs b/src/tasks/install_desktop_shortcut.rs new file mode 100644 index 0000000..58cb797 --- /dev/null +++ b/src/tasks/install_desktop_shortcut.rs @@ -0,0 +1,113 @@ +//! Generates shortcuts for a specified file. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskDependency; +use tasks::TaskMessage; +use tasks::TaskParamType; + +use config::PackageDescription; + +use logging::LoggingErrors; + +use native::create_desktop_shortcut; + +pub struct InstallDesktopShortcutTask { + pub name: String, + pub should_run: bool, +} + +impl Task for InstallDesktopShortcutTask { + fn execute( + &mut self, + _: Vec, + context: &mut InstallerFramework, + messenger: &dyn Fn(&TaskMessage), + ) -> Result { + if !self.should_run { + return Ok(TaskParamType::GeneratedShortcuts(Vec::new())); + } + + messenger(&TaskMessage::DisplayMessage( + &format!("Generating desktop shortcuts for package {:?}...", self.name), + 0.0, + )); + + let path = context + .install_path + .as_ref() + .log_expect("No install path specified"); + + let starting_dir = path + .to_str() + .log_expect("Unable to build shortcut metadata (startingdir)"); + + let mut installed_files = Vec::new(); + + let mut metadata: Option = None; + for description in &context + .config + .as_ref() + .log_expect("Should have packages by now") + .packages + { + if self.name == description.name { + metadata = Some(description.clone()); + break; + } + } + + let package = match metadata { + Some(v) => v, + None => return Err(format!("Package {:?} could not be found.", self.name)), + }; + + // Generate installer path + let platform_extension = if cfg!(windows) { + "maintenancetool.exe" + } else { + "maintenancetool" + }; + + for shortcut in package.shortcuts { + let tool_path = path.join(platform_extension); + let tool_path = tool_path + .to_str() + .log_expect("Unable to build shortcut metadata (tool)"); + + let exe_path = path.join(shortcut.relative_path); + let exe_path = exe_path + .to_str() + .log_expect("Unable to build shortcut metadata (exe)"); + + installed_files.push(create_desktop_shortcut( + &shortcut.name, + &shortcut.description, + tool_path, + // TODO: Send by list + &format!("--launcher \"{}\"", exe_path), + &starting_dir, + exe_path, + )?); + } + + // Update the installed packages shortcuts information in the database + let packages = &mut context.database.packages; + for pack in packages { + if pack.name == self.name { + pack.shortcuts.append(&mut installed_files); + } + } + + Ok(TaskParamType::GeneratedShortcuts(installed_files)) + } + + fn dependencies(&self) -> Vec { + vec![] + } + + fn name(&self) -> String { + format!("InstallDesktopShortcutTask (for {:?}, should_run = {:?})", self.name, self.should_run) + } +} diff --git a/src/tasks/install_global_shortcut.rs b/src/tasks/install_global_shortcut.rs index 26013e4..58e1597 100644 --- a/src/tasks/install_global_shortcut.rs +++ b/src/tasks/install_global_shortcut.rs @@ -58,6 +58,7 @@ impl Task for InstallGlobalShortcutsTask { // TODO: Send by list "", &starting_dir, + "", )?; if !shortcut_file.is_empty() { diff --git a/src/tasks/install_pkg.rs b/src/tasks/install_pkg.rs index c2f2750..0299a31 100644 --- a/src/tasks/install_pkg.rs +++ b/src/tasks/install_pkg.rs @@ -24,9 +24,11 @@ use archives; use std::fs::OpenOptions; use std::path::Path; +use tasks::install_desktop_shortcut::InstallDesktopShortcutTask; pub struct InstallPackageTask { pub name: String, + pub create_desktop_shortcuts: bool, } impl Task for InstallPackageTask { @@ -66,20 +68,15 @@ impl Task for InstallPackageTask { None => return Err(format!("Package {:?} could not be found.", self.name)), }; - // Grab data from the shortcut generator - let shortcuts = input.pop().log_expect("Should have input from resolver!"); - let shortcuts = match shortcuts { - TaskParamType::GeneratedShortcuts(files) => files, - // If the resolver returned early, we need to unwind + // Ignore input from the uninstaller - no useful information passed + // If a previous task Breaks, then just early exit + match input.pop().log_expect("Install Package Task should have guaranteed output!") { TaskParamType::Break => return Ok(TaskParamType::None), - _ => return Err("Unexpected shortcuts param type to install package".to_string()), + _ => (), }; - // Ignore input from the uninstaller - no useful information passed - input.pop(); - // Grab data from the resolver - let data = input.pop().log_expect("Should have input from resolver!"); + let data = input.pop().log_expect("Install Package Task should have input from resolver!"); let (version, file, data) = match data { TaskParamType::FileContents(version, file, data) => (version, file, data), _ => return Err("Unexpected file contents param type to install package".to_string()), @@ -170,7 +167,7 @@ impl Task for InstallPackageTask { context.database.packages.push(LocalInstallation { name: package.name.to_owned(), version, - shortcuts, + shortcuts: Vec::new(), files: installed_files, }); @@ -195,11 +192,18 @@ impl Task for InstallPackageTask { }), ), TaskDependency::build( - TaskOrdering::Pre, + TaskOrdering::Post, Box::new(InstallShortcutsTask { name: self.name.clone(), }), ), + TaskDependency::build( + TaskOrdering::Post, + Box::new(InstallDesktopShortcutTask { + name: self.name.clone(), + should_run: self.create_desktop_shortcuts + }), + ), TaskDependency::build(TaskOrdering::Post, Box::new(SaveDatabaseTask {})), ] } diff --git a/src/tasks/install_shortcuts.rs b/src/tasks/install_shortcuts.rs index e9c3986..d445dcc 100644 --- a/src/tasks/install_shortcuts.rs +++ b/src/tasks/install_shortcuts.rs @@ -83,9 +83,18 @@ impl Task for InstallShortcutsTask { // TODO: Send by list &format!("--launcher \"{}\"", exe_path), &starting_dir, + exe_path, )?); } + // Update the installed packages shortcuts information in the database + let packages = &mut context.database.packages; + for pack in packages { + if pack.name == self.name { + pack.shortcuts.append(&mut installed_files); + } + } + Ok(TaskParamType::GeneratedShortcuts(installed_files)) } diff --git a/src/tasks/launch_installed_on_exit.rs b/src/tasks/launch_installed_on_exit.rs new file mode 100644 index 0000000..5b67cf5 --- /dev/null +++ b/src/tasks/launch_installed_on_exit.rs @@ -0,0 +1,74 @@ +//! Configures lift to launch the new package on fresh install after its closed +//! If theres multiple launchable packages, then choose the first listed in config +//! If there are multiple shortcuts for the first package, then launch the first. + +use installer::InstallerFramework; + +use tasks::Task; +use tasks::TaskDependency; +use tasks::TaskMessage; +use tasks::TaskParamType; + +use config::PackageDescription; + +use logging::LoggingErrors; + +pub struct LaunchOnExitTask {} + +impl Task for LaunchOnExitTask { + + fn execute( + &mut self, + _: Vec, + context: &mut InstallerFramework, + _: &dyn Fn(&TaskMessage), + ) -> Result { + let pkg = &context.database.packages.first(); + if pkg.is_none() { + return Ok(TaskParamType::None) + } + let pkg = pkg.unwrap(); + + // look up the first shortcut for the first listed package in the database + let path = context + .install_path + .as_ref() + .log_expect("No install path specified"); + + let mut metadata: Option = None; + for description in &context + .config + .as_ref() + .log_expect("Should have packages by now") + .packages + { + if pkg.name == description.name { + metadata = Some(description.clone()); + break; + } + } + + let package_desc = match metadata { + Some(v) => v, + // Package metadata is missing. Dunno what went wrong but we can skip this then + None => return Ok(TaskParamType::None), + }; + + let shortcut = package_desc.shortcuts.first(); + + // copy the path to the actual exe into launcher_path so it'll load it on exit + context.launcher_path = shortcut.map(|s| { + path.join(s.relative_path.clone()).to_str().map(|t| { t.to_string() }).unwrap() + }); + + Ok(TaskParamType::None) + } + + fn dependencies(&self) -> Vec { + vec![] + } + + fn name(&self) -> String { + "LaunchOnExitTask".to_string() + } +} \ No newline at end of file diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index a898f50..85f2c22 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -13,10 +13,12 @@ pub mod check_authorization; pub mod download_pkg; pub mod ensure_only_instance; pub mod install; +pub mod install_desktop_shortcut; pub mod install_dir; pub mod install_global_shortcut; pub mod install_pkg; pub mod install_shortcuts; +pub mod launch_installed_on_exit; pub mod resolver; pub mod save_database; pub mod save_executable; diff --git a/ui/public/thicc_logo_installer__ea_shadow.png b/ui/public/thicc_logo_installer__ea_shadow.png new file mode 100644 index 0000000..369f130 Binary files /dev/null and b/ui/public/thicc_logo_installer__ea_shadow.png differ diff --git a/ui/public/thicc_logo_installer_shadow.png b/ui/public/thicc_logo_installer_shadow.png new file mode 100644 index 0000000..46070d2 Binary files /dev/null and b/ui/public/thicc_logo_installer_shadow.png differ diff --git a/ui/src/App.vue b/ui/src/App.vue index 60e865c..77ff5f3 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -4,7 +4,7 @@
- Application icon + Application icon

@@ -52,6 +52,30 @@ body, div, span, h1, h2, h3, h4, h5, h6 { cursor: default; } +#applicationIcon { + width:0px; height: 0px; + padding: 50px 60% 0px 0px; + background: url("./assets/light_mode_installer_logo.png") left top no-repeat; + background-size: contain; +} + +body.has-background-black-ter #applicationIcon { + background: url("./assets/dark_mode_installer_logo.png") left top no-repeat; + background-size: contain; +} + +.package-icon { + width: 3rem; + height: 3rem; + float: left; + padding-right: 10px; + padding-top: 10px; +} + +.package-description { + overflow: hidden; +} + pre { -webkit-user-select: text; -moz-user-select: text; @@ -124,7 +148,7 @@ pre { } /* Dark mode */ -body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div { +body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div, body.has-background-black-ter section { color: hsl(0, 0%, 96%); } diff --git a/ui/src/assets/dark_mode_installer_logo.png b/ui/src/assets/dark_mode_installer_logo.png new file mode 100644 index 0000000..e3516a7 Binary files /dev/null and b/ui/src/assets/dark_mode_installer_logo.png differ diff --git a/ui/src/assets/how-to-open.png b/ui/src/assets/how-to-open.png index 9db0922..989659b 100644 Binary files a/ui/src/assets/how-to-open.png and b/ui/src/assets/how-to-open.png differ diff --git a/ui/src/assets/light_mode_installer_logo.png b/ui/src/assets/light_mode_installer_logo.png new file mode 100644 index 0000000..239ad7d Binary files /dev/null and b/ui/src/assets/light_mode_installer_logo.png differ diff --git a/ui/src/assets/logo.png b/ui/src/assets/logo.png deleted file mode 100644 index 7c6918d..0000000 Binary files a/ui/src/assets/logo.png and /dev/null differ diff --git a/ui/src/main.js b/ui/src/main.js index d54e051..8e81fbb 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -115,7 +115,8 @@ var app = new Vue({ '\n\nPlease upload the log file (in ' + search_location + ') to ' + 'the ' + app.attrs.name + ' team' }}); - } + }, + {} // pass in nothing to cause `ajax` to post instead of get ) }, check_authentication: function (success, error) { diff --git a/ui/src/router.js b/ui/src/router.js index 8b021d7..5a0a2e9 100644 --- a/ui/src/router.js +++ b/ui/src/router.js @@ -30,7 +30,7 @@ export default new Router({ component: SelectPackages }, { - path: '/install/:kind', + path: '/install/:kind/:desktop_shortcut', name: 'install', component: InstallPackages }, diff --git a/ui/src/views/AuthenticationView.vue b/ui/src/views/AuthenticationView.vue index 4ce7600..125c5e0 100644 --- a/ui/src/views/AuthenticationView.vue +++ b/ui/src/views/AuthenticationView.vue @@ -1,5 +1,6 @@