# liftoff A classically Unix-ish application launcher for Wayland. Most launchers today are built around XDG desktop entries — they show you `.desktop` files. liftoff does the older, simpler thing: it runs programs from your `$PATH`, optionally with arguments, the way a shell would. You type a command, it completes on the fly, and pressing space (to add arguments) or enter (to run) uses the highlighted match. It's not identical to typing at a shell prompt, but it's close. ![liftoff completing a command prefix](docs/screenshot.png) ## Behavior - **Completion, live.** As you type, liftoff filters the executables on your `$PATH` and lists the matches. When the highlighted match continues what you typed, its remaining letters are shown as a dimmed "ghost" after the cursor. - **`$PATH` order, not alphabetical.** Matches are listed in search order — everything from the first directory on your `$PATH`, then the second, and so on — with names from the same directory sorted alphabetically. So the copy a shell would actually run is the one you reach first. A command found in several directories is listed once, under the earliest. - **A whole name wins.** If what you typed *is* a command, it heads the list wherever it lives on your `$PATH`: `git` gets you `git`, not `gitk` from some directory ahead of it. Where the matching ignores case, so does this — typing `steam` puts `Steam` at the top. - **Forgiving matching.** liftoff tries progressively looser criteria and stops at the first that finds anything: names that *start with* what you typed, then names that *contain* it, then a case-insensitive contains. So `valve` finds `com.valvesoftware.Steam` — but an exact prefix is never diluted by looser hits. - **The highlighted match is what runs.** It defaults to the first match listed; the arrow keys move it. - **Space commits, then you type arguments.** Pressing space replaces your typed text with the highlighted command and switches to argument entry. From there, further typing builds an argument string (split with shell-like quoting rules), and spaces are literal. - **Enter runs it.** The highlighted command is launched, detached, with whatever arguments you've typed, and liftoff exits. Executables reached through symlinks count too, so flatpak's exported app ids (like `com.valvesoftware.Steam`) turn up alongside everything else — no `.desktop` files needed. And because liftoff only ever launches a name it found on your `$PATH`, it never tries to run something that doesn't exist. ## Keys | Key | Action | | -------------- | ------------------------------------------------------------------------- | | *(type)* | Filter commands by prefix | | `↑` / `↓` | Move the highlight up / down the match list | | `Space` / `Tab`| Commit the highlighted command and start typing arguments | | `Enter` | Run the highlighted command (with any arguments) and exit | | `Backspace` | Delete a character; from empty arguments, return to editing the command | | `Esc` | Dismiss without running anything | ## Requirements - A Wayland compositor implementing the **`wlr-layer-shell`** protocol — the wlroots family (Sway, Hyprland, river, Wayfire, …) and others. liftoff appears as a centered overlay and takes keyboard focus while it is open. - A recent Rust toolchain (edition 2024). liftoff renders itself in software with [`cosmic-text`] for font shaping, so it needs no GPU and pulls in no GTK/Qt. It uses your installed system fonts. ## Install ```sh git clone https://git.highenergymagic.org/liftoff.git cd liftoff cargo install --path . ``` This puts a `liftoff` binary in `~/.cargo/bin`. ## Use liftoff has no options — run it and start typing. In practice you bind it to a key in your compositor. For example, in Sway: ``` bindsym $mod+space exec liftoff ``` or in Hyprland (Lua config): ```lua hl.bind("SUPER + SPACE", hl.dsp.exec_cmd("liftoff")) ``` ## How it works liftoff is split into a display-independent core library and a thin UI binary: - **`executables`** scans every directory on `$PATH` for executable files (following symlinks), keeping the names sorted and deduplicated so prefix matches are a contiguous binary-searched range and the substring fallbacks are a simple scan. Each name remembers which `$PATH` directory it came from, and the matches a search returns are stably re-sorted by exactness and then that — which, from an alphabetical starting point, yields the typed name first and then search order with an alphabetical tie-break. - **`query`** is the editing model behind the input line: a small state machine that moves between completing a command name and typing its arguments, and tracks which candidate is highlighted. - **`launch`** spawns the resolved command in its own session so it survives liftoff exiting. - The binary draws the overlay with [`smithay-client-toolkit`] (layer shell, input) and [`cosmic-text`], compositing into a shared-memory buffer by hand. The overlay is put on screen before anything slow happens. Until it is mapped it does not hold keyboard focus, so any key struck in the meantime goes to the window underneath — which matters, because both scanning `$PATH` and loading fonts can take a noticeable moment on a cold cache. So liftoff commits its surface first and does that work afterwards, with the `$PATH` scan on a worker thread. You can type immediately; a keystroke that needs the command list before it has arrived (enter, tab, the arrow keys) is held and applied the moment it does, in the order you typed it. The core is covered by unit tests (`cargo test`). ## License liftoff is released into the public domain under the [Unlicense](LICENSE). Do whatever you like with it. [`cosmic-text`]: https://crates.io/crates/cosmic-text [`smithay-client-toolkit`]: https://crates.io/crates/smithay-client-toolkit