123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- #!/usr/bin/env python3
- import time
- import os
- import sys
- import curses
- from curses import wrapper
- import argparse
- from datetime import datetime, timedelta
- import subprocess
- class PomodoroTimer:
- def __init__(self, work_time=25, short_break=5, long_break=15, cycles=4):
- """
- Инициализация таймера Pomodoro
-
- Args:
- work_time (int): продолжительность рабочего периода в минутах
- short_break (int): продолжительность короткого перерыва в минутах
- long_break (int): продолжительность длинного перерыва в минутах
- cycles (int): количество циклов до длинного перерыва
- """
- self.work_time = work_time * 60
- self.short_break = short_break * 60
- self.long_break = long_break * 60
- self.cycles = cycles
- self.current_cycle = 1
- self.total_completed = 0
- self.stats = {'work': 0, 'short_break': 0, 'long_break': 0}
-
- def format_time(self, seconds):
- """Преобразует секунды в формат MM:SS"""
- minutes, seconds = divmod(seconds, 60)
- return f"{minutes:02d}:{seconds:02d}"
-
- def get_progress_bar(self, current, total, width=50):
- """Создает красивую полосу прогресса"""
- progress = int(width * current / total) if total > 0 else 0
- return "█" * progress + "░" * (width - progress)
-
- def send_notification(self, title, message, urgency="normal", sound=True):
- """Отправляет уведомление через notify-send с звуком"""
- try:
- # Отправка уведомления
- subprocess.run([
- "notify-send",
- f"--urgency={urgency}",
- f"--icon=appointment-soon",
- title,
- message
- ])
-
- # Воспроизведение звука с помощью системного звонка
- if sound:
- # Используем paplay для воспроизведения системного звука (если доступно)
- try:
- # Стандартные звуки уведомлений в Linux
- sound_files = [
- "/usr/share/sounds/freedesktop/stereo/complete.oga",
- "/usr/share/sounds/ubuntu/stereo/message.ogg",
- "/usr/share/sounds/gnome/default/alerts/glass.ogg"
- ]
-
- # Проверяем существование файлов и воспроизводим первый найденный
- for sound_file in sound_files:
- if os.path.exists(sound_file):
- subprocess.run(["paplay", sound_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- break
- else:
- # Если ни один файл не найден, используем системный звуковой сигнал
- sys.stdout.write('\a')
- sys.stdout.flush()
-
- except Exception:
- # В случае ошибки используем системный звуковой сигнал
- sys.stdout.write('\a')
- sys.stdout.flush()
- except Exception as e:
- # Если notify-send не доступен, используем системный звуковой сигнал
- sys.stdout.write('\a')
- sys.stdout.flush()
-
- def run(self, stdscr):
- """Основной метод выполнения таймера Pomodoro с использованием curses"""
- # Инициализация curses
- curses.curs_set(0) # Скрыть курсор
- curses.start_color()
- curses.use_default_colors()
-
- # Определить цветовые пары
- curses.init_pair(1, curses.COLOR_GREEN, -1) # Рабочий режим
- curses.init_pair(2, curses.COLOR_CYAN, -1) # Короткий перерыв
- curses.init_pair(3, curses.COLOR_BLUE, -1) # Длинный перерыв
- curses.init_pair(4, curses.COLOR_YELLOW, -1) # Заголовки
- curses.init_pair(5, curses.COLOR_RED, -1) # Кнопка выхода
-
- # Получить размеры терминала
- max_y, max_x = stdscr.getmaxyx()
-
- # Установить режим неблокирующего ввода
- stdscr.nodelay(True)
-
- # Отправить начальное уведомление
- self.send_notification("Pomodoro Timer", "Таймер запущен! Начинаем работу.", "normal", True)
-
- try:
- while True:
- # Работа
- self._run_timer(stdscr, "РАБОТА", self.work_time, curses.color_pair(1))
- self.stats['work'] += 1
- self.total_completed += 1
-
- # Проверка, нужен ли длинный перерыв
- if self.current_cycle == self.cycles:
- # Уведомление о длинном перерыве
- self.send_notification(
- "Pomodoro - Длинный перерыв!",
- f"Работа завершена! Время для длинного перерыва ({self.long_break // 60} минут).",
- "critical",
- True
- )
-
- # Длинный перерыв
- self._run_timer(stdscr, "ДЛИННЫЙ ПЕРЕРЫВ", self.long_break, curses.color_pair(3))
- self.stats['long_break'] += 1
- self.current_cycle = 1
-
- # Уведомление о завершении длинного перерыва
- self.send_notification(
- "Pomodoro - Перерыв окончен!",
- "Длинный перерыв завершен! Пора вернуться к работе.",
- "critical",
- True
- )
- else:
- # Уведомление о коротком перерыве
- self.send_notification(
- "Pomodoro - Короткий перерыв!",
- f"Работа завершена! Время для короткого перерыва ({self.short_break // 60} минут).",
- "normal",
- True
- )
-
- # Короткий перерыв
- self._run_timer(stdscr, "КОРОТКИЙ ПЕРЕРЫВ", self.short_break, curses.color_pair(2))
- self.stats['short_break'] += 1
- self.current_cycle += 1
-
- # Уведомление о завершении короткого перерыва
- self.send_notification(
- "Pomodoro - Перерыв окончен!",
- "Короткий перерыв завершен! Пора вернуться к работе.",
- "normal",
- True
- )
-
- except KeyboardInterrupt:
- # Уведомление о завершении работы
- self.send_notification(
- "Pomodoro - Завершено",
- f"Сессия завершена! Выполнено циклов: {self.total_completed}",
- "low",
- True
- )
- return
-
- def _run_timer(self, stdscr, label, duration, color_pair):
- """Запускает один таймер (работа или перерыв)"""
- stdscr.clear()
- max_y, max_x = stdscr.getmaxyx()
-
- # Время старта и окончания
- start_time = datetime.now()
- end_time = start_time + timedelta(seconds=duration)
-
- # Отображение основной информации
- while datetime.now() < end_time:
- remaining = int((end_time - datetime.now()).total_seconds())
- if remaining < 0:
- break
-
- # Очистить экран и обновить данные
- stdscr.clear()
-
- # Получить текущие размеры окна (на случай изменения размера)
- current_y, current_x = stdscr.getmaxyx()
-
- # Заголовок с цветом
- title = f"🍅 POMODORO TIMER 🍅"
- stdscr.addstr(2, (current_x - len(title)) // 2, title, curses.color_pair(4) | curses.A_BOLD)
-
- # Текущий статус с соответствующим цветом
- status = f"[ {label} ]"
- stdscr.addstr(4, (current_x - len(status)) // 2, status, color_pair | curses.A_BOLD)
-
- # Отображение времени крупными цифрами
- time_str = self.format_time(remaining)
- stdscr.addstr(6, (current_x - len(time_str)) // 2, time_str, color_pair | curses.A_BOLD)
-
- # Полоса прогресса
- progress = 1.0 - (remaining / duration)
- bar_width = min(current_x - 10, 50)
- progress_bar = self.get_progress_bar(progress * duration, duration, bar_width)
- stdscr.addstr(8, (current_x - len(progress_bar)) // 2, progress_bar, color_pair)
-
- # Процент выполнения
- percent = f"{progress * 100:.0f}%"
- stdscr.addstr(9, (current_x - len(percent)) // 2, percent, color_pair)
-
- # Информация о циклах
- cycle_info = f"Цикл: {self.current_cycle}/{self.cycles} | Всего завершено: {self.total_completed}"
- stdscr.addstr(11, (current_x - len(cycle_info)) // 2, cycle_info)
-
- # Время окончания
- end_info = f"Окончание: {end_time.strftime('%H:%M:%S')}"
- stdscr.addstr(12, (current_x - len(end_info)) // 2, end_info)
-
- # Статистика
- stats_info = f"Работа: {self.stats['work']} | Короткие перерывы: {self.stats['short_break']} | Длинные перерывы: {self.stats['long_break']}"
- if current_x > len(stats_info) + 4:
- stdscr.addstr(13, (current_x - len(stats_info)) // 2, stats_info)
-
- # Управление
- controls = "Нажмите 'q' для выхода, пробел для паузы/продолжения"
- if current_y > 15 and current_x > len(controls) + 4:
- stdscr.addstr(current_y - 2, (current_x - len(controls)) // 2, controls)
-
- # Уведомление о скором завершении
- if remaining == 60: # За минуту до конца
- self.send_notification(
- f"Pomodoro - Скоро завершение",
- f"Осталась 1 минута до завершения периода: {label}",
- "normal",
- True
- )
-
- # Обработка ввода
- key = stdscr.getch()
- if key == ord('q'):
- raise KeyboardInterrupt
- elif key == ord(' '):
- # Пауза
- pause_start = datetime.now()
- stdscr.addstr(current_y // 2, (current_x - 12) // 2, "⏸️ ПАУЗА ⏸️", curses.color_pair(5) | curses.A_BOLD)
- self.send_notification("Pomodoro - Пауза", "Таймер на паузе. Нажмите любую клавишу для продолжения.", "low", True)
- stdscr.nodelay(False) # Включить блокирующий ввод для паузы
- stdscr.getch() # Ждать нажатия клавиши
- stdscr.nodelay(True) # Вернуть неблокирующий ввод
- pause_duration = datetime.now() - pause_start
- end_time += pause_duration # Добавить время паузы к времени окончания
- self.send_notification("Pomodoro - Продолжение", "Таймер снова запущен!", "low", True)
-
- # Обновить экран
- stdscr.refresh()
- time.sleep(0.1)
-
- # Уведомление о завершении
- for _ in range(3): # Мигание уведомления
- stdscr.clear()
- stdscr.addstr(current_y // 2, (current_x - 20) // 2, f"{label} ЗАВЕРШЕН!", color_pair | curses.A_BOLD)
- stdscr.refresh()
- time.sleep(0.3)
- stdscr.clear()
- stdscr.refresh()
- time.sleep(0.2)
- def main():
- parser = argparse.ArgumentParser(description='Терминальный таймер Pomodoro')
- parser.add_argument('-w', '--work', type=int, default=25, help='Продолжительность работы (минуты)')
- parser.add_argument('-s', '--short', type=int, default=5, help='Продолжительность короткого перерыва (минуты)')
- parser.add_argument('-l', '--long', type=int, default=15, help='Продолжительность длинного перерыва (минуты)')
- parser.add_argument('-c', '--cycles', type=int, default=4, help='Количество циклов до длинного перерыва')
-
- args = parser.parse_args()
-
- pomodoro = PomodoroTimer(args.work, args.short, args.long, args.cycles)
- wrapper(pomodoro.run)
- if __name__ == "__main__":
- main()
|