diff --git a/src/time_tracker/app.py b/src/time_tracker/app.py index 7864bf9..793ddb7 100644 --- a/src/time_tracker/app.py +++ b/src/time_tracker/app.py @@ -40,15 +40,27 @@ class Application: def _start_activity_listeners(self): def on_activity(*_): - with self._lock: - self.last_activity = time.time() - self.is_idle = False + # Активность зафиксирована — но обработаем её только если окно рабочее + self._check_and_update_activity() self.mouse_listener = mouse.Listener(on_move=on_activity, on_click=on_activity) self.keyboard_listener = keyboard.Listener(on_press=on_activity) self.mouse_listener.start() self.keyboard_listener.start() + def _check_and_update_activity(self): + """Обновляет last_activity, только если текущее окно — рабочее""" + window_info = self.platform.get_active_window() + group_name = self._match_window_to_policy(window_info) + is_blocked = self._is_group_blocked(group_name) + is_unknown = (group_name == "Неизвестное приложение") + + # Считаем "рабочим", если не blocked и не unknown + if not is_blocked and not is_unknown: + with self._lock: + self.last_activity = time.time() + self.is_idle = False + def _start_tracking_loop(self): def loop(): while self._running: @@ -63,36 +75,25 @@ class Application: if not self.tracking_enabled or not self.current_task: return - # Проверка: спит ли система? (упрощённо — если активность > 60 сек назад) current_time = time.time() - if current_time - self.last_activity > 60: - self.is_idle = True - self.last_window_group = None - return - - # Получаем текущее окно window_info = self.platform.get_active_window() - if not window_info.title and not window_info.process_name: - self.is_idle = True - return - - # Определяем группу по политике group_name = self._match_window_to_policy(window_info) is_blocked = self._is_group_blocked(group_name) - if is_blocked: + was_idle = self.is_idle + + if current_time - self.last_activity > 60 or is_blocked: self.is_idle = True self.last_window_group = None - return + else: + self.is_idle = False + if self.last_window_group != group_name: + self.last_window_group = group_name + self._add_work_detail(group_name, 5.0) - # Если рабочая группа — накапливаем время - self.is_idle = False - if self.last_window_group != group_name: - # Новая группа — завершаем предыдущую сессию (если была) - self.last_window_group = group_name - - # Добавляем 5 секунд к текущей группе - self._add_work_detail(group_name, 5.0) + # Сохраняем при переходе в бездействие + if not was_idle and self.is_idle: + self.maybe_save_task() def _match_window_to_policy(self, window_info) -> str: title = window_info.title.lower() @@ -142,8 +143,14 @@ class Application: # === Публичные методы с локом === + def maybe_save_task(self): + """Сохраняет текущую задачу, если она есть""" + if self.current_task: + self.save_task(self.current_task) + def toggle_tracking(self): with self._lock: + was_enabled = self.tracking_enabled if not self.current_task: self.tracking_enabled = False return @@ -152,6 +159,10 @@ class Application: self.config.tracking_enabled = self.tracking_enabled self._save_config(self.config) + # Сохраняем при переходе в паузу + if was_enabled and not self.tracking_enabled: + self.maybe_save_task() + def create_task(self, task_id: str, project_id: Optional[str], estimated_minutes: Optional[float]): with self._lock: ensure_dir(self.tasks_dir) @@ -245,6 +256,7 @@ class Application: def shutdown(self): self._running = False + self.maybe_save_task() # ← сохраняем перед выходом if hasattr(self, 'mouse_listener'): self.mouse_listener.stop() if hasattr(self, 'keyboard_listener'): diff --git a/src/time_tracker/ui/overlay.py b/src/time_tracker/ui/overlay.py index 7729058..f83ebbe 100644 --- a/src/time_tracker/ui/overlay.py +++ b/src/time_tracker/ui/overlay.py @@ -1,6 +1,9 @@ import tkinter as tk from tkinter import Menu, Label, Button, Frame from typing import TYPE_CHECKING + +from ..utils import format_seconds + if TYPE_CHECKING: from ..app import Application @@ -10,6 +13,7 @@ class OverlayWindow: def __init__(self, app: "Application"): self.app = app self.root = tk.Tk() + self.root.protocol("WM_DELETE_WINDOW", self.on_close) self.root.title("Citrus Time Tracker") self.root.geometry("320x180") self.root.overrideredirect(True) # убираем заголовок @@ -36,6 +40,10 @@ class OverlayWindow: self._auto_update() + def on_close(self): + self.app.shutdown() + self.root.destroy() + def center_window(self): self.root.update_idletasks() screen_width = self.root.winfo_screenwidth() @@ -92,14 +100,13 @@ class OverlayWindow: # Время if self.app.current_task: - worked = self.app.current_task.total_worked_seconds / 60 + worked_str = format_seconds(self.app.current_task.total_worked_seconds) est = self.app.current_task.estimated_seconds if est is not None: - est_min = est / 60 - rem = self.app.current_task.remaining_seconds / 60 - time_info = f"Затрачено: {worked:.1f} мин | Оценка: {est_min:.1f} мин" + est_str = format_seconds(est) + time_info = f"Затрачено: {worked_str} | Оценка: {est_str}" else: - time_info = f"Затрачено: {worked:.1f} мин | Оценка: —" + time_info = f"Затрачено: {worked_str} | Оценка: —" else: time_info = "Задача не выбрана" self.time_info_label.config(text=time_info) diff --git a/src/time_tracker/utils.py b/src/time_tracker/utils.py index e541374..7c5684e 100644 --- a/src/time_tracker/utils.py +++ b/src/time_tracker/utils.py @@ -13,4 +13,14 @@ def get_platform() -> PlatformBase: def ensure_dir(path: Path) -> None: - path.mkdir(parents=True, exist_ok=True) \ No newline at end of file + path.mkdir(parents=True, exist_ok=True) + +def format_seconds(seconds: float) -> str: + seconds = int(abs(seconds)) + h = seconds // 3600 + m = (seconds % 3600) // 60 + s = seconds % 60 + if h > 0: + return f"{h}:{m:02d}:{s:02d}" + else: + return f"{m}:{s:02d}" \ No newline at end of file