Desktop
Scripted Decorations currently have the following desktop-facing capabilities:
| API | Purpose | Form |
|---|---|---|
fs.readText(path) | Read a UTF-8 text file | Returns text, error |
fs.writeText(path, text) | Replace a UTF-8 text file | Returns success/error |
process.start(program, arguments, callback) | Run one external program | Asynchronous |
http.request(options, callback) | HTTP/HTTPS GET | Asynchronous, text-only |
system.os(callback) | OS information | Asynchronous |
system.kernel(callback) | Kernel information | Asynchronous |
system.plasma(callback) | Plasma version | Asynchronous |
system.cpu(callback) | CPU information | Asynchronous |
system.memory(callback) | Memory information | Asynchronous |
system.disk(callback) | Root filesystem information | Asynchronous |
system.uptime(callback) | System uptime | Asynchronous |
Filesystem text operations use UTF-8 text only. Paths are limited to 16 KiB and text values to 16 MiB. They do not provide directory browsing, file metadata, deletion, append mode, permissions, or file handles.
process.start has no public process handle or explicit cancellation operation. Program arguments are passed separately rather than through a shell command line. HTTP provides GET with copied text responses, not sockets or streaming. The following sections give the callable forms and result fields.
Files and Modules
For a Scripted Decoration, fs.readText(path) returns text on success or nil, error; fs.writeText(path, text) returns true after replacement succeeds or nil, error. Do not supply a callback. They look synchronous to Lua, but TaskManSync suspends/resumes the invocation while native I/O runs asynchronously. There is no public await or request handle.
Paths must be non-empty valid UTF-8 without NUL. Absolute paths are normalized lexically; relative paths use the imported source file’s directory, or the host working directory captured for the runtime if no source-file location exists. Export does not change that base. The entry-point file need not still exist, but external data and required modules must remain available. For editor-only scripts, explicit absolute paths avoid dependence on the host working directory.
There is no TaskManSync sandbox root, file whitelist, or permission prompt. Normal OS permissions apply, and symlinks follow native file behavior. Replacement writes can create a file but not parent directories; they do not append. Empty text and newlines are preserved exactly, with no added final newline. A failure before replacement preserves an existing file; successful replacement is not a power-loss durability guarantee. Errors include operation, code, message, and the resolved path.
This reads a user-chosen file without interpreting its contents as TDML. Create the file first and change the absolute path to your own:
function onTaskLeftClick()
local text, err = fs.readText("/home/alex/tms-data/status.txt")
if text == nil then
decoration.text = "File unavailable"
log.error(err.code .. ": " .. err.message)
return
end
decoration.text = "Read " .. #text .. " bytes"
endRead errors include not-found, not-a-file, not-readable, invalid-utf8, too-large, and io-failure. Write errors include parent-missing, open-failed, write-failed, and replacement-failed. For writes to the same path from independent runtimes, completion order is not guaranteed.
require(name) loads pure-Lua text modules in the same runtime and capability context. The initial package.path is ./?.lua;./?/init.lua, relative to the same base as file access. Dots in a module name become directory separators. For example, require("format.status") searches format/status.lua then format/status/init.lua. General API shows a reusable module and cache behavior. TaskManSync supplies no library directory or package manager; exporting the entry-point source does not bundle module files.
Processes
process.start(program, arguments, callback) returns true after admission or nil, error. A successful completion calls callback(result), where result contains integer exit_code, exit_status = "normal", and separate UTF-8 stdout/stderr strings. A failed start, non-zero exit, crash, invalid UTF-8, or output overflow calls callback(nil, error) instead. Check both failure stages:
function onTaskLeftClick()
local accepted, startError = process.start("date", { "+%H:%M" }, function(result, err)
if not result then
decoration.text = "Command failed"
log.error(err.code .. ": " .. err.message)
return
end
decoration.text = result.stdout:gsub("%s+$", "")
end)
if not accepted then log.error(startError.message) end
endThis example requires the date program on PATH. The program must be a non-empty UTF-8 name/path up to 16 KiB, without NUL. Arguments form a contiguous array of at most 128 UTF-8 strings, each up to 64 KiB and totaling at most 1 MiB; empty arguments are preserved. There is no implicit shell: quotes, $, *, pipes, and redirection are literal argument text, not shell syntax. The API provides no working-directory, environment, stdin, pipeline, detachment, or signal options; child stdin reads from the null device.
Each output stream is limited to 4 MiB; overflow terminates the child and returns output-limit, not a partial success. Non-zero/crashed errors can include exit_code, exit_status, stdout, and stderr when the output is valid. There can be at most eight processes per runtime and 64 across the service. Reload/removal cancels owned work and suppresses callbacks, but termination is not instantaneous and terminating descendants is not promised. A command’s external side effects are not rolled back.
HTTP and JSON
http.request(options, callback) accepts these options:
| Option | Contract |
|---|---|
url | Absolute http/https URL with a host, no fragment or embedded username/password; up to 16 KiB |
method | Optional; only "GET" is accepted |
headers | Optional contiguous { name="Accept", value="application/json" } records; at most 32, no case-insensitive duplicate names |
timeout | Total timeout in ms, 100..120000; default 30000 |
Header names are ASCII HTTP tokens up to 256 bytes, values valid UTF-8 up to 8 KiB, and aggregate request headers at most 64 KiB. Cookies are disabled, and a supplied Cookie header is rejected. There are no request bodies, uploads, binary responses, or streaming. Default system TLS verification and proxy selection apply; Lua has no TLS/proxy controls.
The call returns true on admission or nil, error. Completion supplies response with status, headers, body, and effective_url. Response header records have lowercase names, preserve duplicates, and are limited to 64 entries/64 KiB total with values up to 8 KiB. Bodies are complete strict UTF-8 text up to 4 MiB. HTTP 4xx/5xx are responses, not Lua errors: check status yourself. Transport, TLS, timeout, oversized/invalid text, and redirect failures instead call callback(nil, error).
Only same-origin redirects (same scheme, host, and effective port) are followed, up to five hops, for 301/302/303/307/308. A redirect to another host or scheme fails with redirect-rejected; excess hops fail with redirect-limit. A 3xx without a valid Location is returned normally. effective_url identifies the final accepted URL.
This pattern expects a JSON response such as { "count": 3 }. Replace the placeholder example.test URL with your own endpoint; it is not a working service. JSON decoding has a separate 256 KiB limit, smaller than the HTTP body limit:
decoration.interval = 60000
local pending = false
local function refresh()
if pending then return end
pending = true
local accepted, admissionError = http.request({
url = "https://example.test/status",
headers = { { name = "Accept", value = "application/json" } },
}, function(response, err)
pending = false
if not response then
decoration.text = "Network unavailable"
log.error(err.message)
return
end
if response.status < 200 or response.status >= 300 then
decoration.text = "HTTP " .. response.status
return
end
local data, decodeError = json.decode(response.body)
if type(data) ~= "table" or type(data.count) ~= "number" then
decoration.text = "Invalid response"
if decodeError then log.error(decodeError.message) end
return
end
decoration.text = "Count: " .. data.count
end)
if not accepted then
pending = false
log.error(admissionError.message)
end
end
function onLoad() refresh() end
function onTick() refresh() end
function onTaskLeftClick() refresh() endAt most four requests per runtime and 32 across the service may be outstanding. Stop, reload, faults, a hand-off to another applet, and shutdown suppress stale completions. The pending flag prevents this script from overlapping a slow request with its next refresh. The exported Temperature.lua provides a real HTTP/JSON example with location and service-identification requirements.
System Information
Each system function takes a callback and returns true after admission or nil, error. The callback receives copied data, or nil, error for failure. Fields can be absent; do not assume every platform supplies them. Request each capability separately and refresh by calling it again:
| Function | Result |
|---|---|
system.os | Optional string fields name, id, version, version_id, pretty_name |
system.kernel | Optional strings type, version |
system.plasma | Optional version string from TaskManSync’s build, not a live Plasma query |
system.cpu | Optional architecture string and positive integer ideal_thread_count |
system.memory | Optional non-negative byte integers total_bytes, available_bytes, free_bytes, cached_bytes, buffers_bytes, swap_total_bytes, swap_free_bytes |
system.disk | Root filesystem only; optional non-negative byte integers total_bytes, available_bytes |
system.uptime | Non-negative integer seconds since boot; nil, nil if unavailable |
Only one system request may be outstanding per runtime, and 16 across the service. For a dashboard, request the next capability from the preceding completion callback rather than starting all seven together. There is no combined snapshot, watcher, request handle, or cancellation method. Missing fields are not whole-request errors.
local accepted, admissionError = system.memory(function(memory, err)
if err then
log.error(err.message)
return
end
local available = memory and memory.available_bytes
decoration.text = available and string.format("Available: %.0f MiB", available / 1048576)
or "Memory unavailable"
end)
if not accepted then log.error(admissionError.message) endThe API does not expose used_bytes, CPU utilization/model/topology, GPU, sensors, network statistics, process lists, arbitrary mount paths, or desktop/session identity. free_bytes and available_bytes are different OS categories. The exported System Information Dashboard.lua demonstrates sequential collection; NVIDIA VRAM Usage.lua uses an external program rather than a hidden GPU API.
The following general-script desktop capabilities are not installed in Scripted Decoration runtimes:
kde.notifications.sendkde.services.launchkde.config.readui.createMenu()
There is no generic D-Bus, Qt, Plasma, or application-task access through these modules. Scripts are trusted local-user code, not a permission-isolated sandbox; review their file, program, and network effects before importing them.