fix(linux): launch on NVIDIA + Wayland; config webAppUrl for "call in browser"

NVIDIA + Wayland (reported on CachyOS/KDE, driver 615.71, webkit2gtk
2.52.6): no window. GDK dies with "Error 71 (Protocol error) dispatching to
Wayland display"; forced onto XWayland, WebKit's DMA-BUF renderer then fails
with "Failed to create GBM buffer … Invalid argument". The reporter's
workaround, WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11, runs fine.

gpu_workarounds::apply(), first thing in main() (before GTK/WebKit init):
when the NVIDIA driver is loaded (/proc/driver/nvidia/version or
/sys/module/nvidia), set WEBKIT_DISABLE_DMABUF_RENDERER=1, and on a Wayland
session that has XWayland (DISPLAY set) also GDK_BACKEND=x11. Values the user
already set win; LOTUS_NO_GPU_WORKAROUNDS=1 turns it all off. Logs what it
set to stderr. Non-NVIDIA systems are untouched. Unit tests for the decision.

config.json: webAppUrl = https://chat.lotusguild.org, used by the web client
(cinny `calls-open-in-browser`) to send calls this WebKitGTK build can't make
to the web app in the browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-27 16:56:36 -04:00
co-authored by Claude Opus 5.5
parent a4dd23dc4a
commit df760abdc7
4 changed files with 144 additions and 5 deletions
+128
View File
@@ -0,0 +1,128 @@
//! WebKitGTK workarounds for NVIDIA's proprietary driver on Linux.
//!
//! On NVIDIA + Wayland (reported on CachyOS/KDE, driver 615, webkit2gtk 2.52)
//! the app never shows a window: GDK dies with "Error 71 (Protocol error)
//! dispatching to Wayland display", and under XWayland WebKit's DMA-BUF
//! renderer then fails with "Failed to create GBM buffer … Invalid argument".
//! `WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11` makes it run normally.
//!
//! So, before GTK/WebKit initialise, and only when the NVIDIA driver is loaded:
//! - `WEBKIT_DISABLE_DMABUF_RENDERER=1`;
//! - on a Wayland session with XWayland available, `GDK_BACKEND=x11`.
//!
//! Anything the user already set wins, and `LOTUS_NO_GPU_WORKAROUNDS=1`
//! disables all of it (e.g. once a newer driver/WebKit fixes this).
/// Environment variables to set: pure, for tests.
pub(crate) fn decide(
nvidia: bool,
get: impl Fn(&str) -> Option<String>,
) -> Vec<(&'static str, &'static str)> {
let set = |k: &str| get(k).is_some_and(|v| !v.is_empty());
if !nvidia || set("LOTUS_NO_GPU_WORKAROUNDS") {
return Vec::new();
}
let mut out = Vec::new();
if !set("WEBKIT_DISABLE_DMABUF_RENDERER") {
out.push(("WEBKIT_DISABLE_DMABUF_RENDERER", "1"));
}
let wayland = set("WAYLAND_DISPLAY")
|| get("XDG_SESSION_TYPE").is_some_and(|v| v.eq_ignore_ascii_case("wayland"));
// Only fall back to X11 when there is an X server (XWayland) to talk to.
if wayland && set("DISPLAY") && !set("GDK_BACKEND") {
out.push(("GDK_BACKEND", "x11"));
}
out
}
#[cfg(target_os = "linux")]
fn nvidia_driver_loaded() -> bool {
std::path::Path::new("/proc/driver/nvidia/version").exists()
|| std::path::Path::new("/sys/module/nvidia").exists()
}
/// Call first thing in `main`, before anything starts GTK or spawns threads.
pub fn apply() {
#[cfg(target_os = "linux")]
for (key, value) in decide(nvidia_driver_loaded(), |k| std::env::var(k).ok()) {
eprintln!("gpu-workarounds: NVIDIA driver detected, setting {key}={value} (LOTUS_NO_GPU_WORKAROUNDS=1 to disable)");
std::env::set_var(key, value);
}
}
#[cfg(test)]
mod tests {
use super::decide;
use std::collections::HashMap;
fn run(nvidia: bool, env: &[(&str, &str)]) -> Vec<(&'static str, &'static str)> {
let map: HashMap<String, String> = env
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
decide(nvidia, |k| map.get(k).cloned())
}
const WAYLAND: &[(&str, &str)] = &[("WAYLAND_DISPLAY", "wayland-0"), ("DISPLAY", ":0")];
#[test]
fn nothing_without_nvidia() {
assert!(run(false, WAYLAND).is_empty());
}
#[test]
fn nvidia_wayland_gets_both() {
assert_eq!(
run(true, WAYLAND),
vec![
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
("GDK_BACKEND", "x11")
]
);
}
#[test]
fn nvidia_x11_session_only_disables_dmabuf() {
assert_eq!(
run(true, &[("DISPLAY", ":0")]),
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
}
#[test]
fn wayland_without_xwayland_keeps_wayland() {
assert_eq!(
run(true, &[("WAYLAND_DISPLAY", "wayland-0")]),
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
}
#[test]
fn xdg_session_type_counts_as_wayland() {
assert_eq!(
run(true, &[("XDG_SESSION_TYPE", "wayland"), ("DISPLAY", ":1")]),
vec![
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
("GDK_BACKEND", "x11")
]
);
}
#[test]
fn user_settings_win() {
let env = [
("WAYLAND_DISPLAY", "wayland-0"),
("DISPLAY", ":0"),
("WEBKIT_DISABLE_DMABUF_RENDERER", "0"),
("GDK_BACKEND", "wayland"),
];
assert!(run(true, &env).is_empty());
}
#[test]
fn opt_out_disables_everything() {
let mut env = WAYLAND.to_vec();
env.push(("LOTUS_NO_GPU_WORKAROUNDS", "1"));
assert!(run(true, &env).is_empty());
}
}
+1
View File
@@ -11,6 +11,7 @@ use tauri::{
};
use tauri_plugin_opener::OpenerExt;
pub mod gpu_workarounds;
mod native;
/// Bring the main window to the foreground from the tray / a hidden /
+2
View File
@@ -1,5 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
// Before anything initialises GTK/WebKit (NVIDIA + Wayland workarounds).
app_lib::gpu_workarounds::apply();
app_lib::run();
}