Merge pull request #124 from flectar/iamdarkle/appimage-on-arch-clicking-links-does-nothing-cop

fix: open links from the AppImage
This commit is contained in:
Tomás Romero authored and GitHub committed 2026-10-24 04:19:39 +02:00
commit a2970bf0a1
5 files changed
+59 -48

No files matched your search

+3 -11
View File
@@ -79,18 +79,10 @@ if [[ "${1:-}" == "--integrate" ]]; then
exit 0
fi
# The mail shell has a small number of worker threads. One glibc allocation
# arena is enough and prevents per-thread arenas from retaining render/sync
# scratch memory after a burst of work. Respect an explicit user override.
export MALLOC_ARENA_MAX="${MALLOC_ARENA_MAX:-1}"
export MALLOC_TRIM_THRESHOLD_="${MALLOC_TRIM_THRESHOLD_:-65536}"
# The executable finds the bundled libraries in usr/lib through its RUNPATH.
# Do not export LD_LIBRARY_PATH: the browser and every other program the app
# launches would inherit it and load those libraries instead of the host's.
# Preserve the host search path for external programs such as the browser.
export FLECTAR_MAIL_HOST_LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}"
export FLECTAR_MAIL_BUNDLED=1
# linuxdeploy places any non-system ELF dependencies here.
export LD_LIBRARY_PATH="$app_dir/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
# The Linux binary supports Slint's Wayland and X11 winit integrations. Slint
# paints every application control while the window system owns window chrome.
export SLINT_BACKEND="${SLINT_BACKEND:-winit-software}"
+16 -1
View File
@@ -108,7 +108,22 @@ for library in libxkbcommon-x11.so.0 libxkbcommon.so.0; do
fi
done
# Preserve the allocator tuning and stable process name from our launcher.
# AppRun leaves LD_LIBRARY_PATH to the host, so the executable and bundled
# libraries must find usr/lib through the RUNPATH linuxdeploy sets.
require_runpath() {
local dynamic
dynamic="$(readelf -d "$1")"
if [[ "$dynamic" != *"Library runpath: [$2]"* ]]; then
printf '%s does not have RUNPATH %s.\n' "$1" "$2" >&2
exit 1
fi
}
require_runpath "$app_dir/usr/bin/flectar-mail" '$ORIGIN/../lib'
for library in "$app_dir"/usr/lib/*.so*; do
require_runpath "$library" '$ORIGIN'
done
# Preserve the renderer default and stable process name from our launcher.
cp "$project_dir/resources/AppRun" "$app_dir/AppRun"
chmod +x "$app_dir/AppRun"
+10
View File
@@ -406,6 +406,16 @@ class ReleaseTests(unittest.TestCase):
f"print({str(libraries)!r})\n"
)
pkg_config.chmod(0o755)
# The fixture ELFs carry no RUNPATH; report the one linuxdeploy sets.
readelf = root / "bin/readelf"
readelf.write_text(
"#!/usr/bin/env python3\n"
"import sys\n"
"assert sys.argv[1] == '-d'\n"
"runpath = '$ORIGIN/../lib' if sys.argv[2].endswith('/usr/bin/flectar-mail') else '$ORIGIN'\n"
"print(f' 0x000000000000001d (RUNPATH) Library runpath: [{runpath}]')\n"
)
readelf.chmod(0o755)
deploy = root / "bin/linuxdeploy"
deploy.write_text('''#!/usr/bin/env python3
import pathlib, shutil, sys
+6 -36
View File
@@ -6,9 +6,9 @@ type OpenUrl = dyn Fn(&str) -> Result<(), String> + Send + Sync;
/// The URL service carried by [`crate::PlatformContext`].
///
/// The default uses the system browser, preserving Linux's portal and AppImage
/// library-path handling for authorization. Hosts without desktop launchers
/// supply a callback with [`Self::new`]. Clones share that callback's state.
/// The default uses the system browser, preferring Linux's desktop portal for
/// authorization. Hosts without desktop launchers supply a callback with
/// [`Self::new`]. Clones share that callback's state.
#[derive(Clone, Default)]
pub struct ExternalUrlOpener {
handler: Option<Arc<OpenUrl>>,
@@ -51,13 +51,8 @@ impl ExternalUrlOpener {
use std::process::{Command, Stdio};
#[cfg(linux_desktop)]
fn host_command(program: &str) -> Command {
fn quiet_command(program: &str) -> Command {
let mut command = Command::new(program);
restore_library_path(
&mut command,
std::env::var_os("FLECTAR_MAIL_BUNDLED").is_some(),
std::env::var_os("FLECTAR_MAIL_HOST_LD_LIBRARY_PATH"),
);
command
.stdin(Stdio::null())
.stdout(Stdio::null())
@@ -65,21 +60,6 @@ fn host_command(program: &str) -> Command {
command
}
#[cfg(linux_desktop)]
fn restore_library_path(command: &mut Command, bundled: bool, path: Option<std::ffi::OsString>) {
if !bundled {
return;
}
match path.filter(|p| !p.is_empty()) {
Some(path) => {
command.env("LD_LIBRARY_PATH", path);
}
None => {
command.env_remove("LD_LIBRARY_PATH");
}
}
}
#[cfg(linux_desktop)]
fn launch(mut command: Command) -> Result<(), String> {
let mut child = command.spawn().map_err(|e| e.to_string())?;
@@ -132,12 +112,12 @@ fn open_system_authorization(address: &url::Url) -> Result<(), String> {
if open_with_desktop_portal(address).is_ok() {
return Ok(());
}
let mut xdg = host_command("xdg-open");
let mut xdg = quiet_command("xdg-open");
xdg.arg(url);
if launch(xdg).is_ok() {
return Ok(());
}
let mut gio = host_command("gio");
let mut gio = quiet_command("gio");
gio.args(["open", url]);
launch(gio).map_err(|_| "Could not open the default browser".into())
}
@@ -266,16 +246,6 @@ mod tests {
assert!(launch(command).is_ok());
}
#[test]
fn restores_host_libraries_only_for_bundled_builds() {
let mut command = Command::new("xdg-open");
restore_library_path(&mut command, false, None);
assert_eq!(command.get_envs().count(), 0);
restore_library_path(&mut command, true, Some("/host/lib".into()));
assert_eq!(command.get_envs().next().unwrap().1.unwrap(), "/host/lib");
restore_library_path(&mut command, true, Some("".into()));
assert_eq!(command.get_envs().next().unwrap().1, None);
}
#[test]
fn foreground_browser_does_not_hold_up_authorization() {
let mut command = Command::new("sh");
command.args(["-c", "sleep 2"]);
+24
View File
@@ -1,6 +1,8 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(glibc)]
tune_allocator();
// Slint 1.18 keeps the native window and its rendering surface when
// hidden on X11 and Windows. Use its window recreation path for tray mode,
// as it already does on Wayland, so a hidden window holds no surface.
@@ -18,3 +20,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
flectar_mail::run_desktop(flectar_mail::platform::launch_context()?)
}
/// The mail shell has a small number of worker threads. One glibc allocation
/// arena is enough and prevents per-thread arenas from retaining render/sync
/// scratch memory after a burst of work. Set in-process rather than through
/// the environment, which the browser and other launched programs would
/// inherit. Explicit user tuning wins.
#[cfg(glibc)]
fn tune_allocator() {
let tunables = std::env::var("GLIBC_TUNABLES").unwrap_or_default();
let user_set = |variable: &str, tunable: &str| {
std::env::var_os(variable).is_some() || tunables.contains(tunable)
};
// SAFETY: mallopt only adjusts allocator parameters; no pointers change.
unsafe {
if !user_set("MALLOC_ARENA_MAX", "glibc.malloc.arena_max") {
libc::mallopt(libc::M_ARENA_MAX, 1);
}
if !user_set("MALLOC_TRIM_THRESHOLD_", "glibc.malloc.trim_threshold") {
libc::mallopt(libc::M_TRIM_THRESHOLD, 64 * 1024);
}
}
}