From ceddfefa37bfaf3926953a863641ad26efd75fff Mon Sep 17 00:00:00 2001 From: Zhang LongQi Date: Fri, 25 Sep 2026 15:47:31 +0800 Subject: [PATCH] fix(linux): install settings application menu entry for user installs Install the desktop launcher and icon during source installation and package activation, and remove them after the last user integration is uninstalled. Use absolute launcher and icon paths so settings remain discoverable without session PATH or icon-theme cache updates. Cover launcher escaping, icon installation, launch, and cleanup in an isolated XDG environment. --- README.md | 4 ++ packaging/scripts/stage-system-package.sh | 6 ++ scripts/install/common/desktop-integration.sh | 58 +++++++++++++++++++ scripts/install/common/install-native-user.sh | 4 ++ .../install/common/uninstall-native-user.sh | 4 +- scripts/test/desktop-integration.sh | 37 ++++++++++++ 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 scripts/install/common/desktop-integration.sh create mode 100755 scripts/test/desktop-integration.sh diff --git a/README.md b/README.md index c7e2035..8129782 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,10 @@ AI页配置 OpenAI-compatible endpoint、model、API key、超时、SSE与thinki 若端点不在本机,转写文本会发送到该接口;语音编辑还会发送当前应用提供的 surrounding text、光标与选区。 +### Linux 应用菜单 + +完成用户安装或系统包的用户激活后,可从应用菜单搜索「VoCoType 设置」,直接打开设置中心。 + ### Playground、诊断与设置中心 Linux使用 GTK 3/C++设置中心,macOS使用 AppKit原生设置中心。两端均覆盖模型、麦克风、AI、术语、诊断和教程;macOS另外负责首次安装/升级 InputMethodKit组件。设置文本区域支持系统原生 `⌘Z`,同时兼容 `Ctrl+Z`撤销和对应重做快捷键。 diff --git a/packaging/scripts/stage-system-package.sh b/packaging/scripts/stage-system-package.sh index eeaca59..8168f7c 100755 --- a/packaging/scripts/stage-system-package.sh +++ b/packaging/scripts/stage-system-package.sh @@ -92,6 +92,12 @@ install -Dm755 "$PROJECT_DIR/scripts/install/common/install-native-user.sh" \ install -Dm755 "$PROJECT_DIR/scripts/install/common/uninstall-native-user.sh" \ "$source_root/scripts/install/common/uninstall-native-user.sh" +install -Dm644 "$PROJECT_DIR/scripts/install/common/desktop-integration.sh" \ + "$source_root/scripts/install/common/desktop-integration.sh" +install -Dm644 "$PROJECT_DIR/resources/desktop/io.github.LeonardNJU.VoCoType.Settings.desktop" \ + "$source_root/resources/desktop/io.github.LeonardNJU.VoCoType.Settings.desktop" +install -Dm644 "$PROJECT_DIR/web/icon-192.png" "$source_root/web/icon-192.png" + # Compile the desktop/runtime-facing programs for the target distribution. cmake -S "$PROJECT_DIR/src/desktop" -B "$DESKTOP_BUILD" \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/scripts/install/common/desktop-integration.sh b/scripts/install/common/desktop-integration.sh new file mode 100644 index 0000000..010d220 --- /dev/null +++ b/scripts/install/common/desktop-integration.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Shared by source installs and per-user activation of system packages. +# shellcheck shell=bash + +desktop_exec_path() { + local value=$1 + value=${value//\\/\\\\} + value=${value//\"/\\\"} + value=${value//\$/\\\$} + value=${value//\`/\\\`} + value=${value//%/%%} + # Desktop Entry string escaping is applied before Exec argument unquoting. + value=${value//\\/\\\\} + printf '"%s"' "$value" +} + +desktop_string_value() { + local value=$1 + value=${value//\\/\\\\} + value=${value//$'\n'/\\n} + value=${value//$'\r'/\\r} + value=${value//$'\t'/\\t} + printf '%s' "$value" +} + +install_desktop_entries() { + local root=$1 settings=$2 + local data="${XDG_DATA_HOME:-$HOME/.local/share}" + local desktop=io.github.LeonardNJU.VoCoType.Settings.desktop + local icon="$data/icons/hicolor/192x192/apps/vocotype.png" + mkdir -p "$data/applications" "$data/icons/hicolor/192x192/apps" + install -m644 "$root/web/icon-192.png" "$icon" + # Use an absolute Icon path so KDE does not need a refreshed icon-theme cache. + # The configured command must work even when ~/.local/bin is absent from PATH. + # env also avoids GLib testing an unexpanded %% in the executable filename. + while IFS= read -r line; do + case "$line" in + Icon=*) printf 'Icon=%s\n' "$(desktop_string_value "$icon")" ;; + Exec=*) printf 'Exec=/usr/bin/env %s\n' "$(desktop_exec_path "$settings")" ;; + *) printf '%s\n' "$line" ;; + esac + done < "$root/resources/desktop/$desktop" > "$data/applications/$desktop" + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$data/applications" >/dev/null 2>&1 || true + fi + if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -f -t "$data/icons/hicolor" >/dev/null 2>&1 || true + fi +} + +remove_desktop_entries() { + local data="${XDG_DATA_HOME:-$HOME/.local/share}" + command rm -f "$data/applications/io.github.LeonardNJU.VoCoType.Settings.desktop" \ + "$data/icons/hicolor/192x192/apps/vocotype.png" + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$data/applications" >/dev/null 2>&1 || true + fi +} diff --git a/scripts/install/common/install-native-user.sh b/scripts/install/common/install-native-user.sh index 38e4021..dc2a5d8 100755 --- a/scripts/install/common/install-native-user.sh +++ b/scripts/install/common/install-native-user.sh @@ -455,4 +455,8 @@ case "$FRAMEWORK" in universal) install_fcitx; install_ibus ;; esac cleanup_legacy_runtime +# Both source and package activation install a discoverable settings entry. +. "$SCRIPT_DIR/desktop-integration.sh" +install_desktop_entries "$PROJECT_DIR" "$USER_BIN/vocotype-settings" +log "The VoCoType settings entry is installed in the application menu." log "✓ Native-only runtime installed. Configuration and model caches were preserved." diff --git a/scripts/install/common/uninstall-native-user.sh b/scripts/install/common/uninstall-native-user.sh index b5a2104..1dede4a 100755 --- a/scripts/install/common/uninstall-native-user.sh +++ b/scripts/install/common/uninstall-native-user.sh @@ -101,7 +101,9 @@ if [[ "$PURGE_RUNTIME" == true ]]; then rm -rf "$HOME/.local/lib/vocotype-native" "$HOME/.local/lib/vocotype-streaming" fi if ! fcitx_present && ! ibus_present; then - rm -f "$HOME/.local/bin/vocotype-settings" + . "$SCRIPT_DIR/desktop-integration.sh" + remove_desktop_entries + command rm -f "$HOME/.local/bin/vocotype-settings" fi if [[ "$REMOVE_USER_DATA" == true ]]; then rm -rf "${XDG_CONFIG_HOME:-$HOME/.config}/vocotype" diff --git a/scripts/test/desktop-integration.sh b/scripts/test/desktop-integration.sh new file mode 100755 index 0000000..bd24fc7 --- /dev/null +++ b/scripts/test/desktop-integration.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) + +export TEST_DESKTOP_DIR +TEST_DESKTOP_DIR=$(mktemp -d) +trap 'command rm -rf "$TEST_DESKTOP_DIR"' EXIT +export XDG_DATA_HOME="$TEST_DESKTOP_DIR/data" XDG_CONFIG_HOME="$TEST_DESKTOP_DIR/config" +export XDG_CACHE_HOME="$TEST_DESKTOP_DIR/cache" +# Keep the test desktop isolated from the real session. +export XDG_RUNTIME_DIR="$TEST_DESKTOP_DIR/runtime" +mkdir -m700 "$XDG_RUNTIME_DIR" +export GTK_USE_PORTAL=0 NO_AT_BRIDGE=1 GSETTINGS_BACKEND=memory +. "$ROOT/scripts/install/common/desktop-integration.sh" +# Exercise Exec quoting instead of assuming the home directory has no spaces, +# percent field codes, backslashes, quotes, or shell metacharacters. +# shellcheck disable=SC1003 +launcher="$TEST_DESKTOP_DIR/"'settings space % $ ` " \' +# The child launcher expands the exported test path when invoked. +# shellcheck disable=SC2016 +printf '#!/usr/bin/env bash\ntouch "$TEST_DESKTOP_DIR/launched"\n' > "$launcher" +chmod +x "$launcher" +install_desktop_entries "$ROOT" "$launcher" +settings="$XDG_DATA_HOME/applications/io.github.LeonardNJU.VoCoType.Settings.desktop" +desktop-file-validate "$settings" +grep -Fxq "Icon=$XDG_DATA_HOME/icons/hicolor/192x192/apps/vocotype.png" "$settings" +cmp "$ROOT/web/icon-192.png" "$XDG_DATA_HOME/icons/hicolor/192x192/apps/vocotype.png" +timeout 10 dbus-run-session -- gio launch "$settings" +for _ in {1..50}; do + [[ -f "$TEST_DESKTOP_DIR/launched" ]] && break + sleep 0.1 +done +[[ -f "$TEST_DESKTOP_DIR/launched" ]] +remove_desktop_entries +[[ ! -e "$settings" ]] +[[ ! -e "$XDG_DATA_HOME/icons/hicolor/192x192/apps/vocotype.png" ]] +printf 'Application menu installation, launch, and uninstall: PASS\n'