Back to Noctalia

Entry Scripts

v5-plugins-development-entries.md

latest7.9 KB
Original Source

Entry Scripts

A script defines global functions that Noctalia calls, and drives the UI through the API namespaces. Which globals apply depends on the entry kind:

FunctionWidgetShortcutLauncherDesktopPanelServiceWhen
update()every update interval
onClick() / onRightClick()pointer press
onMiddleClick()middle press
onQuery(text)launcher text changed (behind the prefix)
onActivate(id)a launcher result was selected
onOpen(context) / onClose()the panel is opened / closed
onFrameTick(deltaMs)every frame, after desktopWidget.setNeedsFrameTick(true)
onIpc(event, payload)noctalia msg plugin …
onConfigChanged()a plugin setting changed (see below)

The top level of the script runs once at load - set up state and register noctalia.state.watch handlers there.

Reacting to settings changes in a service

Section titled “Reacting to settings changes in a service”

When a user edits a plugin setting, the change reaches each entry differently:

  • Widgets, desktop widgets, panels are rebuilt, so their next getConfig(...) reads the new value automatically.
  • Services are long-lived. If a service defines onConfigChanged(), the host updates its settings in place and calls that function , noctalia.getConfig(...) returns the new values, and the service keeps all its in-memory state (timers, caches, connections). If a service does not define onConfigChanged(), the host instead restarts the service runtime (the top-level chunk re-runs with the new settings).

Either way, noctalia.state is process-lifetime and survives a service restart, so any cache you store there must be keyed by the config it depends on , otherwise a service will keep serving data fetched for the old settings. For example, store the location alongside cached results and re-fetch when it differs:

function onConfigChanged() local loc = noctalia.getConfig("city") .. "|" .. noctalia.getConfig("country") if loc ~= noctalia.state.get("location") then noctalia.state.set("location", loc) refetch() -- location changed; invalidate and reload endend

Desktop widgets and panels use declarative ui.* trees. See Declarative UI for their rendering APIs and controls.

barWidget.* - widget presentation + settings

Section titled “barWidget.* - widget presentation + settings”

setText · setGlyph · setImage · setTooltip · clearTooltip · setFont · setColor · setGlyphColor · setVisible · isVertical · render · getConfig(key)

A bar widget can either patch the built-in glyph/text row with the imperative setters below, or describe composite content as a ui.* tree with barWidget.render(tree) - see Declarative UI.

local label = barWidget.getConfig("label")
function update() noctalia.setUpdateInterval(1000) barWidget.setGlyph("puzzle") barWidget.setText(label)end
function onClick() noctalia.notify("Hello", "you clicked me")end

Custom fonts

Section titled “Custom fonts”

Register a font file with noctalia.loadFont(path), then pass the returned family name to setFont. The font becomes usable anywhere text is drawn - setFont and a ui.* label’s fontFamily prop - and stays available across every surface once loaded.

setFont(family, baseline?) takes a font family name and an optional baseline mode:

BaselineUse
"text" (default)Normal text, cap-band centered.
"textFixedHeight"Text whose row height is locked to the font metrics (steady height in lists).
"inkCentered"Centers the glyph’s ink.
"pictographic"Art/icon fonts anchored at the ink top (keeps pose art steady, e.g. bongocat).
local font = noctalia.loadFont("fonts/bongocat.otf")if font then barWidget.setFont(font, "pictographic")end

shortcut.* - control-center tile

Section titled “shortcut.* - control-center tile”

setLabel(text) · setIcon(on [, off]) · setActive(bool) · setEnabled(bool)

local on = noctalia.state.get("toggled") == true
local function render() shortcut.setLabel(on and "On" or "Off") shortcut.setIcon("bulb") shortcut.setActive(on)endrender()
function onClick() on = not on noctalia.state.set("toggled", on) render()end

launcher.* - launcher provider

Section titled “launcher.* - launcher provider”

A launcher provider answers queries behind a prefix declared in the manifest. The user types the prefix, and everything after it is passed to onQuery(text); the provider replies with launcher.setResults(query, results). onActivate(id) runs when a result is selected (the id is whatever you set on that result).

Manifest fields on a [[launcher_provider]] entry: prefix (the trigger - use a leading / to match the built-in providers and appear in the launcher’s / overview), glyph (default result icon), include_in_global_search (also answer the un-prefixed search; default false), and debounce_ms (wait this long after the last keystroke before running onQuery - set it for network-backed providers so you aren’t called on every character; default 0).

setResults(query, results) · setQuery(query) · getConfig(key)

  • query must echo the text from onQuery, so late results map back to the query they answer - the latest one wins. Calling it with an empty list clears the provider’s results.
  • Each result is a table: { id, title, subtitle?, glyph?, icon?, badge?, query?, score? }. id is passed back to onActivate unless query is set. Results are ordered by score (descending), then insertion order.
  • glyph (a Tabler/Nerd-Font name) or icon (a themed icon name) is the row’s leading visual. badge is a short string (e.g. an emoji or =) drawn in place of the icon - setting it hides glyph/icon. Use the subtitle for secondary text.
  • setQuery(query) replaces the open launcher’s raw input text. Call it from onActivate to treat a result as a drill-in step: the launcher rewrites the input and stays open instead of closing, then re-runs onQuery with the new text. A result-level query field does the same declaratively - when that result is selected the launcher replaces the input with it and stays open, no onActivate needed. Either way, include your prefix to stay routed to the same provider, e.g. "/tr french ". (An onActivate that does not call setQuery closes the launcher as usual.)

Use noctalia.fuzzyScore(pattern, text) to rank local lists with Noctalia’s native fuzzy matcher. It returns a numeric score, or nil when pattern does not match text.

Queries run off the UI thread, so a result can arrive after onQuery returns - publish a placeholder synchronously, then call setResults again from an async callback (HTTP, subprocess) when the real answer lands.

[[launcher_provider]]id = "translate"entry = "translator.luau"prefix = "/tr"glyph = "language"
function onQuery(text) if text == "" then launcher.setResults(text, { { id = "hint", title = "Type something to translate" } }) return end launcher.setResults(text, { { id = "loading", title = "Translating…", glyph = "loader" } }) noctalia.http({ url = endpoint(text) }, function(res) launcher.setResults(text, { { id = res.body, title = res.body, glyph = "language" } }) end)end
function onActivate(id) noctalia.copyToClipboard(id, "text/plain")end