Skip to content

Instantly share code, notes, and snippets.

@stefjow
Created April 4, 2026 14:04
Show Gist options
  • Select an option

  • Save stefjow/8a0d9cac8a3392dfa9324e2113bda820 to your computer and use it in GitHub Desktop.

Select an option

Save stefjow/8a0d9cac8a3392dfa9324e2113bda820 to your computer and use it in GitHub Desktop.
Waybar khal calendar integration
#!/usr/bin/env python3
import json
import os
import pty
import re
import select
import subprocess
from datetime import datetime
# ANSI color code to Pango color mapping
ANSI_COLORS = {
"30": "#000000", "31": "#ff5555", "32": "#00ff00", "33": "#ffff00",
"34": "#5599ff", "35": "#ff00ff", "36": "#66d9ef", "37": "#ffffff",
"90": "#555555", "91": "#ff5555", "92": "#55ff55", "93": "#ffff55",
"94": "#79b8ff", "95": "#ff55ff", "96": "#88e0ef", "97": "#ffffff",
}
def run_khal_with_pty():
"""Run khal in a PTY so it outputs ANSI colors."""
master, slave = pty.openpty()
try:
p = subprocess.Popen(
["khal", "calendar"],
stdout=slave, stderr=subprocess.DEVNULL, close_fds=True
)
os.close(slave)
output = b""
while True:
r, _, _ = select.select([master], [], [], 3)
if not r:
break
try:
data = os.read(master, 4096)
if not data:
break
output += data
except OSError:
break
p.wait()
finally:
try:
os.close(master)
except OSError:
pass
return output.decode("utf-8", errors="replace").replace("\r\n", "\n").strip()
def ansi_to_pango(text):
"""Convert ANSI escape sequences to Pango markup."""
# Escape Pango special chars first (but not things inside ANSI sequences)
# Split on ANSI sequences, escape the text parts, then reassemble
parts = re.split(r"(\x1b\[[0-9;]*m)", text)
result = []
open_spans = 0
for part in parts:
m = re.match(r"\x1b\[([0-9;]*)m", part)
if m:
codes = m.group(1).split(";") if m.group(1) else ["0"]
# Reset
if codes == ["0"] or codes == [""]:
result.append("</span>" * open_spans)
open_spans = 0
continue
attrs = []
bold = False
color = None
reverse = False
for code in codes:
if code == "1":
bold = True
elif code == "7":
reverse = True
elif code in ANSI_COLORS:
color = ANSI_COLORS[code]
if reverse:
# Reverse video: use white bg, dark fg for "today" highlight
attrs.append("background='#ffffff'")
attrs.append("foreground='#1e1e2e'")
elif color:
attrs.append(f"foreground='{color}'")
if bold:
attrs.append("weight='bold'")
if attrs:
result.append(f"<span {' '.join(attrs)}>")
open_spans += 1
else:
# Escape Pango markup characters in text
part = part.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
result.append(part)
# Close any remaining open spans
result.append("</span>" * open_spans)
return "".join(result)
now = datetime.now()
clock_text = now.strftime("%Y/%m/%d %H:%M")
try:
raw_output = run_khal_with_pty()
khal_output = ansi_to_pango(raw_output)
except Exception:
khal_output = "khal unavailable"
print(json.dumps({
"text": clock_text,
"tooltip": f"<tt><span font_family='JetBrainsMono'>{khal_output}</span></tt>",
"markup": "pango",
"class": "clock"
}))

Waybar khal calendar integration

Setup

  1. Place clock_khal.py in ~/.config/waybar/scripts/ and make it executable
  2. Add "custom/clock" to your waybar config modules-right
  3. Add this module definition to your waybar config:
"custom/clock": {
    "format": "{}",
    "return-type": "json",
    "interval": 60,
    "exec": "~/.config/waybar/scripts/clock_khal.py",
    "on-click": "alacritty -e khal interactive"
}
  1. In your ~/.config/khal/config, enable event day highlighting:
[default]
highlight_event_days = True

Hovering over the clock shows your khal calendar with color-coded events.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment