fullscreen (#360)
* fullscreen * improvements, and error catching * yarn prettier * discord rpc * fixed uninitialized options.txt * working discord version * incorrect boolean * change * merge issue; regex solution * fixed error * multi line mode * moved \n to start
This commit is contained in:
parent
ce01ee6a2d
commit
c364468ed5
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -575,7 +575,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fnv",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -986,7 +986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1107,6 +1107,18 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discord-rich-presence"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47fc4beffb85ee1461588499073a4d9c20dcc7728c4b13d6b282ab6c508947e5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@ -2522,7 +2534,7 @@ dependencies = [
|
||||
"crash-handler",
|
||||
"minidumper",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3810,7 +3822,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time 0.3.22",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4299,7 +4311,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tao-macros",
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"x11-dl",
|
||||
@ -4382,7 +4394,7 @@ dependencies = [
|
||||
"time 0.3.22",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@ -4428,7 +4440,7 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time 0.3.22",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@ -4506,7 +4518,7 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
@ -4524,7 +4536,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@ -4606,6 +4618,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"daedalus",
|
||||
"dirs 5.0.1",
|
||||
"discord-rich-presence",
|
||||
"dunce",
|
||||
"futures",
|
||||
"indicatif",
|
||||
@ -4633,7 +4646,7 @@ dependencies = [
|
||||
"tracing-error 0.1.2",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"whoami",
|
||||
"winreg 0.50.0",
|
||||
"zip",
|
||||
@ -4661,7 +4674,7 @@ dependencies = [
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"webbrowser",
|
||||
"winreg 0.11.0",
|
||||
]
|
||||
@ -4695,7 +4708,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-error 0.1.2",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"window-shadows",
|
||||
]
|
||||
|
||||
@ -4725,7 +4738,7 @@ dependencies = [
|
||||
"tracing-error 0.1.2",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.0",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
@ -5209,6 +5222,15 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.0"
|
||||
|
||||
@ -55,6 +55,8 @@ dunce = "1.0.3"
|
||||
|
||||
whoami = "1.4.0"
|
||||
|
||||
discord-rich-presence = "0.2.3"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.50.0"
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ pub mod prelude {
|
||||
jre, metadata, pack, process,
|
||||
profile::{self, create, Profile},
|
||||
settings,
|
||||
state::JavaGlobals,
|
||||
state::{JavaGlobals, SetFullscreen},
|
||||
state::{ProfilePathId, ProjectPathId},
|
||||
util::{
|
||||
io::{canonicalize, IOError},
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
event::LoadingBarId,
|
||||
@ -106,7 +105,7 @@ pub struct ATLauncherMod {
|
||||
// Check if folder has a instance.json that parses
|
||||
pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
let instance: String =
|
||||
fs::read_to_string(&instance_folder.join("instance.json"))
|
||||
io::read_to_string(&instance_folder.join("instance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let instance: Result<ATInstance, serde_json::Error> =
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
@ -42,7 +41,7 @@ pub struct MinecraftInstance {
|
||||
// Check if folder has a minecraftinstance.json that parses
|
||||
pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
|
||||
let minecraftinstance: String =
|
||||
fs::read_to_string(&instance_folder.join("minecraftinstance.json"))
|
||||
io::read_to_string(&instance_folder.join("minecraftinstance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let minecraftinstance: Result<MinecraftInstance, serde_json::Error> =
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
@ -32,7 +31,7 @@ pub struct GDLauncherLoader {
|
||||
// Check if folder has a config.json that parses
|
||||
pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
|
||||
let config: String =
|
||||
fs::read_to_string(&instance_folder.join("config.json"))
|
||||
io::read_to_string(&instance_folder.join("config.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let config: Result<GDLauncherConfig, serde_json::Error> =
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
pack::{
|
||||
@ -126,7 +125,7 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
let instance_cfg = instance_folder.join("instance.cfg");
|
||||
let mmc_pack = instance_folder.join("mmc-pack.json");
|
||||
|
||||
let mmc_pack = match fs::read_to_string(&mmc_pack).await {
|
||||
let mmc_pack = match io::read_to_string(&mmc_pack).await {
|
||||
Ok(mmc_pack) => mmc_pack,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
@ -66,45 +66,46 @@ pub async fn get_importable_instances(
|
||||
}
|
||||
|
||||
// Import an instance from a launcher type and base path
|
||||
// Note: this *deletes* the submitted empty profile
|
||||
#[theseus_macros::debug_pin]
|
||||
#[tracing::instrument]
|
||||
pub async fn import_instance(
|
||||
profile_path: ProfilePathId,
|
||||
profile_path: ProfilePathId, // This should be a blank profile
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> crate::Result<()> {
|
||||
tracing::debug!("Importing instance from {instance_folder}");
|
||||
match launcher_type {
|
||||
let res = match launcher_type {
|
||||
ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => {
|
||||
mmc::import_mmc(
|
||||
base_path, // path to base mmc folder
|
||||
instance_folder, // instance folder in mmc_base_path
|
||||
profile_path, // path to profile
|
||||
base_path, // path to base mmc folder
|
||||
instance_folder, // instance folder in mmc_base_path
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::ATLauncher => {
|
||||
atlauncher::import_atlauncher(
|
||||
base_path, // path to atlauncher folder
|
||||
instance_folder, // instance folder in atlauncher
|
||||
profile_path, // path to profile
|
||||
base_path, // path to atlauncher folder
|
||||
instance_folder, // instance folder in atlauncher
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::GDLauncher => {
|
||||
gdlauncher::import_gdlauncher(
|
||||
base_path.join("instances").join(instance_folder), // path to gdlauncher folder
|
||||
profile_path, // path to profile
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::Curseforge => {
|
||||
curseforge::import_curseforge(
|
||||
base_path.join("Instances").join(instance_folder), // path to curseforge folder
|
||||
profile_path, // path to profile
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::Unknown => {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
@ -112,6 +113,16 @@ pub async fn import_instance(
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
// If import failed, delete the profile
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::warn!("Import failed: {:?}", e);
|
||||
let _ = crate::api::profile::remove(&profile_path).await;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Check existing managed packs for potential updates
|
||||
|
||||
@ -121,7 +121,7 @@ pub async fn wait_for_by_uuid(uuid: &Uuid) -> crate::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Kill a running child process directly, and wait for it to be killed
|
||||
// Kill a running child process directly
|
||||
#[tracing::instrument(skip(running))]
|
||||
pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
|
||||
running
|
||||
@ -131,7 +131,7 @@ pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
|
||||
.kill()
|
||||
.await
|
||||
.map_err(IOError::from)?;
|
||||
wait_for(running).await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Await on the completion of a child process directly
|
||||
|
||||
@ -7,7 +7,9 @@ use crate::event::LoadingBarType;
|
||||
use crate::pack::install_from::{
|
||||
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
|
||||
};
|
||||
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
|
||||
use crate::prelude::{
|
||||
JavaVersion, ProfilePathId, ProjectPathId, SetFullscreen,
|
||||
};
|
||||
use crate::state::ProjectMetadata;
|
||||
|
||||
use crate::util::io::{self, IOError};
|
||||
@ -838,9 +840,23 @@ pub async fn run_credentials(
|
||||
None
|
||||
};
|
||||
|
||||
// Any options.txt settings that we want set, add here
|
||||
let mut mc_set_options: Vec<(String, String)> = Vec::new();
|
||||
match profile.force_fullscreen {
|
||||
SetFullscreen::LeaveUnset => {}
|
||||
SetFullscreen::SetWindowed => {
|
||||
mc_set_options
|
||||
.push(("fullscreen".to_string(), "false".to_string()));
|
||||
}
|
||||
SetFullscreen::SetFullscreen => {
|
||||
mc_set_options.push(("fullscreen".to_string(), "true".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let mc_process = crate::launcher::launch_minecraft(
|
||||
java_args,
|
||||
env_args,
|
||||
&mc_set_options,
|
||||
wrapper,
|
||||
&memory,
|
||||
&resolution,
|
||||
|
||||
@ -49,6 +49,9 @@ pub enum ErrorKind {
|
||||
#[error("Incorrect Sha1 hash for download: {0} != {1}")]
|
||||
HashError(String, String),
|
||||
|
||||
#[error("Regex error: {0}")]
|
||||
RegexError(#[from] regex::Error),
|
||||
|
||||
#[error("Paths stored in the database need to be valid UTF-8: {0}")]
|
||||
UTFError(std::path::PathBuf),
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::jre::{self, JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
|
||||
use crate::launcher::io::IOError;
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::state::ProfileInstallStage;
|
||||
use crate::util::io;
|
||||
@ -164,6 +165,16 @@ pub async fn install_minecraft(
|
||||
)
|
||||
})?;
|
||||
|
||||
// Test jre version
|
||||
let java_version = jre::check_jre(java_version.path.clone().into())
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Java path invalid or non-functional: {}",
|
||||
java_version.path
|
||||
))
|
||||
})?;
|
||||
|
||||
// Download minecraft (5-90)
|
||||
download::download_minecraft(
|
||||
&state,
|
||||
@ -246,6 +257,7 @@ pub async fn install_minecraft(
|
||||
)?)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &java_version.path))
|
||||
.map_err(|err| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Error running processor: {err}",
|
||||
@ -291,6 +303,7 @@ pub async fn install_minecraft(
|
||||
pub async fn launch_minecraft(
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
mc_set_options: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
memory: &st::MemorySettings,
|
||||
resolution: &st::WindowSize,
|
||||
@ -440,6 +453,33 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
command.envs(env_args);
|
||||
|
||||
// Overwrites the minecraft options.txt file with the settings from the profile
|
||||
// Uses 'a:b' syntax which is not quite yaml
|
||||
use regex::Regex;
|
||||
|
||||
let options_path = instance_path.join("options.txt");
|
||||
let mut options_string = String::new();
|
||||
|
||||
if options_path.exists() {
|
||||
options_string = io::read_to_string(&options_path).await?;
|
||||
}
|
||||
|
||||
for (key, value) in mc_set_options {
|
||||
let re = Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
||||
// check if the regex exists in the file
|
||||
if !re.is_match(&options_string) {
|
||||
// The key was not found in the file, so append it
|
||||
options_string.push_str(&format!("\n{}:{}", key, value));
|
||||
} else {
|
||||
let replaced_string = re
|
||||
.replace_all(&options_string, &format!("{}:{}", key, value))
|
||||
.to_string();
|
||||
options_string = replaced_string;
|
||||
}
|
||||
}
|
||||
|
||||
io::write(&options_path, options_string).await?;
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
|
||||
@ -501,6 +541,14 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Add game played to discord rich presence
|
||||
let _ = state
|
||||
.discord_rpc
|
||||
.set_activity(&format!("Playing {}", profile.metadata.name), true)
|
||||
.await;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@ -145,6 +145,13 @@ impl Children {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Clear game played for Discord RPC
|
||||
// May have other active processes, so we clear to the next running process
|
||||
let state = crate::State::get().await?;
|
||||
let _ = state.discord_rpc.clear_to_default(true).await;
|
||||
}
|
||||
|
||||
// If in tauri, window should show itself again after process exists if it was hidden
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
|
||||
167
theseus/src/state/discord.rs
Normal file
167
theseus/src/state/discord.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use discord_rich_presence::{
|
||||
activity::{Activity, Assets},
|
||||
DiscordIpc, DiscordIpcClient,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub struct DiscordGuard {
|
||||
client: Arc<RwLock<DiscordIpcClient>>,
|
||||
connected: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DiscordGuard {
|
||||
/// Initialize discord IPC client, and attempt to connect to it
|
||||
/// If it fails, it will still return a DiscordGuard, but the client will be unconnected
|
||||
pub async fn init() -> crate::Result<DiscordGuard> {
|
||||
let mut dipc =
|
||||
DiscordIpcClient::new("1084015525241311292").map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not create Discord client {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
let res = dipc.connect(); // Do not need to connect to Discord to use app
|
||||
let connected = if res.is_ok() {
|
||||
Arc::new(AtomicBool::new(true))
|
||||
} else {
|
||||
Arc::new(AtomicBool::new(false))
|
||||
};
|
||||
|
||||
let client = Arc::new(RwLock::new(dipc));
|
||||
Ok(DiscordGuard { client, connected })
|
||||
}
|
||||
|
||||
/// If the client failed connecting during init(), this will check for connection and attempt to reconnect
|
||||
/// This MUST be called first in any client method that requires a connection, because those can PANIC if the client is not connected
|
||||
/// (No connection is different than a failed connection, the latter will not panic and can be retried)
|
||||
pub async fn retry_if_not_ready(&self) -> bool {
|
||||
let mut client = self.client.write().await;
|
||||
if !self.connected.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
if client.connect().is_ok() {
|
||||
self.connected
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Set the activity to the given message
|
||||
pub async fn set_activity(
|
||||
&self,
|
||||
msg: &str,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
// Attempt to connect if not connected. Do not continue if it fails, as the client.set_activity can panic if it never was connected
|
||||
if !self.retry_if_not_ready().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let activity = Activity::new().state(msg).assets(
|
||||
Assets::new()
|
||||
.large_image("modrinth_simple")
|
||||
.large_text("Modrinth Logo"),
|
||||
);
|
||||
|
||||
// Attempt to set the activity
|
||||
// If the existing connection fails, attempt to reconnect and try again
|
||||
let mut client: tokio::sync::RwLockWriteGuard<'_, DiscordIpcClient> =
|
||||
self.client.write().await;
|
||||
let res = client.set_activity(activity.clone());
|
||||
let could_not_set_err = |e: Box<dyn serde::ser::StdError>| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not update Discord activity {}",
|
||||
e,
|
||||
))
|
||||
};
|
||||
|
||||
if reconnect_if_fail {
|
||||
if let Err(_e) = res {
|
||||
client.reconnect().map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not reconnect to Discord IPC {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
return Ok(client
|
||||
.set_activity(activity)
|
||||
.map_err(could_not_set_err)?); // try again, but don't reconnect if it fails again
|
||||
}
|
||||
} else {
|
||||
res.map_err(could_not_set_err)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity
|
||||
pub async fn clear_activity(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
// Attempt to connect if not connected. Do not continue if it fails, as the client.clear_activity can panic if it never was connected
|
||||
if !self.retry_if_not_ready().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Attempt to clear the activity
|
||||
// If the existing connection fails, attempt to reconnect and try again
|
||||
let mut client = self.client.write().await;
|
||||
let res = client.clear_activity();
|
||||
|
||||
let could_not_clear_err = |e: Box<dyn serde::ser::StdError>| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not clear Discord activity {}",
|
||||
e,
|
||||
))
|
||||
};
|
||||
|
||||
if reconnect_if_fail {
|
||||
if res.is_err() {
|
||||
client.reconnect().map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not reconnect to Discord IPC {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
return Ok(client
|
||||
.clear_activity()
|
||||
.map_err(could_not_clear_err)?); // try again, but don't reconnect if it fails again
|
||||
}
|
||||
} else {
|
||||
res.map_err(could_not_clear_err)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity, but if there is a running profile, set the activity to that instead
|
||||
pub async fn clear_to_default(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
let state: Arc<tokio::sync::RwLockReadGuard<'_, State>> =
|
||||
State::get().await?;
|
||||
if let Some(existing_child) = state
|
||||
.children
|
||||
.read()
|
||||
.await
|
||||
.running_profile_paths()
|
||||
.await?
|
||||
.first()
|
||||
{
|
||||
self.set_activity(
|
||||
&format!("Playing {}", existing_child),
|
||||
reconnect_if_fail,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.clear_activity(reconnect_if_fail).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,9 @@ pub use self::java_globals::*;
|
||||
mod safe_processes;
|
||||
pub use self::safe_processes::*;
|
||||
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
// Global state
|
||||
// RwLock on state only has concurrent reads, except for config dir change which takes control of the State
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
@ -81,6 +84,9 @@ pub struct State {
|
||||
/// Launcher processes that should be safely exited on shutdown
|
||||
pub(crate) safety_processes: RwLock<SafeProcesses>,
|
||||
|
||||
/// Discord RPC
|
||||
pub discord_rpc: DiscordGuard,
|
||||
|
||||
/// File watcher debouncer
|
||||
pub(crate) file_watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
}
|
||||
@ -156,6 +162,9 @@ impl State {
|
||||
let children = Children::new();
|
||||
let auth_flow = AuthTask::new();
|
||||
let safety_processes = SafeProcesses::new();
|
||||
|
||||
let discord_rpc = DiscordGuard::init().await?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
Ok::<RwLock<Self>, crate::Error>(RwLock::new(Self {
|
||||
@ -175,6 +184,7 @@ impl State {
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
tags: RwLock::new(tags),
|
||||
discord_rpc,
|
||||
safety_processes: RwLock::new(safety_processes),
|
||||
file_watcher: RwLock::new(file_watcher),
|
||||
}))
|
||||
|
||||
@ -149,6 +149,8 @@ pub struct Profile {
|
||||
pub memory: Option<MemorySettings>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<WindowSize>,
|
||||
#[serde(default)]
|
||||
pub force_fullscreen: SetFullscreen,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hooks: Option<Hooks>,
|
||||
pub projects: HashMap<ProjectPathId, Project>,
|
||||
@ -223,6 +225,21 @@ impl ModLoader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
|
||||
pub enum SetFullscreen {
|
||||
#[serde(rename = "Leave unset")]
|
||||
LeaveUnset,
|
||||
#[serde(rename = "Set windowed")]
|
||||
SetWindowed,
|
||||
#[serde(rename = "Set fullscreen")]
|
||||
SetFullscreen,
|
||||
}
|
||||
impl Default for SetFullscreen {
|
||||
fn default() -> Self {
|
||||
Self::LeaveUnset
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JavaSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@ -268,6 +285,7 @@ impl Profile {
|
||||
java: None,
|
||||
memory: None,
|
||||
resolution: None,
|
||||
force_fullscreen: SetFullscreen::LeaveUnset,
|
||||
hooks: None,
|
||||
modrinth_update_version: None,
|
||||
})
|
||||
|
||||
@ -46,11 +46,9 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
];
|
||||
for java_path in java_paths {
|
||||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
||||
for java_subpath in java_subpaths {
|
||||
if let Ok(java_subpath) = java_subpath {
|
||||
let path = java_subpath.path();
|
||||
jre_paths.insert(path.join("bin"));
|
||||
}
|
||||
for java_subpath in java_subpaths.flatten() {
|
||||
let path = java_subpath.path();
|
||||
jre_paths.insert(path.join("bin"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,19 +91,17 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
pub fn get_paths_from_jre_winregkey(jre_key: RegKey) -> HashSet<PathBuf> {
|
||||
let mut jre_paths = HashSet::new();
|
||||
|
||||
for subkey in jre_key.enum_keys() {
|
||||
if let Ok(subkey) = subkey {
|
||||
if let Ok(subkey) = jre_key.open_subkey(subkey) {
|
||||
let subkey_value_names =
|
||||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
||||
for subkey in jre_key.enum_keys().flatten() {
|
||||
if let Ok(subkey) = jre_key.open_subkey(subkey) {
|
||||
let subkey_value_names =
|
||||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
||||
|
||||
for subkey_value in subkey_value_names {
|
||||
let path: Result<String, std::io::Error> =
|
||||
subkey.get_value(subkey_value);
|
||||
let Ok(path) = path else {continue};
|
||||
for subkey_value in subkey_value_names {
|
||||
let path: Result<String, std::io::Error> =
|
||||
subkey.get_value(subkey_value);
|
||||
let Ok(path) = path else {continue};
|
||||
|
||||
jre_paths.insert(PathBuf::from(path).join("bin"));
|
||||
}
|
||||
jre_paths.insert(PathBuf::from(path).join("bin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ pub async fn import_get_importable_instances(
|
||||
}
|
||||
|
||||
/// Import an instance from a launcher type and base path
|
||||
/// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted
|
||||
/// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1")
|
||||
#[tauri::command]
|
||||
pub async fn import_import_instance(
|
||||
|
||||
@ -276,6 +276,7 @@ pub struct EditProfile {
|
||||
pub memory: Option<MemorySettings>,
|
||||
pub resolution: Option<WindowSize>,
|
||||
pub hooks: Option<Hooks>,
|
||||
pub force_fullscreen: Option<SetFullscreen>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -315,6 +316,9 @@ pub async fn profile_edit(
|
||||
prof.java = edit_profile.java.clone();
|
||||
prof.memory = edit_profile.memory;
|
||||
prof.resolution = edit_profile.resolution;
|
||||
if let Some(force_fullscreen) = edit_profile.force_fullscreen {
|
||||
prof.force_fullscreen = force_fullscreen;
|
||||
}
|
||||
prof.hooks = edit_profile.hooks.clone();
|
||||
|
||||
prof.metadata.date_modified = chrono::Utc::now();
|
||||
|
||||
@ -182,6 +182,9 @@
|
||||
<span class="label__title size-card-header">Window</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<DropdownSelect v-model="forceFullscreen" :options="fullscreenOptions" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<Checkbox v-model="overrideWindowSettings" label="Override global window settings" />
|
||||
</div>
|
||||
@ -439,10 +442,12 @@ const maxMemory = Math.floor((await get_max_memory().catch(handleError)) / 1024)
|
||||
|
||||
const overrideWindowSettings = ref(!!props.instance.resolution)
|
||||
const resolution = ref(props.instance.resolution ?? globalSettings.game_resolution)
|
||||
|
||||
const overrideHooks = ref(!!props.instance.hooks)
|
||||
const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
|
||||
|
||||
const fullscreenOptions = ref(['Leave unchanged', 'Set windowed', 'Set fullscreen'])
|
||||
const forceFullscreen = ref(props.instance.force_fullscreen)
|
||||
|
||||
watch(
|
||||
[
|
||||
title,
|
||||
@ -458,6 +463,7 @@ watch(
|
||||
memory,
|
||||
overrideWindowSettings,
|
||||
resolution,
|
||||
forceFullscreen,
|
||||
overrideHooks,
|
||||
hooks,
|
||||
],
|
||||
@ -505,6 +511,10 @@ watch(
|
||||
editProfile.resolution = resolution.value
|
||||
}
|
||||
|
||||
if (forceFullscreen.value) {
|
||||
editProfile.force_fullscreen = forceFullscreen.value
|
||||
}
|
||||
|
||||
if (overrideHooks.value) {
|
||||
editProfile.hooks = hooks.value
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user