rdi_core/backend.rs
1//! The backend abstraction that the animation engine drives.
2//!
3//! Every backend runs on the engine's dedicated worker thread and is
4//! therefore free to hold non-`Sync` platform state (COM interfaces, PIDL
5//! caches, GPU devices, etc.). The trait only requires `Send + 'static`
6//! so the worker thread can own it exclusively.
7
8use crate::{
9 DesktopError, FinalCommitOutcome, IconId, IconRenderPlan, IconSnapshot, MonitorInfo,
10 OverlayRenderOptions, Point, SnapshotFrame,
11};
12
13/// The single point of contact between the animation engine and the OS
14/// desktop.
15///
16/// # Threading
17///
18/// A `DesktopBackend` is **owned** by the engine's worker thread and is
19/// never accessed concurrently. It only needs to be `Send`, not `Sync`.
20///
21/// # Method contracts
22///
23/// * [`list_icons`] returns a snapshot of every icon currently visible
24/// on the desktop. Order is unspecified.
25/// * [`get_flags`] returns the desktop folder-view flags word verbatim.
26/// * [`apply_flags(mask, values)`] performs a masked update:
27/// `new_flags = (old_flags & !mask) | (values & mask)` — matching the
28/// `IFolderView2::SetCurrentFolderFlags` semantics used by the legacy
29/// Windows implementation.
30/// * [`set_positions`](DesktopBackend::set_positions) commits the entire batch atomically (from
31/// the caller's perspective) and returns the subset of ids that could
32/// not be resolved by the backend so the engine can drop them from the
33/// active set. Used by the direct-positioning API **and** by the
34/// engine's overlay-unavailable fallback path.
35/// * [`list_monitors`] returns every connected display in the virtual-
36/// screen coordinate system icon positions use. Order is unspecified
37/// but the primary monitor always has `is_primary = true`.
38/// * [`begin_overlay_session`] / [`commit_overlay_frame`] /
39/// [`finalize_overlay_session`] together drive the overlay-based
40/// animation flow — see the trio's individual docs.
41///
42/// [`list_icons`]: DesktopBackend::list_icons
43/// [`get_flags`]: DesktopBackend::get_flags
44/// [`apply_flags`]: DesktopBackend::apply_flags
45/// [`set_positions`]: DesktopBackend::set_positions
46/// [`list_monitors`]: DesktopBackend::list_monitors
47/// [`begin_overlay_session`]: DesktopBackend::begin_overlay_session
48/// [`commit_overlay_frame`]: DesktopBackend::commit_overlay_frame
49/// [`finalize_overlay_session`]: DesktopBackend::finalize_overlay_session
50pub trait DesktopBackend: Send + 'static {
51 fn list_icons(&mut self) -> Result<Vec<IconSnapshot>, DesktopError>;
52
53 fn get_flags(&mut self) -> Result<u32, DesktopError>;
54
55 fn apply_flags(&mut self, mask: u32, values: u32) -> Result<(), DesktopError>;
56
57 /// Commit a batch of `(id, new_position)` moves. Returns the ids the
58 /// backend could not resolve (i.e. icons that vanished or were never
59 /// on the desktop).
60 fn set_positions(
61 &mut self,
62 moves: &[(IconId, Point)],
63 ) -> Result<Vec<IconId>, DesktopError>;
64
65 /// Enumerate every connected display. Bounds are reported in the
66 /// same virtual-screen coordinate system used by
67 /// [`list_icons`](Self::list_icons) and
68 /// [`set_positions`](Self::set_positions).
69 fn list_monitors(&mut self) -> Result<Vec<MonitorInfo>, DesktopError>;
70
71 /// Query live display/grid metrics using the worker's current icon snapshot.
72 /// Must not reposition icons or change folder flags.
73 fn desktop_info(&mut self, _icons: &[IconSnapshot]) -> Result<crate::DesktopInfo, DesktopError> {
74 Err(DesktopError::UnsupportedPlatform)
75 }
76
77 // -----------------------------------------------------------------
78 // Overlay-based animation.
79 // -----------------------------------------------------------------
80
81 /// Prepare an overlay animation session covering the icons in `plans`.
82 ///
83 /// The backend acquires whatever OS resources it needs (window,
84 /// renderer, icon bitmaps), pre-renders the first frame at each
85 /// icon's *source* position, and returns success only after the
86 /// overlay is ready to display that first frame.
87 ///
88 /// On success the backend has **not yet**:
89 /// * shown the overlay window,
90 /// * hidden the real desktop icons.
91 ///
92 /// Both happen implicitly on the first call to
93 /// [`commit_overlay_frame`](Self::commit_overlay_frame).
94 ///
95 /// # Errors
96 /// * [`DesktopError::OverlayUnavailable`] — the platform doesn't
97 /// support overlay rendering, or a documented compatibility probe
98 /// failed. The engine handles this by taking the loud-warning
99 /// fallback path.
100 /// * any other `DesktopError` — engine surfaces as
101 /// [`FinishReason::Error`](crate::events::FinishReason::Error)
102 /// and aborts the animation without moving the icons.
103 fn begin_overlay_session(
104 &mut self,
105 plans: &[IconRenderPlan],
106 render_options: OverlayRenderOptions,
107 ) -> Result<(), DesktopError>;
108
109 /// Update the overlay's rendered icon positions to `positions`.
110 ///
111 /// The first call to this method also, atomically from the caller's
112 /// perspective:
113 /// * shows the overlay window,
114 /// * waits for at least one composition frame to reach the display,
115 /// * hides the real desktop icons via the Shell's own visibility
116 /// mechanism.
117 ///
118 /// Subsequent calls only update the overlay's icon-copy positions
119 /// and re-present. **No** contact is made with `IFolderView2`.
120 fn commit_overlay_frame(
121 &mut self,
122 positions: &[(IconId, Point)],
123 ) -> Result<(), DesktopError>;
124
125 /// Commit positions and visual clocks together. Non-GPU backends ignore visuals.
126 fn commit_visual_frame(&mut self, frame: &[crate::IconFrame]) -> Result<(), DesktopError> {
127 let positions: Vec<_> = frame.iter().map(|entry| (entry.id.clone(), entry.position)).collect();
128 self.commit_overlay_frame(&positions)
129 }
130
131 /// Discard a prepared, never-started session without touching Shell positions.
132 fn discard_overlay_session(&mut self) {}
133
134 /// Reject a prepared snapshot invalidated by environment changes, before any Shell writes.
135 fn validate_prepared_session(&mut self) -> Result<(), DesktopError> { Ok(()) }
136
137 fn prepare_scene_renderer(&mut self, _canvas: crate::Canvas, _plans: &[IconRenderPlan], _options: OverlayRenderOptions) -> Result<Box<dyn crate::SceneRenderer>, DesktopError> {
138 Err(DesktopError::UnsupportedPlatform)
139 }
140
141 fn capture_overlay(&mut self, _seconds: f64) -> Result<crate::CapturedFrame, DesktopError> {
142 Err(DesktopError::UnsupportedPlatform)
143 }
144
145 fn set_real_icons_visible(&mut self, _visible: bool) -> Result<(), DesktopError> {
146 Err(DesktopError::UnsupportedPlatform)
147 }
148
149 fn poll_overlay_session(&mut self) -> Result<(), DesktopError> {
150 self.validate_prepared_session()
151 }
152
153 /// End the animation session and commit the final positions to the
154 /// Shell.
155 ///
156 /// Sequence:
157 /// 1. `IFolderView2::SelectAndPositionItems(final_positions,
158 /// SVSI_POSITIONITEM)` — one call, all icons, matching legacy.
159 /// 2. Poll `IFolderView2::GetItemPosition` until at least one
160 /// moved icon reports its new position (up to a short timeout).
161 /// 3. Restore real-icon visibility.
162 /// 4. Force one desktop repaint.
163 /// 5. Hide + destroy the overlay window.
164 ///
165 /// This method **must** clear any Shell-side icon-hiding flag it
166 /// applied, even on failure paths — the engine relies on this
167 /// invariant to guarantee the real icons come back after an
168 /// animation.
169 fn finalize_overlay_session(
170 &mut self,
171 final_positions: &[(IconId, Point)],
172 ) -> Result<FinalCommitOutcome, DesktopError>;
173
174 /// Render one frame of the overlay off-screen and return the raw
175 /// pixels — never touches an `HWND`, DirectComposition target, or
176 /// the real desktop. Safe to call at any time, including while a
177 /// [`begin_overlay_session`](Self::begin_overlay_session) is
178 /// active on another animation.
179 ///
180 /// `positions` are in **overlay-local pixel coordinates** — the
181 /// caller is responsible for translating from virtual-screen
182 /// coordinates if needed (subtract the overlay's origin). Only
183 /// icons whose id is present in `positions` are drawn; ids not
184 /// found in the backend's caches (never `list_icons`'d, or the
185 /// icon vanished) render as placeholder tiles.
186 ///
187 /// `dpi_scale` is the scale factor to render at (`1.0` → 96 DPI,
188 /// `2.5` → 240 DPI). Icon-bitmap sizing follows the
189 /// backend-supplied `IconRenderPlan::size_px`; the DPI setting
190 /// only affects DirectWrite text hinting.
191 ///
192 /// Default implementation returns
193 /// [`DesktopError::OverlayUnavailable`] — non-rendering backends
194 /// (`FakeBackend`, `StubBackend`) inherit this.
195 fn render_overlay_snapshot(
196 &mut self,
197 width_px: u32,
198 height_px: u32,
199 dpi_scale: f32,
200 positions: &[(IconId, Point)],
201 render_options: OverlayRenderOptions,
202 ) -> Result<SnapshotFrame, DesktopError> {
203 let _ = (width_px, height_px, dpi_scale, positions, render_options);
204 Err(DesktopError::OverlayUnavailable(
205 "render_overlay_snapshot is not implemented for this backend".into(),
206 ))
207 }
208}