Skip to content

Desktop

Scripted Decorations currently have the following desktop-facing capabilities:

APIPurposeForm
fs.readText(path)Read a UTF-8 text fileReturns text, error
fs.writeText(path, text)Replace a UTF-8 text fileReturns success/error
process.start(program, arguments, callback)Run one external programAsynchronous
http.request(options, callback)HTTP/HTTPS GETAsynchronous, text-only
system.os(callback)OS informationAsynchronous
system.kernel(callback)Kernel informationAsynchronous
system.plasma(callback)Plasma versionAsynchronous
system.cpu(callback)CPU informationAsynchronous
system.memory(callback)Memory informationAsynchronous
system.disk(callback)Root filesystem informationAsynchronous
system.uptime(callback)System uptimeAsynchronous

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"
end

Read 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
end

This 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:

OptionContract
urlAbsolute http/https URL with a host, no fragment or embedded username/password; up to 16 KiB
methodOptional; only "GET" is accepted
headersOptional contiguous { name="Accept", value="application/json" } records; at most 32, no case-insensitive duplicate names
timeoutTotal 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() end

At 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:

FunctionResult
system.osOptional string fields name, id, version, version_id, pretty_name
system.kernelOptional strings type, version
system.plasmaOptional version string from TaskManSync’s build, not a live Plasma query
system.cpuOptional architecture string and positive integer ideal_thread_count
system.memoryOptional non-negative byte integers total_bytes, available_bytes, free_bytes, cached_bytes, buffers_bytes, swap_total_bytes, swap_free_bytes
system.diskRoot filesystem only; optional non-negative byte integers total_bytes, available_bytes
system.uptimeNon-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) end

The 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.send
  • kde.services.launch
  • kde.config.read
  • ui.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.