- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| pim_waybar.py | ||
| README.md | ||
pim-waybar
Утилита для интеграции календаря и списка задач в панель Waybar. Работает напрямую с файлами формата VEVENT/VTODO (.ics) в vdir-каталогах (структура, которую использует, например, vdirsyncer), без обращения к внешним программам khal или todo (todoman). Включает фоновый агент синхронизации/уведомлений и полноэкранный текстовый интерфейс (curses) для просмотра и редактирования событий и задач.
Возможности
- Два модуля для Waybar: календарь и задачи, каждый — отдельный JSON-объект
{text, tooltip, class}. - Фоновый агент: периодически запускает
vdirsyncer sync, перестраивает локальный кэш и отправляет desktop-уведомления по сработавшим напоминаниям. - Комбинированный TUI (
calendar-tui/tasks-tui) с двумя панелями — календарём (сетка месяца + временная шкала дня) и списком задач. - Создание, редактирование, удаление и экспорт событий и задач напрямую в
.ics-файлах; чтение и запись реализованы через модульicalendar, разбор повторов — черезdateutil.rrule. - Полноценная обработка повторяющихся событий и задач:
RRULE,EXDATE, переопределения отдельных вхождений черезRECURRENCE-ID, отмена (STATUS:CANCELLED) конкретного вхождения. - Иерархия задач «родитель — подзадача» через
RELATED-TO, каскадное завершение и удаление поддерева. - Напоминания (
VALARM) для событий и задач с отправкой черезnotify-sendи защитой от повторной отправки одного и того же напоминания. - Разделение статусов «сеть недоступна» и «реальная ошибка синхронизации/чтения» — временная недоступность сети не помечается как ошибка в интерфейсе.
Требования
- Python 3 с модулем
curses(в некоторых дистрибутивах ставится отдельным пакетомpython3-curses). - Пакет
icalendar(python3-icalendarилиpip install --user icalendar). - Пакет
python-dateutil(python3-dateutilилиpip install --user python-dateutil). vdirsyncer— опционально, нужен только для синхронизации с сервером CalDAV/CardDAV; без него скрипт продолжает работать с локальными файлами, а в статусе синхронизации фиксируется ошибка «command not found».notify-send— опционально, нужен для всплывающих напоминаний.- Шрифт с поддержкой Nerd Font/Font Awesome в Waybar, если используются иконки (
calendar_icon,tasks_icon).
Хранение данных
Никакой собственной базы данных нет: TUI читает и правит те же самые .ics-файлы, которые лежат в vdir-каталогах.
- События (
VEVENT) собираются из каталогов, перечисленных вcalendar_roots. - Задачи (
VTODO) — из каталогов вtodo_roots. - По умолчанию оба списка указывают на
~/.calendars,~/.local/share/calendars,~/.local/share/vdirsyncer; внутри каждого корня автоматически обнаруживаются все вложенные vdir-коллекции. - Если
auto_discover_todomanвключён (по умолчанию так), пути коллекций дополнительно подхватываются из существующего конфига todoman (config.py) — это используется только как источник путей, сам бинарникtodoнигде не запускается. - Если один и тот же UID мастер-компонента найден сразу в нескольких файлах (например, из-за прерванной синхронизации), используется только самая свежая копия — по
DTSTAMP, а если его нет, по времени изменения файла. Остальные файлы на диске не трогаются, предупреждение попадает вdebug.uid_conflicts(см. командуdebug).
Служебные файлы
Каталог $XDG_CACHE_HOME/pim-waybar (по умолчанию ~/.cache/pim-waybar):
| Файл | Назначение |
|---|---|
cache.json |
Данные для Waybar-модулей и текущий статус синхронизации; читается командой bar |
notified.json |
Журнал уже отправленных напоминаний (дедупликация) |
agent.log |
Лог фонового агента, ротация при достижении ~1 МБ |
agent.pid |
PID работающего агента |
agent.lock, sync.lock |
Файловые блокировки: не более одного агента и не более одной синхронизации одновременно |
Конфигурация: $XDG_CONFIG_HOME/pim-waybar/config.json (по умолчанию ~/.config/pim-waybar/config.json), формат — JSON. Отсутствующие в файле ключи берутся из значений по умолчанию.
Команды
| Команда | Действие |
|---|---|
bar calendar / bar tasks |
Печатает в stdout JSON-объект {text, tooltip, class} соответствующего модуля — читается из cache.json, для использования в Waybar (return-type: json) |
agent |
Запускает фоновый процесс: цикл синхронизации, пересборки кэша и уведомлений. Если агент уже запущен, повторный вызов просто завершается |
restart |
Останавливает уже работающий агент (проверяя по /proc, что это действительно процесс pim-waybar agent, а не переиспользованный PID) и запускает новый |
refresh-once |
Одна пересборка кэша из локальных файлов, без синхронизации и без уведомлений |
sync-once |
Один прогон vdirsyncer sync и последующая пересборка кэша, без уведомлений |
calendar-tui |
Комбинированный TUI с начальным фокусом на панели календаря |
tasks-tui |
Комбинированный TUI с начальным фокусом на панели задач |
debug |
JSON-дамп текущей конфигурации, обнаруженных каталогов/коллекций и содержимого кэша |
debug-month [YYYY-MM] |
Пошаговая трассировка обработки одного месяца: какие файлы найдены, какие исключены как дубликаты UID, что дала разворачивание повтора (RRULE) для каждого файла, что в итоге вернула сборка событий. Без аргумента берётся текущий месяц |
Настройка модулей Waybar
Пример записи в ~/.config/waybar/config (пути и интервал — под конкретную установку):
"custom/pim-calendar": {
"exec": "~/scripts/pim_waybar.py bar calendar",
"return-type": "json",
"interval": 15,
"on-click": "~/scripts/pim_waybar.py calendar-tui"
},
"custom/pim-tasks": {
"exec": "~/scripts/pim_waybar.py bar tasks",
"return-type": "json",
"interval": 15,
"on-click": "~/scripts/pim_waybar.py tasks-tui"
}
Сам bar только читает уже готовый cache.json — данные обновляет фоновый агент, поэтому его нужно запускать при старте сессии (например, exec_always pim_waybar.py restart в конфиге Sway/i3), а не по требованию из Waybar. Если cache.json не обновлялся дольше stale_after_sec (по умолчанию — авто: max(60, 3 × cache_interval_sec)), модуль получает дополнительный CSS-класс stale; класс empty — когда показывать нечего.
Управление в TUI
Общие клавиши
| Клавиша | Действие |
|---|---|
Tab |
Переключение фокуса между панелями «Календарь» и «Задачи» |
F1 / ? |
Экран со списком клавиш |
r |
Полное обновление обеих панелей с диска |
F10 / q / Esc |
Выход |
Панель календаря
| Клавиша | Действие |
|---|---|
F6 |
Переключение между сеткой месяца (верх) и временной шкалой дня (низ) |
F3 / Enter / s |
Просмотр события (при нескольких событиях в дне — выбор из списка) |
F4 / e |
Редактирование (для повторяющихся — выбор области: только это / это и последующие / вся серия) |
F7 / n |
Новое событие |
F8 / x |
Удаление (с тем же выбором области для повторяющихся) |
F9 / X |
Экспорт события (целиком, вся серия) в отдельный .ics-файл |
F5 / t |
Переход к сегодняшнему дню, шкала центрируется на текущем времени |
: |
Переход к дате (форматы: YYYY-MM-DD, DD.MM.YYYY, today, tomorrow) |
F2 |
Обновление с диска |
PgUp / PgDn |
Предыдущий / следующий месяц |
↑/↓, j/k |
Сетка: на неделю; шкала: перемещение выделенного события |
←/→, h/l |
На день |
+ / - |
В режиме шкалы: масштаб (4ч / 2ч / 1ч / 30мин / 15мин на строку) |
Панель задач
| Клавиша | Действие |
|---|---|
F2 / D |
Редактирование заметок ($EDITOR) |
F3 / Enter / s |
Просмотр деталей задачи |
F4 / e |
Редактирование (название, список, срок, приоритет, теги, повтор, напоминание) |
F5 / z |
Отложить (+1 день, 1ч/3ч/1д/1нед, либо конкретная дата) |
F6 / P |
Назначить/сменить родительскую задачу |
F7 / n |
Новая задача |
F8 / x |
Удаление (вместе с открытыми подзадачами, с подтверждением) |
F9 / H |
История выполненных задач |
d |
Выполнить (вместе с открытыми подзадачами, с подтверждением) |
a |
Редактирование сразу с переходом к полю «Напоминание» |
p |
Редактирование сразу с переходом к полю «Повтор» |
R |
Редактирование сырого .ics в $EDITOR |
N |
Новая подзадача выбранной задачи |
u |
Отвязать от родителя |
/ |
Фильтр по названию/списку/тегу |
↑/↓, j/k |
Перемещение по списку |
PgUp/PgDn |
Перемещение на 10 позиций |
Синхронизация
- Команда синхронизации задаётся
vdirsyncer_cmd(по умолчанию["vdirsyncer", "sync"]). - Интервал между синхронизациями —
sync_interval_sec(по умолчанию 600 с); тайм-аут одного запуска —sync_timeout_sec(120 с). - Если синхронизация не удалась из-за сети (список шаблонов в
sync_offline_patterns: недоступность хоста, DNS, тайм-аут соединения и т. п.), статус фиксируется как «офлайн», а не как ошибка — повтор происходит быстрее, черезsync_offline_retry_sec(60 с), а не через полный интервал. - Любая другая ненулевая ошибка (авторизация, конфигурация и т. д.) фиксируется как реальная ошибка синхронизации.
- Параллельный запуск синхронизации исключён файловой блокировкой (
sync.lock); если она уже занята, попытка просто пропускается.
Уведомления
- Источник — свойства
VALARMвнутри самихVEVENT/VTODO; отдельного механизма напоминаний вне ICS нет. - Каждое конкретное напоминание отправляется один раз: ключ (UID события/задачи + время срабатывания) сохраняется в
notified.json, повторный показ того же напоминания не происходит даже при частых перестройках кэша. - Окно проверки — «с последней проверки по текущий момент», а не фиксированная задержка после времени напоминания: это гарантирует доставку даже если предыдущий цикл агента был занят долгой синхронизацией.
alarm_grace_sec(90 с) — дополнительное окно «догона» только при самом старте агента, для напоминаний, которые сработали незадолго до запуска.notify_overdue_tasks(по умолчанию выключено) — отдельная ежедневная всплывающая нотификация по просроченным задачам без явногоVALARM; счётчик просроченных задач в тултипе Waybar от этой настройки не зависит и показывается всегда.event_urgency/task_urgency— уровень срочности (notify-send -u) для уведомлений по событиям и задачам.
Часовые пояса и формат записи дат
- По умолчанию (
use_tzid_dates: true) дата со временем записывается как;TZID=<зона>:<локальное время>вместе с минимальным сопутствующим блокомVTIMEZONE— по той же схеме, что используют iOS/2Do. Зона по умолчанию определяется автоматически (/etc/timezoneлибо/etc/localtime), либо задаётся явно черезtzid_name. - При
use_tzid_dates: falseдаты пишутся в абсолютном UTC-формате (суффиксZ), безVTIMEZONE. - Для повторяющейся задачи с ограничением по дате (
RRULE;UNTIL=...) TZID-схема принудительно не используется независимо от настройки — это обход известной проблемы совместимости вpython-dateutilпри продвижении такой задачи на следующее вхождение.
Полный список параметров конфигурации
| Ключ | По умолчанию | Назначение |
|---|---|---|
calendar_roots |
~/.calendars, ~/.local/share/calendars, ~/.local/share/vdirsyncer |
Корневые каталоги с vdir-коллекциями событий |
todo_roots |
те же три пути | Корневые каталоги с vdir-коллекциями задач |
auto_discover_todoman |
true |
Дополнительно брать пути коллекций из конфига todoman, если он есть |
cascade_complete_with_open_children |
true |
При завершении задачи с открытыми подзадачами завершать и их (иначе — отказ) |
cascade_delete_with_open_children |
true |
При удалении задачи с открытыми подзадачами удалять и их (иначе — они остаются, но без родителя) |
lookahead_days |
7 |
Горизонт «предстоящих» событий в тултипе и окне уведомлений |
cache_interval_sec |
30 |
Период пересборки кэша фоновым агентом |
sync_interval_sec |
600 |
Период синхронизации через vdirsyncer |
sync_offline_retry_sec |
60 |
Задержка перед повторной попыткой после «офлайн»-результата |
sync_timeout_sec |
120 |
Тайм-аут одного запуска vdirsyncer |
stale_after_sec |
0 (авто) |
Порог, после которого модуль Waybar помечается классом stale; 0 = max(60, 3 × cache_interval_sec) |
sync_offline_patterns |
список сетевых сообщений об ошибке | По каким подстрокам в выводе vdirsyncer ошибка считается временной (сеть), а не реальной |
alarm_grace_sec |
90 |
Окно «догона» напоминаний сразу после старта агента |
event_default_length_min |
60 |
Длительность нового события по умолчанию, если конец не указан |
event_title_max |
32 |
Обрезка названия события в тексте модуля Waybar |
task_title_max |
48 |
Обрезка названия задачи в тултипе |
calendar_timeline_start_hour / calendar_timeline_end_hour |
0 / 24 |
Границы временной шкалы дня в TUI |
waybar_span_size |
small |
Размер шрифта (Pango <span size>) основного текста модулей |
calendar_icon_span_size / tasks_icon_span_size |
small |
Размер шрифта иконок |
calendar_icon_rise / tasks_icon_rise |
1400 / `` |
Вертикальное смещение глифа иконки (Pango rise) |
calendar_icon / tasks_icon |
`` | Символ иконки перед текстом модуля |
new_task_due_today |
true |
Новой задаче из TUI сразу ставится срок «сегодня» |
new_task_list |
`` | Фиксированный список для новых задач; пусто — определяется автоматически или запрашивается |
new_event_calendar |
не задан | Фиксированный календарь для новых событий, создаваемых не через TUI-форму |
use_tzid_dates |
true |
Схема записи дат: TZID + VTIMEZONE (true) или абсолютный UTC (false) |
tzid_name |
`` (автоопределение) | Явное имя часового пояса IANA для схемы TZID |
raw_editor |
`` | Редактор для правки сырого .ics; пусто — $VISUAL, затем $EDITOR, затем vi |
tooltip_limit |
8 |
Максимум строк-пунктов одной секции тултипа |
tooltip_newline |
\n |
Символ переноса строк в тултипе |
history_limit |
50 |
Максимум строк в истории выполненных задач (H в TUI) |
vdirsyncer_cmd |
["vdirsyncer", "sync"] |
Команда синхронизации |
notify_cmd |
["notify-send"] |
Команда отправки desktop-уведомлений |
event_urgency |
normal |
Срочность уведомлений по событиям |
task_urgency |
critical |
Срочность уведомлений по задачам |
notify_overdue_tasks |
false |
Отдельное ежедневное всплывающее уведомление по просроченным задачам без явного напоминания |
Диагностика
debug— сводка по текущей конфигурации, обнаруженным каталогам/коллекциям и итоговому содержимому кэша (модули, статус синхронизации, счётчики загруженных событий/задач, конфликты UID).debug-month [YYYY-MM]— по каждому файлу вcalendar_roots: список сырыхVEVENT(с признакамиRRULE/RECURRENCE-ID), результат разворачивания повторов в окне месяца (включая текст исключения, если разворачивание упало), и итоговый список событий после полной сборки. Полезно, когда конкретное событие не показывается там, где ожидается.agent.log— лог фонового агента: результаты синхронизаций, ошибки одной итерации цикла (одна неудачная итерация не останавливает агент), решения об офлайн-статусе.