You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user