Launcher
Press Super+d, type a few letters, press Enter, and the app opens. Without
this slot a fresh desktop’s launcher key does nothing; with the
profile on, two binds open one menu:
programs.scoot.desktop = { enable = true; # The menu on Super+d lists XDG apps, most-launched first; # Ctrl+Alt+Space lists PATH executables on their own panel: # launcher.enable = false; # no menu at all on this box};What runs: Super+d spawns scoot-launcher (drun: every XDG app in
the user’s ~/. plus the system’s
XDG_DATA_DIRS, most-launched first), Ctrl+Alt+Space spawns the
same script with --list-executables-in-path (run: PATH executables
on their own panel). The menu draws on the overlay layer with
exclusive keyboard, above fullscreen windows, taking every key while
open. There is no daemon, no unit, no config file: the launcher holds
nothing when closed (no process, no memory). The ranking lives in
scootlaunch’s own history ($XDG_STATE_HOME/scootlaunch/history). A
second press while it is open dismisses it, so one bind toggles.
The menu follows the look: background and text, selection and border
from its palette, through the look layer at every open. A value you set
in your own launch. (font, width, rows) wins per key, and
theme. leaves the whole look out while
the rest follows it. In music-desk paper and ink:

And in moonrise (same menu, same flags, that look’s roles):

Measured against fuzzel 1.14.1 on the M2
(dev/benchmarks/launcher-ab-2026-10., N=30 per cell, release
binaries, same session): tied inside the method floor up to 1000
entries, clearly faster at 5000 (101–102 vs 184 ms median
launch-to-first-frame), query update flat while fuzzel climbs from 26 to
53 ms at 5000 (scootlaunch’s values sit below the measurement floor, so
only the ranking is reliable), about half the memory (8.7 vs 18.0 MB PSS
at 5000), and zero idle cost and zero resident cost on both sides.
Ranking agrees on the winner for all 10 probes.
The dmenu contract is the other half of the slot: lines on stdin, the
selection on stdout, with --no-run-if-empty staying quiet on an empty
history and --only-match refusing custom entries (the pickers’
flags). printf 'alpha\nbeta\ngamma\n' | scootlaunch --dmenu, with the
filter and Return driven through scoot’s own IPC, prints beta and
exits 0. The clipboard picker, the WiFi/Bluetooth pickers and the
screencast chooser run the same --dmenu switch, and the bar’s WiFi /
power / audio pickers swap daemons without changing a call site. Two
script-visible differences from fuzzel’s dmenu are handled inside the
slot’s scripts (a cancel exits 0 from every picker either way):
- Dismissing the menu exits 1. Fuzzel’s dmenu exits 2 there (its app mode exits 0). A script that keys on fuzzel’s 2 breaks under the default, so treat any nonzero exit as a dismiss.
- The run bind lists PATH executables on their own panel. Fuzzel’s
--list-executables-in-pathadds them beside the apps. The flag spelling is the same behind both binds; what it lists is not.
scootlaunch forks the picked entry (a Terminal=true entry through
your terminal) and exits, and the window maps and takes focus through
the usual activation path. Over the session lock the binds never fire
(plain strings: never repeat, never allow_when_locked), and a manual
run behind the lock only lists app names, never clipboard or
notification content.
Every value is an option, applied on rebuild/switch (the binds
re-render into [binds]; no re-login, scoot msg reload is enough):
| Option | Type | Default | Meaning |
|---|---|---|---|
desktop. |
bool | true with the profile |
the package on PATH and both binds bound (drun on Super+d, run on Ctrl+Alt+Space) |
desktop. |
enum ("scootlaunch", "fuzzel") |
"scootlaunch" |
the program behind enable (fuzzel is one line back: its color flags, its frecency cache, its additive run list; see Migrating from fuzzel) |
desktop. |
package or null | scootlaunch (Linux-only: null off Linux) | point at your own scootlaunch build; null with the switch on fails evaluation naming it |
desktop. |
bool | true |
theme the menu from the look (background and text, selection and border); false leaves the menu’s own style standing |
desktop. / . |
bool | true |
bind the drun / run menu (false leaves that combo unbound) |
Migrating from fuzzel
Section titled “Migrating from fuzzel”One line restores the previous menu:
programs.scoot.desktop.launcher.daemon = "fuzzel";What changes back with it: the menu reads fuzzel. again (font,
lines, borders; scootlaunch never reads that file and takes font,
width_em, rows from programs. instead),
the look’s seven colors travel as CLI flags again, frecency comes
from $XDG_CACHE_HOME/fuzzel instead of
$XDG_STATE_HOME/scootlaunch/history, a dismiss exits 2 from dmenu
instead of 1, and the run bind lists apps beside PATH executables
instead of executables alone. The binds, the wrapper name
(scoot-launcher) and every picker stay as they are.
Troubleshooting, by symptom:
Super+d(orCtrl+Alt+Space) opens nothing. The slot renders those binds only withlauncher.beside the keymap (both on with the profile); either off leaves the combo unbound. Then check the binary:enable scoot-launcherfrom a terminal. Outside the session it still lists apps, so a failure there names the cause (no compositor to map on, or a badlaunch.;toml scootlaunch --checksays which).- The menu lists no apps. scootlaunch found no
.files: it searches thedesktop applicationssubdirectory ofXDG_DATA_HOMEandXDG_DATA_DIRS. Inside the session both are set (tryecho $XDG_DATA_DIRSin a terminal); over ssh they may be empty. - The wrong app keeps winning. The
most-launched app sorts first. Reset it by deleting the history
(
rm ~/.with the menu closed).local/state/scootlaunch/history - My
launch.font is ignored. The file is read whole at every open, and a bad file is refused loudly (toml scootlaunch --checknames the key). With no file the look’s resolved font file (font-filein the fragmentscoot look applywrites) is used, else its family is looked for in the usual font directories, then the first well-known font file wins; runscoot look applyagain, install DejaVu or setfontto a file you have. - Two menus stack. They cannot from these binds: the second press
dismisses the open panel. A second menu means a
second launcher (another menu tool running beside this one): turn
this one off (
launcher.) or uninstall the other.enable = false
