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'