https://github.com/olafkfreund/waycal
Calendar, Mail and Tasks widgets for Wayland/niri — Quickshell (QML) front end driven by the gog CLI. Declarative Nix flake + home-manager.
https://github.com/olafkfreund/waycal
calendar gmail google-tasks gruvbox home-manager niri nix qml quickshell wayland
Last synced: 10 days ago
JSON representation
Calendar, Mail and Tasks widgets for Wayland/niri — Quickshell (QML) front end driven by the gog CLI. Declarative Nix flake + home-manager.
- Host: GitHub
- URL: https://github.com/olafkfreund/waycal
- Owner: olafkfreund
- Created: 2026-06-20T05:21:49.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-06-20T05:49:39.000Z (26 days ago)
- Last Synced: 2026-06-20T07:14:03.365Z (26 days ago)
- Topics: calendar, gmail, google-tasks, gruvbox, home-manager, niri, nix, qml, quickshell, wayland
- Language: QML
- Size: 41 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# waycal
A three-widget Google productivity suite for **Wayland / niri**, rendered with
[Quickshell](https://quickshell.org) and driven entirely by the
[`gog`](https://github.com/) CLI — no in-app OAuth, no Google API client.
| Widget | What it shows | Toggle |
| --- | --- | --- |
| **Calendar** | always-on agenda (next N days) + full-month dashboard overlay | `Mod+C` |
| **Mail** | unread Gmail threads + detail overlay | `Mod+M` |
| **Tasks** | open Google Tasks, check to complete | `Mod+T` |
Inspired by [waylandar](https://github.com/samjoshuadud/waylandar), but the
backend is the `gog` CLI rather than a bundled Python Google-API client.
## How it works
```
niri keybind ─► qs -c waycal ipc call toggle
│
systemd --user: waycal.service (EnvironmentFile → GOG_KEYRING_PASSWORD, GOG_ACCOUNT)
│ spawns (inherits env)
QML Process ─► waycal-fetch (thin Python adapter, stdlib only)
│ spawns (inherits env)
gog --json … (Google Calendar / Gmail / Tasks)
```
The entire frontend↔backend contract is **"a process prints a JSON array to
stdout; QML `JSON.parse`s it."** `waycal-fetch` shells out to `gog --json …`,
normalizes the result into a small uniform schema, and prints it. On failure it
prints `{"error": …, "needsAuth": …}` so a widget can show a hint instead of
crashing. No sockets, no IPC files.
### Normalized schemas
- **event** `{id, title, description, start, end, allDay, location, link, calendar}`
- **mail** `{id, threadId, from, subject, snippet, date, unread, link}`
- **task** `{id, listId, list, title, notes, due, status, link}`
## Prerequisites
- `gog` configured with at least one account (`gog auth add you@example.com`).
- A Wayland compositor with `wlr-layer-shell` (niri, Hyprland, sway, …).
- Quickshell (pulled in by the flake / home-manager module).
### The one hard requirement: gog's keyring password
`gog` stores its OAuth refresh token in a **file keyring** that needs
`GOG_KEYRING_PASSWORD` to be readable in a non-interactive process. The widgets
run non-interactively, so this **must** be supplied. The home-manager module
does it via a systemd `EnvironmentFile` secret (see below). Verify your setup:
```bash
gog auth doctor
```
## Install (NixOS flake + home-manager module)
> home-manager is consumed as a **flake module**, not the `home-manager` CLI.
```nix
# flake.nix
{
inputs.waycal.url = "github:olafkfreund/waycal";
# in your home-manager configuration:
imports = [ inputs.waycal.homeManagerModules.waycal ];
programs.waycal = {
enable = true;
account = "you@example.com";
# secret file containing: GOG_KEYRING_PASSWORD=… (and optionally GOG_ACCOUNT=…)
keyringPasswordFile = config.age.secrets."waycal-gog".path;
settings = {
agenda_days = 7;
mail_query = "in:inbox is:unread";
task_lists = "all";
};
};
}
```
This installs `waycal` + `quickshell`, writes the Quickshell config to
`~/.config/quickshell/waycal`, renders `~/.config/waycal/config.toml`, and starts
a **systemd --user** service (`waycal.service`) that runs `qs -c waycal` with the
keyring secret in its environment.
Then add the [niri keybinds](examples/niri-config.kdl).
## Use without the module
```bash
# run the UI directly (gog must find GOG_KEYRING_PASSWORD in the env)
nix run github:olafkfreund/waycal# # or: nix develop, then `qs -p ./frontend`
# debug the adapter on its own
nix run github:olafkfreund/waycal#waycal-fetch -- agenda --days 7 | jq
```
Copy [`examples/config.toml`](examples/config.toml) to `~/.config/waycal/config.toml`.
## Configuration
All keys live in `~/.config/waycal/config.toml` (or `programs.waycal.settings`):
| key | default | meaning |
| --- | --- | --- |
| `account` | `""` | gog account email (`""` → `$GOG_ACCOUNT` / single token) |
| `agenda_days` | `7` | days ahead for the agenda widget |
| `agenda_max` | `50` | max agenda events |
| `calendars` | `"all"` | `"all"` or comma-separated ids/names |
| `mail_query` | `"in:inbox is:unread"` | Gmail search query |
| `mail_max` | `20` | max mail rows |
| `task_lists` | `"all"` | `"all"` or comma-separated list ids/names |
| `task_max` | `50` | max tasks per list |
## Development
```bash
nix develop # quickshell + python3 + uv + jq + nix linters
qs -p ./frontend # run the UI from the source tree
python backend/waycal_fetch.py agenda --days 7 | jq
python backend/waycal_fetch.py raw -- calendar events --from today --days 1 # dump raw gog JSON
```
`waycal-fetch raw -- ` prints gog's untouched JSON — handy if a field
isn't being picked up and a normalizer in `backend/waycal_fetch.py` needs a tweak.
## Roadmap (Phase 2)
- `notify-send` reminder daemon (a systemd timer running `waycal-fetch agenda`,
independent of widget visibility).
- Live [matugen](matugen/Theme.qml.tmpl) theming (the `Theme` singleton is already
the single source of all colors).
- Multi-account.
## Layout
```
backend/waycal_fetch.py # the gog adapter (stdlib only)
frontend/ # Quickshell config (shell.qml + widgets + 3 service singletons)
nix/package.nix # build: copy + waycal-fetch wrapper
modules/home-manager.nix # programs.waycal (config + systemd secret wiring)
matugen/Theme.qml.tmpl # optional theming template
examples/ # niri keybinds + sample config.toml
```
## License
MIT.