From 816513fdeda7480dcfa2e7d752f66af082ff98ea Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:15:11 -0700 Subject: [PATCH] fix(desktop): disable WebKitGTK DMA-BUF renderer on NVIDIA Linux (#14687) On Linux with the proprietary NVIDIA driver the desktop app window opens but stays blank. The sidecar/hub is healthy; only rendering fails, with stderr showing: KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied Failed to create GBM buffer of size 1500x980: Permission denied This is the known WebKitGTK DMA-BUF/NVIDIA incompatibility documented by Tauri (https://v2.tauri.app/develop/debug/linux-graphics/), whose fix is to set WEBKIT_DISABLE_DMABUF_RENDERER before the webview is created. When /sys/module/nvidia is loaded and the variable is not already set, set it to "1" as the first statement in main(), before tauri::Builder and any threads. Existing values (including "0" and empty) are left alone so users on hybrid-GPU systems can opt back into the DMA-BUF path with WEBKIT_DISABLE_DMABUF_RENDERER=0. Done in the executable rather than the .desktop launcher so terminal launches and every package format benefit. Co-authored-by: Saoud Rizwan --- apps/examples/desktop-app/README.md | 11 +++ .../src-tauri/src/linux_webview.rs | 68 +++++++++++++++++++ .../desktop-app/src-tauri/src/main.rs | 7 ++ 3 files changed, 86 insertions(+) create mode 100644 apps/examples/desktop-app/src-tauri/src/linux_webview.rs diff --git a/apps/examples/desktop-app/README.md b/apps/examples/desktop-app/README.md index bd20ac444a..76f08b2ba4 100644 --- a/apps/examples/desktop-app/README.md +++ b/apps/examples/desktop-app/README.md @@ -365,6 +365,17 @@ credentials, request headers, recorded audio, or transcript contents. - Tauri restarts the desktop backend if the sidecar process exits and kills it on app teardown. - Chat sends now preflight provider credentials. If a provider that requires API-key auth is selected without a key, the UI blocks the turn with a clear error message instead of starting a hanging session. - If a turn completes with `finishReason=error` before any assistant content is produced, the UI now adds an explicit error chat message so failed turns are visible in the transcript. +- Linux with the proprietary NVIDIA driver: WebKitGTK's DMA-BUF renderer + fails there (`KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied`, + `Failed to create GBM buffer ...: Permission denied`) and the window stays + blank, so when `/sys/module/nvidia` is loaded the app sets + `WEBKIT_DISABLE_DMABUF_RENDERER=1` before creating the webview (see + [Tauri's Linux graphics guide](https://v2.tauri.app/develop/debug/linux-graphics/)). + This also matches hybrid-GPU systems where another GPU renders, which then + lose the faster DMA-BUF path (not hardware acceleration as a whole). To opt + out, fully Quit from the tray (closing the window only hides it) and launch + with `WEBKIT_DISABLE_DMABUF_RENDERER=0 cline-app`; any value you set is + left untouched. - If package changes are not reflected, rebuild SDK packages (`bun run build:sdk`). The next desktop or CLI Hub connection will reuse a compatible running Hub or replace an incompatible one through the shared discovery path. diff --git a/apps/examples/desktop-app/src-tauri/src/linux_webview.rs b/apps/examples/desktop-app/src-tauri/src/linux_webview.rs new file mode 100644 index 0000000000..71b8432872 --- /dev/null +++ b/apps/examples/desktop-app/src-tauri/src/linux_webview.rs @@ -0,0 +1,68 @@ +//! WebKitGTK's DMA-BUF renderer does not work on the proprietary NVIDIA +//! driver: the window opens but stays blank while stderr reports +//! `KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied` and +//! `Failed to create GBM buffer of size WxH: Permission denied`. The sidecar +//! is healthy; only rendering fails. Tauri's guidance is to set +//! `WEBKIT_DISABLE_DMABUF_RENDERER` before the webview is created: +//! https://v2.tauri.app/develop/debug/linux-graphics/ + +use std::ffi::OsStr; +use std::path::Path; + +const DMABUF_RENDERER_VAR: &str = "WEBKIT_DISABLE_DMABUF_RENDERER"; +const NVIDIA_MODULE_PATH: &str = "/sys/module/nvidia"; + +/// Must run before `tauri::Builder` and before any thread is spawned: WebKit +/// reads the variable during initialization, and mutating the environment is +/// only sound while the process is still single-threaded. +pub fn configure_environment() { + if should_disable_dmabuf_renderer( + Path::new(NVIDIA_MODULE_PATH).is_dir(), + std::env::var_os(DMABUF_RENDERER_VAR).as_deref(), + ) { + std::env::set_var(DMABUF_RENDERER_VAR, "1"); + } +} + +/// Gated on the loaded NVIDIA kernel module rather than arch or session type +/// (the failure reproduces on ARM64/X11 too). Any value the user already set +/// is left alone, including "0" and the empty string, so the documented +/// opt-out (`WEBKIT_DISABLE_DMABUF_RENDERER=0`) keeps working on hybrid-GPU +/// systems where another GPU does the rendering. +fn should_disable_dmabuf_renderer(nvidia_module_loaded: bool, current: Option<&OsStr>) -> bool { + nvidia_module_loaded && current.is_none() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::os::unix::ffi::OsStrExt; + + #[test] + fn disables_the_renderer_when_nvidia_is_loaded_and_the_var_is_unset() { + assert!(should_disable_dmabuf_renderer(true, None)); + } + + #[test] + fn leaves_the_renderer_alone_without_the_nvidia_module() { + assert!(!should_disable_dmabuf_renderer(false, None)); + assert!(!should_disable_dmabuf_renderer( + false, + Some(OsStr::new("1")) + )); + } + + #[test] + fn preserves_an_explicit_opt_out() { + assert!(!should_disable_dmabuf_renderer(true, Some(OsStr::new("0")))); + assert!(!should_disable_dmabuf_renderer(true, Some(OsStr::new("")))); + } + + #[test] + fn preserves_a_non_utf8_value() { + assert!(!should_disable_dmabuf_renderer( + true, + Some(OsStr::from_bytes(b"\xff")) + )); + } +} diff --git a/apps/examples/desktop-app/src-tauri/src/main.rs b/apps/examples/desktop-app/src-tauri/src/main.rs index b2b40db83b..5f7e5d8a2b 100644 --- a/apps/examples/desktop-app/src-tauri/src/main.rs +++ b/apps/examples/desktop-app/src-tauri/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +#[cfg(target_os = "linux")] +mod linux_webview; #[cfg(target_os = "macos")] mod macos_notification; @@ -1465,6 +1467,11 @@ fn set_tray_status( } fn main() { + // Must stay first: WebKitGTK reads the environment while initializing, + // and the variable has to be in place before any thread exists. + #[cfg(target_os = "linux")] + linux_webview::configure_environment(); + let desktop_backend = Arc::new(DesktopBackendState::default()); let launch_cwd = std::env::current_dir() .map(|p| p.to_string_lossy().to_string())