I was reading the DankMaterialShell docs and found a way to add events to their top bar drop down calendar. You can sync Google Calendar with Khal or manually add events directly with Khal. The thing is, I use Thunderbird, which grabs my Google calendar already so why not just grab them there?
This code will grab 7 days of events, or whatever period you need, from Thunderbird and add the records to Khal, which then get picked up by the drop-down calendar. I imagine this will work on other window managers with DankMaterialShell installed, but I am running Niri on CachyOS.
You can set the script to grab n days of events, one week seems good, so the code is set to run every Sunday. Errors display as desktop notifications so you know if your events don’t get added. Errors could be more granular, I suppose. It works for my purposes. This does not handle tasks, as far as I know.
Setup
In Thunderbird you need to set your calendars to local so they get added to the database as it syncs with Google. This should collect offline calendar events without making any changes. If you use Evolution, Kalendar, or something else, this code will not work without rewriting the database query.
Install Khal and configure.
Issues
The start date and end dates are odd, or maybe it’s just me… For example, holidays all start a day early and end on the actual holiday. I am not sure if Google does this or Thunderbird. In the Dank calendar you only get an event for the day before. Maybe that is by design?
In my case I decided to simply use the end date with no timestamp. I only have full day events so they don’t span days or weeks. I am mostly interested in holidays, paydays, birthdays, and things I need to do on a specific date.
To remedy this for others, I think adding a day to the start date and end date with time should do it. That could cause issues with some events, but I do not know since I have not tested it. Play around with it until you find a solution that works for your calendars.
Finally. If you do not see your events in Dank, log out and back in.
Code
#!/usr/bin/env python3
import sqlite3
import os
import datetime
import subprocess
'''
Add Google calendar events from Thunderbird to Niri desktop calendar (top bar).
Mohawke, Feb. 2026
Since I already get my Google calendar in Thunderbird it seemed silly to setup
another process to grab the same data.
I am in the US, Eastern.
Install khal (Arch): sudo pacman -S khal
Run: khal configure
I had to manually add the personal calendar in the khal config (~/.config/khal/config...).
see: https://khal.readthedocs.io/en/latest/configure.html
Before running this script!
Set ADDEVENTS at line 34ish to False.
Run this code in the terminal and verify your events, dates, and times.
If everything looks good, set ADDEVENTS = True
Things you can do to make this better.
Add error handling and logging or tap into notifications on error so you are aware.
In my case: notify-send "message" will send a notification on the desktop.
'''
'''
===============================================================================
User Configs
===============================================================================
'''
# Set your Khal calendar. Private might work, but I have not tried it.
Calendar = "personal"
# The database lives in ~/.thunderbird under a sub-folder that is likely not the
# same as mine. calendar-data/cache.sqlite should be the same.
# Swap USERNAME and profile, t0vttsrv.default-release, for yours.
Database = "/home/USERNAME/.thunderbird/t0vttsrv.default-release/calendar-data/cache.sqlite"
# Add the events to Khal
ADDEVENTS = True
# If DayOnly is set to True, AdjustAddDay and UseTimeStamp get set to False
# and events will be added by end date with no time.
AdjustAddDay = False # Add day to start and end event dates. Includes time.
UseTimeStamp = False # Do not add day to start and end event dates. Includes time.
DayOnly = True # Adds event with end date only with no time.
# Limit in days. For 7 days you should run this script one day a week, etc...
DateLimit = 7
'''
===============================================================================
Start Code Below
===============================================================================
'''
# datetime stuff...
now = datetime.datetime.now()
delta = now - datetime.timedelta(days=DateLimit)
cutoff_epoch_time = delta.timestamp()
cutoff_epoch_time = cutoff_epoch_time * 1000000
now = now.timestamp() * 1000000
if DayOnly:
AdjustAddDay = False
UseTimeStamp = False
if AdjustAddDay:
UseTimeStamp = False
DayOnly = False
if AdjustAddDay:
UseTimeStamp = False
DayOnly = False
if os.path.exists(Database):
try:
with sqlite3.connect(Database) as connection:
cur = connection.cursor()
cur.execute(f'SELECT title,event_start,event_end FROM cal_events WHERE event_end > {cutoff_epoch_time} AND event_end < {now} ORDER BY event_start;')
rows = cur.fetchall()
for row in rows:
title = row[0]
'''
Most of my events are a single day with no need for times. I also found that single day events start a day ahead and
did not factor in the next day. As an example, Valentine's day started on 13th and ended on the 14th, but the calendar
only marked the 13th so all my events where a day ahead. If you do not have this issue you can add start and end with
time, but you may need to add a day to end_date for the calendar to register the last day. I am sticking with only using
date from end_date. I will leave the code here using start and end with times.
'''
# Adjusts events start and end dates a day and includes time for multiday events.
if AdjustAddDay:
start_dt = datetime.datetime.fromtimestamp(row[1] / 1000000)
end_dt = datetime.datetime.fromtimestamp(row[2] / 1000000)
delta_start = now - datetime.timedelta(days=1)
delta_end = now - datetime.timedelta(days=1)
start_date = delta_start.strftime("%m/%d/%Y %H:%M:%S")
end_date = delta_end.strftime("%m/%d/%Y %H:%M:%S")
args = f"/usr/bin/khal new -a {Calendar} {start_date} {end_date} {title}"
if UseTimeStamp:
start_date = datetime.datetime.fromtimestamp(row[1] / 1000000).strftime("%m/%d/%Y %H:%M:%S")
end_date = datetime.datetime.fromtimestamp(row[2] / 1000000).strftime("%m/%d/%Y %H:%M:%S")
args = f"/usr/bin/khal new -a {Calendar} {start_date} {end_date} {title}"
if DayOnly:
end_date = datetime.datetime.fromtimestamp(row[2] / 1000000).strftime("%m/%d/%Y")
args = ["/usr/bin/khal", "new", "-a", Calendar, end_date, title]
print(args) # To validate events.
# Verify the above args before allowing this code to run.
if ADDEVENTS:
stdoutdata, stderrdata = subprocess.Popen(args).communicate()
stdoutdata, stderrdata = subprocess.Popen(["/usr/bin/notify-send","--app-name=Event Update", "Events updated."]).communicate()
except Exception as e:
stdoutdata, stderrdata = subprocess.Popen(["/usr/bin/notify-send","--app-name=Event Update Failed!", e]).communicate()
Run once a week via Systemd
Note: You could use crontab if you prefer.
I saved my Python script as ~/Documents/CronScripts/thunderbird2khal.py
I want to run this script every Sunday at 10:00am.
Create two files in ~/.config/systemd/user
Filename: khalevents.timer
Adjust for your preferred run-time, description, and service name if you changed it from khalevents.service.
Contents
[Unit] Description=Thunderbird events to khal synchronization timer Requires=khalevents.service [Timer] OnCalendar=Sun *-*-* 10:00:00 [Install] WantedBy=timers.target
Filename: khalevents.service
Adjust for your paths, filenames, and description. Be sure to swap USERNAME with your username.
Contents
[Unit] description=Thunderbird events to khal synchronization timer [Service] Type=oneshot WorkingDirectory=/home/USERNAME/Documents/CronScripts ExecStart=/usr/bin/python /home/USERNAME/Documents/CronScripts/thunderbird2khal.py [Install] WantedBy=default.target
Refresh services, so the system is aware of the new files.
systemctl --user daemon-reload
Enable and start the services
systemctl --user enable khalevents.timer
systemctl --user enable khalevents.service
You can check the logs to see if they are running if you did not receive a desktop notification as expected.
journalctl --user | grep "khal"