minor refactoring of app startup sequence & some extra trivial matters

This commit is contained in:
2026-02-03 22:28:05 +08:00
parent e15cf16817
commit f696d5e385
24 changed files with 284 additions and 337 deletions

View File

@@ -756,8 +756,10 @@ mod windows_impl {
pub static ACTIVE_APP_CHANGED: &str = "active-app-changed";
/// Initializes the active app change listener and emits events to the Tauri app on changes.
pub fn init_active_app_changes_listener() {
/// Initializes the foreground app change listener
/// and emits events to the Tauri app on changes.
/// Used for app to emit user foreground app to peers.
pub fn init_foreground_app_change_listener() {
let app_handle = get_app_handle();
listen_for_active_app_changes(|app_names: AppMetadata| {
if let Err(e) = app_handle.emit(ACTIVE_APP_CHANGED, app_names) {

View File

@@ -114,7 +114,7 @@ fn generate_code_challenge(code_verifier: &str) -> String {
/// Returns the auth pass object, including
/// access token, refresh token, expire time etc.
/// Automatically refreshes if expired.
pub async fn get_tokens() -> Option<AuthPass> {
pub async fn get_session_token() -> Option<AuthPass> {
info!("Retrieving tokens");
let Some(auth_pass) = ({ lock_r!(FDOLL).auth.auth_pass.clone() }) else {
return None;
@@ -173,7 +173,7 @@ pub async fn get_tokens() -> Option<AuthPass> {
/// Helper function to get the current access token.
pub async fn get_access_token() -> Option<String> {
get_tokens().await.map(|pass| pass.access_token)
get_session_token().await.map(|pass| pass.access_token)
}
/// Save auth_pass to secure storage (keyring) and update app state.
@@ -367,7 +367,7 @@ pub fn clear_auth_pass() -> Result<(), OAuthError> {
/// ```
pub fn logout() -> Result<(), OAuthError> {
info!("Logging out user");
lock_w!(FDOLL).auth.auth_pass = None;
lock_w!(FDOLL).auth.auth_pass = None;
clear_auth_pass()?;
// Clear OAuth flow state as well
@@ -386,8 +386,16 @@ pub async fn logout_and_restart() -> Result<(), OAuthError> {
let (refresh_token, session_state, base_url) = {
let guard = lock_r!(FDOLL);
(
guard.auth.auth_pass.as_ref().map(|p| p.refresh_token.clone()),
guard.auth.auth_pass.as_ref().map(|p| p.session_state.clone()),
guard
.auth
.auth_pass
.as_ref()
.map(|p| p.refresh_token.clone()),
guard
.auth
.auth_pass
.as_ref()
.map(|p| p.session_state.clone()),
guard
.app_config
.api_base_url
@@ -676,7 +684,7 @@ where
{
let mut guard = lock_w!(FDOLL);
guard.auth.auth_pass = Some(auth_pass.clone());
guard.auth.oauth_flow = Default::default();
guard.auth.oauth_flow = Default::default();
}
if let Err(e) = save_auth_pass(&auth_pass) {
error!("Failed to save auth pass: {}", e);
@@ -720,7 +728,8 @@ pub async fn refresh_token(refresh_token: &str) -> Result<AuthPass, OAuthError>
(
guard.app_config.clone(),
guard
.network.clients
.network
.clients
.as_ref()
.expect("clients present")
.http_client

View File

@@ -40,8 +40,8 @@ pub fn get_latest_cursor_position() -> Option<CursorPosition> {
/// Convert absolute screen coordinates to normalized coordinates (0.0 - 1.0)
pub fn absolute_to_normalized(pos: &CursorPosition) -> CursorPosition {
let guard = lock_r!(FDOLL);
let screen_w = guard.ui.app_data.scene.display.screen_width as f64;
let screen_h = guard.ui.app_data.scene.display.screen_height as f64;
let screen_w = guard.user_data.scene.display.screen_width as f64;
let screen_h = guard.user_data.scene.display.screen_height as f64;
CursorPosition {
x: (pos.x / screen_w).clamp(0.0, 1.0),
@@ -52,8 +52,8 @@ pub fn absolute_to_normalized(pos: &CursorPosition) -> CursorPosition {
/// Convert normalized coordinates to absolute screen coordinates
pub fn normalized_to_absolute(normalized: &CursorPosition) -> CursorPosition {
let guard = lock_r!(FDOLL);
let screen_w = guard.ui.app_data.scene.display.screen_width as f64;
let screen_h = guard.ui.app_data.scene.display.screen_height as f64;
let screen_w = guard.user_data.scene.display.screen_width as f64;
let screen_h = guard.user_data.scene.display.screen_height as f64;
CursorPosition {
x: (normalized.x * screen_w).round(),
@@ -61,10 +61,9 @@ pub fn normalized_to_absolute(normalized: &CursorPosition) -> CursorPosition {
}
}
/// Initialize cursor tracking - can be called multiple times safely from any window
/// Only the first call will actually start tracking, subsequent calls are no-ops
#[tauri::command]
pub async fn start_cursor_tracking() -> Result<(), String> {
/// Initialize cursor tracking. Broadcasts cursor
/// position changes via `cursor-position` event.
pub async fn init_cursor_tracking() {
info!("start_cursor_tracking called");
// Use OnceCell to ensure this only runs once, even if called from multiple windows
@@ -74,17 +73,16 @@ pub async fn start_cursor_tracking() -> Result<(), String> {
info!("First call to start_cursor_tracking - spawning cursor tracking task");
tauri::async_runtime::spawn(async {
if let Err(e) = init_cursor_tracking().await {
if let Err(e) = init_cursor_tracking_i().await {
error!("Failed to initialize cursor tracking: {}", e);
}
});
});
info!("Cursor tracking initialization registered");
Ok(())
}
async fn init_cursor_tracking() -> Result<(), String> {
async fn init_cursor_tracking_i() -> Result<(), String> {
info!("Initializing cursor tracking...");
// Create a channel to decouple event generation (producer) from processing (consumer).
@@ -124,7 +122,7 @@ async fn init_cursor_tracking() -> Result<(), String> {
#[cfg(target_os = "windows")]
let scale_factor = {
let guard = lock_r!(FDOLL);
guard.ui.app_data.scene.display.monitor_scale_factor
guard.user_data.scene.display.monitor_scale_factor
};
// The producer closure moves `tx` into it.

View File

@@ -1,36 +1,15 @@
use crate::get_app_handle;
use crate::{lock_r, state::FDOLL, system_tray::update_system_tray};
use tauri::{Emitter, Manager};
use tauri::Manager;
use tauri_plugin_dialog::{DialogExt, MessageDialogBuilder, MessageDialogKind};
use tauri_plugin_positioner::WindowExt;
use tracing::{error, info};
pub static HEALTH_MANAGER_WINDOW_LABEL: &str = "health_manager";
pub static HEALTH_MANAGER_EVENT: &str = "health-error";
fn close_window_if_exists(label: &str) {
let app_handle = get_app_handle();
if let Some(window) = app_handle.get_window(label) {
info!("Closing window with label: {}", label);
if let Err(e) = window.close() {
error!("Failed to close {} window: {}", label, e);
} else {
info!("Closed window with label: {}", label);
}
} else {
info!("No window found with label: {}", label);
}
}
/// Closes primary UI windows and shows the health manager with an optional error message.
pub fn show_health_manager_with_error(error_message: Option<String>) {
pub fn open_health_manager_window(error_message: Option<String>) {
let app_handle = get_app_handle();
// Ensure other windows are closed before showing health manager
close_window_if_exists(crate::services::scene::SPLASH_WINDOW_LABEL);
close_window_if_exists(crate::services::scene::SCENE_WINDOW_LABEL);
close_window_if_exists(crate::services::app_menu::APP_MENU_WINDOW_LABEL);
update_system_tray(false);
let existing_webview_window = app_handle.get_window(HEALTH_MANAGER_WINDOW_LABEL);
@@ -45,12 +24,6 @@ pub fn show_health_manager_with_error(error_message: Option<String>) {
.kind(MessageDialogKind::Error)
.show(|_| {});
}
if let Some(message) = error_message {
if let Err(e) = window.emit(HEALTH_MANAGER_EVENT, message.clone()) {
error!("Failed to emit health error event: {}", e);
}
}
return;
}
@@ -58,7 +31,13 @@ pub fn show_health_manager_with_error(error_message: Option<String>) {
let webview_window = match tauri::WebviewWindowBuilder::new(
app_handle,
HEALTH_MANAGER_WINDOW_LABEL,
tauri::WebviewUrl::App("/health-manager".into()),
tauri::WebviewUrl::App(
format!(
"/health-manager?err={}",
error_message.unwrap_or(String::from("Something went wrong!"))
)
.into(),
),
)
.title("Health Manager")
.inner_size(420.0, 420.0)
@@ -89,12 +68,6 @@ pub fn show_health_manager_with_error(error_message: Option<String>) {
error!("Failed to move health manager window to center: {}", e);
}
if let Some(message) = error_message {
if let Err(e) = webview_window.emit(HEALTH_MANAGER_EVENT, message.clone()) {
error!("Failed to emit health error event: {}", e);
}
}
if let Err(e) = webview_window.show() {
error!("Failed to show health manager window: {}", e);
MessageDialogBuilder::new(
@@ -117,7 +90,7 @@ pub fn close_health_manager_window() {
} else {
info!("Health manager window closed");
let guard = lock_r!(FDOLL);
let is_logged_in = guard.ui.app_data.user.is_some();
let is_logged_in = guard.user_data.user.is_some();
drop(guard);
update_system_tray(is_logged_in);
}

View File

@@ -1,3 +1,7 @@
use tauri::Manager;
use crate::get_app_handle;
pub mod active_app;
pub mod app_menu;
pub mod auth;
@@ -10,3 +14,11 @@ pub mod scene;
pub mod sprite_recolor;
pub mod welcome;
pub mod ws;
pub fn close_all_windows() {
let app_handle = get_app_handle();
let webview_windows = app_handle.webview_windows();
for window in webview_windows {
window.1.close().unwrap();
}
}

View File

@@ -1,15 +1,32 @@
use std::time::Duration;
use rust_socketio::ClientBuilder;
use tauri::async_runtime;
use tokio::time::sleep;
use tracing::{error, info};
use crate::{
lock_r, lock_w,
services::client_config_manager::AppConfig,
services::{auth::get_access_token, client_config_manager::AppConfig},
state::FDOLL,
};
use super::handlers;
pub async fn establish_websocket_connection() {
const MAX_ATTEMPTS: u8 = 5;
const BACKOFF: Duration = Duration::from_millis(300);
for _attempt in 1..=MAX_ATTEMPTS {
if get_access_token().await.is_some() {
init_ws_client().await;
return;
}
sleep(BACKOFF).await;
}
}
pub async fn init_ws_client() {
let app_config = {
let guard = lock_r!(FDOLL);
@@ -26,16 +43,19 @@ pub async fn init_ws_client() {
}
Err(e) => {
error!("Failed to initialize WebSocket client: {}", e);
// If we failed because no token, clear the WS client to avoid stale retries
let mut guard = lock_w!(FDOLL);
if let Some(clients) = guard.network.clients.as_mut() {
clients.ws_client = None;
clients.is_ws_initialized = false;
}
clear_ws_client().await;
}
}
}
pub async fn clear_ws_client() {
let mut guard = lock_w!(FDOLL);
if let Some(clients) = guard.network.clients.as_mut() {
clients.ws_client = None;
clients.is_ws_initialized = false;
}
}
pub async fn build_ws_client(
app_config: &AppConfig,
) -> Result<rust_socketio::client::Client, String> {

View File

@@ -3,8 +3,7 @@ use tauri::async_runtime;
use tracing::error;
use crate::{
lock_r,
services::{cursor::CursorPosition, health_manager::show_health_manager_with_error},
init::lifecycle::handle_disasterous_failure, lock_r, services::cursor::CursorPosition,
state::FDOLL,
};
@@ -41,11 +40,11 @@ pub async fn report_cursor_data(cursor_position: CursorPosition) {
Ok(Ok(_)) => (),
Ok(Err(e)) => {
error!("Failed to emit cursor report: {}", e);
show_health_manager_with_error(Some(format!("WebSocket emit failed: {}", e)));
handle_disasterous_failure(Some(format!("WebSocket emit failed: {}", e))).await;
}
Err(e) => {
error!("Failed to execute blocking task for cursor report: {}", e);
show_health_manager_with_error(Some(format!("WebSocket task failed: {}", e)));
handle_disasterous_failure(Some(format!("WebSocket task failed: {}", e))).await;
}
}
}

View File

@@ -36,8 +36,7 @@ pub fn on_doll_updated(payload: Payload, _socket: RawClient) {
let is_active_doll = if let Some(id) = doll_id {
let guard = lock_r!(FDOLL);
guard
.ui
.app_data
.user_data
.user
.as_ref()
.and_then(|u| u.active_doll_id.as_ref())
@@ -75,8 +74,7 @@ pub fn on_doll_deleted(payload: Payload, _socket: RawClient) {
let is_active_doll = if let Some(id) = doll_id {
let guard = lock_r!(FDOLL);
guard
.ui
.app_data
.user_data
.user
.as_ref()
.and_then(|u| u.active_doll_id.as_ref())

View File

@@ -41,7 +41,7 @@ impl WS_EVENT {
pub const CLIENT_SEND_INTERACTION: &str = "client-send-interaction";
}
mod client;
pub mod client;
mod connection;
mod cursor;
mod doll;
@@ -50,6 +50,5 @@ mod handlers;
mod interaction;
mod user_status;
pub use client::init_ws_client;
pub use cursor::report_cursor_data;
pub use user_status::{report_user_status, UserStatusPayload};

View File

@@ -1,12 +1,12 @@
use once_cell::sync::Lazy;
use rust_socketio::Payload;
use tauri::async_runtime;
use tauri::async_runtime::{self};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tracing::error;
use crate::{lock_r, services::health_manager::show_health_manager_with_error, state::FDOLL};
use crate::{init::lifecycle::handle_disasterous_failure, lock_r, state::FDOLL};
use super::WS_EVENT;
@@ -66,20 +66,16 @@ pub async fn report_user_status(status: UserStatusPayload) {
Ok(Ok(_)) => (),
Ok(Err(e)) => {
error!("Failed to emit user status report: {}", e);
show_health_manager_with_error(Some(format!(
"WebSocket emit failed: {}",
e
)));
handle_disasterous_failure(Some(format!("WebSocket emit failed: {}", e)))
.await;
}
Err(e) => {
error!(
"Failed to execute blocking task for user status report: {}",
e
);
show_health_manager_with_error(Some(format!(
"WebSocket task failed: {}",
e
)));
handle_disasterous_failure(Some(format!("WebSocket task failed: {}", e)))
.await;
}
}
}