{ config, pkgs, lib, ... }: let # Python with the ICS parsing stack baked in: icalendar + recurring # recurrence expansion for the reminder scanner. python = pkgs.python3.withPackages (ps: [ ps.icalendar ps.recurring-ical-events ]); in { options.services.quickshell-cal = { enable = lib.mkEnableOption "QuickShell calendar + weather sync (Nextcloud CalDAV via vdirsyncer/khal, Open-Meteo)"; eventDays = lib.mkOption { type = lib.types.int; default = 31; description = "How many days ahead of today khal should expand/return events for the shell popup."; }; }; config = lib.mkIf config.services.quickshell-cal.enable { # The one Nextcloud fact the sync pipeline needs beyond the secret: the # base user-principal CalDAV URL (vdirsyncer discovers every calendar # under it). The username + password live in the SOPS secret # "hp-laptop/nextcloud-cal-env". home.packages = [ python pkgs.vdirsyncer pkgs.pipewire pkgs.libnotify ]; # ---------- sync script ---------- # Store the python in the Nix store with tool paths baked in (user units # have an unpredictable PATH), then expose it on the QuickShell PATH. # Substitutions keep the source tree readable. xdg.configFile."quickshell-cal/qs-cal-sync.py".text = builtins.replaceStrings [ "@vdirsyncer@" "@libnotify@" "@pipewire@" ] [ "${pkgs.vdirsyncer}" "${pkgs.libnotify}" "${pkgs.pipewire}" ] (builtins.readFile ./quickshell-cal.py); home.file.".local/bin/qs-cal-sync" = { executable = true; text = '' #!/bin/sh exec ${python}/bin/python3 \ ${config.home.homeDirectory}/.config/quickshell-cal/qs-cal-sync.py "$@" ''; }; # ---------- khal config ---------- # Generated at runtime by the sync script from the vdirsyncer discovery: # one [[calendar]] section per subdir of ~/.local/share/vdirsyncer (khal # cannot glob, and the calendar set only changes when Nextcloud does). # Removing this file from HM keeps ~/.config/khal/config a plain user # file the script may rewrite on every sync. # ---------- periodic sync ---------- # Runs as the user so caches are written to ~/.cache without privilege # games, and the SOPS env file is readable because the host config sets # owner petere / group users / mode 0440 on it. systemd.user.services.qs-cal-sync = { Unit = { Description = "Sync Nextcloud calendar + weather for the QuickShell popup"; After = [ "network-online.target" ]; }; Service = { Type = "oneshot"; ExecStart = "${config.home.homeDirectory}/.local/bin/qs-cal-sync sync"; }; }; systemd.user.timers.qs-cal-sync = { Unit = { Description = "Periodic Nextcloud calendar + weather sync"; After = [ "network-online.target" ]; }; Install = { WantedBy = [ "timers.target" ]; }; Timer = { OnBootSec = "1min"; OnUnitActiveSec = "20min"; Persistent = true; }; }; # ---------- reminders ---------- # Fires VALARM due-tones once per minute: plays the cached chime and # sends a desktop notification that lands in the QuickShell notification # center. Reads only the local .ics stores (no network), so it is cheap # enough to run every 60s. systemd.user.services.qs-cal-remind = { Unit = { Description = "Fire QuickShell calendar reminders"; }; Service = { Type = "oneshot"; ExecStart = "${config.home.homeDirectory}/.local/bin/qs-cal-sync remind"; }; }; systemd.user.timers.qs-cal-remind = { Unit = { Description = "Per-minute QuickShell calendar reminder check"; }; Install = { WantedBy = [ "timers.target" ]; }; Timer = { OnBootSec = "1min"; OnUnitActiveSec = "30s"; # default AccuracySec is a whole minute, which (plus polling phase) # lets alerts land up to ~2min late; 1s keeps current-skew tiny. AccuracySec = "1s"; Persistent = true; }; }; }; }