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 @@
-
+
@@ -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 @@
+
Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.
When you are done, enter the token below.
@@ -10,19 +11,28 @@
To be an Early Access member, you must be a Patreon Early Access Subscriber.
+
+
+
+
+
+
+
Login failed!
Double check that your token is correct and try again
@@ -47,6 +57,7 @@
Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier!
Log into your patreon account and choose to back the Early Access reward tier.
+