diff --git a/Cargo.toml b/Cargo.toml index 5e500e1..507ea29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "activity-tracker" version = "0.1.0" edition = "2021" authors = ["Activity Tracker Team"] -description = "Backend de suivi d'activité pour reconstruire l'historique de travail" +description = "Backend de suivi d'activité pour reconstruire l'historique de travail (Windows uniquement)" [dependencies] # Core dependencies @@ -17,7 +17,15 @@ env_logger = "0.11" screenshots = "0.6" image = "0.24" webp = "0.2" -xcap = "0.0.10" + +# Windows APIs +[target.'cfg(windows)'.dependencies] +windows = { version = "0.52", features = [ + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Threading", + "Win32_System_ProcessStatus", +] } # Storage (SQLite + Encryption) rusqlite = { version = "0.31", features = ["bundled"] } diff --git a/README.md b/README.md index a3cdcac..90bbebf 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@ ![Version](https://img.shields.io/badge/version-0.1.0-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Rust](https://img.shields.io/badge/rust-1.70+-orange) +![Platform](https://img.shields.io/badge/platform-Windows-blue) -**Activity Tracker** est un système de suivi d'activité conçu pour aider les utilisateurs à reconstruire leur historique de travail via une analyse automatisée des actions numériques. +**Activity Tracker** est un système de suivi d'activité **Windows uniquement** conçu pour aider les utilisateurs à reconstruire leur historique de travail via une analyse automatisée des actions numériques. ## Caractéristiques (MVP) -- **Capture passive** : Screenshots toutes les 5 minutes + métadonnées fenêtres +- **Capture passive** : Screenshots toutes les 5 minutes + métadonnées fenêtres Windows +- **API Windows native** : Utilise GetForegroundWindow et les APIs Win32 - **Stockage sécurisé** : Base SQLite avec chiffrement AES-256-GCM - **Analyse intelligente** : Classification automatique en 5 catégories - **Rapports journaliers** : Export JSON avec statistiques détaillées @@ -18,28 +20,27 @@ ### Prérequis -- Rust 1.70+ -- Cargo -- SQLite3 +- **Windows 10 ou supérieur** (requis) +- Rust 1.70+ et Cargo (installer via [rustup](https://rustup.rs/)) +- SQLite3 (inclus automatiquement via Cargo) ### Compilation -```bash +```powershell git clone https://gitea.legion-muyue.fr/Muyue/activity-tracker.git cd activity-tracker cargo build --release ``` -Le binaire compilé sera disponible dans `target/release/activity-tracker`. +Le binaire compilé sera disponible dans `target\release\activity-tracker.exe`. ### Installation système -```bash -# Linux/macOS -sudo cp target/release/activity-tracker /usr/local/bin/ +```powershell +# Ajoutez le répertoire à votre PATH ou copiez le binaire +copy target\release\activity-tracker.exe C:\Program Files\ActivityTracker\ -# Ou ajoutez le chemin à votre PATH -export PATH=$PATH:$(pwd)/target/release +# Ou utilisez directement depuis le dossier target\release ``` ## Utilisation @@ -173,7 +174,7 @@ inactivity_threshold = 600 # 10 minutes [storage] max_storage_mb = 500 retention_days = 30 -db_path = "data/activity_tracker.db" +db_path = "data\\activity_tracker.db" [ai] categories = ["Development", "Meeting", "Research", "Design", "Other"] @@ -277,11 +278,12 @@ Pattern::new(vec!["slack", "discord", "telegram"], 0.9), - [ ] **Détection audio** de réunions - [ ] **Intégrations** (Trello, Jira, calendriers) -## Problèmes connus +## Plateforme supportée -- **Linux** : L'accès aux métadonnées de fenêtres nécessite X11 (Wayland non supporté) -- **macOS** : Nécessite autorisations Accessibilité (voir documentation officielle) -- **Windows** : Fonctionne avec les privilèges standards +- **Windows uniquement** : Fonctionne avec Windows 10 et supérieur +- Utilise les APIs Windows natives pour la capture de fenêtres +- Aucune autorisation spéciale requise (privilèges standards suffisants) +- Les plateformes Linux et macOS ne sont **pas supportées** ## Contribution diff --git a/config/settings.toml b/config/settings.toml index 0ee55a1..19e9520 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,4 +1,4 @@ -# Activity Tracker MVP Configuration +# Activity Tracker MVP Configuration (Windows uniquement) [capture] interval_seconds = 300 # 5 minutes (as per MVP spec) @@ -8,7 +8,7 @@ inactivity_threshold = 600 # 10 minutes [storage] max_storage_mb = 500 retention_days = 30 -db_path = "data/activity_tracker.db" +db_path = "data\\activity_tracker.db" # Chemin Windows [ai] categories = ["Development", "Meeting", "Research", "Design", "Other"] diff --git a/src/capture/window.rs b/src/capture/window.rs index ba6d7eb..3aac4f9 100644 --- a/src/capture/window.rs +++ b/src/capture/window.rs @@ -1,9 +1,15 @@ -/// Window metadata extraction +/// Window metadata extraction (Windows uniquement) use serde::{Deserialize, Serialize}; use crate::error::{AppError, Result}; -#[cfg(target_os = "linux")] -use xcap::Window; +#[cfg(windows)] +use windows::{ + core::PWSTR, + Win32::Foundation::{HWND, MAX_PATH}, + Win32::System::ProcessStatus::GetModuleBaseNameW, + Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}, + Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowTextW, GetWindowThreadProcessId}, +}; /// Window metadata structure #[derive(Debug, Clone, Serialize, Deserialize)] @@ -81,39 +87,72 @@ impl WindowMetadata { } } -/// 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")] +#[cfg(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()) + unsafe { + // Get the foreground window handle + let hwnd = GetForegroundWindow(); + if hwnd.0 == 0 { + return Ok(WindowMetadata::inactive()); + } + + // Get window title + let mut title_buffer = [0u16; 512]; + let title_len = GetWindowTextW(hwnd, &mut title_buffer); + let title = if title_len > 0 { + String::from_utf16_lossy(&title_buffer[..title_len as usize]) + } else { + "Unknown".to_string() + }; + + // Get process ID + let mut process_id: u32 = 0; + GetWindowThreadProcessId(hwnd, Some(&mut process_id)); + + // Get process name + let process_name = get_process_name(process_id).unwrap_or_else(|| "unknown".to_string()); + + Ok(WindowMetadata { + title, + process_name, + process_id, + is_active: true, + }) + } } -/// Get metadata for the currently active window (macOS implementation) -#[cfg(target_os = "macos")] +/// Get process name from process ID (Windows) +#[cfg(windows)] +fn get_process_name(process_id: u32) -> Option { + unsafe { + let process_handle = OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + false, + process_id, + ).ok()?; + + let mut name_buffer = [0u16; MAX_PATH as usize]; + let name_len = GetModuleBaseNameW( + process_handle, + None, + &mut name_buffer, + ); + + if name_len > 0 { + Some(String::from_utf16_lossy(&name_buffer[..name_len as usize])) + } else { + None + } + } +} + +/// Fallback for non-Windows platforms (not supported) +#[cfg(not(windows))] pub fn get_active_window_metadata() -> Result { - // Simplified implementation for MVP - // In production, would use macOS APIs to get active window - Ok(WindowMetadata::unknown()) + Err(AppError::Capture( + "This application only supports Windows".to_string() + )) } #[cfg(test)] diff --git a/src/config.rs b/src/config.rs index 3269e12..c6ba72f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -77,7 +77,7 @@ impl Config { storage: StorageConfig { max_storage_mb: 500, retention_days: 30, - db_path: "data/activity_tracker.db".to_string(), + db_path: "data\\activity_tracker.db".to_string(), }, ai: AiConfig { categories: vec![