feat(display): add Kitty graphics backend and improve font detection

- Add KittyDisplay using kitty's native graphics protocol
- Improve cross-platform font detection for SixelDisplay
- Add run-kitty mise task for testing kitty backend
- Add kitty_test.py for testing graphics protocol
This commit is contained in:
2026-03-15 23:56:48 -07:00
parent 9e4d54a82e
commit 20ed014491
5 changed files with 398 additions and 6 deletions

View File

@@ -188,6 +188,88 @@ class SixelDisplay:
self.cell_width = cell_width
self.cell_height = cell_height
self._initialized = False
self._font_path = None
def _get_font_path(self) -> str | None:
"""Get font path from env or detect common locations (cross-platform)."""
import os
import sys
from pathlib import Path
if self._font_path:
return self._font_path
env_font = os.environ.get("MAINLINE_SIXEL_FONT")
if env_font and os.path.exists(env_font):
self._font_path = env_font
return env_font
def search_dir(base_path: str) -> str | None:
"""Search directory for Geist font."""
if not os.path.exists(base_path):
return None
if os.path.isfile(base_path):
return base_path
for font_file in Path(base_path).rglob("*"):
if font_file.suffix.lower() in (".ttf", ".otf", ".ttc"):
name = font_file.stem.lower()
if "geist" in name and ("nerd" in name or "mono" in name):
return str(font_file)
return None
search_dirs: list[str] = []
if sys.platform == "darwin":
search_dirs.extend(
[
os.path.expanduser("~/Library/Fonts/"),
"/System/Library/Fonts/",
]
)
elif sys.platform == "win32":
search_dirs.extend(
[
os.path.expanduser(
"~\\AppData\\Local\\Microsoft\\Windows\\Fonts\\"
),
"C:\\Windows\\Fonts\\",
]
)
else:
search_dirs.extend(
[
os.path.expanduser("~/.local/share/fonts/"),
os.path.expanduser("~/.fonts/"),
"/usr/share/fonts/",
]
)
for search_dir_path in search_dirs:
found = search_dir(search_dir_path)
if found:
self._font_path = found
return found
if sys.platform != "win32":
try:
import subprocess
for pattern in ["GeistMono", "Geist-Mono", "Geist"]:
result = subprocess.run(
["fc-match", "-f", "%{file}", pattern],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0 and result.stdout.strip():
font_file = result.stdout.strip()
if os.path.exists(font_file):
self._font_path = font_file
return font_file
except Exception:
pass
return None
def init(self, width: int, height: int) -> None:
self.width = width
@@ -210,12 +292,15 @@ class SixelDisplay:
img = Image.new("RGBA", (img_width, img_height), (0, 0, 0, 255))
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
self.cell_height - 2,
)
except Exception:
font_path = self._get_font_path()
font = None
if font_path:
try:
font = ImageFont.truetype(font_path, self.cell_height - 2)
except Exception:
font = None
if font is None:
try:
font = ImageFont.load_default()
except Exception: