1use std::sync::Mutex;
9use std::thread::{self, JoinHandle};
10
11use crossbeam_channel::{bounded, unbounded, Sender};
12
13use crate::backend::DesktopBackend;
14use crate::engine::{worker_main, WorkerMsg};
15use crate::handle::{AnimationHandle, PreObservers};
16use crate::spec::{AnimationOptions, IconAnimationSpec};
17use crate::{DesktopError, IconId, IconSnapshot, MonitorInfo, OverlayRenderOptions, Point, SnapshotFrame};
18
19pub struct DesktopController {
25 tx: Sender<WorkerMsg>,
26 worker: Mutex<Option<JoinHandle<()>>>,
27}
28
29pub struct PreparedAnimation {
32 trigger: Sender<crate::engine::StartMode>,
33 handle: AnimationHandle,
34}
35
36impl PreparedAnimation {
37 pub fn start(self) -> Result<AnimationHandle, DesktopError> {
39 self.trigger.send(crate::engine::StartMode::Animation).map_err(|_| DesktopError::WorkerCrashed("preparation expired".into()))?;
40 Ok(self.handle)
41 }
42
43 pub fn open_timeline(self) -> Result<crate::TimelineSession, DesktopError> {
44 let (session, runtime) = crate::TimelineSession::pair(self.handle.clone());
45 let (ready, response) = bounded(1);
46 self.trigger.send(crate::engine::StartMode::Timeline { runtime, ready })
47 .map_err(|_| DesktopError::BackendUnavailable("preparation expired".into()))?;
48 response.recv().map_err(|_| DesktopError::BackendUnavailable(
49 format!("timeline opening failed: {:?}", self.handle.wait())
50 ))?;
51 Ok(session)
52 }
53
54 pub fn cancel(self) {
56 let _ = self.trigger.send(crate::engine::StartMode::Cancel);
57 self.handle.wait();
58 }
59}
60
61impl DesktopController {
62 pub fn new<B: DesktopBackend>(backend: B) -> Result<Self, DesktopError> {
65 Self::from_boxed(Box::new(backend))
66 }
67
68 pub fn from_boxed(backend: Box<dyn DesktopBackend>) -> Result<Self, DesktopError> {
71 let (tx, rx) = unbounded::<WorkerMsg>();
72 let tx_for_worker = tx.clone();
73 let worker = thread::Builder::new()
74 .name("rdi-worker".into())
75 .spawn(move || worker_main(tx_for_worker, rx, backend))
76 .map_err(|e| {
77 DesktopError::BackendUnavailable(format!("failed to spawn worker thread: {e}"))
78 })?;
79 Ok(Self {
80 tx,
81 worker: Mutex::new(Some(worker)),
82 })
83 }
84
85 pub fn list_icons(&self) -> Result<Vec<IconSnapshot>, DesktopError> {
89 let (rtx, rrx) = bounded(1);
90 self.tx
91 .send(WorkerMsg::ListIcons { resp: rtx })
92 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
93 rrx.recv()
94 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
95 }
96
97 pub fn get_flags(&self) -> Result<u32, DesktopError> {
99 let (rtx, rrx) = bounded(1);
100 self.tx
101 .send(WorkerMsg::GetFlags { resp: rtx })
102 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
103 rrx.recv()
104 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
105 }
106
107 pub fn apply_flags(&self, mask: u32, values: u32) -> Result<(), DesktopError> {
117 let (rtx, rrx) = bounded(1);
118 self.tx
119 .send(WorkerMsg::ApplyFlags {
120 mask,
121 values,
122 resp: rtx,
123 })
124 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
125 rrx.recv()
126 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
127 }
128
129 #[inline]
131 pub fn set_flags(&self, flags: u32) -> Result<(), DesktopError> {
132 self.apply_flags(flags, 0xFFFF_FFFF)
133 }
134
135 #[inline]
137 pub fn unset_flags(&self, flags: u32) -> Result<(), DesktopError> {
138 self.apply_flags(flags, 0)
139 }
140
141 pub fn toggle_flags(&self, flags: u32) -> Result<(), DesktopError> {
143 let current = self.get_flags()?;
144 self.apply_flags(flags, current ^ flags)
145 }
146
147 pub fn set_flags_exactly(&self, flags: u32) -> Result<(), DesktopError> {
149 self.apply_flags(crate::engine::build_true_mask(flags), flags)
150 }
151
152 pub fn set_positions(
159 &self,
160 moves: Vec<(IconId, Point)>,
161 ) -> Result<Vec<IconId>, DesktopError> {
162 let (rtx, rrx) = bounded(1);
163 self.tx
164 .send(WorkerMsg::SetPositions { moves, resp: rtx })
165 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
166 rrx.recv()
167 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
168 }
169
170 pub fn list_monitors(&self) -> Result<Vec<MonitorInfo>, DesktopError> {
178 let (rtx, rrx) = bounded(1);
179 self.tx
180 .send(WorkerMsg::ListMonitors { resp: rtx })
181 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
182 rrx.recv()
183 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
184 }
185
186 pub fn desktop_info(&self) -> Result<crate::DesktopInfo, DesktopError> {
188 let (response, receiver) = bounded(1);
189 self.tx.send(WorkerMsg::DesktopInfo { resp: response })
190 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
191 receiver.recv()
192 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
193 }
194
195 pub fn render_overlay_snapshot(
212 &self,
213 width_px: u32,
214 height_px: u32,
215 dpi_scale: f32,
216 positions: Vec<(IconId, Point)>,
217 render_options: OverlayRenderOptions,
218 ) -> Result<SnapshotFrame, DesktopError> {
219 let (rtx, rrx) = bounded(1);
220 self.tx
221 .send(WorkerMsg::RenderOverlaySnapshot {
222 width_px,
223 height_px,
224 dpi_scale,
225 positions,
226 render_options,
227 resp: rtx,
228 })
229 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
230 rrx.recv()
231 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
232 }
233
234 pub fn prepare_scene(&self, scene: crate::Scene) -> Result<crate::RenderSession, DesktopError> {
237 let (resp, response) = bounded(1);
238 self.tx.send(WorkerMsg::PrepareScene { scene, resp })
239 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
240 response.recv().map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
241 }
242
243 pub fn prepare(
246 &self,
247 specs: Vec<IconAnimationSpec>,
248 options: AnimationOptions,
249 ) -> Result<PreparedAnimation, DesktopError> {
250 let (trigger, start) = bounded(1);
251 let (ready, readiness) = bounded(1);
252 let (resp, response) = bounded(1);
253 self.tx.send(WorkerMsg::StartAnimation {
254 specs, options, observers: PreObservers::default(), resp,
255 preparation: Some((ready, start)),
256 }).map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
257 let handle = response.recv().map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))??;
258 if readiness.recv().is_err() {
259 return Err(DesktopError::BackendUnavailable(format!("preparation failed: {:?}", handle.wait())));
260 }
261 Ok(PreparedAnimation { trigger, handle })
262 }
263
264 pub fn animate(
278 &self,
279 specs: Vec<IconAnimationSpec>,
280 options: AnimationOptions,
281 ) -> Result<AnimationHandle, DesktopError> {
282 self.animate_with_observers(specs, options, PreObservers::default())
283 }
284
285 pub fn animate_with_observers(
289 &self,
290 specs: Vec<IconAnimationSpec>,
291 options: AnimationOptions,
292 observers: PreObservers,
293 ) -> Result<AnimationHandle, DesktopError> {
294 let (rtx, rrx) = bounded(1);
295 self.tx
296 .send(WorkerMsg::StartAnimation {
297 specs,
298 options,
299 observers,
300 resp: rtx,
301 preparation: None,
302 })
303 .map_err(|_| DesktopError::WorkerCrashed("channel closed".into()))?;
304 rrx.recv()
305 .map_err(|_| DesktopError::WorkerCrashed("worker dropped response".into()))?
306 }
307
308 pub fn shutdown(&self) {
319 let _ = self.tx.send(WorkerMsg::Shutdown);
320 if let Ok(mut guard) = self.worker.lock() {
321 if let Some(handle) = guard.take() {
322 let _ = handle.join();
323 }
324 }
325 }
326}
327
328impl Drop for DesktopController {
329 fn drop(&mut self) {
330 self.shutdown();
331 }
332}