Skip to main content

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}