dropterm
A Yakuake-style dropdown terminal for Wayland: press a key, a real terminal rolls down from the top of the screen, press it again and it goes away.

It began as a panel plugin for the noctalia desktop shell. noctalia v5 was rewritten from QML to native C++ with a sandboxed Luau plugin API that cannot host a terminal — no PTY, no canvas to draw a cell grid on, and no raw keyboard input (the details). Rather than lose the terminal, it took ownership of its own window.
So it is now a standalone application that depends on no shell at all: it
draws on its own wlr-layer-shell surface, the same mechanism bars and docks
use. It needs a compositor supporting layer-shell (Hyprland, sway, river,
niri…) and a key bound to dropterm toggle. Nothing else.
Using the old noctalia v4 plugin? It is still there and still works: the
v1.1.0tag, or thenoctalia-v4branch for fixes. Everything fromv2.0.0on is the standalone application described here.
Features
- Tabbed terminal sessions, persisting across open/close
- Interactive scrollbar with drag and click-to-jump
- Built on libvterm-neovim for complete terminal emulation
- Drops down flush beneath a bar without hardcoding the bar's height
- Click outside to dismiss; no shell or compositor plugin required
How it works
The terminal itself is Qt Quick over
libvterm: PtyIFace runs the
shell on a pty, VTermBridge turns its output into a screen model, and
TextRender paints the cell grid. That part was carried over intact from the
plugin — what changed is who owns the window. Instead of being drawn into a
host shell's panel slot, dropterm creates its own layer-shell surface via
LayerShellQt.
Two details of that surface are load-bearing, and both are easy to get wrong:
- The surface spans the whole usable output, not just the terminal. A
surface sized to the terminal alone is never told about clicks that land
elsewhere, so outside-click dismissal would be impossible. The area around
the terminal is transparent and simply catches those clicks. Anchoring all
four edges with an exclusive zone of
0makes the compositor size the surface to the output minus everyone else's exclusive zones, which is why the terminal sits flush under a bar with no configured offset. - Keyboard interactivity is exclusive. Under focus-follows-mouse, an on-demand surface loses the keyboard the moment the pointer drifts off it, so typing would silently go elsewhere. Exclusive keeps input here while the terminal is mapped. Compositor keybinds still take priority, so the toggle works.
The consequence is that the dropdown is modal: while it is open, clicks elsewhere dismiss it rather than interacting, and typing goes to the terminal. That is the trade-off for outside-click dismissal, and it matches how shell panels normally behave.
Installation
Dependencies: Qt 6 (qtbase, qtdeclarative, qtwayland), LayerShellQt, libvterm-neovim, CMake, pkg-config.
NixOS (flake + home-manager)
The repository is still named noctalia-dropdown-terminal for the benefit of
anyone already depending on it; the binary and the module are dropterm.
inputs.dropterm.url = "github:ajunca/noctalia-dropdown-terminal";
imports = [ inputs.dropterm.homeManagerModules.default ];
programs.dropterm = {
enable = true;
settings = {
widthPercent = 0.6;
heightPercent = 0.3;
fontFamily = "Hack";
fontSize = 10.5;
};
};
No QML import path wiring is needed any more — the QML is compiled into the binary.
Manual (non-Nix)
cmake -S src -B build -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build
cmake --install build
Usage
dropterm is a single binary. The first invocation starts it; later
invocations are remote controls over a per-user socket in $XDG_RUNTIME_DIR.
dropterm toggle # default
dropterm show
dropterm hide
dropterm reload # re-read configuration, including theme.conf
dropterm settings # open the settings window
Bind the toggle in your compositor. Hyprland:
bind = , F12, exec, dropterm toggle
Configuration
Settings live in $XDG_CONFIG_HOME/dropterm/dropterm.conf (INI, read with
QSettings, so keys go under [General]). The Nix module writes this for you.
| Key | Default | Meaning |
|---|---|---|
widthPercent | 0.6 | Terminal width as a fraction of the usable area (0.2–1.0) |
heightPercent | 0.3 | Terminal height as a fraction of the usable area (0.15–1.0) |
fontFamily | Hack | Terminal font |
fontSize | 10.5 | Point size |
shellProgram | (empty) | Shell to run; empty uses the shell from passwd |
foreground | #ebebeb | Default text colour |
background | #000000 | Terminal background |
backgroundOpacity | 0.92 | Background alpha (0.0–1.0) |
cornerRadius | 8 | Radius of the two free (bottom) corners |
animationMs | 180 | Roll-down duration; 0 for instant |
Two bits of polish are compositor rules rather than application settings, because an application can neither blur what is behind it nor decide how the compositor animates its surface appearing. Hyprland:
layerrule = blur, dropterm # blur behind the terminal
layerrule = noanim, dropterm # drop the compositor's fade on open/close
The second is worth explaining: dropterm animates the roll-down itself, inside its own surface. A compositor that also fades the surface in on map layers one animation over the other. Suppressing it leaves just the movement. Both rules are namespace-matched, so they affect nothing else.
Where a setting comes from
Each key is resolved from the first of these that provides it:
| file | written by | |
|---|---|---|
| 1 | ~/.config/dropterm/dropterm.conf | dropterm itself, when you change something |
| 2 | ~/.config/dropterm/theme.conf | a desktop theme engine (optional) |
| 3 | ~/.config/dropterm/defaults.conf | whatever installed dropterm (optional) |
| 4 | — | built-in defaults |
Only the first is ever written by dropterm. The settings window shows which layer each colour came from, and clicking that label drops your override so the key follows the layers below it again.
Following a desktop colour scheme
dropterm knows nothing about any particular desktop. The contract is two things, and anything that can do them will work — matugen, pywal, a theme engine, or a shell script in a wallpaper hook:
-
Write
~/.config/dropterm/theme.conf:[General] foreground=#e8e1db background=#15130f -
Run
dropterm reload.
reload only ever talks to a running terminal; if none is running it does
nothing rather than starting one, so it is safe from a hook that fires often.
A template for noctalia ships at
share/dropterm/templates/dropterm.conf. Point its template engine at it:
[theme.templates.user.dropterm]
input_path = ".../share/dropterm/templates/dropterm.conf"
output_path = "~/.config/dropterm/theme.conf"
post_hook = "dropterm reload"
The template uses {{colors.terminal_foreground.default.hex}}-style variables.
Other engines use different variable names, so adapt the two lines; the output
format is what matters, and it is only an INI file with two keys.
Because your own choices sit above the theme layer, picking a colour in the settings window stops that key following the scheme, and reverting it resumes.
Keyboard shortcuts
| Shortcut | Action |
|---|---|
| Ctrl+Shift+C | Copy |
| Ctrl+Shift+V | Paste |
| Shift+Insert | Paste |
| Ctrl+Shift+T / N | New tab |
| Ctrl+Shift+W | Close tab |
| Ctrl+Tab | Next tab |
| Ctrl+Shift+Tab | Previous tab |
Why it left noctalia
noctalia v5 is a ground-up rewrite from QML/Quickshell to native C++ with a sandboxed Luau plugin API. A terminal cannot be built on it:
- No PTY. Nothing in the codebase opens one; plugins get line-streamed process output only. That rules out job control, interactive programs, terminal resize and signal delivery.
- No canvas. Plugin UI is fixed declarative primitives, with no custom-drawing surface for a cell grid.
- No raw keyboard.
capture_keysdelivers only key chords declared up front in the manifest, and cannot conditionally consume them. A terminal needs every keystroke. - No native code loading. Plugins are Luau only, so the existing C++ terminal could not be loaded even if the above were solved.
Embedding an external terminal in a panel is not a way out either: Wayland has no cross-client surface embedding.
Standalone sidesteps all of it, and the terminal no longer depends on any particular shell.
License
MIT