Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 1 addition & 90 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
"markdown-it": "^14.3.2",
"pixi.js": "^8.19.0",
"qrcode": "^1.5.4",
"vue": "^3.5.38",
"vue-i18n": "^11.4.6"
"vue": "^3.5.38"
},
"overrides": {
"@vueuse/core": "14.4.0"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ tauri-plugin-single-instance = "2.5.4"
tauri-plugin-global-shortcut = "2.4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
once_cell = "1.21.3"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12.28", features = ["json", "native-tls", "stream", "gzip", "deflate"], default-features = false }
axum = "0.8.8"
tower-http = { version = "0.6.8", features = ["cors"] }
once_cell = "1.21.3"
wmi = "0.18"
quick-xml = { version = "0.37", features = ["serialize"] }
regex = "1.11.3"
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/client_fingerprint_rules.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use futures_util::StreamExt;
use log::{debug, info, warn};
use once_cell::sync::Lazy;
use std::sync::LazyLock;
use reqwest::header::{ETAG, IF_NONE_MATCH};
use std::time::Duration;

Expand All @@ -13,7 +13,7 @@ const MIN_REFRESH_HOURS: u64 = 1;
const MAX_REFRESH_HOURS: u64 = 168;
const CONFIG_RECHECK_INTERVAL: Duration = Duration::from_secs(15 * 60);

static RULE_CLIENT: Lazy<Result<reqwest::Client, String>> = Lazy::new(|| {
static RULE_CLIENT: LazyLock<Result<reqwest::Client, String>> = LazyLock::new(|| {
reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(30))
Expand Down
8 changes: 4 additions & 4 deletions src-tauri/src/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ struct State {
next_token: u32,
}

static AGENT: once_cell::sync::Lazy<Mutex<State>> =
once_cell::sync::Lazy::new(|| Mutex::new(State::default()));
static AGENT: std::sync::LazyLock<Mutex<State>> =
std::sync::LazyLock::new(|| Mutex::new(State::default()));

#[derive(Serialize, Clone)]
pub struct ClipboardStatus {
Expand Down Expand Up @@ -689,8 +689,8 @@ async fn fetch_blob(id: &str) -> Result<(Vec<u8>, String), String> {
/// Retained text payloads of in-flight compound bursts (frames sharing a
/// non-zero token). Text applies on arrival; the image later upgrades the
/// clipboard to one compound write using the retained bytes.
static BURSTS: once_cell::sync::Lazy<Mutex<HashMap<u32, (Vec<u8>, Instant)>>> =
once_cell::sync::Lazy::new(|| Mutex::new(HashMap::new()));
static BURSTS: std::sync::LazyLock<Mutex<HashMap<u32, (Vec<u8>, Instant)>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));

fn prune_bursts(map: &mut HashMap<u32, (Vec<u8>, Instant)>) {
map.retain(|_, (_, ts)| ts.elapsed() < BURST_RETENTION);
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/controllermeta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// 4. 启动 EXE(GUI 进程,不捕获窗口)

use log::{debug, info, warn};
use once_cell::sync::Lazy;
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Mutex;
Expand All @@ -32,7 +32,7 @@ const DOWNLOAD_PROXY_PREFIXES: &[&str] = &[
];

/// 已启动的子进程句柄(仅用于判断是否由我们拉起)
static CHILD_PROCESS: Lazy<Mutex<Option<std::process::Child>>> = Lazy::new(|| Mutex::new(None));
static CHILD_PROCESS: LazyLock<Mutex<Option<std::process::Child>>> = LazyLock::new(|| Mutex::new(None));

// ========== 数据结构 ==========

Expand Down
28 changes: 22 additions & 6 deletions src-tauri/src/dualsense/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use super::packages::classify_local_component_packages;
use super::probe::{
dualsense_get_status_with_config, ensure_no_active_session,
ensure_no_active_session_for_uninstall, run_installed_probe, validate_requested_profile,
StatusProbeReuse, usbip_transport_status,
};
use super::{
COMPONENT_OPERATION, DualSenseStatus, DualSenseTuningResult, MAX_LOCAL_COMPONENT_PACKAGES,
Expand Down Expand Up @@ -52,7 +53,7 @@ pub async fn dualsense_set_haptics_tuning(

#[tauri::command]
pub async fn dualsense_get_status() -> Result<DualSenseStatus, String> {
dualsense_get_status_with_config(None).await
dualsense_get_status_with_config(None, StatusProbeReuse::default()).await
}

#[tauri::command]
Expand Down Expand Up @@ -112,22 +113,37 @@ pub async fn dualsense_set_config(
"DS5-RUN-002: another DualSense component operation is still running".to_string()
})?;
ensure_no_active_session().await?;
// Validation probes double as the status-assembly inputs: the sidecar
// binary cannot change between validation and the final status within
// this command, so reuse them instead of probing twice.
let mut reuse = StatusProbeReuse::default();
if audio_haptics {
let probe = tokio::task::spawn_blocking(run_installed_probe)
.await
.map_err(|error| format!("DS5-PKG-003: sidecar probe task failed: {error}"))??;
let transport = crate::usbip::usbip_get_status().await?;
if !transport.ready && !transport.detail.is_empty() {
return Err(transport.detail);
let (usbip_version, usbip_version_valid, usbip_ready, usbip_error_code, usbip_detail) =
usbip_transport_status(crate::usbip::usbip_get_status().await);
if !usbip_ready && !usbip_detail.is_empty() {
return Err(usbip_detail.clone());
}
let usbip_available = probe.usbip_available && transport.ready;
let usbip_available = probe.usbip_available && usbip_ready;
if genshin_compatibility && !probe.genshin_compatibility_identity {
return Err(
"DS5-PROTO-001: the installed sidecar does not support Genshin compatibility mode"
.to_string(),
);
}
validate_requested_profile(audio_haptics, genshin_compatibility, usbip_available)?;
reuse = StatusProbeReuse {
probe: Some(probe),
transport: Some((
usbip_version,
usbip_version_valid,
usbip_ready,
usbip_error_code,
usbip_detail,
)),
};
} else {
validate_requested_profile(audio_haptics, genshin_compatibility, false)?;
}
Expand All @@ -144,7 +160,7 @@ pub async fn dualsense_set_config(
"DS5-CFG-004: timed out while applying DualSense configuration; the resulting state is unknown"
.to_string()
})??;
dualsense_get_status_with_config(Some(applied.response)).await
dualsense_get_status_with_config(Some(applied.response), reuse).await
}

#[tauri::command]
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/src/dualsense/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub(crate) use elevated::{
};

use log::info;
use once_cell::sync::Lazy;
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
use tauri::Emitter;
Expand Down Expand Up @@ -98,8 +98,8 @@ pub(crate) const COMPONENT_FS_RETRY_ATTEMPTS: usize = 20;
pub(crate) const COMPONENT_FS_RETRY_DELAY: std::time::Duration =
std::time::Duration::from_millis(200);

pub(crate) static COMPONENT_OPERATION: Lazy<tokio::sync::Mutex<()>> =
Lazy::new(|| tokio::sync::Mutex::new(()));
pub(crate) static COMPONENT_OPERATION: LazyLock<tokio::sync::Mutex<()>> =
LazyLock::new(|| tokio::sync::Mutex::new(()));
pub(crate) static LAST_OBSERVED_CONFIG_REVISION: AtomicU64 = AtomicU64::new(0);

pub(crate) fn observe_config_revision(revision: u64) {
Expand Down
28 changes: 22 additions & 6 deletions src-tauri/src/dualsense/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ pub(crate) fn usbip_transport_status(
}

pub(crate) fn local_uninstalled_status() -> DualSenseStatus {
// 前端 useDualSenseComponent 另有一份首次渲染前的占位默认值,
// 与此处的 DualSenseStatus 字段一一对应,改动时两处同步。
let (usbip_version, usbip_version_valid, error_code, detail) =
usbip_discovery_status(installed_usbip_version());
DualSenseStatus {
Expand Down Expand Up @@ -407,9 +409,20 @@ pub(crate) fn validate_requested_profile(
}
}

/// Probe/transport results captured earlier in the same command. The sidecar
/// binary cannot change between validation and status assembly within one
/// command, so `dualsense_set_config` reuses them instead of probing twice.
#[derive(Default)]
pub(crate) struct StatusProbeReuse {
pub(crate) probe: Option<ProbeResult>,
/// `usbip_transport_status` output: (version, version_valid, ready, code, detail).
pub(crate) transport: Option<(String, bool, bool, String, String)>,
}

/// Combines component integrity, runtime capabilities, shared transport and Core settings.
pub(crate) async fn dualsense_get_status_with_config(
confirmed: Option<CoreDualSenseResponse>,
reuse: StatusProbeReuse,
) -> Result<DualSenseStatus, String> {
let (settings, config_revision, config_error) =
resolve_core_config(confirmed, get_core_ds5_settings).await;
Expand All @@ -420,15 +433,15 @@ pub(crate) async fn dualsense_get_status_with_config(
let executable = sidecar_path();
let installed = executable.is_file();
let in_use = has_active_session().await;
let probe = if installed {
Some(
let probe = match reuse.probe {
Some(result) => Some(Ok(result)),
None if installed => Some(
tokio::task::spawn_blocking(run_installed_probe)
.await
.map_err(|error| format!("DS5-PKG-003: sidecar probe task failed: {error}"))
.and_then(|result| result),
)
} else {
None
),
None => None,
};
let (probe_succeeded, result, mut error_code, mut detail) = match probe {
Some(Ok(result)) => (true, result, String::new(), String::new()),
Expand All @@ -453,7 +466,10 @@ pub(crate) async fn dualsense_get_status_with_config(
// A valid uninstall registration does not prove that the active driver
// accepts the installed tools. Share the transport panel's actual probe.
let (usbip_version, usbip_version_valid, usbip_ready, usbip_error_code, usbip_detail) =
usbip_transport_status(crate::usbip::usbip_get_status().await);
match reuse.transport {
Some(transport) => transport,
None => usbip_transport_status(crate::usbip::usbip_get_status().await),
};
let usbip_available = result.usbip_available && usbip_ready;
let manifest = installed.then(installed_component_manifest).flatten();
let update_available = installed && component_update_available(manifest.as_ref());
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/dualsense/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ fn usbip_transport_probe_rejects_a_broken_driver_with_a_valid_registration() {
version_valid: true,
reboot_recommended: false,
vhci_residual: false,
error_code: "USBIP-EXEC-006".to_string(),
attached_devices: Vec::new(),
detail: detail.to_string(),
};
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/elevation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ static HELPER_JOB: once_cell::sync::OnceCell<std::os::windows::io::OwnedHandle>
/// Bind the elevated helper and every process it launches to one lifetime
/// job. Windows kills the whole tree when this process's handles close, so
/// grandchildren (installers, downloaders) can never outlive the helper.
/// once_cell 的 get_or_try_init 在这里是库级保证的可失败惰性初始化:
/// 失败不缓存、下次调用重试——不手写双检样板。
pub(crate) fn bind_lifetime_job(error_code: &str) -> Result<(), String> {
use std::os::windows::io::{AsRawHandle, FromRawHandle};
use windows::Win32::Foundation::HANDLE;
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ impl Log for TauriLogger {
}
}

static LOG_COLLECTOR: once_cell::sync::OnceCell<Arc<LogCollector>> =
once_cell::sync::OnceCell::new();
static LOG_COLLECTOR: std::sync::OnceLock<Arc<LogCollector>> =
std::sync::OnceLock::new();
static LOG_LEVEL_FILTER: AtomicU8 = AtomicU8::new(3);

fn level_to_u8(level: LevelFilter) -> u8 {
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/src/moonlight_web.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use log::{debug, info, warn};
use once_cell::sync::Lazy;
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Mutex;
Expand All @@ -15,10 +15,10 @@ const PROCESS_CHECK_NAME: &str = "web-server.exe";
// ========== 状态管理 ==========

/// 子进程句柄(由控制面板启动时持有)
static CHILD_PROCESS: Lazy<Mutex<Option<std::process::Child>>> = Lazy::new(|| Mutex::new(None));
static CHILD_PROCESS: LazyLock<Mutex<Option<std::process::Child>>> = LazyLock::new(|| Mutex::new(None));

/// 当前配置的绑定端口
static BIND_PORT: Lazy<Mutex<u16>> = Lazy::new(|| Mutex::new(DEFAULT_BIND_PORT));
static BIND_PORT: LazyLock<Mutex<u16>> = LazyLock::new(|| Mutex::new(DEFAULT_BIND_PORT));

// ========== 数据结构 ==========

Expand Down
Loading
Loading