Scripted Decorations
A Scripted Decoration exposes a small set of objects and functions rather than arbitrary Qt, QML, Plasma, or TaskManSync objects.
Unlike a Static Decoration, it runs Lua to compute its presentation. Its taskbar output is called the Taskbar Decoration; an optional separate Script-Created Window belongs to the same script. Neither surface lets Lua manipulate other applications’ task items. See Getting Started to create and place a definition.
The main object is decoration:
decoration.text = "CPU: {bld}18%{}"
decoration.backgroundColor = "#202020"
decoration.widthMode = "Content"The commonly used properties are:
| Property | Purpose | Values / syntax |
|---|---|---|
decoration.text | Main Decoration text | TDML string |
decoration.backgroundColor | Background of the whole Decoration surface | Color string, such as #RRGGBB or #RRGGBBAA; empty string removes the override |
decoration.widthMode | Requested mode; effective mode can fall back | "Content", "Fixed", "Flexible" |
decoration.fixedWidth | Requested extent along the taskbar flow when using Fixed mode | Integer 1..4096 logical pixels; assignment selects Fixed |
decoration.geometry | Realized surface size | Read-only table; nil until a valid surface exists |
decoration.interval | Requests periodic update opportunities | Integer 50..2147483647 ms; set during top-level initialization only |
Content, Fixed, and Flexible are Scripted Decoration requested layout choices. They do not determine the Decoration’s position in the task order. Content measures parsed whole-Decoration text only; positioned text and graphics are excluded. Visual-only output falls back to effective Flexible with a warning, while genuinely empty output remains Content.
Flexible Decorations share the remaining taskbar layout space equally with other participating Flexible Decorations. A Fixed extent follows the taskbar flow, so it is the height on a vertical taskbar. Your drawing still uses the realized surface’s width and height, not the requested flow extent.
There is a precedence to the scripted width controls:
decoration.fixedWidth = valueselects Fixed mode automatically. You do not need to assigndecoration.widthMode = "Fixed"separately.- A later
decoration.widthMode = "Flexible"changes the requested mode while retaining the previously assigned fixed width for a later return to Fixed. - Content is the default when the script does not assign
widthMode. decoration.widthModeaccepts Content, Fixed, and Flexible; unsupported values raise a script diagnostic and leave the previous requested mode unchanged.
decoration.widthMode describes the requested mode. The immutable
presentation snapshot carries the effective mode consumed by layout; they
match except for the visual-only Content fallback described above. Positioned
or animated graphics bounds are not measured.
decoration.geometry reports the realized Decoration surface, not the requested width or the size of the taskbar. Its values are logical pixels:
local geometry = decoration.geometry
if geometry then
log.info(geometry.width .. " x " .. geometry.height)
endThe geometry table is a copy; changing it cannot resize the surface. Defer geometry-dependent calculations while it is nil. Later surface-size changes request a coalesced onTick() opportunity, folded into the next periodic tick when periodic updates are enabled. Initial geometry does not itself trigger an initial tick.
decoration.text uses TDML, not HTML. Plain text is valid, but dynamically inserted literal text containing TDML delimiters must use the TDML escape rules. Absolute size/sz values use logical pixels; szr is a positive factor of the internal Decoration height, not percentage syntax. Root-level posh and posv position the whole text presentation inside the surface; they do not move the taskbar item. offseth/osh and offsetv/osv offset a logical line.
A non-empty decoration.backgroundColor overrides the whole surface. Clearing it restores TDML dcolor/dc, then the definition and Theme fallbacks. TDML bgc colors only its text run. Successful background values read back as canonical #RRGGBBAA; invalid non-empty colors raise a script diagnostic and preserve the previous value. These runtime changes do not save new definition settings.
Updating a Decoration
For an immediate additional update opportunity, use:
requestUpdate()This is coalesced: multiple requests can result in one pending onTick() opportunity. It does not recursively call onTick() and does not promise immediate rendering.
A periodic update can be requested during top-level initialization. This clock publishes immediately and refreshes once per requested second:
decoration.interval = 1000
local function updateClock()
local fields = time.localDateTime(time.now())
decoration.text = string.format("%02d:%02d:%02d", fields.hour, fields.minute, fields.second)
end
function onLoad()
updateClock()
end
function onTick()
updateClock()
endThe value is a requested interval, not a Lua-owned timer. The applet that runs the script schedules serialized, coalesced opportunities; other applets render its shared output. onTick(frameTimeSeconds) receives elapsed seconds for periodic or animated Window frame opportunities, but an explicit requestUpdate() supplies no argument. Check for nil before using the argument for animation. Missed ticks are not queued for catch-up, and callbacks should do bounded work rather than loops that wait for time to pass.
Pending work is cancelled when the runtime is stopped, reloaded, removed, faulted, or handed off to another applet. A reload starts fresh Lua variables and handles; use persistent storage for values that must survive it. The interval is initialization-only, and onLoad() is already too late to assign it. See Scheduling for one-shot deadlines and cancellation.
Click Modifiers
Task-item click callbacks receive a modifiers table:
| Field | Meaning |
|---|---|
modifiers.shift | Shift was held |
modifiers.ctrl | Ctrl was held |
modifiers.alt | Alt was held |
modifiers.meta | Meta/Super was held |
All fields are boolean. There is no global keyboard-state API and no key-down/key-up callback for the Decoration’s task item.
For example, let an ordinary click open a Window and Shift-click reset a stored counter by branching on modifiers.shift. Right-click remains TaskManSync’s Decoration context menu; there is no onTaskRightClick or script-owned menu API. Window keyboard and action events are separate from task-item clicks.
Positioned Text
A Scripted Decoration can create independently positioned text elements:
local status = decoration:createText("status")
status.x = 12
status.y = 4
status.text = "{bld}Ready{}"The key must be non-empty and unique among the live elements in the current runtime. Elements retain creation order; later elements are drawn above earlier elements.
Positioned text is useful when ordinary multiline TDML is not enough. Its coordinates are relative to the realized Decoration surface and are clipped by that surface.
Coordinates are finite fractional logical pixels with a top-left origin; x increases rightward and y downward. Negative and off-surface coordinates are allowed. The elements are content-sized and never enlarge the taskbar allocation or contribute to Content sizing. status.horizontalFlip = true mirrors an element horizontally; status:remove() removes it and invalidates its handle. A removed key can be reused for a new element, but every old handle becomes invalid on reload.
Each element parses its TDML independently. Run styles and line offsets work, but whole-Decoration directives posh, posv, dcolor, and dc are invalid here; use x/y and the parent’s surface background instead. Alignment has no visible effect in the initial content-sized element box. Element changes within one callback publish together; assignment is not an immediate paint. Elements draw above whole-Decoration text, with later-created elements above earlier ones.
Graphics
Scripted Decoration graphics use two retained collections:
decoration.graphics.linesdecoration.graphics.paths
For example:
decoration.graphics = {
lines = {
{ x1 = 0, y1 = 0, x2 = 0.9, y2 = 0.5, color = "#39FF72", width = 2 },
},
paths = {
{
x = 12, y = 4, width = 24, height = 16,
fill = "#39FF72",
operations = {
{ op = "move", x = 0.1, y = 0.5 },
{ op = "cubic", c1x = 0.2, c1y = 0, c2x = 0.8, c2y = 1, x = 0.9, y = 0.5 },
{ op = "close" },
},
},
},
}Line endpoints are normalized from 0.0 through 1.0 relative to the surface, with a top-left origin and y increasing downward. Path rectangles use logical pixels; path operation coordinates are normalized within each rectangle.
Line width is an integer from 1 through 256, defaulting to 1; at most 2,048 lines are accepted. For a path, x/y range from -4,096 through 4,096 and required width/height from 1 through 4,096. Optional fill and stroke select paint, and strokeWidth is a finite logical-pixel value from 0 through 4,096, defaulting to 1. Missing/empty fill or stroke means no such paint. Paths are mapped to the rectangle without preserving aspect ratio, so use its dimensions to control stretching.
move starts a subpath; line and cubic need an existing one; close closes it. Open paths and multiple subpaths are allowed. Graphics accept at most 2,048 paths, 32,768 total operations, and 4 MiB of copied path data. Arrays must be contiguous; malformed records, unknown fields, invalid colors, and out-of-range values reject the assignment and retain the previous graphics.
Assigning decoration.graphics replaces both collections as one copied value; omitted collections become empty. To animate, keep the data in Lua, change it, and assign the complete value again from onTick(). { lines = {}, paths = {} } clears graphics. The surface clips all primitives. Lines draw before paths, and positioned text draws above them. Graphics do not change taskbar width allocation and have no pointer hit-testing API.
These are the supported scripted graphics primitives. They are not an SVG, PNG, shader, arbitrary native-object, or general scene-graph API.