fix: bundle fcitx gtk module in appimage

This commit is contained in:
lucaronin
2026-05-13 16:44:38 +02:00
parent 41f0e00cfb
commit 201aefca05
9 changed files with 327 additions and 25 deletions

View File

@@ -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 })
}

View File

@@ -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)
})