Repository navigation
Authoring in YAML
Skin sources are written as YAML — a skin.yml (a full document) or a *.ymlinc (an include fragment, the YAML equivalent of .xmlinc) — and converted to and from plain XML+ by yml2xml/xml2yml (the single-file tools ymlcompile drives via yml2xmldomain/xml2ymldomain).
yml2xml <source .yml/.ymlinc file> <destination .xml/.xmlinc file> # YAML -> XML+, feed the result to xmlinc
xml2yml <source .xml/.xmlinc file> <destination .yml/.ymlinc file> # XML+ -> YAML, the reverse
- An element
<tag attr="val">...children...</tag>becomes{tag: {attr: "val", ..., children: [...]}}. - A standalone
<!-- comment -->before a child element becomes a standalone# commentright before the matching list item (and back). - A document with more than one top-level element — a real possibility for a
.xmlincfragment (spliced into a parent by literal text substitution, so it isn't required to have a single root the way a real XML document is) — becomes a top-level- tag: {...}dash list instead of the usual singletag: {...}mapping, the same shape achildren:list already uses:A single-root document (by far the common case) keeps the plain- eLabel: {backgroundColor: "separator", position: "0,e-56", size: "e,2"} - eLabel: {backgroundColor: "barBG", position: "0,e-55", size: "e,55"}
tag: {...}form unchanged.
A <convert type="..."> block holding a TemplatedMultiContent/TemplatedMultiContentEx template is a Python dict literal, not markup — RT_*/BT_* flag algebra, MultiContentEntry* call names, and numeric font indices. Rather than mirroring that syntax 1:1, it's translated into a domain-level cell: describing what's actually shown in each row:
cell:
itemHeight: 150
fonts:
- "Regular;32"
- "Regular;20"
border: {width: 1, color: 0x595959, font: "Regular;20", flags: RT_VALIGN_CENTER}
fields:
- text:
position: "5,5" # pos=/size= kept as the same "x,y" strings every other widget's position=/size= use
size: "55,25"
font: "Regular;20" # resolved from the fonts index, same font="..." syntax every other widget uses
flags: RT_HALIGN_LEFT | RT_VALIGN_CENTER
value: 0 # renamed from text= - still the raw tuple index, see note below
- icon:
position: "0,5"
size: "35,35"
flags: BT_SCALE
value: 23 # renamed from png=
- progress:
position: "90,5"
size: "90,14"
value: -22 # renamed from percent=
foreColor: "#bababa"-
Three field kinds cover every real template in this codebase:
text(fromMultiContentEntryText),icon(fromMultiContentEntryPixmapAlphaBlend, orMultiContentEntryPixmapAlphaTest— marked with avariant: "AlphaTest"key, omitted for the far more common Blend case),progress(fromMultiContentEntryProgress) — each only when the call has exactly the plain kwarg set observed in practice. Anything else (Rectangle,LinearGradient*,ProgressPixmap, or a call with an unusual extra kwarg likecornerRadius) falls back to arawfield carrying the untouched{call, args?, kwargs?}shape described below — never lossy, it just doesn't get the readability upgrade. Atextfield'sfont:is itself optional — some plugins omitfont=and let enigma2 default it, and that's preserved as an absent key rather than forced to a guessed value. -
cell.fontsis the source template's"fonts"list, kept verbatim, same order, same positions — not deduplicated or renumbered, and never dropped, even for an entry nofont=Nanywhere in the template currently points to.font=Nis a positional reference something outside this one<convert>block may rely on, so round-tripping must never renumber or delete a slot just because nothing here currently uses it. A plain two-arggFont(family, size)entry renders as the same"Family;Size"string everyfont:reference elsewhere uses (as above); anything else (e.g.parseFont(...)) falls back to the verbose{call, args}form, still insidefonts:. -
cell.varscoversTemplatedMultiContentEx's local-variable feature — some plugins declare a"var": (name := expr, ...)tuple of walrus-bound values ahead of"template"and reference them throughout itspos=/size=expressions (grid math shared across several rows/columns, computed once). Preserved as an ordered list of the exact binding text, since order matters (a later binding can reference an earlier one by name) and the right-hand side is an arbitrary Python expression, not typed data. Each entry accepts eithername := expror the more natural-lookingname = expr— always compiled back out as:=, the only valid syntax inside the"var": (...)tuple literal itself:cell: vars: - "ih = 70" - "hspace = 10" - "x1 = hspace//2+thumb_w+hspace" fields: - icon: position: "hspace//2,(ih-thumb_h)//2" # position:/size: freely mix var names, literals, and grid math size: "thumb_w,thumb_h" ...
-
MultiContentTemplateColor(n)still nests inside a color-ish key exactly as before:color: {call: MultiContentTemplateColor, args: [24]}. -
Not in scope: resolving
value: 0to a semantic name (value: startHM) — that needs a per-plugin tuple-index mapping (e.g. a screen's ownIndex.py) this tool has no generic way to discover, sovalue:stays the raw index/literal, same information astext=/png=/percent=always held. -
Every entry factory is covered the same generic way —
Text,Pixmap,PixmapAlphaTest,PixmapAlphaBlend,Progress,ProgressPixmap,Rectangle,LinearGradient,LinearGradientAlphaBlend(the complete list inComponents/MultiContent.py) — thecallname is just emitted as-is. -
TemplatedMultiContentEx'se/cgrid variables work inpos=/size=arithmetic (and inside a domain field'sposition:/size:):position: "e-350,15". -
Kwarg values are quoted strings by default (real Python string literals) — except
flags,call, anddirection, the three keys that hold code (RT_*/BT_*/GRADIENT_*constants, a callable name) rather than data. This is decided by key name, not by how the value was written: YAML's own parser can't tell a quoted string from a bare one apart once parsed, so quoting style alone can't carry the distinction through a round trip.
yml2xml, xml2yml, and xmlpretty all independently enforce the same conventions on a <convert> block's Python-source body, so a file generated by one and reformatted by another never disagrees with itself:
-
pos=(x,y)/size=(w,h)tuples,gFont(family,size)calls, andflagsOR-chains (A|B) stay tight — no space after the comma or around|— even though every other comma (between kwargs, between list items) does get a space. This matches how these are overwhelmingly hand-written across the real plugins in this codebase. - Strings always use
", never'. -
"key": valueobject pairs (itemHeight,fonts, ...) always have a space after the colon. - A raw-preserved convert body (one
parseConvertTemplatedoesn't recognize the shape of, e.g. a plugin-specific type with extra top-level keys) still gets its outer indentation normalized — the opening{/closing}line up with the<convert>tag itself, one level deeper for its own keys — without touching a single character of content it doesn't understand.