diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d752c408..2db8f621 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -207,7 +207,7 @@ The main Tauri window derives its minimum width from the visible panes instead o The main Tauri window also persists its last normal size and screen position in the app config directory as `window-state.json`. The state stores logical window points, while `window_state.rs` migrates older physical-pixel state on read so Retina and non-Retina launches restore the same user-facing bounds. On startup, the restored frame applies only to the main window and clamps to the currently available monitor work areas, so stale coordinates from a disconnected display fall back to a visible placement. Maximized, fullscreen, minimized, and detached note-window frames are not written as the restore baseline. Linux uses custom React-rendered window chrome instead of the native Tauri menu bar. `setup_linux_window_chrome()` drops server-side decorations on the main window, `openNoteInNewWindow()` does the same for detached note windows, and `LinuxTitlebar`/`LinuxMenuButton` route both window controls and menu actions back through the same shared command pipeline that macOS uses for native menu clicks. -When Tolaria is launched from a Linux AppImage, `run()` also injects `WEBKIT_DISABLE_DMABUF_RENDERER=1` unless the user already set that variable. This keeps the workaround scoped to bundled WebKitGTK launches that are prone to Fedora/Wayland DMA-BUF crashes without changing native package installs. +When Tolaria is launched from a Linux AppImage, `run()` also applies AppImage-only WebKitGTK startup safeguards without changing native package installs. It injects `WEBKIT_DISABLE_DMABUF_RENDERER=1` unless the user already set that variable, and on Wayland sessions it re-execs once with the first available system `libwayland-client.so` in `LD_PRELOAD` when the user has not provided their own preload. The re-exec addresses AppImage library-order failures that can surface as `Could not create default EGL display: EGL_BAD_PARAMETER` before GTK/WebKit create the display. ## Multi-Window (Note Windows) @@ -532,7 +532,7 @@ sequenceDiagram participant MCP as MCP Server participant U as User - T->>T: apply Linux AppImage WebKit env override
(AppImage only) + T->>T: apply Linux AppImage WebKit env/preload safeguards
(AppImage only) T->>T: run_startup_tasks()
(migrate + seed only) T->>MCP: spawn_ws_bridge() — ports 9710 + 9711 T->>A: App mounts diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 281b1b1d..604c8506 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -29,6 +29,22 @@ If you run the desktop app on Linux, install Tauri's WebKit2GTK 4.1 dependencies libappindicator-gtk3-devel librsvg2-devel ``` +### Linux AppImage Wayland troubleshooting + +On some Wayland systems, the Linux AppImage may fail to launch with: + +```text +Could not create default EGL display: EGL_BAD_PARAMETER. Aborting... +``` + +Recent Tolaria AppImages automatically retry startup with the system Wayland client library when they detect this class of AppImage + Wayland environment. If you are running an older build, use this workaround: + +```bash +LD_PRELOAD=/usr/lib/libwayland-client.so ./Tolaria*.AppImage +``` + +If your distribution stores the library elsewhere, use that path instead, for example `/usr/lib64/libwayland-client.so.0` or `/usr/lib/x86_64-linux-gnu/libwayland-client.so.0`. + ## Quick Start ```bash diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f7ed83f2..bb70ff75 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -32,6 +32,8 @@ mod window_state; use std::ffi::OsStr; use std::process::Command; +#[cfg(all(desktop, target_os = "linux"))] +use std::os::unix::process::CommandExt; #[cfg(desktop)] use std::path::{Path, PathBuf}; #[cfg(desktop)] @@ -70,15 +72,67 @@ struct StartupEnvOverride { value: &'static str, } +#[cfg(any(test, all(desktop, target_os = "linux")))] +const WAYLAND_CLIENT_PRELOAD_CANDIDATES: [&str; 7] = [ + "/usr/lib/libwayland-client.so", + "/usr/lib/libwayland-client.so.0", + "/usr/lib64/libwayland-client.so", + "/usr/lib64/libwayland-client.so.0", + "/lib64/libwayland-client.so.0", + "/lib/x86_64-linux-gnu/libwayland-client.so.0", + "/usr/lib/x86_64-linux-gnu/libwayland-client.so.0", +]; + +#[cfg(any(test, all(desktop, target_os = "linux")))] +fn is_linux_appimage_launch(mut get_var: F) -> bool +where + F: FnMut(&str) -> Option, +{ + ["APPIMAGE", "APPDIR"] + .into_iter() + .any(|key| get_var(key).is_some_and(|value| !value.trim().is_empty())) +} + +#[cfg(any(test, all(desktop, target_os = "linux")))] +fn is_wayland_session(mut get_var: F) -> bool +where + F: FnMut(&str) -> Option, +{ + get_var("WAYLAND_DISPLAY").is_some_and(|value| !value.trim().is_empty()) + || get_var("XDG_SESSION_TYPE") + .is_some_and(|value| value.trim().eq_ignore_ascii_case("wayland")) +} + +#[cfg(any(test, all(desktop, target_os = "linux")))] +fn linux_appimage_wayland_client_preload_path_with( + mut get_var: F, + mut file_exists: E, +) -> Option<&'static str> +where + F: FnMut(&str) -> Option, + E: FnMut(&str) -> bool, +{ + if !is_linux_appimage_launch(&mut get_var) || !is_wayland_session(&mut get_var) { + return None; + } + + if get_var("LD_PRELOAD").is_some_and(|value| !value.trim().is_empty()) + || get_var("TOLARIA_APPIMAGE_WAYLAND_PRELOAD_ATTEMPTED").is_some_and(|value| value == "1") + { + return None; + } + + WAYLAND_CLIENT_PRELOAD_CANDIDATES + .into_iter() + .find(|path| file_exists(path)) +} + #[cfg(any(test, all(desktop, target_os = "linux")))] fn linux_appimage_startup_env_overrides_with(mut get_var: F) -> Vec where F: FnMut(&str) -> Option, { - let is_appimage = ["APPIMAGE", "APPDIR"] - .into_iter() - .any(|key| get_var(key).is_some_and(|value| !value.trim().is_empty())); - if !is_appimage { + if !is_linux_appimage_launch(&mut get_var) { return Vec::new(); } @@ -94,11 +148,40 @@ where #[cfg(all(desktop, target_os = "linux"))] fn apply_linux_appimage_startup_env_overrides() { + apply_linux_appimage_wayland_client_preload(); + for env_override in linux_appimage_startup_env_overrides_with(|key| std::env::var(key).ok()) { std::env::set_var(env_override.key, env_override.value); } } +#[cfg(all(desktop, target_os = "linux"))] +fn apply_linux_appimage_wayland_client_preload() { + let Some(preload_path) = linux_appimage_wayland_client_preload_path_with( + |key| std::env::var(key).ok(), + |path| std::path::Path::new(path).is_file(), + ) else { + return; + }; + + let exe = match std::env::current_exe() { + Ok(exe) => exe, + Err(e) => { + eprintln!( + "Tolaria AppImage Wayland preload skipped: failed to resolve executable ({e})" + ); + return; + } + }; + + let error = Command::new(exe) + .args(std::env::args_os().skip(1)) + .env("LD_PRELOAD", preload_path) + .env("TOLARIA_APPIMAGE_WAYLAND_PRELOAD_ATTEMPTED", "1") + .exec(); + eprintln!("Tolaria AppImage Wayland preload skipped: failed to re-exec ({error})"); +} + #[cfg(not(all(desktop, target_os = "linux")))] fn apply_linux_appimage_startup_env_overrides() {} @@ -497,6 +580,7 @@ pub fn run() { #[cfg(test)] mod tests { use super::linux_appimage_startup_env_overrides_with; + use super::linux_appimage_wayland_client_preload_path_with; use super::StartupEnvOverride; use super::MACOS_WEBVIEW_RESERVED_COMMAND_KEYS; use super::MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS; @@ -549,6 +633,52 @@ mod tests { assert!(overrides.is_empty()); } + #[test] + fn linux_appimage_wayland_preload_uses_first_available_system_library() { + let preload_path = linux_appimage_wayland_client_preload_path_with( + |key| match key { + "APPIMAGE" => Some("/tmp/Tolaria.AppImage".to_string()), + "XDG_SESSION_TYPE" => Some("wayland".to_string()), + _ => None, + }, + |path| path == "/lib/x86_64-linux-gnu/libwayland-client.so.0", + ); + + assert_eq!( + preload_path, + Some("/lib/x86_64-linux-gnu/libwayland-client.so.0") + ); + } + + #[test] + fn linux_appimage_wayland_preload_preserves_explicit_ld_preload() { + let preload_path = linux_appimage_wayland_client_preload_path_with( + |key| match key { + "APPDIR" => Some("/tmp/.mount_Tolaria".to_string()), + "WAYLAND_DISPLAY" => Some("wayland-0".to_string()), + "LD_PRELOAD" => Some("/custom/libwayland-client.so".to_string()), + _ => None, + }, + |_| true, + ); + + assert_eq!(preload_path, None); + } + + #[test] + fn linux_appimage_wayland_preload_is_empty_for_x11_sessions() { + let preload_path = linux_appimage_wayland_client_preload_path_with( + |key| match key { + "APPIMAGE" => Some("/tmp/Tolaria.AppImage".to_string()), + "XDG_SESSION_TYPE" => Some("x11".to_string()), + _ => None, + }, + |_| true, + ); + + assert_eq!(preload_path, None); + } + #[cfg(desktop)] #[test] fn selected_mcp_bridge_vault_path_uses_persisted_active_vault() {