A small, low-latency side-panel launcher for KDE Plasma on Wayland.
Press a global shortcut (default Meta+F) and a full-height, ~150 px wide panel appears instantly on the left or right edge of the screen. Press it again to dismiss.
This is a working MVP of the mechanism: global hotkey → instant layer-shell surface. The panel content is placeholder graphics; wiring up real launcher entries and text rendering is the next step.
On Wayland, clients cannot grab global keys — the compositor owns the keyboard. So the app uses KDE's own broker, KGlobalAccel:
Meta+F ──► KWin (owns the keyboard; hosts the KGlobalAccel daemon)
│
└── D-Bus signal `globalShortcutPressed` ──► this app
│
create layer surface
│
zwlr_layer_shell_v1
The window is a zwlr_layer_shell_v1 surface (KWin supports it), anchored
to the left or right edge, full height, exclusive_zone = 0 so it overlays the
desktop instead of reserving space.
- KDE Plasma on Wayland (KWin provides
zwlr_layer_shell_v1). - The KGlobalAccel D-Bus service
org.kde.kglobalaccel. On Plasma 6 Wayland this is hosted inside KWin (KWin linkslibKGlobalAccelDand callsKGlobalAccelD::init()); the standalonekglobalaccelddaemon is only used by other sessions. - Rust toolchain to build.
No X11 support, by design.
cargo build --release
# binary at target/release/sidepanelNote:
smithay-client-toolkitis pulled in withdefault-features = false. Its defaultxkbcommonfeature needs the systemlibxkbcommon-devpackage, which the MVP does not require. See "Keyboard input" below.
./target/release/sidepanelIt runs as a daemon (it does not exit). Press Meta+F to toggle the panel.
Run the installer, which builds the binary, installs it to ~/.local/bin, and
sets up a systemd user service bound to the graphical session:
./contrib/install.shThat is equivalent to:
- copy
target/release/sidepanelto~/.local/bin/sidepanel, - copy
contrib/sidepanel.serviceto~/.config/systemd/user/, systemctl --user enable --now sidepanel.service.
The service starts with graphical-session.target and stops with it. It is
deliberately not lingered: the app needs WAYLAND_DISPLAY, which only
exists inside a graphical session.
Useful flags: --prefix DIR (install elsewhere), --no-start (enable but do
not start now), --uninstall.
The installer uses systemd rather than the XDG autostart protocol. On Plasma 6 the autostart path is backed by systemd anyway, but its generated unit hardcodes
Restart=no, so a crashed daemon would not come back until the next login. (contrib/sidepanel.desktopis retained for reference but is not used by the installer.)
Copy contrib/config.toml to ~/.config/sidepanel/config.toml:
side = "left" # "left" or "right"
width = 150 # logical pixels; height always fills the screen
shortcut = "Meta+F"shortcut is only the default. Once registered, the action also appears in
System Settings → Shortcuts (component "Side Panel"), where the user can
rebind it. A user-set binding is respected on later launches.
The single biggest win is architectural: this is a long-running daemon, so there is no process spawn on each keypress. Beyond that:
- The panel is created on demand and destroyed on hide. That costs one
configureround-trip (~1 frame). This is deliberate: KWin has had bugs around re-mapping the same layer surface (no freshconfigure), so creating a fresh surface each time is the reliable path. - The release profile uses LTO +
stripso the binary loads fast. - Next optimization (not in the MVP): keep the surface mapped but hidden
(transparent buffer + empty input region) and "show" it with a single
commit, avoiding the configure round-trip entirely.
A full-height surface that grabs the keyboard sounds risky, so it is worth being
precise: the panel uses keyboard_interactivity = Exclusive so it is focused
automatically, but global shortcuts still work. KWin's
GlobalShortcutFilter::keyboardKey processes shortcuts unless
isKeyboardShortcutsInhibited() is set, which a layer surface does not set.
So Meta+F always dismisses the panel — you cannot get stuck.
The MVP does not read keyboard input, so Escape does not close the panel;
use Meta+F. Adding Escape requires SCTK's xkbcommon feature, which needs the
system dev package:
# Debian/Ubuntu
sudo apt install libxkbcommon-dev
# Fedora
sudo dnf install libxkbcommon-devel
# Arch
sudo pacman -S libxkbcommonThen in Cargo.toml:
smithay-client-toolkit = { version = "0.21", features = ["calloop", "xkbcommon"] }and re-add the SeatState / SeatHandler / KeyboardHandler wiring (see the
SCTK simple_layer example; the press_key handler is where you would check
event.keysym == Keysym::Escape and call hide()).
- Placeholder rendering only — no text, no real launcher entries.
- No Escape-to-dismiss (see above).
- No multi-monitor output selection (the compositor picks the active output).
- No fractional-scale handling.
- Pre-mapped surface optimization not yet implemented.
- "zwlr_layer_shell_v1 is not available" — you are not on a compositor that
implements layer-shell (KWin does). Check with
wayland-info | grep layer. - Shortcut never fires — check that the action appears in
System Settings → Shortcuts (component "Side Panel"). You can introspect
the service (read-only) with:
gdbus introspect --session --dest org.kde.kglobalaccel --object-path /kglobalaccel
- Shortcut conflicts — KGlobalAccel resolves clashes itself:
setShortcutKeysreturns the binding actually in effect, which the app logs at startup. If Meta+F was already taken, the log line shows a different code.
⚠️ Never call a KGlobalAccel method that takes aQKeySequencewith a bare integer. The argument is aQKeySequence(D-Bus(ai): a struct wrapping an array of exactly four ints). Qt's demarshaller reads all four without checking the array length, so a malformed argument makes the receiver read past the end and abort on a libdbus assertion. On Plasma 6 Wayland the receiver is KWin, so a bad call takes down the entire session.