diff --git a/config.json b/config.json index 9c7442b..d524158 100644 --- a/config.json +++ b/config.json @@ -8,13 +8,21 @@ "allowCustomHomeservers": true, "featuredCommunities": { "openAsDefault": false, - "spaces": ["!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc", "#homelab:codestorm.net"], - "rooms": ["#jellyfin:matrix.org"], - "servers": ["matrixrooms.info"] + "spaces": [ + "!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc", + "#homelab:codestorm.net" + ], + "rooms": [ + "#jellyfin:matrix.org" + ], + "servers": [ + "matrixrooms.info" + ] }, "hashRouter": { "enabled": true, "basename": "/" }, - "gifApiKey": "" -} \ No newline at end of file + "gifApiKey": "", + "webAppUrl": "https://chat.lotusguild.org" +} diff --git a/src-tauri/src/gpu_workarounds.rs b/src-tauri/src/gpu_workarounds.rs new file mode 100644 index 0000000..2c36e97 --- /dev/null +++ b/src-tauri/src/gpu_workarounds.rs @@ -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, +) -> 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 = 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()); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0aac989..8cad3b2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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 / diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cea0b30..3326c16 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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(); } \ No newline at end of file