feat: calendar integration (Google Calendar + CalDAV)#142
Open
flathead wants to merge 3 commits intoAxenide:devfrom
Open
feat: calendar integration (Google Calendar + CalDAV)#142flathead wants to merge 3 commits intoAxenide:devfrom
flathead wants to merge 3 commits intoAxenide:devfrom
Conversation
Full calendar integration for the Ambxst shell, including: Backend (scripts/calendar_service.py): - Python service communicating with QML via stdin/stdout JSON protocol - Google Calendar via OAuth2 (google-api-python-client) - CalDAV support (caldav library) - Import of existing gcalcli tokens - Background sync thread with configurable interval - Notification system: reminders and arrival alerts via notify-send - Sound on arrival via canberra-gtk-play / paplay / pw-play / aplay fallback chain - Persisted notification deduplication across restarts (~/.cache/ambxst/calendar_notified.json) - CRUD operations (create, update, delete) with cmd_start/cmd_result protocol - Atomic token writes with 0o600 permissions QML service (modules/services/CalendarService.qml): - Singleton wrapping the Python process - Accounts, calendars, events state - Operation feedback: operationPending, operationResult signal - Arrival state with iconBlinking and 30s auto-dismiss timer - All audio handled by Python; QML does not play sounds Bar widget (modules/bar/BarCalendarIndicator.qml): - Shows upcoming event count or next event title + time - barAlwaysShow mode: splits popup into Upcoming / Past sections - Blinking icon animation on event arrival - barShowNextEvent mode: shows event title and countdown Dashboard panels: - CalendarPanel.qml: accounts management, Google OAuth, CalDAV auth, gcalcli import, per-calendar enable/disable, all settings - EventDetailPanel.qml: full create/edit form with time spinners, calendar selector, reminder, description, location, Google Meet toggle, operation feedback (saving state, timeout, error display), back = cancel edit, close button always exits - EventItem.qml / EventPopup.qml: event list item and quick popup Config: - config/defaults/calendar.js: all defaults - Config.qml: JsonAdapter with full property set including barAlwaysShow i18n: - All user-facing strings in calendar files wrapped with _t(key, fallback) - Helper is safe without the I18n singleton (try/catch fallback) - Translation keys added to feature/i18n branch (en/ru/es)
Add _t() helper to EventPopup.qml and wrap all user-visible strings with translation keys. Wrap all remaining ~30 hardcoded strings in CalendarPanel.qml (section headers, form labels, buttons, hints). All strings have English fallbacks for use without i18n module.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds a full calendar integration to Ambxst — a Python-backed service with QML frontend supporting Google Calendar (OAuth2) and CalDAV accounts.
This is a personal implementation that grew organically, so it is almost certainly not production-ready and likely needs significant rework before it would fit this codebase well. It is submitted as a reference / starting point rather than a finished feature. See the Known Issues section at the bottom.
What's included
Backend —
scripts/calendar_service.pyA standalone Python process that communicates with QML over stdin/stdout using a line-delimited JSON protocol. Responsibilities:
google-api-python-client), CalDAV via thecaldavlibrary, import of existinggcalclitokensnotify-send; arrival sound viacanberra-gtk-playwithpaplay/pw-play/aplayfallback chaincmd_start/cmd_resultacknowledgement protocol so the UI can show operation state~/.cache/ambxst/calendar_notified.json), token files written atomically with0o600permissionsQML service —
modules/services/CalendarService.qmlSingleton wrapping the Python process. Exposes accounts, calendars, events lists; operation pending/result state; arrival blinking state. All audio is handled by Python — QML plays no sounds.
Bar widget —
modules/bar/BarCalendarIndicator.qmlbarShowNextEvent: shows next event title and countdown timerbarAlwaysShow: always visible, popup splits into Upcoming / Past sections with past events dimmedDashboard — CalendarPanel, EventDetailPanel, EventItem, EventPopup
Config
New
calendarmodule inConfig.qmlandconfig/defaults/calendar.js, following the existingJsonAdapterpattern.i18n readiness
All user-facing strings in the calendar files are wrapped with a local
_t(key, fallback)helper that safely falls back to the hardcoded English string when theI18nsingleton is not present. Translation keys (calendar.bar.*,calendar.event.*,calendar.form.*,calendar.settings.*) have been added tofeature/i18n(39 keys, en/ru/es).Dependencies
google-api-python-clientgoogle-auth-oauthlibcaldavrequestscanberra-gtk-play(optional)paplay/pw-play/aplayKnown issues and areas that need proper work
The implementation has several rough edges that a maintainer should address before this would be mergeable:
Architecture
_auth_google()and_auth_caldav()run directly on the stdin dispatch thread. During an OAuth browser flow this blocks all other commands indefinitely. Both need to be offloaded to worker threads following the same pattern as_do_sync.self.tokensis accessed from both the main thread and the sync thread without lock protection. In practice the GIL prevents corruption, but concurrent token refresh and account removal could produce a partially-written token file.Security
~/.config/ambxst/config/calendar.json), which is world-readable by default. The token file is correctly0o600, but the secret that mints it is not protected.gcalclitoken import usespickle.load()on the gcalcli token file. The risk is documented in the code but not surfaced to the user in the UI.Validation / consistency
EventPopup.qml(the click-on-day path from the calendar grid) saves events without validating title or end > start, whileEventDetailPanel.qml(the dashboard path) does both. The validation logic should be shared.EventItem.qmlis missingpragma ComponentBehavior: Bound, inconsistent with every other file in the feature.EventPopup.qmlstrings are not wrapped with_t(), inconsistent withEventDetailPanel.qml.EventItem.qmlis shown for plain-text addresses but only opens URLs starting withhttp. It should either construct a maps search URL or hide itself for non-URL values.These are not exhaustive — the code has not been tested against real Google Calendar or CalDAV accounts in a CI environment, and edge cases around timezone handling, recurring events, and multi-day all-day events are likely incomplete.
Screenshots
2026-04-01T19-11-10-038Z.mp4