lingxia_platform/traits/screenshot.rs
1use crate::error::PlatformError;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4
5/// Description of a top-level window belonging to the host app.
6///
7/// Returned by [`AppScreenshot::list_app_windows`]. The `id` is opaque
8/// platform-specific (macOS NSWindow.windowNumber, Windows HWND, etc.) and
9/// is the value to pass back to [`AppScreenshot::take_app_screenshot`] to
10/// target that specific window.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct WindowInfo {
13 /// Opaque platform-specific identifier (stringified for portability).
14 pub id: String,
15 /// Window title (may be empty if the platform / app does not set one).
16 pub title: String,
17 /// `true` if this window currently has keyboard focus / is "key".
18 pub focused: bool,
19 /// `true` if this window is the app's main window (macOS concept).
20 pub main: bool,
21 /// `true` if the window is currently on-screen / not minimized.
22 pub visible: bool,
23 /// Width in the platform's window-content coordinate unit (points on
24 /// Apple platforms, client pixels on Windows).
25 pub width: u32,
26 /// Height in the platform's window-content coordinate unit.
27 pub height: u32,
28}
29
30/// Capture a PNG snapshot of the host app's window(s).
31///
32/// Conceptually one level above `WebViewController::take_screenshot`: the
33/// WebView API only sees web content, while this captures the entire window
34/// the host renders — host-drawn navigation bars, native overlays, multiple
35/// WebViews composited together, etc.
36#[async_trait]
37pub trait AppScreenshot: Send + Sync {
38 /// Enumerate the app's top-level windows.
39 ///
40 /// Mobile platforms typically return a single "main" entry. Desktop
41 /// platforms return one entry per open NSWindow / HWND.
42 async fn list_app_windows(&self) -> Result<Vec<WindowInfo>, PlatformError> {
43 Err(PlatformError::NotSupported(
44 "list_app_windows is not implemented for this platform".to_string(),
45 ))
46 }
47
48 /// Resolve an optional id using the same default-window policy as capture
49 /// and input, returning the concrete target for automation metadata.
50 async fn resolve_app_window(
51 &self,
52 window_id: Option<&str>,
53 ) -> Result<WindowInfo, PlatformError> {
54 let windows = self.list_app_windows().await?;
55 if let Some(window_id) = window_id {
56 return windows
57 .into_iter()
58 .find(|window| window.id == window_id)
59 .ok_or_else(|| {
60 PlatformError::InvalidParameter(format!(
61 "window id does not belong to this app: {window_id}"
62 ))
63 });
64 }
65 windows
66 .iter()
67 .find(|window| window.focused && window.visible)
68 .or_else(|| windows.iter().find(|window| window.main && window.visible))
69 .or_else(|| windows.iter().find(|window| window.visible))
70 .or_else(|| windows.first())
71 .cloned()
72 .ok_or_else(|| PlatformError::Platform("no app window is available".to_string()))
73 }
74
75 /// Capture and return PNG-encoded bytes of the app's window.
76 ///
77 /// When `window_id` is `None`, the platform picks a sensible default
78 /// (key/focused window on desktop; the sole window on mobile).
79 async fn take_app_screenshot(&self, window_id: Option<&str>) -> Result<Vec<u8>, PlatformError> {
80 let _ = window_id;
81 Err(PlatformError::NotSupported(
82 "app screenshot is not implemented for this platform".to_string(),
83 ))
84 }
85}