A Rust + GPUI frontend for gpu-screen-recorder.
This is a rewrite of the Qt6 frontend
(gpu-screen-recorder-qt)
in Rust, keeping the same behaviour and configuration format: the same
pages, the same config file, the same system-tray menu and the same
org.freedesktop.portal.GlobalShortcuts hotkeys.
GPUI is the GPU-accelerated UI framework from Zed. This project builds
against the maintained standalone release published as gpui-unofficial,
imported as gpui:
gpui = { package = "gpui-unofficial", version = "=1.24.2" }
gpui_platform = { package = "gpui-platform-gpui-unofficial", version = "=1.24.2" }Runtime:
- gpu-screen-recorder (the backend)
- A working GPU driver (the UI is rendered on the GPU)
- A StatusNotifierItem host (KDE Plasma, GNOME with an extension, swaybar, …) for the tray icon
- A session bus for tray/notifications/hotkeys
Build:
- Rust (edition 2024)
- Development headers for:
libwayland,libxkbcommon(and-x11),libx11,libxcb(render0,shape0,xfixes0),libfontconfig,libfreetype,libvulkan,libssl,libEGL/libGL
On Debian/Ubuntu:
sudo apt install build-essential pkg-config libssl-dev \
libfontconfig-dev libfreetype-dev libwayland-dev \
libxkbcommon-dev libxkbcommon-x11-dev libx11-dev libxcb1-dev \
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
libvulkan-dev libegl1 libgl1-mesa-dricargo build --releaseBuilding from source on Arch with makepkg needs options=('!lto'). makepkg's
lto option is on by default and puts -flto=auto into CFLAGS; GCC then
compiles the C shim inside wayland-backend as an LTO object, and rustc bundles
it into the rlib without an archive-index entry, so the final link fails with
undefined symbol: wl_log_trampoline_to_rust_client. Appending -fno-lto to
CFLAGS works too. The AUR package already does this.
./target/release/gpu-screen-recorder-gpuiOn Arch, the package is in the AUR:
yay -S gpu-screen-recorder-gpui # builds from source
yay -S gpu-screen-recorder-gpui-bin # prebuilt binary, no Rust toolchain neededOtherwise, from a built checkout:
sudo install -Dm755 target/release/gpu-screen-recorder-gpui /usr/local/bin/gpu-screen-recorder-gpui
sudo install -Dm644 com.coobyk.gpu_screen_recorder.desktop /usr/share/applications/
sudo install -d /usr/share/icons/hicolor/scalable/apps
sudo install -m644 icons/hicolor/scalable/apps/*.svg /usr/share/icons/hicolor/scalable/apps/That icon directory holds the application icon plus the three tray variants
(….tray-idle, ….tray-recording, ….tray-paused) that the tray looks up by
name.
The application id is com.coobyk.gpu_screen_recorder — the reverse-DNS form of
the coobyk.com domain — and it is the same string as the .desktop file
name, which is what the compositor and the portal expect.
Every push to main is released automatically. The bump job increments the
patch version in Cargo.toml, commits it as chore(release): vX.Y.Z, and the
rest of the run builds, tags vX.Y.Z and publishes the artifacts below plus the
two AUR packages. The tag is created only after the Linux build succeeds, so a
failed build leaves no tag behind.
Pushing a v* tag (or running the workflow by hand from the Actions tab)
releases the version already in Cargo.toml instead, without bumping it. A tag
push must match Cargo.toml (vX.Y.Z), and re-running a release republishes
the same version with a bumped AUR pkgrel.
Each release consists of:
gpu-screen-recorder-gpui-<version>-x86_64-linux.tar.gz— the binary with the.desktopand icon files; run./install.sh(honoursPREFIX).gpu-screen-recorder-gpui_<version>_amd64.deb—sudo apt install ./<file>.deb.SHA256SUMS— checksums.
The gpu-screen-recorder backend is not bundled; install it separately.
The packages depend on the EGL/Vulkan/Wayland libraries the UI needs, but a
working GPU driver (Mesa or the NVIDIA driver) must already be installed.
The same run also refreshes two AUR packages, built from packaging/aur/:
| AUR package | PKGBUILD | Sources |
|---|---|---|
gpu-screen-recorder-gpui |
packaging/aur/gpu-screen-recorder-gpui/PKGBUILD |
the vX.Y.Z source tarball, compiled with cargo |
gpu-screen-recorder-gpui-bin |
packaging/aur/gpu-screen-recorder-gpui-bin/PKGBUILD |
tarball built on Arch in CI, attached to the release |
aur-build runs in an archlinux:base-devel container on the exact commit
that was released (so the -bin binary is linked against Arch's libraries, not
Ubuntu's), builds with makepkg, checks the binary has no unresolved libraries,
and attaches gpu-screen-recorder-gpui-<version>-x86_64-archlinux.tar.gz plus
the .pkg.tar.zst to the release. aur-publish then bumps pkgver/pkgrel,
regenerates .SRCINFO, fills in real checksums and pushes both packages.
The AUR jobs live in release.yml rather than a separate release: published
workflow on purpose: the release is created here with GITHUB_TOKEN, and
GitHub does not run workflows for events caused by that token. The same is what
keeps the automatic chore(release): vX.Y.Z commit from starting another run,
so the bump cannot recurse.
pkgrel is reset to 1 for a new version and incremented when the AUR already
carries that version, so re-running the workflow (Actions → Release → Run
workflow) republishes the current release instead of doing nothing. It refuses
to push a version older than what the AUR already has.
| Secret | Value |
|---|---|
AUR_SSH_PRIVATE_KEY |
private key whose public half is registered on your AUR account |
AUR_USERNAME |
git commit author name |
AUR_EMAIL |
git commit author email |
ssh-keygen -t ed25519 -C "aur ci" -f ~/.ssh/aur-gpu-screen-recorder-gpui
# paste ~/.ssh/aur-gpu-screen-recorder-gpui.pub into https://aur.archlinux.org/account/
gh secret set AUR_SSH_PRIVATE_KEY < ~/.ssh/aur-gpu-screen-recorder-gpui
gh secret set AUR_USERNAME --body Coobyk
gh secret set AUR_EMAIL --body you@example.comThe first successful run creates the gpu-screen-recorder-gpui-bin package.
The configuration lives in the same place as the other frontends:
$XDG_CONFIG_HOME/gpu-screen-recorder/config # usually ~/.config/gpu-screen-recorder/config
It is the same key value format used by the GTK and Qt frontends, so an
existing config is picked up unchanged. The config is rewritten on exit.
Recordings default to the XDG videos directory (from ~/.config/user-dirs.dirs).
Hotkeys are registered through the
org.freedesktop.portal.GlobalShortcuts portal. The portal only supports
compositors that implement it; in practice that means KDE Plasma on
Wayland. On other Wayland compositors the app prints a warning and runs
without hotkeys. The hotkeys can be rebound by the user through the
compositor's shortcut settings — the app asks the portal to bind them.
The portal derives the application id from the process it is called by, which
it resolves to an installed .desktop file. Running the binary straight out
of target/ therefore fails with An app id is required; install the app
(and com.coobyk.gpu_screen_recorder.desktop) for the hotkeys to register.
The tray icon is a StatusNotifierItem. Its menu mirrors the Qt frontend: it shows only the actions relevant to the page you are on, plus "Hide/Show window", the hide-when-recording option and "Exit".
This is intended to be behaviourally equivalent to the Qt frontend. A few points are deliberately different:
- Global shortcut binding. The Qt frontend asked the portal to
list shortcuts but never actually bound them, so no hotkey ever fired.
This version performs the
BindShortcutscall, so the hotkeys work. - "Recording saved" notification. The Qt frontend kept a
m_prevExitStatusfield that was only refreshed when the recorder exited on its own. When the user stopped recording by hand the field was stale, so the "saved" notification (and its success path) could not fire. This version uses the actual exit status of the process it just reaped, so the saved/error notifications fire on both the manual and natural stop paths. - Choosing an output directory. The Qt frontend opened a native
QFileDialogto pick the directory. This version uses an editable text field for the save directory (for recordings and replays), which avoids a secondary toolkit dependency but is keyboard-edited rather than browsed. - Hide to tray. The Qt frontend hid the window. This version minimizes
the window (
Window::minimize_window), which is the closest GPUI equivalent on Linux; the window is restored withactivate_window. Compositors that ignore client-side minimize requests (Hyprland, sway) leave the window where it is, so use the tray menu's "Exit" instead. - Closing the window. The Qt frontend hid the window on close; this
version quits through the same orderly shutdown as the tray's "Exit"
instead — the recorder is stopped so the file is finalized, the config is
written, and then the process exits. That makes a compositor close request
(the window's "X", or a desktop keybinding such as Hyprland's
SUPER+W) actually end the app rather than leave it running. - Termination signals.
SIGINT/SIGTERM/SIGHUP(Ctrl+C,kill, a closing terminal) trigger the same orderly shutdown as the tray's "Exit": the recorder is stopped so the file is finalized, the config is written, and then the app quits. The Qt frontend had no handler, so these killed it abruptly.
Everything else — the page set, the argument builder, the exit-code
handling, the notification texts, the audio merge/split and inversion
rules, and the configuration keys (including
streaming.start_stop_recording_hotkey) — is ported verbatim.
GPL-3.0. See LICENSE.
The Qt and GTK frontends this derives from are GPL-3.0, so the whole work stays under that license (no "or later" is granted upstream, and a derivative cannot add one). This is a modified version: the Rust/GPUI rewrite and every behavioural change are listed above under "Deviations from the Qt frontend".
For the same reason the application id, .desktop file and icon names live
in this project's own com.coobyk.* namespace rather than the upstream
author's — the GPL grants no rights to the upstream name. References to the
upstream projects (git.dec05eba.com) remain as attribution, and the
Flatpak app id in the "failed to run" hint is the backend's, not this
app's.