A lightweight CalDAV directory watcher daemon for Linux desktop environments. CalWatch monitors your local CalDAV directories (synced via vdirsyncer) and sends desktop notifications for upcoming calendar events.
I'm a programmer, just not for the go language. This daemon was built by providing claude with the use case and the architecture I'd build in the languages I'm fluent in, see design.md then setting claude to work see progress.md
I have not yet read over it to look for glaring logic errors, just verified that the basic functionality is there. Use at your own discretion.
- Real-time monitoring of CalDAV directories using inotify
- Proper ICS parsing with recurring event support via gocal
- Configurable alerts with multiple time offsets (minutes, hours, days)
- Template-based notifications with rich formatting options
- Desktop integration via D-Bus notifications (no external dependencies)
- XDG compliant configuration and template management
- Systemd integration for background daemon operation
- No database dependency - direct ICS file parsing
- Memory efficient with daily event indexing
- π Laptop-optimized: Sleep/wake detection with missed event recovery
- π― Smart notifications: Different durations for normal vs. missed events
- βοΈ User-friendly config: Human-readable durations (no more magic milliseconds)
- π± Flexible policies: Configure how to handle missed events (all/summary/priority/skip)
Perfect for users who:
- Sync calendars via vdirsyncer with Nextcloud/CalDAV
- Use tiling window managers (Hyprland, Sway, i3)
- Want calendar notifications without running a full calendar application
- Prefer lightweight, terminal-focused tools
- Use laptops that frequently sleep/hibernate and need reliable event catch-up
- Go 1.20+ (or use the provided
shell.nixfor NixOS) - D-Bus session bus (standard on most Linux desktop environments)
- A CalDAV sync solution like vdirsyncer
Add CalWatch to your Home Manager configuration:
{ config, pkgs, lib, ... }:
let
calwatch = pkgs.callPackage (builtins.fetchGit {
url = "https://github.com/svensp/calwatch";
ref = "main";
} + "/default.nix") {};
in
{
# Install calwatch package
home.packages = [ calwatch ];
# CalWatch configuration
xdg.configFile."calwatch/config.yaml".text = ''
directories:
- directory: ~/.calendars/personal
template: default.tpl
automatic_alerts:
- value: 15
unit: minutes
- value: 1
unit: hours
notification:
backend: notify-send
duration:
type: timed
value: 5
unit: seconds
duration_when_late:
type: until_dismissed
wakeup_handling:
enable: true
missed_event_policy: all
logging:
level: info
'';
# CalWatch service
systemd.user.services.calwatch = {
Unit = {
Description = "Calendar event notification daemon";
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${calwatch}/bin/calwatch";
Restart = "on-failure";
RestartSec = "5";
Environment = [
"PATH=${pkgs.libnotify}/bin:/run/current-system/sw/bin"
];
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
}# Enter development shell with all dependencies
nix develop
# Or use legacy shell
nix-shellgit clone https://github.com/svensp/calwatch.git
cd calwatch
# Build
go build ./cmd/calwatch
# Install (optional)
sudo cp calwatch /usr/local/bin/-
Initialize configuration:
./calwatch init
-
Edit configuration to point to your calendar directories:
vim ~/.config/calwatch/config.yaml -
Run the daemon:
./calwatch
Edit ~/.config/calwatch/config.yaml:
directories:
- directory: ~/.calendars/personal
template: detailed.tpl
automatic_alerts:
- value: 15
unit: minutes
- value: 1
unit: hours
- directory: ~/.calendars/work
template: minimal.tpl
automatic_alerts:
- value: 5
unit: minutes
notification:
backend: notify-send
# Normal notification duration
duration:
type: timed
value: 5
unit: seconds
# Duration for missed/late notifications
duration_when_late:
type: until_dismissed # Requires user action to dismiss
# Sleep/wake handling for laptop users
wakeup_handling:
enable: true
missed_event_policy: all # all, summary, priority_only, skip
max_missed_days: 7 # Limit how far back to process
summary_threshold: 5 # Show summary if more than N events
max_catchup_time:
value: 30
unit: seconds
logging:
level: infoCalWatch includes several built-in templates:
- default.tpl - Simple event summary with time
- detailed.tpl - Full event details with emojis
- minimal.tpl - Just event name and time
- family.tpl - Family-friendly format with emoji
Create custom templates in ~/.config/calwatch/templates/:
π
{{.Summary}}
π {{.StartTime}} - {{.EndTime}} ({{.Duration}})
{{if .Location}}π {{.Location}}{{end}}
{{if .Description}}π {{.Description}}{{end}}
β° {{.AlertOffset}} warning
{{.Summary}}- Event title{{.Description}}- Event description{{.Location}}- Event location{{.StartTime}}- Start time (HH:MM format){{.EndTime}}- End time (HH:MM format){{.Duration}}- Event duration (human readable){{.AlertOffset}}- Alert timing (e.g. "15 minutes"){{.UID}}- Event unique identifier
CalWatch is optimized for laptop users who frequently sleep/hibernate their machines. When the system wakes up, CalWatch automatically detects the gap and processes any missed events.
Configure how CalWatch handles events that were missed during sleep:
wakeup_handling:
missed_event_policy: all # Choose one of four policiesPolicy Options:
all- Show every missed event individually (good for light calendar usage)summary- Group missed events into summary notifications when threshold exceededpriority_only- Only show high-priority missed events (meetings, deadlines, etc.)skip- Clean slate approach, skip all missed events (useful after vacations)
CalWatch automatically classifies events by priority based on:
- Keywords: "meeting", "deadline", "urgent", "interview", etc.
- Attendees: Events with multiple people are prioritized
- Work indicators: Events in work calendars or with work-related keywords
- Time sensitivity: Events starting soon get higher priority
notification:
# Normal notifications (auto-dismiss)
duration:
type: timed
value: 5
unit: seconds
# Missed notifications (require user action)
duration_when_late:
type: until_dismissedDuration Types:
timed- Auto-dismiss after specified time (default behavior)until_dismissed- Persistent notification requiring user click
Supported Time Units:
milliseconds(ms)seconds(default, most user-friendly)minutes(for very long notifications)
-
Install service file:
sudo cp calwatch.service /etc/systemd/system/[email protected]
-
Enable and start:
systemctl --user enable calwatch@$(id -u).service systemctl --user start calwatch@$(id -u).service
-
Check status:
systemctl --user status calwatch@$(id -u).service journalctl --user -f -u calwatch@$(id -u).service
CalWatch works perfectly with vdirsyncer. Example vdirsyncer configuration:
[general]
status_path = "~/.vdirsyncer/status/"
[pair my_calendar]
a = "my_calendar_local"
b = "my_calendar_remote"
collections = ["from a", "from b"]
[storage my_calendar_local]
type = "filesystem"
path = "~/.calendars/personal"
fileext = ".ics"
[storage my_calendar_remote]
type = "caldav"
url = "https://your-nextcloud.com/remote.php/dav/"
username = "your-username"
password = "your-password"Then configure CalWatch to watch ~/.calendars/personal.
If missed events aren't being detected after sleep:
-
Check wake-up handling is enabled:
wakeup_handling: enable: true
-
Verify state file location:
ls -la ~/.local/state/calwatch/state.json -
Check daemon logs:
journalctl --user -f -u calwatch@$(id -u).service
If missed event notifications aren't staying visible:
-
Check duration configuration:
notification: duration_when_late: type: until_dismissed # Not "timed"
-
Verify D-Bus support:
# Test D-Bus notifications notify-send --expire-time=0 "Test" "This should stay until clicked"
If CalWatch is slow after extended sleep periods:
-
Reduce catchup time limit:
wakeup_handling: max_catchup_time: value: 10 # Reduce from 30 seconds unit: seconds
-
Limit missed days processed:
wakeup_handling: max_missed_days: 3 # Reduce from 7 days
-
Use summary policy for many events:
wakeup_handling: missed_event_policy: summary summary_threshold: 3
calwatch # Start the daemon
calwatch init # Create default configuration and templates
calwatch help # Show usage information
calwatch status # Show daemon status (planned)
calwatch stop # Stop the daemon (planned)CalWatch follows a clean, modular architecture:
- Config - YAML configuration with XDG directory support and user-friendly durations
- Storage - In-memory event storage with daily indexing and persistent state management
- Parser - ICS file parsing using gocal library with timezone awareness
- Watcher - File system monitoring via fsnotify/inotify
- Alerts - Minute-based alert scheduling with wake-up detection and missed event processing
- Notifications - Template rendering and desktop notification delivery with context-aware durations
- State - XDG-compliant persistent state tracking for reliable sleep/wake recovery
See design.md for detailed architecture documentation.
Display upcoming events in your status bar by creating a custom Waybar module:
"custom/calendar": {
"exec": "khal list now 24h --format '{start-time} {title}' | head -1",
"interval": 300,
"tooltip": true,
"tooltip-format": "Upcoming Events"
}- Go 1.20+
- For NixOS: Use the provided
shell.nix
go test ./...calwatch/
βββ cmd/calwatch/ # Main application entry point
βββ internal/ # Core packages
β βββ config/ # Configuration management
β βββ storage/ # Event storage and indexing
β βββ parser/ # ICS file parsing
β βββ watcher/ # File system monitoring
β βββ alerts/ # Alert scheduling
β βββ notifications/ # Desktop notifications
βββ templates/ # Default notification templates
βββ docs/ # Documentation
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
go test ./...) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- gocal - Excellent ICS parsing library
- fsnotify - Cross-platform file system notifications
- vdirsyncer - CalDAV synchronization
- The CalDAV/CardDAV community for maintaining open standards
- Support for ICS event VALARM components (respect event-defined alerts in addition to global configuration)
- Snooze and dismiss functionality with D-Bus action buttons to silence remaining alerts for specific event occurrences
- Alert policies for context-aware notifications (e.g., day-long events get 1 week/2 days/1 day alerts instead of minutes-before)
- Issues: Report bugs and feature requests on GitHub
- Discussions: Use GitHub Discussions for questions and ideas
- Documentation: Check the design.md for technical details
CalWatch - Simple, effective calendar notifications for Linux desktop environments.