A native Wayland voice activity overlay for Discord Desktop and Vesktop on Linux.
Discord Voice Overlay shows who is speaking without changing the game process: participant names and avatars, speaking and mute/deaf indicators, configurable positioning, and click-through behavior. It includes a shared custom Vencord plugin and the v1.3.0 universal installer/manager.
A lightweight, highly responsive, Wayland-native (layer-shell) overlay that displays current voice channel participants and highlights active speakers in Discord Desktop or Vesktop.
- 🔒 Privacy-first: No Discord token access, no self-bots, no separate Gateway connections
- 🖥️ Wayland-native: Uses
layer-shellprotocol with an empty input region, so mouse clicks pass through to whatever is underneath (wlroots compositors: Hyprland, sway, niri, etc.) - ⚡ Low latency: Event-driven voice updates over a local Unix socket
- 🎮 Game compatible: Click-through overlay works over fullscreen XWayland games
- 🔄 Auto-reconnect: Fast bounded backoff (≤ 2s) if Discord Desktop, Vesktop, or the overlay restarts; the latest settings and voice snapshot are replayed automatically so no voice activity is needed to repopulate the overlay
- 🚀 Session autostart: ships a
systemd --userservice (vesktop-voice-overlay.service) - 🧰 Universal manager: v1.3.0 provides install, status, doctor, update, repair, and uninstall for supported native clients
┌─────────────────┐ Unix Socket ($XDG_RUNTIME_DIR) ┌──────────────────┐
│ Discord/Vesktop │ ──────────────────────────────────────► │ Overlay Client │
│ (Vencord Plugin)│ ◄────────────────────────────────────── │ (Rust + GTK4) │
└─────────────────┘ user-only (0700) └──────────────────┘
| Component | Technology | Role |
|---|---|---|
| Vencord Plugin | TypeScript / Node.js | Runs inside Discord Desktop or Vesktop, extracts voice state, sends JSON snapshots via Unix socket |
| Overlay App | Rust / GTK4 / layer-shell | Wayland click-through overlay, receives snapshots, renders avatars + speaking indicators |
- ✅ No Discord token — Never reads, stores, or transmits your account token
- ✅ No self-bots — No separate Discord Gateway connections
- ✅ Same-user IPC — Unix domain socket under
$XDG_RUNTIME_DIRwith0700permissions +SO_PEERCREDUID validation - ✅ Minimal data boundary — Only required voice-state snapshots cross the local socket; avatar images may be fetched from Discord's CDN over HTTPS
The primary v1.3.0 installation path manages the overlay and one native Vencord client without changing pacman packages or requiring root. It supports native Arch Linux Discord Desktop and Vesktop installations:
git clone https://github.com/Roddygithub/discord-voice-overlay.git
cd discord-voice-overlay
./install.shUse ./install.sh --client discord or ./install.sh --client vesktop when
both clients are installed. The manager keeps its Vencord checkout and overlay
under ~/.local/share/discord-voice-overlay/, verifies release checksums,
enables only the managed plugin in Vencord settings, and preserves
~/.config/vesktop-voice-overlay/config.toml.
./install.sh install
./install.sh status
./install.sh doctor
./install.sh update
./install.sh repair
./install.sh uninstallFlatpak, AppImage, arbitrary custom installations, and existing foreign custom
Vencord/injected setups are detected or rejected safely; see
docs/installer.md. --dry-run shows changes without
modifying client, service, or overlay state.
Release binaries and plugin source bundles are available on the
v1.3.0 release
page; the manager normally downloads and verifies the matching release assets
for you.
If an existing custom Vencord integration or injected Discord target is found,
the manager refuses to overwrite or silently adopt it. This protects existing
Vencord plugins and client modifications. Use status and doctor to inspect
ownership and supported-client problems; review docs/installer.md
before deciding whether to remove or reconfigure the foreign integration.
The following paths are for development, unsupported packaging variants, or users who intentionally manage the client integration themselves. They are not the primary v1.3.0 installation path.
# 1. Install build/runtime dependencies
sudo pacman -S rust gtk4 gtk4-layer-shell pkg-config
# 2. Build the overlay
git clone https://github.com/Roddygithub/discord-voice-overlay.git
cd discord-voice-overlay/overlay
cargo build --release --locked
# 3. Build Vencord with the source userplugin (instructions below), then run
./target/release/vesktop-voice-overlayThe repository contains an AUR PKGBUILD, but no package is currently
published in the AUR.
# Arch
sudo pacman -S rust gtk4 libadwaita gtk4-layer-shell pkg-config
# Other distros: install rustc/cargo, GTK4, libadwaita and gtk4-layer-shell
# (on distros that do not package gtk4-layer-shell, build it from source:
# https://github.com/wmww/gtk4-layer-shell)git clone https://github.com/Roddygithub/discord-voice-overlay.git
cd discord-voice-overlay/overlay
cargo build --release --locked
# Binary at: target/release/vesktop-voice-overlaycd ~/discord-voice-overlay/plugin
npm ci
npm pack # Produces a source bundle, not a directly installable Vesktop pluginVencord does not load arbitrary npm archives from Vesktop's plugin settings. The plugin is currently distributed as a Vencord source userplugin and must be included in a custom Vencord build. It has not been accepted into Vencord's built-in plugin set.
This is the workflow used for development and for custom Vencord client builds (this is how the plugin is actually built and loaded when using a local Vencord):
git clone https://github.com/Vendicated/Vencord.git
cd Vencord
git checkout ef29bbeb # revision pinned by CI (.github/workflows/ci.yml)
mkdir -p src/userplugins/vesktopVoiceOverlay
cp <repo>/plugin/src/{index.ts,native.ts,protocol.ts,resendCache.ts,voiceState.ts} \
src/userplugins/vesktopVoiceOverlay/
pnpm install --no-frozen-lockfile
pnpm build
# REQUIRED: without this sentinel file, the client considers the dist dir
# invalid and silently downloads stock Vencord over your build at launch.
printf '{}\n' > dist/package.jsonThen point Vesktop at the build: Developer Settings → Vencord Location →
select .../Vencord/dist, and fully restart the client.
Verification: grep -c VesktopVoiceOverlay dist/vencordDesktopRenderer.js
and dist/vencordDesktopMain.js must both be ≥ 1.
# Start overlay (keep running in background)
./target/release/vesktop-voice-overlay
# Or with debug logging
RUST_LOG=debug ./target/release/vesktop-voice-overlayWhy does Vencord show VesktopVoiceOverlay?
VesktopVoiceOverlay is the historical internal Vencord plugin identifier.
Vencord uses it as part of plugin identity and persisted settings, so it is
intentionally retained for backward compatibility. The project itself is
Discord Voice Overlay and supports both Discord Desktop and Vesktop.
Runtime behavior is driven by the plugin settings in Discord Desktop or Vesktop (Vencord plugin options: position, custom X/Y, user display, name display, avatar size). Changes apply immediately and are replayed automatically after any restart.
An optional TOML file at ~/.config/vesktop-voice-overlay/config.toml is
read at overlay startup if present — it is never created or written by
the overlay. The [socket] path and [overlay].max_participants are durable
local options. Other overlay display values act as startup defaults and are
overridden when plugin settings arrive. Legacy [appearance] and
overlay.avatar_size keys are ignored:
[socket]
path = "/run/user/1000/vesktop-voice-overlay.sock" # default: $XDG_RUNTIME_DIR/vesktop-voice-overlay.sockThe plugin fails closed if $XDG_RUNTIME_DIR is unavailable. An explicit
overlay socket path is only useful for manual protocol clients because the
plugin always uses the runtime-directory path.
- Start the overlay (
systemctl --user start vesktop-voice-overlayor./target/release/vesktop-voice-overlay) - Open Discord Desktop or Vesktop and join a voice channel
- Overlay appears automatically with participant avatars
- Green ring = currently speaking (static highlight)
The packaging template installs vesktop-voice-overlay.service in
/usr/lib/systemd/user/. Once installed, it starts the overlay with your graphical session,
restarts it if it ever exits, and is independent of Vesktop's lifecycle
(the plugin reconnects whenever Vesktop appears).
# Enable autostart for every session:
systemctl --user enable --now vesktop-voice-overlay.service
# Manual control:
systemctl --user status vesktop-voice-overlay.service
journalctl --user -u vesktop-voice-overlay.service -fIf your compositor session does not activate graphical-session.target
(e.g. Hyprland started without uwsm), either start it from your Hyprland
config (exec-once = systemctl --user start vesktop-voice-overlay) or enable
the default.target variant:
systemctl --user enable vesktop-voice-overlay.serviceRunning a second instance manually while the service owns the socket fails
cleanly with another vesktop-voice-overlay instance owns ... and exit code 1.
| Component | Channel | Install Command |
|---|---|---|
| Overlay (Rust) | Source build; unpublished AUR template | Build with Cargo |
| Plugin (TypeScript) | Source userplugin / GitHub source bundle | Build inside pinned Vencord source |
Both components versioned together via Git tags (v1.0.0, v1.1.0, etc.) — matching plugin + overlay share compatible socket protocol.
vesktop-voice-overlay/
├── plugin/ # Vencord Plugin (TypeScript)
│ ├── src/
│ │ ├── index.ts # Plugin entry point
│ │ ├── protocol.ts # Socket protocol types + serialization
│ │ ├── native.ts # Node.js socket client (main process, net)
│ │ ├── resendCache.ts # Reconnect backoff + settings/snapshot replay
│ │ └── voiceState.ts # Vencord voice state accessors
│ ├── package.json
│ └── tsconfig.json
├── overlay/ # Overlay App (Rust)
│ ├── src/
│ │ ├── main.rs # GTK4 app entry
│ │ ├── layer_shell.rs # Wayland layer-shell setup
│ │ ├── socket_server.rs # Unix socket server + SO_PEERCRED
│ │ ├── lifecycle.rs # Overlay show/hide logic
│ │ ├── protocol.rs # Protocol deserialization
│ │ ├── config.rs # Optional TOML config (loaded once at startup)
│ │ └── ui/ # GTK4 widgets
│ └── Cargo.toml
├── packaging/aur/ # AUR PKGBUILD
├── memory-bank/ # Engineering docs (PRD, Tech Stack, Plan)
├── docs/protocol.md # Socket protocol v1 spec
└── .github/workflows/ # CI/CD pipelines
- CI (
.github/workflows/ci.yml): Format check, build, test for both components - Release (
.github/workflows/release.yml): Tag push → validates and builds artifacts → GitHub Release → optional AUR update when credentials are present
# Local validation
cd overlay && cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test --locked
cd ../plugin && npm run lint && npm testSee docs/protocol.md for full spec.
Handshake:
Server sends: "VESKTOP_VOICE_OVERLAY/1.0\n"
Client validates, then sends JSON Lines snapshots
On voice-channel leave, the plugin sends {"type":"clear"} so stale rows are
removed immediately and the clear state is replayed after reconnects.
Snapshot:
{
"version": 1,
"timestamp": 1692000000000,
"self": {
"userId": "123...",
"username": "You",
"avatarUrl": "https://cdn.discordapp.com/...",
"mute": false,
"deaf": false,
"speaking": true
},
"participants": [
{ "userId": "456...", "username": "Friend", "avatarUrl": "...", "speaking": false, "volume": 80 }
]
}Layer-shell support is compositor-dependent:
- ✅ Hyprland (primary target) — validated end-to-end on Hyprland 0.56 with Guild Wars 2 (windowed/borderless): overlay visibility, pointer click-through, game focus, speaking show/hide, avatar sizing
⚠️ sway / niri / wayfire — expected to work through layer-shell, but not individually validated⚠️ GNOME/KDE (layer-shell support varies) — untested⚠️ Multi-monitor placement and exclusive-fullscreen games are untested
GPL-3.0 — See LICENSE
Compatible with upstream projects:
- Discover Overlay (GPL-3.0) — Design inspiration
- Vesktop (GPL-3.0) — Supported client
- Discord — Supported client
- Vencord (GPL-3.0) — Plugin platform
This project is not affiliated with Discord, Vesktop, Vencord, or Discover Overlay. Client modifications may violate Discord's Terms of Service; use at your own risk.
See CONTRIBUTING.md for guidelines.
Built with 🦀 Rust + 📘 TypeScript + 🎨 GTK4 + 🌊 Wayland layer-shell