mycal is a private, local-first calendar, reminder manager, and Markdown note
popup for Waybar. It runs natively on Wayland with GTK4 and
gtk4-layer-shell.
The screenshot uses an isolated profile and synthetic events.
- No account or cloud service
- No Google credentials or OAuth setup
- No telemetry or required network access
- Local notes, reminders, settings, and attachments
- Month and year views
- Quick events and a full Markdown editor
- Missed reminder delivery after the next login or system resume
- 46 built-in interface languages
- Matugen, Omarchy, pywal, TOML, and manual color support
The project is developed and tested on Arch Linux with niri. Other Wayland compositors that implement layer-shell may also work.
At runtime, data stays on the local machine:
| Path | Contents |
|---|---|
${XDG_DATA_HOME:-~/.local/share}/mycal/local.json |
Notes and schedules |
${XDG_DATA_HOME:-~/.local/share}/mycal/settings.json |
Preferences and custom languages |
${XDG_DATA_HOME:-~/.local/share}/mycal/images/ |
Pasted note images |
${XDG_STATE_HOME:-~/.local/state}/mycal/fired.json |
Delivered reminder state |
${XDG_CONFIG_HOME:-~/.config}/mycal/theme.toml |
Optional theme configuration |
$XDG_RUNTIME_DIR/mycal/, or ${XDG_CACHE_HOME:-~/.cache}/mycal/ when unavailable |
PID and temporary Waybar cache |
Local data is not application-level encrypted. Use normal filesystem permissions and disk encryption when the contents are sensitive.
- Linux and a Wayland compositor with layer-shell support
- Python 3.11 or newer at
/usr/bin/python3 - GTK 4
- gtk4-layer-shell
- PyGObject and pycairo
- Babel
- Waybar
systemd --userflock,pkill,readlink, and standard GNU userland tools- A notification daemon such as mako, swaync, or dunst
- A Nerd Font or Font Awesome font is recommended for interface icons
sudo pacman -S --needed \
git python python-gobject python-cairo python-babel \
gtk4 gtk4-layer-shell waybar procps-ng coreutils util-linuxInstall and configure a notification daemon separately if the desktop does not already provide one.
For other distributions, install equivalent packages from the system package manager. PyGObject and gtk4-layer-shell should be installed as system packages, not from an isolated Python virtual environment.
Clone this repository and run the per-user installer:
git clone https://github.com/Jamoncillo22/mycal.git
cd mycal
./install.shDo not run the installer with sudo.
install.sh first checks the interpreter, Python modules, GTK libraries, and
required commands. It then calls the atomic deployment script, which:
- Stages and compiles a release.
- Installs code under
$HOME/.local/share/mycal. - Installs
mycal.serviceunder the user systemd configuration. - Enables and starts the reminder daemon.
- Verifies the daemon PID and service state.
- Rolls back automatically if activation fails.
- Keeps the active release and one rollback release.
Notes and settings are never copied from the source checkout and are preserved during updates.
The repository previously contained the Google Calendar-based
waybar-ycal. When detected, install.sh disables and stops its user service
or standalone popup before deploying mycal, preventing both daemons from
running together.
The migration deliberately does not delete ~/.config/waybar-ycal, Google
credentials, tokens, or package-manager files, and it does not import Google
events into the local calendar. Replace the old Waybar commands with the
fragment below. After verifying mycal, remove the old package and private
directory manually if they are no longer needed.
Add the fragment below to the top-level object in
~/.config/waybar/config.jsonc:
Then place custom/ycal in one module list, for example:
"modules-center": ["custom/ycal"]The installer normally copies the same fragment to:
~/.config/mycal/examples/waybar-module.jsonc
Optional neutral styling is available in waybar-style.css:
#custom-ycal {
padding: 0 12px;
margin: 4px 2px;
background: rgba(0, 0, 0, 0.25);
border-radius: 10px;
}
#custom-ycal.error {
color: #ff6b6b;
}After changing the Waybar configuration, reload or restart Waybar once.
Click the Waybar module to open or hide the calendar. The service remains in the background so reminders can be delivered while the popup is closed.
| Action | Result |
|---|---|
| Click the Waybar module | Show or hide mycal |
| Click a day | Select that day |
| Scroll over the calendar | Change month or year |
| Click Quick note | Open the compact event form |
| Click Add | Open the complete Markdown editor |
| Click an event title | Open the event viewer |
| Click the pencil | Edit an event |
| Right-click an event | Toggle completion |
Press Escape |
Close the active window or popup |
An event can be all-day, have one start time, or have a start and end time. End times at or before the start are treated as overnight events.
| Quick note | Markdown editor | Note preview |
|---|---|---|
![]() |
![]() |
![]() |
| General settings | Language selection | Appearance settings |
|---|---|---|
![]() |
![]() |
![]() |
The screenshots contain demonstration data rather than a private calendar.
The editor supports headings, lists, task lists, block quotes, fenced code, bold, italic, inline code, links, and local images. Raw HTML is escaped.
Use the image button to choose a local image, or paste an image with
Ctrl+V. Managed images are stored under the mycal data directory. Remote
images are not downloaded into Markdown previews.
Reminders can be scheduled in seconds, minutes, hours, or days before an event. The daemon handles:
- Duplicate delivery protection
- Catch-up after login or resume
- All-day and overnight events
- Daylight-saving gaps and repeated hours
- Completed-event exclusion
If the computer is off, asleep, or no graphical session is running when a reminder becomes due, mycal checks for missed reminders at the next login or resume and delivers the notification then. Catch-up remains valid until the event's explicit end, until the next midnight for an all-day event, or for up to 24 hours after a timed event without an end time.
Notifications use the standard org.freedesktop.Notifications D-Bus
interface.
Open the native settings window from the calendar to configure:
- Interface language
- Monday or Sunday week start
- Notification enable/disable
- System or custom IANA time zone
- Automatic or custom date format
- 12-hour or 24-hour time
- Font family and size
- Interface scale
- Theme colors
- Custom language catalogs
The default settings example is normally copied to
~/.config/mycal/examples/settings.example.json.
Matugen theme shown with the same synthetic calendar profile.
The default auto theme source checks providers in this order:
- Matugen
- Omarchy
- pywal
- Built-in defaults
When no generated provider is available, mycal uses this built-in fallback:
| Role | Color |
|---|---|
| Text | #eee0d5 |
| Background | #18120c |
| Accent | #f9ba72 |
| Secondary accent | #e0c1a3 |
| Error | #ffb4ab |
Omarchy and pywal work without additional mycal configuration when their standard color files already exist.
Matugen needs a one-time output entry because mycal cannot safely edit an
existing Matugen configuration. The installer places the input template at
~/.config/mycal/matugen-template.toml. Add this block to
~/.config/matugen/config.toml:
[templates.mycal]
input_path = "~/.config/mycal/matugen-template.toml"
output_path = "~/.cache/mycal/matugen.toml"Then run the normal Matugen generation command used for the wallpaper. Once
~/.cache/mycal/matugen.toml exists, the default auto source selects it
before Omarchy or pywal. The same block is normally copied to
~/.config/mycal/examples/matugen-config-snippet.toml.
Configuration examples are best-effort: deployment prints a warning instead of replacing an active release if the examples directory is not writable.
The installer copies templates but cannot generate a palette because it does
not know which wallpaper or theme the user wants. Keep the default auto
source, then regenerate colors with the provider whenever the wallpaper or
desktop theme changes:
- Matugen: run the normal Matugen command or wallpaper script, such as
setwall, after adding the template block above. - Omarchy: apply the Omarchy theme normally so its
colors.tomlis updated. - pywal: run the normal
walcommand so~/.cache/wal/colors.jsonis updated.
If ~/.config/mycal/theme.toml contains source = "manual", generated colors
are intentionally ignored. Remove that file or change it to source = "auto"
to follow Matugen, Omarchy, or pywal again.
mycal reads the generated files each time the popup opens. If it is already visible, close and reopen it from the Waybar module. Restarting the service is an equivalent fallback:
systemctl --user restart mycal.serviceWaybar does not need to be reloaded just to update the popup colors. If the same theme command also changed Waybar's own CSS and did not reload it, reload Waybar separately:
pkill -SIGUSR2 -x waybarTo use a manual theme:
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/mycal"
cp \
"${XDG_CONFIG_HOME:-$HOME/.config}/mycal/theme.example.toml" \
"${XDG_CONFIG_HOME:-$HOME/.config}/mycal/theme.toml"Edit the copied file and set source = "manual". Supported sources are
auto, matugen, omarchy, pywal, manual, and file. Theme opacity and
corner radius are configured in the same TOML file.
These commands apply to an existing mycal installation. Users of the legacy Google Calendar version should follow Migrating from waybar-ycal first.
cd mycal
git pull --ff-only
./install.shUpdates use the same validation, atomic activation, and rollback process as a first installation.
First remove custom/ycal from the Waybar configuration. Then uninstall the
service and application code while preserving all notes and settings:
./uninstall.shTo permanently delete the code and every local note, setting, pasted image, and reminder record:
./uninstall.sh --purge-dataThe purge operation cannot be undone without a backup.
Check the service:
systemctl --user status mycal.service
journalctl --user-unit=mycal.service -n 50 --no-pagerCheck the Waybar JSON producer directly:
"$HOME/.local/share/mycal/bar.py"Toggle the popup directly:
"$HOME/.local/share/mycal/toggle.sh"If the service cannot start, confirm that /usr/bin/python3 can import
gi, babel, GTK4, and Gtk4LayerShell. Running ./install.sh repeats the
same dependency check.
Run every syntax check and test in isolated temporary XDG directories:
./verify.shThe unittest modules live under tests/; application files remain at the
repository root for direct deployment.
The verification script does not read or write the real calendar. Tests cover storage validation, bounded I/O, atomic writes, concurrency, reminders, DST, completion, Markdown safety, languages, themes, Waybar output, deployment rollback, and uninstall behavior.
Planned local-first features include handwritten notes with a mouse or stylus, user-controlled voice-note recording, and optional calendar synchronization. See docs/ROADMAP.md for scope, privacy requirements, and later ideas.
| File | Responsibility |
|---|---|
popup.py |
GTK windows, editor, settings, reminders, and D-Bus integration |
bar.py |
Localized Waybar JSON and event indicator cache |
mycal_core.py |
Event validation, storage transactions, schedules, and reminders |
mycal_io.py |
Bounded regular-file reads |
mycal_markdown.py |
Safe Markdown parsing, links, tasks, and local images |
mycal_theme.py |
Theme discovery, parsing, and validation |
i18n.py |
Languages, date/time formatting, and settings |
launcher.py |
Stable launcher pinned to one release |
deploy.sh |
Atomic deployment and rollback |
See SECURITY.md. Do not attach real local.json,
settings.json, images, notification contents, or reminder-state files to a
public issue.
mycal began from the GTK popup concept in waybar-ycal by yagybaba. The upstream MIT notice is retained in LICENSE.
MIT. See LICENSE.







