1use std::collections::HashMap;
26use std::sync::{Arc, Mutex};
27
28use crate::backend::DesktopBackend;
29use crate::{
30 DesktopError, FinalCommitOutcome, IconId, IconRenderPlan, IconSnapshot, MonitorInfo,
31 OverlayRenderOptions, Point, Rect,
32};
33
34#[derive(Clone, Debug, Default)]
36pub struct FakeDesktop {
37 inner: Arc<Mutex<FakeDesktopInner>>,
38}
39
40#[derive(Debug)]
41pub struct FakeDesktopInner {
42 pub icons: HashMap<IconId, IconSnapshot>,
44 pub flags: u32,
46 pub commit_log: Vec<Vec<(IconId, Point)>>,
50 pub flag_ops: Vec<(u32, u32)>,
52 pub next_list_error: Option<DesktopError>,
55 pub monitors: Vec<MonitorInfo>,
59 pub desktop_info: Option<crate::DesktopInfo>,
60
61 pub next_overlay_error: Option<DesktopError>,
65 pub overlay_active: bool,
68 pub real_icons_visible: bool,
69 pub last_overlay_plans: Vec<IconRenderPlan>,
72 pub last_overlay_render_options: OverlayRenderOptions,
76 pub overlay_frame_log: Vec<Vec<(IconId, Point)>>,
78 pub visual_frame_log: Vec<Vec<crate::IconFrame>>,
79 pub overlay_finalize_log: Vec<Vec<(IconId, Point)>>,
82 pub next_commit_errors: std::collections::VecDeque<DesktopError>,
88}
89
90impl Default for FakeDesktopInner {
91 fn default() -> Self {
92 Self {
93 icons: HashMap::new(),
94 flags: 0,
95 commit_log: Vec::new(),
96 flag_ops: Vec::new(),
97 next_list_error: None,
98 monitors: vec![MonitorInfo {
99 id: "FAKE-PRIMARY".into(),
100 name: "FAKE-PRIMARY".into(),
101 bounds: Rect::from_origin_size(0, 0, 1920, 1080),
102 work_area: Rect::from_origin_size(0, 0, 1920, 1040),
103 is_primary: true,
104 scale_factor: 1.0,
105 }],
106 next_overlay_error: None,
107 desktop_info: None,
108 overlay_active: false,
109 real_icons_visible: true,
110 last_overlay_plans: Vec::new(),
111 last_overlay_render_options: OverlayRenderOptions::all_enabled(),
112 overlay_frame_log: Vec::new(),
113 visual_frame_log: Vec::new(),
114 overlay_finalize_log: Vec::new(),
115 next_commit_errors: std::collections::VecDeque::new(),
116 }
117 }
118}
119
120impl FakeDesktop {
121 pub fn real_icons_visible(&self) -> bool {
122 self.inner.lock().expect("FakeDesktop poisoned").real_icons_visible
123 }
124
125 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn add_icon(&self, snap: IconSnapshot) {
131 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
132 guard.icons.insert(snap.id.clone(), snap);
133 }
134
135 pub fn remove_icon(&self, id: &IconId) -> bool {
138 self.inner
139 .lock()
140 .expect("FakeDesktop poisoned")
141 .icons
142 .remove(id)
143 .is_some()
144 }
145
146 pub fn icon_count(&self) -> usize {
147 self.inner.lock().expect("FakeDesktop poisoned").icons.len()
148 }
149
150 pub fn position_of(&self, id: &IconId) -> Option<Point> {
152 self.inner
153 .lock()
154 .expect("FakeDesktop poisoned")
155 .icons
156 .get(id)
157 .map(|s| s.position)
158 }
159
160 pub fn flags(&self) -> u32 {
162 self.inner.lock().expect("FakeDesktop poisoned").flags
163 }
164
165 pub fn commit_log(&self) -> Vec<Vec<(IconId, Point)>> {
167 self.inner
168 .lock()
169 .expect("FakeDesktop poisoned")
170 .commit_log
171 .clone()
172 }
173
174 pub fn flag_ops(&self) -> Vec<(u32, u32)> {
176 self.inner
177 .lock()
178 .expect("FakeDesktop poisoned")
179 .flag_ops
180 .clone()
181 }
182
183 pub fn set_next_list_error(&self, err: DesktopError) {
185 self.inner
186 .lock()
187 .expect("FakeDesktop poisoned")
188 .next_list_error = Some(err);
189 }
190
191 pub fn set_monitors(&self, monitors: Vec<MonitorInfo>) {
194 self.inner
195 .lock()
196 .expect("FakeDesktop poisoned")
197 .monitors = monitors;
198 }
199
200 pub fn set_desktop_info(&self, desktop: crate::DesktopInfo) {
201 self.inner.lock().expect("FakeDesktop poisoned").desktop_info = Some(desktop);
202 }
203
204 pub fn set_next_overlay_error(&self, err: DesktopError) {
208 self.inner
209 .lock()
210 .expect("FakeDesktop poisoned")
211 .next_overlay_error = Some(err);
212 }
213
214 pub fn overlay_active(&self) -> bool {
216 self.inner
217 .lock()
218 .expect("FakeDesktop poisoned")
219 .overlay_active
220 }
221
222 pub fn last_overlay_plans(&self) -> Vec<IconRenderPlan> {
225 self.inner
226 .lock()
227 .expect("FakeDesktop poisoned")
228 .last_overlay_plans
229 .clone()
230 }
231
232 pub fn last_overlay_render_options(&self) -> OverlayRenderOptions {
236 self.inner
237 .lock()
238 .expect("FakeDesktop poisoned")
239 .last_overlay_render_options
240 }
241
242 pub fn overlay_frame_log(&self) -> Vec<Vec<(IconId, Point)>> {
244 self.inner
245 .lock()
246 .expect("FakeDesktop poisoned")
247 .overlay_frame_log
248 .clone()
249 }
250
251 pub fn visual_frame_log(&self) -> Vec<Vec<crate::IconFrame>> {
252 self.inner.lock().expect("FakeDesktop poisoned").visual_frame_log.clone()
253 }
254
255 pub fn overlay_finalize_log(&self) -> Vec<Vec<(IconId, Point)>> {
257 self.inner
258 .lock()
259 .expect("FakeDesktop poisoned")
260 .overlay_finalize_log
261 .clone()
262 }
263
264 pub fn queue_commit_error(&self, err: DesktopError) {
271 self.inner
272 .lock()
273 .expect("FakeDesktop poisoned")
274 .next_commit_errors
275 .push_back(err);
276 }
277}
278
279impl DesktopBackend for FakeDesktop {
280 fn list_icons(&mut self) -> Result<Vec<IconSnapshot>, DesktopError> {
281 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
282 if let Some(err) = guard.next_list_error.take() {
283 return Err(err);
284 }
285 Ok(guard.icons.values().cloned().collect())
286 }
287
288 fn get_flags(&mut self) -> Result<u32, DesktopError> {
289 Ok(self.inner.lock().expect("FakeDesktop poisoned").flags)
290 }
291
292 fn apply_flags(&mut self, mask: u32, values: u32) -> Result<(), DesktopError> {
293 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
294 guard.flag_ops.push((mask, values));
295 guard.flags = (guard.flags & !mask) | (values & mask);
296 Ok(())
297 }
298
299 fn set_positions(
300 &mut self,
301 moves: &[(IconId, Point)],
302 ) -> Result<Vec<IconId>, DesktopError> {
303 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
304 let mut applied = Vec::with_capacity(moves.len());
305 let mut missing = Vec::new();
306 for (id, pt) in moves {
307 if let Some(snap) = guard.icons.get_mut(id) {
308 snap.position = *pt;
309 applied.push((id.clone(), *pt));
310 } else {
311 missing.push(id.clone());
312 }
313 }
314 guard.commit_log.push(applied);
315 Ok(missing)
316 }
317
318 fn list_monitors(&mut self) -> Result<Vec<MonitorInfo>, DesktopError> {
319 Ok(self.inner.lock().expect("FakeDesktop poisoned").monitors.clone())
320 }
321
322 fn desktop_info(&mut self, _icons: &[IconSnapshot]) -> Result<crate::DesktopInfo, DesktopError> {
323 self.inner.lock().expect("FakeDesktop poisoned").desktop_info.clone()
324 .ok_or(DesktopError::UnsupportedPlatform)
325 }
326
327 fn begin_overlay_session(
328 &mut self,
329 plans: &[IconRenderPlan],
330 render_options: OverlayRenderOptions,
331 ) -> Result<(), DesktopError> {
332 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
333 if let Some(err) = guard.next_overlay_error.take() {
334 return Err(err);
335 }
336 guard.overlay_active = true;
337 guard.real_icons_visible = false;
338 guard.last_overlay_plans = plans.to_vec();
339 guard.last_overlay_render_options = render_options;
340 Ok(())
341 }
342
343 fn commit_overlay_frame(
344 &mut self,
345 positions: &[(IconId, Point)],
346 ) -> Result<(), DesktopError> {
347 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
348 if let Some(err) = guard.next_commit_errors.pop_front() {
349 return Err(err);
350 }
351 guard.overlay_frame_log.push(positions.to_vec());
352 Ok(())
353 }
354
355 fn discard_overlay_session(&mut self) {
356 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
357 guard.overlay_active = false;
358 guard.real_icons_visible = true;
359 }
360
361 fn commit_visual_frame(&mut self, frame: &[crate::IconFrame]) -> Result<(), DesktopError> {
362 self.commit_overlay_frame(&frame.iter().map(|entry| (entry.id.clone(), entry.position)).collect::<Vec<_>>())?;
363 self.inner.lock().expect("FakeDesktop poisoned").visual_frame_log.push(frame.to_vec());
364 Ok(())
365 }
366
367 fn poll_overlay_session(&mut self) -> Result<(), DesktopError> {
368 match self.inner.lock().expect("FakeDesktop poisoned").next_commit_errors.pop_front() {
369 Some(error) => Err(error),
370 None => Ok(()),
371 }
372 }
373
374 fn set_real_icons_visible(&mut self, visible: bool) -> Result<(), DesktopError> {
375 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
376 if !guard.overlay_active {
377 return Err(DesktopError::BackendUnavailable("no active overlay".into()));
378 }
379 guard.real_icons_visible = visible;
380 Ok(())
381 }
382
383 fn finalize_overlay_session(
384 &mut self,
385 final_positions: &[(IconId, Point)],
386 ) -> Result<FinalCommitOutcome, DesktopError> {
387 let mut guard = self.inner.lock().expect("FakeDesktop poisoned");
388 guard.overlay_finalize_log.push(final_positions.to_vec());
389 guard.overlay_active = false;
390 guard.real_icons_visible = true;
391
392 let mut moved = Vec::with_capacity(final_positions.len());
396 let mut missing = Vec::new();
397 for (id, pt) in final_positions {
398 match guard.icons.get_mut(id) {
399 Some(snap) => {
400 snap.position = *pt;
401 moved.push(id.clone());
402 }
403 None => missing.push(id.clone()),
404 }
405 }
406 Ok(FinalCommitOutcome {
407 moved_ids: moved,
408 missing_ids: missing,
409 })
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 fn snap(id: &str, x: i32, y: i32) -> IconSnapshot {
418 IconSnapshot::new(IconId::from(id), id, None, false, Point::new(x, y))
419 }
420
421 #[test]
422 fn apply_flags_mask_semantics_match_shell() {
423 let d = FakeDesktop::new();
424 let mut b = d.clone();
425 b.apply_flags(0b1111, 0b0101).unwrap();
427 assert_eq!(d.flags(), 0b0101);
428
429 b.apply_flags(0b0010, 0xFFFF_FFFF).unwrap();
431 assert_eq!(d.flags(), 0b0111);
432
433 b.apply_flags(0b0100, 0).unwrap();
435 assert_eq!(d.flags(), 0b0011);
436 }
437
438 #[test]
439 fn set_positions_updates_and_reports_missing() {
440 let d = FakeDesktop::new();
441 d.add_icon(snap("a", 0, 0));
442 d.add_icon(snap("b", 10, 10));
443
444 let mut b = d.clone();
445 let missing = b
446 .set_positions(&[
447 (IconId::from("a"), Point::new(1, 2)),
448 (IconId::from("ghost"), Point::new(0, 0)),
449 ])
450 .unwrap();
451
452 assert_eq!(missing, vec![IconId::from("ghost")]);
453 assert_eq!(d.position_of(&IconId::from("a")), Some(Point::new(1, 2)));
454 assert_eq!(d.position_of(&IconId::from("b")), Some(Point::new(10, 10)));
455 let log = d.commit_log();
456 assert_eq!(log.len(), 1);
457 assert_eq!(log[0], vec![(IconId::from("a"), Point::new(1, 2))]);
458 }
459
460 #[test]
461 fn list_icons_returns_error_when_armed() {
462 let d = FakeDesktop::new();
463 d.set_next_list_error(DesktopError::BackendUnavailable("bang".into()));
464 let mut b = d.clone();
465 assert!(matches!(
466 b.list_icons(),
467 Err(DesktopError::BackendUnavailable(_))
468 ));
469 assert!(b.list_icons().is_ok());
471 }
472}