Repository navigation
Releases
Releases · Prslc/WayRun
WayRun v0.12.0
Added
theme.toml's[motion].entrance_mssets how long the launcher's entrance
fade and rise take, in milliseconds (0–2000, default 240).0skips the
entrance while the card height spring and result-row motion still run;
reduced = truestill disables all motion. It applies live.theme.toml's[layout].fixed_height(defaultfalse) holds the card at
the height ofmax_rowsrows once there is a query or results, so results,
split previews and the action panel no longer resize it. A pristine show
(no query, no results) stays the compact search-only card. It applies live.- Lua plugins can read the session's desktop id with
wayrun.desktop()
(kde,niri,hyprland, …), resolved once at startup. - The
sessionaction ({"type":"session","verb":"reboot"}) lets a row ask
forlock,suspend,reboot,shutdownorlogoutwithout embedding a
desktop-specific command; the executor resolves it for the session. - A
run_in_terminalaction takeskeep_open
({"type":"run_in_terminal","cmd":"pacman -S x","keep_open":true}): once the
command exits, the terminal continues in an interactive$SHELLinstead of
closing, so a plugin's install or build output stays readable. The plugin
that emits the action decides; rows without the field behave as before, and
the field is additive, soPLUGIN_APIstays at 1. wayrun.apps()lists the installed applications as{ id, name, icon, exec }, with the name in the interface language and the icon resolved to an
absolute path exactly as anapp-searchrow shows them, so a plugin does not
parse.desktopfiles to label a process. The binding is additive: a script
can test for it, andPLUGIN_APIstays at 1.wayrun.fs.readlink(path)returns a symbolic link's target, or nil when the
path is absent or not a link. The link itself must lie in the plugin's
readable places while its target may point anywhere, so a plugin can read
/proc/<pid>/exeto tell apart processes that share a name. The binding is
additive: a script can test for it, andPLUGIN_APIstays at 1.
Changed
- The action panel no longer draws a header band: the actions start where the
first result row would, and the parent row or group title moves to the right
of the footer, beside the action count.[layout].panel_headeris gone; an
existing entry is ignored. A panel now matches a list with the same number
of rows in height. - The text family moved from
config.toml's[font].familyto
theme.toml's[font].family, next to the interface size it belongs with.
It still applies live. A[font]table left inconfig.tomlis ignored, so
move the key totheme.toml. - On KDE Plasma,
reboot,shutdownandlogoutaskorg.kde.Shutdownto
end the session first and fall back tosystemctl/loginctl, so systemd no
longer stopsplasmashellitself and waits out its timeout. wayrun statusreports the detected desktop, the daemon pid, the socket path
and the version after thevisible/hiddenline.- The built-in system command rows describe the action instead of showing the
shell command they run.
Removed
- Breaking:
wayrun openandwayrun closeare gone;wayrun toggle
covers both, and the socket accepts onlytoggleandstatus. Update
scripts that used them.
Fixed
- The footer no longer paints hints or the no-match note over the search field
while the card is still compact. "No results" waits for the search to land
and uses the count pill's chip style instead of the accent colour.
WayRun v0.11.0
Added
- A text preview can render Markdown. Add
format = "markdown"to a text
preview to lay out headings, emphasis, inline code, code blocks, lists,
quotes, rules and link text in the preview column;plainremains the
default and the wire field is additive, soPLUGIN_APIstays at 1.
Over-long plain and CJK tokens split on character boundaries instead of
being elided. wayrun.date(seconds?)answers the UTC civil fields (year, month, day,
hour, minute and second) for a Unix timestamp, defaulting to now. The
binding is additive: a script can test for it, andPLUGIN_APIstays at 1.- The LuaLS stub types the row, action, preview and plugin tables, and the
two newexecflags, so a misspelled row field is caught at edit time.
Changed
- The installation guide and both READMEs now lead with the prebuilt x86_64
tarball (built on Ubuntu 24.04); building from source remains the path for
other architectures and older distributions. - The guides are also published as a VitePress site at
https://prslc.github.io/WayRun/, in English and Chinese, with the same
pages kept in the repository. - The guides gain a plugin directory listing the worked Lua and Python
examples from the WayRun-Plugins workspace, with what each needs and how
to register one.
Fixed
- A keyword-less external host gets 250ms in the default chain instead of
50ms. A cold host such as the KDE settings plugin, which runs
kcmshell6 --list, took about 140ms and was silently dropped, so a query
such as输入法returned nothing. - The blur region is committed only after the card fades in, so the first
entrance frame no longer shows the smeared wallpaper across the card's
rect before the card covers it. wayrun.execreportsstdout_truncatedandstderr_truncatedwhen a
stream exceeds its cap (1 MiB stdout, 8 KiB stderr); a stream that exactly
fills its cap stays false.
WayRun v0.10.0
Added
- A KDE session follows its colour scheme. The accent is
[General] AccentColor, else the scheme'sDecorationFocus; the mode is the window
background's luminance; and~/.config/kdeglobalsis watched like the DMS
palette. A leftoverdms-colors.jsonno longer decides the mode. - An action entry can be a group: file and directory rows gather their content
type's handlers under oneOpen withentry instead of flattening them into
the row's own commands.Enterdescends,Escbacks out one level and
Shift+Entercloses the panel from any level. The remembered default and a
resumed command follow the tree, and a group carries a trailing chevron.
Changed
- Breaking: the built-in
wopen-window search and the
compositor-niri/compositor-hyprlandbuild features are gone; one
binary runs on every supported compositor. A user who wants window search
needs a plugin that provides it.
Fixed
- A desktop action runs once.
g_desktop_app_info_launch_actionreturns
void, and reading its return register as a success flag madelaunch()
run the hand-expandedExec=after GLib had already started the action. - KWin: the launcher uses
Layer::Topand theutilitynamespace, so it no
longer covers the input-method candidate window and KWin does not animate
the dimmed backdrop as a normal window on open. - A settled repaint no longer accumulates the hairline: the antialiased rim
falls outside the card rect, and the restore and damage now carry a
one-logical-pixel pad. - The blur region follows the card's rounded corner instead of a rectangle
that left a staircase on it.
WayRun v0.9.0
Added
- A keyword-routed host streams its results. With
"stream": truein the
request, a Lua host receives anemitcallback assearch's second
argument and eachemitreplaces the rows on screen; a forked or resident
host writes the samepartiallines. The core forwards them as
results_partialnotifications, the shell keeps the search pending until
the finalresults, and a superseded search drops its queued partials. - A result's
iconand an image preview's path may be anhttps://URL. The
core fetches it into a bounded background cache — four workers, superseded
when a new search starts — rewrites it to a local path and sends
image_readywhen it lands, so the row or preview reloads without a new
payload.icon_shape: "circle"clips a row's icon. - The selected row can carry a split-view preview: an image or plain UTF-8
text in one fixed column of 46% of the content width, so the column does not
move when the row changes kind. A short list reserves a square for an image
or a readable minimum for text, and host text is capped at 64 KiB.
[preview] enabled = falseturns the column off. - File and directory rows list the system's handlers for their content type,
after the built-in terminal, reveal and copy actions.open_withlaunches
one through GIO with the launcher's language context, and the list is cached
per content type over the.desktopandmimeapps.listroots. wayrun plugin add|enable|disable|set-keyword|remove|list|checkand
wayrun config get|list|set|unsetedit the user'splugins.tomland
config.tomlthroughtoml_edit, keeping the template's comments,
re-parsing the typed model and writing atomically; a running launcher
reloads the file through its watcher.plugin addloads the script first
and refuses a configured id or a keyword another entry owns, and
plugin checkreports a script's shebang, exec bit, manifest, API version
and registration.plugin{plugin,action,payload}hands a row's command back to its host:
commandroutes it to the named plugin'scommandmethod, inside the
plugin's sandbox, and runs it as a pending command so a dismissal cannot cut
it.- Lua plugins declare the
PATHprograms they may run, and
wayrun.exec(program, args?)runs one with argv, no shell, in$HOMEand
under a 1.5 s deadline. The sandbox keeps its env whitelist. A plugin also
gains a writable cache directory,fs.write/fs.delete,wayrun.import
for modules loaded in the same_ENV,wayrun.file_uri,wayrun.locale,
wayrun.http.get_many, andwayrun.http.cached, which answers from the
store withstale = truewhen expired.printandwayrun.logwrite to
the journal, with a per-call budget. - A Lua host reloads when its script or a module it imported changes, and a
failed reload keeps the last good VM serving. theme.tomlcan pin any derived surface:[surfaces]takescolorand/or
alpha(and the hairline'swidth),[surfaces.dark]/[surfaces.light]
layer per mode, and an omitted field keeps deriving from the palette.
[footer] enabled = falsedrops the hint bar and gives its band back to the
card.- The list reports its wait: after 150 ms the footer says
Searching…and
suppresses the no-results note, and a host's search error becomes one
display-only row with the first line of the message. - A LuaLS stub ships for editor completion.
Changed
- Breaking: the clipboard history is no longer built in. The
ckeyword,
its provider, strings and glyph are gone; installexamples/lua/cliphist.lua
from the WayRun-Plugins workspace and register it withkeyword = "c"and
resident = true. The Lua exec stdout cap is now 1 MiB so a full
cliphist listfits.wl-clipboardstays for copy and paste. - Breaking: the command line is clap's. A bare
wayrunon a terminal
prints the command list, andwayrun runstarts the launcher; a spawn
without a terminal still runs it.--list-pluginsis gone,wayrun-core
always serves the protocol,--helpand--versionwork, and a second
instance says the launcher is already running and points at
wayrun toggle. - Breaking: a Lua plugin's relative filesystem names resolve under its own
directory, absolute and~reads stay inside a declared area,fs.write
lands in the plugin directory, andkv.dband its siblings are reserved. - A built-in file row no longer previews an image; the split column is a
plugin capability now, and[preview] enabledgoverns plugin previews. - The card animates: results extend it through a critically damped height
spring (growth ~150 ms, shrink ~240 ms), a reordered row slides from where
it is drawn, and a list appearing from nothing fades in with a 4 px rise.
The entrance eases its opacity out while the card rises 8 px, and the
default card sits at 0.20 of the output height. - The UI is its own
wayrun-uicrate, holding the drawing and the
presentation it derives from, and the large modules are split by concern. - The render buffer and its shm pool stay for the process's life, so every
show after the first commits a warm frame; the allocator is trimmed on
dismissal and the shaped-line cache is capped at 32 entries. - A search and its dispatch share one lifetime: a newer query aborts the stale
dispatch, so a forked host dies with its future, and a resident host whose
caller was dropped is replaced instead of leaving the next call to wait out
its deadline. - Remote images are fetched concurrently and pruned by the newest search; a
preview PNG streams row by row and a JPEG decodes at the smallest DCT scale
that covers the pane, so a 4K wallpaper no longer builds the full buffers. - The dark card is more opaque and its hairline is white 0.12; the light
card's hairline is black 0.10.
Fixed
- Enter no longer launches the row still on screen while a search is pending:
the press is held until the final payload lands, and an edit, the action
panel or a dismissal drops it. - A list refresh faded every row in on each keystroke; only a list appearing
from nothing animates now. - A keystroke inside the 150 ms loading delay no longer restarts it, so the
footer does not keep deferring its first show. - A plugin action that runs the row's command stands in for the synthesized
Open entry, so the plugin's title and icon lead the panel. - The Lua HTTP cache is keyed by the request headers, so an authenticated or
language-specific reply never answers another request, and query parameters
are sorted so one parameter set builds one URL. - An SVG preview reads the SVG's intrinsic size, and an image preview is
fitted to its aspect and averaged on a downscale. - The builtin icons are packaged from
ui/assets/icons, so a release build
keeps its glyphs. plugin listpads by display width (a CJK name takes the columns its glyphs
take), clips an overflowing cell, and says enabled or disabled.- A host's identity is re-resolved on a chmod as well as an edit, and a spawn
failure other than "not found" is named on stderr. - An exec child starts in
$HOMEunder a 1.5 s deadline, and arunaction's
stderr stays on the journal. - The resident unit's template no longer sets
PATH=(it replaces the user
manager's environment; useenvironment.d), and the open-with docs name a
content type's handlers correctly.
WayRun v0.8.0
Added
- The resident core's disk-backed caches have a lifecycle.
Source/Live
watches the roots behind the app list, the$PATHscan, the icon chain and
the window icons, bumps an epoch when one changes, and rebuilds on the next
query; a synchronous consumer still reads the last snapshot without touching
the filesystem. An installed application, a new$PATHexecutable, a theme
install or a[icon] themeedit no longer waits for a restart. Access events
are ignored and a rebuild never runs inside the notify callback. - An external host's identity is re-resolved when its executable's
(mtime, size)stamp moves, and the configured executables are watched, so
editing a script updates its name, icon and ready hint live. Results were
already fresh: the host forks per call. candwlist their content — the clipboard history and the open
windows — instead of the identity card. A default view revalidates its burst
cache before answering, because the shell redraws only on the next query
change and a stale-while-revalidate answer would leave the first show one
fetch behind.- A path query needs no keyword:
~/Project,/etc/hostsor
Project/WayRunis one row for the existing path, and otherwise the typed
directory's entries matching the last component are listed. Only the typed
directory is read, the listing is capped, and an exact hit leads.f/d
keep their file/directory filter. - A plain query no provider answers lists the first
$HOMElevel, one
readdir, so a partial name still finds the directory it starts with
without walking the home. - Tab completes the last
/- or space-separated component from the visible
rows: it grows to the candidates' common prefix, else takes the selected
row, replacing only that component. It is off during IME preedit and while
the action panel is open. - A desktop entry's actions launch through GLib's
g_desktop_app_info_launch_action, soDBusActivatable(an action with no
Execat all),Path=,Terminal=and the field codes are GLib's; the
hand-expandedExecremains the fallback. Nested ids (foo/bar.desktop)
resolve. - Terminal actions prefer
$TERMINAL, thenxdg-terminal-exec, then the
built-in emulator table, and the action's directory becomes the helper's
working directory. theme.toml's[layout].panel_headersets the action panel's header band
height, 28 by default.
Changed
- Breaking:
config.toml,plugins.toml,theme.tomland the Lua plugin
directory now live under$XDG_CONFIG_HOME/wayrun, andstate.dbunder
$XDG_STATE_HOME/wayrun(migrated automatically from the old location). A
user who setsXDG_CONFIG_HOMEmust move the three files and the plugin
directory there. The cache followsdirs::cache_dir, and the socket and its
lock require an absolute$XDG_RUNTIME_DIR; a missing value is an error, not
a shared/tmpfallback. - The default
[font] familyis the genericsans-serif, resolved by
cosmic-text per machine, instead of a named family. - The README carries measured warm and cold query latencies, with the full
per-provider table and the method folded into a details block. - Local release builds use thin LTO and the default codegen units; the
shipped binary keeps fat LTO and one codegen unit, set by the CI and release
workflows. - The large modules are split by concern — the surface, plugin registry, file
provider, icon cache, executor's terminal handling, theme config and Lua
host — with no behaviour change.
Fixed
- The POSIX and freedesktop audit batch: a detached
shorgiono longer
inherits the JSON-RPC pipe, a helper is bounded by a 2 s deadline, a host
command resolves likeexecvp, XDG config dirs and relative data dirs are
honored, flatpak user exports and flat pixmaps resolve,ShowItemsgets its
URI list first, an action honorsExec/Path/Terminal, and the cache,
state, config andkvdirectories are created 0700. - Launched children get
SIGPIPEback to its default; an ignored disposition
survives exec and Rust ignores it. - The remaining desktop and process edge cases: nested ids resolve, an empty
%iicon is dropped,~userstays unresolved, sqlite temporaries are
per-process, a cross-device rename falls back to copy, the IPC client fails
on an error reply, accepts back off, the instance lock is taken before
Wayland, and a dead core exits 1 instead of looking like a clean dismiss. - Localisation follows GLib's order —
LANGUAGEfirst, ignored for a C base
locale — and the session's originalLANGUAGEis handed to launched apps
rather than the launcher's pinned UI locale. - The help usage placeholder and the window workspace summary are translated,
not inline English. - Program resolution checks the exec bit, as
execvpdoes. - The fallback walk follows
user-dirs.dirs, including localized and moved
Desktop/Documents/Downloads. - An unset, empty or separator-only
XDG_DATA_DIRSmeans the spec default,
and the flatpak user export path is built from$XDG_DATA_HOME. ~/.iconsprecedes the system icon roots, matching the Icon Theme spec's
legacy location.- Clipboard history skips entries whose id is not numeric, and the copy
pipeline runs without a nested shell wrapper. - Path completion resolves each typed parent component case-insensitively,
after trying it exactly.
WayRun v0.7.0
Added
- The plugin contract carries a version.
wire::PLUGIN_APIis one number both
plugin surfaces report — a host fromlist_plugins'sapi, a Lua script from
its plugin table'sapi— and the core logs a mismatch against it rather than
refusing the plugin, which may still work; a plugin written before versioning
counts as0and is told so until it states what it means. Lua reads the
current number aswayrun.api, so one script can span two launcher versions.
It moves when a plugin can observe the change, not for an addition or a
launcher-internal change: adding a binding leaves it alone, while tightening a
sandbox rule moves it. Seeplugins.mdandjsonrpc.md. - A Lua plugin declares the areas it may read (
read: absolute paths or
~/…, no patterns) and the environment names it may read (env: exact
names, no patterns). A path is resolved through symlinks, so a link cannot
escape, and the plugin's own directory and its script's directory are
implicit. Asqlitehandle answers only for a plugin that could have
opened its source itself, so a sibling cannot borrow another plugin's
snapshot. Seeplugins.md. wayrun.kvkeeps strings under keys in a SQLite database inside the
plugin's own directory, with an optional TTL per entry, so a cache can
outlive one show: it is the sandbox's only write, bounded to that directory,
its keys not paths.kv.keysandkv.pairsenumerate the store as ordered
arrays with expired rows filtered out, so a plugin keeping a list of its own
no longer maintains an index key by hand.wayrun.http.postcarries a JSON, form or raw body, and both verbs take an
options table —timeout_ms,headers, and attlongetthat caches a
2xx reply in the plugin's own key-value store. A reply's response headers
come back lowercased and its body as raw bytes, and the timeout defaults to
two seconds, down from five, clamped inside the host call's own ceiling.
Aheaderssub-table amongparamsand a bare timeout as the third
argument both still work. The one network exit isureqnow — a connection
pool, system-root TLS and gzip — whereminreqwas, so request header
names go out lowercased. Seeresident.md.wayrun.which(name)answers an executable's path from the host's$PATH—
the same one arunrow's command will see — ornil, and
wayrun.fuzzy.match(query, candidates)ranks a script's own candidates by
the built-ins' match kinds, best first, ties keeping the input order.wayrun.cryptoanswerssha256,md5andhmac_sha256as hex digests
over raw bytes, plusbase64_encode/base64_decode, where a decode
answersnilon garbage rather than raising, so the translation and base64
plugins no longer hand-roll them.
Changed
- Breaking: the empty-query launch history is gone. An empty query returns
no rows and clears the list locally, and thetopandforgetmethods, the
history read path, the host forget relay, the panel's launcher-owned
Remove from history entry and thebuiltin:removeglyph go with it. A
host's owntopdefault view is a different method and stays. See
jsonrpc.md. - Breaking: pins are gone, with the
pin/unpinmethods, the pin table
and the panel's Pin to top/Unpin entries. A pin's scope was the exact
trimmed query, so a pinned row could only be summoned by retyping that
string, and pinning froze a copy of the row beside its live result; the
action panel covers the same ground with entries a plugin owns and can make
default. - Breaking:
selectisrecord, taking{"on_click": Action}, and the
database moves tostate.dbwith theusagetable shrunk to(on_click, count). The oldusage.dbis left where it is and no longer read, so the
counts and the remembered defaults start over once. Seejsonrpc.md. - Breaking: an
actionsentry'sactionis anActionobject
({"type":"run",…}) rather than{"type":"execute","command":{…}};
ResultItemloses itsbadgeandephemeralfields, and a row is exempt
from recording only when its command is acopy. A host that sets either
field is ignored rather than obeyed: the badge's only producer was the pin
that just went, and with history gone the counts are the whole story. - Breaking:
wayrun.envraises for a name the calling plugin has not
declared, andfs.list,fs.stat,sqlite.snapshotandsqlite.query
refuse a path — or a handle whose source lies — outside its declaredread
areas. A script that read the environment or the filesystem without
declaring them must declare them now. - Breaking:
wayrun.icon()no longer answers abuiltin:glyph, and a
host's icon must be an absolute path — a file it ships, or a theme icon or
papirus:spec thatwayrun.icon()resolved. The built-in glyphs render in
the shell now, from its own compiled SVGs, and the core keeps only the
vocabulary (wire::BUILTIN_GLYPHS) it validates a spec against. The
bookmark,clockandglobeglyphs went with the plugins that shipped
them, theimages/fallback next to the executable, under an XDG data dir
or in the dev tree is gone, and a glyph no longer touches the cache
directory, so a show writes nothing and a missing or unwritable cache no
longer costs a row its placeholder. Seeplugins.md. - Breaking:
wayrun.tis gone: a Lua script carries its own text. The
table it read was the launcher's own key set, which had a Lua-side owner
only while the bundled plugins were in tree; plugin-side localisation would
arrive as a locale accessor instead. - Breaking:
theme.tomlkeeps only the keys a user reaches for.[colors]
keeps the three base roles plusdim, and every other surface derives from
its role at a shipped alpha;[layout]keepswidth,radius,top_ratio,
alignandmax_rows;[motion]keepsreduced. The width triple folds
into one key,follow_systemgoes (an unset key already follows the system,
per key), and the stroke, offset and derived-radius keys become constants.
Every dropped key is inert rather than an error, and an untouched file still
renders the same frame. Seetheme.md. - Breaking:
config.toml's[web_search]table goes, with
wayrun.web_search_engine(): the engine choice drives the web-search plugin
alone, which keeps its own setting now. Seeconfig.md. - The card is one surface: the inner field rectangle is never drawn, the search
line sits above the rows, and a pristine card rests at the height it fills
instead of reserving a gap and a footer band it would not fill. The✕clear
button goes with it (Ctrl+A plus Delete, or backspacing, still clear the
field), sotheme.toml's[colors] fieldand[layout] field_radiusare
inert. - The keyword chip goes too — it echoed the prefix the typed text already
stated, so[layout] chip_radiusis inert — and the footer renders no hints
while the field is untouched, the old help line having become the whole
message on an empty card. The placeholder is "Type to search": the line it
replaces promised a web search a given plugin set may not have installed. - The result list draws no default dot. A remembered default is filed per
plugin scope, so it covers every row that scope decorates at once and could
never single one out; the panel still marks the effective entry and the
footer still names the selected row's Enter action. - The README demo is re-recorded.
- The Lua plugin guide moved here, beside the SDK it documents:
lua.mdnow
carries the plugin table, thewayruntable, the sandbox and registration,
and a guard test holds its binding table to the table the sandbox builds, so a
binding added without a row is a red test rather than a stale page. The
WayRun-Plugins workspace keeps the Python framework and both example sets. The
reference material the two repos had each restated — theActionshapes, the
result-item keys, the icon rules, theplugins.tomlfields — now has one home
each, and the new page links instead of copying. - The tree follows the 2018 module shape — every
foo/mod.rsisfoo.rs
beside itsfoo/directory — and the layer-namedmodel.rsfiles fold into
the module they serve:Metajoins thePlugintrait, theplugins.toml
schema and the host cache join the registry, bothconfig/model.rsmerge
into theirconfig.rs. The index builder renames tobuilder.rs, which a
build.rsreads as a Cargo build script. No behaviour change. - The resident core's icon memo drops whole at 4096 distinct specs: a host's
wayrun.icon()was the one caller that could mint keys without bound.
Fixed
- A cleared query no longer shows rows under it. An empty
textsent as a
notification cancels the pending search at the core, where it was previously
ignored, and the shell drops any payload that lands while its query is
empty. Seejsonrpc.md. fs.listsorts the names it hands to Lua, and the$PATHranking falls back
to the name when hits share their kind and length, so a listing and a row
order no longer follow readdir or$PATHorder, which differ per machine.wayrun.sqlitereuses one snapshot handle per source, whose connection
follows its newest copy, instead of opening a connection per call in a
resident host; pruning leaves another source's copies alone.- A one-shot external host's stderr joins the journal, as a resident host's
already did, sowayrun.logreaches it on both paths.
WayRun v0.6.0
Added
- A plugin can be one Lua script the launcher runs itself.
wayrun --lua-host <script.lua>answers the external-host contract (list_plugins,search,
top,forget) with Lua 5.4 compiled into the binary, so nothing has to be
installed beside it, and aplugins.tomlcommandentry points at the script
like any other host, its shebang naming the mode. The script runs sandboxed: a
curated global set (noos,io,package,loadorprint) and a
wayruntable of bindings —t()for locale-correct strings,icon,
urlencode,json/tomldecoders,sqliteover an immutable snapshot of a
database,http.get, a scopedfs,time,env,home,cache_dir,
web_search_engine,script_dir,plugin_dirandlog. The API guide lives
in the WayRun-Plugins workspace (docs/en/LUA.md). resident = truekeeps a host process alive across calls: about a
millisecond per call against the fresh fork's several. A per-request deadline
kills and restarts a stalled or crashed host, a call a newer one overtook is
skipped so a keystroke burst cannot stack work on the pipe, and a host is
reaped when the launcher dismisses or after two minutes idle; a resident
host's stderr joins the journal, where a fork-per-call child's is nulled. See
plugins.md.wayrun.plugin_dir(id)names~/.config/wayrun/plugins/<id>/, the directory
a plugin keeps its files in, andfs.readreads a relative path under it,
refusing an absolute path or a..;wayrun.toml.decodeparses the plugin's
own config file, the format the launcher's own configs are written in. A
plugin id is validated as a plain name, since it becomes a directory root.
Changed
- Breaking:
b,handsno longer ship. The Firefox and web plugins are
Lua scripts in the WayRun-Plugins workspace now, registered as ordinary hosts
with acommandentry, and what remains in the launcher is the mechanism:
--lua-host, the bindings andresident. Seeplugins.md. - Breaking:
wayrun.http.getanswers{status, body}instead of the bare
body string, and takes aheaderssub-table beside the query parameters. A
script that read the body must readres.body; the headers are there because
minreq sends noUser-Agentof its own, so an API that requires one
(GitHub's) was unreachable from a plugin while the Python SDKs, whose urllib
sends one by default, never saw the problem. - Host rows parse from the reply without a per-item clone: measured on the real
Firefox payloads, the bookmark query takes 1.56 → 1.36 ms and the history
query 2.48 → 2.35 ms, and every Python host call gains the same slice.
Fixed
ui.localegoverns the text the.desktopfiles contribute (application
names, their comments and desktop action labels), not just the launcher's own
strings: the core pins the configured locale intoLANGUAGEas well, which is
the variable glib reads its language from, and the executor hands every app it
launches the session's original value back. Seeconfig.mdandusage.md.
WayRun v0.5.0
Added
- Every spawn in
executor.rsgoes throughsystemd-run --user --scope --collect --slice=app.slice, so an app the launcher starts is no longer
accounted towayrun-launcher.service: the unit's memory and CPU stay its own,
a restart cannot strand apps asleft-over process, and systemd-oomd's victim
is the app rather than the launcher plus everything it ever started. Expansion
stays off (--expand-environment=no), since systemd-run's default rewrites a
literal${HOME}in a command line or URI; the gio paths go throughgio launch/gio openso the process gio forks lands in the scope too, and a spawn
now hands the child to a task that waits it, so it no longer lingers as a
zombie. Without a reachable user manager or the gio CLI the spawn keeps its
in-process call, and apps launched before this release keep their old cgroup —
a scope is not retroactive. Seeresident.md. - The
dismissmethod: the launcher says it has closed, and the core cancels
the search still in flight and drops the payload apinnames rows in. A
resultspayload that lands after a dismissal no longer refills the rows or
warms their icons.
Changed
- Breaking:
[files] depthis gone. It only mattered with the index off, so
the fallback walk owns it as a constant now; a config still carrying the key
parses and the key is ignored. An absentexcludenow means the shipped
node_modules,targetand__pycache__rather than no exclusion at all, a
user's own list replaces those names whole, andexclude = []searches
everything but hidden names. A[files]section that setdepthor relied on
omittingexcludeshould be reviewed. Seeconfig.md. - Breaking:
pintakes{"scope","on_click": Action}, the pairunpin
already took, and no longer accepts anitempayload: the core looks the row
up in the payload it last emitted for that scope, so a client names the row by
the command it carries, and a command that payload no longer holds answers
pinned: falseinstead of storing a stale row. Every row used to embed a copy
of itself in its pin entry, and that copy crossed the wire twice; on a 20k-row
history thetoppayload fell 17,131,343 → 13,004,661 bytes (a row 942 → 678,
its pin entry 349 → 116) and peak RSS 74–76 → 69.6 MB, at 0.13 s wall either
way. What a pin stores is unchanged. Seejsonrpc.md. - The file index loads lazily again, on the first
f/d: pre-warming it cost
every launcher open 32 MB of page cache and a 33 MB mapping whether or not the
index was ever wanted. An index found stale now answers the query that found it
stale instead of falling back to the depth-3 walk, which buys recall (hundreds
of shallow rows against tens of thousands, once the image is a minute old) at
the price of row data up to ~0.1 s out of date. - The interactive path, by effect: a clipboard or window search answers from the
last fetch and revalidates in the background, whereniri msg -j windows
(~12 ms) andcliphist list(~4 ms) used to run on every search past the
500 ms burst window; the file scan's chunks go through rayon (d downloadp50
2.29 → 2.02 ms, and the pool parks between searches: 0 CPU ticks over 90 s
idle); a streamingtopno longer runs on the read loop, so typing does not
queue behind a full history build; the usage database gained an
(on_click, key)index and WAL, taking a one-shotselectfrom 8 fdatasyncs
to 0; icons decode on a worker thread instead of in the frame that draws them;
the query and session-bus work moved off the runtime's workers; and a hidden
resident launcher drops the rows it was holding. - The release binary is linked with full LTO rather than thin: 6,984,904 →
6,510,872 bytes (6.8% smaller, ~2.87 MB gzipped), with startup unchanged.
Fixed
- A
runline reaches the shell as it was typed.%u,%U,%fand%Fwere
being deleted from it, sor date +%Frandate +, and a host'srun{cmd}
was stripped the same way; the terminal path never did it, so which of the two
a command took decided whether the codes survived.jsonrpc.mdnow states that
onlylaunchanddesktop_actionread.desktopfield codes. - The shell's two external interactions are bounded. A clipboard read is killed
after one second and answered with no text, so a stuck selection owner cannot
wedge a paste, and the pipe is read from a thread of its own so a selection
larger than the pipe buffer cannot deadlock it. The IPC listener takes the
wayrun.lockflock before binding the socket, closing the race where two
simultaneous starts both unlinked it. - The shared copy-link action files its default under its own
copy-urlscope
instead of inheritingfirefox-bookmarks, so a remembered default applies to
every URL row whichever provider answered. - Opening or closing the action panel re-derives the hover, so a stationary
pointer no longer highlights a row that moved out from under it; the shell's
selectpayload sendssummaryandiconasnullrather than an empty
string, matching the rows the core emits. - A
.desktopaction's%ccomes from the same locale list as the row's title,
so a session that setsLC_ALLorLANGUAGEoverLANGno longer launches an
action named in a different language than the panel showed.
WayRun v0.4.0
Added
fanddsearch the whole home directory at any depth through a background
index ($XDG_CACHE_HOME/wayrun/file-index.bin): built on the firstf/d
search, mapped only while in use and unmapped after two minutes idle, and
re-checked within a minute by a parallel sweep that re-reads just the
directories whose mtime moved — a patch of ~0.1 s against the walk of ~1.2 s
it replaces. Every row carries a bigram bloom (index format v4) that the scans
reject on before touching a name.config.md's[files]holdsindex
(defaulttrue),depth(the unindexed fallback,3) andexclude.[files] excludenames the directories the walk never enters. The shipped
config.tomllistsnode_modules,targetand__pycache__; a name
starting with a dot is always hidden, and an edit to the list rebuilds on the
nextf/dsearch.
Changed
- Breaking: a plain query no longer stops at the first provider with
results.calculator,system-commandsandapp-searchall answer, and
their rows are merged by each row's relevance — the match kind's weight scaled
by the surface it matched on — then by the row's usage count, then by
registry order, so a prefix command can no longer hide an exact app. - Breaking: the app list's metadata surfaces are visible in a plain query:
a keyword orGenericNamematches by word and aCommentby prefix, and a
near spelling of a name answers too. A title match leads a description match
of comparable strength, while an exact keyword still beats a title the query
only sits inside of. - Every provider ranks by one match vocabulary: the file index, the path scans,
the runner, the window list and the app list share the same kinds, and
nucleois gone with its transitive crates. Af/dbuild logs itself on
stderr (file index … (patched)/(loaded)/(capped)).
Fixed
- A clipboard preview longer than 80 bytes is cut on a character boundary
instead of panicking on a multi-byte preview, which silently emptied the
whole list. - A keystroke burst no longer runs one search per key: the core coalesces
superseded queries and drops their payloads. - Firefox snapshots are opened read-only and immutable, so a
b/hquery takes
no locks on the private copy. - The plugin host cache (now under
$XDG_CACHE_HOME/wayrun) and the built-in
glyphs are written atomically, so a crash mid-write cannot leave a torn file
behind. - The file index only serves a cache it can vouch for: an unreadable walk is
never persisted as an empty index, a directory layout that is not the walk's
own pre-order is rejected, a non-UTF-8$HOMEstill indexes and serves, and
the first search after an idle unmap remaps instead of falling back to the
walk. - The index's own search no longer pays for work it discards: every indexed row
ran aLoosesubsequence pass its caller threw away and then a second
substring pass.f reporton this home's 555k-name index went ~12 ms -> ~3 ms
— confident-only classifiers, amemchr2candidate walk that fuses the word
and substring checks, and a per-row bloom the scans reject on before touching
a name. - An index build's peak memory fell 144 MB -> 96 MB on this home: the walk keeps
one length-prefixed names buffer per directory instead of one allocation per
name, and the image streams into the cache file instead of being materialised
beside the tables.
WayRun v0.3.1
Added
- The interface is translated:
locales/en.ymlandlocales/zh_cn.ymlhold
every string the launcher shows, and the locale is resolved once at startup
from$LC_ALL,$LC_MESSAGES,$LANG—zh_CN.UTF-8becomeszh_cn, any
zh_*variant reads the one Chinese table, anything else falls back to
English. Plugin names and ready hints, action titles, the?help line, the
system commands and the web-search rows are all covered.wayrun status,
--list-pluginsand the RPC errors stay English, and whatever a host sends
is relayed as it is. config.toml's[ui] localepins the interface language regardless of the
session, which is what a resident service needs: its unit inherits the
session's locale, or none at all. It is read at startup, so a change needs a
restart.- The action panel leads with the row's own command (Open), so its first
slot is what Enter runs:Shift+Enterthen Enter opens the row instead of
pinning it, andAlt+Enteron Open clears a remembered default whichever
scope set it. - A remembered default is visible without opening the panel: the row carries the
same dot, its footer names the action Enter will run (⏎ Open in terminal)
instead of⏎ Launch, and?lists each plugin's remembered action. - An action an external host attaches can be made the default Enter action: the
core stamps the host as the owner of the actions it parses out of a response,
so the host's scope is what the gesture sets. - A new launcher mark,
images/logo.svg(replacing
images/application_default.png), trails three fading echoes behind the W,
and the built-inbuiltin:appglyph is drawn from the same rest geometry.
Changed
- A file or directory row lists "Open in terminal" next to Open, with
"Reveal in file manager" and "Copy path" after them, so the open-family
entries are grouped at the top of the panel instead of the terminal being
last. - Pin/unpin and the default gesture re-send the query with the panel held open
on the entry that changed, so the change lands where it was made. The resume
is dropped by a close, a hide and every query edit, so a slow host's reply
cannot resurrect a panel the user closed. - Both README editions no longer name Papirus as the icon theme: app and file
rows follow the desktop's own theme. A License section credits the bundled
Material Symbols (Apache-2.0) and links the notice. - The user-facing pages state what a feature is and how to use it: the
implementation narrative (process split, caches, internal paths) is gone, and
the two language editions keep the same sections.