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() {