start.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #!/usr/bin/env python3
  2. import time
  3. import os
  4. import sys
  5. import curses
  6. from curses import wrapper
  7. import argparse
  8. from datetime import datetime, timedelta
  9. import subprocess
  10. class PomodoroTimer:
  11. def __init__(self, work_time=25, short_break=5, long_break=15, cycles=4):
  12. """
  13. Инициализация таймера Pomodoro
  14. Args:
  15. work_time (int): продолжительность рабочего периода в минутах
  16. short_break (int): продолжительность короткого перерыва в минутах
  17. long_break (int): продолжительность длинного перерыва в минутах
  18. cycles (int): количество циклов до длинного перерыва
  19. """
  20. self.work_time = work_time * 60
  21. self.short_break = short_break * 60
  22. self.long_break = long_break * 60
  23. self.cycles = cycles
  24. self.current_cycle = 1
  25. self.total_completed = 0
  26. self.stats = {'work': 0, 'short_break': 0, 'long_break': 0}
  27. def format_time(self, seconds):
  28. """Преобразует секунды в формат MM:SS"""
  29. minutes, seconds = divmod(seconds, 60)
  30. return f"{minutes:02d}:{seconds:02d}"
  31. def get_progress_bar(self, current, total, width=50):
  32. """Создает красивую полосу прогресса"""
  33. progress = int(width * current / total) if total > 0 else 0
  34. return "█" * progress + "░" * (width - progress)
  35. def send_notification(self, title, message, urgency="normal", sound=True):
  36. """Отправляет уведомление через notify-send с звуком"""
  37. try:
  38. # Отправка уведомления
  39. subprocess.run([
  40. "notify-send",
  41. f"--urgency={urgency}",
  42. f"--icon=appointment-soon",
  43. title,
  44. message
  45. ])
  46. # Воспроизведение звука с помощью системного звонка
  47. if sound:
  48. # Используем paplay для воспроизведения системного звука (если доступно)
  49. try:
  50. # Стандартные звуки уведомлений в Linux
  51. sound_files = [
  52. "/usr/share/sounds/freedesktop/stereo/complete.oga",
  53. "/usr/share/sounds/ubuntu/stereo/message.ogg",
  54. "/usr/share/sounds/gnome/default/alerts/glass.ogg"
  55. ]
  56. # Проверяем существование файлов и воспроизводим первый найденный
  57. for sound_file in sound_files:
  58. if os.path.exists(sound_file):
  59. subprocess.run(["paplay", sound_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  60. break
  61. else:
  62. # Если ни один файл не найден, используем системный звуковой сигнал
  63. sys.stdout.write('\a')
  64. sys.stdout.flush()
  65. except Exception:
  66. # В случае ошибки используем системный звуковой сигнал
  67. sys.stdout.write('\a')
  68. sys.stdout.flush()
  69. except Exception as e:
  70. # Если notify-send не доступен, используем системный звуковой сигнал
  71. sys.stdout.write('\a')
  72. sys.stdout.flush()
  73. def run(self, stdscr):
  74. """Основной метод выполнения таймера Pomodoro с использованием curses"""
  75. # Инициализация curses
  76. curses.curs_set(0) # Скрыть курсор
  77. curses.start_color()
  78. curses.use_default_colors()
  79. # Определить цветовые пары
  80. curses.init_pair(1, curses.COLOR_GREEN, -1) # Рабочий режим
  81. curses.init_pair(2, curses.COLOR_CYAN, -1) # Короткий перерыв
  82. curses.init_pair(3, curses.COLOR_BLUE, -1) # Длинный перерыв
  83. curses.init_pair(4, curses.COLOR_YELLOW, -1) # Заголовки
  84. curses.init_pair(5, curses.COLOR_RED, -1) # Кнопка выхода
  85. # Получить размеры терминала
  86. max_y, max_x = stdscr.getmaxyx()
  87. # Установить режим неблокирующего ввода
  88. stdscr.nodelay(True)
  89. # Отправить начальное уведомление
  90. self.send_notification("Pomodoro Timer", "Таймер запущен! Начинаем работу.", "normal", True)
  91. try:
  92. while True:
  93. # Работа
  94. self._run_timer(stdscr, "РАБОТА", self.work_time, curses.color_pair(1))
  95. self.stats['work'] += 1
  96. self.total_completed += 1
  97. # Проверка, нужен ли длинный перерыв
  98. if self.current_cycle == self.cycles:
  99. # Уведомление о длинном перерыве
  100. self.send_notification(
  101. "Pomodoro - Длинный перерыв!",
  102. f"Работа завершена! Время для длинного перерыва ({self.long_break // 60} минут).",
  103. "critical",
  104. True
  105. )
  106. # Длинный перерыв
  107. self._run_timer(stdscr, "ДЛИННЫЙ ПЕРЕРЫВ", self.long_break, curses.color_pair(3))
  108. self.stats['long_break'] += 1
  109. self.current_cycle = 1
  110. # Уведомление о завершении длинного перерыва
  111. self.send_notification(
  112. "Pomodoro - Перерыв окончен!",
  113. "Длинный перерыв завершен! Пора вернуться к работе.",
  114. "critical",
  115. True
  116. )
  117. else:
  118. # Уведомление о коротком перерыве
  119. self.send_notification(
  120. "Pomodoro - Короткий перерыв!",
  121. f"Работа завершена! Время для короткого перерыва ({self.short_break // 60} минут).",
  122. "normal",
  123. True
  124. )
  125. # Короткий перерыв
  126. self._run_timer(stdscr, "КОРОТКИЙ ПЕРЕРЫВ", self.short_break, curses.color_pair(2))
  127. self.stats['short_break'] += 1
  128. self.current_cycle += 1
  129. # Уведомление о завершении короткого перерыва
  130. self.send_notification(
  131. "Pomodoro - Перерыв окончен!",
  132. "Короткий перерыв завершен! Пора вернуться к работе.",
  133. "normal",
  134. True
  135. )
  136. except KeyboardInterrupt:
  137. # Уведомление о завершении работы
  138. self.send_notification(
  139. "Pomodoro - Завершено",
  140. f"Сессия завершена! Выполнено циклов: {self.total_completed}",
  141. "low",
  142. True
  143. )
  144. return
  145. def _run_timer(self, stdscr, label, duration, color_pair):
  146. """Запускает один таймер (работа или перерыв)"""
  147. stdscr.clear()
  148. max_y, max_x = stdscr.getmaxyx()
  149. # Время старта и окончания
  150. start_time = datetime.now()
  151. end_time = start_time + timedelta(seconds=duration)
  152. # Отображение основной информации
  153. while datetime.now() < end_time:
  154. remaining = int((end_time - datetime.now()).total_seconds())
  155. if remaining < 0:
  156. break
  157. # Очистить экран и обновить данные
  158. stdscr.clear()
  159. # Получить текущие размеры окна (на случай изменения размера)
  160. current_y, current_x = stdscr.getmaxyx()
  161. # Заголовок с цветом
  162. title = f"🍅 POMODORO TIMER 🍅"
  163. stdscr.addstr(2, (current_x - len(title)) // 2, title, curses.color_pair(4) | curses.A_BOLD)
  164. # Текущий статус с соответствующим цветом
  165. status = f"[ {label} ]"
  166. stdscr.addstr(4, (current_x - len(status)) // 2, status, color_pair | curses.A_BOLD)
  167. # Отображение времени крупными цифрами
  168. time_str = self.format_time(remaining)
  169. stdscr.addstr(6, (current_x - len(time_str)) // 2, time_str, color_pair | curses.A_BOLD)
  170. # Полоса прогресса
  171. progress = 1.0 - (remaining / duration)
  172. bar_width = min(current_x - 10, 50)
  173. progress_bar = self.get_progress_bar(progress * duration, duration, bar_width)
  174. stdscr.addstr(8, (current_x - len(progress_bar)) // 2, progress_bar, color_pair)
  175. # Процент выполнения
  176. percent = f"{progress * 100:.0f}%"
  177. stdscr.addstr(9, (current_x - len(percent)) // 2, percent, color_pair)
  178. # Информация о циклах
  179. cycle_info = f"Цикл: {self.current_cycle}/{self.cycles} | Всего завершено: {self.total_completed}"
  180. stdscr.addstr(11, (current_x - len(cycle_info)) // 2, cycle_info)
  181. # Время окончания
  182. end_info = f"Окончание: {end_time.strftime('%H:%M:%S')}"
  183. stdscr.addstr(12, (current_x - len(end_info)) // 2, end_info)
  184. # Статистика
  185. stats_info = f"Работа: {self.stats['work']} | Короткие перерывы: {self.stats['short_break']} | Длинные перерывы: {self.stats['long_break']}"
  186. if current_x > len(stats_info) + 4:
  187. stdscr.addstr(13, (current_x - len(stats_info)) // 2, stats_info)
  188. # Управление
  189. controls = "Нажмите 'q' для выхода, пробел для паузы/продолжения"
  190. if current_y > 15 and current_x > len(controls) + 4:
  191. stdscr.addstr(current_y - 2, (current_x - len(controls)) // 2, controls)
  192. # Уведомление о скором завершении
  193. if remaining == 60: # За минуту до конца
  194. self.send_notification(
  195. f"Pomodoro - Скоро завершение",
  196. f"Осталась 1 минута до завершения периода: {label}",
  197. "normal",
  198. True
  199. )
  200. # Обработка ввода
  201. key = stdscr.getch()
  202. if key == ord('q'):
  203. raise KeyboardInterrupt
  204. elif key == ord(' '):
  205. # Пауза
  206. pause_start = datetime.now()
  207. stdscr.addstr(current_y // 2, (current_x - 12) // 2, "⏸️ ПАУЗА ⏸️", curses.color_pair(5) | curses.A_BOLD)
  208. self.send_notification("Pomodoro - Пауза", "Таймер на паузе. Нажмите любую клавишу для продолжения.", "low", True)
  209. stdscr.nodelay(False) # Включить блокирующий ввод для паузы
  210. stdscr.getch() # Ждать нажатия клавиши
  211. stdscr.nodelay(True) # Вернуть неблокирующий ввод
  212. pause_duration = datetime.now() - pause_start
  213. end_time += pause_duration # Добавить время паузы к времени окончания
  214. self.send_notification("Pomodoro - Продолжение", "Таймер снова запущен!", "low", True)
  215. # Обновить экран
  216. stdscr.refresh()
  217. time.sleep(0.1)
  218. # Уведомление о завершении
  219. for _ in range(3): # Мигание уведомления
  220. stdscr.clear()
  221. stdscr.addstr(current_y // 2, (current_x - 20) // 2, f"{label} ЗАВЕРШЕН!", color_pair | curses.A_BOLD)
  222. stdscr.refresh()
  223. time.sleep(0.3)
  224. stdscr.clear()
  225. stdscr.refresh()
  226. time.sleep(0.2)
  227. def main():
  228. parser = argparse.ArgumentParser(description='Терминальный таймер Pomodoro')
  229. parser.add_argument('-w', '--work', type=int, default=25, help='Продолжительность работы (минуты)')
  230. parser.add_argument('-s', '--short', type=int, default=5, help='Продолжительность короткого перерыва (минуты)')
  231. parser.add_argument('-l', '--long', type=int, default=15, help='Продолжительность длинного перерыва (минуты)')
  232. parser.add_argument('-c', '--cycles', type=int, default=4, help='Количество циклов до длинного перерыва')
  233. args = parser.parse_args()
  234. pomodoro = PomodoroTimer(args.work, args.short, args.long, args.cycles)
  235. wrapper(pomodoro.run)
  236. if __name__ == "__main__":
  237. main()