Files
Genarrative/apps/desktop-shell/src-tauri/src/shell/lifecycle.rs
T
kdletters a471e69455 补齐桌面壳后台生命周期
桌面壳生命周期按可见性、最小化和焦点统一归一

托盘隐藏前注入 background 并在恢复窗口时回放当前状态

H5 lifecycle 测试覆盖 background 暂停语义

宿主壳文档和原生壳门禁同步桌面 background 约定
2026-06-19 04:25:42 +08:00

160 lines
4.5 KiB
Rust

use crate::shell::events::host_bridge_event_script;
use crate::shell::navigation::register_desktop_navigation_events;
use crate::shell::network::register_desktop_network_events;
use serde_json::json;
use tauri::webview::PageLoadEvent;
use tauri::{WebviewWindow, WindowEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DesktopLifecyclePayload {
state: &'static str,
focused: bool,
native_state: &'static str,
}
pub(crate) fn resolve_desktop_lifecycle_payload(
focused: bool,
visible: bool,
minimized: bool,
) -> DesktopLifecyclePayload {
if !visible {
return DesktopLifecyclePayload {
state: "background",
focused: false,
native_state: "hidden",
};
}
if minimized {
return DesktopLifecyclePayload {
state: "background",
focused: false,
native_state: "minimized",
};
}
if focused {
DesktopLifecyclePayload {
state: "active",
focused: true,
native_state: "focused",
}
} else {
DesktopLifecyclePayload {
state: "inactive",
focused: false,
native_state: "blurred",
}
}
}
pub(crate) fn emit_desktop_lifecycle_event(
window: &WebviewWindow,
state: &'static str,
focused: bool,
native_state: &'static str,
) -> tauri::Result<()> {
let script = host_bridge_event_script(
"app.lifecycle",
json!({
"state": state,
"focused": focused,
"nativeState": native_state,
}),
)
.map_err(tauri::Error::Json)?;
window.eval(script)
}
pub(crate) fn emit_desktop_lifecycle_payload(
window: &WebviewWindow,
payload: DesktopLifecyclePayload,
) -> tauri::Result<()> {
emit_desktop_lifecycle_event(window, payload.state, payload.focused, payload.native_state)
}
pub(crate) fn emit_current_desktop_lifecycle_event(window: &WebviewWindow) -> tauri::Result<()> {
let visible = window.is_visible().unwrap_or(true);
let minimized = window.is_minimized().unwrap_or(false);
let focused = window.is_focused().unwrap_or(visible && !minimized);
emit_desktop_lifecycle_payload(
window,
resolve_desktop_lifecycle_payload(focused, visible, minimized),
)
}
pub(crate) fn register_desktop_lifecycle_events(window: &WebviewWindow) {
let lifecycle_window = window.clone();
window.on_window_event(move |event| {
if matches!(event, WindowEvent::Focused(_) | WindowEvent::Resized(_)) {
let _ = emit_current_desktop_lifecycle_event(&lifecycle_window);
}
});
}
pub(crate) fn should_replay_desktop_webview_state_on_page_load(event: PageLoadEvent) -> bool {
event == PageLoadEvent::Finished
}
pub(crate) fn replay_desktop_webview_state(window: &WebviewWindow) {
let _ = emit_current_desktop_lifecycle_event(window);
let _ = register_desktop_network_events(window);
let _ = register_desktop_navigation_events(window);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn desktop_page_load_replays_state_only_after_finished_load() {
assert!(should_replay_desktop_webview_state_on_page_load(
PageLoadEvent::Finished
));
assert!(!should_replay_desktop_webview_state_on_page_load(
PageLoadEvent::Started
));
}
#[test]
fn desktop_lifecycle_payload_maps_hidden_and_minimized_to_background() {
assert_eq!(
resolve_desktop_lifecycle_payload(true, false, false),
DesktopLifecyclePayload {
state: "background",
focused: false,
native_state: "hidden",
}
);
assert_eq!(
resolve_desktop_lifecycle_payload(true, true, true),
DesktopLifecyclePayload {
state: "background",
focused: false,
native_state: "minimized",
}
);
}
#[test]
fn desktop_lifecycle_payload_maps_visible_focus_state() {
assert_eq!(
resolve_desktop_lifecycle_payload(true, true, false),
DesktopLifecyclePayload {
state: "active",
focused: true,
native_state: "focused",
}
);
assert_eq!(
resolve_desktop_lifecycle_payload(false, true, false),
DesktopLifecyclePayload {
state: "inactive",
focused: false,
native_state: "blurred",
}
);
}
}