Skip to main content

fotos_lib/capture/
mod.rs

1pub mod detect;
2#[cfg(target_os = "linux")]
3pub mod portal;
4pub mod xcap_backend;
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum CaptureMode {
14    Fullscreen,
15    Monitor(u32),
16    Region { x: i32, y: i32, w: u32, h: u32 },
17    Window(u64),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct CaptureMetadata {
22    pub timestamp: DateTime<Utc>,
23    pub mode: CaptureMode,
24    pub monitor: Option<String>,
25    pub window_title: Option<String>,
26    pub dimensions: (u32, u32),
27}
28
29#[derive(Debug)]
30pub struct CaptureResult {
31    pub id: Uuid,
32    pub image: Arc<image::DynamicImage>,
33    pub metadata: CaptureMetadata,
34}
35
36/// Global image store shared across the application.
37/// Used by capture, AI processing, and file operations.
38#[derive(Clone)]
39pub struct ImageStore {
40    images: Arc<RwLock<HashMap<Uuid, Arc<image::DynamicImage>>>>,
41}
42
43impl ImageStore {
44    pub fn new() -> Self {
45        Self {
46            images: Arc::new(RwLock::new(HashMap::new())),
47        }
48    }
49
50    pub fn insert(&self, id: Uuid, image: Arc<image::DynamicImage>) {
51        self.images
52            .write()
53            .unwrap_or_else(|e| e.into_inner())
54            .insert(id, image);
55    }
56
57    pub fn get(&self, id: &Uuid) -> Option<Arc<image::DynamicImage>> {
58        self.images
59            .read()
60            .unwrap_or_else(|e| e.into_inner())
61            .get(id)
62            .cloned()
63    }
64
65    pub fn ids(&self) -> Vec<Uuid> {
66        self.images
67            .read()
68            .unwrap_or_else(|e| e.into_inner())
69            .keys()
70            .copied()
71            .collect()
72    }
73
74    pub fn remove(&self, id: &Uuid) -> Option<Arc<image::DynamicImage>> {
75        self.images
76            .write()
77            .unwrap_or_else(|e| e.into_inner())
78            .remove(id)
79    }
80}
81
82impl Default for ImageStore {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use image::{DynamicImage, RgbaImage};
92
93    fn dummy_image() -> Arc<DynamicImage> {
94        Arc::new(DynamicImage::ImageRgba8(RgbaImage::new(10, 10)))
95    }
96
97    #[test]
98    fn image_store_insert_and_get() {
99        let store = ImageStore::new();
100        let id = Uuid::new_v4();
101        let img = dummy_image();
102        store.insert(id, img.clone());
103        assert!(store.get(&id).is_some());
104    }
105
106    #[test]
107    fn image_store_get_missing_returns_none() {
108        let store = ImageStore::new();
109        assert!(store.get(&Uuid::new_v4()).is_none());
110    }
111
112    #[test]
113    fn image_store_remove_clears_entry() {
114        let store = ImageStore::new();
115        let id = Uuid::new_v4();
116        store.insert(id, dummy_image());
117        assert!(store.remove(&id).is_some());
118        assert!(store.get(&id).is_none());
119    }
120
121    #[test]
122    fn image_store_get_after_remove_is_none() {
123        let store = ImageStore::new();
124        let id = Uuid::new_v4();
125        store.insert(id, dummy_image());
126        store.remove(&id);
127        assert!(store.get(&id).is_none());
128    }
129}