/// Window metadata extraction use serde::{Deserialize, Serialize}; use crate::error::{AppError, Result}; #[cfg(target_os = "linux")] use xcap::Window; /// Window metadata structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WindowMetadata { pub title: String, pub process_name: String, pub process_id: u32, pub is_active: bool, } impl WindowMetadata { /// Create metadata for inactive state pub fn inactive() -> Self { Self { title: "Inactive".to_string(), process_name: "none".to_string(), process_id: 0, is_active: false, } } /// Create metadata for unknown window pub fn unknown() -> Self { Self { title: "Unknown".to_string(), process_name: "unknown".to_string(), process_id: 0, is_active: true, } } /// Extract category hints from window title pub fn guess_category(&self) -> String { let title_lower = self.title.to_lowercase(); let process_lower = self.process_name.to_lowercase(); // Development patterns if title_lower.contains("vscode") || title_lower.contains("visual studio") || title_lower.contains("intellij") || title_lower.contains("pycharm") || process_lower.contains("code") { return "Development".to_string(); } // Meeting patterns if title_lower.contains("zoom") || title_lower.contains("meet") || title_lower.contains("teams") || title_lower.contains("skype") { return "Meeting".to_string(); } // Design patterns if title_lower.contains("figma") || title_lower.contains("sketch") || title_lower.contains("photoshop") || title_lower.contains("illustrator") { return "Design".to_string(); } // Research patterns (browsers) if process_lower.contains("chrome") || process_lower.contains("firefox") || process_lower.contains("safari") || process_lower.contains("edge") { return "Research".to_string(); } "Other".to_string() } } /// Get metadata for the currently active window #[cfg(target_os = "linux")] pub fn get_active_window_metadata() -> Result { let windows = Window::all() .map_err(|e| AppError::Capture(format!("Failed to get windows: {}", e)))?; // Find the active/focused window // For MVP, we'll use the first window as a fallback let active_window = windows.first() .ok_or_else(|| AppError::Capture("No windows found".to_string()))?; Ok(WindowMetadata { title: active_window.title().to_string(), process_name: active_window.app_name().to_string(), process_id: active_window.id() as u32, is_active: true, }) } /// Get metadata for the currently active window (Windows implementation) #[cfg(target_os = "windows")] pub fn get_active_window_metadata() -> Result { // Simplified implementation for MVP // In production, would use Windows API to get active window Ok(WindowMetadata::unknown()) } /// Get metadata for the currently active window (macOS implementation) #[cfg(target_os = "macos")] pub fn get_active_window_metadata() -> Result { // Simplified implementation for MVP // In production, would use macOS APIs to get active window Ok(WindowMetadata::unknown()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_window_metadata_creation() { let metadata = WindowMetadata::inactive(); assert!(!metadata.is_active); assert_eq!(metadata.title, "Inactive"); } #[test] fn test_category_guessing() { let metadata = WindowMetadata { title: "VSCode - main.rs".to_string(), process_name: "code".to_string(), process_id: 1234, is_active: true, }; assert_eq!(metadata.guess_category(), "Development"); let metadata2 = WindowMetadata { title: "Zoom Meeting".to_string(), process_name: "zoom".to_string(), process_id: 5678, is_active: true, }; assert_eq!(metadata2.guess_category(), "Meeting"); } }