- Rust 86%
- Nix 14%
| crates | ||
| .envrc | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
fix-qq-x11-leak
Local workaround for the Linux QQ X11 connection leak, rewritten in Rust.
Based on discovery from Jerry-Terrasse/fix-linuxqq-x11-leak, but with a simpler strategy: instead of tracing wrapper.node and blocking one exact module + offset, this version limits how many outstanding X11 connections wrapper.node can keep open.
What it does
This project injects a small LD_PRELOAD library that hooks XOpenDisplay, XCloseDisplay, and XGetImage.
- Calls from
wrapper.nodeare tracked. - Once the number of unclosed tracked connections reaches
QQ_X11_MAX_CONN, newXOpenDisplaycalls fromwrapper.nodereturnNULL. XGetImagecalls fromwrapper.nodeare intercepted and bridged to a native Wayland screenshot via a separate helper process.- Other callers are passed through normally.
Architecture
The project is split into a Cargo workspace with two crates for maximum isolation:
| Crate | Type | Purpose |
|---|---|---|
fix-qq-x11-leak |
cdylib + bin |
The lightweight .so hook library (no xcap dependency) and the launcher binary. |
wayland-screenshot-helper |
bin | A standalone binary that uses xcap to capture the Wayland desktop. Heavy graphical dependencies (libgbm, libwayland, …) live only here. |
The .so stays deliberately lightweight so that LD_PRELOAD-ing it into unrelated processes (your shell, ls, cat) won't crash them under Nix where library paths are strictly isolated. When XGetImage is intercepted, the .so spawns the helper process (with LD_PRELOAD stripped), reads RGBA pixels over a pipe, converts to BGRA, and feeds a forged XImage back to the caller.
Build
cargo build --release
Using Nix Flake:
nix build
Usage
Run wrapped QQ directly
nix run git+https://git.gjz010.com/gjz010/fix-qq-x11-leak#qq
The qq flake package is QQ wrapped with the workaround. Its QQ source is
currently pinned to Linux QQ 3.2.32-51802 (9.9.33 beta), while the package
logic and runtime dependencies are inherited from nixpkgs.
Overlay
Add this flake as an input and apply its default overlay. The resulting
pkgs.qq is wrapped automatically. QQ has an unfree license, so the consuming
nixpkgs configuration must allow it:
{
inputs.fix-qq-x11-leak.url =
"git+https://git.gjz010.com/gjz010/fix-qq-x11-leak";
outputs = { nixpkgs, fix-qq-x11-leak, ... }: {
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
modules = [
{
nixpkgs.overlays = [ fix-qq-x11-leak.overlays.default ];
nixpkgs.config.allowUnfree = true;
}
({ pkgs, ... }: {
environment.systemPackages = [ pkgs.qq ];
})
];
};
};
}
Wrap a specific QQ package
Use the flake-level lib.wrapQQ function when you need to choose the exact QQ
derivation yourself:
let
wrappedQQ = fix-qq-x11-leak.lib.wrapQQ {
inherit pkgs;
qq = myQQPackage;
};
in
{
environment.systemPackages = [ wrappedQQ ];
}
qq defaults to pkgs.qq, so the shorter form is also valid:
fix-qq-x11-leak.lib.wrapQQ { inherit pkgs; }
Wrap an arbitrary command
The default package remains the generic launcher:
FIX_QQ_X11_LEAK_LOG=trace nix run \
git+https://git.gjz010.com/gjz010/fix-qq-x11-leak -- qq
For a local source build:
FIX_QQ_X11_LEAK_LOG=trace cargo run qq
Environment variables
| Variable | Description |
|---|---|
QQ_X11_MAX_CONN |
Max outstanding wrapper.node X11 connections. Default: 5. |
FIX_QQ_X11_LEAK_LOG |
Log level for env_logger, for example info or debug. |
WAYLAND_SCREENSHOT_HELPER |
Path to the wayland-screenshot-helper binary. Set automatically by the launcher; override for manual LD_PRELOAD usage. |
Notes
- It is simpler than the original offset-based blocker, but also less precise.
- If QQ changes how the leaking code is packaged, the
/wrapper.nodeheuristic may stop matching.
Credit
Original investigation and preload-hook approach: Jerry-Terrasse/fix-linuxqq-x11-leak.
License
MIT. See LICENSE.