Templates: Jinja → XML → image
A screen template (the file a screens: entry points template: at) is a Jinja template. Rendering it
produces a string of XML, which is then parsed and walked to build the image — so everything in a
template must produce well-formed XML once Jinja’s {{ }}/{% %} are evaluated. This has two practical
consequences: attribute values coming from Jinja expressions need to end up quoted (x="{{ x_pos }}",
not x={{ x_pos }}), and any literal &/</> in text content needs XML escaping (& for a
literal &, as in the example below).
Variables and functions available to every template
Name |
Description |
|---|---|
|
The panel size from the config’s |
|
The current render time, as a timezone-aware |
|
|
|
|
|
Calls |
The tag vocabulary
Every element in the rendered XML is a “view”: either a layout container (<box>, <column>, <row>)
or a piece of content (<text>, <image>, <line>, <rectangle>, and a few source-specific ones like
<weather.icon>). The full reference, with every attribute each one accepts, is in
Widget reference. The root element of every template must be <screen>.
A worked example
This is configs/home/family_calendar.xml.jinja, trimmed slightly:
{% macro homework() -%}
{% with ical = source("school_work") -%}
{% for event in ical.walk('vevent') -%}
{% set start = event.get('dtstart').dt -%}
{% if start > timestamp.date() %}
<box height="85">
<image y="0" x="0" path="home/cal_task.png" />
<text y="12" x="80" size="35" font="home/Roboto-Bold.ttf">{{ start.strftime('%-d/%-m') }}</text>
<text y="12" x="300" width="440" size="35" font="home/Roboto.ttf">{{ event.get('summary') }}</text>
</box>
{% endif %}
{% endfor %}
{% endwith %}
{%- endmacro %}
<screen>
<rectangle x="0" y="0" width="{{ width }}" height="70" fill="true" color="#5c5c5c" />
<text x="{{ width // 2 | int }}" y="10" size="40" align="center" font="home/Roboto-Bold.ttf" color="white">
{{ timestamp.strftime('%A, %B %d') }}
</text>
<column x="700" y="100">
<box>
<text size="50" font="home/Roboto-Bold.ttf">LÄXOR & PROV</text>
<line y="65" x2="700" y2="65" width="7" />
<column y="95">
{{ homework() }}
</column>
</box>
</column>
</screen>
A few things worth noting from this example, since they’re easy to trip over:
source("school_work")is called inside a Jinja{% for %}loop, iterating events straight from theicalendarcalendar object thecalendar.icalsource returns (see Calendar: calendar.ical, calendar.google).A Jinja macro (
homework()) that itself contains XML tags is a normal way to factor out a repeated chunk of layout — the macro’s output is just more XML text, spliced into the surrounding document.Nesting a
<box>of<text>/<line>items inside a<column>composes layout containers just like composing HTML — see Layout: box, column, row, screen for exactly how each one sizes and positions its children.width="{{ width }}"on the<rectangle>pulls the panel width from the template variable, not a hardcoded pixel value, so the same template adapts if the config’sscreens.widthchanges.
Debugging a template
Two tools make it much faster to figure out why a screen doesn’t look right:
uv run todays_paper <config> -s <screen> -d -o preview.png— the-dflag logs the XML Jinja produced before it’s walked into an image, which is the quickest way to spot a missing attribute, a mis-nested tag, or a macro that didn’t expand the way you expected.-t "2024-12-25 08:00"— render as if it were a specific date/time, useful for testing day-of-week/time-of-day branches in a template (like the homework-deadline check above) without waiting for that date to actually arrive.