You've already forked AstralRinth
forked from didirus/AstralRinth
Misc settings (#137)
* Initial bug fixes * fix compile error on non-mac * Fix even more bugs * Fix more * fix more * fix build * fix build * Search fixes * Fix small instance ui * working basic * fix javaw issue * removed zip * working functions * merge fixes * fixed loadintg bar bug * menu fix * wait for settings to sync * safety expanded and for loading bars * swtiching to windows * minimize * default landing page * test link registry * url redirection * fix formatting * .mrpack windows * working mrpack reader * changed to one layer deep * working .mrpack + command handling for both opening and existing process * forge version numbers * working mac opening mrpack * reverted changes * prettier/fmt * missed debug statement * improvements + refactoring * renamed things to fit plugin * fixed bugs * removed println * overrides dont include mrpack * merge * fixes * fixes * fixed deletion * merge errors * force sync before export * removed testing * missed line * removed console log * mac error reverted * incoreclty named helper * additional fixes * added removed merges * fixed mislabled invokes * mac * added to new register method * comments, cleanup * mac clippy change * review changes * minor changes * moved create pack * removed playground compilation bug * fixed linux bug; other add ons * fixed review commets * cicd fix * mistaken import for prod * cicd fix --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
162
Cargo.lock
generated
162
Cargo.lock
generated
@@ -313,22 +313,6 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
|
||||
|
||||
[[package]]
|
||||
name = "attohttpc"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"http",
|
||||
"log",
|
||||
"native-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -501,6 +485,9 @@ name = "bytes"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
@@ -900,12 +887,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cty"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.94"
|
||||
@@ -2144,6 +2125,19 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rustc_version",
|
||||
"to_method",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.9"
|
||||
@@ -2839,6 +2833,28 @@ dependencies = [
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845"
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.3.0-beta.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef3a6024722b4230242a53e5b5759ce117548983696b8e4b7bc2fd1f8fce621e"
|
||||
dependencies = [
|
||||
"objc-sys",
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "2.0.0-pre.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f8f7297b786454a87e392631e2b2754ed59a7b413effa8521225d93f46b2192"
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
@@ -3443,12 +3459,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
|
||||
dependencies = [
|
||||
"cty",
|
||||
]
|
||||
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
@@ -3894,11 +3907,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "2.3.3"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
|
||||
checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap",
|
||||
@@ -3910,9 +3923,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "2.3.3"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
|
||||
checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
|
||||
dependencies = [
|
||||
"darling 0.20.1",
|
||||
"proc-macro2",
|
||||
@@ -4197,6 +4210,19 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "5.0.0"
|
||||
@@ -4315,13 +4341,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.3.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d42ba3a2e8556722f31336a0750c10dbb6a81396a1c452977f515da83f69f842"
|
||||
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
"cocoa",
|
||||
"dirs-next",
|
||||
"embed_plist",
|
||||
@@ -4343,6 +4369,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rfd",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -4350,6 +4377,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"serialize-to-javascript",
|
||||
"state",
|
||||
"sys-locale",
|
||||
"tar",
|
||||
"tauri-macros",
|
||||
"tauri-runtime",
|
||||
@@ -4387,9 +4415,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde"
|
||||
checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"brotli",
|
||||
@@ -4413,9 +4441,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46"
|
||||
checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -4425,6 +4453,22 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33a3ae55bcfe692e5361edc4708bd9f415270cc02e1cdba8ab7768566208b4e2"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"interprocess",
|
||||
"log",
|
||||
"objc2",
|
||||
"once_cell",
|
||||
"tauri-utils",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "0.0.0"
|
||||
@@ -4455,9 +4499,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b80ea3fcd5fefb60739a3b577b277e8fc30434538a2f5bba82ad7d4368c422"
|
||||
checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -4476,9 +4520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1c396950b1ba06aee1b4ffe6c7cd305ff433ca0e30acbc5fa1a2f92a4ce70f1"
|
||||
checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -4496,12 +4540,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864"
|
||||
checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"dunce",
|
||||
"glob",
|
||||
"heck 0.4.1",
|
||||
"html5ever",
|
||||
@@ -4598,8 +4643,9 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"toml 0.7.3",
|
||||
"tracing",
|
||||
"tracing-error 0.2.0",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"tracing-appender",
|
||||
"tracing-error 0.1.2",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"url",
|
||||
"uuid",
|
||||
"whoami",
|
||||
@@ -4641,8 +4687,11 @@ dependencies = [
|
||||
"chrono",
|
||||
"cocoa",
|
||||
"daedalus",
|
||||
"dirs 5.0.1",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"os_info",
|
||||
"sentry",
|
||||
"sentry-rust-minidump",
|
||||
@@ -4650,6 +4699,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-window-state",
|
||||
"theseus",
|
||||
@@ -4658,7 +4708,6 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"tracing-error 0.1.2",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"url",
|
||||
"uuid",
|
||||
"window-shadows",
|
||||
@@ -4768,6 +4817,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "to_method"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.27.0"
|
||||
@@ -4896,6 +4951,17 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9965507e507f12c8901432a33e31131222abac31edd90cabbcf85cf544b7127a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
|
||||
@@ -28,9 +28,11 @@ dirs = "5.0.1"
|
||||
regex = "1.5"
|
||||
sys-info = "0.9.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.17"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-subscriber = {version = "0.2", features = ["chrono"]}
|
||||
tracing-error = "0.1"
|
||||
tracing-appender = "0.1"
|
||||
|
||||
paste = { version = "1.0"}
|
||||
|
||||
|
||||
72
theseus/src/api/handler.rs
Normal file
72
theseus/src/api/handler.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::event::{
|
||||
emit::{emit_command, emit_warning},
|
||||
CommandPayload,
|
||||
};
|
||||
|
||||
/// Handles external functions (such as through URL deep linkage)
|
||||
/// Link is extracted value (link) in somewhat URL format, such as
|
||||
/// subdomain1/subdomain2
|
||||
/// (Does not include modrinth://)
|
||||
pub async fn handle_url(sublink: &str) -> crate::Result<CommandPayload> {
|
||||
Ok(match sublink.split_once('/') {
|
||||
// /mod/{id} - Installs a mod of mod id
|
||||
Some(("mod", id)) => CommandPayload::InstallMod { id: id.to_string() },
|
||||
// /version/{id} - Installs a specific version of id
|
||||
Some(("version", id)) => {
|
||||
CommandPayload::InstallVersion { id: id.to_string() }
|
||||
}
|
||||
// /modpack/{id} - Installs a modpack of modpack id
|
||||
Some(("modpack", id)) => {
|
||||
CommandPayload::InstallModpack { id: id.to_string() }
|
||||
}
|
||||
_ => {
|
||||
emit_warning(&format!(
|
||||
"Invalid command, unrecognized path: {sublink}"
|
||||
))
|
||||
.await?;
|
||||
return Err(crate::ErrorKind::InputError(format!(
|
||||
"Invalid command, unrecognized path: {sublink}"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn parse_command(
|
||||
command_string: &str,
|
||||
) -> crate::Result<CommandPayload> {
|
||||
tracing::debug!("Parsing command: {}", &command_string);
|
||||
|
||||
// modrinth://some-command
|
||||
// This occurs when following a web redirect link
|
||||
if let Some(sublink) = command_string.strip_prefix("modrinth://") {
|
||||
Ok(handle_url(sublink).await?)
|
||||
} else {
|
||||
// We assume anything else is a filepath to an .mrpack file
|
||||
let path = PathBuf::from(command_string);
|
||||
let path = path.canonicalize()?;
|
||||
if let Some(ext) = path.extension() {
|
||||
if ext == "mrpack" {
|
||||
return Ok(CommandPayload::RunMRPack { path });
|
||||
}
|
||||
}
|
||||
emit_warning(&format!(
|
||||
"Invalid command, unrecognized filetype: {}",
|
||||
path.display()
|
||||
))
|
||||
.await?;
|
||||
Err(crate::ErrorKind::InputError(format!(
|
||||
"Invalid command, unrecognized filetype: {}",
|
||||
path.display()
|
||||
))
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn parse_and_emit_command(command_string: &str) -> crate::Result<()> {
|
||||
let command = parse_command(command_string).await?;
|
||||
emit_command(command).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! API for interacting with Theseus
|
||||
pub mod auth;
|
||||
pub mod handler;
|
||||
pub mod jre;
|
||||
pub mod logs;
|
||||
pub mod metadata;
|
||||
@@ -7,6 +8,7 @@ pub mod pack;
|
||||
pub mod process;
|
||||
pub mod profile;
|
||||
pub mod profile_create;
|
||||
pub mod safety;
|
||||
pub mod settings;
|
||||
pub mod tags;
|
||||
|
||||
@@ -22,6 +24,7 @@ pub mod prelude {
|
||||
pub use crate::{
|
||||
auth::{self, Credentials},
|
||||
data::*,
|
||||
event::CommandPayload,
|
||||
jre, metadata, pack, process,
|
||||
profile::{self, Profile},
|
||||
profile_create, settings,
|
||||
|
||||
5
theseus/src/api/safety.rs
Normal file
5
theseus/src/api/safety.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use crate::state::{ProcessType, SafeProcesses};
|
||||
|
||||
pub async fn check_safe_loading_bars() -> crate::Result<bool> {
|
||||
SafeProcesses::is_complete(ProcessType::LoadingBar).await
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Theseus profile management interface
|
||||
|
||||
pub use crate::{
|
||||
state::{
|
||||
Hooks, JavaSettings, MemorySettings, Profile, Settings, WindowSize,
|
||||
|
||||
@@ -90,6 +90,10 @@ pub enum ErrorKind {
|
||||
|
||||
#[error("Error: {0}")]
|
||||
OtherError(String),
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("Tauri error: {0}")]
|
||||
TauriError(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use super::LoadingBarId;
|
||||
use crate::event::{
|
||||
EventError, LoadingBar, LoadingBarType, ProcessPayloadType,
|
||||
ProfilePayloadType,
|
||||
use crate::{
|
||||
event::{
|
||||
CommandPayload, EventError, LoadingBar, LoadingBarType,
|
||||
ProcessPayloadType, ProfilePayloadType,
|
||||
},
|
||||
state::{ProcessType, SafeProcesses},
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use tracing::warn;
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
use crate::event::{
|
||||
@@ -42,15 +44,29 @@ const CLI_PROGRESS_BAR_TOTAL: u64 = 1000;
|
||||
}
|
||||
*/
|
||||
|
||||
// Initialize a loading bar for use in emit_loading
|
||||
// This will generate a LoadingBarId, which is used to refer to the loading bar uniquely.
|
||||
// total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity)
|
||||
// title is the title of the loading bar
|
||||
/// Initialize a loading bar for use in emit_loading
|
||||
/// This will generate a LoadingBarId, which is used to refer to the loading bar uniquely.
|
||||
/// total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity)
|
||||
/// title is the title of the loading bar
|
||||
/// The app will wait for this loading bar to finish before exiting, as it is considered safe.
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn init_loading(
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
let key = init_loading_unsafe(bar_type, total, title).await?;
|
||||
SafeProcesses::add_uuid(ProcessType::LoadingBar, key.0).await?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// An unsafe loading bar can be created without adding it to the SafeProcesses list,
|
||||
/// meaning that the app won't ask to wait for it to finish before exiting.
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn init_loading_unsafe(
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let key = LoadingBarId(Uuid::new_v4());
|
||||
@@ -76,8 +92,6 @@ pub async fn init_loading(
|
||||
).unwrap()
|
||||
.progress_chars("#>-"),
|
||||
);
|
||||
//pb.set_message(title);
|
||||
|
||||
pb
|
||||
},
|
||||
},
|
||||
@@ -215,7 +229,25 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
)
|
||||
.map_err(EventError::from)?;
|
||||
}
|
||||
warn!("{}", message);
|
||||
tracing::warn!("{}", message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// emit_command(CommandPayload::Something { something })
|
||||
// ie: installing a pack, opening an .mrpack, etc
|
||||
// Generally used for url deep links and file opens that we we want to handle in the frontend
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
|
||||
tracing::debug!("Command: {}", serde_json::to_string(&command)?);
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
event_state
|
||||
.app
|
||||
.emit_all("command", command)
|
||||
.map_err(EventError::from)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ use tokio::sync::OnceCell;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::state::SafeProcesses;
|
||||
|
||||
pub mod emit;
|
||||
|
||||
// Global event state
|
||||
@@ -48,6 +50,12 @@ impl EventState {
|
||||
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
|
||||
}
|
||||
|
||||
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Self::init().await
|
||||
}
|
||||
|
||||
// Values provided should not be used directly, as they are clones and are not guaranteed to be up-to-date
|
||||
pub async fn list_progress_bars() -> crate::Result<HashMap<Uuid, LoadingBar>>
|
||||
{
|
||||
@@ -62,10 +70,11 @@ impl EventState {
|
||||
Ok(display_list)
|
||||
}
|
||||
|
||||
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Self::init().await
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get_main_window() -> crate::Result<Option<tauri::Window>> {
|
||||
use tauri::Manager;
|
||||
let value = Self::get().await?;
|
||||
Ok(value.app.get_window("main"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +100,6 @@ pub struct LoadingBarId(Uuid);
|
||||
impl Drop for LoadingBarId {
|
||||
fn drop(&mut self) {
|
||||
let loader_uuid = self.0;
|
||||
let _event = LoadingBarType::StateInit;
|
||||
let _message = "finished".to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(event_state) = EventState::get().await {
|
||||
let mut bars = event_state.loading_bars.write().await;
|
||||
@@ -132,6 +139,11 @@ impl Drop for LoadingBarId {
|
||||
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
||||
bars.remove(&loader_uuid);
|
||||
}
|
||||
let _ = SafeProcesses::complete(
|
||||
crate::state::ProcessType::LoadingBar,
|
||||
loader_uuid,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -184,6 +196,24 @@ pub struct WarningPayload {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(tag = "event")]
|
||||
pub enum CommandPayload {
|
||||
InstallMod {
|
||||
id: String,
|
||||
},
|
||||
InstallVersion {
|
||||
id: String,
|
||||
},
|
||||
InstallModpack {
|
||||
id: String,
|
||||
},
|
||||
RunMRPack {
|
||||
// run or install .mrpack
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ProcessPayload {
|
||||
pub uuid: Uuid, // processes in state are going to be identified by UUIDs, as they might change to different processes
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::jre::{JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::state::ProfileInstallStage;
|
||||
use crate::EventState;
|
||||
use crate::{
|
||||
process,
|
||||
state::{self as st, MinecraftChild},
|
||||
@@ -471,6 +472,18 @@ pub async fn launch_minecraft(
|
||||
"{MINECRAFT_UUID}".to_string(),
|
||||
);
|
||||
|
||||
// If in tauri, and the 'minimize on launch' setting is enabled, minimize the window
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let window = EventState::get_main_window().await?;
|
||||
if let Some(window) = window {
|
||||
let settings = state.settings.read().await;
|
||||
if settings.hide_on_process {
|
||||
window.minimize()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
|
||||
@@ -15,9 +15,11 @@ mod config;
|
||||
mod error;
|
||||
mod event;
|
||||
mod launcher;
|
||||
mod logger;
|
||||
mod state;
|
||||
|
||||
pub use api::*;
|
||||
pub use error::*;
|
||||
pub use event::{EventState, LoadingBar, LoadingBarType};
|
||||
pub use logger::start_logger;
|
||||
pub use state::State;
|
||||
|
||||
75
theseus/src/logger.rs
Normal file
75
theseus/src/logger.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
eg. RUST_LOG=info will show info, warn, and error logs
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
|
||||
Error messages returned to Tauri will display as traced error logs if they return an error.
|
||||
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
|
||||
|
||||
on unix:
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
|
||||
The default is theseus=show, meaning only logs from theseus will be displayed, and at the info or higher level.
|
||||
|
||||
*/
|
||||
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
|
||||
// Handling for the live development logging
|
||||
// This will log to the console, and will not log to a file
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("theseus=info"));
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("setting default subscriber failed");
|
||||
None
|
||||
}
|
||||
|
||||
// Handling for the live production logging
|
||||
// This will log to a file in the logs directory, and will not show any logs in the console
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use crate::prelude::DirectoryInfo;
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
// Initialize and get logs directory path
|
||||
let path = if let Some(dir) = DirectoryInfo::init().ok() {
|
||||
dir.launcher_logs_dir()
|
||||
} else {
|
||||
eprintln!("Could not create logger.");
|
||||
return None;
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("theseus=info"));
|
||||
|
||||
let file_appender =
|
||||
RollingFileAppender::new(Rotation::DAILY, path, "theseus.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false) // disable ANSI escape codes
|
||||
.with_timer(ChronoLocal::rfc3339()),
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("Setting default subscriber failed");
|
||||
|
||||
Some(guard)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use tracing::error;
|
||||
|
||||
use crate::event::emit::emit_process;
|
||||
use crate::event::ProcessPayloadType;
|
||||
use crate::EventState;
|
||||
use tokio::task::JoinHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -129,6 +130,16 @@ impl Children {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If in tauri, window should show itself again after process exists if it was hidden
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let window = EventState::get_main_window().await?;
|
||||
if let Some(window) = window {
|
||||
window.unminimize()?;
|
||||
}
|
||||
}
|
||||
|
||||
if !mc_exit_status.success() {
|
||||
emit_process(
|
||||
uuid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Theseus directory information
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectoryInfo {
|
||||
@@ -11,7 +11,7 @@ pub struct DirectoryInfo {
|
||||
impl DirectoryInfo {
|
||||
/// Get all paths needed for Theseus to operate properly
|
||||
#[tracing::instrument]
|
||||
pub async fn init() -> crate::Result<Self> {
|
||||
pub fn init() -> crate::Result<Self> {
|
||||
// Working directory
|
||||
let working_dir = std::env::current_dir().map_err(|err| {
|
||||
crate::ErrorKind::FSError(format!(
|
||||
@@ -26,7 +26,7 @@ impl DirectoryInfo {
|
||||
"Could not find valid config dir".to_string(),
|
||||
))?;
|
||||
|
||||
fs::create_dir_all(&config_dir).await.map_err(|err| {
|
||||
fs::create_dir_all(&config_dir).map_err(|err| {
|
||||
crate::ErrorKind::FSError(format!(
|
||||
"Error creating Theseus config directory: {err}"
|
||||
))
|
||||
@@ -130,6 +130,11 @@ impl DirectoryInfo {
|
||||
.join("modrinth_logs")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn launcher_logs_dir(&self) -> PathBuf {
|
||||
self.config_dir.join("launcher_logs")
|
||||
}
|
||||
|
||||
/// Get the file containing the global database
|
||||
#[inline]
|
||||
pub fn database_file(&self) -> PathBuf {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Theseus state management system
|
||||
use crate::event::emit::emit_loading;
|
||||
use crate::event::emit::{emit_loading, init_loading_unsafe};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::event::emit::init_loading;
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::loading_join;
|
||||
|
||||
@@ -46,6 +45,9 @@ pub use self::tags::*;
|
||||
mod java_globals;
|
||||
pub use self::java_globals::*;
|
||||
|
||||
mod safe_processes;
|
||||
pub use self::safe_processes::*;
|
||||
|
||||
// Global state
|
||||
static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
|
||||
pub struct State {
|
||||
@@ -75,6 +77,8 @@ pub struct State {
|
||||
pub(crate) users: RwLock<Users>,
|
||||
/// Launcher tags
|
||||
pub(crate) tags: RwLock<Tags>,
|
||||
/// Launcher processes that should be safely exited on shutdown
|
||||
pub(crate) safety_processes: RwLock<SafeProcesses>,
|
||||
|
||||
/// File watcher debouncer
|
||||
pub(crate) file_watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
@@ -88,7 +92,7 @@ impl State {
|
||||
LAUNCHER_STATE
|
||||
.get_or_try_init(|| {
|
||||
async {
|
||||
let loading_bar = init_loading(
|
||||
let loading_bar = init_loading_unsafe(
|
||||
LoadingBarType::StateInit,
|
||||
100.0,
|
||||
"Initializing launcher",
|
||||
@@ -97,7 +101,7 @@ impl State {
|
||||
|
||||
let mut file_watcher = init_watcher().await?;
|
||||
|
||||
let directories = DirectoryInfo::init().await?;
|
||||
let directories = DirectoryInfo::init()?;
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
// Settings
|
||||
@@ -132,6 +136,7 @@ impl State {
|
||||
|
||||
let children = Children::new();
|
||||
let auth_flow = AuthTask::new();
|
||||
let safety_processes = SafeProcesses::new();
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
@@ -151,6 +156,7 @@ impl State {
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
tags: RwLock::new(tags),
|
||||
safety_processes: RwLock::new(safety_processes),
|
||||
file_watcher: RwLock::new(file_watcher),
|
||||
}))
|
||||
}
|
||||
|
||||
69
theseus/src/state/safe_processes.rs
Normal file
69
theseus/src/state/safe_processes.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::State;
|
||||
|
||||
// We implement a store for safe loading bars such that we can wait for them to complete
|
||||
// We create this store separately from the loading bars themselves, because this may be extended as needed
|
||||
pub struct SafeProcesses {
|
||||
pub loading_bars: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ProcessType {
|
||||
LoadingBar,
|
||||
// Potentially other types of processes (ie: IO operations?)
|
||||
}
|
||||
|
||||
impl SafeProcesses {
|
||||
// init
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
loading_bars: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a new running safe process to the list by uuid
|
||||
pub async fn add_uuid(
|
||||
r#type: ProcessType,
|
||||
uuid: Uuid,
|
||||
) -> crate::Result<Uuid> {
|
||||
let state = State::get().await?;
|
||||
let mut safe_processes = state.safety_processes.write().await;
|
||||
match r#type {
|
||||
ProcessType::LoadingBar => {
|
||||
safe_processes.loading_bars.push(uuid);
|
||||
}
|
||||
}
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
// Mark a safe process as finishing
|
||||
pub async fn complete(
|
||||
r#type: ProcessType,
|
||||
uuid: Uuid,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut safe_processes = state.safety_processes.write().await;
|
||||
|
||||
match r#type {
|
||||
ProcessType::LoadingBar => {
|
||||
safe_processes.loading_bars.retain(|x| *x != uuid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check if there are any pending safe processes of a given type
|
||||
pub async fn is_complete(r#type: ProcessType) -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let safe_processes = state.safety_processes.read().await;
|
||||
match r#type {
|
||||
ProcessType::LoadingBar => {
|
||||
if safe_processes.loading_bars.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,10 @@ pub struct Settings {
|
||||
pub version: u32,
|
||||
pub collapsed_navigation: bool,
|
||||
#[serde(default)]
|
||||
pub hide_on_process: bool,
|
||||
#[serde(default)]
|
||||
pub default_page: DefaultPage,
|
||||
#[serde(default)]
|
||||
pub developer_mode: bool,
|
||||
#[serde(default)]
|
||||
pub opt_out_analytics: bool,
|
||||
@@ -54,6 +58,8 @@ impl Default for Settings {
|
||||
max_concurrent_writes: 10,
|
||||
version: CURRENT_FORMAT_VERSION,
|
||||
collapsed_navigation: false,
|
||||
hide_on_process: false,
|
||||
default_page: DefaultPage::Home,
|
||||
developer_mode: false,
|
||||
opt_out_analytics: false,
|
||||
advanced_rendering: true,
|
||||
@@ -126,7 +132,8 @@ impl Settings {
|
||||
"Error saving settings to file: {err}"
|
||||
))
|
||||
.as_error()
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,3 +179,16 @@ pub struct Hooks {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub post_exit: Option<String>,
|
||||
}
|
||||
|
||||
/// Opening window to start with
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub enum DefaultPage {
|
||||
Home,
|
||||
Library,
|
||||
}
|
||||
|
||||
impl Default for DefaultPage {
|
||||
fn default() -> Self {
|
||||
Self::Home
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ theseus = { path = "../../theseus", features = ["tauri"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.3", features = ["app-all", "devtools", "dialog", "dialog-open", "macos-private-api", "os-all", "protocol-asset", "shell-open", "updater", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-set-decorations", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||
|
||||
tauri = { version = "1.3", features = ["app-all", "devtools", "dialog", "dialog-confirm", "dialog-open", "macos-private-api", "os-all", "protocol-asset", "shell-open", "updater", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-set-decorations", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-deep-link = "0.1.1"
|
||||
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
@@ -28,17 +31,21 @@ futures = "0.3"
|
||||
daedalus = {version = "0.1.15", features = ["bincode"] }
|
||||
chrono = "0.4.26"
|
||||
|
||||
dirs = "5.0.1"
|
||||
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||
os_info = "3.7.0"
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.2"
|
||||
tracing-error = "0.1"
|
||||
|
||||
sentry = "0.30"
|
||||
sentry-rust-minidump = "0.5"
|
||||
|
||||
lazy_static = "1"
|
||||
once_cell = "1"
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
window-shadows = "0.2.1"
|
||||
|
||||
|
||||
63
theseus_gui/src-tauri/Info.plist
Normal file
63
theseus_gui/src-tauri/Info.plist
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<!-- Obviously needs to be replaced with your app's bundle identifier -->
|
||||
<string>com.modrinth.theseus</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<!-- register the myapp:// and myscheme:// schemes -->
|
||||
<string>modrinth</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<!-- Declare file types your app can open -->
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Modrinth type</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.modrinth.theseus-type</string>
|
||||
</array>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>NSDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Modrinth File</string>
|
||||
<key>UTTypeIcons</key>
|
||||
<dict/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.modrinth.theseus-type</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mrpack</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>application/x-mrpack</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
346
theseus_gui/src-tauri/msi/main.wxs
Normal file
346
theseus_gui/src-tauri/msi/main.wxs
Normal file
@@ -0,0 +1,346 @@
|
||||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{product_name}}"
|
||||
UpgradeCode="{{upgrade_code}}"
|
||||
Language="!(loc.TauriLanguage)"
|
||||
Manufacturer="{{manufacturer}}"
|
||||
Version="{{version}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="0"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||
|
||||
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
{{#if banner_path}}
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||
{{/if}}
|
||||
{{#if dialog_image_path}}
|
||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||
{{/if}}
|
||||
{{#if license}}
|
||||
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||
{{/if}}
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<Property Id="WixShellExecTarget" Value="[!Path]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
{{#unless license}}
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg"
|
||||
Control="Next"
|
||||
Event="NewDialog"
|
||||
Value="InstallDirDlg"
|
||||
Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg"
|
||||
Control="Back"
|
||||
Event="NewDialog"
|
||||
Value="WelcomeDlg"
|
||||
Order="2">1</Publish>
|
||||
{{/unless}}
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="DesktopFolder" Name="Desktop">
|
||||
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="RegistryEntries" Guid="*">
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
<!-- THESEUS -->
|
||||
<ProgId Id="theseus.mrpack.Document" Description="Modrinth File">
|
||||
<Extension Id="mrpack" ContentType="application/mrpack">
|
||||
<!-- no flags on argument, so we can hijack deep link library-->
|
||||
<Verb Id="open" Command="Open" TargetFile="Path" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
<!-- /THESEUS -->
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{#if enable_elevated_update_task}}
|
||||
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
{{/if}}
|
||||
{{resources}}
|
||||
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{product_name}}"
|
||||
Description="Uninstalls {{product_name}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="INSTALLDIR"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||
Name="Uninstaller Shortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- THESEUS -->
|
||||
<Component Id="FileTypeAssociationsReg" Guid="*">
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationDescription" Value="theseus" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationIcon" Value="[INSTALLDIR]theseus,0" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationName" Value="theseus" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\DefaultIcon" Value="[INSTALLDIR]theseus,1" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\FileAssociations" Name=".mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\MIMEAssociations" Name="application/mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\shell\Open\command" Value=""[INSTALLDIR]theseus" -e "%1"" Type="string" />
|
||||
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\RegisteredApplications" Name="theseus" Value="SOFTWARE\modrinth\theseus\Capabilities" Type="string" KeyPath="yes" />
|
||||
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\theseus.mrpack.Document" Name="MRPACK File" Value="Modrinth Modpack Installer" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack" Name="Content Type" Value="application/mrpack" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithList\theseus" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithProgids" Name="theseus.mrpack.Document" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\SupportedTypes" Name=".mrpack" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\shell\open" Name="FriendlyAppName" Value="theseus" Type="string" />
|
||||
</Component>
|
||||
<!-- /THESEUS -->
|
||||
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{product_name}}"
|
||||
Description="Runs {{product_name}}"
|
||||
Target="[!Path]"
|
||||
Icon="ProductIcon"
|
||||
WorkingDirectory="INSTALLDIR">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||
</Shortcut>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
{{#each merge_modules as |msm| ~}}
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||
<MergeRef Id="{{ msm.name }}"/>
|
||||
</Feature>
|
||||
{{/each~}}
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="!(loc.InstallAppFeature)"
|
||||
Level="1"
|
||||
ConfigurableDirectory="INSTALLDIR"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
<ComponentRef Id="RegistryEntries"/>
|
||||
|
||||
<!-- THESEUS -->
|
||||
<ComponentRef Id="FileTypeAssociationsReg" />
|
||||
<!-- /THESEUS -->
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<ComponentRef Id="UpdateTask" />
|
||||
<ComponentRef Id="UpdateTaskInstaller" />
|
||||
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||
{{/if}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="Path"/>
|
||||
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="!(loc.PathEnvVarFeature)"
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<ComponentRef Id="{{ bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||
{{#each component_group_refs as |id| ~}}
|
||||
<ComponentGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each component_refs as |id| ~}}
|
||||
<ComponentRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_group_refs as |id| ~}}
|
||||
<FeatureGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_refs as |id| ~}}
|
||||
<FeatureRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each merge_refs as |id| ~}}
|
||||
<MergeRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||
<CustomAction
|
||||
Id="CreateUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
Execute="commit"
|
||||
Impersonate="yes"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||
NOT(REMOVE)
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
<!-- Remove elevated update task during uninstall -->
|
||||
<CustomAction
|
||||
Id="DeleteUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
||||
@@ -13,7 +13,6 @@ pub mod profile_create;
|
||||
pub mod settings;
|
||||
pub mod tags;
|
||||
pub mod utils;
|
||||
pub mod window_ext;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||
|
||||
@@ -33,6 +32,10 @@ pub enum TheseusSerializableError {
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("Callback error: {0}")]
|
||||
Callback(String),
|
||||
}
|
||||
|
||||
// Generic implementation of From<T> for ErrorTypeA
|
||||
@@ -80,6 +83,12 @@ macro_rules! impl_serialize {
|
||||
}
|
||||
|
||||
// Use the macro to implement Serialize for TheseusSerializableError
|
||||
#[cfg(target_os = "macos")]
|
||||
impl_serialize! {
|
||||
IO
|
||||
IO,
|
||||
Callback
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use theseus::{handler, prelude::CommandPayload, State};
|
||||
|
||||
use crate::api::Result;
|
||||
use std::process::Command;
|
||||
use std::{env, process::Command};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("utils")
|
||||
@@ -7,6 +9,9 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
should_disable_mouseover,
|
||||
show_in_folder,
|
||||
progress_bars_list,
|
||||
safety_check_safe_loading_bars,
|
||||
get_opening_command,
|
||||
await_sync,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -21,6 +26,12 @@ pub async fn progress_bars_list(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Check if there are any safe loading bars running
|
||||
#[tauri::command]
|
||||
pub async fn safety_check_safe_loading_bars() -> Result<bool> {
|
||||
Ok(theseus::safety::check_safe_loading_bars().await?)
|
||||
}
|
||||
|
||||
// cfg only on mac os
|
||||
// disables mouseover and fixes a random crash error only fixed by recent versions of macos
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -83,3 +94,34 @@ pub fn show_in_folder(path: String) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get opening command
|
||||
// For example, if a user clicks on an .mrpack to open the app.
|
||||
// This should be called once and only when the app is done booting up and ready to receive a command
|
||||
// Returns a Command struct- see events.js
|
||||
#[tauri::command]
|
||||
pub async fn get_opening_command() -> Result<Option<CommandPayload>> {
|
||||
// Tauri is not CLI, we use arguments as path to file to call
|
||||
let cmd_arg = env::args_os().nth(1);
|
||||
|
||||
let cmd_arg = cmd_arg.map(|path| path.to_string_lossy().to_string());
|
||||
if let Some(cmd) = cmd_arg {
|
||||
tracing::debug!("Opening command: {:?}", cmd);
|
||||
return Ok(Some(handler::parse_command(&cmd).await?));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// helper function called when redirected by a weblink (ie: modrith://do-something) or when redirected by a .mrpack file (in which case its a filepath)
|
||||
// We hijack the deep link library (which also contains functionality for instance-checking)
|
||||
pub async fn handle_command(command: String) -> Result<()> {
|
||||
Ok(theseus::handler::parse_and_emit_command(&command).await?)
|
||||
}
|
||||
|
||||
// Waits for state to be synced
|
||||
#[tauri::command]
|
||||
pub async fn await_sync() -> Result<()> {
|
||||
State::sync().await?;
|
||||
tracing::info!("State synced");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
98
theseus_gui/src-tauri/src/macos/delegate.rs
Normal file
98
theseus_gui/src-tauri/src/macos/delegate.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::NSAutoreleasePool,
|
||||
};
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::api::TheseusSerializableError;
|
||||
|
||||
type Callback = OnceCell<Box<dyn Fn(String) + Send + Sync + 'static>>;
|
||||
|
||||
static CALLBACK: Callback = OnceCell::new();
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
unsafe impl Sync for AppDelegateClass {}
|
||||
|
||||
// Obj C class for the app delegate
|
||||
// This inherits from the TaoAppDelegate (used by tauri) so we do not accidentally override any functionality
|
||||
// The application_open_file method is the only method we override, as it is currently unimplemented in tauri
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref THESEUS_APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
|
||||
let superclass = class!(TaoAppDelegate);
|
||||
let mut decl = ClassDecl::new("TheseusAppDelegate", superclass).unwrap();
|
||||
|
||||
// Add the method to the class
|
||||
decl.add_method(
|
||||
sel!(application:openFile:),
|
||||
application_open_file as extern "C" fn(&Object, Sel, id, id) -> bool,
|
||||
);
|
||||
|
||||
// Other methods are inherited
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn application_open_file(
|
||||
_: &Object,
|
||||
_: Sel,
|
||||
_: id,
|
||||
file: id,
|
||||
) -> bool {
|
||||
let file = nsstring_to_string(file);
|
||||
callback(file)
|
||||
}
|
||||
|
||||
pub fn callback(file: String) -> bool {
|
||||
if let Some(callback) = CALLBACK.get() {
|
||||
callback(file);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_open_file<T>(
|
||||
callback: T,
|
||||
) -> Result<(), TheseusSerializableError>
|
||||
where
|
||||
T: Fn(String) + Send + Sync + 'static,
|
||||
{
|
||||
unsafe {
|
||||
// Modified from tao: https://github.com/tauri-apps/tao
|
||||
// sets the current app delegate to be the inherited app delegate rather than the default tauri/tao one
|
||||
let app: id = msg_send![class!(TaoApp), sharedApplication];
|
||||
|
||||
let delegate: id = msg_send![THESEUS_APP_DELEGATE_CLASS.0, new];
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
let _: () = msg_send![app, setDelegate: delegate];
|
||||
let _: () = msg_send![pool, drain];
|
||||
}
|
||||
CALLBACK.set(Box::new(callback)).map_err(|_| {
|
||||
TheseusSerializableError::Callback("Callback already set".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert an NSString to a Rust `String`
|
||||
/// From 'fruitbasket' https://github.com/mrmekon/fruitbasket/
|
||||
#[allow(clippy::cmp_null)]
|
||||
pub fn nsstring_to_string(nsstring: *mut Object) -> String {
|
||||
unsafe {
|
||||
let cstr: *const i8 = msg_send![nsstring, UTF8String];
|
||||
if cstr != std::ptr::null() {
|
||||
std::ffi::CStr::from_ptr(cstr)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
2
theseus_gui/src-tauri/src/macos/mod.rs
Normal file
2
theseus_gui/src-tauri/src/macos/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod delegate;
|
||||
pub mod window_ext;
|
||||
@@ -12,7 +12,6 @@ pub trait WindowExt {
|
||||
impl<R: Runtime> WindowExt for Window<R> {
|
||||
fn set_transparent_titlebar(&self, transparent: bool) {
|
||||
use cocoa::appkit::{NSWindow, NSWindowTitleVisibility};
|
||||
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
unsafe {
|
||||
@@ -3,16 +3,15 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use theseus::prelude::*;
|
||||
|
||||
use tauri::Manager;
|
||||
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
mod error;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
// Should be called in launcher initialization
|
||||
#[tauri::command]
|
||||
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
@@ -27,89 +26,95 @@ fn is_dev() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
}
|
||||
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
args: Vec<String>,
|
||||
cwd: String,
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
//let client = sentry::init("https://19a14416dafc4b4a858fa1a38db3b704@o485889.ingest.sentry.io/4505349067374592");
|
||||
tauri_plugin_deep_link::prepare("com.modrinth.theseus");
|
||||
|
||||
//let _guard = sentry_rust_minidump::init(&client);
|
||||
/*
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
eg. RUST_LOG=info will show info, warn, and error logs
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
eg. RUST_LOG=info will show info, warn, and error logs
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
|
||||
Error messages returned to Tauri will display as traced error logs if they return an error.
|
||||
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
|
||||
Error messages returned to Tauri will display as traced error logs if they return an error.
|
||||
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
|
||||
|
||||
on unix:
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
on unix:
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
|
||||
*/
|
||||
let filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("theseus=info"));
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.with(ErrorLayer::default());
|
||||
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("setting default subscriber failed");
|
||||
|
||||
let mut builder = tauri::Builder::default()
|
||||
let mut builder = tauri::Builder::default();
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build());
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.setup(|app| {
|
||||
// Register deep link handler, allowing reading of modrinth:// links
|
||||
if let Err(e) = tauri_plugin_deep_link::register(
|
||||
"modrinth",
|
||||
|request: String| {
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
request,
|
||||
));
|
||||
},
|
||||
) {
|
||||
// Allow it to fail- see https://github.com/FabianLars/tauri-plugin-deep-link/issues/19
|
||||
tracing::error!("Error registering deep link handler: {}", e);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
builder = builder.setup(|app| {
|
||||
let win = app.get_window("main").unwrap();
|
||||
win.set_decorations(false).unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
use window_shadows::set_shadow;
|
||||
set_shadow(&win, true).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use macos::window_ext::WindowExt;
|
||||
win.set_transparent_titlebar(true);
|
||||
win.position_traffic_lights(9.0, 16.0);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
win.set_decorations(false).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::delegate::register_open_file(|filename| {
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
filename,
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
builder = builder.setup(|app| {
|
||||
let win = app.get_window("main").unwrap();
|
||||
set_shadow(&win, true).unwrap();
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use tauri::WindowEvent;
|
||||
|
||||
builder = builder
|
||||
.setup(|app| {
|
||||
use api::window_ext::WindowExt;
|
||||
let win = app.get_window("main").unwrap();
|
||||
win.set_transparent_titlebar(true);
|
||||
builder = builder.on_window_event(|e| {
|
||||
use macos::window_ext::WindowExt;
|
||||
if let WindowEvent::Resized(..) = e.event() {
|
||||
let win = e.window();
|
||||
win.position_traffic_lights(9.0, 16.0);
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|e| {
|
||||
use api::window_ext::WindowExt;
|
||||
if let WindowEvent::Resized(..) = e.event() {
|
||||
let win = e.window();
|
||||
win.position_traffic_lights(9.0, 16.0);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
let builder = builder
|
||||
.plugin(api::auth::init())
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"dialog": {
|
||||
"confirm": true,
|
||||
"open": true
|
||||
},
|
||||
"protocol": {
|
||||
@@ -75,7 +76,10 @@
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com"
|
||||
"timestampUrl": "http://timestamp.digicert.com",
|
||||
"wix": {
|
||||
"template": "./msi/main.wxs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -29,6 +29,10 @@ import mixpanel from 'mixpanel-browser'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import OnboardingModal from '@/components/OnboardingModal.vue'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { window } from '@tauri-apps/api'
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -69,6 +73,33 @@ defineExpose({
|
||||
},
|
||||
})
|
||||
|
||||
const confirmClose = async () => {
|
||||
const confirmed = await confirm(
|
||||
'An action is currently in progress. Are you sure you want to exit?',
|
||||
{
|
||||
title: 'Modrinth',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
return confirmed
|
||||
}
|
||||
|
||||
const handleClose = async () => {
|
||||
const isSafe = await check_safe_loading_bars_complete()
|
||||
if (!isSafe) {
|
||||
const response = await confirmClose()
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
}
|
||||
await await_sync()
|
||||
window.getCurrent().close()
|
||||
}
|
||||
|
||||
window.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
|
||||
await handleClose()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
router.afterEach((to, from, failure) => {
|
||||
if (mixpanel.__loaded) {
|
||||
@@ -220,7 +251,7 @@ const accounts = ref(null)
|
||||
@click="
|
||||
() => {
|
||||
saveWindowState(StateFlags.ALL)
|
||||
appWindow.close()
|
||||
handleClose()
|
||||
}
|
||||
"
|
||||
>
|
||||
|
||||
@@ -70,6 +70,19 @@ export async function profile_listener(callback) {
|
||||
return await listen('profile', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'command' event
|
||||
/*
|
||||
CommandPayload {
|
||||
event: event type ("InstallMod", "InstallModpack", "InstallVersion"),
|
||||
id: string id of the mod/modpack/version to install
|
||||
}
|
||||
*/
|
||||
export async function command_listener(callback) {
|
||||
return await listen('command', (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
}
|
||||
|
||||
/// Payload for the 'warning' event
|
||||
/*
|
||||
WarningPayload {
|
||||
|
||||
@@ -15,3 +15,21 @@ export async function initialize_state() {
|
||||
export async function progress_bars_list() {
|
||||
return await invoke('plugin:utils|progress_bars_list')
|
||||
}
|
||||
|
||||
// Check if any safe loading bars are active
|
||||
export async function check_safe_loading_bars_complete() {
|
||||
return await invoke('plugin:utils|safety_check_safe_loading_bars')
|
||||
}
|
||||
|
||||
// Get opening command
|
||||
// For example, if a user clicks on an .mrpack to open the app.
|
||||
// This should be called once and only when the app is done booting up and ready to receive a command
|
||||
// Returns a Command struct- see events.js
|
||||
export async function get_opening_command() {
|
||||
return await invoke('plugin:utils|get_opening_command')
|
||||
}
|
||||
|
||||
// Wait for settings to sync
|
||||
export async function await_sync() {
|
||||
return await invoke('plugin:utils|await_sync')
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import 'omorphia/dist/style.css'
|
||||
import '@/assets/stylesheets/global.scss'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import { initialize_state } from '@/helpers/state'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import loadCssMixin from './mixins/macCssFix.js'
|
||||
import { get } from '@/helpers/settings'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
@@ -20,7 +21,24 @@ app.mixin(loadCssMixin)
|
||||
const mountedApp = app.mount('#app')
|
||||
|
||||
initialize_state()
|
||||
.then(() => mountedApp.initialize())
|
||||
.then(() => {
|
||||
// First, redirect to other landing page if we have that setting
|
||||
get()
|
||||
.then((fetchSettings) => {
|
||||
if (fetchSettings?.default_page && fetchSettings?.default_page !== 'Home') {
|
||||
router.push({ name: fetchSettings?.default_page })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
.finally(() => {
|
||||
mountedApp.initialize()
|
||||
get_opening_command().then((command) => {
|
||||
console.log(JSON.stringify(command)) // change me to use whatever FE command handler is made
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
@@ -7,6 +7,8 @@ import { get_max_memory } from '@/helpers/jre'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const pageOptions = ['Home', 'Library']
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const fetchSettings = await get().catch(handleError)
|
||||
@@ -143,6 +145,43 @@ watch(
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="minimize-launcher">
|
||||
<span class="label__title">Minimize launcher</span>
|
||||
<span class="label__description"
|
||||
>Minimize the launcher when a Minecraft process starts.</span
|
||||
>
|
||||
</label>
|
||||
<Toggle
|
||||
id="minimize-launcher"
|
||||
:model-value="settings.hide_on_process"
|
||||
:checked="settings.hide_on_process"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.hide_on_process = e
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="opening-page">
|
||||
<label for="opening-page">
|
||||
<span class="label__title">Default landing page</span>
|
||||
<span class="label__description">Change the page to which the launcher opens on.</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
id="opening-page"
|
||||
name="Opening page dropdown"
|
||||
:options="pageOptions"
|
||||
:default-value="settings.default_page"
|
||||
:model-value="settings.default_page"
|
||||
class="opening-page"
|
||||
@change="
|
||||
(e) => {
|
||||
settings.default_page = e.option
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
|
||||
3
theseus_playground/link_test.html
Normal file
3
theseus_playground/link_test.html
Normal file
@@ -0,0 +1,3 @@
|
||||
HTML Testing playground for Theseus:
|
||||
|
||||
<br><br><a href="modrinth://mod/test_id">Install mod 'test_id'</a>
|
||||
@@ -9,9 +9,6 @@ use theseus::prelude::*;
|
||||
|
||||
use theseus::profile_create::profile_create;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// A simple Rust implementation of the authentication run
|
||||
// 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend)
|
||||
@@ -35,16 +32,7 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
|
||||
async fn main() -> theseus::Result<()> {
|
||||
println!("Starting.");
|
||||
|
||||
let filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("theseus=info"));
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("setting default subscriber failed");
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
// Initialize state
|
||||
let st = State::get().await?;
|
||||
|
||||
Reference in New Issue
Block a user