fix: bundle fcitx gtk module in appimage
This commit is contained in:
1
.github/workflows/release-stable.yml
vendored
1
.github/workflows/release-stable.yml
vendored
@@ -283,6 +283,7 @@ jobs:
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
fcitx5-frontend-gtk3 \
|
||||
librsvg2-dev \
|
||||
curl \
|
||||
wget \
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -339,6 +339,7 @@ jobs:
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
fcitx5-frontend-gtk3 \
|
||||
librsvg2-dev \
|
||||
curl \
|
||||
wget \
|
||||
|
||||
@@ -821,7 +821,7 @@ Tolaria delegates remote auth to the user's system git setup:
|
||||
- `clone_git_repo` and `create_getting_started_vault` both run system git clone work in blocking Tokio tasks so clone UIs stay responsive
|
||||
- On macOS, system-git commands prefer the user's login-shell `git` and `PATH`, and `git_add_remote` preflights HTTPS remotes through `git credential fill` so Keychain can prompt/grant access before the first fetch or push
|
||||
- On Linux AppImage launches, every system-git command and MCP Node subprocess removes AppImage loader overrides such as `LD_LIBRARY_PATH`, `LD_PRELOAD`, and `GIT_EXEC_PATH` before spawning, so helpers like `git-remote-https` and system `node` bind against the host library stack instead of Tolaria's bundled WebKit/AppImage libraries
|
||||
- On Linux AppImage Wayland launches, startup environment safeguards also set `GTK_IM_MODULE=fcitx` when common input-method variables already indicate fcitx and the user has not explicitly chosen a GTK IM module, allowing WebKitGTK editor input to reach fcitx5 on compositors such as niri without overriding deliberate `GTK_IM_MODULE` choices.
|
||||
- On Linux AppImage launches, release packaging bundles the GTK3 fcitx immodule into the AppImage and startup environment safeguards write a cache-local `GTK_IM_MODULE_FILE` that points GTK at the mounted module whenever fcitx is configured. If the user has not explicitly chosen a GTK IM module, Tolaria also sets `GTK_IM_MODULE=fcitx`, allowing WebKitGTK editor input to reach fcitx5 on both Wayland and X11 fallback launches without relying on host GTK module cache paths.
|
||||
- `git_add_remote` uses the same system git path and refuses remotes whose history is unrelated or ahead of the local vault
|
||||
- Existing `git_pull` / `git_push` commands keep surfacing raw git errors, and clone commands fail fast when git wants interactive terminal input
|
||||
- No provider-specific token or username is stored in app settings
|
||||
|
||||
@@ -234,7 +234,7 @@ The main Tauri window also persists its last normal size and screen position in
|
||||
Tauri setup keeps launch-time filesystem and subprocess work off the window creation critical path. Legacy `~/Laputa` housekeeping and the initial persisted-vault MCP bridge sync run on named background threads, so large legacy vaults, stale active-vault paths, or slow process startup cannot beachball the macOS app before React mounts. React still resyncs the bridge from `useVaultSwitcher` after the persisted selection loads, and no selected vault stops the bridge. The HTML bootstrap also installs a Tauri-only one-shot watchdog: React reports readiness from an effect after the root commits, and if that readiness signal never arrives the WebView reloads once instead of leaving macOS users in an inert rendered shell.
|
||||
|
||||
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 the desktop native menus use. The native app menu keeps macOS-only Services/Hide entries off Windows and Linux, registers the macOS Window submenu with Tauri's reserved `WINDOW_SUBMENU_ID` so NSApp can add system window-management and window-list items, registers a window-scoped menu event handler on Windows where Tauri delivers menu clicks through the main `WebviewWindow`, and cross-platform custom items such as Check for Updates emit Tolaria command IDs with visible updater feedback.
|
||||
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` and `WEBKIT_DISABLE_COMPOSITING_MODE=1` independently unless the user already set either variable, and on Wayland sessions it re-execs once with the first architecture-matching system `libwayland-client.so` in `LD_PRELOAD` when the user has not provided their own preload. The candidate order prefers Fedora-style `lib64` and Debian-style `x86_64-linux-gnu` paths before generic `/usr/lib`, and the ELF header is checked so a 64-bit Tolaria process does not retry with a 32-bit Wayland client library. If an AppImage Wayland session advertises fcitx through common input-method environment hints and the user has not already chosen `GTK_IM_MODULE`, Tolaria sets `GTK_IM_MODULE=fcitx` before WebKit starts so GTK/WebKit input contexts reach fcitx5 on compositors that do not reliably provide the Wayland input method frontend. The same AppImage path checks whether `fc-match` resolves the default emoji font to `Noto-COLRv1.ttf`; when the user has not provided `FONTCONFIG_FILE` or `FONTCONFIG_PATH`, Tolaria writes a cache-local fontconfig file that rejects only that matched font file and exports it before WebKit starts. The rendering overrides keep AppImage WebViews from blanking after accelerated compositing/DMA-BUF failures, the re-exec addresses AppImage library-order failures that can surface as `Could not create default EGL display: EGL_BAD_PARAMETER`, the fcitx GTK fallback preserves Chinese/Japanese/Korean IME composition for affected AppImage Wayland users, and the fontconfig guard avoids known WebKit crashes in COLRv1 emoji font rendering while leaving other emoji fonts available.
|
||||
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` and `WEBKIT_DISABLE_COMPOSITING_MODE=1` independently unless the user already set either variable, and on Wayland sessions it re-execs once with the first architecture-matching system `libwayland-client.so` in `LD_PRELOAD` when the user has not provided their own preload. The candidate order prefers Fedora-style `lib64` and Debian-style `x86_64-linux-gnu` paths before generic `/usr/lib`, and the ELF header is checked so a 64-bit Tolaria process does not retry with a 32-bit Wayland client library. Linux release jobs install `fcitx5-frontend-gtk3`, the AppImage output-plugin shim copies the GTK3 fcitx immodule and client library into the AppDir before sealing, and runtime startup writes a mount-path-specific `GTK_IM_MODULE_FILE` cache when fcitx is configured via `GTK_IM_MODULE=fcitx` or common fcitx environment hints. If the user has not already chosen `GTK_IM_MODULE`, Tolaria sets `GTK_IM_MODULE=fcitx` before WebKit starts, so both Wayland and X11 AppImage launches can load the bundled module instead of depending on host GTK module caches. The same AppImage path checks whether `fc-match` resolves the default emoji font to `Noto-COLRv1.ttf`; when the user has not provided `FONTCONFIG_FILE` or `FONTCONFIG_PATH`, Tolaria writes a cache-local fontconfig file that rejects only that matched font file and exports it before WebKit starts. The rendering overrides keep AppImage WebViews from blanking after accelerated compositing/DMA-BUF failures, the re-exec addresses AppImage library-order failures that can surface as `Could not create default EGL display: EGL_BAD_PARAMETER`, the fcitx GTK bundle preserves Chinese/Japanese/Korean IME composition for affected AppImage users, and the fontconfig guard avoids known WebKit crashes in COLRv1 emoji font rendering while leaving other emoji fonts available.
|
||||
|
||||
## Multi-Window (Note Windows)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Linux release CI prepares Tauri's AppImage tools cache before `pnpm tauri build`
|
||||
node scripts/appimage-launcher-tools.mjs prepare-plugin
|
||||
```
|
||||
|
||||
That step patches linuxdeploy's generated GTK `AppRun` wrapper before the AppImage is sealed so direct launches, absolute symlinks, and relative symlinks resolve the real AppRun path before choosing its directory. After build, CI extracts every produced AppImage launcher and verifies the symlink-safe resolver:
|
||||
That step patches linuxdeploy's generated GTK `AppRun` wrapper before the AppImage is sealed so direct launches, absolute symlinks, and relative symlinks resolve the real AppRun path before choosing its directory. Linux release jobs also install `fcitx5-frontend-gtk3`; the same AppImage output-plugin wrapper copies the GTK3 fcitx immodule and client library into the AppDir so Chinese/Japanese/Korean input does not depend on host GTK module cache paths. After build, CI extracts every produced AppImage launcher and verifies the symlink-safe resolver plus the bundled fcitx files:
|
||||
|
||||
```bash
|
||||
node scripts/appimage-launcher-tools.mjs validate-appimages src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
||||
|
||||
24
docs/adr/0117-appimage-fcitx-gtk3-frontend-bundle.md
Normal file
24
docs/adr/0117-appimage-fcitx-gtk3-frontend-bundle.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ADR-0117: Bundle the fcitx GTK3 frontend in Linux AppImages
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Linux AppImages run WebKitGTK through the GTK3 input-method stack. Users with fcitx5 can export `GTK_IM_MODULE=fcitx`, `QT_IM_MODULE=fcitx`, and `XMODIFIERS=@im=fcitx`, but the AppImage still cannot load the host GTK immodule reliably because the GTK module cache and library paths are isolated from the mounted AppDir.
|
||||
|
||||
The previous AppImage startup fallback set `GTK_IM_MODULE=fcitx` when fcitx was detected, but it did not make the `im-fcitx5.so` module available inside the AppImage. That left Chinese/Japanese/Korean input dependent on host paths that GTK may not search from a sealed AppImage.
|
||||
|
||||
## Decision
|
||||
|
||||
Linux release jobs install `fcitx5-frontend-gtk3` and the AppImage output-plugin shim copies the GTK3 fcitx immodule plus its client library into the AppDir before the AppImage is sealed. At runtime, AppImage startup writes a mount-path-specific `GTK_IM_MODULE_FILE` cache that points GTK at the bundled module whenever fcitx is configured explicitly or through common fcitx environment hints.
|
||||
|
||||
The sealed AppImage validation step extracts every produced AppImage and fails the release if the symlink-safe AppRun resolver, the bundled fcitx immodule, or the fcitx client library is missing.
|
||||
|
||||
## Consequences
|
||||
|
||||
- fcitx5 input works in AppImage launches without relying on the host GTK immodule cache path.
|
||||
- X11 fallback launches with explicit `GTK_IM_MODULE=fcitx` use the same bundled module path as Wayland launches.
|
||||
- Linux AppImage builds now depend on the distro package that provides the GTK3 fcitx frontend.
|
||||
- If the Ubuntu package path changes, the AppImage validation step fails before publishing a broken bundle.
|
||||
@@ -21,10 +21,19 @@ export const FIXED_LINUXDEPLOY_APPRUN_DIR_LINE =
|
||||
'this_dir="$(dirname "$(readlink -f "$0")")"'
|
||||
export const APPIMAGE_PLUGIN_WRAPPER_NAME = 'linuxdeploy-plugin-appimage.AppImage'
|
||||
export const REAL_APPIMAGE_PLUGIN_NAME = 'linuxdeploy-plugin-appimage.real.AppImage'
|
||||
export const APPIMAGE_FCITX_GTK3_IM_MODULE_PATH =
|
||||
'usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/im-fcitx5.so'
|
||||
export const APPIMAGE_FCITX_GCLIENT_LIBRARY_PATH =
|
||||
'usr/lib/x86_64-linux-gnu/libFcitx5GClient.so.2'
|
||||
export const DEFAULT_APPIMAGE_PLUGIN_URL =
|
||||
'https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage'
|
||||
|
||||
const WRAPPER_MARKER = 'Tolaria AppImage symlink launcher shim'
|
||||
const REQUIRED_APPIMAGE_PATHS = [
|
||||
'AppRun',
|
||||
APPIMAGE_FCITX_GTK3_IM_MODULE_PATH,
|
||||
APPIMAGE_FCITX_GCLIENT_LIBRARY_PATH,
|
||||
]
|
||||
|
||||
export function tauriToolsCacheDir(env = process.env) {
|
||||
if (env.TOLARIA_TAURI_TOOLS_DIR) {
|
||||
@@ -76,6 +85,8 @@ set -euo pipefail
|
||||
PLUGIN_URL="\${TOLARIA_APPIMAGE_PLUGIN_URL:-${pluginUrl}}"
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
REAL_PLUGIN="\${TOLARIA_APPIMAGE_REAL_PLUGIN:-"$SCRIPT_DIR/${REAL_APPIMAGE_PLUGIN_NAME}"}"
|
||||
FCITX_GTK3_IM_MODULE="\${TOLARIA_FCITX_GTK3_IM_MODULE:-/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/im-fcitx5.so}"
|
||||
FCITX_LIBRARY_DIR="\${TOLARIA_FCITX_LIBRARY_DIR:-/usr/lib/x86_64-linux-gnu}"
|
||||
|
||||
appdir_from_args() {
|
||||
local previous=""
|
||||
@@ -148,8 +159,39 @@ elif "autogenerated by linuxdeploy" in text and "AppRun.wrapped" in text:
|
||||
PY
|
||||
}
|
||||
|
||||
bundle_fcitx_gtk3_module() {
|
||||
local appdir="\${APPDIR:-}"
|
||||
|
||||
if [ -z "$appdir" ]; then
|
||||
appdir="$(appdir_from_args "$@" || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$appdir" ] || [ ! -d "$appdir" ] || [ ! -f "$FCITX_GTK3_IM_MODULE" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local module_dest="$appdir/${APPIMAGE_FCITX_GTK3_IM_MODULE_PATH}"
|
||||
local library_dest_dir="$appdir/usr/lib/x86_64-linux-gnu"
|
||||
|
||||
mkdir -p "$(dirname -- "$module_dest")" "$library_dest_dir"
|
||||
cp -a "$FCITX_GTK3_IM_MODULE" "$module_dest"
|
||||
|
||||
local copied_library=0
|
||||
shopt -s nullglob
|
||||
for lib in "$FCITX_LIBRARY_DIR"/libFcitx5GClient.so* "$FCITX_LIBRARY_DIR"/libFcitx5Utils.so*; do
|
||||
cp -a "$lib" "$library_dest_dir/"
|
||||
copied_library=1
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
if [ "$copied_library" -eq 0 ]; then
|
||||
echo "No fcitx GTK client libraries found in $FCITX_LIBRARY_DIR" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
download_real_plugin
|
||||
patch_apprun "$@"
|
||||
bundle_fcitx_gtk3_module "$@"
|
||||
exec "$REAL_PLUGIN" "$@"
|
||||
`
|
||||
}
|
||||
@@ -181,6 +223,45 @@ export async function validateAppRunFile(path) {
|
||||
assertSymlinkSafeAppRunText(text, path)
|
||||
}
|
||||
|
||||
function extractAppImagePath(appImage, requiredPath, tempDir) {
|
||||
const result = spawnSync(appImage, ['--appimage-extract', requiredPath], {
|
||||
cwd: tempDir,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
|
||||
if (result.status === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
`Failed to extract ${requiredPath} from ${appImage}`,
|
||||
result.stdout.trim(),
|
||||
result.stderr.trim(),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
)
|
||||
}
|
||||
|
||||
function assertAppImagePathsExtracted(appImage, tempDir, requiredPaths) {
|
||||
for (const requiredPath of requiredPaths) {
|
||||
const extractedPath = join(tempDir, 'squashfs-root', requiredPath)
|
||||
if (!existsSync(extractedPath)) {
|
||||
throw new Error(`${appImage} is missing ${requiredPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function validateExtractedAppImage(appImage, tempDir) {
|
||||
for (const requiredPath of REQUIRED_APPIMAGE_PATHS) {
|
||||
extractAppImagePath(appImage, requiredPath, tempDir)
|
||||
}
|
||||
|
||||
await validateAppRunFile(join(tempDir, 'squashfs-root', 'AppRun'))
|
||||
assertAppImagePathsExtracted(appImage, tempDir, REQUIRED_APPIMAGE_PATHS.slice(1))
|
||||
}
|
||||
|
||||
export async function validateAppImages(paths) {
|
||||
if (paths.length === 0) {
|
||||
throw new Error('At least one AppImage path is required for launcher validation')
|
||||
@@ -189,24 +270,7 @@ export async function validateAppImages(paths) {
|
||||
for (const appImage of paths.map((path) => resolve(path))) {
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'tolaria-appimage-'))
|
||||
try {
|
||||
const result = spawnSync(appImage, ['--appimage-extract', 'AppRun'], {
|
||||
cwd: tempDir,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
[
|
||||
`Failed to extract AppRun from ${appImage}`,
|
||||
result.stdout.trim(),
|
||||
result.stderr.trim(),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
)
|
||||
}
|
||||
|
||||
await validateAppRunFile(join(tempDir, 'squashfs-root', 'AppRun'))
|
||||
await validateExtractedAppImage(appImage, tempDir)
|
||||
} finally {
|
||||
await rm(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -112,3 +112,52 @@ test('plugin wrapper patches AppRun before delegating to the real output plugin'
|
||||
assert.equal(patched.includes(BROKEN_LINUXDEPLOY_APPRUN_DIR_LINE), false)
|
||||
assert.equal(patched.includes(FIXED_LINUXDEPLOY_APPRUN_DIR_LINE), true)
|
||||
})
|
||||
|
||||
test('plugin wrapper bundles fcitx GTK3 input module before sealing AppImage', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'tolaria-appimage-fcitx-'))
|
||||
const appDir = join(root, 'Tolaria.AppDir')
|
||||
const wrapper = join(root, 'linuxdeploy-plugin-appimage.AppImage')
|
||||
const realPlugin = join(root, 'linuxdeploy-plugin-appimage.real.AppImage')
|
||||
const pluginMarker = join(root, 'plugin-ran')
|
||||
const hostModule = join(root, 'host', 'im-fcitx5.so')
|
||||
const hostLibraryDir = join(root, 'host-lib')
|
||||
const hostLibrary = join(hostLibraryDir, 'libFcitx5GClient.so.2')
|
||||
|
||||
await mkdir(appDir)
|
||||
await mkdir(dirname(hostModule), { recursive: true })
|
||||
await mkdir(hostLibraryDir)
|
||||
await writeFile(hostModule, 'fake fcitx gtk module', 'utf8')
|
||||
await writeFile(hostLibrary, 'fake fcitx client library', 'utf8')
|
||||
await writeFile(wrapper, appImagePluginWrapperSource(), 'utf8')
|
||||
await chmod(wrapper, 0o755)
|
||||
await writeFile(
|
||||
realPlugin,
|
||||
`#!/usr/bin/env bash\nset -euo pipefail\ntouch "${pluginMarker}"\n`,
|
||||
'utf8',
|
||||
)
|
||||
await chmod(realPlugin, 0o755)
|
||||
|
||||
const result = spawnSync(wrapper, [], {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
APPDIR: appDir,
|
||||
TOLARIA_APPIMAGE_REAL_PLUGIN: realPlugin,
|
||||
TOLARIA_FCITX_GTK3_IM_MODULE: hostModule,
|
||||
TOLARIA_FCITX_LIBRARY_DIR: hostLibraryDir,
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(result.status, 0, result.stderr)
|
||||
assert.equal(existsSync(pluginMarker), true)
|
||||
assert.equal(
|
||||
existsSync(
|
||||
join(
|
||||
appDir,
|
||||
'usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/im-fcitx5.so',
|
||||
),
|
||||
),
|
||||
true,
|
||||
)
|
||||
assert.equal(existsSync(join(appDir, 'usr/lib/x86_64-linux-gnu/libFcitx5GClient.so.2')), true)
|
||||
})
|
||||
|
||||
@@ -25,6 +25,10 @@ const FCITX_ENV_HINT_KEYS: [&str; 4] = [
|
||||
"QT_IM_MODULE",
|
||||
"SDL_IM_MODULE",
|
||||
];
|
||||
const FCITX_GTK3_IM_MODULE_RELATIVE_PATH: &str =
|
||||
"usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/im-fcitx5.so";
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
const TOLARIA_FCITX_IMMODULES_CACHE_FILE: &str = "tolaria-appimage-fcitx5-immodules.cache";
|
||||
const COLRV1_EMOJI_FONT_FILE: &str = "Noto-COLRv1.ttf";
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
const TOLARIA_COLRV1_FONTCONFIG_FILE: &str = "tolaria-appimage-no-colrv1-emoji.conf";
|
||||
@@ -101,6 +105,68 @@ where
|
||||
.any(|key| get_var(key).is_some_and(|value| env_mentions_fcitx(&value)))
|
||||
}
|
||||
|
||||
fn non_empty_env<F>(get_var: &mut F, key: &str) -> Option<String>
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
{
|
||||
get_var(key).filter(|value| !value.trim().is_empty())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct FcitxGtkModuleFileOverride {
|
||||
cache_path: std::path::PathBuf,
|
||||
cache_contents: String,
|
||||
}
|
||||
|
||||
fn fcitx_immodules_cache_contents(module_path: &std::path::Path) -> String {
|
||||
format!(
|
||||
r#"# Tolaria AppImage GTK input method modules
|
||||
"{}"
|
||||
"fcitx" "Fcitx 5" "fcitx" "" "ja:ko:zh:*"
|
||||
"#,
|
||||
module_path.display()
|
||||
)
|
||||
}
|
||||
|
||||
fn should_use_bundled_fcitx_gtk_module<F>(get_var: &mut F) -> bool
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
{
|
||||
if !is_linux_appimage_launch(&mut *get_var) {
|
||||
return false;
|
||||
}
|
||||
if has_non_empty_env(get_var, "GTK_IM_MODULE_FILE") {
|
||||
return false;
|
||||
}
|
||||
if let Some(gtk_im_module) = non_empty_env(get_var, "GTK_IM_MODULE") {
|
||||
return env_mentions_fcitx(>k_im_module);
|
||||
}
|
||||
|
||||
has_fcitx_env_hint(get_var)
|
||||
}
|
||||
|
||||
fn fcitx_gtk_im_module_file_override_with<F, E>(
|
||||
get_var: &mut F,
|
||||
mut module_exists: E,
|
||||
cache_path: std::path::PathBuf,
|
||||
) -> Option<FcitxGtkModuleFileOverride>
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
E: FnMut(&std::path::Path) -> bool,
|
||||
{
|
||||
if !should_use_bundled_fcitx_gtk_module(get_var) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let appdir = non_empty_env(get_var, "APPDIR")?;
|
||||
let module_path = std::path::Path::new(appdir.trim()).join(FCITX_GTK3_IM_MODULE_RELATIVE_PATH);
|
||||
|
||||
module_exists(&module_path).then(|| FcitxGtkModuleFileOverride {
|
||||
cache_path,
|
||||
cache_contents: fcitx_immodules_cache_contents(&module_path),
|
||||
})
|
||||
}
|
||||
|
||||
fn fcitx_gtk_im_module_override_with<F>(get_var: &mut F) -> Option<StartupEnvOverride>
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
@@ -108,9 +174,6 @@ where
|
||||
if !is_linux_appimage_launch(&mut *get_var) {
|
||||
return None;
|
||||
}
|
||||
if !is_wayland_session(&mut *get_var) {
|
||||
return None;
|
||||
}
|
||||
if has_env(get_var, "GTK_IM_MODULE") {
|
||||
return None;
|
||||
}
|
||||
@@ -245,12 +308,43 @@ where
|
||||
pub(crate) fn apply_startup_env_overrides() {
|
||||
apply_wayland_client_preload();
|
||||
apply_colrv1_emoji_font_guard();
|
||||
apply_fcitx_gtk_im_module_file();
|
||||
|
||||
for env_override in 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_fcitx_gtk_im_module_file() {
|
||||
let Some(cache_path) = fcitx_immodules_cache_file_path() else {
|
||||
eprintln!("Tolaria AppImage fcitx GTK module skipped: failed to resolve cache directory");
|
||||
return;
|
||||
};
|
||||
let Some(env_override) = fcitx_gtk_im_module_file_override_with(
|
||||
&mut |key| std::env::var(key).ok(),
|
||||
std::path::Path::is_file,
|
||||
cache_path,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let Some(parent) = env_override.cache_path.parent() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(error) = std::fs::create_dir_all(parent) {
|
||||
eprintln!("Tolaria AppImage fcitx GTK module skipped: failed to prepare cache ({error})");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(error) = std::fs::write(&env_override.cache_path, env_override.cache_contents) {
|
||||
eprintln!("Tolaria AppImage fcitx GTK module skipped: failed to write cache ({error})");
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("GTK_IM_MODULE_FILE", env_override.cache_path.as_os_str());
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
fn apply_colrv1_emoji_font_guard() {
|
||||
let Some(font_path) =
|
||||
@@ -309,6 +403,18 @@ fn colrv1_fontconfig_file_path() -> Option<std::path::PathBuf> {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
fn fcitx_immodules_cache_file_path() -> Option<std::path::PathBuf> {
|
||||
let cache_dir =
|
||||
dirs::cache_dir().or_else(|| dirs::home_dir().map(|home| home.join(".cache")))?;
|
||||
|
||||
Some(
|
||||
cache_dir
|
||||
.join("tolaria")
|
||||
.join(TOLARIA_FCITX_IMMODULES_CACHE_FILE),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
fn launched_appimage_path() -> Result<std::path::PathBuf, String> {
|
||||
if let Some(appimage) = std::env::var_os("APPIMAGE").filter(|value| !value.is_empty()) {
|
||||
@@ -366,7 +472,8 @@ fn apply_wayland_client_preload() {
|
||||
mod tests {
|
||||
use super::{
|
||||
colrv1_emoji_font_path_with, colrv1_emoji_fontconfig_contents, elf_library_matches_process,
|
||||
startup_env_overrides_with, wayland_client_preload_path_with, StartupEnvOverride,
|
||||
fcitx_gtk_im_module_file_override_with, startup_env_overrides_with,
|
||||
wayland_client_preload_path_with, StartupEnvOverride, FCITX_GTK3_IM_MODULE_RELATIVE_PATH,
|
||||
};
|
||||
|
||||
fn default_webkit_overrides() -> Vec<StartupEnvOverride> {
|
||||
@@ -442,6 +549,18 @@ mod tests {
|
||||
assert_eq!(overrides, default_webkit_and_fcitx_overrides());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_env_overrides_enable_fcitx_gtk_module_for_x11_appimage() {
|
||||
let overrides = startup_env_overrides_with(|key| match key {
|
||||
"APPIMAGE" => Some("/tmp/Tolaria.AppImage".to_string()),
|
||||
"XDG_SESSION_TYPE" => Some("x11".to_string()),
|
||||
"XMODIFIERS" => Some("@im=fcitx".to_string()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
assert_eq!(overrides, default_webkit_and_fcitx_overrides());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_env_overrides_preserve_explicit_gtk_im_module() {
|
||||
let overrides =
|
||||
@@ -461,6 +580,50 @@ mod tests {
|
||||
assert_eq!(overrides, default_webkit_overrides());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fcitx_module_file_override_points_gtk_to_bundled_appimage_module() {
|
||||
let appdir = std::path::Path::new("/tmp/.mount_Tolaria");
|
||||
let module_path = appdir.join(FCITX_GTK3_IM_MODULE_RELATIVE_PATH);
|
||||
let cache_path = std::path::PathBuf::from("/tmp/tolaria/immodules.cache");
|
||||
let env_override = fcitx_gtk_im_module_file_override_with(
|
||||
&mut |key| match key {
|
||||
"APPDIR" => Some(appdir.display().to_string()),
|
||||
"APPIMAGE" => Some("/tmp/Tolaria.AppImage".to_string()),
|
||||
"GTK_IM_MODULE" => Some("fcitx".to_string()),
|
||||
"XDG_SESSION_TYPE" => Some("x11".to_string()),
|
||||
_ => None,
|
||||
},
|
||||
|path| path == module_path,
|
||||
cache_path.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(env_override.cache_path, cache_path);
|
||||
assert!(env_override
|
||||
.cache_contents
|
||||
.contains(&module_path.display().to_string()));
|
||||
assert!(env_override
|
||||
.cache_contents
|
||||
.contains("\"fcitx\" \"Fcitx 5\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fcitx_module_file_override_preserves_explicit_module_cache() {
|
||||
let env_override = fcitx_gtk_im_module_file_override_with(
|
||||
&mut |key| match key {
|
||||
"APPDIR" => Some("/tmp/.mount_Tolaria".to_string()),
|
||||
"APPIMAGE" => Some("/tmp/Tolaria.AppImage".to_string()),
|
||||
"GTK_IM_MODULE" => Some("fcitx".to_string()),
|
||||
"GTK_IM_MODULE_FILE" => Some("/tmp/custom-immodules.cache".to_string()),
|
||||
_ => None,
|
||||
},
|
||||
|_| true,
|
||||
std::path::PathBuf::from("/tmp/tolaria/immodules.cache"),
|
||||
);
|
||||
|
||||
assert_eq!(env_override, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colrv1_font_guard_targets_reported_appimage_emoji_font() {
|
||||
let font_path = colrv1_emoji_font_path_with(
|
||||
|
||||
Reference in New Issue
Block a user