From dbc26435835f2e0964e075b43c9c8c1961e1dfdf Mon Sep 17 00:00:00 2001 From: HKLHaoBin Date: Mon, 8 Jun 2026 12:21:43 +0800 Subject: [PATCH 1/3] Add Android APK CI workflow for manual dispatch on beta. Sync android-apk.yml from openless-android so the workflow appears in Actions. Co-authored-by: Cursor --- .github/workflows/android-apk.yml | 134 ++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 .github/workflows/android-apk.yml diff --git a/.github/workflows/android-apk.yml b/.github/workflows/android-apk.yml new file mode 100644 index 00000000..2530acef --- /dev/null +++ b/.github/workflows/android-apk.yml @@ -0,0 +1,134 @@ +name: Android APK (debug) + +# Triggers: +# - push v*-tauri tag → build debug APK, upload artifact + attach to GitHub Release +# - workflow_dispatch → build debug APK, upload artifact only (no release) +# +# v1 scope: in-app dictation APK with RECORD_AUDIO merged from android-scaffolding. +# IME v2 / overlay v3 manifest snippets are intentionally out of scope. + +on: + push: + tags: + - 'v*-tauri' + workflow_dispatch: + +jobs: + build-android-apk: + permissions: + contents: write + runs-on: ubuntu-latest + env: + CI: true + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Java 17 (Zulu) + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: '17' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install NDK and accept SDK licenses + shell: bash + run: | + set -euo pipefail + sdkmanager "ndk;26.1.10909125" + ndk_dir="$ANDROID_HOME/ndk/26.1.10909125" + if [ ! -d "$ndk_dir" ]; then + echo "::error::NDK not found at $ndk_dir" + exit 1 + fi + echo "ANDROID_NDK_HOME=$ndk_dir" >> "$GITHUB_ENV" + echo "NDK_HOME=$ndk_dir" >> "$GITHUB_ENV" + set +o pipefail + yes | sdkmanager --licenses + sdkmanager_exit=${PIPESTATUS[1]} + set -o pipefail + if [ "$sdkmanager_exit" -ne 0 ]; then + echo "::error::sdkmanager --licenses failed with exit code $sdkmanager_exit" + exit "$sdkmanager_exit" + fi + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: openless-all/app/package-lock.json + + - uses: dtolnay/rust-toolchain@stable + with: + targets: >- + aarch64-linux-android, + armv7-linux-androideabi, + i686-linux-android, + x86_64-linux-android + + - name: Cache Cargo + uses: swatinem/rust-cache@v2 + with: + workspaces: openless-all/app/src-tauri -> target + + - name: Install npm deps + working-directory: openless-all/app + run: npm ci + + - name: Build frontend + working-directory: openless-all/app + run: npm run build + + - name: Initialize Android project + working-directory: openless-all/app + run: npm run tauri -- android init --ci + + - name: Merge APK v1 manifest (RECORD_AUDIO) + working-directory: openless-all/app + run: node scripts/merge-android-v1-manifest.mjs + + - name: Build Android debug APK + working-directory: openless-all/app + run: npm run tauri:android:build + + - name: Collect debug APK + id: apk + shell: bash + working-directory: openless-all/app + run: | + set -euo pipefail + apk_path="$(find src-tauri/gen/android -type f -name '*.apk' -path '*/outputs/*' | sort | head -n 1)" + if [ -z "$apk_path" ]; then + echo "::error::No APK found under src-tauri/gen/android/**/outputs/" + find src-tauri/gen/android -type f -name '*.apk' 2>/dev/null || true + exit 1 + fi + if [[ "${{ github.ref }}" == refs/tags/v* ]] && [[ "${{ github.ref_name }}" == *-tauri ]]; then + label="${{ github.ref_name }}" + else + label="run-${{ github.run_number }}" + fi + dest="$RUNNER_TEMP/OpenLess-android-debug-${label}.apk" + cp "$apk_path" "$dest" + echo "path=$dest" >> "$GITHUB_OUTPUT" + echo "Collected APK: $apk_path → $dest" + + - name: Upload Android APK artifact + uses: actions/upload-artifact@v4 + with: + name: openless-android-debug + path: ${{ steps.apk.outputs.path }} + if-no-files-found: error + + - name: Attach APK to GitHub Release + if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: OpenLess ${{ github.ref_name }} + draft: false + prerelease: ${{ endsWith(github.ref_name, '-beta-tauri') }} + files: ${{ steps.apk.outputs.path }} From cb63eaa7666b3aadabf59dd0c192a6b36321fdcb Mon Sep 17 00:00:00 2001 From: HKLHaoBin Date: Mon, 8 Jun 2026 15:26:46 +0800 Subject: [PATCH 2/3] ci(android): harden Gradle wrapper download in APK workflow Co-authored-by: Cursor --- .github/workflows/android-apk.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/android-apk.yml b/.github/workflows/android-apk.yml index 2530acef..285c1bde 100644 --- a/.github/workflows/android-apk.yml +++ b/.github/workflows/android-apk.yml @@ -74,6 +74,8 @@ jobs: with: workspaces: openless-all/app/src-tauri -> target + - uses: gradle/actions/setup-gradle@v4 + - name: Install npm deps working-directory: openless-all/app run: npm ci @@ -90,6 +92,21 @@ jobs: working-directory: openless-all/app run: node scripts/merge-android-v1-manifest.mjs + - name: Prime Gradle wrapper + working-directory: openless-all/app + shell: bash + run: | + set -euo pipefail + for attempt in 1 2 3; do + if src-tauri/gen/android/gradlew --project-dir src-tauri/gen/android --version --no-daemon; then + exit 0 + fi + if [ "$attempt" -lt 3 ]; then + sleep $([ "$attempt" -eq 1 ] && echo 15 || echo 30) + fi + done + exit 1 + - name: Build Android debug APK working-directory: openless-all/app run: npm run tauri:android:build From 1ea467a001411c6afc6d3d095c0a778f2ea70051 Mon Sep 17 00:00:00 2001 From: HKLHaoBin Date: Fri, 12 Jun 2026 17:58:41 +0800 Subject: [PATCH 3/3] feat(android): port Android APK runtime onto beta --- .github/workflows/android-apk.yml | 126 +- .github/workflows/ci.yml | 1 + .gitignore | 24 +- README.md | 6 + docs/android-mobile-apk-overlay-plan.md | 315 + openless-all/app/android/README.md | 79 + .../components/AndroidPermissionsPanel.tsx | 386 ++ .../app/android/frontend/lib/androidIpc.ts | 43 + .../lib/androidMicrophonePermission.ts | 104 + .../app/android/frontend/lib/androidTypes.ts | 38 + .../kotlin/MicrophonePermissionActivity.kt | 49 + .../OpenLessAccessibilityCommandReceiver.kt | 36 + .../kotlin/OpenLessAccessibilityService.kt | 373 + .../kotlin/OpenLessAndroidPreferences.kt | 112 + .../app/android/kotlin/OpenLessAppContext.kt | 13 + .../app/android/kotlin/OpenLessApplication.kt | 84 + .../app/android/kotlin/OpenLessNative.kt | 42 + .../android/kotlin/OpenLessOverlayBridge.kt | 33 + .../android/kotlin/OpenLessOverlayService.kt | 831 +++ .../kotlin/OpenLessPermissionBridge.kt | 46 + .../kotlin/OverlayPermissionActivity.kt | 27 + openless-all/app/android/kotlin/README.md | 27 + .../manifests/AndroidManifest.v1.snippet.xml | 7 + .../manifests/AndroidManifest.v3.snippet.xml | 20 + .../res/xml/openless_accessibility_config.xml | 9 + .../manifests/res/xml/openless_ime_method.xml | 9 + openless-all/app/package.json | 6 + openless-all/app/public/AppIcon.png | Bin 371995 -> 87011 bytes .../app/scripts/copy-android-scaffolding.mjs | 187 + .../merge-android-overlay-manifest.mjs | 174 + .../app/scripts/merge-android-v1-manifest.mjs | 133 + openless-all/app/src-tauri/Cargo.lock | 583 +- openless-all/app/src-tauri/Cargo.toml | 29 +- openless-all/app/src-tauri/build.rs | 11 + .../app/src-tauri/capabilities/default.json | 1 + .../app/src-tauri/capabilities/mobile.json | 18 + .../android/drawable/ic_overlay_logo.xml | 4 + .../icons/android/mipmap-hdpi/ic_launcher.png | Bin 2639 -> 2034 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 18326 -> 12057 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 2498 -> 2529 bytes .../icons/android/mipmap-mdpi/ic_launcher.png | Bin 2492 -> 1937 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 9395 -> 6048 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 2435 -> 2424 bytes .../android/mipmap-xhdpi/ic_launcher.png | Bin 6593 -> 4983 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 29136 -> 18904 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 6511 -> 6384 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 12852 -> 9777 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 55538 -> 35392 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 12382 -> 11646 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 20489 -> 15806 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 86570 -> 55373 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 19879 -> 18185 bytes .../src-tauri/src/android/accessibility.rs | 123 + .../app/src-tauri/src/android/insert.rs | 51 + openless-all/app/src-tauri/src/android/jni.rs | 510 ++ openless-all/app/src-tauri/src/android/mod.rs | 25 + .../src-tauri/src/android/native_bridge.rs | 396 ++ .../app/src-tauri/src/android/overlay.rs | 145 + .../app/src-tauri/src/android/types.rs | 323 + .../src/asr/local/apple_speech_provider.rs | 14 +- .../app/src-tauri/src/asr/local/download.rs | 14 +- .../app/src-tauri/src/asr/local/test_run.rs | 5 +- .../app/src-tauri/src/commands/credentials.rs | 32 + .../app/src-tauri/src/commands/misc.rs | 50 + .../app/src-tauri/src/commands/mod.rs | 38 +- .../src/commands/permissions_cmds.rs | 41 + .../app/src-tauri/src/commands/providers.rs | 11 +- openless-all/app/src-tauri/src/commands/qa.rs | 13 + .../app/src-tauri/src/commands/settings.rs | 26 + openless-all/app/src-tauri/src/coordinator.rs | 6013 ++++++++++++++++- .../src-tauri/src/coordinator/dictation.rs | 2469 ++++++- .../app/src-tauri/src/coordinator/qa.rs | 4 +- .../src-tauri/src/coordinator/resources.rs | 23 +- .../app/src-tauri/src/external_url.rs | 70 + openless-all/app/src-tauri/src/insertion.rs | 127 +- openless-all/app/src-tauri/src/lib.rs | 688 +- .../app/src-tauri/src/mobile_runtime.rs | 79 + .../src/mobile_stubs/combo_hotkey.rs | 46 + .../app/src-tauri/src/mobile_stubs/hotkey.rs | 49 + .../src-tauri/src/mobile_stubs/qa_hotkey.rs | 33 + .../src-tauri/src/mobile_stubs/selection.rs | 54 + .../src/mobile_stubs/shortcut_binding.rs | 38 + .../src/mobile_stubs/unicode_keystroke.rs | 40 + openless-all/app/src-tauri/src/permissions.rs | 49 +- openless-all/app/src-tauri/src/persistence.rs | 852 +-- openless-all/app/src-tauri/src/recorder.rs | 53 +- .../app/src-tauri/src/remote_server/mod.rs | 48 +- openless-all/app/src-tauri/src/types.rs | 234 +- .../app/src-tauri/tauri.android.conf.json | 19 + openless-all/app/src-tauri/tauri.conf.json | 3 + openless-all/app/src/App.tsx | 137 +- openless-all/app/src/components/AudioCue.tsx | 21 +- .../app/src/components/AutoUpdate.tsx | 23 +- .../app/src/components/AutoUpdateGate.tsx | 13 +- .../app/src/components/FloatingShell.tsx | 389 +- .../app/src/components/Onboarding.tsx | 519 +- .../app/src/components/SettingsModal.tsx | 200 +- .../app/src/components/WindowChrome.tsx | 7 +- openless-all/app/src/components/ui/Row.tsx | 8 +- .../app/src/components/ui/SelectLite.tsx | 10 + openless-all/app/src/i18n/en.ts | 103 + openless-all/app/src/i18n/ja.ts | 69 + openless-all/app/src/i18n/ko.ts | 69 + openless-all/app/src/i18n/zh-CN.ts | 103 + openless-all/app/src/i18n/zh-TW.ts | 69 + .../src/lib/androidMicrophonePermission.ts | 1 + openless-all/app/src/lib/ipc.ts | 195 +- openless-all/app/src/lib/platform.ts | 122 + openless-all/app/src/lib/stylePrefs.test.ts | 6 + openless-all/app/src/lib/types.ts | 49 +- openless-all/app/src/lib/useMobileLayout.ts | 25 + openless-all/app/src/pages/History.tsx | 30 +- openless-all/app/src/pages/Overview.tsx | 248 +- openless-all/app/src/pages/QaPanel.tsx | 261 +- openless-all/app/src/pages/_atoms.tsx | 96 +- .../app/src/pages/settings/AboutSection.tsx | 28 +- .../src/pages/settings/BetaChannelSection.tsx | 10 +- .../src/pages/settings/LocalModelSection.tsx | 12 +- .../src/pages/settings/MicrophoneSelect.tsx | 4 +- .../src/pages/settings/PermissionsSection.tsx | 87 +- .../src/pages/settings/ProvidersSection.tsx | 43 +- .../pages/settings/RecordingInputSection.tsx | 25 +- .../src/pages/settings/ShortcutsSection.tsx | 12 + .../app/src/pages/settings/shared.tsx | 13 +- openless-all/app/src/pages/settings/tabs.tsx | 33 +- openless-all/app/src/styles/tokens.css | 282 +- openless-all/app/tsconfig.json | 8 +- openless-all/app/vite.config.ts | 22 +- 128 files changed, 17908 insertions(+), 2238 deletions(-) create mode 100644 docs/android-mobile-apk-overlay-plan.md create mode 100644 openless-all/app/android/README.md create mode 100644 openless-all/app/android/frontend/components/AndroidPermissionsPanel.tsx create mode 100644 openless-all/app/android/frontend/lib/androidIpc.ts create mode 100644 openless-all/app/android/frontend/lib/androidMicrophonePermission.ts create mode 100644 openless-all/app/android/frontend/lib/androidTypes.ts create mode 100644 openless-all/app/android/kotlin/MicrophonePermissionActivity.kt create mode 100644 openless-all/app/android/kotlin/OpenLessAccessibilityCommandReceiver.kt create mode 100644 openless-all/app/android/kotlin/OpenLessAccessibilityService.kt create mode 100644 openless-all/app/android/kotlin/OpenLessAndroidPreferences.kt create mode 100644 openless-all/app/android/kotlin/OpenLessAppContext.kt create mode 100644 openless-all/app/android/kotlin/OpenLessApplication.kt create mode 100644 openless-all/app/android/kotlin/OpenLessNative.kt create mode 100644 openless-all/app/android/kotlin/OpenLessOverlayBridge.kt create mode 100644 openless-all/app/android/kotlin/OpenLessOverlayService.kt create mode 100644 openless-all/app/android/kotlin/OpenLessPermissionBridge.kt create mode 100644 openless-all/app/android/kotlin/OverlayPermissionActivity.kt create mode 100644 openless-all/app/android/kotlin/README.md create mode 100644 openless-all/app/android/manifests/AndroidManifest.v1.snippet.xml create mode 100644 openless-all/app/android/manifests/AndroidManifest.v3.snippet.xml create mode 100644 openless-all/app/android/manifests/res/xml/openless_accessibility_config.xml create mode 100644 openless-all/app/android/manifests/res/xml/openless_ime_method.xml create mode 100644 openless-all/app/scripts/copy-android-scaffolding.mjs create mode 100644 openless-all/app/scripts/merge-android-overlay-manifest.mjs create mode 100644 openless-all/app/scripts/merge-android-v1-manifest.mjs create mode 100644 openless-all/app/src-tauri/capabilities/mobile.json create mode 100644 openless-all/app/src-tauri/icons/android/drawable/ic_overlay_logo.xml create mode 100644 openless-all/app/src-tauri/src/android/accessibility.rs create mode 100644 openless-all/app/src-tauri/src/android/insert.rs create mode 100644 openless-all/app/src-tauri/src/android/jni.rs create mode 100644 openless-all/app/src-tauri/src/android/mod.rs create mode 100644 openless-all/app/src-tauri/src/android/native_bridge.rs create mode 100644 openless-all/app/src-tauri/src/android/overlay.rs create mode 100644 openless-all/app/src-tauri/src/android/types.rs create mode 100644 openless-all/app/src-tauri/src/external_url.rs create mode 100644 openless-all/app/src-tauri/src/mobile_runtime.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/combo_hotkey.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/hotkey.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/qa_hotkey.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/selection.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/shortcut_binding.rs create mode 100644 openless-all/app/src-tauri/src/mobile_stubs/unicode_keystroke.rs create mode 100644 openless-all/app/src-tauri/tauri.android.conf.json create mode 100644 openless-all/app/src/lib/androidMicrophonePermission.ts create mode 100644 openless-all/app/src/lib/platform.ts create mode 100644 openless-all/app/src/lib/useMobileLayout.ts diff --git a/.github/workflows/android-apk.yml b/.github/workflows/android-apk.yml index 285c1bde..a4156250 100644 --- a/.github/workflows/android-apk.yml +++ b/.github/workflows/android-apk.yml @@ -4,8 +4,7 @@ name: Android APK (debug) # - push v*-tauri tag → build debug APK, upload artifact + attach to GitHub Release # - workflow_dispatch → build debug APK, upload artifact only (no release) # -# v1 scope: in-app dictation APK with RECORD_AUDIO merged from android-scaffolding. -# IME v2 / overlay v3 manifest snippets are intentionally out of scope. +# Scope: full overlay/accessibility APK for ADB testing (v1 RECORD_AUDIO + overlay manifest merge). on: push: @@ -33,6 +32,8 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 + with: + packages: platform-tools - name: Install NDK and accept SDK licenses shell: bash @@ -88,10 +89,18 @@ jobs: working-directory: openless-all/app run: npm run tauri -- android init --ci - - name: Merge APK v1 manifest (RECORD_AUDIO) + - name: Copy Android scaffolding (Kotlin + XML) + working-directory: openless-all/app + run: node scripts/copy-android-scaffolding.mjs + + - name: Merge APK v1 manifest permissions working-directory: openless-all/app run: node scripts/merge-android-v1-manifest.mjs + - name: Merge overlay / accessibility manifest + working-directory: openless-all/app + run: node scripts/merge-android-overlay-manifest.mjs + - name: Prime Gradle wrapper working-directory: openless-all/app shell: bash @@ -111,33 +120,114 @@ jobs: working-directory: openless-all/app run: npm run tauri:android:build - - name: Collect debug APK + - name: Free disk before artifact upload + shell: bash + working-directory: openless-all/app + run: | + set -euo pipefail + rm -rf src-tauri/target + rm -rf ~/.cargo/registry ~/.cargo/git ~/.gradle/caches + df -h + + - name: Collect split debug APKs id: apk shell: bash working-directory: openless-all/app run: | set -euo pipefail - apk_path="$(find src-tauri/gen/android -type f -name '*.apk' -path '*/outputs/*' | sort | head -n 1)" - if [ -z "$apk_path" ]; then - echo "::error::No APK found under src-tauri/gen/android/**/outputs/" - find src-tauri/gen/android -type f -name '*.apk' 2>/dev/null || true - exit 1 - fi if [[ "${{ github.ref }}" == refs/tags/v* ]] && [[ "${{ github.ref_name }}" == *-tauri ]]; then label="${{ github.ref_name }}" else label="run-${{ github.run_number }}" fi - dest="$RUNNER_TEMP/OpenLess-android-debug-${label}.apk" - cp "$apk_path" "$dest" - echo "path=$dest" >> "$GITHUB_OUTPUT" - echo "Collected APK: $apk_path → $dest" + export OPENLESS_APK_LABEL="$label" + python - <<'PY' + import os + import shutil + import sys + import zipfile + from pathlib import Path + + expected = { + "arm64-v8a": "arm64_v8a", + "armeabi-v7a": "armeabi_v7a", + "x86": "x86", + "x86_64": "x86_64", + } + root = Path("src-tauri/gen/android") + label = os.environ["OPENLESS_APK_LABEL"] + out_dir = Path(os.environ["RUNNER_TEMP"]) / "openless-android-debug-split" + out_dir.mkdir(parents=True, exist_ok=True) + found = {} + candidates = [ + apk for apk in sorted(root.rglob("*.apk")) + if "outputs" in apk.parts + ] + if not candidates: + print("::error::No APK found under src-tauri/gen/android/**/outputs/") + for apk in sorted(root.rglob("*.apk")): + print(apk) + sys.exit(1) + for apk in candidates: + with zipfile.ZipFile(apk) as archive: + abis = sorted({ + name.split("/")[1] + for name in archive.namelist() + if name.startswith("lib/") and len(name.split("/")) >= 3 + }) + if len(abis) != 1: + print(f"::error::{apk} contains ABI directories {abis}; expected exactly one ABI per APK") + sys.exit(1) + abi = abis[0] + if abi not in expected: + print(f"::error::{apk} contains unexpected ABI {abi}") + sys.exit(1) + if abi in found: + print(f"::error::Duplicate APKs for ABI {abi}: {found[abi]} and {apk}") + sys.exit(1) + dest = out_dir / f"OpenLess-android-debug-{abi}-{label}.apk" + shutil.copy2(apk, dest) + found[abi] = dest + print(f"Collected {abi}: {apk} -> {dest}") + missing = sorted(set(expected) - set(found)) + if missing: + print(f"::error::Missing split APKs for ABI(s): {', '.join(missing)}") + sys.exit(1) + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + for abi, key in expected.items(): + output.write(f"{key}_path={found[abi]}\n") + output.write("release_files<_x64-setup.exe` — run the installer. +- **Android debug APK**: choose exactly one split APK for your device: + - `OpenLess-android-debug-arm64-v8a-*.apk` for most modern Android phones. + - `OpenLess-android-debug-armeabi-v7a-*.apk` for older 32-bit ARM phones. + - `OpenLess-android-debug-x86_64-*.apk` for most 64-bit Android emulator images. + - `OpenLess-android-debug-x86-*.apk` for older 32-bit x86 emulator images. + - If unsure, run `adb shell getprop ro.product.cpu.abi` and download the APK matching that ABI. Do not install the x86 builds on a normal ARM phone. - **macOS (Homebrew)**: ```bash brew tap appergb/openless https://github.com/appergb/openless diff --git a/docs/android-mobile-apk-overlay-plan.md b/docs/android-mobile-apk-overlay-plan.md new file mode 100644 index 00000000..28fcfd9b --- /dev/null +++ b/docs/android-mobile-apk-overlay-plan.md @@ -0,0 +1,315 @@ +# OpenLess Android APK 与悬浮窗实施计划 + +> 状态:实施中 / 后端分层已落地 +> 日期:2026-06-07 +> 范围:Android APK v1(应用内录音)→ IME v2(跨 App 输入)→ 悬浮窗 v3;不改桌面语义 + +目标是把现有 OpenLess 桌面应用扩展为 Android APK,并在手机端提供可用的语音输入体验。当前项目是 React/Vite + Tauri v2 + Rust 后端,Tauri 官方支持 Android 构建;现仓库已增加 Android 分层与脚手架,桌面核心能力(全局热键、托盘、桌面浮窗、TSF IME 等)通过 `#[cfg(not(mobile))]` 收口。 + +核心策略:先产出可安装 APK,再做 Android 输入法服务,最后做跨 App 悬浮窗。桌面功能保持不受影响。 + +参考依据: +- Tauri Android 依赖:Android Studio、SDK/NDK、`ANDROID_HOME`、`NDK_HOME`、Android Rust targets。 +- Tauri Android 构建命令:`tauri android init`,`tauri android build --apk`。 +- Android 悬浮窗:Android 8.0+ 使用 `SYSTEM_ALERT_WINDOW` + `TYPE_APPLICATION_OVERLAY`。 + +--- + +## 1. 目标与非目标 + +### 目标 + +- **APK v1**:应用内主窗口、设置、历史、云端 ASR/LLM、基础录音、复制结果 +- **IME v2**:`OpenLessImeService` 作为跨 App 输入主路径 +- **悬浮窗 v3**:前台服务 + `TYPE_APPLICATION_OVERLAY`,仅作录音控制入口 +- **平台能力查询**:前端通过 `get_platform_capabilities` 隐藏桌面专属设置项 +- **桌面零破坏**:所有适配经 `#[cfg(not(mobile))]` / `#[cfg(mobile)]` 分层 + +### 非目标 + +- APK 首版不纳入本地 ASR(Foundry、Sherpa、Qwen 桌面假设) +- 首版不承诺直接写入其他 App(无 IME 时走复制兜底) +- Accessibility 跨 App 输入不作为默认路径 +- 悬浮窗不承担最终文本插入职责 +- 不改桌面现有功能语义 + +### 明确边界 + +- **不动** macOS / Windows / Linux 热键、托盘、胶囊、QA 浮窗逻辑 +- **Android 命令名与桌面一致**;不支持的命令返回明确 unavailable 状态 +- **Coordinator 听写主链路复用**;`end_session` 在 Android 增加 IME commit 分支 + +--- + +## 2. 架构定位 + +``` +openless-all/app/ + package.json # tauri:android:* scripts + vite.config.ts # TAURI_ENV_PLATFORM → 0.0.0.0 + HMR + src-tauri/ + tauri.conf.json # bundle.android.minSdkVersion ≥ 26 + tauri.android.conf.json # 单 main 窗口(无 capsule/qa/tray) + capabilities/ + default.json # 桌面 + mobile.json # Android 主窗口 + android-scaffolding/ # Kotlin 模板(init 后复制到 gen/android) + src/ + lib.rs # desktop/mobile run() 分层 + android_ime.rs # IME 状态 + commit(JNI 桩) + android_overlay.rs # 悬浮窗权限/状态(JNI 桩) + permissions.rs # Android 麦克风 runtime permission 分支 + persistence.rs # Android 凭据加密文件桩 + types.rs # PlatformCapabilities + commands.rs # get_platform_capabilities + 桌面命令 stub + coordinator/dictation.rs # end_session Android IME 分支 +``` + +平台分层示意: + +``` +┌─────────────────────────────────────────────────────────┐ +│ React UI(能力查询 → 隐藏桌面专属设置) │ +├─────────────────────────────────────────────────────────┤ +│ Tauri commands(同名 IPC;mobile 返回 unavailable stub) │ +├──────────────┬──────────────────────────────────────────┤ +│ desktop │ mobile (Android) │ +│ hotkey/tray │ in-app dictation + cloud ASR │ +│ capsule/qa │ android_ime (v2) / android_overlay(v3) │ +│ TSF/AX/粘贴 │ IME commit (v1); clipboard TBD │ +└──────────────┴──────────────────────────────────────────┘ +``` + +> **v1 剪贴板**:APK v1 不使用 Android 剪贴板兜底(未接 arboard);跨 App 文本输入依赖后续 IME/JNI 接线。 + +--- + +## 3. 模块设计 + +### 3.1 `PlatformCapabilities`(`types.rs`) + +```rust +pub struct PlatformCapabilities { + pub platform: String, + pub supports_ime_input: bool, + pub supports_overlay: bool, + pub supports_desktop_hotkey: bool, + pub supports_tray: bool, + pub supports_local_asr: bool, + pub supports_capsule_overlay: bool, +} +``` + +- Android:`supportsImeInput=true`(v2 起)、`supportsOverlay=true`(v3 起)、`supportsDesktopHotkey=false`、`supportsTray=false` +- 桌面:按 OS 填真实能力 + +### 3.2 `android_ime.rs` + +- `get_android_ime_status()` → 是否已启用 OpenLess 输入法 +- `commit_text(text)` → 通过 JNI 提交到 `InputConnection`(桩 → 日志 + 返回未连接) +- Kotlin:`OpenLessImeService` 继承 `InputMethodService` + +### 3.3 `android_overlay.rs` + +- `get_android_overlay_status()` → `SYSTEM_ALERT_WINDOW` 授权状态 +- `request_android_overlay_permission()` → 跳转 `OverlayPermissionActivity` +- Kotlin:`OpenLessOverlayService`(前台服务 + overlay window) + +### 3.4 `permissions.rs` / `persistence.rs` + +- 麦克风:Android runtime permission 分支(JNI 桩;未接线时 `NotDetermined`) +- 凭据:Android 加密 JSON 文件桩(`credentials.enc.json`),不沿用桌面 keyring + +### 3.5 `coordinator/dictation.rs` + +`end_session` 插入阶段: + +``` +#[cfg(target_os = "android")] + android_ime::commit_text → Inserted + 失败 → copy_fallback → CopiedFallback +#[cfg(not(mobile))] + 现有 Windows TSF / AX / paste 路径 +``` + +--- + +## 4. 原生层接口 + +| 组件 | 职责 | +|---|---| +| `MainActivity` | Tauri WebView + 权限状态桥接 | +| `OpenLessImeService` | 接收识别结果,`commitText` 到当前输入框 | +| `OpenLessOverlayService` | 悬浮窗开始/停止录音、显示状态 | +| `OverlayPermissionActivity` | 引导用户授权 `SYSTEM_ALERT_WINDOW` | + +Rust ↔ Kotlin 通信:Tauri mobile plugin / `jni`(脚手架阶段为桩,init 后接线)。 + +--- + +## 5. 实施里程碑 + +### M0 环境与文档(本期) + +- 扩展本计划文档(元数据、架构、文件表、风险) +- `package.json`:`tauri:android:init|dev|build` +- `vite.config.ts`:mobile dev host `0.0.0.0` + HMR +- `tauri.conf.json`:`bundle.android.minSdkVersion: 26` +- `tauri.android.conf.json` + `capabilities/mobile.json` + +### M1 Rust 分层 + APK 骨架(本期) + +- `lib.rs` desktop/mobile `run()` 分层 +- `Cargo.toml` gate 桌面专属依赖 +- `PlatformCapabilities` + 命令 stub +- `permissions.rs` / `persistence.rs` Android 分支 +- `cargo check` 桌面通过;Android target 尽力验证 + +### M2 IME v2 + +- 接线 `OpenLessImeService` JNI +- 设置页显示输入法启用状态 +- 跨 App 提交验收 + +### M3 悬浮窗 v3 + +- 接线 `OpenLessOverlayService` +- 授权引导 + 前台服务稳定性 + +--- + +## 6. 文件触达表 + +| 文件 | 变更 | +|---|---| +| `docs/android-mobile-apk-overlay-plan.md` | 扩展为完整实施规划(本文档) | +| `openless-all/app/package.json` | `tauri:android:*` scripts | +| `.github/workflows/android-apk.yml` | Android debug APK CI | +| `openless-all/app/scripts/merge-android-v1-manifest.mjs` | v1 manifest merge (RECORD_AUDIO) | +| `openless-all/app/vite.config.ts` | mobile dev server / HMR | +| `openless-all/app/src-tauri/tauri.conf.json` | `bundle.android` | +| `openless-all/app/src-tauri/tauri.android.conf.json` | 单 main 窗口 | +| `openless-all/app/src-tauri/capabilities/mobile.json` | Android 权限集 | +| `openless-all/app/src-tauri/Cargo.toml` | gate desktop deps | +| `openless-all/app/src-tauri/src/lib.rs` | mobile/desktop 分层 | +| `openless-all/app/src-tauri/src/types.rs` | `PlatformCapabilities` 等 | +| `openless-all/app/src-tauri/src/commands.rs` | 能力查询 + mobile stub | +| `openless-all/app/src-tauri/src/permissions.rs` | Android 麦克风 | +| `openless-all/app/src-tauri/src/persistence.rs` | Android 凭据 | +| `openless-all/app/src-tauri/src/android_ime.rs` | 新增 | +| `openless-all/app/src-tauri/src/android_overlay.rs` | 新增 | +| `openless-all/app/src-tauri/src/coordinator/dictation.rs` | `end_session` Android 分支 | +| `openless-all/app/src-tauri/android-scaffolding/*.kt` | Kotlin 模板 | + +--- + +## 7. 风险与对策 + +| 风险 | 对策 | +|---|---| +| 本机无 Android SDK / NDK | 文档记录手动脚手架;`android-scaffolding/` 供 init 后复制 | +| `global-hotkey` / `enigo` / `arboard` 无法编 Android | `Cargo.toml` `cfg(not(mobile))` gate | +| `keyring` 在 Android 不可用 | 加密文件桩 + 后续 Keystore 接线 | +| 桌面构建被 mobile 分层破坏 | 桌面 `cargo check` 为 CI 门禁;mobile 代码 `#[cfg(mobile)]` 隔离 | +| JNI 未接线时 IME/overlay 假成功 | 状态查询返回 `enabled=false`;commit 走 copy fallback | +| 多窗口配置污染移动端 | `tauri.android.conf.json` 仅声明 `main` | +| 本地 ASR 拖慢 Android 首版 | 明确排除;`supportsLocalAsr=false` | +| `SYSTEM_ALERT_WINDOW` 用户拒绝 | 设置页显示状态;悬浮窗功能降级为应用内入口 | + +--- + +## Android APK CI Workflow + +GitHub Actions workflow: [`.github/workflows/android-apk.yml`](../.github/workflows/android-apk.yml) + +### Capability platform isolation + +Desktop permissions live in `capabilities/default.json` with `"platforms": ["macOS", "windows", "linux"]`, so updater, autostart, and multi-window permissions do not apply on Android. Android uses `capabilities/mobile.json` with `"platforms": ["android"]` for the main-window permission set only (no updater/autostart). + +### Triggers + +| Trigger | Behavior | +|---|---| +| `workflow_dispatch` | Build debug APK → upload Actions artifact only | +| Push tag `v*-tauri` | Build debug APK → upload artifact **and** attach APK to the existing GitHub Release for that tag | + +Tag-triggered runs share the same `v*-tauri` convention as [`.github/workflows/release-tauri.yml`](../.github/workflows/release-tauri.yml). The Android job is independent and does not modify the desktop release workflow. + +### Debug APK policy + +- CI builds **debug** APKs (`tauri android build --apk --debug`) for faster iteration and to avoid release-signing requirements in v1. +- Actions artifact name: `openless-android-debug`. +- On-disk APK filename: + - Tag runs (`v*-tauri`): `OpenLess-android-debug-.apk` (e.g. `OpenLess-android-debug-v1.0.0-tauri.apk`) + - Manual dispatch: `OpenLess-android-debug-run-.apk` (not branch name) + +### Command chain (CI) + +```bash +cd openless-all/app +npm ci && npm run build +CI=true npm run tauri -- android init --ci +node scripts/merge-android-v1-manifest.mjs +CI=true npm run tauri:android:build +``` + +Local equivalent (after Android SDK/NDK setup): + +```bash +cd openless-all/app +npm run build +npm run tauri:android:init +npm run merge:android-v1-manifest +npm run tauri:android:build +``` + +### Manifest merge (v1 only) + +`scripts/merge-android-v1-manifest.mjs` merges **only** `RECORD_AUDIO` from `android-scaffolding/AndroidManifest.v1.snippet.xml` into the generated `src-tauri/gen/android/app/src/main/AndroidManifest.xml`. The script is idempotent (skips if permission already present). v2 (IME) and v3 (overlay) manifest snippets are **not** merged in this workflow. + +--- + +## 8. 验收标准 + +### 构建验证 + +```bash +cd openless-all/app +npm run build +cargo check --manifest-path src-tauri/Cargo.toml +# 需 Android SDK / NDK(与 CI 一致:debug APK): +npm run tauri:android:init +npm run merge:android-v1-manifest +npm run tauri:android:build +# 等价于 CI 的 debug 构建: +# CI=true npm run tauri -- android init --ci && node scripts/merge-android-v1-manifest.mjs && CI=true npm run tauri:android:build +``` + +### APK v1 + +- 首次启动进入主界面 +- 麦克风授权流程可触发 +- 应用内录音 → 云端转写 → 历史 + 复制 +- 桌面专属命令不导致前端白屏(返回 unavailable) +- 桌面 `cargo check` 仍通过 + +### IME v2 / 悬浮窗 v3 + +见原 Summary 中 Test Plan 章节(启用输入法后跨 App 提交;悬浮窗授权与前台服务稳定性)。 + +--- + +## Compatibility fixes(2026-06-07) + +- **`app_invoke_handler_mobile`**:仅保留 dictation / settings / credentials / history / cloud ASR / platform capabilities / Android IME·overlay / permissions / marketplace / style packs / mic devices;已移除 `get_hotkey_*`、`set_shortcut_recording_active` 及全部 desktop-only 命令(热键 setter、updater、local ASR、coding agent、tray 等)。前端 `ipc.ts` 在 `supportsDesktopHotkey === false` 时本地返回 stub,不再 invoke 这些命令。 +- **`mobile_stubs`**:`unicode_keystroke` 补齐 `typed_chars()` / `Partial`(与 coordinator 流式插入一致);`shortcut_binding::binding_from_legacy_trigger` 与桌面实现对齐。 +- **`Cargo.toml`**:`enigo` / `global-hotkey` / updater / single-instance / autostart 仅在 `cfg(not(mobile))`;Android 侧 `jni` + `ndk-context` 已声明。 + +--- + +## 9. 相关参考 + +- Tauri Android:https://v2.tauri.app/develop/mobile/ +- 桌面 Windows ASR 规划风格:`docs/windows-sherpa-onnx-asr-plan.md` +- 主听写链路:`openless-all/app/src-tauri/src/coordinator/dictation.rs` +- Windows IME unavailable 模式:`openless-all/app/src-tauri/src/windows_ime_profile.rs` diff --git a/openless-all/app/android/README.md b/openless-all/app/android/README.md new file mode 100644 index 00000000..30b04abc --- /dev/null +++ b/openless-all/app/android/README.md @@ -0,0 +1,79 @@ +# OpenLess Android 平台代码 + +Android 相关 Rust、Kotlin 与前端代码的统一入口。桌面端通过 `#[cfg(not(mobile))]` 分层,不受影响。 + +## 目录结构 + +```text +android/ +├── kotlin/ # Kotlin 模板(CI 复制到 gen/android/) +├── manifests/ # AndroidManifest snippet + res/xml +└── frontend/ # React 模块(Vite 别名 @android) + +src-tauri/src/android/ # Rust 运行时模块(crate::android) +``` + +## Rust(`src-tauri/src/android/`) + +| 模块 | 职责 | +|------|------| +| `jni.rs` | JNI 工具(clipboard、overlay service、accessibility) | +| `native_bridge.rs` | Kotlin ↔ Coordinator JNI 入口 | +| `overlay.rs` | 悬浮窗权限与 show/hide | +| `accessibility.rs` | 无障碍服务状态与 paste | +| `insert.rs` | 跨 App 文本插入策略 | +| `types.rs` | Android 偏好与状态类型 | + +主 crate 通过 `mod android;` 引入,常用 API 经 `crate::android::` 扁平 re-export。 + +## Kotlin(`android/kotlin/`) + +`tauri android init` 后由 [`scripts/copy-android-scaffolding.mjs`](../scripts/copy-android-scaffolding.mjs) 复制到 `src-tauri/gen/android/app/src/main/java/com/openless/app/`。 + +Manifest 合并脚本: + +- [`scripts/merge-android-v1-manifest.mjs`](../scripts/merge-android-v1-manifest.mjs) — 麦克风权限(`android/manifests/AndroidManifest.v1.snippet.xml`) +- [`scripts/merge-android-overlay-manifest.mjs`](../scripts/merge-android-overlay-manifest.mjs) — 悬浮窗 / 无障碍 + +## 前端(`android/frontend/`,别名 `@android`) + +| 路径 | 职责 | +|------|------| +| `lib/androidTypes.ts` | Android 偏好与状态 TS 类型 | +| `lib/androidIpc.ts` | overlay / accessibility Tauri invoke | +| `lib/androidMicrophonePermission.ts` | WebView 麦克风权限辅助 | +| `components/AndroidPermissionsPanel.tsx` | 设置页 Android 权限与 overlay 配置 | + +`src/lib/types.ts` 与 `src/lib/ipc.ts` 保留 re-export,现有 import 路径仍可用。 + +## 构建与 CI + +**CI(overlay / 无障碍 ADB 测试 APK)** — 合并 v1 麦克风权限 + overlay / 无障碍 manifest,用于真机 ADB 测试完整悬浮窗与无障碍能力(非仅应用内听写): + +```bash +cd openless-all/app +npm ci && npm run build +CI=true npm run tauri -- android init --ci +node scripts/copy-android-scaffolding.mjs +node scripts/merge-android-v1-manifest.mjs +node scripts/merge-android-overlay-manifest.mjs +CI=true npm run tauri:android:build +``` + +Workflow: [`.github/workflows/android-apk.yml`](../../.github/workflows/android-apk.yml) + +**本地 overlay / 无障碍开发(v3)** — 与 CI 相同的 manifest 合并链,使用本地 init / copy 脚本: + +```bash +cd openless-all/app +npm run tauri:android:init +npm run copy:android-scaffolding +node scripts/merge-android-v1-manifest.mjs +node scripts/merge-android-overlay-manifest.mjs +npm run tauri:android:build +``` + +## 相关文档 + +- [AGENTS.md](../../AGENTS.md) — 真机闪退排查 +- [docs/android-mobile-apk-overlay-plan.md](../../docs/android-mobile-apk-overlay-plan.md) — 分阶段产品计划 diff --git a/openless-all/app/android/frontend/components/AndroidPermissionsPanel.tsx b/openless-all/app/android/frontend/components/AndroidPermissionsPanel.tsx new file mode 100644 index 00000000..959a5565 --- /dev/null +++ b/openless-all/app/android/frontend/components/AndroidPermissionsPanel.tsx @@ -0,0 +1,386 @@ +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Icon } from '../../../src/components/Icon'; +import { getSettings, setSettings } from '../../../src/lib/ipc'; +import type { UserPreferences } from '../../../src/lib/types'; +import { Btn, Pill } from '../../../src/pages/_atoms'; +import { SettingRow } from '../../../src/pages/settings/shared'; +import { + getAndroidAccessibilityStatus, + getAndroidOverlayStatus, + requestAndroidAccessibilityPermission, + requestAndroidOverlayPermission, +} from '../lib/androidIpc'; +import type { + AndroidAccessibilityStatus, + AndroidInsertStrategy, + AndroidOverlayActivationMode, + AndroidOverlayCancelSwipeDirection, + AndroidOverlayLeftSwipeAction, + AndroidOverlayStatus, + AndroidOverlayTrigger, + AndroidPreferenceKey, +} from '../lib/androidTypes'; +import { + clampAndroidOverlaySize, + normalizeAndroidOverlayTrigger, +} from '../lib/androidTypes'; + +type AndroidPrefsSlice = Pick; + +function pickAndroidPrefs(settings: UserPreferences): AndroidPrefsSlice { + return { + androidInsertStrategy: settings.androidInsertStrategy, + androidOverlayTrigger: settings.androidOverlayTrigger, + androidOverlayActivationMode: settings.androidOverlayActivationMode, + androidOverlayLeftSwipeAction: settings.androidOverlayLeftSwipeAction, + androidOverlayCancelSwipeDirection: settings.androidOverlayCancelSwipeDirection, + androidOverlaySizeDp: settings.androidOverlaySizeDp, + }; +} + +let androidOverlaySaveQueue = Promise.resolve(); + +function enqueueAndroidOverlaySave(task: () => Promise): Promise { + const run = androidOverlaySaveQueue.then(task); + androidOverlaySaveQueue = run.then(() => undefined, () => undefined); + return run; +} + +function persistAndroidOverlayPrefs(patch: Partial): Promise { + return enqueueAndroidOverlaySave(async () => { + const settings = await getSettings(); + const next = { + ...settings, + ...patch, + }; + await setSettings(next); + return pickAndroidPrefs(next); + }); +} + +type AndroidPermissionsPanelMode = 'all' | 'accessibility' | 'overlayPermission' | 'overlayConfig'; + +interface AndroidPermissionsPanelProps { + mode?: AndroidPermissionsPanelMode; +} + +export function AndroidPermissionsPanel({ mode = 'all' }: AndroidPermissionsPanelProps) { + const { t } = useTranslation(); + const [androidOverlay, setAndroidOverlay] = useState(null); + const [androidAccessibility, setAndroidAccessibility] = useState(null); + const [androidPrefs, setAndroidPrefs] = useState | null>(null); + const [sizeDraft, setSizeDraft] = useState(null); + const sizeDebounceRef = useRef(null); + const sizePendingRef = useRef(false); + + const refreshAndroid = async () => { + const [overlay, accessibility, settings] = await Promise.all([ + getAndroidOverlayStatus(), + getAndroidAccessibilityStatus(), + getSettings(), + ]); + let migratedSettings = settings; + if (settings.androidOverlayTrigger === 'keyboard') { + const migratedPrefs = await persistAndroidOverlayPrefs({ + androidOverlayTrigger: normalizeAndroidOverlayTrigger(settings.androidOverlayTrigger), + }); + migratedSettings = { ...settings, ...migratedPrefs }; + } + setAndroidOverlay(overlay); + setAndroidAccessibility(accessibility); + setAndroidPrefs(pickAndroidPrefs(migratedSettings)); + }; + + useEffect(() => { + void refreshAndroid(); + const androidId = window.setInterval(refreshAndroid, 3000); + const onFocus = () => { void refreshAndroid(); }; + window.addEventListener('focus', onFocus); + return () => { + window.clearInterval(androidId); + window.removeEventListener('focus', onFocus); + }; + }, []); + + useEffect(() => { + if (sizePendingRef.current) return; + if (androidPrefs?.androidOverlaySizeDp != null) { + setSizeDraft(androidPrefs.androidOverlaySizeDp); + } + }, [androidPrefs?.androidOverlaySizeDp]); + + useEffect(() => { + return () => { + if (sizeDebounceRef.current) clearTimeout(sizeDebounceRef.current); + }; + }, []); + + const saveAndroidOverlaySize = async (value: number) => { + const clamped = clampAndroidOverlaySize(value); + try { + const nextPrefs = await persistAndroidOverlayPrefs({ androidOverlaySizeDp: clamped }); + setAndroidPrefs((prev) => (prev ? { ...prev, ...nextPrefs } : nextPrefs)); + setSizeDraft(clamped); + } catch (error) { + console.error('[android] failed to save overlay size', error); + const settings = await getSettings(); + const rolledBack = settings.androidOverlaySizeDp; + const safeDraft = rolledBack != null ? clampAndroidOverlaySize(rolledBack) : null; + setAndroidPrefs((prev) => (prev ? { ...prev, androidOverlaySizeDp: rolledBack } : null)); + setSizeDraft(safeDraft); + } finally { + sizePendingRef.current = false; + } + }; + + const scheduleAndroidOverlaySizeSave = (value: number) => { + if (sizeDebounceRef.current) clearTimeout(sizeDebounceRef.current); + sizeDebounceRef.current = window.setTimeout(() => { + sizeDebounceRef.current = null; + void saveAndroidOverlaySize(value); + }, 200); + }; + + const flushAndroidOverlaySizeSave = (value: number) => { + const hasDebounce = sizeDebounceRef.current != null; + const hasPending = sizePendingRef.current; + if (!hasDebounce && !hasPending) return; + + if (sizeDebounceRef.current) { + clearTimeout(sizeDebounceRef.current); + sizeDebounceRef.current = null; + } + + const clamped = clampAndroidOverlaySize(value); + const committed = androidPrefs?.androidOverlaySizeDp; + if (committed != null && clamped === committed) { + sizePendingRef.current = false; + return; + } + + void saveAndroidOverlaySize(clamped); + }; + + const handleAndroidOverlaySizeChange = (value: number) => { + const clamped = clampAndroidOverlaySize(value); + sizePendingRef.current = true; + setSizeDraft(clamped); + scheduleAndroidOverlaySizeSave(clamped); + }; + + const updateAndroidPref = async (key: K, value: UserPreferences[K]) => { + if (key !== 'androidOverlaySizeDp' && sizeDebounceRef.current) { + clearTimeout(sizeDebounceRef.current); + sizeDebounceRef.current = null; + } + + const patch: Partial = { + [key]: key === 'androidOverlayTrigger' + ? normalizeAndroidOverlayTrigger(value as AndroidOverlayTrigger) + : value, + }; + + if (key !== 'androidOverlaySizeDp') { + const committedSize = androidPrefs?.androidOverlaySizeDp; + const draftSize = sizeDraft; + if (sizePendingRef.current || (draftSize != null && draftSize !== committedSize)) { + patch.androidOverlaySizeDp = clampAndroidOverlaySize(draftSize ?? committedSize ?? 72); + } + sizePendingRef.current = false; + } + + try { + const nextPrefs = await persistAndroidOverlayPrefs(patch); + setAndroidPrefs(nextPrefs); + if (patch.androidOverlaySizeDp != null) { + setSizeDraft(nextPrefs.androidOverlaySizeDp); + } + await refreshAndroid(); + } catch (error) { + console.error('[android] failed to save overlay pref', error); + await refreshAndroid(); + } + }; + + const showOverlayPermission = mode === 'all' || mode === 'overlayPermission'; + const showAccessibility = mode === 'all' || mode === 'accessibility'; + const showOverlayConfig = mode === 'all' || mode === 'overlayConfig'; + + return ( + <> + {showOverlayPermission && ( + +
+ {androidOverlay?.message && ( + + {androidOverlay.message} + + )} + + {androidOverlay?.permission !== 'granted' && ( + { void requestAndroidOverlayPermission().then(refreshAndroid); }}> + {t('settings.permissions.grant')} + + )} +
+
+ )} + {showAccessibility && ( + +
+
+ {androidAccessibility?.message && ( + + {androidAccessibility.message} + + )} + + {!androidAccessibility?.enabled && ( + { void requestAndroidAccessibilityPermission().then(refreshAndroid); }}> + {t('settings.permissions.openSystem')} + + )} +
+ + {t('settings.permissions.androidAccessibilityImpact')} + +
+
+ )} + {showOverlayConfig && ( + <> + +
+ + + {t(`settings.permissions.androidInsertStrategyHint.${androidPrefs?.androidInsertStrategy ?? 'accessibility'}`)} + +
+
+ +
+ + + {t(`settings.permissions.androidOverlayTriggerHint.${androidPrefs?.androidOverlayTrigger ?? 'background'}`)} + + + {t('settings.permissions.androidOverlayTriggerDisabled.keyboard')} + +
+
+ +
+ + + {t(`settings.permissions.androidOverlayActivationModeHint.${androidPrefs?.androidOverlayActivationMode ?? 'tap'}`)} + +
+
+ +
+ + + {t(`settings.permissions.androidOverlayLeftSwipeActionHint.${androidPrefs?.androidOverlayLeftSwipeAction ?? 'translation'}`)} + +
+
+ +
+ + + {t(`settings.permissions.androidOverlayCancelSwipeDirectionHint.${androidPrefs?.androidOverlayCancelSwipeDirection ?? 'up'}`)} + +
+
+ +
+
+ { + handleAndroidOverlaySizeChange(Number(event.target.value)); + }} + onPointerUp={(event) => { + flushAndroidOverlaySizeSave(Number(event.currentTarget.value)); + }} + onTouchEnd={(event) => { + flushAndroidOverlaySizeSave(Number(event.currentTarget.value)); + }} + onBlur={(event) => { + flushAndroidOverlaySizeSave(Number(event.currentTarget.value)); + }} + style={{ width: 132 }} + /> + + {sizeDraft ?? androidPrefs?.androidOverlaySizeDp ?? 72} dp + +
+ + {t('settings.permissions.androidOverlaySizeHint')} + +
+
+ + )} + + ); +} + +function AndroidOverlayStatusPill({ status }: { status: AndroidOverlayStatus | null }) { + const { t } = useTranslation(); + if (!status) return {t('settings.permissions.checking')}; + if (status.permission === 'granted') { + return {t('settings.permissions.granted')}; + } + return {t('settings.permissions.denied')}; +} + +function AndroidAccessibilityStatusPill({ status }: { status: AndroidAccessibilityStatus | null }) { + const { t } = useTranslation(); + if (!status) return {t('settings.permissions.checking')}; + if (status.enabled) { + return {t('settings.permissions.granted')}; + } + return {t('settings.permissions.denied')}; +} diff --git a/openless-all/app/android/frontend/lib/androidIpc.ts b/openless-all/app/android/frontend/lib/androidIpc.ts new file mode 100644 index 00000000..062407ab --- /dev/null +++ b/openless-all/app/android/frontend/lib/androidIpc.ts @@ -0,0 +1,43 @@ +import { invokeOrMock } from '../../../src/lib/ipc'; +import type { + AndroidAccessibilityStatus, + AndroidOverlayStatus, +} from './androidTypes'; + +export function getAndroidOverlayStatus(): Promise { + return invokeOrMock('get_android_overlay_status', undefined, () => ({ + permission: 'notAndroid', + overlayVisible: false, + message: 'Android overlay is only available on Android', + })); +} + +export function requestAndroidOverlayPermission(): Promise<{ launched: boolean; message: string }> { + return invokeOrMock('request_android_overlay_permission', undefined, () => ({ + launched: false, + message: 'Mock: overlay permission unavailable in browser preview', + })); +} + +export function showAndroidOverlay(): Promise { + return invokeOrMock('show_android_overlay', undefined, () => undefined); +} + +export function hideAndroidOverlay(): Promise { + return invokeOrMock('hide_android_overlay', undefined, () => undefined); +} + +export function getAndroidAccessibilityStatus(): Promise { + return invokeOrMock('get_android_accessibility_status', undefined, () => ({ + state: 'notAndroid', + enabled: false, + message: 'Android accessibility is only available on Android', + })); +} + +export function requestAndroidAccessibilityPermission(): Promise<{ launched: boolean; message: string }> { + return invokeOrMock('request_android_accessibility_permission', undefined, () => ({ + launched: false, + message: 'Mock: accessibility settings unavailable in browser preview', + })); +} diff --git a/openless-all/app/android/frontend/lib/androidMicrophonePermission.ts b/openless-all/app/android/frontend/lib/androidMicrophonePermission.ts new file mode 100644 index 00000000..d5fee524 --- /dev/null +++ b/openless-all/app/android/frontend/lib/androidMicrophonePermission.ts @@ -0,0 +1,104 @@ +import type { PermissionStatus as AppPermissionStatus } from '../../../src/lib/types'; +import { checkMicrophonePermission, requestMicrophonePermission } from '../../../src/lib/ipc'; + +const ANDROID_MIC_GRANTED_KEY = 'openless.androidMicrophoneGranted'; +const ANDROID_MIC_REQUESTED_KEY = 'openless.androidMicrophoneRequested'; + +export async function checkAndroidMicrophoneAccess(): Promise { + try { + const nativeStatus = await checkMicrophonePermission(); + if (nativeStatus === 'granted' || nativeStatus === 'notApplicable') { + localStorage.setItem(ANDROID_MIC_GRANTED_KEY, '1'); + return 'granted'; + } + if (nativeStatus === 'denied' || nativeStatus === 'restricted') { + localStorage.removeItem(ANDROID_MIC_GRANTED_KEY); + return localStorage.getItem(ANDROID_MIC_REQUESTED_KEY) === '1' ? 'denied' : 'notDetermined'; + } + } catch { + // Fall through to WebView-local checks below. + } + + if (localStorage.getItem(ANDROID_MIC_GRANTED_KEY) === '1') { + return 'granted'; + } + + try { + const permissions = navigator.permissions; + if (permissions?.query) { + const status = await permissions.query({ name: 'microphone' as PermissionName }); + if (status.state === 'granted') return 'granted'; + if (status.state === 'denied') { + localStorage.removeItem(ANDROID_MIC_GRANTED_KEY); + return 'denied'; + } + } + } catch { + // Android WebView versions differ on navigator.permissions support. + } + + return 'notDetermined'; +} + +export async function requestAndroidMicrophoneAccess(): Promise { + try { + localStorage.setItem(ANDROID_MIC_REQUESTED_KEY, '1'); + const nativeStatus = await requestMicrophonePermission(); + if (nativeStatus === 'granted' || nativeStatus === 'notApplicable') { + localStorage.setItem(ANDROID_MIC_GRANTED_KEY, '1'); + return 'granted'; + } + if (nativeStatus === 'denied' || nativeStatus === 'restricted') { + localStorage.removeItem(ANDROID_MIC_GRANTED_KEY); + } + if (nativeStatus === 'notDetermined') { + return 'notDetermined'; + } + } catch { + // Fall through to WebView-local checks below. + } + + if (localStorage.getItem(ANDROID_MIC_GRANTED_KEY) === '1') { + return 'granted'; + } + + try { + const permissions = navigator.permissions; + if (permissions?.query) { + const status = await permissions.query({ name: 'microphone' as PermissionName }); + if (status.state === 'granted') { + localStorage.setItem(ANDROID_MIC_GRANTED_KEY, '1'); + return 'granted'; + } + if (status.state === 'denied') { + localStorage.removeItem(ANDROID_MIC_GRANTED_KEY); + localStorage.setItem(ANDROID_MIC_REQUESTED_KEY, '1'); + return 'denied'; + } + } + } catch { + // Android WebView versions differ on navigator.permissions support. + } + + const mediaDevices = navigator.mediaDevices; + if (!mediaDevices?.getUserMedia) { + return 'notDetermined'; + } + + let stream: MediaStream | null = null; + try { + localStorage.setItem(ANDROID_MIC_REQUESTED_KEY, '1'); + stream = await mediaDevices.getUserMedia({ audio: true }); + localStorage.setItem(ANDROID_MIC_GRANTED_KEY, '1'); + return 'granted'; + } catch (error) { + console.warn('[android-mic] WebView microphone permission request failed', error); + localStorage.removeItem(ANDROID_MIC_GRANTED_KEY); + if (error instanceof DOMException && error.name === 'NotAllowedError') { + return 'denied'; + } + return 'notDetermined'; + } finally { + stream?.getTracks().forEach(track => track.stop()); + } +} diff --git a/openless-all/app/android/frontend/lib/androidTypes.ts b/openless-all/app/android/frontend/lib/androidTypes.ts new file mode 100644 index 00000000..387d5419 --- /dev/null +++ b/openless-all/app/android/frontend/lib/androidTypes.ts @@ -0,0 +1,38 @@ +/** Android-specific preference and status types (mirrors Rust IPC payloads). */ + +export type AndroidInsertStrategy = 'accessibility' | 'clipboard'; +export type AndroidOverlayTrigger = 'background' | 'keyboard' | 'always'; +export type AndroidOverlayActivationMode = 'tap' | 'long_press'; +export type AndroidOverlayLeftSwipeAction = 'translation' | 'style_pack'; +export type AndroidOverlayCancelSwipeDirection = 'up' | 'down'; + +export interface AndroidOverlayStatus { + permission: 'granted' | 'notGranted' | 'notAndroid'; + overlayVisible: boolean; + message: string; +} + +export interface AndroidAccessibilityStatus { + state: 'enabled' | 'notEnabled' | 'notAndroid'; + enabled: boolean; + message: string; +} + +export type AndroidPreferenceKey = + | 'androidInsertStrategy' + | 'androidOverlayTrigger' + | 'androidOverlayActivationMode' + | 'androidOverlayLeftSwipeAction' + | 'androidOverlayCancelSwipeDirection' + | 'androidOverlaySizeDp'; + +export function normalizeAndroidOverlayTrigger( + trigger: AndroidOverlayTrigger, +): AndroidOverlayTrigger { + return trigger === 'keyboard' ? 'background' : trigger; +} + +export function clampAndroidOverlaySize(size: number): number { + if (!Number.isFinite(size)) return 72; + return Math.min(120, Math.max(48, Math.round(size / 4) * 4)); +} diff --git a/openless-all/app/android/kotlin/MicrophonePermissionActivity.kt b/openless-all/app/android/kotlin/MicrophonePermissionActivity.kt new file mode 100644 index 00000000..746941f2 --- /dev/null +++ b/openless-all/app/android/kotlin/MicrophonePermissionActivity.kt @@ -0,0 +1,49 @@ +package com.openless.app + +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log + +class MicrophonePermissionActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { + OpenLessPermissionBridge.resolveRecordAudioPermission(true) + finish() + return + } + requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQUEST_RECORD_AUDIO) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray, + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode != REQUEST_RECORD_AUDIO) { + return + } + val granted = grantResults.isNotEmpty() && + grantResults[0] == PackageManager.PERMISSION_GRANTED + Log.i(TAG, "RECORD_AUDIO permission result granted=$granted") + OpenLessPermissionBridge.resolveRecordAudioPermission(granted) + finish() + } + + override fun onDestroy() { + if (isFinishing) { + OpenLessPermissionBridge.resolveRecordAudioPermission( + checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED, + ) + } + super.onDestroy() + } + + companion object { + private const val TAG = "OpenLessMicPermission" + private const val REQUEST_RECORD_AUDIO = 9101 + } +} diff --git a/openless-all/app/android/kotlin/OpenLessAccessibilityCommandReceiver.kt b/openless-all/app/android/kotlin/OpenLessAccessibilityCommandReceiver.kt new file mode 100644 index 00000000..a3d69ad4 --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessAccessibilityCommandReceiver.kt @@ -0,0 +1,36 @@ +package com.openless.app + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.ResultReceiver +import android.util.Log + +class OpenLessAccessibilityCommandReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + if (intent?.action != ACTION_PASTE) return + val pasted = OpenLessAccessibilityService.performPasteFromCommand() + resultReceiver(intent)?.send( + if (pasted) RESULT_PASTE_SUCCESS else RESULT_PASTE_FAILED, + Bundle().apply { putBoolean(EXTRA_PASTE_RESULT, pasted) }, + ) + if (!pasted) { + Log.w(TAG, "paste command did not find an editable focused field") + } + } + + @Suppress("DEPRECATION") + private fun resultReceiver(intent: Intent): ResultReceiver? { + return intent.getParcelableExtra(EXTRA_RESULT_RECEIVER) as? ResultReceiver + } + + companion object { + const val ACTION_PASTE = "com.openless.app.accessibility.PASTE" + const val EXTRA_RESULT_RECEIVER = "result_receiver" + const val EXTRA_PASTE_RESULT = "paste_result" + const val RESULT_PASTE_FAILED = 0 + const val RESULT_PASTE_SUCCESS = 1 + private const val TAG = "OpenLessA11yCommand" + } +} diff --git a/openless-all/app/android/kotlin/OpenLessAccessibilityService.kt b/openless-all/app/android/kotlin/OpenLessAccessibilityService.kt new file mode 100644 index 00000000..8a843049 --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessAccessibilityService.kt @@ -0,0 +1,373 @@ +package com.openless.app + +import android.accessibilityservice.AccessibilityService +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.ResultReceiver +import android.provider.Settings +import android.util.Log +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo +import android.view.accessibility.AccessibilityWindowInfo +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Detects IME windows for overlay keyboard trigger mode and performs paste insertion. + */ +class OpenLessAccessibilityService : AccessibilityService() { + private val mainHandler = Handler(Looper.getMainLooper()) + private val heartbeatRunnable = object : Runnable { + override fun run() { + markServiceAlive() + mainHandler.postDelayed(this, HEARTBEAT_INTERVAL_MS) + } + } + private val keyboardRefreshRunnable = Runnable { updateKeyboardOverlayState() } + private var lastEditableFocus: AccessibilityNodeInfo? = null + + override fun onServiceConnected() { + super.onServiceConnected() + instance = this + startHeartbeat() + updateKeyboardOverlayState() + scheduleKeyboardOverlayRefresh() + } + + override fun onAccessibilityEvent(event: AccessibilityEvent?) { + if (event == null) return + markServiceAlive() + when (event.eventType) { + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + AccessibilityEvent.TYPE_WINDOWS_CHANGED, + AccessibilityEvent.TYPE_VIEW_FOCUSED -> { + rememberFocusedEditable(event) + updateKeyboardOverlayState() + scheduleKeyboardOverlayRefresh() + } + } + } + + override fun onInterrupt() = Unit + + override fun onDestroy() { + mainHandler.removeCallbacks(heartbeatRunnable) + mainHandler.removeCallbacks(keyboardRefreshRunnable) + lastEditableFocus?.recycle() + lastEditableFocus = null + if (instance === this) { + instance = null + } + super.onDestroy() + } + + private fun scheduleKeyboardOverlayRefresh() { + mainHandler.removeCallbacks(keyboardRefreshRunnable) + for (delayMs in KEYBOARD_REFRESH_DELAYS_MS) { + mainHandler.postDelayed(keyboardRefreshRunnable, delayMs) + } + } + + private fun startHeartbeat() { + mainHandler.removeCallbacks(heartbeatRunnable) + heartbeatRunnable.run() + } + + private fun updateKeyboardOverlayState() { + if (!shouldTrackKeyboard()) { + return + } + if (!canDrawOverlays()) { + return + } + val imeBounds = findInputMethodBounds() + val intent = Intent(this, OpenLessOverlayService::class.java).apply { + action = OpenLessOverlayService.ACTION_KEYBOARD_CHANGED + putExtra(OpenLessOverlayService.EXTRA_KEYBOARD_VISIBLE, imeBounds != null) + imeBounds?.let { + putExtra(OpenLessOverlayService.EXTRA_KEYBOARD_TOP, it.top) + putExtra(OpenLessOverlayService.EXTRA_KEYBOARD_BOTTOM, it.bottom) + } + } + try { + Log.i(TAG, "keyboard overlay event visible=${imeBounds != null} bounds=$imeBounds") + startService(intent) + } catch (error: Throwable) { + Log.w(TAG, "send keyboard overlay event failed", error) + } + } + + private fun findInputMethodBounds(): Rect? { + for (window in windows) { + if (window.type != AccessibilityWindowInfo.TYPE_INPUT_METHOD) { + continue + } + val bounds = Rect() + window.getBoundsInScreen(bounds) + if (!bounds.isEmpty) { + return bounds + } + } + return null + } + + private fun shouldTrackKeyboard(): Boolean { + return OpenLessAndroidPreferences.isKeyboardOverlayTrigger(this) + } + + private fun canDrawOverlays(): Boolean { + return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Settings.canDrawOverlays(this) + } else { + true + } + } + + private fun performPasteToFocusedField(): Boolean { + val target = findEditableTarget() ?: return false + return try { + target.performAction(AccessibilityNodeInfo.ACTION_FOCUS) + pasteWithRetryOrSetText(target) + } finally { + target.recycle() + } + } + + private fun rememberFocusedEditable(event: AccessibilityEvent) { + val source = event.source ?: return + try { + if (!source.isEditable) return + lastEditableFocus?.recycle() + lastEditableFocus = AccessibilityNodeInfo.obtain(source) + } finally { + source.recycle() + } + } + + private fun findEditableTarget(): AccessibilityNodeInfo? { + lastEditableFocus?.let { cached -> + if (cached.refresh() && cached.isEditable) { + return AccessibilityNodeInfo.obtain(cached) + } + } + val root = rootInActiveWindow ?: return null + editableFocusedNode(root, AccessibilityNodeInfo.FOCUS_INPUT)?.let { return it } + editableFocusedNode(root, AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)?.let { return it } + return findEditableInTree(root, 0) + } + + private fun editableFocusedNode(root: AccessibilityNodeInfo, focusType: Int): AccessibilityNodeInfo? { + val focused = root.findFocus(focusType) ?: return null + if (focused.isEditable) { + return focused + } + focused.recycle() + return null + } + + private fun findEditableInTree(node: AccessibilityNodeInfo, depth: Int): AccessibilityNodeInfo? { + if (depth > MAX_EDITABLE_SEARCH_DEPTH) return null + var firstEditable: AccessibilityNodeInfo? = null + if (node.isEditable) { + if (node.isFocused) { + return AccessibilityNodeInfo.obtain(node) + } + firstEditable = AccessibilityNodeInfo.obtain(node) + } + for (index in 0 until node.childCount) { + val child = node.getChild(index) ?: continue + try { + findEditableInTree(child, depth + 1)?.let { found -> + firstEditable?.recycle() + return found + } + } finally { + child.recycle() + } + } + return firstEditable + } + + private fun pasteWithRetryOrSetText(target: AccessibilityNodeInfo): Boolean { + sleepQuietly(PASTE_INITIAL_DELAY_MS) + repeat(PASTE_RETRY_COUNT) { attempt -> + if (target.performAction(AccessibilityNodeInfo.ACTION_PASTE)) { + Log.i(TAG, "paste=true attempt=${attempt + 1} package=${target.packageName}") + return true + } + sleepQuietly(PASTE_RETRY_DELAY_MS) + } + val setText = appendClipboardTextWithSetText(target) + Log.i(TAG, "paste=false setText=$setText package=${target.packageName}") + return setText + } + + private fun appendClipboardTextWithSetText(target: AccessibilityNodeInfo): Boolean { + if (target.isPassword) return false + val clipboardText = clipboardText().takeIf { it.isNotEmpty() } ?: return false + val existingText = target.text?.toString().orEmpty() + val args = Bundle().apply { + putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, + existingText + clipboardText, + ) + } + return target.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args) + } + + private fun clipboardText(): String { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return "" + val clip = clipboard.primaryClip ?: return "" + if (clip.itemCount <= 0) return "" + return clip.getItemAt(0)?.coerceToText(this)?.toString().orEmpty() + } + + private fun sleepQuietly(delayMs: Long) { + try { + Thread.sleep(delayMs) + } catch (_: InterruptedException) { + Thread.currentThread().interrupt() + } + } + + private fun captureSelectedTextFromFocusedNode(): String { + val root = rootInActiveWindow ?: return "" + val focused = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT) + ?: root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) + focused?.let { + return try { + selectedTextFromNode(it) + } finally { + it.recycle() + } + } + return selectedTextFromTree(root) + } + + private fun selectedTextFromTree(node: AccessibilityNodeInfo?): String { + if (node == null) return "" + selectedTextFromNode(node).takeIf { it.isNotBlank() }?.let { return it } + for (index in 0 until node.childCount) { + val child = node.getChild(index) ?: continue + try { + selectedTextFromTree(child).takeIf { it.isNotBlank() }?.let { return it } + } finally { + child.recycle() + } + } + return "" + } + + private fun selectedTextFromNode(node: AccessibilityNodeInfo): String { + val text = node.text?.toString() ?: return "" + val start = node.textSelectionStart + val end = node.textSelectionEnd + if (start < 0 || end < 0 || start == end) return "" + val from = minOf(start, end).coerceIn(0, text.length) + val to = maxOf(start, end).coerceIn(0, text.length) + if (from >= to) return "" + return text.substring(from, to) + } + + private fun markServiceAlive() { + getSharedPreferences(PREFS_NAME, prefsMode()) + .edit() + .putLong(PREF_KEY_LAST_HEARTBEAT, System.currentTimeMillis()) + .apply() + } + + companion object { + @Volatile + var instance: OpenLessAccessibilityService? = null + private set + + @JvmStatic + fun pasteToFocusedField(): Boolean { + instance?.let { return it.performPasteToFocusedField() } + return sendPasteRequestToAccessibilityProcess() + } + + @JvmStatic + fun captureSelectedText(): String { + return instance?.captureSelectedTextFromFocusedNode().orEmpty() + } + + @JvmStatic + fun isEnabled(context: Context): Boolean { + val enabled = Settings.Secure.getInt( + context.contentResolver, + Settings.Secure.ACCESSIBILITY_ENABLED, + 0, + ) == 1 + if (!enabled) return false + val services = Settings.Secure.getString( + context.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + ) ?: return false + return services.contains("${context.packageName}/${OpenLessAccessibilityService::class.java.name}") + } + + @JvmStatic + fun isOperational(context: Context): Boolean { + if (!isEnabled(context)) return false + val lastHeartbeat = context + .getSharedPreferences(PREFS_NAME, prefsMode()) + .getLong(PREF_KEY_LAST_HEARTBEAT, 0L) + if (lastHeartbeat <= 0L) return false + return System.currentTimeMillis() - lastHeartbeat <= HEARTBEAT_STALE_MS + } + + internal fun performPasteFromCommand(): Boolean { + return instance?.performPasteToFocusedField() == true + } + + private fun sendPasteRequestToAccessibilityProcess(): Boolean { + val context = OpenLessAppContext.context ?: return false + if (!isOperational(context)) return false + val latch = CountDownLatch(1) + val success = AtomicBoolean(false) + val receiver = object : ResultReceiver(null) { + override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { + success.set(resultCode == OpenLessAccessibilityCommandReceiver.RESULT_PASTE_SUCCESS) + latch.countDown() + } + } + return try { + val intent = Intent(context, OpenLessAccessibilityCommandReceiver::class.java).apply { + action = OpenLessAccessibilityCommandReceiver.ACTION_PASTE + putExtra(OpenLessAccessibilityCommandReceiver.EXTRA_RESULT_RECEIVER, receiver) + } + context.sendBroadcast(intent) + if (!latch.await(PASTE_COMMAND_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + Log.w(TAG, "accessibility paste result timed out") + return false + } + success.get() + } catch (error: Throwable) { + Log.w(TAG, "send accessibility paste request failed", error) + false + } + } + + @Suppress("DEPRECATION") + private fun prefsMode(): Int = Context.MODE_PRIVATE or Context.MODE_MULTI_PROCESS + + private val KEYBOARD_REFRESH_DELAYS_MS = longArrayOf(120L, 360L, 900L, 1600L) + private const val MAX_EDITABLE_SEARCH_DEPTH = 4 + private const val PASTE_INITIAL_DELAY_MS = 50L + private const val PASTE_RETRY_COUNT = 3 + private const val PASTE_RETRY_DELAY_MS = 80L + private const val PASTE_COMMAND_TIMEOUT_MS = 800L + private const val TAG = "OpenLessAccessibility" + private const val PREFS_NAME = "openless_accessibility" + private const val PREF_KEY_LAST_HEARTBEAT = "last_heartbeat" + private const val HEARTBEAT_INTERVAL_MS = 5_000L + private const val HEARTBEAT_STALE_MS = 15_000L + } +} diff --git a/openless-all/app/android/kotlin/OpenLessAndroidPreferences.kt b/openless-all/app/android/kotlin/OpenLessAndroidPreferences.kt new file mode 100644 index 00000000..1b06e9c8 --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessAndroidPreferences.kt @@ -0,0 +1,112 @@ +package com.openless.app + +import android.content.Context +import android.util.Log +import java.io.File +import org.json.JSONObject + +/** + * Reads Android-visible preferences without depending on the Rust coordinator. + */ +object OpenLessAndroidPreferences { + private const val TAG = "OpenLessAndroidPrefs" + private const val APP_DIR = "OpenLess" + private const val PREFERENCES_FILE = "preferences.json" + private const val KEY_OVERLAY_TRIGGER = "androidOverlayTrigger" + private const val KEY_OVERLAY_ACTIVATION_MODE = "androidOverlayActivationMode" + private const val KEY_OVERLAY_LEFT_SWIPE_ACTION = "androidOverlayLeftSwipeAction" + private const val KEY_OVERLAY_CANCEL_SWIPE_DIRECTION = "androidOverlayCancelSwipeDirection" + private const val KEY_OVERLAY_SIZE_DP = "androidOverlaySizeDp" + private const val DEFAULT_OVERLAY_SIZE_DP = 72 + private const val MIN_OVERLAY_SIZE_DP = 48 + private const val MAX_OVERLAY_SIZE_DP = 120 + private val VALID_OVERLAY_TRIGGERS = setOf("background", "always") + private val VALID_OVERLAY_ACTIVATION_MODES = setOf("tap", "long_press") + private val VALID_OVERLAY_LEFT_SWIPE_ACTIONS = setOf("translation", "style_pack") + private val VALID_OVERLAY_CANCEL_SWIPE_DIRECTIONS = setOf("up", "down") + + fun overlayTriggerMode(context: Context): String? { + val value = readPreferenceString(context, KEY_OVERLAY_TRIGGER) ?: return null + if (value == "keyboard") { + return "background" + } + return value.takeIf { it in VALID_OVERLAY_TRIGGERS } + } + + /** True when preferences still store legacy `"keyboard"` (before migration). */ + fun isKeyboardOverlayTrigger(context: Context): Boolean { + return readPreferenceString(context, KEY_OVERLAY_TRIGGER) == "keyboard" + } + + fun overlayActivationMode(context: Context): String { + return readPreferenceString(context, KEY_OVERLAY_ACTIVATION_MODE) + ?.takeIf { it in VALID_OVERLAY_ACTIVATION_MODES } + ?: "tap" + } + + fun overlayLeftSwipeAction(context: Context): String { + return readPreferenceString(context, KEY_OVERLAY_LEFT_SWIPE_ACTION) + ?.takeIf { it in VALID_OVERLAY_LEFT_SWIPE_ACTIONS } + ?: "translation" + } + + fun overlayCancelSwipeDirection(context: Context): String { + return readPreferenceString(context, KEY_OVERLAY_CANCEL_SWIPE_DIRECTION) + ?.takeIf { it in VALID_OVERLAY_CANCEL_SWIPE_DIRECTIONS } + ?: "up" + } + + fun overlaySizeDp(context: Context): Int { + return readPreferenceInt(context, KEY_OVERLAY_SIZE_DP) + ?.coerceIn(MIN_OVERLAY_SIZE_DP, MAX_OVERLAY_SIZE_DP) + ?: DEFAULT_OVERLAY_SIZE_DP + } + + private fun readPreferenceString(context: Context, key: String): String? { + for (file in preferenceFiles(context).distinctBy { it.absolutePath }) { + if (!file.isFile) { + continue + } + val value = try { + JSONObject(file.readText()).optString(key, "") + } catch (error: Throwable) { + Log.w(TAG, "read ${file.absolutePath} failed", error) + "" + } + if (value.isNotBlank()) { + return value + } + } + return null + } + + private fun readPreferenceInt(context: Context, key: String): Int? { + for (file in preferenceFiles(context).distinctBy { it.absolutePath }) { + if (!file.isFile) { + continue + } + val value = try { + val json = JSONObject(file.readText()) + if (json.has(key)) json.optInt(key) else null + } catch (error: Throwable) { + Log.w(TAG, "read ${file.absolutePath} failed", error) + null + } + if (value != null) { + return value + } + } + return null + } + + private fun preferenceFiles(context: Context): List { + val files = mutableListOf() + val envDir = System.getenv("TAURI_ANDROID_APP_DATA_DIR") + if (!envDir.isNullOrBlank()) { + files += File(File(envDir), APP_DIR).resolve(PREFERENCES_FILE) + } + files += File(File(context.cacheDir, APP_DIR), PREFERENCES_FILE) + files += File(File(context.filesDir, APP_DIR), PREFERENCES_FILE) + return files + } +} diff --git a/openless-all/app/android/kotlin/OpenLessAppContext.kt b/openless-all/app/android/kotlin/OpenLessAppContext.kt new file mode 100644 index 00000000..e1c627ae --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessAppContext.kt @@ -0,0 +1,13 @@ +package com.openless.app + +import android.content.Context + +object OpenLessAppContext { + @Volatile + var context: Context? = null + private set + + fun initialize(context: Context) { + this.context = context.applicationContext + } +} diff --git a/openless-all/app/android/kotlin/OpenLessApplication.kt b/openless-all/app/android/kotlin/OpenLessApplication.kt new file mode 100644 index 00000000..8e25fd93 --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessApplication.kt @@ -0,0 +1,84 @@ +package com.openless.app + +import android.app.Activity +import android.app.Application +import android.content.Intent +import android.os.Bundle +import android.provider.Settings +import android.util.Log + +/** + * Registers activity lifecycle hooks for overlay background trigger mode. + */ +class OpenLessApplication : Application() { + override fun onCreate() { + super.onCreate() + OpenLessAppContext.initialize(this) + registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit + override fun onActivityStarted(activity: Activity) { + if (activity.javaClass.name.endsWith("MainActivity")) { + maybeHideOverlayOnForeground() + } + } + override fun onActivityResumed(activity: Activity) = Unit + override fun onActivityPaused(activity: Activity) = Unit + override fun onActivityStopped(activity: Activity) { + if (activity.javaClass.name.endsWith("MainActivity")) { + maybeShowOverlayOnBackground() + } + } + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + override fun onActivityDestroyed(activity: Activity) = Unit + }) + } + + private fun maybeShowOverlayOnBackground() { + val configured = configuredOverlayTriggerMode() + val shouldShow = configured == "background" || + configured == "always" + if (!shouldShow) { + return + } + if (!canDrawOverlays()) { + return + } + sendOverlayAction(OpenLessOverlayService.ACTION_SHOW) + } + + private fun maybeHideOverlayOnForeground() { + if (configuredOverlayTriggerMode() == "always") { + if (canDrawOverlays()) { + sendOverlayAction(OpenLessOverlayService.ACTION_SHOW) + } + return + } + sendOverlayAction(OpenLessOverlayService.ACTION_HIDE) + } + + private fun configuredOverlayTriggerMode(): String { + return OpenLessAndroidPreferences.overlayTriggerMode(this) ?: "background" + } + + private fun canDrawOverlays(): Boolean { + return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Settings.canDrawOverlays(this) + } else { + true + } + } + + private fun sendOverlayAction(action: String) { + try { + startService(Intent(this, OpenLessOverlayService::class.java).apply { + this.action = action + }) + } catch (error: Throwable) { + Log.w(TAG, "overlay action failed: $action", error) + } + } + + companion object { + private const val TAG = "OpenLessApplication" + } +} diff --git a/openless-all/app/android/kotlin/OpenLessNative.kt b/openless-all/app/android/kotlin/OpenLessNative.kt new file mode 100644 index 00000000..3c6f297a --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessNative.kt @@ -0,0 +1,42 @@ +package com.openless.app + +/** + * JNI bridge from Kotlin overlay / lifecycle code into Rust Coordinator. + */ +object OpenLessNative { + init { + try { + System.loadLibrary("openless_lib") + } catch (error: UnsatisfiedLinkError) { + android.util.Log.e("OpenLessNative", "failed to load openless_lib", error) + } + } + + @JvmStatic external fun nativeStartDictation() + + @JvmStatic external fun nativeStartDictationWithTranslation(translation: Boolean) + + @JvmStatic external fun nativeStopDictation() + + @JvmStatic external fun nativeStopDictationWithTranslation(translation: Boolean) + + @JvmStatic external fun nativeCancelDictation() + + @JvmStatic external fun nativeSwitchStylePack() + + @JvmStatic external fun nativeOpenQaFromOverlay() + + @JvmStatic external fun nativeFinalizeQaFromOverlay() + + @JvmStatic external fun nativeGetOverlayTriggerMode(): String + + @JvmStatic external fun nativeCanDrawOverlays(context: android.content.Context): Boolean + + @JvmStatic external fun nativeShowOverlay(context: android.content.Context) + + @JvmStatic external fun nativeHideOverlay(context: android.content.Context) + + @JvmStatic external fun nativeIsOverlayVisible(): Boolean + + @JvmStatic external fun nativeNotifyOverlayPermissionChanged(context: android.content.Context) +} diff --git a/openless-all/app/android/kotlin/OpenLessOverlayBridge.kt b/openless-all/app/android/kotlin/OpenLessOverlayBridge.kt new file mode 100644 index 00000000..92406730 --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessOverlayBridge.kt @@ -0,0 +1,33 @@ +package com.openless.app + +import android.os.Handler +import android.os.Looper + +/** + * Rust calls back into this object to refresh overlay UI state. + */ +object OpenLessOverlayBridge { + private val mainHandler = Handler(Looper.getMainLooper()) + + @Volatile + var listener: OverlayStateListener? = null + + interface OverlayStateListener { + fun onCapsuleStateChanged(state: String, message: String?) + } + + @JvmStatic + fun onCapsuleStateChanged(state: String, message: String?) { + mainHandler.post { + listener?.onCapsuleStateChanged(state, message) + } + } + + @JvmStatic + fun showToast(message: String) { + mainHandler.post { + val service = OpenLessOverlayService.instance ?: return@post + android.widget.Toast.makeText(service.applicationContext, message, android.widget.Toast.LENGTH_SHORT).show() + } + } +} diff --git a/openless-all/app/android/kotlin/OpenLessOverlayService.kt b/openless-all/app/android/kotlin/OpenLessOverlayService.kt new file mode 100644 index 00000000..658d22eb --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessOverlayService.kt @@ -0,0 +1,831 @@ +package com.openless.app + +import android.Manifest +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.drawable.GradientDrawable +import android.os.Build +import android.os.IBinder +import android.provider.Settings +import android.util.Log +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.Toast +import kotlin.math.abs + +/** + * Foreground service + TYPE_APPLICATION_OVERLAY floating dictation control. + */ +class OpenLessOverlayService : Service(), OpenLessOverlayBridge.OverlayStateListener { + + private var windowManager: WindowManager? = null + private var rootView: FrameLayout? = null + private var layoutParams: WindowManager.LayoutParams? = null + private var recording = false + private var processing = false + private var keyboardVisible = false + private var armed = false + private var dragStartX = 0 + private var dragStartY = 0 + private var paramStartX = 0 + private var paramStartY = 0 + private var dragging = false + private var longPressRecording = false + private var pendingSwipe: SwipeDirection? = null + private var swipeConsumed = false + + private lateinit var iconContainer: FrameLayout + private lateinit var iconButton: ImageView + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onCreate() { + super.onCreate() + instance = this + OpenLessOverlayBridge.listener = this + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.i( + TAG, + "onStartCommand action=${intent?.action} startId=$startId rootAttached=${rootView?.isAttachedToWindow}", + ) + when (intent?.action) { + ACTION_SHOW -> showOverlay() + ACTION_START_RECORDING -> { + showOverlay() + startRecordingFromOverlay() + } + ACTION_HIDE -> { + hideOverlay() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + @Suppress("DEPRECATION") + stopForeground(true) + } + stopSelf(startId) + } + ACTION_REPLACE_OVERLAY -> replaceOverlay() + ACTION_TOGGLE_EXPAND -> handleIconClick() + ACTION_KEYBOARD_CHANGED -> handleKeyboardChanged(intent) + ACTION_REFRESH_LAYOUT -> refreshOverlayLayout() + } + return START_STICKY + } + + override fun onDestroy() { + if (OpenLessOverlayBridge.listener === this) { + OpenLessOverlayBridge.listener = null + } + hideOverlay() + if (instance === this) { + instance = null + } + super.onDestroy() + } + + override fun onCapsuleStateChanged(state: String, message: String?) { + when (state) { + "recording" -> { + recording = true + processing = false + if (!tryPromoteRecordingForeground()) { + try { + OpenLessNative.nativeCancelDictation() + } catch (error: Throwable) { + Log.w(TAG, "cancel dictation bridge unavailable", error) + } + return + } + applyVisualState(OverlayVisualState.Recording) + } + "transcribing", "polishing" -> { + recording = false + processing = true + applyVisualState(OverlayVisualState.Processing) + } + "done" -> { + recording = false + processing = false + setArmed(false) + } + "error" -> { + recording = false + processing = false + setArmed(false) + applyVisualState(OverlayVisualState.Error) + message?.takeIf { it.isNotBlank() }?.let { showToast(it) } + } + "cancelled", "idle" -> { + recording = false + processing = false + setArmed(false) + } + } + } + + private fun showOverlay() = withOverlayLock { + windowManager = getSystemService(WINDOW_SERVICE) as WindowManager + reconcileOverlayRoots() + overlayRoots.lastOrNull()?.let { existing -> + val params = existing.layoutParams as? WindowManager.LayoutParams + if (params != null) { + refreshExistingOverlayLayout(existing, params) + Log.i(TAG, "overlay already shown roots=${overlayRoots.size}") + return@withOverlayLock + } + removeOverlayRoot(existing) + synchronized(overlayRoots) { + overlayRoots.remove(existing) + } + if (rootView === existing) { + rootView = null + layoutParams = null + } + } + attachNewOverlayRoot() + } + + private fun replaceOverlay() = withOverlayLock { + windowManager = windowManager ?: getSystemService(WINDOW_SERVICE) as WindowManager + reconcileOverlayRoots() + val removed = clearAllOverlayRoots() + if (removed > 0) { + Log.i(TAG, "overlay replace cleared removed=$removed") + } + if (!canDrawOverlays()) { + Log.i(TAG, "overlay replace skipped no overlay permission") + return@withOverlayLock + } + if (!attachNewOverlayRoot()) { + return@withOverlayLock + } + if (recording) { + tryPromoteRecordingForeground() + } + Log.i(TAG, "overlay replaced roots=1") + } + + private fun hideOverlay() = withOverlayLock { + val removed = clearAllOverlayRoots() + if (removed > 0) { + Log.i(TAG, "overlay hidden removed=$removed") + } + } + + private fun clearAllOverlayRoots(): Int { + windowManager = windowManager ?: getSystemService(WINDOW_SERVICE) as WindowManager + val views = synchronized(overlayRoots) { + (overlayRoots + listOfNotNull(rootView)).distinct().also { + overlayRoots.clear() + } + } + views.forEach { view -> + removeOverlayRoot(view) + } + rootView = null + layoutParams = null + return views.size + } + + private fun attachNewOverlayRoot(): Boolean { + val savedPosition = loadSavedPosition() + val params = WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + } else { + @Suppress("DEPRECATION") + WindowManager.LayoutParams.TYPE_PHONE + }, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT, + ).apply { + gravity = Gravity.TOP or Gravity.START + x = savedPosition.first + y = savedPosition.second + } + layoutParams = params + + val root = FrameLayout(this).apply { + contentDescription = "OpenLess" + isClickable = true + isFocusable = false + setOnClickListener { handleIconClick() } + } + iconContainer = root + iconButton = buildIconButton() + root.addView( + iconButton, + FrameLayout.LayoutParams(1, 1, Gravity.CENTER), + ) + applyOverlaySize(root) + attachDragHandler(root, params) + return try { + windowManager?.addView(root, params) + rootView = root + synchronized(overlayRoots) { + overlayRoots.add(root) + } + Log.i(TAG, "overlay shown x=${params.x} y=${params.y} roots=${overlayRoots.size}") + applyVisualState( + when { + recording -> OverlayVisualState.Recording + processing -> OverlayVisualState.Processing + armed -> OverlayVisualState.Armed + else -> OverlayVisualState.Idle + }, + ) + true + } catch (error: Throwable) { + Log.w(TAG, "show overlay failed", error) + layoutParams = null + false + } + } + + private fun canDrawOverlays(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Settings.canDrawOverlays(this) + } else { + true + } + } + + private fun refreshExistingOverlayLayout( + root: FrameLayout, + params: WindowManager.LayoutParams, + ) { + rootView = root + layoutParams = params + iconContainer = root + (root.getChildAt(0) as? ImageView)?.let { iconButton = it } + applyOverlaySize(root) + attachDragHandler(root, params) + clampToScreen(params) + windowManager?.updateViewLayout(root, params) + } + + private fun refreshOverlayLayout() = withOverlayLock { + windowManager = windowManager ?: getSystemService(WINDOW_SERVICE) as WindowManager + reconcileOverlayRoots() + val root = overlayRoots.lastOrNull() ?: rootView + if (root == null || !root.isAttachedToWindow) { + Log.i(TAG, "overlay refresh skipped roots=0") + return@withOverlayLock + } + val params = root.layoutParams as? WindowManager.LayoutParams + if (params == null) { + Log.i(TAG, "overlay refresh skipped roots=0") + return@withOverlayLock + } + refreshExistingOverlayLayout(root, params) + val sizeDp = OpenLessAndroidPreferences.overlaySizeDp(this) + Log.i(TAG, "overlay layout refreshed sizeDp=$sizeDp roots=1") + } + + private fun reconcileOverlayRoots() { + val roots = synchronized(overlayRoots) { + (overlayRoots + listOfNotNull(rootView)).distinct().filter { it.isAttachedToWindow }.also { + overlayRoots.clear() + overlayRoots.addAll(it) + } + } + if (roots.isEmpty()) { + rootView = null + layoutParams = null + return + } + roots.dropLast(1).forEach { staleRoot -> + removeOverlayRoot(staleRoot) + synchronized(overlayRoots) { + overlayRoots.remove(staleRoot) + } + } + val activeRoot = roots.last() + synchronized(overlayRoots) { + overlayRoots.clear() + overlayRoots.add(activeRoot) + } + rootView = activeRoot + layoutParams = activeRoot.layoutParams as? WindowManager.LayoutParams + Log.i(TAG, "reconciled overlay roots kept=1 removed=${roots.size - 1}") + } + + private fun removeOverlayRoot(view: FrameLayout) { + try { + if (view.isAttachedToWindow) { + windowManager?.removeViewImmediate(view) + } + } catch (error: Throwable) { + Log.w(TAG, "remove overlay root failed", error) + } + } + + private fun buildIconButton(): ImageView { + return ImageView(this).apply { + setImageResource(R.drawable.ic_overlay_logo) + scaleType = ImageView.ScaleType.CENTER_INSIDE + setPadding(0, 0, 0, 0) + contentDescription = "OpenLess" + isClickable = false + isFocusable = false + } + } + + private fun applyOverlaySize(container: FrameLayout) { + if (!::iconButton.isInitialized) return + val sizeDp = OpenLessAndroidPreferences.overlaySizeDp(this) + val paddingDp = overlayPaddingDp(sizeDp) + val imageSizePx = dp((sizeDp - paddingDp * 2).coerceAtLeast(MIN_ICON_IMAGE_SIZE_DP)) + val paddingPx = dp(paddingDp) + container.setPadding(paddingPx, paddingPx, paddingPx, paddingPx) + (iconButton.layoutParams as? FrameLayout.LayoutParams)?.let { childParams -> + childParams.width = imageSizePx + childParams.height = imageSizePx + childParams.gravity = Gravity.CENTER + iconButton.layoutParams = childParams + } + container.requestLayout() + Log.i(TAG, "overlay size applied sizeDp=$sizeDp imagePx=$imageSizePx") + } + + private fun overlayPaddingDp(sizeDp: Int): Int { + return (sizeDp * ICON_PADDING_DP / DEFAULT_ICON_SIZE_DP).coerceIn(8, 20) + } + + private fun handleIconClick() { + if (processing) return + if (recording) { + stopRecordingFromOverlay() + return + } + if (!isTapActivationMode()) { + return + } + startRecordingFromOverlay() + } + + private fun handleKeyboardChanged(intent: Intent) { + val visible = intent.getBooleanExtra(EXTRA_KEYBOARD_VISIBLE, false) + keyboardVisible = visible + Log.i(TAG, "keyboard changed visible=$visible") + if (visible) { + showOverlay() + return + } + if (!recording && !processing) { + hideOverlay() + } + } + + private fun attachDragHandler(view: View, params: WindowManager.LayoutParams) { + view.setOnTouchListener { touchedView, event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + dragging = false + swipeConsumed = false + longPressRecording = false + pendingSwipe = null + if (processing) { + return@setOnTouchListener true + } + dragStartX = event.rawX.toInt() + dragStartY = event.rawY.toInt() + paramStartX = params.x + paramStartY = params.y + if (!isTapActivationMode() && !recording && !processing) { + longPressRecording = true + startRecordingFromOverlay() + } + true + } + MotionEvent.ACTION_MOVE -> { + val dx = event.rawX.toInt() - dragStartX + val dy = event.rawY.toInt() - dragStartY + val verticalSwipe = detectVerticalSwipe(dx, dy) + if (recording && verticalSwipe != null && matchesConfiguredCancelSwipe(verticalSwipe) && !swipeConsumed) { + pendingSwipe = verticalSwipe + swipeConsumed = true + applySwipePreview(verticalSwipe) + return@setOnTouchListener true + } + val swipe = detectHorizontalSwipe(dx, dy) + if ((recording || armed || longPressRecording) && swipe != null && !swipeConsumed) { + pendingSwipe = swipe + swipeConsumed = true + applySwipePreview(swipe) + return@setOnTouchListener true + } + if (!processing && !armed && !recording && !longPressRecording && (abs(dx) > DRAG_SLOP_PX || abs(dy) > DRAG_SLOP_PX)) { + dragging = true + params.x = paramStartX + dx + params.y = paramStartY + dy + clampToScreen(params) + rootView?.let { windowManager?.updateViewLayout(it, params) } + } + true + } + MotionEvent.ACTION_UP -> { + if (!dragging) { + val swipe = pendingSwipe + if (swipe != null) { + commitSwipe(swipe) + } else if (longPressRecording || (!isTapActivationMode() && recording)) { + stopRecordingFromOverlay() + } else if (!isTapActivationMode()) { + setArmed(false) + } else if (!swipeConsumed) { + touchedView.performClick() + } + } else { + savePosition(params.x, params.y) + } + longPressRecording = false + pendingSwipe = null + swipeConsumed = false + true + } + MotionEvent.ACTION_CANCEL -> { + if (longPressRecording || (!isTapActivationMode() && recording)) { + stopRecordingFromOverlay() + } + longPressRecording = false + pendingSwipe = null + swipeConsumed = false + true + } + else -> false + } + } + } + + private fun applyVisualState(state: OverlayVisualState) { + if (!::iconContainer.isInitialized || !::iconButton.isInitialized) return + val (alpha, fill, stroke, strokeWidth, enabled) = when (state) { + OverlayVisualState.Idle -> VisualStyle( + alpha = 0.58f, + fill = Color.parseColor("#66202A36"), + stroke = Color.parseColor("#66FFFFFF"), + strokeWidth = 1, + enabled = true, + ) + OverlayVisualState.Armed -> VisualStyle( + alpha = 1f, + fill = Color.parseColor("#E6111827"), + stroke = Color.parseColor("#38BDF8"), + strokeWidth = 3, + enabled = true, + ) + OverlayVisualState.Recording -> VisualStyle( + alpha = 1f, + fill = Color.parseColor("#E6111827"), + stroke = Color.parseColor("#F43F5E"), + strokeWidth = 3, + enabled = true, + ) + OverlayVisualState.Processing -> VisualStyle( + alpha = 0.86f, + fill = Color.parseColor("#D1111827"), + stroke = Color.parseColor("#38BDF8"), + strokeWidth = 2, + enabled = true, + ) + OverlayVisualState.Error -> VisualStyle( + alpha = 0.95f, + fill = Color.parseColor("#E67F1D1D"), + stroke = Color.parseColor("#EF4444"), + strokeWidth = 2, + enabled = true, + ) + } + iconContainer.alpha = alpha + iconContainer.isEnabled = enabled + iconContainer.background = circleDrawable(fill, stroke, dp(strokeWidth)) + iconButton.isEnabled = enabled + } + + private fun setArmed(value: Boolean) { + armed = value + if (!recording && !processing) { + applyVisualState(if (value) OverlayVisualState.Armed else OverlayVisualState.Idle) + } + } + + private fun detectHorizontalSwipe(dx: Int, dy: Int): SwipeDirection? { + if (abs(dx) < dp(SWIPE_THRESHOLD_DP)) return null + if (abs(dy) > abs(dx) * SWIPE_VERTICAL_RATIO) return null + return if (dx < 0) SwipeDirection.Left else SwipeDirection.Right + } + + private fun detectVerticalSwipe(dx: Int, dy: Int): SwipeDirection? { + if (abs(dy) < dp(SWIPE_THRESHOLD_DP)) return null + if (abs(dx) > abs(dy) * SWIPE_VERTICAL_RATIO) return null + return if (dy < 0) SwipeDirection.Up else SwipeDirection.Down + } + + private fun matchesConfiguredCancelSwipe(direction: SwipeDirection): Boolean { + val configured = OpenLessAndroidPreferences.overlayCancelSwipeDirection(this) + return (direction == SwipeDirection.Up && configured == "up") || + (direction == SwipeDirection.Down && configured == "down") + } + + private fun applySwipePreview(direction: SwipeDirection) { + when (direction) { + SwipeDirection.Left -> applyVisualState(OverlayVisualState.Armed) + SwipeDirection.Right -> applyVisualState(OverlayVisualState.Processing) + SwipeDirection.Up, + SwipeDirection.Down -> applyVisualState(OverlayVisualState.Error) + } + } + + private fun commitSwipe(direction: SwipeDirection) { + Log.i(TAG, "commit swipe direction=$direction recording=$recording processing=$processing") + when (direction) { + SwipeDirection.Left -> handleLeftSwipe() + SwipeDirection.Right -> finalizeQaFromOverlay() + SwipeDirection.Up, + SwipeDirection.Down -> cancelRecordingFromOverlay(direction) + } + } + + private fun cancelRecordingFromOverlay(direction: SwipeDirection) { + if (!recording || !matchesConfiguredCancelSwipe(direction)) { + return + } + try { + OpenLessNative.nativeCancelDictation() + recording = false + processing = false + longPressRecording = false + setArmed(false) + applyVisualState(OverlayVisualState.Idle) + Log.i(TAG, "recording cancelled from overlay direction=$direction") + } catch (error: Throwable) { + Log.w(TAG, "cancel dictation bridge unavailable", error) + applyVisualState(OverlayVisualState.Error) + showToast("语音服务未就绪,请打开 OpenLess 后重试") + } + } + + private fun handleLeftSwipe() { + when (OpenLessAndroidPreferences.overlayLeftSwipeAction(this)) { + "style_pack" -> { + switchStylePackFromOverlay() + if (recording) { + stopRecordingFromOverlay() + } + } + else -> stopRecordingFromOverlay(translation = true) + } + } + + private fun switchStylePackFromOverlay() { + try { + OpenLessNative.nativeSwitchStylePack() + setArmed(false) + } catch (error: Throwable) { + Log.w(TAG, "switch style pack bridge unavailable", error) + applyVisualState(OverlayVisualState.Error) + showToast("语音服务未就绪,请打开 OpenLess 后重试") + } + } + + private fun openQaFromOverlay() { + try { + Log.i(TAG, "open QA from overlay") + OpenLessNative.nativeOpenQaFromOverlay() + setArmed(false) + } catch (error: Throwable) { + Log.w(TAG, "open QA bridge unavailable", error) + applyVisualState(OverlayVisualState.Error) + showToast("问答服务未就绪,请打开 OpenLess 后重试") + } + } + + private fun finalizeQaFromOverlay() { + try { + Log.i(TAG, "finalize QA from overlay") + OpenLessNative.nativeFinalizeQaFromOverlay() + recording = false + processing = true + setArmed(false) + applyVisualState(OverlayVisualState.Processing) + } catch (error: Throwable) { + Log.w(TAG, "finalize QA bridge unavailable", error) + processing = false + applyVisualState(OverlayVisualState.Error) + showToast("问答服务未就绪,请打开 OpenLess 后重试") + } + } + + private fun startRecordingFromOverlay(translation: Boolean = false) { + showOverlay() + if (tryPromoteRecordingForeground()) { + try { + if (translation) { + OpenLessNative.nativeStartDictationWithTranslation(true) + } else { + OpenLessNative.nativeStartDictation() + } + recording = true + processing = false + setArmed(false) + applyVisualState(OverlayVisualState.Recording) + } catch (error: Throwable) { + Log.w(TAG, "start dictation bridge unavailable", error) + recording = false + processing = false + applyVisualState(OverlayVisualState.Error) + showToast("语音服务未就绪,请打开 OpenLess 后重试") + } + return + } + applyVisualState(OverlayVisualState.Error) + } + + private fun stopRecordingFromOverlay(translation: Boolean = false) { + try { + recording = false + processing = true + applyVisualState(OverlayVisualState.Processing) + if (translation) { + OpenLessNative.nativeStopDictationWithTranslation(true) + } else { + OpenLessNative.nativeStopDictation() + } + } catch (error: Throwable) { + Log.w(TAG, "stop dictation bridge unavailable", error) + recording = false + processing = false + applyVisualState(OverlayVisualState.Error) + showToast("语音服务未就绪,请打开 OpenLess 后重试") + } + } + + private fun tryPromoteRecordingForeground(): Boolean { + if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + showToast("请先授予麦克风权限") + return false + } + val notification = buildNotification("录音中") + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground( + NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE, + ) + } else { + startForeground(NOTIFICATION_ID, notification) + } + true + } catch (error: SecurityException) { + Log.w(TAG, "microphone foreground service not allowed from current state", error) + showToast("系统限制后台录音,请在 OpenLess 内开始") + false + } + } + + private fun buildNotification(contentText: String): Notification { + val channelId = "openless_overlay" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val nm = getSystemService(NotificationManager::class.java) + nm.createNotificationChannel( + NotificationChannel(channelId, "OpenLess Overlay", NotificationManager.IMPORTANCE_LOW), + ) + } + return Notification.Builder(this, channelId) + .setContentTitle("OpenLess") + .setContentText(contentText) + .setSmallIcon(R.mipmap.ic_launcher) + .build() + } + + private fun circleDrawable(color: Int, strokeColor: Int, strokeWidth: Int): GradientDrawable { + return GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(color) + setStroke(strokeWidth, strokeColor) + } + } + + private fun overlaySize(): Int { + val root = rootView + val measured = maxOf(root?.width ?: 0, root?.height ?: 0) + return measured.takeIf { it > 0 } ?: dp(OpenLessAndroidPreferences.overlaySizeDp(this)) + } + + private fun clampToScreen(params: WindowManager.LayoutParams) { + val iconSize = overlaySize() + val margin = dp(8) + val maxX = (resources.displayMetrics.widthPixels - iconSize - margin).coerceAtLeast(margin) + val maxY = (resources.displayMetrics.heightPixels - iconSize - margin).coerceAtLeast(margin) + params.x = params.x.coerceIn(margin, maxX) + params.y = params.y.coerceIn(margin, maxY) + } + + private fun loadSavedPosition(): Pair { + val prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + val defaultX = dp(24) + val defaultY = dp(120) + val x = prefs.getInt(PREF_KEY_X, defaultX) + val y = prefs.getInt(PREF_KEY_Y, defaultY) + return x to y + } + + private fun savePosition(x: Int, y: Int) { + getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + .edit() + .putInt(PREF_KEY_X, x) + .putInt(PREF_KEY_Y, y) + .apply() + } + + private fun isTapActivationMode(): Boolean { + return OpenLessAndroidPreferences.overlayActivationMode(this) == "tap" + } + + private fun showToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } + + private fun dp(value: Int): Int { + return (value * resources.displayMetrics.density).toInt() + } + + private inline fun withOverlayLock(block: () -> T): T { + return synchronized(overlayLock) { + block() + } + } + + private data class VisualStyle( + val alpha: Float, + val fill: Int, + val stroke: Int, + val strokeWidth: Int, + val enabled: Boolean, + ) + + private enum class OverlayVisualState { + Idle, + Armed, + Recording, + Processing, + Error, + } + + private enum class SwipeDirection { + Left, + Right, + Up, + Down, + } + + companion object { + const val ACTION_SHOW = "com.openless.app.overlay.SHOW" + const val ACTION_HIDE = "com.openless.app.overlay.HIDE" + const val ACTION_REPLACE_OVERLAY = "com.openless.app.overlay.REPLACE_OVERLAY" + const val ACTION_REFRESH_LAYOUT = "com.openless.app.overlay.REFRESH_LAYOUT" + const val ACTION_TOGGLE_EXPAND = "com.openless.app.overlay.TOGGLE_EXPAND" + const val ACTION_START_RECORDING = "com.openless.app.overlay.START_RECORDING" + const val ACTION_KEYBOARD_CHANGED = "com.openless.app.overlay.KEYBOARD_CHANGED" + const val EXTRA_KEYBOARD_VISIBLE = "keyboard_visible" + const val EXTRA_KEYBOARD_TOP = "keyboard_top" + const val EXTRA_KEYBOARD_BOTTOM = "keyboard_bottom" + private const val DEFAULT_ICON_SIZE_DP = 72 + private const val MIN_ICON_IMAGE_SIZE_DP = 32 + private const val ICON_PADDING_DP = 12 + private const val DRAG_SLOP_PX = 8 + private const val SWIPE_THRESHOLD_DP = 56 + private const val SWIPE_VERTICAL_RATIO = 0.6f + private const val PREFS_NAME = "openless_overlay" + private const val PREF_KEY_X = "overlay_x" + private const val PREF_KEY_Y = "overlay_y" + private const val NOTIFICATION_ID = 42001 + private const val TAG = "OpenLessOverlayService" + + private val overlayLock = Any() + private val overlayRoots = mutableListOf() + + @Volatile + var instance: OpenLessOverlayService? = null + private set + } +} diff --git a/openless-all/app/android/kotlin/OpenLessPermissionBridge.kt b/openless-all/app/android/kotlin/OpenLessPermissionBridge.kt new file mode 100644 index 00000000..9fb437fd --- /dev/null +++ b/openless-all/app/android/kotlin/OpenLessPermissionBridge.kt @@ -0,0 +1,46 @@ +package com.openless.app + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import java.util.concurrent.atomic.AtomicBoolean + +object OpenLessPermissionBridge { + private const val TAG = "OpenLessPermissionBridge" + + private val requestInFlight = AtomicBoolean(false) + + @JvmStatic + fun requestRecordAudioPermission(context: Context): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true + } + if (context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { + return true + } + if (!requestInFlight.compareAndSet(false, true)) { + Log.i(TAG, "RECORD_AUDIO permission request already in flight") + return false + } + return try { + val intent = Intent(context, MicrophonePermissionActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + false + } catch (error: Throwable) { + requestInFlight.set(false) + Log.w(TAG, "failed to launch RECORD_AUDIO permission activity", error) + context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED + } + } + + @JvmStatic + fun resolveRecordAudioPermission(granted: Boolean) { + Log.i(TAG, "RECORD_AUDIO permission completed granted=$granted") + requestInFlight.set(false) + } +} diff --git a/openless-all/app/android/kotlin/OverlayPermissionActivity.kt b/openless-all/app/android/kotlin/OverlayPermissionActivity.kt new file mode 100644 index 00000000..d3a05f8f --- /dev/null +++ b/openless-all/app/android/kotlin/OverlayPermissionActivity.kt @@ -0,0 +1,27 @@ +package com.openless.app + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings + +/** + * 引导用户授权 SYSTEM_ALERT_WINDOW。 + * Rust 命令 request_android_overlay_permission 通过 Intent 启动本 Activity。 + */ +class OverlayPermissionActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { + val intent = Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:$packageName"), + ) + startActivity(intent) + } + finish() + } +} diff --git a/openless-all/app/android/kotlin/README.md b/openless-all/app/android/kotlin/README.md new file mode 100644 index 00000000..465107de --- /dev/null +++ b/openless-all/app/android/kotlin/README.md @@ -0,0 +1,27 @@ +# Android Kotlin scaffolding + +Copy these files into `src-tauri/gen/android/` after running: + +```bash +cd openless-all/app +npm run tauri:android:init +``` + +## Copy / merge paths + +| Source (this folder) | Destination (after init) | +| --- | --- | +| `OpenLessOverlayService.kt` | `gen/android/app/src/main/java/com/openless/app/OpenLessOverlayService.kt` | +| `OverlayPermissionActivity.kt` | `gen/android/app/src/main/java/com/openless/app/OverlayPermissionActivity.kt` | +| `AndroidManifest.v1.snippet.xml` | 在 `../manifests/`,merge 进 `gen/android/.../AndroidManifest.xml` | +| `AndroidManifest.v3.snippet.xml` | 在 `../manifests/`,**future / not complete** — overlay v3 only | + +Tauri `android init` generates the base manifest under `gen/android/app/src/main/AndroidManifest.xml`. +Merge the v1 snippet permissions into that file before building APK v1. + +## Manifest snippets + +- **v1** (`AndroidManifest.v1.snippet.xml`): `RECORD_AUDIO` and `MODIFY_AUDIO_SETTINGS` for in-app dictation — required for APK v1. +- **v3** (`AndroidManifest.v3.snippet.xml`): overlay + foreground service — **not complete / future**. + +Do not treat v3 snippets as shipped; they document planned permissions and service entries only. diff --git a/openless-all/app/android/manifests/AndroidManifest.v1.snippet.xml b/openless-all/app/android/manifests/AndroidManifest.v1.snippet.xml new file mode 100644 index 00000000..0afe73c0 --- /dev/null +++ b/openless-all/app/android/manifests/AndroidManifest.v1.snippet.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/openless-all/app/android/manifests/AndroidManifest.v3.snippet.xml b/openless-all/app/android/manifests/AndroidManifest.v3.snippet.xml new file mode 100644 index 00000000..859f99b6 --- /dev/null +++ b/openless-all/app/android/manifests/AndroidManifest.v3.snippet.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/openless-all/app/android/manifests/res/xml/openless_accessibility_config.xml b/openless-all/app/android/manifests/res/xml/openless_accessibility_config.xml new file mode 100644 index 00000000..281b8c75 --- /dev/null +++ b/openless-all/app/android/manifests/res/xml/openless_accessibility_config.xml @@ -0,0 +1,9 @@ + + diff --git a/openless-all/app/android/manifests/res/xml/openless_ime_method.xml b/openless-all/app/android/manifests/res/xml/openless_ime_method.xml new file mode 100644 index 00000000..5a2afd44 --- /dev/null +++ b/openless-all/app/android/manifests/res/xml/openless_ime_method.xml @@ -0,0 +1,9 @@ + + + + diff --git a/openless-all/app/package.json b/openless-all/app/package.json index 9795ebf4..567fcf6c 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -8,6 +8,12 @@ "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", + "tauri:android:init": "tauri android init", + "tauri:android:dev": "tauri android dev", + "tauri:android:build": "tauri android build --apk --debug --target aarch64 armv7 i686 x86_64 --split-per-abi", + "merge:android-v1-manifest": "node scripts/merge-android-v1-manifest.mjs", + "merge:android-overlay-manifest": "node scripts/merge-android-overlay-manifest.mjs", + "copy:android-scaffolding": "node scripts/copy-android-scaffolding.mjs", "check:aura-skin": "node scripts/aura-skin-contract.test.mjs", "check:macos-capsule-spaces": "node scripts/macos-capsule-spaces-contract.test.mjs", "check:hotkey-injection": "node scripts/check-hotkey-injection.mjs" diff --git a/openless-all/app/public/AppIcon.png b/openless-all/app/public/AppIcon.png index ee876eb5f9c93fb69deb5b29dc0f0278413d5ed2..3bdcb6b84e31229cdf17a7188b1a6f0e84e8000f 100755 GIT binary patch literal 87011 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 H1fTyWypAGq literal 371995 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 + +`; + +const GENERATED_STRINGS_SNIPPET = ` + OpenLess uses accessibility to detect the keyboard and paste dictation results without switching your current keyboard. +`; + +function printHelp() { + console.log(`Usage: node scripts/copy-android-scaffolding.mjs [options] + +Copy Kotlin scaffolding and XML resources into gen/android after \`tauri android init\`. + +Options: + --dry-run Print planned copies without writing + --help Show this help text +`); +} + +function parseArgs(argv) { + let dryRun = false; + for (const arg of argv) { + if (arg === '--help' || arg === '-h') { + printHelp(); + process.exit(0); + } + if (arg === '--dry-run') { + dryRun = true; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + return { dryRun }; +} + +function ensureDir(path, dryRun) { + if (dryRun || existsSync(path)) { + return; + } + mkdirSync(path, { recursive: true }); +} + +function mergeStringsXml(dryRun) { + const stringsPath = join(genRoot, 'res/values/strings.xml'); + if (!existsSync(stringsPath)) { + const content = ` +${GENERATED_STRINGS_SNIPPET} + +`; + if (dryRun) { + console.log(`[dry-run] Would create ${stringsPath}`); + return; + } + ensureDir(dirname(stringsPath), dryRun); + writeFileSync(stringsPath, content, 'utf8'); + console.log(`Created ${stringsPath}`); + return; + } + + const existing = readFileSync(stringsPath, 'utf8'); + if (existing.includes('openless_accessibility_description')) { + console.log(`OpenLess strings already present in ${stringsPath}; skipping.`); + return; + } + + const updated = existing.replace('', `${GENERATED_STRINGS_SNIPPET}\n`); + if (dryRun) { + console.log(`[dry-run] Would merge OpenLess strings into ${stringsPath}`); + return; + } + writeFileSync(stringsPath, updated, 'utf8'); + console.log(`Merged OpenLess strings into ${stringsPath}`); +} + +function copyDirectoryContents(srcRoot, destRoot, dryRun) { + if (!existsSync(srcRoot)) { + throw new Error(`Missing Android icon resources: ${srcRoot}`); + } + + ensureDir(destRoot, dryRun); + for (const entry of readdirSync(srcRoot)) { + const src = join(srcRoot, entry); + const dest = join(destRoot, entry); + if (statSync(src).isDirectory()) { + copyDirectoryContents(src, dest, dryRun); + continue; + } + if (dryRun) { + console.log(`[dry-run] Would copy ${src} -> ${dest}`); + continue; + } + ensureDir(dirname(dest), dryRun); + copyFileSync(src, dest); + console.log(`Copied ${dest}`); + } +} + +function main() { + const { dryRun } = parseArgs(process.argv.slice(2)); + + if (!existsSync(join(appRoot, 'src-tauri/gen/android'))) { + throw new Error( + `Generated Android project not found under src-tauri/gen/android.\nRun "npm run tauri -- android init --ci" first.`, + ); + } + + ensureDir(kotlinDest, dryRun); + ensureDir(resXmlDest, dryRun); + copyDirectoryContents(androidIconRoot, resDest, dryRun); + + for (const file of KOTLIN_FILES) { + const src = join(kotlinRoot, file); + const dest = join(kotlinDest, file); + if (!existsSync(src)) { + throw new Error(`Missing scaffolding file: ${src}`); + } + if (dryRun) { + console.log(`[dry-run] Would copy ${src} -> ${dest}`); + continue; + } + copyFileSync(src, dest); + console.log(`Copied ${file}`); + } + + for (const [relSrc, destName] of XML_FILES) { + const src = join(manifestsRoot, relSrc); + const dest = join(resXmlDest, destName); + const content = existsSync(src) + ? readFileSync(src, 'utf8') + : GENERATED_ACCESSIBILITY_CONFIG; + if (dryRun) { + console.log(`[dry-run] Would write ${dest}`); + continue; + } + writeFileSync(dest, content, 'utf8'); + console.log(`Wrote ${destName}`); + } + + mergeStringsXml(dryRun); +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +} diff --git a/openless-all/app/scripts/merge-android-overlay-manifest.mjs b/openless-all/app/scripts/merge-android-overlay-manifest.mjs new file mode 100644 index 00000000..66810c2f --- /dev/null +++ b/openless-all/app/scripts/merge-android-overlay-manifest.mjs @@ -0,0 +1,174 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const targetPath = fileURLToPath( + new URL('../src-tauri/gen/android/app/src/main/AndroidManifest.xml', import.meta.url), +); + +const PERMISSIONS = [ + 'android.permission.SYSTEM_ALERT_WINDOW', + 'android.permission.FOREGROUND_SERVICE', + 'android.permission.FOREGROUND_SERVICE_MICROPHONE', +]; + +const APPLICATION_SNIPPET = ` + + +`; + +const SERVICE_SNIPPETS = [ + ``, + ` + + + + + `, + ``, + ``, + ``, +]; + +function printHelp() { + console.log(`Usage: node scripts/merge-android-overlay-manifest.mjs [options] + +Merge overlay / accessibility declarations into generated AndroidManifest.xml. + +Options: + --dry-run Print planned changes without writing + --help Show this help text +`); +} + +function parseArgs(argv) { + let dryRun = false; + for (const arg of argv) { + if (arg === '--help' || arg === '-h') { + printHelp(); + process.exit(0); + } + if (arg === '--dry-run') { + dryRun = true; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + return { dryRun }; +} + +function permissionExists(manifestXml, permissionName) { + const escaped = permissionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`]*android:name="${escaped}"[^>]*\\/?>`).test(manifestXml); +} + +function mergePermissions(manifestXml) { + let content = manifestXml; + let changed = false; + for (const name of PERMISSIONS) { + if (permissionExists(content, name)) { + continue; + } + const line = ` `; + const applicationIdx = content.indexOf(''); + } + content = `${content.slice(0, applicationIdx)}${line}\n${content.slice(applicationIdx)}`; + changed = true; + } + return { content, changed }; +} + +function ensureApplicationName(manifestXml) { + if (/android:name="\.OpenLessApplication"/.test(manifestXml)) { + return { content: manifestXml, changed: false }; + } + const updated = manifestXml.replace( + /)/, + ''); + if (closingIdx === -1) { + throw new Error('Target manifest is missing '); + } + + for (const snippet of SERVICE_SNIPPETS) { + const marker = snippet.match(/android:name="([^"]+)"/)?.[1]; + if (!marker || snippetExists(content, marker)) { + continue; + } + content = `${content.slice(0, closingIdx)} ${snippet}\n${content.slice(closingIdx)}`; + changed = true; + } + + return { content, changed }; +} + +function main() { + const { dryRun } = parseArgs(process.argv.slice(2)); + + if (!existsSync(targetPath)) { + throw new Error( + `Generated Android manifest not found: ${targetPath}\nRun "npm run tauri -- android init --ci" first.`, + ); + } + + let content = readFileSync(targetPath, 'utf8'); + let changed = false; + + for (const step of [mergePermissions, ensureApplicationName, mergeApplicationChildren]) { + const result = step(content); + content = result.content; + changed = changed || result.changed; + } + + if (!changed) { + console.log(`Overlay manifest entries already present in ${targetPath}; skipping merge.`); + return; + } + + if (dryRun) { + console.log(`[dry-run] Would merge overlay manifest entries into ${targetPath}`); + return; + } + + writeFileSync(targetPath, content, 'utf8'); + console.log(`Merged overlay / accessibility entries into ${targetPath}`); +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +} diff --git a/openless-all/app/scripts/merge-android-v1-manifest.mjs b/openless-all/app/scripts/merge-android-v1-manifest.mjs new file mode 100644 index 00000000..e5982d8d --- /dev/null +++ b/openless-all/app/scripts/merge-android-v1-manifest.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const targetPath = fileURLToPath( + new URL('../src-tauri/gen/android/app/src/main/AndroidManifest.xml', import.meta.url), +); +const sourcePath = fileURLToPath( + new URL('../android/manifests/AndroidManifest.v1.snippet.xml', import.meta.url), +); + +const PERMISSION_LINE_RE = + /]*android:name="([^"]+)"[^>]*\/?>/g; + +function printHelp() { + console.log(`Usage: node scripts/merge-android-v1-manifest.mjs [options] + +Merge APK v1 permissions from android/manifests into the generated +AndroidManifest.xml (post \`tauri android init\`). + +Options: + --dry-run Print planned changes without writing the manifest + --help Show this help text + +Target: ${targetPath} +Source: ${sourcePath} +`); +} + +function parseArgs(argv) { + let dryRun = false; + for (const arg of argv) { + if (arg === '--help' || arg === '-h') { + printHelp(); + process.exit(0); + } + if (arg === '--dry-run') { + dryRun = true; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + return { dryRun }; +} + +function extractPermissionLines(snippetXml) { + const lines = []; + for (const match of snippetXml.matchAll(PERMISSION_LINE_RE)) { + lines.push({ name: match[1], line: match[0] }); + } + if (lines.length === 0) { + throw new Error( + `Source manifest snippet does not contain any uses-permission entries: ${sourcePath}`, + ); + } + return lines; +} + +function permissionExists(manifestXml, permissionName) { + const escaped = permissionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`]*android:name="${escaped}"[^>]*\\/?>`).test(manifestXml); +} + +function mergePermissionLines(manifestXml, permissionLines) { + const missing = permissionLines.filter((permission) => !permissionExists(manifestXml, permission.name)); + if (missing.length === 0) { + return { changed: false, content: manifestXml }; + } + + const insertionBlock = (indent) => missing.map((permission) => `${indent}${permission.line}`).join('\n') + '\n'; + + const applicationIdx = manifestXml.indexOf(''); + if (closingManifestIdx === -1) { + throw new Error(`Target manifest is missing : ${targetPath}`); + } + + const indent = ' '; + return { + changed: true, + content: `${manifestXml.slice(0, closingManifestIdx)}${insertionBlock(indent)}${manifestXml.slice(closingManifestIdx)}`, + }; +} + +function main() { + const { dryRun } = parseArgs(process.argv.slice(2)); + + if (!existsSync(targetPath)) { + throw new Error( + `Generated Android manifest not found: ${targetPath}\nRun "npm run tauri -- android init --ci" first.`, + ); + } + if (!existsSync(sourcePath)) { + throw new Error(`Source manifest snippet not found: ${sourcePath}`); + } + + const permissionLines = extractPermissionLines(readFileSync(sourcePath, 'utf8')); + const manifestXml = readFileSync(targetPath, 'utf8'); + const { changed, content } = mergePermissionLines(manifestXml, permissionLines); + + if (!changed) { + console.log(`APK v1 permissions already present in ${targetPath}; skipping merge.`); + return; + } + + if (dryRun) { + console.log(`[dry-run] Would merge APK v1 permissions into ${targetPath}`); + for (const permission of permissionLines) { + console.log(`[dry-run] Permission line: ${permission.line}`); + } + return; + } + + writeFileSync(targetPath, content, 'utf8'); + console.log(`Merged APK v1 permissions into ${targetPath}`); +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +} diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index fdde3bfe..dda80bcc 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -50,7 +50,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "libc", ] @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" @@ -422,7 +422,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cexpr", "clang-sys", "itertools", @@ -430,7 +430,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] @@ -457,9 +457,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -534,9 +534,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -545,19 +545,28 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecheck" @@ -644,7 +653,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -716,14 +725,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -776,9 +785,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -904,7 +913,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -917,7 +926,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -941,7 +950,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "libc", ] @@ -959,9 +968,9 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +checksum = "b9b4739a805a62757a83e5654fa3faabec0442666b263bb2287d5a8185bfd953" dependencies = [ "bindgen", ] @@ -1062,7 +1071,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.13.1", + "phf", "smallvec", ] @@ -1339,7 +1348,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -1347,9 +1356,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1453,9 +1462,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embed-resource" @@ -1637,7 +1646,7 @@ dependencies = [ "anyhow", "ferrous-opencc-compiler", "fst", - "phf 0.13.1", + "phf", "phf_codegen", "rkyv", "serde", @@ -1669,13 +1678,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -1771,9 +1779,9 @@ dependencies = [ [[package]] name = "foundry-local-sdk" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6f6b9ce8ef529348022814444562c54d7216417e6ed89af3d56807f52e5788" +checksum = "16fc2dc5ba41aeb97864c9ca63976f81ad867cf26d191d8f018559b180581082" dependencies = [ "async-openai", "futures-core", @@ -2041,11 +2049,10 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" dependencies = [ - "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.117", @@ -2089,7 +2096,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -2262,9 +2269,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -2320,9 +2327,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2365,9 +2372,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -2454,7 +2461,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -2633,7 +2640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2663,16 +2670,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is-docker" version = "0.2.0" @@ -2738,9 +2735,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "log", @@ -2751,9 +2748,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -2846,13 +2843,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2884,7 +2880,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "serde", "unicode-segmentation", ] @@ -2973,14 +2969,11 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", "libc", - "plain", - "redox_syscall 0.7.5", ] [[package]] @@ -2989,7 +2982,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", ] @@ -3027,9 +3020,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lru-slab" @@ -3086,9 +3079,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -3148,9 +3141,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -3169,9 +3162,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" +checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" dependencies = [ "crossbeam-channel", "dpi", @@ -3231,7 +3224,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys 0.5.0+25.2.9519653", @@ -3245,7 +3238,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys 0.6.0+11769913", @@ -3284,7 +3277,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "byteorder", "derive_builder", "getset", @@ -3319,7 +3312,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -3380,9 +3373,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -3498,7 +3491,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -3514,7 +3507,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", @@ -3528,7 +3521,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -3539,7 +3532,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3561,7 +3554,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", ] @@ -3572,7 +3565,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -3617,7 +3610,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -3644,7 +3637,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -3656,7 +3649,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -3669,7 +3662,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -3680,7 +3673,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3692,7 +3685,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", @@ -3704,7 +3697,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3717,7 +3710,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -3729,7 +3722,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-cloud-kit", @@ -3760,7 +3753,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-app-kit 0.3.2", @@ -3805,9 +3798,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.4" +version = "5.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" dependencies = [ "dunce", "is-wsl", @@ -3842,10 +3835,12 @@ dependencies = [ "hmac", "hyper", "hyper-util", + "jni 0.21.1", "keyring", "libc", "local-ip-address", "log", + "ndk-context", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -3861,6 +3856,7 @@ dependencies = [ "sherpa-onnx", "simplelog", "subtle", + "tao", "tar", "tauri", "tauri-build", @@ -3884,11 +3880,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.79" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "foreign-types 0.3.2", "libc", @@ -3915,9 +3911,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -4014,7 +4010,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -4062,24 +4058,14 @@ dependencies = [ "indexmap 2.14.0", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", - "phf_shared 0.13.1", + "phf_macros", + "phf_shared", "serde", ] @@ -4089,18 +4075,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", + "phf_generator", + "phf_shared", ] [[package]] @@ -4110,20 +4086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", + "phf_shared", ] [[package]] @@ -4132,22 +4095,13 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "syn 2.0.117", ] -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "phf_shared" version = "0.13.1" @@ -4180,12 +4134,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plist" version = "1.9.0" @@ -4218,7 +4166,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -4320,7 +4268,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -4347,28 +4295,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -4412,9 +4338,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.39.3" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] @@ -4588,16 +4514,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_syscall" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" -dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -4644,9 +4561,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -4667,9 +4584,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rend" @@ -4730,9 +4647,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -4813,7 +4730,7 @@ checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" dependencies = [ "bytecheck", "bytes", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "indexmap 2.14.0", "munge", "ptr_meta", @@ -4856,7 +4773,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -4880,9 +4797,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -5050,7 +4967,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5063,7 +4980,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -5086,12 +5003,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cssparser", "derive_more", "log", "new_debug_unreachable", - "phf 0.13.1", + "phf", "phf_codegen", "precomputed-hash", "rustc-hash", @@ -5164,9 +5081,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -5218,11 +5135,12 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64 0.22.1", + "bs58", "chrono", "hex", "indexmap 1.9.3", @@ -5237,9 +5155,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -5339,6 +5257,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "sigchld" version = "0.2.4" @@ -5417,15 +5341,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -5446,7 +5370,7 @@ dependencies = [ "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "tracing", "wasm-bindgen", "web-sys", @@ -5499,7 +5423,7 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.13.1", + "phf_shared", "precomputed-hash", ] @@ -5509,8 +5433,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -5585,7 +5509,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5615,11 +5539,11 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.2" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "core-foundation 0.10.1", "core-graphics 0.25.0", @@ -5666,9 +5590,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -5683,9 +5607,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d059f2527558d9dba6f186dec4772610e1aecfd3f94002397613e7e648752b66" +checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" dependencies = [ "anyhow", "bytes", @@ -5711,7 +5635,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.13.3", + "reqwest 0.13.4", "serde", "serde_json", "serde_repr", @@ -5734,9 +5658,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.6.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9aa8c59a894f76c29a002501c589de5eb4987a5913d62a6e0a47f320901988" +checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" dependencies = [ "anyhow", "cargo_toml", @@ -5755,9 +5679,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e4e8230d565106aa19dfbaa01a7ed01abf78047fe0577a83377224bd1bf20e" +checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" dependencies = [ "base64 0.22.1", "brotli", @@ -5782,9 +5706,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8de2cddbbc33dbdf4c84f170121886595efdbcc9cb4b3d76342b79d082cedc" +checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -5796,9 +5720,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d5f58bfd0cdcfdbc0a68dc08b354eea2afc551b421de91b07b69e0dd769d57" +checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" dependencies = [ "anyhow", "glob", @@ -5899,7 +5823,7 @@ dependencies = [ "thiserror 2.0.18", "tracing", "windows-sys 0.60.2", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -5918,7 +5842,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest 0.13.3", + "reqwest 0.13.4", "rustls", "semver", "serde", @@ -5937,9 +5861,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.11.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e42bbcb76237351fbaa02f08d808c537dc12eb5a6eabbf3e517b50056334d95" +checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" dependencies = [ "cookie", "dpi", @@ -5962,9 +5886,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.11.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117" +checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" dependencies = [ "gtk", "http", @@ -5988,9 +5912,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.9.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f61d2bf7188fbcf2b0ed095b67a6bc498f713c939314bb19eb700118a573b7" +checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" dependencies = [ "anyhow", "brotli", @@ -6004,7 +5928,7 @@ dependencies = [ "json-patch", "log", "memchr", - "phf 0.11.3", + "phf", "plist", "proc-macro2", "quote", @@ -6181,9 +6105,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.2" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -6247,11 +6171,11 @@ dependencies = [ "futures-util", "log", "rustls", - "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tungstenite", + "webpki-roots 0.26.11", ] [[package]] @@ -6306,7 +6230,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6362,14 +6286,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6378,7 +6302,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6404,20 +6328,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -6530,9 +6454,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "uds_windows" @@ -6600,9 +6524,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -6718,9 +6642,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6811,9 +6735,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -6824,9 +6748,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -6834,9 +6758,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6844,9 +6768,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -6857,9 +6781,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] @@ -6918,7 +6842,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap 2.14.0", "semver", @@ -6943,7 +6867,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "rustix", "wayland-backend", "wayland-scanner", @@ -6955,7 +6879,7 @@ version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -6967,7 +6891,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -6996,9 +6920,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -7020,7 +6944,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ - "phf 0.13.1", + "phf", "phf_codegen", "string_cache", "string_cache_codegen", @@ -7300,6 +7224,19 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-future" version = "0.2.1" @@ -7806,9 +7743,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -7906,7 +7843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "indexmap 2.14.0", "log", "serde", @@ -8099,9 +8036,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8154,9 +8091,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -8181,10 +8118,10 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.2", - "zbus_macros 5.15.0", + "winnow 1.0.3", + "zbus_macros 5.16.0", "zbus_names 4.3.2", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -8202,17 +8139,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", "zbus_names 4.3.2", - "zvariant 5.11.0", - "zvariant_utils 3.3.1", + "zvariant 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -8233,24 +8170,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 1.0.2", - "zvariant 5.11.0", + "winnow 1.0.3", + "zvariant 5.12.0", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -8259,9 +8196,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -8449,16 +8386,16 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.2", - "zvariant_derive 5.11.0", - "zvariant_utils 3.3.1", + "winnow 1.0.3", + "zvariant_derive 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -8476,15 +8413,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", - "zvariant_utils 3.3.1", + "zvariant_utils 3.4.0", ] [[package]] @@ -8500,13 +8437,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", "serde", "syn 2.0.117", - "winnow 1.0.2", + "winnow 1.0.3", ] diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 3dd38f47..5f6d0bcc 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -18,11 +18,10 @@ cc = "1.1" [dependencies] # 锁 ~2.11 因为 npm @tauri-apps/api 与 plugin-dialog 都已升 2.11; # tauri build 跨 minor 一致性检查会拒绝 npm 2.11 + Rust 2.10 的组合。 -tauri = { version = "~2.11", features = ["macos-private-api", "tray-icon"] } +# macos-private-api must live in [dependencies] so tauri_build can match tauri.conf.json +# macOSPrivateApi; tray-icon stays desktop-only in the target table below. +tauri = { version = "~2.11", features = ["macos-private-api"] } tauri-plugin-shell = "2" -tauri-plugin-updater = "2" -tauri-plugin-single-instance = "2" -tauri-plugin-autostart = "2" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -39,9 +38,9 @@ getrandom = "0.3" bzip2 = "0.4" tar = "0.4" tokio = { version = "1", features = ["full"] } -tokio-tungstenite = { version = "0.24", features = ["rustls-tls-native-roots"] } +tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] } futures-util = "0.3" -reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls", "native-tls", "stream", "system-proxy"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls", "stream", "system-proxy"] } zip = "2" thiserror = "1" anyhow = "1" @@ -56,10 +55,17 @@ bytes = "1" url = "2" raw-window-handle = "0.6" ferrous-opencc = "0.4" +# Audio capture — shared across desktop and mobile. +cpal = "0.15" -# Hotkey + audio + insertion +# Desktop-only plugins, hotkey/insertion helpers, and tray-icon (not built for mobile). +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +reqwest = { version = "0.12", default-features = false, features = ["native-tls"] } +tauri = { version = "~2.11", features = ["macos-private-api", "tray-icon"] } +tauri-plugin-updater = "2" +tauri-plugin-single-instance = "2" +tauri-plugin-autostart = "2" global-hotkey = "0.6" -cpal = "0.15" enigo = "0.2" arboard = { version = "3", features = ["wayland-data-control"] } rcgen = "^0.13" @@ -83,11 +89,16 @@ features = ["windows-native"] [target.'cfg(target_os = "linux")'.dependencies] dbus = "0.9" -[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.keyring] +[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies.keyring] version = "3.6.3" default-features = false features = ["linux-native-sync-persistent", "crypto-rust"] +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.21" +ndk-context = "0.1" +tao = "0.35" + [target.'cfg(target_os = "macos")'.dependencies] block2 = "0.5" core-foundation = "0.10" diff --git a/openless-all/app/src-tauri/build.rs b/openless-all/app/src-tauri/build.rs index fe64d59f..c7332edb 100644 --- a/openless-all/app/src-tauri/build.rs +++ b/openless-all/app/src-tauri/build.rs @@ -5,9 +5,20 @@ fn main() { #[cfg(target_os = "macos")] build_qwen_asr_macos(); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") { + link_android_cpp_runtime(); + } + tauri_build::build(); } +/// cpal → oboe → oboe-sys 会编译 C++;最终 cdylib 需显式链接 NDK libc++。 +fn link_android_cpp_runtime() { + // oboe-ext 已部分静态链入 libc++;补链 c++abi 提供 __cxa_pure_virtual 等 ABI 符号。 + println!("cargo:rustc-link-lib=c++_static"); + println!("cargo:rustc-link-lib=c++abi"); +} + #[cfg(target_os = "windows")] fn link_windows_common_controls_v6_manifest_dependency() { let mut source_path = std::path::PathBuf::from( diff --git a/openless-all/app/src-tauri/capabilities/default.json b/openless-all/app/src-tauri/capabilities/default.json index 760d74ed..759ae4f2 100644 --- a/openless-all/app/src-tauri/capabilities/default.json +++ b/openless-all/app/src-tauri/capabilities/default.json @@ -2,6 +2,7 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Default capabilities for OpenLess windows", + "platforms": ["macOS", "windows", "linux"], "windows": ["main", "capsule", "qa", "less-computer", "less-computer-glow"], "permissions": [ "core:default", diff --git a/openless-all/app/src-tauri/capabilities/mobile.json b/openless-all/app/src-tauri/capabilities/mobile.json new file mode 100644 index 00000000..911ad21d --- /dev/null +++ b/openless-all/app/src-tauri/capabilities/mobile.json @@ -0,0 +1,18 @@ +{ + "$schema": "../gen/schemas/mobile-schema.json", + "identifier": "mobile", + "description": "Capabilities for OpenLess Android main window", + "platforms": ["android"], + "windows": ["main", "qa"], + "permissions": [ + "core:default", + "core:window:default", + "core:window:allow-show", + "core:window:allow-hide", + "core:window:allow-set-focus", + "core:webview:default", + "core:event:default", + "shell:allow-open", + "dialog:default" + ] +} diff --git a/openless-all/app/src-tauri/icons/android/drawable/ic_overlay_logo.xml b/openless-all/app/src-tauri/icons/android/drawable/ic_overlay_logo.xml new file mode 100644 index 00000000..12c75565 --- /dev/null +++ b/openless-all/app/src-tauri/icons/android/drawable/ic_overlay_logo.xml @@ -0,0 +1,4 @@ + + diff --git a/openless-all/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/openless-all/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png index 776a8ca29bc6645d4d8366b6b0ce9471edd99516..f869615717bf16ae17a2fe793ac5ca28929abbc6 100644 GIT binary patch delta 2021 zcmV;v_)0hM5-E9QPZkT6%`UG1=@`uO)0IKMs3yp2ycgm zD9Bhe7@J+wu>qSk-noy{AG5oxZH#vStNGzi8p*ped(ZjKxqs)(J)eP5jA9g{7{#Xr zM8rRB>$9K+7-Kg3w*YW630cON8EB^%IK@6lM2seMIO8Dk%2?EwJY==uP_h=>7z>$)4Y*5_yhVT?Z4(cx~)lH&-tTds=S z9(7%JgKxxm#(x;4wYJ7svbO(aj3KQxDWwKnnv5}np7(!a$zRVXV~j^ci&6%9TLr2+==cysdR-Wg%$z*a?F+_%|2?BsvEVj;ZoN)jY z5lNye+mtZ|V+^b%Na-)v)pY@FZSCmn?1C~Hkw^qpm49*6*498u2_nE44I#w9pn)+a z9fvDGjElu$>i}%VFm-WF5{xlQBocyiwh{nYYeiulGsd8`hT}L80@#1xFy8+CJ~TD` z8_84$(&;qF5<(>*jIAg~{nR=vUAhPl+rBt_+GR7EFH%vln)#W%cn%hQ& z8Tjb<$9UqYXK>=gNrXxzrc9oU<)2@I#>P8PU4K&r>4ebJ(~XvvQ~2k<-bYi@hu}h> zqP!ey);x+et5+enTLx!YU2+_G>u@B7MK%*Xg~hD3CL&AC%`J4>?Q{z4iv)``bTIT2_LM8`fd{H`hWb1!D{x$H^AE$X|}*fO7`V z^H5V$g>OH<8C6x|VT{E)e|Q(^bT2q(D1YpH#Uc<1Yb_kd!G#MK@#b%K!FV1PE|`Zc z&u)ZL8k~zkZL>cZq?Axfp}J~3);zunG9=NS>c9ubJ_6@FU+5!9g1{KV`}+^#`0*Zql0pbrYtcBT0aNQHqtorefrCvj#(;AkI2%PD0Dbt2A31Uiy?^QR znB6c3i|$?kA{r(G1HoXeK`0c$JxdnCT8n5jiga%u#zZ490kaW^1m_$e;FHs>06a{s zn}T>e7C3U8GjQ&kUc6`?7B8Bcy90p0c(5~Hz)Ub0Yj7!j2>>v5Yy}uAz8Eb5f-|@m z6}JBRJi7Zh!X*MB>7Xp^z%U1o+<(n6Z(6W^6OaVJ7>9KHuIH&>?;r+%2nb1zyJnW+ zvSKI;3CI!&>2N3m#KtfX0YRpeKqQDPgm6$9{|o?*_OnT3G8u$Ip`xgz@-l|raQqOxi{010dDp!3wV5GZdsM`z5K4kn^#Za#?3X=NwvV)YnhN7gj6-z_IJMyK&^`2ar-ClgSLbMxm2CcJ4-N zYdgY`GR&VpH-A6q*d90&Ie!DsJpFY{m{0@Pb+P5S@1d>jEJ{mDp_GQT7DUwVverT= z1%N>af$gvFz}`Rp1(z@P;;#8~QC~M1#ux}O0*cVp4uGELkD#x=v4hIT)l$5=p6*@# zARRh#G=B)p&D*(AG>7h<9(wuZU(lR87t_qoE~M3K)>C(PPj1e>u73mIb&)W}FPZhe8~&0j_}6K7EQt&=EPUQ5yP33N+EEydzf>9*;2(pMf@O}l=#ry$yj3bUNe zwDIlf=0qYf?4j(MnIK<S%;{*H(|~X|3}gJ+UkEX%t!5)HwbnnAQf|)qQFP=7kSI!bBC>hGY#<7Y zI``T0ysK(Y+4}Lc4yHR5O{5qF(BEDTa$?r&lF{ZDm;&hW$agh>&?K^X(ecQx|6OR*-lNHT|t%V>C zFv`mNS;Hg!{|UlPlJy7SX6@1c*ZQLv#VAHGia_`ukr{x{22r8U00000NkvXXu0mjf D75vEt delta 2631 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 Cxho(5 literal 18326 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 hAHVKt2J6HIxV# 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 JPDHLkV1hojoOl2L delta 2483 zcmV;k2~75p54;nQBYz2lNklsB@B-|Ayf?^ zN%Pq3ZuW7v=lr`jY?6HkXWA(j?%eEs{Qviz*MH9W75tAM%71kN*9m;a1hTWUlbf5H z?=ct*t5wxKhi;|NDSbwGR66@4N&cI|;dmiGKmW+3OPBgXeGn2zU0r>7M@PqTS(f+H z&k7niO{ddIbmHm>jQ$bMRa|C(m79Uti8xcJJOT2g0Ze2EL)8VO3dK*$YEM zR+vojPLIdKWPfFvgD#$HDs~1><0VM#RLRaF(h87SGo zIKcc|RlUR{Nf$98KN270l5QfvD@wUv;EQOq<^3Ka1*%QZm_`hdc}VX3=ca5 zXa=g#Gn3H>NtQ>*xZNrgMUGS%PMSwgXO+=tJP?uq4St-Rh@{DO7;*-u(+P@B;_uGO zUHItK8T|FbzoV(S72Q3(a1aSumXMT`gzV{ADEjh^C@Pwbf`WWOjK3#Ah1G^1mO9ez zf{+AUE`MhaM*__pf;pVabip89z2BFKfFc2+vpidmO!zzbz%bV`z&!jGBPHi zyzDDjxpJ8xq-l}OE|gF4ycEWJ6-A#Oj)2=6Dy5)kS+J*$P8Af9q znT{RA0Wy)(%X;xXOPY!Bb2cZM0+S}E7m=8l1hY9onAqI8#n|%GjYv&R2}y#iNcb8N(8WYR3ym!OhaTRHU;Oe{$jQzY z4u5M-GGooRR-kIlYD}9pHK-Kr!^0G!-@*O^FXF<5OUTU3gv~yTLh^|xwr)mBa#B!p z@@XJ;0uF~0ad8Gbwf$*4_T#O{$(@e={z2R{XEt^|{UpA0L*b}!8tBlZJ4{4&?%IRe zH|vp^kwHRmplHsGc>J-4MPv%(e^F|HoqzSJD%lJSdOc45={+p2Tu!zeFC>4<&80Ye z=+|TpCi*>0POTR)K6K=wu?*hmypJ?-=#}4N3X!nc?D)pgJF()McLy}3U>XSG`npke zuU8bI{_RigAX~SIdoyO_;^1$d6X0z`h8;8|B17uTKd;cmd`g#TW2L{P65`Xc> zlk6iM1X3A^THy{UlC*%;SzrG)jvjA7T52kW9m9BX+ZLp!rwL8O(;^kjYyyqP0JZn4 zAGi;BWR7mtjRA{=NHmVik0wNb9g3xO`0x>^bhX%qaL4WCSa|zot@w{Brh*lL>^5$6RKWlc$I9OwNOT@ z^Xb#)*h%4Vx~@${rddW)bBj>7hYF^`f?4FaW*<4-gb2vw^s>+Kmo8a=%6~;)8_8x_ z{IaYQBz4geU~K+jxTm*I48qYTYig#5IKgtINDnt5`>4&1o&kzXdQQJRB8OtoJh={T zPD^7v3Da_ ztF+E^UO{8yha&CnqBAoiJpgE3`;**xvR;t8y`qFGiOyF4n zT#21Ia~`j~{-!XM{(%9M6wemL4wH+Sz}Q@f7qNmbK7ZHWKY*>%aF1&Yf?jFe!;BRJCS>kUk46(lkYi zEOtwxB1=k2#522hU~*czC_wmr&4x$u#I~PfXlO|I1mDvp*MPHm@I9`!xKKHItRBl( zuEQy6!*Cw-DSzcz53IWn^X3%`@LG}=kvT?EkzIY}5>;1Y;|EwfRA;1+)EV+A1L%TmG8h=I=Al?xVW)2tNM! zQ?D;5IssaQSD!?goI>x*Bor&zI{QFH#clY(_p5Qk7YjyGli=W^XlzB&TI6{cB$MEy z{lY~YBY(R-UiUVwrfp;vLn4>qfBc{Gn>f7zQ!*!0BVaD>x@#eBo>$`YEyu_>*QB)= zO>@`VaaKnFSjdsDbajgYsEe*va&(zi+Eg;Jyxbg2r~W86zqD*tGskP%+>NEZuC2zQ zT7zTT?2)$E!?oAPc`Cm^n+cnEQ-MF^0U`;re1B>hVadAO-mCwqWv^VYM+u%cI5q{^ zFAErIcqfFnbu<#JLbk(j-3QGPMa&nmUlxBe;6R%m!5o(7;yE?&jD<1r7E?q7ctef% zqd1K7GoKJ-o|ETh42*>_jolv$ xxh%=2ZK(Y>Y_kU1+zU(K`cD0I0{ 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 literal 9395 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 fGA3j4vB`e{2g=A0qzk{e30UT04lhMzgpg1!6p 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
    ue^ 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
    hKQ_rlSKlo$*Tg(p2`6|5YNBo32!eWGlx zd!n)H(_L*GfZ`7Ai_qpl64o^v|M&E=X*@|o3B}<}DRWkxC2k!po7UW8$s((LZ0G0Z zgwYfYa7qM()pF}BI3!8&+)Z&<%PH+rf?uDRSE>eMEE8v6wRU1Repkj)R6gNS5LJu5 z(iT36_doCSqv1(U0N90uW)dpVJP@-j&Na7}v4&(_<2)LGiW*tLD~`TQ?zqE?yw2XZ zh4-&Z|7r@}wmKhcLvg2`08>-s%)=#YhURL@3Mu(93`MF4#E6 zL46R`tXT~clM~H;jd_-Mt*bT`^jbzy4c}%3U?foXo;`c8kGOlx*SSyjMheC)2N#(m zp%s(O+Z3u(1X)wv^p+wR$2b6Xx%is=c)3Q)Y7&b%BbJHF&=O<$%7LPb+B!OA>t|cQ zalp=k`k<`k>%tr@CFXv7CAeZp=#1%sG2=c+MI^NOVyP&B0~w5&oXArq&7m%tW90JO z=Qck}94-|dE0&rTXUbjQZ|&MOFgZ1Z`2|@D@G;m#^MspO2y?v-uPISO>`{_{#Liv2 zEZD6)ieiiscGqktml2cVZIX*;3{fAKA|nmS$>lf&x{kAtYg@s(6f{N*DFzf!CX~9h z0u?fY0%ry*UROSq+0(egiep~f%G4TFQacbj0OG~#itGvfi{x)b8}f9Fq^x8@m|U5 zbZ&kYb{QFB>_Ei#S=&|wF1~WRz2MtCQnSL4n5*ei5poxdfdRSNCVdW*VumqGF?n~X ziZ{JWxbjtzgAJ}=H4?zAGpkZeb$QioD-7_54@$+2VA-S&wlhxFQUu@)eik1Ku47@6 zxsV)^oua+oSj+BL3q3NGSZI}O@te{Z1eGcv7QAUsU_lWqwZ`YL@6sei{4|B{Lt_%z*o!0l8;a-T6% zP6nAHP?3r-=%d%SFZC1ck7@S$SVNOj&;6LPDm62|!U~oc!$)vt1jSlpVXRgx?Z68L zhMS)8xY{n1*K{uD=VvW*x0(uQ&Wpvfv9i{QcYfO)=u17d8SD<@OX znVb%YGw64+eEBj|5LrYl*JLwG=jPtW8^5K3N&%H>- z9|;(aUQoG~nYqU)rZmgZK#Wz*b6`(?oq`a#vt>?_B1Pic5^BYzWVI?6Dg}nl0E8-~k+SunItc zDFVUjl36Q$kYe^dSTb+AYqNLS8Y->20)S(nHNF6o${SDRp@#%LbA)Dd>?l)xzheeS zDLm&Ck&9HWcg!YIZ#Yd002v%v1s#)tx-hcO!mt(%t5=0oifjRLDJ=k&qIxLy6%)WG z?wC?G)7Npw9gBED$s#G3rV4RKwr>+)Wp$iX^Z-F4(p*r4-VhFP)17xV)juK_At@JW zpA*uhDlJnG0jZeAzD(o0UR1H0Ta2)8-$)IAwN*Gbp}LpJEZ2~kO}5uBOAgApn^Kqa zprZ5eHhu4Eq)Y`GgsOy+3q zyU` z^*c!kCN`fGpFEe#t0X2HN%4j8Q4?cJx)kbRPS)hxaASMK%p4Gs^bC~7jT`;Lk+_wZ zRG585^Y8SxQVHAYN~swG%Aj6o$m2RSgKlrQeF`iOyJT#>YcIt+7Xs{yrX*Mit5&Uq zqmMocZoc`uz^0c0FTo83u)atsb&;Uy-z)*8@}N#mPQmwo@B`R--&Q#KsH1Sgy(skF zQZarx*;a0*(TCA{2)yl6CQ9i?<|vMLou-F^UQB@WWsxC>RA?ySbntTweB7I=6g$=! z+PI~_4a^01Nyj#LEUb`HV>w>fIVg*L@ipvbA<#T{IoP+DKwFk*iJhhFY_t?2o?YxkUDH}0?>48& z-vyJ`b%#yyEs0^L*i@318WI*F5_Ij8hIAD}Z5bP;vwj>@Ldw1LSk1X48wcT(X8S2J zV5eod+zY?4;hOi8{1+b!hAI5nt{2>+9(5c(&rre&z`{N;i1lL_CDt_WG1a~F0i}|e zbP>Pxt()N4&$jHAgLLl&+~a_A`wxaJ$zVRkse9hWseZ%F7gUaAPZa#}tnN+0l% z9O~V^VvWRz$;OCT*hD5$rRrh*x_rX7X^0PP?ECr61 z3Ht%12f$3}u5s87SO$@zOIS+7DygiE;OfPBIO_km_a$(4RAs*3soQ(D&cYH1Sy%#Y zAq3g?QE|l$P;r@2XB-BRK^=8)WEkgxGK$ZqZ=N%P;y#LsGA=A4NMv8aDr=C0gaBbn zLXz%Ici&s*ecw5Cs!r9dI!jgEPIUV63%%c4x9Ze)zU9Bn-3g93;?U@rq1H*eZQfvR z$-u`Uchp&hih zuMh6O|9*Jt>8C{5Os^}&X{+sIM!6`@Ovn@{@-SdkWz2k8C{=d#y4wVGGGbf=O!In! zb+Cw%Yn%QZMO;|3W-Xdop%BW{K>>meq6|^^r|9a|nlCv|xlw}n-9!bvCCu!@NbZa7 zvGGv`^Y)kr1DKQ${EaHAiPp001k|ij577k1tO_+S^%m*E&)I1w*l)jmVePu7f%P+K z)Xj>;Em^c}@VuY{`HN8Y5ub*;?z#sSF4&{(=Bio>`UHx+$*rsHX_aZEi>{$5_~r$B zUj^?nHlG`)ZCVB7IDC1ArtL6N#UIyt5&T>u0N@9H)F+j>B|zN29zxF z4^bI7(Qb&uDo zk?8b183UdDmn?xPSk`i`0jz=Li^E!up6h)8mQbB~5!SQfjyqu^>Mu$QRI@RA(O|)s zJ8Yvj`U0%WKzZs^>vjz)Mc&J0Y+cK4B2~^;8K8L`rC(ewV=!F5{#gMV0FFR$zhfE6 zF&?q5RG^c=E((z1zgA-LtK`NwkCKc;;DiF`)M-;-_j$XCgdtb%%n%e!cCtRnQ8ATu zJ7tKjFYXFZwcYsoIGFyw^nj(X^DaB1M@2I(iFL+FOlD_c`_9p3zzr!(Vh~A@_YDld zAOE-t9(dp(@qH>4vL3O%*_Lm%-~*Mh`4*c3QWYBl7rwRaT-m&LeKW5zK;5#83SSDN zX~T4>G_vP{0v$abc;I0e86FjzxylB07KO+bqwG%&O62F!`C=Nt9<*zrol7dmcftE^ z)~rEsf4NAd)#3YHM!a_d10u^cf3JH-%ucUVA7pHF4Cc+7hrH%RI8AG&%(fF2N}{=@ zU*J54vBbhG5`>Mi%?3`ehlWPr#v5*N-rdTuDauN8xiU>`>=Tp1Nfl;>kM)2nYr_TH zUp2T_nTWawZrBukvixDwdJvp`Y=dka8jUvY0%2k;IPyqkF_!~fK;Pq`{M#1xCKn)m zw!gnu1k*qpz_&zbuQm(~a(4Sib!CuyMmC=` zIr?basrtpm3RI@YbkI0&6L;mh8(KyHgaJ!Y`KS@yhkZ(7tlV_-&Ejw9dwKq}c{f;I znd90gvt@Fo*YGJ4q)?IG-#qo2gW#4AwQovb|o`fXw~IW~l_=&O7gl>~mSt zBQy!8MnO_QYC<`95d)OAz9Ns42MV+^2B%|&Cv`;yx>H4Q2VT1Y zQ2NG92Sq94CL@=^Jsot=0kFsX-Ov~cg&qPXdLSX^vHW?KY!rW$QqpE=3)5J1RxSwo zH#9l|zx>se0q*QSyd5}hMh|QD}n&Ils~R(O5N~2 z)(oazt1nu@HAtJ(#a;To=HSp0HzHwUpLu>Gthnb7Sk-ikfTn_77!O4Xdfq%OSRw-= ze!C@~o*bYEhe$~VB_!0ZpS$xMgxpM(kRC){Fb&R=J=ayTa5r`A9V8&Srpa}b3@Z5Q zCxQ_9l&S5y%Pt7NS%!3_F#$SNeGUeG${=bOlNX2xkloYM1J^BI4u81+0qE=PPF&7P z0(e~%iCxu0>JToiG6}B|OzJM*eI}S?2VAa_WBKhLcdDix>@EW=iH_WV|AX+aSH~GjOYE#Fi_LwS+S(0O z;2u8WtghHQpMX@RPv}yj+j^F+a%(QbrXm&avMmT#jH)TX3*&n%x5vjh+d zfKcVH9^f*4McJn3kzrf=d!>7Tc>EM-`v*u^W$##C%*5^Y6@#A0jk6@~As~WIN zDVJyde)wSr!5+KKg>6{Yve=oCr$*o`Lf-4c87odt1Q`iWpv6D~l`F2i3Z8v#BMx!8 zB7HcsxMmsHO<564*U4s?$?Qb(yKA` zl1%1Rs%+kC(R?hM_eU|;5g~IZO{NTjh%9ZRookOO2<>4E*Acs8-vHcw_dRg=F%6RoRiFRUu~+LsODEALg*=PUz*D*C&;EU(T?CF}6#3bV;p zXt&&UJ8a!LB!nE&j14VI47n9Mo5CDYV2qe8VM~_L&9O1m5ZGIwJERqA!-y{MRk1Nu zHzt*!9SU|+SE^gqC`f(pb+0=fji;y?+)BBc>DeJ<&FM|R^1RoQ0!Z+qA6)`3Zr+MP zu`A|pW?>DcveBmITn3})0%vi}&Z_{o%S>EN%n)6)pe}0RMD{psEr_E2q77hA=Y;5U zPoucWZMWZvJ?=gcxKfh;8X-YvpF~~5n9iDiZuH(FiaB6As=K!VFL}vgr#6iZM_a`d z&0fjy9S3$}6AJLbA-N70sB}I2@WWu&U3Y@9ZQCNno3w>Mtm47)+rYM7mxSAdFlAs0 z+_~aTSbp8_Bcm&o-2{aK7p`1p*9N<<5tM6&Y6={#n_n3xF$1C6U3SXr`nP^&L;g2t8R68bA`(S7y)v(@L@mlX z0G73OEo1L0*;B=Vf!akIHf}-&)y=X}i9Ao_2CWd*N!7x0fY|hV30nm;xSBpxl<+1R zVD7iyelTrXe~xCOKc>`=t>%_c=&qXEjmzv~pj2n`J&V`t?hfD`?>Gq+f)ymD0d1W^ z?(SO0R7tJuIq?5F-G7@9PmK20Jy|_@o!07WEuD=yltzLuKVK)vuS|TW_A)thx zsmzb3+FS|8S(I0~j+H7j5+s+NyB`NOOHfQDU!=Cap_M5i$PRJpVgS`n-E&njwqdf0 zxQwPZuGFq+k-X|v$HKn*Ek=;`IPRL=SUNXE&reO4Z5v0s(LLAPzY||?T7V;?P59pT zFG2icC^gt4_c6}R&1;_^XM?}EWR*T511_K2rmH^C8XreSm%gUycZvYgbp}RXVO$f6 zLU*%ElDbgH-F4xn|Lk+m!_iwYqQ_9GNsTP4U4QbJxipVv#|PFSV*{pNxJqK0byD96RA~ z<+U)57f>)+xOU?6PF!|DSlR-TA=4%PWN^j|xEZN@*IaWg^k6Ts*$OMcTFQ%G)OS9M zggt%USKZ}wT`EGU_DL9)g4)ftl3bLHuAqWD(wdk3$cI{dmrgV!ysMKyk z&7|J6G3R~{lA()!j|4#o2OoS83=H7f$AFxEZub^Wkyf&?KHBNC3}VRT7eLcZNh~YeFL^^ z85ZKPt(GS{rUcWcib#X&#M1=5u~E||l6P8%IDLHfZ4s@BEQrb>(|5Pe3S@Lo?@~FdbDz;5rJk?KZ z$6B=+2PUt_^Miwu7!%ETTr~YtroxIl?}nfJ^wLN?)~*lfW|G5d>7|uru2dbu_y|f= z&4kN%`V^U+m9f!vrDpm|sxeJ8fyZub`rb+J5=BlT^SCN6ny0c6eOA!2kO7jc9{tb8nyON3uCcY#YTw*9@Njm+i*TR4P$3-c2OBI+wnV?feNY#at>zdit%~v&L z$v#}+A{n+aQe(b?5vJG6zXmEO>OmmD^*jnc`N_{=3$E*+(U8etE8#1rm9f*kK^|KN zr2Bt7o@Q&}4nlbzaG0_UUh%S*z}|c9DVSR7KC7!8;3LOYS8HOs=^?r_pX*>m=-Dcj z$dn~cK?3@lPj~}qoyQ<;UW6^wo+Q_?z%XT}1xDhuQ?PXY(GPzD*Z=-z+_`&G;+(Gj zvaY?oPLbnH!;wx%x4Ss~m7&Y|W)_7NM~7fJewGk&O;Ru-R?0QkEEf)teMli~V)@GT zQdb|qM1LNtGMR%W@DOU5q57wtr)a`W>$(RMjH8Y`9Pg_{Ri_G#)72xVsLfEM%ndt^ z+KvAX;p1dx!s4ZaZPH7mO^gE7NhiG>?FAO1;KOLfV}++9BWp)uGfhC&*2g2r1N9b% zM@HcPe)az$r*w-bql+e)CN-1%_r`10n(P!4sZ2>-#>}Y-$Mw)Cr>@~EL`^UUr4Lb5Hi6qg5)Sbc6KtI;B68dq9z0@GDPvD5!?rRmZS@?_udOd zr6v8y17~04vgNHgYlg}$j1v+NO$U>&+Urhlv#Hr9BI^6WWq6I|eRn+hv#~plN+En=cO*g@ReEpjuAdWX%F<2YG z-EyGHK%;DS$9KdQuzd9(U4nX157x$)ZV!f`p|30jN?Mo&QlGks zo9_3q0$KOedTi132r3=ZhA#S`FfN!g8-JKFi=wYvvAkWBJx037T-+O>$PW8@8&;5h z*|_0bA(k0>?GS1=KCqG^B@0(7pd6@VC(d2s^HWZFCoEX7JGzi#V7=*ld09kn6V;`ZUB2tt+3XT+L>F2}4 z!?4$$i{OxhUm4Q`i=@7)U{8zUgiQ~ileW4+Cx$6iO?&*QK0$(uqy!Hy-Zq5YEL^w; zoOI%eA|R%Ei2{Kid)P;Y-rNYuc41>sX87W!&2Zj%pBHL4B*!GAlXyOYz793#?BuhI z=~vxWo-Uc2Yp=9wKQFIB@!^%bl)dFLerC`a#ej#IAt~6bUHcS#?|c6RirTDTGz_wR zzns9BL(1nl*)V0HVi+i@DCwngs^ zuw^wiRDbq3x+2e&_r34Eu*V+ra6&hn^g-xtYd#Q~uT&|JvHBiCBL)Vh!>za70T+Dn z%Mu64R6`hI;Z7GGrmr80k55V7O@Lo^XARjkn<9 zi@yy|Klu#8$$CUDFQMjz36wUaew}gB14w_=0oqFOM)bmEz#17DhP|=(e#8-niBekP zYt5RKRm3uU`p+h8=XS>-mf3AP&0a_rXDFo937oypK8xTz?|CNc=%Qf2bK>RqGvA1vOWJ1(ls9`l#~MEyU`o6X|M+ zP`7A_q|LL~!-oRVr9Zt4uD||f=o=V-)_5y6gi?a73TqgzEFPwG4z2P{pmUf*!MqAv zL%mk(+`7JJDlCfrsf{jxPhk)R*+tj?7MO>F>tI#)YBT9V_mI@BiQu^aSsb-j162 zlg>Qh8VI|MvZoC-3y(?G-r~c^$Pnza*CIIdutPB@hbeT1>0Woa=Y3Vo%R+a`Bs4X8 zs?-DZuuTJf;S7loLm-qo7A=|w?|#?GFp6a}eNXBove;?Q?Lsl9zJ-MSaOj&l1s+3b zrgO2MNWl<C+wYe1#s;xYx{!kU z6q=cR<*WaW0PGO~z$OAfm{S%j{}V_JN!rb%vO$q+++&(n3&S)V;EbU_%3r+dIG8?d zKqeHdZCLFjykJ8sR2`Dl1;g5IIK;Z&d^=-Bm);6f(>f`_z~QtHpN0hWr2>~oi!5fK zaMtcYVdznqZdwHnr`M!KJY&X8)J$9sU%cRdV-JW_a)MZu#3iG#xt3MwJyumM?*xUa zu5#2>R;*6JTboyj?Q3Beg=?pY!Rbq1`ZBC~Y&Ck9OuV1!gtoAkOks=0}cT)GwsTE~#mcHAmr{r<@4K9di`2 zNu)uPlJQElNm#b&tUe9$Wtr$d{8=FSkqycBWD-~#V%Z^P+Q@f4@ADVHZ?C%vts;Ab zonv-GDFS1-?!>kXwn($GYG1Fc{j5(W;VUubQnJ}V)nz@Xt9l7vBi z_`{#VfBo=M=Y${Kf;KzSwiC?4e7KLc;91Ub33oKi499V|Kj)lt z;okcm#IO5eRh)K}ZvUyRi?qKDFkd%!+GV;})_tum)2b>4TIO@5$*V(AFc0HWnKsE^ z{rW1n@SER8CQ5f3ZZCbTpea;Gz9?aslJnAEQGL9r;?c3D9e9;6MW$dUlOnvh&EA` z=}CHWHWQ=(CxstErDBsYq=$HDqLtH$q)jx91_85q`&&nPPEQ(KD2b=#0NV4P}iMqaedp(MweF zN_u%?-)UsI%o!J)<{ac#93<7^AL#Fg)vMOvfagqj_>sq=o?= zINa&SAWs|@vNZtHOE_RupSp)u8&ENZ?Gy`(z?KIY(5&c?5ao;(N(P2 zhize!voDq!GoKsE#CkCpKJefpaQ4}sg$)}vL0|uX!2ZPw)*7}j&(sYvTxO+r$Rl&9 z+~h)0TPYwn$8g}+5AS&A$#^W?qK+=&D;M>0ZP(BB8n_5TyC_B_*93Mvqk5N~VO=Ie zCO?OE93dCj#J9fXjd0@I-U2VaxKZE{vvNw=GB^wNZ@U@50>_BdcF5*n+KfT$CEgEb zobge3QUt|bp^j6VbD92cr}XWr3ISA|`e!$%@o_2Gu=3GW@Gt-R zIavSP3(!mOnpP+)4i0SwQ#{4G)7*e^Cd)kbV^*cKwx%OObby1DQ5>Ya<%HM6f#{V& zt)w7eM&)ZCU!JNy;-)}spK7xmOEKFed#ExhcY)pHruV>85E<&BeDusS&{}a17#Z3s zB+|jb70gc+CvDxL7VtP`rPBuo;l>+pfiq4&6P`wY49XaqT2Qo2i_2)t`DC1~unAv} zoVq=itDXnf9;ivzv;BE_VP@FV(*=(_{3x9D>3@MWYu5>yXESU?_p#I>OsRLphVqeS zVf1TBuZ*YGxPsTjj1>q|-ptfDA3?=!DwR(<@h!L!hbiGzU-PUgLy#hdn@dZhSO^5R zpX=U4T1Jpd(MlHZk9_kQ|#@Nf~K0+ho|4YUeYE zF=Z;|7DfVvq0Nr2?;W?TfRBItlS0L&zi$dO$C_zLIo%*i1j1GfwUIhBVPY4W!!FHA z)X9FL)aDfa<+$!Kbn{*W&N;yc3J$*1?%`;Gu^f zgHu2JQMmo?KZrpK^%@mJA#8<-N{_}P*YRS${Z-dZr%o!KOOL9Gjb)X!nVp!L%V7g( zSgb1+smFxk7Nd5^W&eFSeBzU5!3&$VV$X0IUfD&r`mu7=W5a?9pTu(*gt=hz~QE^fjKzYc-tl=aZ`rxszg+z2=B^{ znGhS&EDGO7l&|Uo;wvp>I_V0EL$H@!=k5ky{KEM#bLOB3M1tYMY*7?WkuYeH@{Y;Cs;am~-B1*l z$+Zvk!dDN{Mbh3Z?3y?|>YH7HDv&Bm#FirH%!L_Paz2mT68s=6Z6s4lI7VctRvWgQr8 z{qdqq(dGJ74T_C(&N=6^?RQj_m;2C{`|R=@*?UXB9@|O&3#`7rod}99Q(x00H2e3|-x(bpgVRqt72b2oJ79Rr zW?^{m3MO`uM}l@wKMm^`3Tj}hN%Bv4)37d-4f{7Z3;Rt&C;>&blT_l!o2l3ywWuVi zvawX*jJh_^T>`AhUNkQwbTN{zOI<~IybD7pyoP;!eF9_psZV_xe)8j=qw3KhZe|V1 zF%~f_l)*0kmpSH`H`Olf6%SK*`ulA_>_D=ly0Cm78rlLUz5T5y{Q4Jysz;WlI{nnN z2J*nywtLMLdS?=NsH!Mb`r2al!8?&5+JcEmCC9~`tIY43dSzs=LxK;_y|5A9`=0m1 z-FM%Qkcg=fXj-ySveP)VOXp=ed5b~%x&+B)5@d_6AXZMMOkwlp4d{G!2%P3FWw+pj64pAa>v(#T*Of5 zLd@9Kt(#H0Y!~uCOE%1<)l(x^8GL{oILYtN3DDU7G)t z8PZ18*H2U9Cj9iLm%@Kucrk8fLofw5RMIolrMhAdA7Jf?1qq0xWaxs}98C|1`g4fh zVHD7UsC=Mc*p1x%QPeoxbKyMr+~@yQu(KprnWboari@%)Uv?82Qk3O<_RjUE8AyQmV3NG()- z8pVU$43lmMaT`ePzG%vK2~QSe5@1>Vj+0M>Ge34F%$YS!^gZeI8j+cnjkD|Gb!+2H z*Wsw>3#%$47iLnm$$sCKWtv`-%3~^%NlS3;6Hmkc{?b?A%B!zNx4LP#vG$4Y#9ejj>^nd)O{~>LAqvyBZDX+H6)(fd?=)Dx z{03nTE@3jE1rHareXlJTX64hOzu~Ge4^*0SsoV+D_l1TnjjkTVE8YVuZb$LZY13dY z^uC}eq4Ivt+ikP39KEba?k!eZ`C^07*yvY9Wwy`6xy;ZdFMt*+GevS+SRxZVt3%UK3@0tRO2DYk()zc&%Yvc<(5G(&%+S_z1NG zQsR@J_!t~^=rS=x=@G}uN+%{|=LB_3gMB#oi5}&4>LJ=xSjuLF+1=X&+Vt%=*`7;f zihT&4z2Jhc!vFd5SA+@uSSw)(un2m=qn=OzL299=Zt#MDr~uy(Ir7O$TCrJ}G{lOY zmoh=x;fIHYP`8c?F7~OX{w*wAum>uojLByU)W4vugjZ!ml4%6S#Dy`OhvB+2WL`Q^eXKxE2ed z9-$hb@#s>s0@uapnkQceQh7Z)IZnOD5CqMNH{$md2(JDxoQbs5_ndN)@F%BOpY=@FLJydC1SO%(@DF_zr++`pdc`0}H0%kLS(qa#ozi444_Af@SEUju|m=i50rI4aSt&a6r+HcGN?*!R;gANq~JkZIQn%7 z7{wM8rC7Ld0Xho4AKrfA+hE$%{-lhi#(L1mS)g_i%_7Et%iy8tx|J=tZLeuFl+WMz zct#G3=ZFet*Q+#PqVkv~$S)$E@bW9Jf^S{?9eDilCtwPe$P_?Z;dtEJi`^Z0p!EwA z29#BrO$Efbtd2@=&?eD=`n(~&Zs3NwWy?l*|NBmXk9_1bEZN5dlq}HP{ajiBuIJJh z6YMhicgZMiC=Sm5L;_-&9#ok|zAM!*n_F9he_wT6>V}sRDLG7Li*NiSQh^W4o$AV z;YQ37`-NwY^vU6=p-B*Bdm^w!S$+gAbZa}vtE^3)lL3laOUei;=#td~O_Wb~)9c}^ zfBb~70RTt~H@F7Gyv;4Im**SUeAZUk%2WBDZ})%*H3PC;Pef4$l5NtuPExbKEBi5B zuxZmK_}f$e9H_=rbs`NU^M|t4cHRMj5!d+ z*&H1OYvjBi*@FzUmQ@2igSMgA@v%oA0q6WP!fU1=ZWSlrjYi;fe(SKVty|2c>@imz zs?-t?i=w%$ci@9vvf-J0`h}*lIq#%P6QC}5=9vxf!4G~2Zn@=F7@Rd*stkjiCX13b zniOY`AzsQspt1LZ)P^DhBE%C(GgOA9$}lRGh^Rk|ESE)#7Qy@e^1X1v8;^&bcbYAZ zUG%jA7Tq9vCd<4y&pz3hua09Lhq$Z`X8v`Q3e+k?Rs#~o5^k`-5(?5V{vP4@@Vq|z z+y?mNFE585|M*h$*ALT1JPA@#Z=1Cl8oCf#YeILvioF$~-V~80`t7gajzbu9aqcy7#6X8jKWl5=>{(UL`MeB*Y#Yq8v%jV5+@WIQ_hTvS7J!??BhektIE;b&r z%e5-nuLWzOqgR0))2Z(jBKLdFTFi%+b33VVuYz=}KW66)6? zD26=l5oP;-Z2>XvE;ZublDv7iaZ^YZ8IQ4`anFedv8^h7e+2uB&DJRFx8J_-hBv$p zUjMq|Vb4ViqEdQH1OSOk%lAJ{pRWw&&2eL?f@jOyXp4ck+)2PRP5qKVO)6Cf0((l2 z$5uZ9zy0mCm=*pC1L0#Bbb7D^ohlS#l2sY?hqtrE& z)d*#VBiJ)M82iTm>)g-6++BB$f?*JgPq_ALt0F>7Y^P4RINFYHy8*Km<05+!q!=aZ zNI>+30#yCo^bH&eDqlAS#kK36hL50G)GfE)A+TrT$Wox`t}X(>Vsk4MQwjoNe8Xt) zM2kKji<|;w8P2uIB#TP;hIq0Ol!|X5R&l}n-QhLJWj*0dZ-9OG-4}6bjYx=KEK)vc zM1=qHrLk#aDf0NWfyPZueqT#gW&26}HTrGB=F#;b`-X=feguB~>nq_`SNs;%tdU~9 zQ!o$)G7_aQe~I=Wj|*iP#M%=C84$Ub*hXB2E8zyFgtA+)!13nITj0pUUkRT(@3XMW z&N~S_Dt*6*VrRRiRRW^Rdhq?*D$lz^F;z@Tw?6%*qQ0PyI6+ar(dBpZLBp~EFN0z? zJdFhYzyIhb;o9q#!wd|HLbjbSi2|P}8{hFDCAn*I1EQKTXZ0Ip>d!Jz5EeD+Sq%P7 zfl|e+rAxemFdfH#qbUD0Had)+)Pq=J9Snbglj&C;xDgiun=4=r>4wU z$`>349J4n@7E+@hmoi4D@>pnI;^)tzpUE97?m~s5>rlM)7FfUjIq1U4 zaSvsSk$6IUF5<~|kXBSt0tqZ9laA$6cuEY#3|UHq#F*9+5lKb>;#!ez=c`}+YB=ZY zPr)3N^rGRJ5^PA_gDgGNb`46trE3w}$)`8y3&APcEVB-U+ZA@>LhEq>WV@UUpNXYy zA((_HSjJ>Z_4sHoLU8g)VAT{;P%BbZAjvS^(66~btsqiz_aEA1c`oHuT!T0viLyy4kes?`0 z2A0F4kNi<&jZ>yfk?%1KWARiQ3x?|l%`;r&0_c!xZ*Z1lXvTwRdMwcjk^~TapUUKb ztV0o6^X50eXFl_3m^!6jTq`}ZFVmm5QR7^MF3Mz*zQAku(mC9*aEMhv)h2#vol`DX zyU!v#7DW9gDouxmhEYxAv+$!!E=9WEED;EkwgHc`wl?`R)ih@b(3V*OVsROp-{)hH z_`;}Tr<;pz`fVe_C@DV%gM%|fukrB14}(JvIY@j?JXk&W%Sm+*mux0CwyZXt%sd;; ztQMDPvI52@yy9jgm){cWl6r#bdADvIhR0St26x|kKiquF?Qr*9_rUYdKQDZc`$(sd zrp8^mtiE{fnVamEL+UooAx?hH?tOyAe9Uk|HRtwtmGdtt?j75Moocwqx& z#@#}!RRw#YGs$_>`vuv?u&tStoLl0ksIFfY3?H8xty0hfqUaT7Ay^>&nJM74TDT!0 z>*TNh`U9xh`M1JGlz!ILXp~y>b>dEaks+>u)+Zq9&qHf#hY5&v0IkaeMBkLYEU38X zcqlMXX41eC`iDRIIh_Cbf5WU~8}8!$QTY|Yaj2wYECG{IKn!$+0GQB2X9q$ScZtJkL~|8vhikJJBkDBX6K z$N(RD=wa;fJ_Dl|ep&dRApTtRS&akkAvt74r3PI+lN98A3qb`eaguZdkWfAj;aK#GGn@ceTdVPM*H zkxj|FAd!cUy*UDwBJNWl<6(}SrDls-q?)a!l8I{|cj(VDt+Y)Pazt57{Y0Fell&Or z2sw*Y^*&kTMp%49j^eRw z!T#r#En7vfTfJrtdK<5W$5*e#$?zIfsa`LBPH&qyygjH=M47q{6XZIFkLNhvaCf2I z1bH3mUK8;(QrT?4+KtL+Vho(6GiS9~F*!WhvUw{`kY{5%=F{++R~;h|0rY(xzgpBo z&x5J?hy&Z~1G3UxF8Yyq$L899_R&18#8kn>K8|mwt4zt67Z9s9cZ-_vzC#-mVBFU{ z#}&9ts@r2|7}bxG|99SXFZ}b_pT&~x0hl^{hR7o0vMLh)jDumSKgG2S5()%G+c~i; zqVpvp0-@0)GcLPR!=t2Zl{cs~+9D%~Ks=_F~?u}&t*QTMXt;{Yip~F%wG(3+@3W!6P z4GicfwJr+*z1QXgo$+<|a>o(Y*K5hW*b%&gGDT{76D;T4^UjA~{Ni$CI`j#l*%nE- zgA{*dL6Fu8puMgN5mAsBqVYLeRAc7hIR1f|Nk1xUQir zKdFaGZgQfx7|QoZz;6N&_jUIPYgHO_(6pDn7evnyMv_`h{787oQNgrn%ICzdRcTA@ z1%;Iqr89jV1TlD%Jhi3uCQo~mF!eQ)eO#tbyKZJfS>pmi3xeGH7z+ZA5Zr zD*K~A3|tN#RDV@ZtP}HgeUQbqNv@_KqZ`FDj}18p+nwup0KkfbBnqT=n3V1s9!AgR z-fsBF>8HU5-~V19A`#gVP^-d~c~T`H=D}cWC2*0%p6z~g#VPwJ^r&^{tA~ic%P|IG z`>^>U1;q8p-`s_IiQ6+EnzZ;_N@G*0ZJSyZL4DevhsQyn?f5mt6fWx|Apk(}h)TI@ zue%=3J@-6#@WDr6#?0BVaHz;u)Zm0czW0XG!vmHwDpCParYbxYSebrKAqj?Yu;A&k zTOW8N&&A`w%CzZtY{;$wpw;;aLyfrs5~LpY2m?(7LGu|=*k%EF(E>2jdUC+B6gdqT ztp^>gug7^Nz7ciZju#U5N&?G8i;KcjiP3cp=g7B4OBJbjG8J{>O26 z@)`uq%f2%g+-g@0udU=Y$?_H52SvfoH6T`lx>^$=x1?526jGZ+xZAI9jo zZ<&Co(}aA1);^KHt_hfrinA+@($*i5>f16~eDcY4aQ+v*43}N@YXSY}>z^v2E2^|( znzD5q2qBY|Mt5)!#>(CKfN8D;z$#QtT+$qH6d)z;DM)uSx&z-c`w<3m~Vk0-EPgk?<=}iE`ttVO0KK^WuBEq2Lob=0Wm-cy;}p!U^Ybu#C-!)Q7K$zI8tS>;%hpT zHFURGWZHxYT+HD6al$IgAV7fa`{3K(z6g#z=5QE7n^Dp-q{*xF?Ep~iES|^l)ujbg zgCAI~=SExw0&@bS7vA*Q0FymO9`a#@*E!QeZd+Trv3m&d=M0kXGwow+1Rq9zCcGHy zm_3ZV9~W0Y8H>@P&78J6EahkzL_u#WO8p;s#G&xbi@pYb`Iqk$B|i0?X?UVb!VR?{VMjMn5Ahoq9sdS1#1_-}RUUeXbt2!3$l-M% z>3zofuI0V!@u^8ZfosW0Z+K)IW{W?B?|kKwRbIJ@C6+LQJ3LN z``-U0GQ<|fzy`*^Z?dtmZD)0N_nu!iAeK47RpjA)Wx!m@VBd+HQGCEEY#K(~TPe}i zjq>jgKl~Vc`O9C0%dfgh*cJ5mO%Y|ZZX>E>y%fI{QuYIhOnq7i71~%9dQZ)WRrG=_ z6ief~1;k>n;36trGT3ofSGW0r*aM;|6HGw%5cf7C_6mM~1SiNu9(vQ8$Sv`&QG#tR zTmzEfL~9^BnDGh3u7VmqJ^QjUVMTp+S2m0fq^Rgv6l=;iAfi9frx_X&y$eCDKl1M( zvWT!hF~;R}XNz?T%=+zi%3`1)ZU!}l9zsaYWtaUoTzv7j;lT%13f2cv&UloC3v($Z zVJ7mNrvqZ_{uh^ML3~qY#Yy8}t_8&SymI9a%p&g=o_gN&WIae}Idb5Y&YM?2)B<}^ zfVTB0bIV|50RcflX35)1>a^;4v@www24;O@2w>kfJOumgyAS-$-+Ta0Kn3$&)S@KT ziDCo+a19ji<}2(YnCu%6>%6~g0WlBX>yjy&5}OjR8_`3=YjO%Qvc_Me3Xjq8DRPd*J7UGy#Z*`+_n@@fcKE`vh4 zz7_pWD+fRyC>ohVK+Fb`P+w4x)0$hdziR8M_FOHI>I{f1Az@8O!`#8DVB$ zVPIFPpYQ$bOWljmAUJ7;!!ZbM*}2*5eE=im93SY7iK$Y?V5$zdOr8L&3n0|hM8_2L z+-*0RrvI1b^^T9rraP&a+fa5vBd&kwJJBbUg&e23WNB3SJ&vvn3QrFza61B2lrkefU3r8G0+!@)`szQv;`nnH5%6-;UcK) zl5M&mNs6d=RXx6a@UHq$#Y6t9iT6<#PWpd{Evxr*$zB%Ri67qWH^D8INw=|9T)kIa zdx%x-PEF6_D@u{KNQxAFOz(||{`5ID-|6EqM;{KaeC2^~^>41hjPX+R0=W~mjSr*r z{WRpk_5jn8r)2QLa&d@49h$c*yCjnMJf#xDql_*uGfYE$c+TskVI1IY*eNClr zU1*FlO7>N-!#)tXBJq_f44xEUzaC-`tJC_P09)xe;hr!eNIT3~zYj>x2i*)G2+Mmy5)fDv^OIz9UuJ zO=W_jD>SC+z2FMQHf7R9UPnFwQ5&diKwqSN1_uW>FwF2Ut_2}xiPvG4c&sZxyNIdc z0*p4Dydr+3k858Q!<;f+vbIT`uenwG1T-Bmg@$0WC2m$VwOoY;>%aNUZ{asrT@4Sf zT#4eTW0+0$pble?433eaPO?}MxqKCL!bl&HwKe$jHT@D_6G%A|iYze0ieZ^PrYVC< zs}u5+Sc6j_u=EG>dUg~5m2s75Di`V*6hiZ3-6LO`YJxv4SOqFiuzh|nKRv5*2H zUhe?S*S4LFjFodSAU5$yyL-|Tl8*9!jc9^nIn0I z&|hpsThv=_xec!V?KN=Qt#`osXP*@ThSYVsdSpp0{62Wv)QCrL1(u7AYX>7M39+oe zZ8k$>O419!$Yv481w1E^8CWnpzsPpu%uCo)V$~|&3yIrO5Jc7j%$zYD_D6fx*P>YK zvBw?_yYIHE_*En`i0dE~tOKo;kOLd?ShJ@4#3X4~rj=9Ht+Gr3uoqA0K(q-;_$X|< z^c+0`ViN;m7n*pT-PP6eIf{h@#%|z~wGg0v2-@1*>RvLtn@qU~C-`U``j#Qe_o0fM zQC{=96AQ}abvSP~KM%uJ?1G1xzHkFv`@}l9;f5RG#v5;jd++@NJp23x!CvmcAxw8y zPb4S-#m3%BTeWcWf>>FWc?YF5hA`XXOL2mW0JyO{S^D)|GA1?bpdm>MGd+ULkmC2$ zfgYhtc;pdBz_G_2120E&EfQ^2G&?0&E5&bhJ-(o=S>T&N)v>nf@HnnpcFz{j>oB)Z z2VK9C8PkeG8SA-C#6paUxF!O=a^=e2MT-_bguk%J1&`<(5bLluEbHFqbKzY|nKFUY zhb@z*sG{dw`TieV(7s|X5h{o=;C11@(a~{az&r)F-*y*Vf8$MX&)xT;^w_hAk825O z|GvK7s8E2OoS8W`c*oD{yK&9lc$*Oetz$7I+ilPinXaHS~f?FAR6iozdatXsW$_2VyDym;{_#X=UPn4)aP zTKelaAv@KjY|aKmpWV&YGpuSC>NCtK11c?}+))ORpQQiX%E8T^e>G`VI5;`^ltxZZ zJn;nF_Xjj>zikEl;SUeMI<)T`8X6I0bQflq6eI~_7e-oS^cWa z<4ougeXaYv-zp|ks$52_=!IBO{hf+|5tdh$%@`10gE!9Ab!UjS6I2&pvP#hM?e&!P zZz-c1;!?uo!7udlth|URyP*$A*YvZ~Tlo$_BvhS)qF1LAL03{)ASD4Q{N?3*`lceE7fUSJD~t^v`7 z=GN(^ZW%y!r;f46>!OT=yYJvgx2zO2L4t~4w$#%V1xWm}_3NL3KmKtQ2FXX@zWW}8 z)vMN^^}&+}&3aLAap{@6h3#jr@cUqmMr3lInBGgVi6C(yh!&^}W?}41pi1IGWpJKk zh{`ZZX1}2f!ja>jc+Nmo9{UB2(*%1a5Pd-3%21#T_UfYmgzGM zSZz~gtz7;pW``eDu`ksDR(M3tN0;GpyHl1|wiC~s`*d~g1XZm@tLV4+E?(1wyUb4O zVgcZ4LhdXKeJJvO%;Qo{=Ob<*aJc`O%VN~Of-nf{yG>OE-LD-0kN!i zNR_qYBGTXk-Kh#FcL~-u&!*(wwU^9t(;q*OvEI8>>}wl|sk9NZ#2auy9%%w;=M7cr z2zXWah^8J(6$$gQ-Y>3Yy9;&CmN5uyp+_LJ^&XS2Tafh{p}$c)-8?W2c?pkVdEFGj z4+CtoC8W*BgkCZ}r09VFa7Rkh!qlFEAz~%z9~myGfJ}Hl`d+u-celSU0!Sm43}3t@ zO5Pw~DG}p5ACM|TFBi8)-`=>3bX_OfHC5EH>kww>#B-VgVqzE2yb-g*qg2dOKhKuL z6#Sa_OW(qeN4jVc)p_CSO1Giq*%pt_l>xq8IE4H-G;8 z5o3l(KZqKPBiQq~5Wn@g2%Qo58Sc32rullU*t{2f<^D|zH?Oy0TU>diYqk$!)5Nn$ zJJqp)*!1m8uUXj3jODTdjULCW{ZCiBTNC_5(GOj-J720>6?lqk|IM{MV)JRv>qnXb ziHkL;>#tw>!1X;DBdlTq3NV%bvOw)?vd09@faJ{hXZhGOFI+GOF%iU9iz<4hZdi(g1x zy~(m|As-||C(Cuyu*QYnTNZYsDcih=9~a|uKAX)BAusb&St9)b9?I9EZ?}95;#~~f z>nK^umgO!tj1?bH;5@abY+2bYHip`Hd_{JTkk$4V%cl&!+)L# zLWB!;v(xOEY=)?1hZ~WzzZhfN^Lg1}ZiblYB|eYO`<0?BF(0PEt=Ymvd&AnYn!Mf9 zWYg8gQi@mYL-H1R4Eb9&E^#shfm-P4D03I@x|NPcq-+ zmS^Mlm#L7d_@lUH=S7*L`#qhX9a1cdf?;lUXq4vAvcv(*5+7=xI5TgLg{niBx@C)O z{gfi2yekmh*Ua8c1JMpnwb@FR)tPe@`gFY(Mv>SOq93-2uuBPUV=$G;+FgeiWq{wV zp2lTN*L>e&C!j{ICiX?X%=%;*v%{BQOdBrB4hu8HOqPgGz|YpEG|oZCmy+fxJMNkR zl{k=)AvF!A%D5J>R6-|AlLLRWA5|B?n%_WO*IUbuAh-+4AI`_VPIMegl)@>&< zE0^(mtYO~m- z=jIFI^cj-4Ku3xKYtdlDc5|2jz`lL6G63Pc8@fof#RrGy6MS8QipvJ(TC%xhFQyyT zwxRlNqy%t78m<&kj>_!xwop&MbH#q@r^R7=cZul0#)+SM6?a?iUPe2N9h8eDcd(Ms~%(;LZQNuh)vku zdjLN!Atf7^a@i?ER09@Ll$G*e@K@4oCt~VXU^TduWiGpY-Y#exTDU-Bd?7S-dR6$8 zsiyaumucl?Wj4r(i|m+7Y2*USVg}Jzm!Xd7+EnG$RyBXJP5oU-0lq^C6PLVbtrTVH zooFGI9ZKcI08X(<$i88$k}*rfE~f(wJPx+CV#SJXlwm@x#PCy5Io_3%3p-JyKhKmM6$N{jU|AJ*;>)ZtW%;g{ zK^K&T4b9+lPnv?asfX%IV6a`UqSzvQQ7|@9jG|aYG0UY?hO!o6vRBDC>WTdZ`-#V6 zDmso|cjsMXpQ)J(>ffdODATBS#eli%v2ic#R9Um{Iw*H8!lf?dOi^3Hhe_ds#q()J z_?{yl+Y6g>qf41=!{e6K`1BcMxp)k@+J>5#KEB!nt}!Hfu5l7mM_Ib7QCsu1y6|hq z0%D@9&3DEZeh`D=Z2WeM($XqW0oN02J3+3dHf3leAUx(=|gt2w$0EXfB5 zTql6nhpJ`@DyCtPZ*QyYwW!0ay$UVXw~y?LA-45JtKM@_7Ko*C_yWeKmqvVL?||rS ztQ<8ch4=#==tqk@vb;_c_-L zt*CVKq3M+=4Nb!UWsOjDj(y&rt!%79pDaV;tlHlz1FzDBeDl_IC&U^Gu)bU+WOzI$ zY2po`cYG^zP*kVm3!(yd_j1eH<9183;|H zrbU6X%6ajfT9yT^ml1g_OVcacOE1gM?v(PADX}oUx3az4l8-R5+C80O62+#h&S6Ib zViXj|DJo;F{vUV%7tmw}zaFn+!s;t0?mKxbE6e1|itsJ3U8jw8c)$1#ZmQ73>b^l; zh5NHc8PG-Iw~-DbWR=>ZoW#DjI(HGNWu**y!iua;h9&K(Q#AD36!! zp;rpai0x6Z95kl-5gyRHXvl(JciVPx{moKY=9+bs=|OomX1%1WIwi?v!y=ZIQgXd< zv)8Z4#L5MCT4q31Ryxa9IcK6}#i~4yO-ilK9=Hu^lt)gen`Y`zDTr7ThRsVFjqX$Q zc(h#tqCR8+X*zT{e*Omhl(NTeACR$&vP+$2*QTkOE6=yn)F_{lx{TIdnQXI6OQecA zblD8omcdo!QrE5SQbifQ6Z)NHC;P6J47LH5LuMWY!|)0m?7lG@i?&-p%n6Fi@$+MJ zI8L2Hs&xh;SIMWktN=P;XjNu=jmjL>O#%($%g7Ib2|0jT?2Y2Z%|hV zR8=fzS(z*oe$oe%=R!aA^;D^w_Ss2~X4g#7@w~;s5dZGgG(1tUr;arl)VR1?QA4vS zqqaz8sh2oz3otGSd(#E8u_LOO50zmG*xsip=vJjVx=uAsH@d4Fy{oW-i|UaHW>s{Y zW!H=g8^=YEq^zKZZQx)Vy7;UCn_Hm|Y{#S)Uc}um5+)4ca@h<_wxHS9p<$y>Agqg1pNH-BX@j^3toT6XBAg)wy=Z_U zdxmuf!`cI)CKo227Z3at1<4`4Ur3DW8vIZ4$~sp(qRVdOikmWVLbKGjD(|(5+nNtn zvu@sXRZo^qwH1m2Z>O{ZeekKa0PAD!RfacSXR=Y%VKNG?BuLFLmI!DKO_Np6uXatVD=MUri`C9aSOnYhAlI@*v@LBt=fZDPeepP!_Wd<{*KrMgduXgPM zttsO_)Bd;i>*w#D#;yIjyvd}G(o$Xk@r5+mOw{S44lT=*t*grHsw$)B>s{?5>F2u^ zwR05DAP96FVV>y^SRDhD2@Qw}r${Qb6v+{%NHzbZ_-}SZ!tSE?O*fJ+2w48E^iD95 z=!+JesT9pZGwh3$=_+X66E^v)X7NsUsdfFFq|cA5^=kg}%VV_Bb>GG z1WnvSC>Xv#6k&`T%c(>r)2J#q!|fUnwZPaNFsr*1I&&69n7D6t63{+(N}Z@(5ib7Q)_SM&R4ZO?0d zPQRN~GrurCXZwxJE$h;Kz2v_qR`5J^TvOX~WbYl*eO5<^;f3dfN@Uq$BMGn8)fg+@ z7jcujt1wV@sDKy+#V*A!92*<^WTO#$ku+ve)mCJSY$A-9Cm)lK$;S@)hLYRmtvrRgTf}v90Ccgt5xSU6End}1b%@eLDoLnN4kIBdXh(JgRS(Ftv zCGc7WAgE*F)X1E zS|EH8Z{M%s^yL5Geg6DJPmurQ0%BenEwPK^e}g;9sj8HYrqYvHT8dFGi*=Fgu$vLgq=9X=oy1x9%RMwd04!C&HMZ^a9+ zKz$#BqiEVj?<<%b#!NmY9~~Qxgc0QgeU-YIb`P$>+wZ@-y29@<)_a%U4%?9f;h#)E z%mzmNq^@4Qx^LdRc}L-=C*rj^9xu!y{clu&jCiC$)PI}|l9P|ghhOj`GDR41kW&FL zy3bbQt#vhi`m=TG*4?mZ(V}fd_w0`T_>&9{k&;fJXN@aYuIyd7aQ6f7{J$E1^ePN{ zOYp+YYX937{k_`V6zQJn=VCHwPCotz1-tl50zzxwB8CXjirfAU1K1*ZS}u$qx0< zq-^r`nSA^iW`>kJjDeKl+lK3W6W#~-4>sa`unvFz7~Ug~<2|wljo%+rli|E7?av_y b>W}{q)4g%B!)1H<00000NkvXXu0mjfANS@7 diff --git a/openless-all/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/openless-all/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png index 6490428c9f9b6d2aa78cf0ce25c4a8a0d63e996b..4ca173152c31710e8e131ec8f26d4abd978f0fcc 100644 GIT binary patch literal 11646 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 literal 12382 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 Qq5uE@07*qoM6N<$f&*m7_y7O^ diff --git a/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png index cfe887bf2c64c50bf19e429e58b90e55ec678841..3964920c0f5ac104afec7b856a72a78d35788c40 100644 GIT binary patch literal 15806 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 literal 20489 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 bH*bih0N3K{M)`Ai;*UUA(@3LE%?|M&28ojo diff --git a/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png index 8baebd6441e53d8ce69043068bb4791c7720b6f9..56d8948a58a4fcebd191da43d0d5c7aae6e41470 100644 GIT binary patch literal 55373 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 Q0dU%ND_cvdx%+?r10GHdF#rGn literal 86570 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

    F(mnQjG^P~4N|@F1I76XvM#VnzTxo)ZC) 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 Z{|D`eBxfPws?Goa002ovPDHLkV1kYpUyJ|% diff --git a/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/openless-all/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png index 404db72a6fea1c1a746cb7e0297b41d92d81a42c..bd389617887e0d1ee2556881e116749f2777d6e6 100644 GIT binary patch literal 18185 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

    xB|{0UT1Xfj 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 AndroidAccessibilityStatus { + #[cfg(target_os = "android")] + { + android_impl::get_android_accessibility_status() + } + + #[cfg(not(target_os = "android"))] + { + AndroidAccessibilityStatus { + state: AndroidAccessibilityState::NotAndroid, + enabled: false, + message: "Android accessibility backend is only available on Android".to_string(), + } + } +} + +pub fn request_android_accessibility_permission() -> AndroidAccessibilityPermissionResult { + #[cfg(target_os = "android")] + { + android_impl::request_android_accessibility_permission() + } + + #[cfg(not(target_os = "android"))] + { + AndroidAccessibilityPermissionResult { + launched: false, + message: "Android accessibility settings are only available on Android".to_string(), + } + } +} + +pub fn paste_via_accessibility() -> bool { + #[cfg(target_os = "android")] + { + return android_impl::paste_via_accessibility(); + } + + #[cfg(not(target_os = "android"))] + false +} + +#[cfg(target_os = "android")] +mod android_impl { + use super::{AndroidAccessibilityPermissionResult, AndroidAccessibilityStatus}; + use crate::android::types::{AndroidAccessibilityState, AndroidAccessibilityStatus as Status}; + + pub fn get_android_accessibility_status() -> AndroidAccessibilityStatus { + let enabled = match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::accessibility_enabled(env, context) + }) { + Ok(enabled) => enabled, + Err(error) => { + return Status { + state: AndroidAccessibilityState::NotEnabled, + enabled: false, + message: error, + }; + } + }; + if !enabled { + return Status { + state: AndroidAccessibilityState::NotEnabled, + enabled: false, + message: "请在系统设置中启用 OpenLess 无障碍服务".to_string(), + }; + } + + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::accessibility_operational(env, context) + }) { + Ok(true) => Status { + state: AndroidAccessibilityState::Enabled, + enabled: true, + message: "无障碍服务已启用".to_string(), + }, + Ok(false) => Status { + state: AndroidAccessibilityState::NotEnabled, + enabled: false, + message: "无障碍服务已开启,但当前未运行或已被系统标记为故障,请重新开启 OpenLess 无障碍服务".to_string(), + }, + Err(error) => Status { + state: AndroidAccessibilityState::NotEnabled, + enabled: false, + message: error, + }, + } + } + + pub fn request_android_accessibility_permission() -> AndroidAccessibilityPermissionResult { + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::launch_accessibility_settings(env, context) + }) { + Ok(()) => AndroidAccessibilityPermissionResult { + launched: true, + message: "已打开无障碍设置".to_string(), + }, + Err(error) => AndroidAccessibilityPermissionResult { + launched: false, + message: error, + }, + } + } + + pub fn paste_via_accessibility() -> bool { + crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::accessibility_paste(env, context) + }) + .unwrap_or(false) + } +} diff --git a/openless-all/app/src-tauri/src/android/insert.rs b/openless-all/app/src-tauri/src/android/insert.rs new file mode 100644 index 00000000..13f4aedb --- /dev/null +++ b/openless-all/app/src-tauri/src/android/insert.rs @@ -0,0 +1,51 @@ +//! Android cross-app text insertion strategies. + +#![cfg(target_os = "android")] +use crate::android::types::AndroidInsertStrategy; +use crate::insertion::TextInserter; +use crate::types::InsertStatus; + +pub fn android_insert_with_strategy( + inserter: &TextInserter, + text: &str, + strategy: AndroidInsertStrategy, +) -> InsertStatus { + if text.is_empty() { + return InsertStatus::CopiedFallback; + } + + match strategy { + AndroidInsertStrategy::Clipboard => clipboard_fallback(inserter, text), + AndroidInsertStrategy::Accessibility + | AndroidInsertStrategy::Auto + | AndroidInsertStrategy::Ime => { + try_accessibility(inserter, text).unwrap_or_else(|| clipboard_fallback(inserter, text)) + } + } +} + +fn try_accessibility(inserter: &TextInserter, text: &str) -> Option { + if !crate::android::accessibility::get_android_accessibility_status().enabled { + log::info!("[android-insert] accessibility service not enabled"); + return None; + } + if !matches!(inserter.copy_fallback(text), InsertStatus::CopiedFallback) { + return None; + } + if crate::android::accessibility::paste_via_accessibility() { + Some(InsertStatus::Inserted) + } else { + log::warn!("[android-insert] accessibility paste failed; text remains on clipboard"); + Some(InsertStatus::CopiedFallback) + } +} + +fn clipboard_fallback(inserter: &TextInserter, text: &str) -> InsertStatus { + let status = inserter.copy_fallback(text); + if matches!(status, InsertStatus::CopiedFallback) { + let _ = crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::show_overlay_toast(env, context, "已复制到剪贴板") + }); + } + status +} diff --git a/openless-all/app/src-tauri/src/android/jni.rs b/openless-all/app/src-tauri/src/android/jni.rs new file mode 100644 index 00000000..dcaeae8a --- /dev/null +++ b/openless-all/app/src-tauri/src/android/jni.rs @@ -0,0 +1,510 @@ +//! Shared JNI helpers for Android Rust modules. + +#[cfg(target_os = "android")] +pub mod android { + use jni::objects::{JClass, JObject, JString, JValue}; + use jni::JNIEnv; + use jni::JavaVM; + + pub fn with_android_env( + f: impl for<'local> FnOnce(&mut JNIEnv<'local>, &JObject<'local>) -> Result, + ) -> Result { + let android_context = ndk_context::android_context(); + let vm = unsafe { + JavaVM::from_raw(android_context.vm().cast()) + .map_err(|error| format!("attach Android JVM: {error}"))? + }; + let mut env = vm + .attach_current_thread() + .map_err(|error| format!("attach Android thread: {error}"))?; + let context = unsafe { JObject::from_raw(android_context.context() as jni::sys::jobject) }; + f(&mut env, &context) + } + + pub fn call_static_void( + env: &mut JNIEnv, + class_name: &str, + method: &str, + sig: &str, + args: &[JValue], + ) -> Result<(), String> { + let class = env + .find_class(class_name) + .map_err(|error| format!("find class {class_name}: {error}"))?; + env.call_static_method(class, method, sig, args) + .map_err(|error| format!("call {class_name}.{method}: {error}"))?; + Ok(()) + } + + fn load_context_class<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + class_name: &str, + ) -> Result, String> { + let class_loader = env + .call_method(context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[]) + .and_then(|value| value.l()) + .map_err(|error| format!("get Context class loader: {error}"))?; + let class_name_obj = jobject_str(env, class_name)?; + let class_obj = env + .call_method( + &class_loader, + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + &[JValue::Object(&class_name_obj)], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("load app class {class_name}: {error}"))?; + Ok(JClass::from(class_obj)) + } + + fn call_static_void_with_context_class<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + class_name: &str, + method: &str, + sig: &str, + args: &[JValue], + ) -> Result<(), String> { + let class = load_context_class(env, context, class_name)?; + env.call_static_method(class, method, sig, args) + .map_err(|error| format!("call {class_name}.{method}: {error}"))?; + Ok(()) + } + + fn call_static_bool_with_context_class<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + class_name: &str, + method: &str, + sig: &str, + args: &[JValue], + ) -> Result { + let class = load_context_class(env, context, class_name)?; + env.call_static_method(class, method, sig, args) + .and_then(|value| value.z()) + .map_err(|error| format!("call {class_name}.{method}: {error}")) + } + + pub fn jstring<'local>( + env: &mut JNIEnv<'local>, + value: &str, + ) -> Result, String> { + env.new_string(value) + .map_err(|error| format!("create jstring: {error}")) + } + + fn jobject_str<'local>( + env: &mut JNIEnv<'local>, + value: &str, + ) -> Result, String> { + Ok(jstring(env, value)?.into()) + } + + pub fn start_activity_class( + env: &mut JNIEnv, + context: &JObject, + class_name: &str, + ) -> Result<(), String> { + start_activity_class_with_flags(env, context, class_name, 0x10000000) + } + + pub fn start_activity_class_with_flags( + env: &mut JNIEnv, + context: &JObject, + class_name: &str, + flags: i32, + ) -> Result<(), String> { + let intent = env + .new_object("android/content/Intent", "()V", &[]) + .map_err(|error| format!("create activity intent: {error}"))?; + let class_name_obj = jobject_str(env, class_name)?; + let component = env + .new_object( + "android/content/ComponentName", + "(Landroid/content/Context;Ljava/lang/String;)V", + &[JValue::Object(context), JValue::Object(&class_name_obj)], + ) + .map_err(|error| format!("create component name: {error}"))?; + env.call_method( + &intent, + "setComponent", + "(Landroid/content/ComponentName;)Landroid/content/Intent;", + &[JValue::Object(&component)], + ) + .map_err(|error| format!("set activity component: {error}"))?; + env.call_method( + &intent, + "addFlags", + "(I)Landroid/content/Intent;", + &[JValue::Int(flags)], + ) + .map_err(|error| format!("set intent flags: {error}"))?; + env.call_method( + context, + "startActivity", + "(Landroid/content/Intent;)V", + &[JValue::Object(&intent)], + ) + .map_err(|error| format!("start activity: {error}"))?; + Ok(()) + } + + pub fn start_service_action( + env: &mut JNIEnv, + context: &JObject, + service_class: &str, + action: &str, + ) -> Result<(), String> { + let intent = env + .new_object("android/content/Intent", "()V", &[]) + .map_err(|error| format!("create service intent: {error}"))?; + let service_class_obj = jobject_str(env, service_class)?; + let component = env + .new_object( + "android/content/ComponentName", + "(Landroid/content/Context;Ljava/lang/String;)V", + &[JValue::Object(context), JValue::Object(&service_class_obj)], + ) + .map_err(|error| format!("create component name: {error}"))?; + env.call_method( + &intent, + "setComponent", + "(Landroid/content/ComponentName;)Landroid/content/Intent;", + &[JValue::Object(&component)], + ) + .map_err(|error| format!("set service component: {error}"))?; + let action_obj = jobject_str(env, action)?; + env.call_method( + &intent, + "setAction", + "(Ljava/lang/String;)Landroid/content/Intent;", + &[JValue::Object(&action_obj)], + ) + .map_err(|error| format!("set service action: {error}"))?; + let start_method = if action.ends_with(".HIDE") || action.ends_with(".SHOW") { + "startService" + } else if android_sdk_int(env)? >= 26 { + "startForegroundService" + } else { + "startService" + }; + env.call_method( + context, + start_method, + "(Landroid/content/Intent;)Landroid/content/ComponentName;", + &[JValue::Object(&intent)], + ) + .map_err(|error| format!("{start_method}: {error}"))?; + Ok(()) + } + + pub fn can_draw_overlays(env: &mut JNIEnv, context: &JObject) -> Result { + if android_sdk_int(env)? < 23 { + return Ok(true); + } + env.call_static_method( + "android/provider/Settings", + "canDrawOverlays", + "(Landroid/content/Context;)Z", + &[JValue::Object(context)], + ) + .and_then(|value| value.z()) + .map_err(|error| format!("Settings.canDrawOverlays: {error}")) + } + + pub fn check_self_permission( + env: &mut JNIEnv, + context: &JObject, + permission: &str, + ) -> Result { + if android_sdk_int(env)? < 23 { + return Ok(true); + } + let permission_obj = jobject_str(env, permission)?; + let result = env + .call_method( + context, + "checkSelfPermission", + "(Ljava/lang/String;)I", + &[JValue::Object(&permission_obj)], + ) + .and_then(|value| value.i()) + .map_err(|error| format!("Context.checkSelfPermission({permission}): {error}"))?; + Ok(result == 0) + } + + pub fn request_record_audio_permission<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + ) -> Result { + call_static_bool_with_context_class( + env, + context, + "com.openless.app.OpenLessPermissionBridge", + "requestRecordAudioPermission", + "(Landroid/content/Context;)Z", + &[JValue::Object(context)], + ) + } + + pub fn launch_app_details_settings(env: &mut JNIEnv, context: &JObject) -> Result<(), String> { + let action_obj = jobject_str(env, "android.settings.APPLICATION_DETAILS_SETTINGS")?; + let null_obj = JObject::null(); + let package_name = env + .call_method(context, "getPackageName", "()Ljava/lang/String;", &[]) + .and_then(|value| value.l()) + .map_err(|error| format!("Context.getPackageName: {error}"))?; + let package_prefix = jobject_str(env, "package")?; + let uri = env + .call_static_method( + "android/net/Uri", + "fromParts", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;", + &[ + JValue::Object(&package_prefix), + JValue::Object(&package_name), + JValue::Object(&null_obj), + ], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("Uri.fromParts(package): {error}"))?; + start_settings_intent(env, context, &action_obj, Some(&uri)) + } + + pub fn launch_overlay_settings(env: &mut JNIEnv, context: &JObject) -> Result<(), String> { + if android_sdk_int(env)? < 23 { + return Ok(()); + } + let action_obj = jobject_str(env, "android.settings.action.MANAGE_OVERLAY_PERMISSION")?; + let null_obj = JObject::null(); + let package_name = env + .call_method(context, "getPackageName", "()Ljava/lang/String;", &[]) + .and_then(|value| value.l()) + .map_err(|error| format!("Context.getPackageName: {error}"))?; + let package_prefix = jobject_str(env, "package")?; + let uri = env + .call_static_method( + "android/net/Uri", + "fromParts", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;", + &[ + JValue::Object(&package_prefix), + JValue::Object(&package_name), + JValue::Object(&null_obj), + ], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("Uri.fromParts(package): {error}"))?; + start_settings_intent(env, context, &action_obj, Some(&uri)) + } + + pub fn android_sdk_int(env: &mut JNIEnv) -> Result { + env.get_static_field("android/os/Build$VERSION", "SDK_INT", "I") + .and_then(|value| value.i()) + .map_err(|error| format!("read SDK_INT: {error}")) + } + + pub fn copy_to_clipboard( + env: &mut JNIEnv, + context: &JObject, + text: &str, + ) -> Result { + let clipboard_name = jobject_str(env, "clipboard")?; + let clipboard = env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[JValue::Object(&clipboard_name)], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("get clipboard service: {error}"))?; + let label = jobject_str(env, "OpenLess")?; + let text_obj = jobject_str(env, text)?; + let clip = env + .call_static_method( + "android/content/ClipData", + "newPlainText", + "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + &[JValue::Object(&label), JValue::Object(&text_obj)], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("new ClipData: {error}"))?; + env.call_method( + &clipboard, + "setPrimaryClip", + "(Landroid/content/ClipData;)V", + &[JValue::Object(&clip)], + ) + .map_err(|error| format!("setPrimaryClip: {error}"))?; + Ok(true) + } + + pub fn notify_overlay_bridge<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + state: &str, + message: Option<&str>, + ) -> Result<(), String> { + let state_obj = jobject_str(env, state)?; + let message_obj = jobject_str(env, message.unwrap_or(""))?; + call_static_void_with_context_class( + env, + context, + "com.openless.app.OpenLessOverlayBridge", + "onCapsuleStateChanged", + "(Ljava/lang/String;Ljava/lang/String;)V", + &[JValue::Object(&state_obj), JValue::Object(&message_obj)], + ) + } + + pub fn show_overlay_toast<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + message: &str, + ) -> Result<(), String> { + let message_obj = jobject_str(env, message)?; + call_static_void_with_context_class( + env, + context, + "com.openless.app.OpenLessOverlayBridge", + "showToast", + "(Ljava/lang/String;)V", + &[JValue::Object(&message_obj)], + ) + } + + pub fn accessibility_paste<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + ) -> Result { + call_static_bool_with_context_class( + env, + context, + "com.openless.app.OpenLessAccessibilityService", + "pasteToFocusedField", + "()Z", + &[], + ) + } + + pub fn accessibility_selected_text<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + ) -> Result, String> { + let class = load_context_class( + env, + context, + "com.openless.app.OpenLessAccessibilityService", + )?; + let value = env + .call_static_method(class, "captureSelectedText", "()Ljava/lang/String;", &[]) + .and_then(|value| value.l()) + .map_err(|error| { + format!("call com.openless.app.OpenLessAccessibilityService.captureSelectedText: {error}") + })?; + if value.is_null() { + return Ok(None); + } + let text = env + .get_string(&JString::from(value)) + .map_err(|error| format!("read selected text jstring: {error}"))? + .to_string_lossy() + .into_owned(); + if text.trim().is_empty() { + Ok(None) + } else { + Ok(Some(text)) + } + } + + pub fn accessibility_enabled<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + ) -> Result { + call_static_bool_with_context_class( + env, + context, + "com.openless.app.OpenLessAccessibilityService", + "isEnabled", + "(Landroid/content/Context;)Z", + &[JValue::Object(context)], + ) + } + + pub fn accessibility_operational<'local>( + env: &mut JNIEnv<'local>, + context: &JObject<'local>, + ) -> Result { + call_static_bool_with_context_class( + env, + context, + "com.openless.app.OpenLessAccessibilityService", + "isOperational", + "(Landroid/content/Context;)Z", + &[JValue::Object(context)], + ) + } + + pub fn launch_accessibility_settings( + env: &mut JNIEnv, + context: &JObject, + ) -> Result<(), String> { + let action_obj = jobject_str(env, "android.settings.ACCESSIBILITY_SETTINGS")?; + start_settings_intent(env, context, &action_obj, None) + } + + fn start_settings_intent( + env: &mut JNIEnv, + context: &JObject, + action_obj: &JObject, + data_uri: Option<&JObject>, + ) -> Result<(), String> { + let intent = env + .new_object( + "android/content/Intent", + "(Ljava/lang/String;)V", + &[JValue::Object(&action_obj)], + ) + .map_err(|error| format!("create settings intent: {error}"))?; + if let Some(uri) = data_uri { + env.call_method( + &intent, + "setData", + "(Landroid/net/Uri;)Landroid/content/Intent;", + &[JValue::Object(uri)], + ) + .map_err(|error| format!("set settings intent data: {error}"))?; + } + env.call_method( + &intent, + "addFlags", + "(I)Landroid/content/Intent;", + &[JValue::Int(0x10000000)], + ) + .map_err(|error| format!("set intent flags: {error}"))?; + env.call_method( + context, + "startActivity", + "(Landroid/content/Intent;)V", + &[JValue::Object(&intent)], + ) + .map_err(|error| format!("start settings activity: {error}"))?; + Ok(()) + } + + pub fn export_jstring(env: &mut JNIEnv, value: &str) -> jni::sys::jstring { + env.new_string(value) + .map(|s| s.into_raw()) + .unwrap_or(std::ptr::null_mut()) + } + + pub fn export_jboolean(value: bool) -> jni::sys::jboolean { + if value { + 1 + } else { + 0 + } + } +} diff --git a/openless-all/app/src-tauri/src/android/mod.rs b/openless-all/app/src-tauri/src/android/mod.rs new file mode 100644 index 00000000..0f58039e --- /dev/null +++ b/openless-all/app/src-tauri/src/android/mod.rs @@ -0,0 +1,25 @@ +//! Android platform integration (JNI, overlay, accessibility, insert). + +pub mod accessibility; +#[cfg(target_os = "android")] +pub mod insert; +pub mod jni; +pub mod native_bridge; +pub mod overlay; +pub use crate::types::android_types as types; + +pub use accessibility::{ + get_android_accessibility_status, paste_via_accessibility, + request_android_accessibility_permission, AndroidAccessibilityPermissionResult, +}; +#[cfg(target_os = "android")] +pub use insert::android_insert_with_strategy; +pub use native_bridge::{ + hide_overlay, is_overlay_visible, notify_capsule_state, refresh_overlay_if_visible, + refresh_overlay_layout, register_android_coordinator, replace_overlay, show_overlay, +}; +pub use overlay::{ + get_android_overlay_status, hide_android_overlay, refresh_android_overlay_if_visible, + refresh_android_overlay_layout, replace_android_overlay, request_android_overlay_permission, + show_android_overlay, AndroidOverlayPermissionResult, +}; diff --git a/openless-all/app/src-tauri/src/android/native_bridge.rs b/openless-all/app/src-tauri/src/android/native_bridge.rs new file mode 100644 index 00000000..9673e2ce --- /dev/null +++ b/openless-all/app/src-tauri/src/android/native_bridge.rs @@ -0,0 +1,396 @@ +//! JNI bridge between Kotlin overlay code and Rust Coordinator. + +use std::sync::{Arc, OnceLock}; + +use crate::coordinator::Coordinator; +use crate::types::{CapsulePayload, CapsuleState}; + +static COORDINATOR: OnceLock> = OnceLock::new(); +static OVERLAY_VISIBLE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +pub fn register_android_coordinator(coordinator: Arc) { + let _ = COORDINATOR.set(coordinator); +} + +pub fn notify_capsule_state(payload: &CapsulePayload) { + #[cfg(target_os = "android")] + { + let state = capsule_state_name(payload.state); + let message = payload.message.as_deref(); + if let Err(error) = crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::notify_overlay_bridge(env, context, state, message) + }) { + log::warn!("[android-native] notify overlay bridge failed: {error}"); + } + } + let _ = payload; +} + +pub fn show_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + crate::android::jni::android::with_android_env(|env, context| { + show_overlay_with_context(env, context) + })?; + } + Ok(()) +} + +pub fn hide_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + crate::android::jni::android::with_android_env(|env, context| { + hide_overlay_with_context(env, context) + })?; + } + Ok(()) +} + +pub fn replace_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + crate::android::jni::android::with_android_env(|env, context| { + replace_overlay_with_context(env, context) + })?; + } + Ok(()) +} + +pub fn refresh_overlay_layout() -> Result<(), String> { + #[cfg(target_os = "android")] + { + crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::start_service_action( + env, + context, + "com.openless.app.OpenLessOverlayService", + "com.openless.app.overlay.REFRESH_LAYOUT", + ) + })?; + } + Ok(()) +} + +pub fn refresh_overlay_if_visible() -> Result<(), String> { + if is_overlay_visible() { + refresh_overlay_layout() + } else { + Ok(()) + } +} + +#[cfg(target_os = "android")] +fn show_overlay_with_context( + env: &mut jni::JNIEnv, + context: &jni::objects::JObject, +) -> Result<(), String> { + crate::android::jni::android::start_service_action( + env, + context, + "com.openless.app.OpenLessOverlayService", + "com.openless.app.overlay.SHOW", + )?; + OVERLAY_VISIBLE.store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) +} + +#[cfg(target_os = "android")] +fn hide_overlay_with_context( + env: &mut jni::JNIEnv, + context: &jni::objects::JObject, +) -> Result<(), String> { + crate::android::jni::android::start_service_action( + env, + context, + "com.openless.app.OpenLessOverlayService", + "com.openless.app.overlay.HIDE", + )?; + OVERLAY_VISIBLE.store(false, std::sync::atomic::Ordering::SeqCst); + Ok(()) +} + +#[cfg(target_os = "android")] +fn replace_overlay_with_context( + env: &mut jni::JNIEnv, + context: &jni::objects::JObject, +) -> Result<(), String> { + crate::android::jni::android::start_service_action( + env, + context, + "com.openless.app.OpenLessOverlayService", + "com.openless.app.overlay.REPLACE_OVERLAY", + )?; + OVERLAY_VISIBLE.store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) +} + +pub fn is_overlay_visible() -> bool { + OVERLAY_VISIBLE.load(std::sync::atomic::Ordering::SeqCst) +} + +pub fn overlay_trigger_mode_name() -> &'static str { + let Some(coordinator) = COORDINATOR.get() else { + return "background"; + }; + match coordinator.android_overlay_trigger() { + crate::types::AndroidOverlayTrigger::Background => "background", + crate::types::AndroidOverlayTrigger::Keyboard => "keyboard", + crate::types::AndroidOverlayTrigger::Always => "always", + } +} + +fn spawn_start_dictation(translation: bool) { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + tauri::async_runtime::spawn(async move { + let result = if translation { + coordinator.start_dictation_with_translation().await + } else { + coordinator.start_dictation().await + }; + if let Err(error) = result { + log::warn!( + "[android-native] {} failed: {error}", + if translation { + "start_dictation_with_translation" + } else { + "start_dictation" + } + ); + } + }); +} + +fn spawn_stop_dictation() { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + tauri::async_runtime::spawn(async move { + if let Err(error) = coordinator.stop_dictation().await { + log::warn!("[android-native] stop_dictation failed: {error}"); + } + }); +} + +fn spawn_stop_dictation_with_translation(translation: bool) { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + tauri::async_runtime::spawn(async move { + if let Err(error) = coordinator + .stop_dictation_with_translation(translation) + .await + { + log::warn!("[android-native] stop_dictation_with_translation failed: {error}"); + } + }); +} + +fn spawn_cancel_dictation() { + let Some(coordinator) = COORDINATOR.get().cloned() else { + return; + }; + coordinator.cancel_dictation(); +} + +fn spawn_switch_style_pack() { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + coordinator.switch_to_previous_style_pack(); +} + +fn spawn_open_qa_from_overlay() { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + log::info!("[android-native] open_qa_from_overlay requested"); + tauri::async_runtime::spawn(async move { + if let Err(error) = coordinator.open_qa_from_overlay().await { + log::warn!("[android-native] open_qa_from_overlay failed: {error}"); + } + }); +} + +fn spawn_finalize_qa_from_overlay() { + let Some(coordinator) = COORDINATOR.get().cloned() else { + log::warn!("[android-native] coordinator unavailable"); + return; + }; + log::info!("[android-native] finalize_qa_from_overlay requested"); + tauri::async_runtime::spawn(async move { + if let Err(error) = coordinator.finalize_qa_from_overlay().await { + log::warn!("[android-native] finalize_qa_from_overlay failed: {error}"); + } + }); +} + +fn capsule_state_name(state: CapsuleState) -> &'static str { + match state { + CapsuleState::Idle => "idle", + CapsuleState::Recording => "recording", + CapsuleState::Transcribing => "transcribing", + CapsuleState::Polishing => "polishing", + CapsuleState::Done => "done", + CapsuleState::Cancelled => "cancelled", + CapsuleState::Error => "error", + } +} + +#[cfg(target_os = "android")] +mod jni_exports { + use super::*; + use jni::objects::{JClass, JObject}; + use jni::sys::{jboolean, jstring, JNIEnv}; + use jni::JNIEnv as JniEnv; + + unsafe fn with_jni_context( + env_ptr: *mut JNIEnv, + context: JObject, + f: impl for<'local> FnOnce(&mut JniEnv<'local>, &JObject<'local>) -> Result, + ) -> Result { + let mut env = + JniEnv::from_raw(env_ptr).map_err(|error| format!("attach JNI env: {error}"))?; + f(&mut env, &context) + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeStartDictation( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_start_dictation(false); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeStartDictationWithTranslation( + _env: *mut JNIEnv, + _class: JClass, + translation: jboolean, + ) { + spawn_start_dictation(translation != 0); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeStopDictation( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_stop_dictation(); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeStopDictationWithTranslation( + _env: *mut JNIEnv, + _class: JClass, + translation: jboolean, + ) { + spawn_stop_dictation_with_translation(translation != 0); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeCancelDictation( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_cancel_dictation(); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeSwitchStylePack( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_switch_style_pack(); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeOpenQaFromOverlay( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_open_qa_from_overlay(); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeFinalizeQaFromOverlay( + _env: *mut JNIEnv, + _class: JClass, + ) { + spawn_finalize_qa_from_overlay(); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeShowOverlay( + env: *mut JNIEnv, + _class: JClass, + context: JObject, + ) { + let _ = with_jni_context(env, context, |env, context| { + show_overlay_with_context(env, context) + }); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeHideOverlay( + env: *mut JNIEnv, + _class: JClass, + context: JObject, + ) { + let _ = with_jni_context(env, context, |env, context| { + hide_overlay_with_context(env, context) + }); + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeCanDrawOverlays( + env: *mut JNIEnv, + _class: JClass, + context: JObject, + ) -> jboolean { + let visible = with_jni_context(env, context, |env, context| { + crate::android::jni::android::can_draw_overlays(env, context) + }) + .unwrap_or(false); + crate::android::jni::android::export_jboolean(visible) + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeIsOverlayVisible( + _env: *mut JNIEnv, + _class: JClass, + ) -> jboolean { + crate::android::jni::android::export_jboolean(is_overlay_visible()) + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeGetOverlayTriggerMode( + env: *mut JNIEnv, + _class: JClass, + ) -> jstring { + let mode = overlay_trigger_mode_name(); + match JniEnv::from_raw(env) { + Ok(mut env) => crate::android::jni::android::export_jstring(&mut env, mode), + Err(_) => std::ptr::null_mut(), + } + } + + #[no_mangle] + pub unsafe extern "system" fn Java_com_openless_app_OpenLessNative_nativeNotifyOverlayPermissionChanged( + env: *mut JNIEnv, + _class: JClass, + context: JObject, + ) { + if overlay_trigger_mode_name() == "always" { + let _ = with_jni_context(env, context, |env, context| { + show_overlay_with_context(env, context) + }); + } + } +} diff --git a/openless-all/app/src-tauri/src/android/overlay.rs b/openless-all/app/src-tauri/src/android/overlay.rs new file mode 100644 index 00000000..4b45b455 --- /dev/null +++ b/openless-all/app/src-tauri/src/android/overlay.rs @@ -0,0 +1,145 @@ +//! Android overlay window permission and foreground service integration. + +use serde::Serialize; + +use crate::android::types::AndroidOverlayStatus; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AndroidOverlayPermissionResult { + pub launched: bool, + pub message: String, +} + +pub fn get_android_overlay_status() -> AndroidOverlayStatus { + #[cfg(target_os = "android")] + { + android_impl::get_android_overlay_status() + } + + #[cfg(not(target_os = "android"))] + { + use crate::android::types::AndroidOverlayPermissionState; + + AndroidOverlayStatus { + permission: AndroidOverlayPermissionState::NotAndroid, + overlay_visible: false, + message: "Android overlay is only available on Android".to_string(), + } + } +} + +pub fn request_android_overlay_permission() -> AndroidOverlayPermissionResult { + #[cfg(target_os = "android")] + { + android_impl::request_android_overlay_permission() + } + + #[cfg(not(target_os = "android"))] + { + AndroidOverlayPermissionResult { + launched: false, + message: "Android overlay permission is only available on Android".to_string(), + } + } +} + +pub fn show_android_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + return crate::android::native_bridge::show_overlay(); + } + #[cfg(not(target_os = "android"))] + { + Err("Android overlay is only available on Android".to_string()) + } +} + +pub fn hide_android_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + return crate::android::native_bridge::hide_overlay(); + } + #[cfg(not(target_os = "android"))] + { + Err("Android overlay is only available on Android".to_string()) + } +} + +pub fn refresh_android_overlay_if_visible() -> Result<(), String> { + #[cfg(target_os = "android")] + { + return crate::android::native_bridge::refresh_overlay_if_visible(); + } + #[cfg(not(target_os = "android"))] + { + Err("Android overlay is only available on Android".to_string()) + } +} + +pub fn refresh_android_overlay_layout() -> Result<(), String> { + #[cfg(target_os = "android")] + { + return crate::android::native_bridge::refresh_overlay_layout(); + } + #[cfg(not(target_os = "android"))] + { + Err("Android overlay is only available on Android".to_string()) + } +} + +pub fn replace_android_overlay() -> Result<(), String> { + #[cfg(target_os = "android")] + { + return crate::android::native_bridge::replace_overlay(); + } + #[cfg(not(target_os = "android"))] + { + Err("Android overlay is only available on Android".to_string()) + } +} + +#[cfg(target_os = "android")] +mod android_impl { + use super::{AndroidOverlayPermissionResult, AndroidOverlayStatus}; + use crate::android::types::{AndroidOverlayPermissionState, AndroidOverlayStatus as Status}; + + pub fn get_android_overlay_status() -> AndroidOverlayStatus { + let granted = crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::can_draw_overlays(env, context) + }) + .unwrap_or(false); + Status { + permission: if granted { + AndroidOverlayPermissionState::Granted + } else { + AndroidOverlayPermissionState::NotGranted + }, + overlay_visible: crate::android::native_bridge::is_overlay_visible(), + message: if granted { + "悬浮窗权限已授予".to_string() + } else { + "请在系统设置中授予悬浮窗权限".to_string() + }, + } + } + + pub fn request_android_overlay_permission() -> AndroidOverlayPermissionResult { + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::start_activity_class( + env, + context, + "com.openless.app.OverlayPermissionActivity", + ) + }) { + Ok(()) => AndroidOverlayPermissionResult { + launched: true, + message: "已打开悬浮窗权限设置".to_string(), + }, + Err(error) => AndroidOverlayPermissionResult { + launched: false, + message: error, + }, + } + } +} diff --git a/openless-all/app/src-tauri/src/android/types.rs b/openless-all/app/src-tauri/src/android/types.rs new file mode 100644 index 00000000..c32d6e02 --- /dev/null +++ b/openless-all/app/src-tauri/src/android/types.rs @@ -0,0 +1,323 @@ +//! Android-specific preference types and status payloads. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum AndroidInsertStrategy { + Auto, + Ime, + Accessibility, + Clipboard, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum AndroidOverlayTrigger { + Background, + Keyboard, + Always, +} + +impl AndroidOverlayTrigger { + pub fn normalized(self) -> Self { + match self { + AndroidOverlayTrigger::Keyboard => AndroidOverlayTrigger::Background, + trigger => trigger, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AndroidOverlayActivationMode { + Tap, + LongPress, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AndroidOverlayLeftSwipeAction { + Translation, + StylePack, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AndroidOverlayCancelSwipeDirection { + Up, + Down, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum AndroidAccessibilityState { + Enabled, + NotEnabled, + NotAndroid, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AndroidAccessibilityStatus { + pub state: AndroidAccessibilityState, + pub enabled: bool, + pub message: String, +} + +pub fn default_android_insert_strategy() -> AndroidInsertStrategy { + AndroidInsertStrategy::Accessibility +} + +pub fn default_android_overlay_trigger() -> AndroidOverlayTrigger { + AndroidOverlayTrigger::Background +} + +pub fn default_android_overlay_activation_mode() -> AndroidOverlayActivationMode { + AndroidOverlayActivationMode::Tap +} + +pub fn default_android_overlay_left_swipe_action() -> AndroidOverlayLeftSwipeAction { + AndroidOverlayLeftSwipeAction::Translation +} + +pub fn default_android_overlay_cancel_swipe_direction() -> AndroidOverlayCancelSwipeDirection { + AndroidOverlayCancelSwipeDirection::Up +} + +pub fn default_android_overlay_size_dp() -> u32 { + 72 +} + +pub fn normalize_android_insert_strategy(strategy: AndroidInsertStrategy) -> AndroidInsertStrategy { + match strategy { + AndroidInsertStrategy::Auto | AndroidInsertStrategy::Ime => { + AndroidInsertStrategy::Accessibility + } + strategy => strategy, + } +} + +pub fn normalize_android_overlay_size_dp(size_dp: u32) -> u32 { + size_dp.clamp(48, 120) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AndroidOverlaySettingsAction { + None, + RefreshLayout, + Transition { + from: AndroidOverlayTrigger, + to: AndroidOverlayTrigger, + }, +} + +pub fn classify_android_overlay_settings_change( + previous: &super::UserPreferences, + next: &super::UserPreferences, +) -> AndroidOverlaySettingsAction { + let trigger_changed = + previous.android_overlay_trigger.normalized() != next.android_overlay_trigger.normalized(); + let size_changed = normalize_android_overlay_size_dp(previous.android_overlay_size_dp) + != normalize_android_overlay_size_dp(next.android_overlay_size_dp); + + if trigger_changed { + return AndroidOverlaySettingsAction::Transition { + from: previous.android_overlay_trigger.normalized(), + to: next.android_overlay_trigger.normalized(), + }; + } + + if size_changed { + return AndroidOverlaySettingsAction::RefreshLayout; + } + + AndroidOverlaySettingsAction::None +} + +#[cfg(test)] +mod android_overlay_tests { + use super::*; + use crate::types::UserPreferences; + + fn overlay_prefs( + trigger: AndroidOverlayTrigger, + size_dp: u32, + activation: AndroidOverlayActivationMode, + ) -> UserPreferences { + let mut prefs = UserPreferences::default(); + prefs.android_overlay_trigger = trigger; + prefs.android_overlay_size_dp = size_dp; + prefs.android_overlay_activation_mode = activation; + prefs + } + + #[test] + fn size_only_change_returns_refresh_layout() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 96, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::RefreshLayout, + ); + } + + #[test] + fn trigger_only_change_returns_transition() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Background, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::Transition { + from: AndroidOverlayTrigger::Background, + to: AndroidOverlayTrigger::Always, + }, + ); + } + + #[test] + fn trigger_and_size_change_returns_transition_only() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Background, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 96, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::Transition { + from: AndroidOverlayTrigger::Background, + to: AndroidOverlayTrigger::Always, + }, + ); + } + + #[test] + fn activation_only_change_returns_none() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::LongPress, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::None, + ); + } + + #[test] + fn out_of_bounds_size_200_to_120_returns_none_after_normalize() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Always, + 200, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 120, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::None, + ); + } + + #[test] + fn out_of_bounds_size_below_min_normalizes_to_same_returns_none() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Always, + 30, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 48, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::None, + ); + } + + #[test] + fn identical_normalized_size_returns_none() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::None, + ); + } + + #[test] + fn keyboard_trigger_normalizes_to_background_for_transition() { + let previous = overlay_prefs( + AndroidOverlayTrigger::Keyboard, + 72, + AndroidOverlayActivationMode::Tap, + ); + let next = overlay_prefs( + AndroidOverlayTrigger::Always, + 72, + AndroidOverlayActivationMode::Tap, + ); + assert_eq!( + classify_android_overlay_settings_change(&previous, &next), + AndroidOverlaySettingsAction::Transition { + from: AndroidOverlayTrigger::Background, + to: AndroidOverlayTrigger::Always, + }, + ); + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum AndroidOverlayPermissionState { + Granted, + NotGranted, + NotAndroid, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AndroidOverlayStatus { + pub permission: AndroidOverlayPermissionState, + pub overlay_visible: bool, + pub message: String, +} diff --git a/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs b/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs index 04d18545..91628f68 100644 --- a/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs +++ b/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs @@ -77,10 +77,11 @@ impl AppleSpeechAsr { // SFSpeechRecognizer 是阻塞且基于 objc runloop 的同步桥接;放到 // spawn_blocking 不占 tokio runtime。与 LocalQwenAsr 走同一个 Tauri // 持有的 runtime handle。 - let result = - tauri::async_runtime::spawn_blocking(move || transcribe_pcm_blocking(&pcm, duration_ms)) - .await - .context("apple-speech transcribe spawn_blocking join 失败")?; + let result = tauri::async_runtime::spawn_blocking(move || { + transcribe_pcm_blocking(&pcm, duration_ms) + }) + .await + .context("apple-speech transcribe spawn_blocking join 失败")?; if result.is_ok() { self.buffer.lock().clear(); @@ -265,8 +266,9 @@ fn build_outcome(result: *mut AnyObject, error: *mut AnyObject) -> RecognitionOu } fn speech_recognizer_class() -> Result<&'static AnyClass> { - AnyClass::get("SFSpeechRecognizer") - .ok_or_else(|| anyhow!("SFSpeechRecognizer 类不可用(需要 macOS 10.15+ 并链接 Speech.framework)")) + AnyClass::get("SFSpeechRecognizer").ok_or_else(|| { + anyhow!("SFSpeechRecognizer 类不可用(需要 macOS 10.15+ 并链接 Speech.framework)") + }) } /// `[[SFSpeechRecognizer alloc] init]` —— 用系统当前 locale。 diff --git a/openless-all/app/src-tauri/src/asr/local/download.rs b/openless-all/app/src-tauri/src/asr/local/download.rs index 81f4f04d..fa4d4e90 100644 --- a/openless-all/app/src-tauri/src/asr/local/download.rs +++ b/openless-all/app/src-tauri/src/asr/local/download.rs @@ -226,18 +226,20 @@ impl DownloadManager { pub(crate) fn build_client() -> Result { // native-tls (macOS=SecureTransport) 不像 rustls 那样把 CDN unclean close - // 当致命错误。 + // 当致命错误。Android/iOS 无 native-tls feature,走默认 rustls。 // // User-Agent 用 aria2 的——hfd(hf-mirror 官方推荐)就是 aria2 包装, // 实测 aria2 UA 在 HF 反滥用规则里走白名单不挨 throttle;自定义 UA // (`openless/x`) 在 sustained 传输后会被 mirror 主动切流。 - reqwest::Client::builder() - .use_native_tls() + let mut builder = reqwest::Client::builder() .user_agent("aria2/1.36.0") .connect_timeout(std::time::Duration::from_secs(30)) - .pool_idle_timeout(std::time::Duration::from_secs(60)) - .build() - .context("build reqwest client failed") + .pool_idle_timeout(std::time::Duration::from_secs(60)); + #[cfg(not(mobile))] + { + builder = builder.use_native_tls(); + } + builder.build().context("build reqwest client failed") } async fn run_download( diff --git a/openless-all/app/src-tauri/src/asr/local/test_run.rs b/openless-all/app/src-tauri/src/asr/local/test_run.rs index 3f60abb9..930bebde 100644 --- a/openless-all/app/src-tauri/src/asr/local/test_run.rs +++ b/openless-all/app/src-tauri/src/asr/local/test_run.rs @@ -55,7 +55,10 @@ pub async fn run_test(model_id: ModelId) -> Result { for fname in &required_files { let path = dir.join(fname); if !path.exists() { - anyhow::bail!("模型文件缺失:{fname},请重新下载(预期路径:{})", path.display()); + anyhow::bail!( + "模型文件缺失:{fname},请重新下载(预期路径:{})", + path.display() + ); } let meta = std::fs::metadata(&path) .map_err(|e| anyhow::anyhow!("读取 {fname} 元数据失败:{e}"))?; diff --git a/openless-all/app/src-tauri/src/commands/credentials.rs b/openless-all/app/src-tauri/src/commands/credentials.rs index 1778199e..164b6ac2 100644 --- a/openless-all/app/src-tauri/src/commands/credentials.rs +++ b/openless-all/app/src-tauri/src/commands/credentials.rs @@ -28,6 +28,14 @@ pub(crate) fn asr_configured_for_provider(provider: &str, snap: &CredentialsSnap if provider == "volcengine" { return volcengine_configured(snap); } + if cfg!(mobile) + && (provider == crate::asr::local::PROVIDER_ID + || provider == crate::asr::local::sherpa::PROVIDER_ID + || provider == crate::asr::local::foundry::PROVIDER_ID + || provider == crate::asr::local::APPLE_SPEECH_PROVIDER_ID) + { + return false; + } if provider == crate::asr::local::PROVIDER_ID || active_apple_speech_asr_is_supported(provider) || active_foundry_asr_is_supported(provider) @@ -100,12 +108,14 @@ fn configured(field: &Option) -> bool { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg(not(mobile))] pub(crate) struct LocalAsrReleasePlan { pub(crate) qwen: bool, pub(crate) foundry: bool, pub(crate) sherpa: bool, } +#[cfg(not(mobile))] pub(crate) fn local_asr_release_plan_for_provider(provider: &str) -> LocalAsrReleasePlan { LocalAsrReleasePlan { qwen: provider != crate::asr::local::PROVIDER_ID, @@ -114,6 +124,7 @@ pub(crate) fn local_asr_release_plan_for_provider(provider: &str) -> LocalAsrRel } } +#[cfg(not(mobile))] pub(crate) async fn release_foundry_runtime_if_inactive( runtime: &Arc, release_foundry: bool, @@ -126,6 +137,7 @@ pub(crate) async fn release_foundry_runtime_if_inactive( } } +#[cfg(not(mobile))] pub(crate) async fn release_sherpa_runtime_if_inactive( runtime: &Arc, release_sherpa: bool, @@ -154,6 +166,26 @@ pub fn set_credential(window: Window, account: String, value: String) -> Result< Ok(()) } +#[cfg(mobile)] +#[tauri::command] +pub async fn set_active_asr_provider( + _coord: CoordinatorState<'_>, + provider: String, +) -> Result<(), String> { + if provider == crate::asr::local::PROVIDER_ID + || provider == crate::asr::local::sherpa::PROVIDER_ID + || provider == crate::asr::local::foundry::PROVIDER_ID + || provider == crate::asr::local::APPLE_SPEECH_PROVIDER_ID + { + return Err("Local ASR is not available on mobile".to_string()); + } + if CredentialsVault::get_active_asr() == provider { + return Ok(()); + } + CredentialsVault::set_active_asr_provider(&provider).map_err(|e| e.to_string()) +} + +#[cfg(not(mobile))] #[tauri::command] pub async fn set_active_asr_provider( coord: CoordinatorState<'_>, diff --git a/openless-all/app/src-tauri/src/commands/misc.rs b/openless-all/app/src-tauri/src/commands/misc.rs index bb2bfc9c..83506a6a 100644 --- a/openless-all/app/src-tauri/src/commands/misc.rs +++ b/openless-all/app/src-tauri/src/commands/misc.rs @@ -36,30 +36,74 @@ pub async fn check_network() -> NetworkCheckResult { #[tauri::command] pub fn get_hotkey_status(coord: CoordinatorState<'_>) -> HotkeyStatus { + #[cfg(mobile)] + { + let _ = coord; + return HotkeyStatus { + adapter: crate::types::HotkeyAdapterKind::Unavailable, + state: crate::types::HotkeyStatusState::Failed, + message: Some("移动端不支持全局热键".into()), + last_error: Some(crate::types::HotkeyInstallError { + code: "unavailable".into(), + message: "Global hotkeys are not available on mobile".into(), + }), + }; + } + #[cfg(not(mobile))] coord.hotkey_status() } #[tauri::command] pub fn get_hotkey_capability(coord: CoordinatorState<'_>) -> HotkeyCapability { + #[cfg(mobile)] + { + let _ = coord; + return HotkeyCapability::current(); + } + #[cfg(not(mobile))] coord.hotkey_capability() } #[tauri::command] pub fn set_shortcut_recording_active(coord: CoordinatorState<'_>, active: bool) { + #[cfg(mobile)] + { + let _ = (coord, active); + return; + } + #[cfg(not(mobile))] coord.set_shortcut_recording_active(active); } #[tauri::command] +#[cfg(not(mobile))] pub fn get_windows_ime_status() -> WindowsImeStatus { crate::windows_ime_profile::get_windows_ime_status() } #[tauri::command] +#[cfg(mobile)] +pub fn list_microphone_devices() -> Result, String> { + Ok(Vec::new()) +} + +#[tauri::command] +#[cfg(not(mobile))] pub fn list_microphone_devices() -> Result, String> { crate::recorder::list_input_devices().map_err(|e| e.to_string()) } #[tauri::command] +#[cfg(mobile)] +pub async fn start_microphone_level_monitor( + _app: AppHandle, + _device_name: String, +) -> Result<(), String> { + Ok(()) +} + +#[tauri::command] +#[cfg(not(mobile))] pub async fn start_microphone_level_monitor( app: AppHandle, device_name: String, @@ -93,6 +137,12 @@ pub async fn start_microphone_level_monitor( #[tauri::command] pub async fn stop_microphone_level_monitor(app: AppHandle) { + #[cfg(mobile)] + { + let _ = app; + return; + } + #[cfg(not(mobile))] let _ = tauri::async_runtime::spawn_blocking(move || { let state = app.state::(); let recorder = state.lock().take(); diff --git a/openless-all/app/src-tauri/src/commands/mod.rs b/openless-all/app/src-tauri/src/commands/mod.rs index 53ebd656..2c128ba5 100644 --- a/openless-all/app/src-tauri/src/commands/mod.rs +++ b/openless-all/app/src-tauri/src/commands/mod.rs @@ -10,8 +10,11 @@ use std::sync::Arc; +#[cfg(not(mobile))] use parking_lot::Mutex; -use tauri::{Manager, State}; +#[cfg(not(mobile))] +use tauri::Manager; +use tauri::State; // 跨域共享的 crate 级导入:以 `pub(crate) use` 重导出,子模块用 `use super::*;` // 即可拿到,避免在 16 个文件里重复同一组 import。 @@ -19,18 +22,22 @@ pub(crate) use serde::Serialize; pub(crate) use serde_json::Value; pub(crate) use tauri::{AppHandle, Emitter, Window}; +#[cfg(not(mobile))] pub(crate) use crate::asr::local::foundry::{ model_alias_is_known, FoundryCatalogModel, FoundryPrepareProgressPayload, FoundryRuntimeStatus, DEFAULT_MODEL_ALIAS, PROVIDER_ID as FOUNDRY_LOCAL_PROVIDER_ID, }; +#[cfg(not(mobile))] pub(crate) use crate::asr::local::sherpa::{ model_alias_is_known as sherpa_model_alias_is_known, SherpaCatalogModel, SherpaPrepareProgressPayload, SherpaRuntimeStatus, DEFAULT_MODEL_ALIAS as SHERPA_DEFAULT_MODEL_ALIAS, }; +#[cfg(not(mobile))] pub(crate) use crate::asr::local::sherpa_download::{ fetch_remote_info as fetch_sherpa_remote_info, SherpaDownloadManager, SherpaRemoteInfo, }; +#[cfg(not(mobile))] pub(crate) use crate::asr::local::{FoundryLocalRuntime, Mirror, SherpaOnnxRuntime}; pub(crate) use crate::coordinator::Coordinator; pub(crate) use crate::net; @@ -44,73 +51,90 @@ pub(crate) use crate::polish::{ OpenAICompatibleConfig, OpenAICompatibleLLMProvider, CODEX_DEFAULT_MODEL, CODEX_OAUTH_PROVIDER_ID, }; +#[cfg(not(mobile))] pub(crate) use crate::recorder::{AudioConsumer, Recorder}; +#[cfg(not(mobile))] +pub(crate) use crate::types::WindowsImeStatus; pub(crate) use crate::types::{ - builtin_style_pack_id, default_active_style_pack_id, ChineseScriptPreference, ComboBinding, - CorrectionRule, CredentialsStatus, DictationSession, DictionaryEntry, HotkeyCapability, - HotkeyStatus, OutputLanguagePreference, PolishMode, ShortcutBinding, StylePack, StylePackKind, - StylePackRuntimeDiagnostics, StyleSystemPrompts, UpdateChannel, UserPreferences, - VocabPresetStore, WindowsImeStatus, + builtin_style_pack_id, default_active_style_pack_id, AndroidAccessibilityStatus, + AndroidOverlayStatus, ChineseScriptPreference, ComboBinding, CorrectionRule, CredentialsStatus, + DictationSession, DictionaryEntry, HotkeyCapability, HotkeyStatus, OutputLanguagePreference, + PolishMode, ShortcutBinding, StylePack, StylePackKind, StylePackRuntimeDiagnostics, + StyleSystemPrompts, UpdateChannel, UserPreferences, VocabPresetStore, }; mod credentials; mod dictation; mod dictionary; +#[cfg(not(mobile))] mod foundry_asr; mod github_oauth; mod history; mod hotkeys; +#[cfg(not(mobile))] mod local_asr; mod marketplace; mod misc; mod permissions_cmds; mod providers; mod qa; +#[cfg(not(mobile))] mod remote_input; mod settings; +#[cfg(not(mobile))] mod sherpa_asr; mod style_packs; pub use credentials::*; pub use dictation::*; pub use dictionary::*; +#[cfg(not(mobile))] pub use foundry_asr::*; pub use github_oauth::*; pub use history::*; pub use hotkeys::*; +#[cfg(not(mobile))] pub use local_asr::*; pub use marketplace::*; pub use misc::*; pub use permissions_cmds::*; pub use providers::*; pub use qa::*; +#[cfg(not(mobile))] pub use remote_input::*; pub use settings::*; // sherpa_onnx_asr_* 命令整组 `#[cfg(target_os = "windows")]`(见 lib.rs 的 // generate_handler! 清单)。非 Windows 平台这组 glob 重导出无人引用,会触发 // unused_imports;这是平台 cfg 的正常结果,不是真正的死代码。 +#[cfg(not(mobile))] #[allow(unused_imports)] pub use sherpa_asr::*; pub use style_packs::*; pub(crate) type CoordinatorState<'a> = State<'a, Arc>; +#[cfg(not(mobile))] pub type MicrophoneMonitorState = Mutex>; +#[cfg(not(mobile))] pub type TrayMicrophoneMenuState = Mutex>; +#[cfg(not(mobile))] pub struct TrayMicrophoneMenuItem { pub id: String, pub device_name: String, pub item: tauri::menu::CheckMenuItem, } +#[cfg(not(mobile))] pub fn sync_tray_microphone_selection(items: &[TrayMicrophoneMenuItem], device_name: &str) { for item in items { let _ = item.item.set_checked(item.device_name == device_name); } } +#[cfg(not(mobile))] pub(crate) struct LevelProbeConsumer; +#[cfg(not(mobile))] impl AudioConsumer for LevelProbeConsumer { fn consume_pcm_chunk(&self, _pcm: &[u8]) {} } @@ -188,7 +212,7 @@ pub(crate) fn open_path_in_file_manager(path: &std::path::Path) -> Result<(), St .map_err(|e| e.to_string()) } -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))] pub(crate) fn open_path_in_file_manager(path: &std::path::Path) -> Result<(), String> { std::process::Command::new("xdg-open") .arg(path) diff --git a/openless-all/app/src-tauri/src/commands/permissions_cmds.rs b/openless-all/app/src-tauri/src/commands/permissions_cmds.rs index d9351636..2a908386 100644 --- a/openless-all/app/src-tauri/src/commands/permissions_cmds.rs +++ b/openless-all/app/src-tauri/src/commands/permissions_cmds.rs @@ -1,5 +1,46 @@ use super::*; +#[tauri::command] +pub fn get_platform_capabilities() -> crate::types::PlatformCapabilities { + crate::types::PlatformCapabilities::current() +} + +#[tauri::command] +pub fn get_android_overlay_status() -> AndroidOverlayStatus { + crate::android::get_android_overlay_status() +} + +#[tauri::command] +pub fn request_android_overlay_permission() -> crate::android::AndroidOverlayPermissionResult { + crate::android::request_android_overlay_permission() +} + +#[tauri::command] +pub fn show_android_overlay() -> Result<(), String> { + crate::android::show_android_overlay() +} + +#[tauri::command] +pub fn hide_android_overlay() -> Result<(), String> { + crate::android::hide_android_overlay() +} + +#[tauri::command] +pub fn get_android_accessibility_status() -> AndroidAccessibilityStatus { + crate::android::get_android_accessibility_status() +} + +#[tauri::command] +pub fn request_android_accessibility_permission( +) -> crate::android::AndroidAccessibilityPermissionResult { + crate::android::request_android_accessibility_permission() +} + +#[tauri::command] +pub fn open_external_url(url: String) -> Result<(), String> { + crate::external_url::open_external_url(&url) +} + #[tauri::command] pub fn check_accessibility_permission() -> PermissionStatus { permissions::check_accessibility() diff --git a/openless-all/app/src-tauri/src/commands/providers.rs b/openless-all/app/src-tauri/src/commands/providers.rs index 2f302e8e..faa830dd 100644 --- a/openless-all/app/src-tauri/src/commands/providers.rs +++ b/openless-all/app/src-tauri/src/commands/providers.rs @@ -245,6 +245,9 @@ async fn validate_bailian_asr_provider() -> Result<(), String> { } pub(crate) fn active_asr_is_keyless_for_validation(provider: &str) -> bool { + if cfg!(mobile) { + return false; + } provider == crate::asr::local::PROVIDER_ID || active_apple_speech_asr_is_supported(provider) || active_foundry_asr_is_supported(provider) @@ -264,11 +267,11 @@ pub(crate) fn active_apple_speech_asr_is_supported(provider: &str) -> bool { } pub(crate) fn active_foundry_asr_is_supported(provider: &str) -> bool { - #[cfg(target_os = "windows")] + #[cfg(all(not(mobile), target_os = "windows"))] { provider == FOUNDRY_LOCAL_PROVIDER_ID } - #[cfg(not(target_os = "windows"))] + #[cfg(not(all(not(mobile), target_os = "windows")))] { let _ = provider; false @@ -276,11 +279,11 @@ pub(crate) fn active_foundry_asr_is_supported(provider: &str) -> bool { } pub(crate) fn active_sherpa_asr_is_supported(provider: &str) -> bool { - #[cfg(target_os = "windows")] + #[cfg(all(not(mobile), target_os = "windows"))] { provider == crate::asr::local::sherpa::PROVIDER_ID } - #[cfg(not(target_os = "windows"))] + #[cfg(not(all(not(mobile), target_os = "windows")))] { let _ = provider; false diff --git a/openless-all/app/src-tauri/src/commands/qa.rs b/openless-all/app/src-tauri/src/commands/qa.rs index 8d7a626b..779f8787 100644 --- a/openless-all/app/src-tauri/src/commands/qa.rs +++ b/openless-all/app/src-tauri/src/commands/qa.rs @@ -48,6 +48,19 @@ pub fn qa_window_pin(coord: CoordinatorState<'_>, pinned: bool) { coord.qa_window_pin(pinned); } +/// 移动端 QA 面板录音按钮:Idle -> begin_qa_session,Recording -> end_qa_session。 +#[tauri::command] +pub async fn qa_toggle_recording(coord: CoordinatorState<'_>) -> Result<(), String> { + coord.qa_toggle_recording().await; + Ok(()) +} + +/// QA 面板键盘输入:复用语音 QA 的 LLM 管线,只替换问题来源。 +#[tauri::command] +pub async fn qa_submit_text(coord: CoordinatorState<'_>, text: String) -> Result<(), String> { + coord.qa_submit_text(text).await +} + /// 用户点 ✕ / 按 Esc 关 Less Computer 浮窗。 #[tauri::command] pub fn less_computer_window_dismiss(coord: CoordinatorState<'_>) { diff --git a/openless-all/app/src-tauri/src/commands/settings.rs b/openless-all/app/src-tauri/src/commands/settings.rs index 56b01698..f5771de5 100644 --- a/openless-all/app/src-tauri/src/commands/settings.rs +++ b/openless-all/app/src-tauri/src/commands/settings.rs @@ -169,6 +169,7 @@ pub(crate) fn persist_settings( Ok(()) } +#[cfg(not(mobile))] #[tauri::command] pub fn set_settings( coord: CoordinatorState<'_>, @@ -180,10 +181,13 @@ pub fn set_settings( let remote_prev = coord.prefs().get(); let packs = coord.style_packs().list().map_err(|e| e.to_string())?; sync_style_pack_preferences(&mut prefs, &packs); + prefs.android_overlay_trigger = prefs.android_overlay_trigger.normalized(); // 广播给所有 webview。issue #205:QaPanel 跑在独立 webview, // 没有 HotkeySettingsContext,必须靠事件感知录音键变化,否则面板可见时 // 用户改键会让浮窗里的 "{recordHotkey}" 文案一直停留在旧值。 persist_settings(&*coord, prefs.clone())?; + #[cfg(target_os = "android")] + coord.apply_android_overlay_settings_change(&remote_prev, &prefs); // refresh_tray_microphone_menu 内部会调用 NSStatusItem.set_menu,必须在主线程上跑。 // set_settings 本身是同步 Tauri command,在 IPC handler 线程上执行;从这里直接调 // 会触发 macOS 主线程断言或在 dispatch 队列上死锁,导致整个 UI 无响应(用户改 @@ -213,6 +217,25 @@ pub fn set_settings( Ok(()) } +#[cfg(mobile)] +#[tauri::command] +pub fn set_settings( + coord: CoordinatorState<'_>, + app: AppHandle, + mut prefs: UserPreferences, +) -> Result<(), String> { + let previous = coord.prefs().get(); + let packs = coord.style_packs().list().map_err(|e| e.to_string())?; + sync_style_pack_preferences(&mut prefs, &packs); + prefs.android_overlay_trigger = prefs.android_overlay_trigger.normalized(); + persist_settings(&*coord, prefs.clone())?; + #[cfg(target_os = "android")] + coord.apply_android_overlay_settings_change(&previous, &prefs); + let _ = app.emit("prefs:changed", &prefs); + let _ = app.emit_to("main", "prefs:changed", &prefs); + Ok(()) +} + // ─────────────────────────── release channel (Beta opt-in) ─────────────────────────── // // 渠道偏好的写入路径跟 set_settings 复用 persist_settings:保持热键兜底归一化 @@ -341,6 +364,7 @@ fn extract_between(haystack: &str, open: &str, close: &str) -> Option { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +#[cfg(not(mobile))] pub struct AppUpdateMetadata { pub rid: tauri::ResourceId, pub current_version: String, @@ -357,6 +381,7 @@ pub struct AppUpdateMetadata { /// 不传则回落到 `prefs.update_channel`(后台 AutoUpdateGate 自动检查走这条)。 /// 返回 None = 当前是最新;Some(metadata) = 有新版可装。 #[tauri::command] +#[cfg(not(mobile))] pub async fn app_check_update_with_channel( coord: CoordinatorState<'_>, webview: tauri::Webview, @@ -401,6 +426,7 @@ pub async fn app_check_update_with_channel( /// 把 fetch_latest_beta_release 找到的最新 prerelease tag 拼成 -beta manifest URL 对。 /// 顺序:先镜像(fastgit.cc 代理 GitHub),后直连 —— 跟 tauri.conf 现有 Stable /// endpoints 一致,让国内访问优先打到 CDN。 +#[cfg(not(mobile))] async fn resolve_beta_manifest_endpoints() -> Result, String> { let Some(latest) = fetch_latest_beta_release().await? else { return Err("尚未发布过 Beta 版本".to_string()); diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 71bea4d3..4e69526d 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -35,6 +35,7 @@ use crate::coordinator_state::{ publish_abort_idle_after_restore, start_processing_if_listening, startup_race_status, BeginOutcome, SessionId, SessionPhase, SessionState, StartupRaceStatus, }; +use crate::correction::apply_correction_rules; use crate::hotkey::{HotkeyEvent, HotkeyMonitor}; use crate::insertion::TextInserter; use crate::persistence::{ @@ -61,52 +62,106 @@ use crate::windows_ime_ipc::ImeSubmitTarget; #[cfg(target_os = "windows")] use crate::windows_ime_session::{PreparedWindowsImeSession, WindowsImeSessionController}; -mod asr_setup; -mod capsule; mod dictation; -mod dictation_end; -mod dictation_session; -mod dictation_streaming; -mod dictation_voice_agent; -mod hotkey_supervisors; -mod ime_insertion; -mod llm_pipeline; mod qa; -mod qa_session; mod resources; -mod voice_agent_hotkeys; - -// glob 重导出:让 dictation.rs/qa.rs/resources.rs/impl 里所有 `super::裸名` -// 引用继续通过父模块解析(拆分前的 `use super::*` 契约)。 -pub(crate) use asr_setup::*; -pub(crate) use capsule::*; -pub(crate) use dictation_end::*; -pub(crate) use dictation_session::*; -pub(crate) use dictation_streaming::*; -pub(crate) use dictation_voice_agent::*; -pub(crate) use hotkey_supervisors::*; -pub(crate) use ime_insertion::*; -pub(crate) use llm_pipeline::*; -pub(crate) use qa_session::*; -pub(crate) use voice_agent_hotkeys::*; + +pub(super) fn qa_event_target() -> &'static str { + #[cfg(target_os = "android")] + { + "main" + } + #[cfg(not(target_os = "android"))] + { + "qa" + } +} #[cfg(test)] -use dictation_session::dictation_error_code; -use dictation::{handle_pressed_edge, handle_released_edge}; -use dictation_session::{begin_session, cancel_session, request_stop_during_starting}; +use dictation::dictation_error_code; +use dictation::{ + begin_session, cancel_session, end_session, handle_pressed_edge, handle_released_edge, + request_stop_during_starting, +}; #[cfg(any(debug_assertions, test))] use dictation::{handle_pressed, handle_released}; -use qa::{close_qa_panel, handle_qa_hotkey_pressed, QaPhase, QaSessionState}; +use qa::{ + close_qa_panel, handle_qa_hotkey_pressed, handle_qa_option_edge, open_qa_panel, QaPhase, + QaSessionState, +}; #[cfg(test)] use resources::discard_startup_resources_for_session; use resources::{ acquire_recording_mute, cancel_active_asr, release_recording_mute, selected_microphone_device_name, stop_microphone_preview_monitor, stop_qa_recorder, - SessionResource, SharedRecordingMuteState, + take_asr_for_session, take_recorder_for_session, SessionResource, SharedRecordingMuteState, }; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum CapsuleShowStrategy { + NoActivate, + FallbackShow, +} + +fn capsule_show_strategy_for_platform() -> CapsuleShowStrategy { + // ⚠️ 如果改下面的 cfg 列表,**必须**同步更新单元测试 + // `capsule_show_strategy_matches_platform_activation_contract` 的两组 cfg — + // 否则 Linux CI 直接红(PR #451 即是这种漏改)。 + #[cfg(any(target_os = "macos", target_os = "windows"))] + { + CapsuleShowStrategy::NoActivate + } + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + { + CapsuleShowStrategy::FallbackShow + } +} + +static CAPSULE_NO_ACTIVATE_FALLBACK_WARNED: AtomicBool = AtomicBool::new(false); +static CAPSULE_SUPPRESSED_BY_TOGGLE_LOGGED: AtomicBool = AtomicBool::new(false); +static CAPSULE_FIRST_SHOW_LOGGED: AtomicBool = AtomicBool::new(false); +// #470 诊断 v2:capsule webview 句柄取不到时的一次性门,区分「窗口压根没创建」(A0)。 +static CAPSULE_WINDOW_MISSING_LOGGED: AtomicBool = AtomicBool::new(false); + +/// 给 #470 诊断日志用的 capsule 状态短名。显式枚举每个变体到 &'static str, +/// 不走 `Debug` —— 哪天 CapsuleState 加了 `String` 字段,`:?` 会把 ASR / polish +/// 内容意外灌进日志(pr_agent 提的 forward-looking 隐患);这里只输出状态名。 +fn capsule_state_log_name(state: CapsuleState) -> &'static str { + match state { + CapsuleState::Idle => "idle", + CapsuleState::Recording => "recording", + CapsuleState::Transcribing => "transcribing", + CapsuleState::Polishing => "polishing", + CapsuleState::Done => "done", + CapsuleState::Cancelled => "cancelled", + CapsuleState::Error => "error", + } +} + +fn show_capsule_window_for_recording( + app: &AppHandle, + window: &tauri::WebviewWindow, +) { + let mut needs_fallback = true; + if capsule_show_strategy_for_platform() == CapsuleShowStrategy::NoActivate { + needs_fallback = !show_capsule_window_no_activate(app, window); + if needs_fallback && !CAPSULE_NO_ACTIVATE_FALLBACK_WARNED.swap(true, Ordering::SeqCst) { + // 产品取舍:no-activate 是 macOS/AeroSpace 的主路径;但如果 ns_window + // 暂不可用,仍优先保住录音反馈,不让用户以为听写没启动。fallback 可能 + // 重新触发 workspace 跳转,只在 no-activate 失败时作为降级路径。 + log::warn!("[capsule] no-activate show failed; falling back to window.show()"); + } + } + + if needs_fallback { + if let Err(e) = window.show() { + log::warn!("[capsule] show fallback failed: {e}"); + } + } +} + #[derive(Clone)] -pub(crate) enum ActiveAsr { +enum ActiveAsr { Volcengine(Arc), Whisper(Arc), Mimo(Arc), @@ -120,7 +175,6 @@ pub(crate) enum ActiveAsr { #[cfg(target_os = "macos")] Local(Arc), /// Apple Speech(SFSpeechRecognizer)系统本地 ASR;只在 macOS 可达。 - /// 无模型下载、无凭据,首次使用弹系统授权(issue #574)。 #[cfg(target_os = "macos")] AppleSpeech(Arc), } @@ -170,7 +224,7 @@ pub struct Coordinator { inner: Arc, } -pub(crate) struct Inner { +struct Inner { app: Mutex>, history: HistoryStore, prefs: PreferencesStore, @@ -252,28 +306,19 @@ pub(crate) struct Inner { /// supervisor 线程,但 integration test 和未来 RunEvent::Exit 钩子需要这条 /// 显式退出路径。审计 3.1.2。 shutdown: AtomicBool, - // ── 远程输入(局域网手机录音)───────────────────────────── - /// true = 当前 begin_session 应跳过本地 cpal,改用手机经 WS 推来的 PCM。 - /// 由 Coordinator::start_remote_dictation 在 begin_session 前置位。 - remote_source_active: AtomicBool, - /// 远程会话的音频入口:begin_session 把组装好的 AudioConsumer 存这里, - /// WS server 收到手机 PCM 时取出 consume_pcm_chunk。等价于本地 cpal 喂 recorder。 + #[cfg(not(mobile))] remote_audio_sink: Mutex>>, - /// 远程输入 HTTPS+WS 服务句柄。None = 未启动。 + #[cfg(not(mobile))] remote_server: Mutex>, - /// refresh_remote_server 的代数:每次调用自增,spawn 出的任务持自己的代数, - /// 持锁后发现已有更新代排队则直接让位(连点开关/连改端口只跑最后一轮)。 + #[cfg(not(mobile))] remote_refresh_gen: AtomicU64, - /// 串行化「停旧 → 启新」全流程的异步锁。无串行化时两轮 refresh 可交错: - /// 后到者 take 到 None 跳过关停、去 bind 旧服务尚未释放的端口 → 误报 port-in-use。 + #[cfg(not(mobile))] remote_refresh_lock: tokio::sync::Mutex<()>, - /// 当前远程输入配对码(6 位数字)。进程内有效,不持久化(每次启动可轮换)。 + #[cfg(not(mobile))] remote_pin: Mutex>, - /// PC 端当前界面语言(BCP-47,如 "zh-CN")。前端切换语言时经命令同步, - /// H5 录音页据此渲染对应语言。进程内镜像,不持久化(前端会在启动/切换时重新下发)。 + #[cfg(not(mobile))] remote_locale: Mutex, - /// 远程「仅回传」开关:true = 手机端关掉了「电脑落字」,本次远程听写不插入到电脑光标, - /// 只把最终文字回传给手机(见 dictation 落字处 + remote:result)。默认 false(照常落字)。 + #[cfg(not(mobile))] remote_no_insert: AtomicBool, /// Less Computer 连续对话:true=浮窗里已有进行中的会话,下一轮 `claude --continue` 续上下文; /// 关闭浮窗(dismiss)复位为 false,下次说话开新会话。 @@ -281,7 +326,7 @@ pub(crate) struct Inner { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ActionHotkeyKind { +enum ActionHotkeyKind { SwitchStyle, OpenApp, } @@ -350,13 +395,19 @@ impl Coordinator { qa_stream_cancelled: Arc::new(AtomicBool::new(false)), local_asr_cache: Arc::new(crate::asr::local::LocalAsrCache::new()), shutdown: AtomicBool::new(false), - remote_source_active: AtomicBool::new(false), + #[cfg(not(mobile))] remote_audio_sink: Mutex::new(None), + #[cfg(not(mobile))] remote_server: Mutex::new(None), + #[cfg(not(mobile))] remote_refresh_gen: AtomicU64::new(0), + #[cfg(not(mobile))] remote_refresh_lock: tokio::sync::Mutex::new(()), + #[cfg(not(mobile))] remote_pin: Mutex::new(None), + #[cfg(not(mobile))] remote_locale: Mutex::new(String::from("zh-CN")), + #[cfg(not(mobile))] remote_no_insert: AtomicBool::new(false), less_computer_conversation: AtomicBool::new(false), }), @@ -426,13 +477,19 @@ impl Coordinator { foundry_local_runtime, sherpa_onnx_runtime, shutdown: AtomicBool::new(false), - remote_source_active: AtomicBool::new(false), + #[cfg(not(mobile))] remote_audio_sink: Mutex::new(None), + #[cfg(not(mobile))] remote_server: Mutex::new(None), + #[cfg(not(mobile))] remote_refresh_gen: AtomicU64::new(0), + #[cfg(not(mobile))] remote_refresh_lock: tokio::sync::Mutex::new(()), + #[cfg(not(mobile))] remote_pin: Mutex::new(None), + #[cfg(not(mobile))] remote_locale: Mutex::new(String::from("zh-CN")), + #[cfg(not(mobile))] remote_no_insert: AtomicBool::new(false), less_computer_conversation: AtomicBool::new(false), }), @@ -493,6 +550,101 @@ impl Coordinator { *self.inner.app.lock() = Some(handle); } + pub fn android_insert_strategy(&self) -> crate::types::AndroidInsertStrategy { + self.inner.prefs.get().android_insert_strategy + } + + pub fn android_overlay_trigger(&self) -> crate::types::AndroidOverlayTrigger { + self.inner.prefs.get().android_overlay_trigger.normalized() + } + + pub fn apply_android_overlay_settings_change( + &self, + previous: &crate::types::UserPreferences, + next: &crate::types::UserPreferences, + ) { + #[cfg(target_os = "android")] + { + use crate::types::android_types::{ + classify_android_overlay_settings_change, AndroidOverlaySettingsAction, + }; + match classify_android_overlay_settings_change(previous, next) { + AndroidOverlaySettingsAction::None => {} + AndroidOverlaySettingsAction::RefreshLayout => { + self.refresh_android_overlay_layout(); + } + AndroidOverlaySettingsAction::Transition { from, to } => { + self.transition_android_overlay_trigger(from, to); + } + } + } + let _ = (previous, next); + } + + pub fn transition_android_overlay_trigger( + &self, + from: crate::types::AndroidOverlayTrigger, + to: crate::types::AndroidOverlayTrigger, + ) { + #[cfg(target_os = "android")] + { + use crate::types::AndroidOverlayTrigger; + fn overlay_trigger_log_name(trigger: AndroidOverlayTrigger) -> &'static str { + match trigger.normalized() { + AndroidOverlayTrigger::Background => "background", + AndroidOverlayTrigger::Keyboard => "keyboard", + AndroidOverlayTrigger::Always => "always", + } + } + if from == to { + return; + } + log::info!( + "[coord] overlay transition from={} to={}", + overlay_trigger_log_name(from), + overlay_trigger_log_name(to), + ); + match (from, to) { + ( + AndroidOverlayTrigger::Background | AndroidOverlayTrigger::Keyboard, + AndroidOverlayTrigger::Always, + ) => { + let _ = crate::android::replace_android_overlay(); + } + ( + AndroidOverlayTrigger::Always, + AndroidOverlayTrigger::Background | AndroidOverlayTrigger::Keyboard, + ) => { + let _ = crate::android::hide_android_overlay(); + } + _ => {} + } + } + let _ = (from, to); + } + + pub fn apply_android_overlay_on_startup(&self) { + #[cfg(target_os = "android")] + { + use crate::types::AndroidOverlayTrigger; + match self.android_overlay_trigger() { + AndroidOverlayTrigger::Always => { + let _ = crate::android::replace_android_overlay(); + } + AndroidOverlayTrigger::Background | AndroidOverlayTrigger::Keyboard => { + let _ = crate::android::hide_android_overlay(); + } + } + } + } + + pub fn refresh_android_overlay_layout(&self) { + #[cfg(target_os = "android")] + { + let _ = crate::android::refresh_android_overlay_layout(); + } + } + /// 让所有 hotkey supervisor loop(dictation / qa / combo / translation / /// switch_style / open_app)在下一轮 sleep / poll 后退出。生产场景下进程退出 /// 一并 reap 所有线程,但 integration test 和未来 RunEvent::Exit 钩子需要 @@ -864,88 +1016,13 @@ impl Coordinator { /// 内联审批卡的 Approve / Deny 回执:解析等待中的 token。 pub fn less_computer_approve(&self, token: &str, approved: bool) { - dictation_voice_agent::resolve_less_computer_approval(token, approved); + dictation::resolve_less_computer_approval(token, approved); } pub fn history(&self) -> &HistoryStore { &self.inner.history } - /// 用**当前配置的** ASR provider 对一段已归档的 16k/mono/16-bit PCM 重新转录 - /// (issue #613「重新转录」)。复用 `build_qa_asr_start`,对所有 provider 统一: - /// 流式 provider 先 open_session 再灌音并取 final,批处理 provider 直接灌音后 - /// transcribe。整段转写按 provider 设置超时,防止挂死。 - /// - /// 只做 ASR,不做润色/落字/写历史 —— 回写历史由 command 层完成,保持本方法纯粹。 - pub async fn retranscribe_pcm(&self, pcm: Vec) -> Result { - let inner = &self.inner; - let active_asr = CredentialsVault::get_active_asr(); - let start = build_qa_asr_start(inner, &active_asr).await?; - start.open_streaming_session().await?; - let consumer = start.recorder_consumer(); - consumer.consume_pcm_chunk(&pcm); - let audio_secs = (pcm::pcm_duration_ms(&pcm) as f64) / 1000.0; - let timeout = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); - let raw = match start.active_asr() { - ActiveAsr::Volcengine(asr) => { - asr.send_last_frame().await.map_err(|e| e.to_string())?; - tokio::time::timeout(timeout, asr.await_final_result()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())? - } - ActiveAsr::Bailian(asr) => { - asr.send_last_frame().await.map_err(|e| e.to_string())?; - tokio::time::timeout(timeout, asr.await_final_result()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())? - } - ActiveAsr::Whisper(w) => { - let timeout = cloud_whisper_transcribe_timeout(audio_secs); - tokio::time::timeout(timeout, w.transcribe()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())? - } - ActiveAsr::Mimo(m) => tokio::time::timeout(timeout, m.transcribe()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())?, - #[cfg(target_os = "windows")] - ActiveAsr::FoundryLocalWhisper(local) => local - .transcribe(asr_setup::foundry_audio_transcribe_timeout_duration()) - .await - .map_err(|e| e.to_string())?, - #[cfg(target_os = "windows")] - ActiveAsr::SherpaOnnxLocal(local) => local - .transcribe(asr_setup::sherpa_audio_transcribe_timeout_duration()) - .await - .map_err(|e| e.to_string())?, - #[cfg(target_os = "macos")] - ActiveAsr::Local(local) => { - let dur = asr_setup::local_qwen_transcribe_timeout( - (local.buffer_duration_ms() as f64) / 1000.0, - ); - inner.local_asr_cache.touch(); - let out = tokio::time::timeout(dur, local.transcribe()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())?; - asr_setup::schedule_local_asr_release(inner); - out - } - #[cfg(target_os = "macos")] - ActiveAsr::AppleSpeech(local) => { - let dur = asr_setup::local_qwen_transcribe_timeout(audio_secs); - tokio::time::timeout(dur, local.transcribe()) - .await - .map_err(|_| "重新转录超时".to_string())? - .map_err(|e| e.to_string())? - } - }; - Ok(raw.text) - } pub fn prefs(&self) -> &PreferencesStore { &self.inner.prefs } @@ -1067,6 +1144,15 @@ impl Coordinator { begin_session(&self.inner).await } + pub async fn start_dictation_with_translation(&self) -> Result<(), String> { + begin_session(&self.inner).await?; + self.inner + .translation_modifier_seen + .store(true, Ordering::SeqCst); + log::info!("[coord] android overlay translation dictation started"); + Ok(()) + } + pub async fn stop_dictation(&self) -> Result<(), String> { if self.inner.state.lock().phase == SessionPhase::Starting { request_stop_during_starting(&self.inner, "manual stop"); @@ -1075,47 +1161,34 @@ impl Coordinator { end_session(&self.inner).await } + pub async fn stop_dictation_with_translation(&self, translation: bool) -> Result<(), String> { + if translation { + mark_translation_modifier_seen(&self.inner); + } + self.stop_dictation().await + } + pub fn cancel_dictation(&self) { cancel_session(&self.inner); } - // ───────────────────────── 远程输入(局域网手机录音)───────────────────────── - // 把"远程输入"实现为一次普通听写会话,只是音频源换成手机经 WS 推来的 PCM: - // 完整复用 begin_session / end_session / cancel_session(一行不改)。本地与远程 - // 共用 inner.state,天然互斥。详见 dictation::start_recorder_for_starting 的远程分支。 - - /// 手机点"开始录音"。本地听写正在进行(phase != Idle)则拒绝并回 "busy"; - /// 否则置位 remote 标志后走 begin_session(内部跳过 cpal,把 consumer 存进 sink)。 - /// 设置远程「仅回传」开关(手机端「电脑落字」开关的反值)。true = 不落字、只回传。 + #[cfg(not(mobile))] pub fn set_remote_no_insert(&self, no_insert: bool) { self.inner .remote_no_insert .store(no_insert, Ordering::SeqCst); } + #[cfg(not(mobile))] pub async fn start_remote_dictation(&self) -> Result<(), String> { - // busy 判定与 remote_source_active 置位都在 begin_session_with_source 的 - // state 临界区内原子完成(与本地热键的 begin_session_state 同构)。之前是 - // 锁外预检查 + 锁外置位,竞态输家会把残留标志泄给抢先启动的本地会话。 - let r = begin_session_with_source(&self.inner, true).await; - if let Err(e) = &r { - // busy = 标志从未置位,不能清——清了会破坏正在进行的远程会话 - // (手机重复点「开始」就会走到这里)。置位之后的失败(ASR 凭据等)才回滚。 - if e != REMOTE_BUSY { - self.clear_remote_source(); - } - } - r + begin_session(&self.inner).await } - /// WS 每收到一帧二进制 PCM 调一次。仅 Starting/Listening 阶段转发给已组装的 - /// consumer(流式 ASR 的 DeferredAsrBridge 在 attach 前自缓冲,不丢早期音频)。 + #[cfg(not(mobile))] pub fn feed_remote_pcm(&self, pcm: &[u8]) { - { - let phase = self.inner.state.lock().phase; - if phase != SessionPhase::Listening && phase != SessionPhase::Starting { - return; - } + let phase = self.inner.state.lock().phase; + if phase != SessionPhase::Listening && phase != SessionPhase::Starting { + return; } let sink = self.inner.remote_audio_sink.lock().clone(); if let Some(consumer) = sink { @@ -1123,18 +1196,8 @@ impl Coordinator { } } - /// 手机点"停止"。Starting 阶段记 pending_stop(等启动完成自动收尾);否则走 - /// end_session(转写→润色→光标落字,与本地一致)。 - /// 远程标志的清理不在这里做:end_session 内的 RemoteFlagsJanitor 在会话真正 - /// 回到 Idle 时统一清。这里清会在 double-stop(第二次调用对 Processing 中的 - /// 在飞 end_session 早退后)把标志过早清掉——在飞调用读到 false 后, - /// 「仅回传」开关失效(文字落到 PC)且 remote:result 不再回传手机。 + #[cfg(not(mobile))] pub async fn stop_remote_dictation(&self) -> Result<(), String> { - // 守卫:当前会话不是远程发起的则忽略。否则手机的 stop 会终止 PC 用户 - // 正在进行的本地听写(stop/cancel 方向没有 busy 那样的天然互斥)。 - if !self.inner.remote_source_active.load(Ordering::SeqCst) { - return Ok(()); - } if self.inner.state.lock().phase == SessionPhase::Starting { request_stop_during_starting(&self.inner, "remote stop"); return Ok(()); @@ -1142,22 +1205,13 @@ impl Coordinator { end_session(&self.inner).await } - /// 手机断连 / 点取消:丢弃本次,不落字。 - /// 手机锁屏/切后台/Wi-Fi 抖动都会触发 WS 断连进而走到这里——守卫确保只 - /// 取消远程发起的会话,不误杀 PC 用户正在进行的本地听写。 + #[cfg(not(mobile))] pub fn cancel_remote_dictation(&self) { - if !self.inner.remote_source_active.load(Ordering::SeqCst) { - return; - } cancel_session(&self.inner); - self.clear_remote_source(); - } - - fn clear_remote_source(&self) { - clear_remote_source_flags(&self.inner); + *self.inner.remote_audio_sink.lock() = None; } - /// 当前远程输入运行态(供命令/前端查询)。 + #[cfg(not(mobile))] pub fn remote_input_status(&self) -> crate::remote_server::RemoteInputStatus { let prefs = self.inner.prefs.get(); let handle = self.inner.remote_server.lock(); @@ -1180,11 +1234,10 @@ impl Coordinator { } } - /// 重新生成 6 位配对码并重启服务。 + #[cfg(not(mobile))] pub fn regenerate_remote_pin(self: &Arc) -> String { let pin = crate::remote_server::generate_pin(); *self.inner.remote_pin.lock() = Some(pin.clone()); - // 写盘持久化,否则下次启动会读回旧的持久化码、把这次重置覆盖掉。 if let Some(app) = self.inner.app.lock().clone() { crate::remote_server::save_pin(&app, &pin); } @@ -1192,8 +1245,7 @@ impl Coordinator { pin } - /// 同步 PC 端界面语言(前端切换语言时调用)。H5 录音页据此选择显示语言。 - /// 仅接受受支持的白名单值,非法输入忽略(值会注入到 H5 的 lang,需防注入)。 + #[cfg(not(mobile))] pub fn set_remote_locale(&self, locale: String) { const SUPPORTED: [&str; 5] = ["zh-CN", "zh-TW", "en", "ja", "ko"]; if SUPPORTED.contains(&locale.as_str()) { @@ -1201,24 +1253,20 @@ impl Coordinator { } } - /// 当前 PC 端界面语言(供 H5 首页注入 lang)。 + #[cfg(not(mobile))] pub fn remote_locale(&self) -> String { self.inner.remote_locale.lock().clone() } - /// 按 prefs 启停 / 重启远程输入服务。在 setup 与 prefs 变更(端口/开关)时调用。 + #[cfg(not(mobile))] pub fn refresh_remote_server(self: &Arc) { let coord = Arc::clone(self); let gen = self.inner.remote_refresh_gen.fetch_add(1, Ordering::SeqCst) + 1; tauri::async_runtime::spawn(async move { - // 串行化整个「停旧 → 启新」:并发的两轮 refresh 交错时,后到者会 take 到 - // None 跳过关停、去 bind 旧服务还没释放的端口 → 误报 port-in-use。 let _serial = coord.inner.remote_refresh_lock.lock().await; - // 已有更新代排队(用户连点开关/连改端口):本代直接让位,只跑最后一轮。 if coord.inner.remote_refresh_gen.load(Ordering::SeqCst) != gen { return; } - // 先停旧(优雅关停) let old = coord.inner.remote_server.lock().take(); if let Some(handle) = old { handle.shutdown().await; @@ -1227,16 +1275,16 @@ impl Coordinator { let app = coord.inner.app.lock().clone(); if !prefs.remote_input_enabled { if let Some(app) = &app { - let _ = - app.emit("remote-input:running", serde_json::json!({"running": false})); + let _ = app.emit( + "remote-input:running", + serde_json::json!({"running": false}), + ); } return; } let Some(app) = app else { return; }; - // PIN:进程内 remote_pin 缺失时从磁盘读持久化的(没有才新生成并写盘)—— - // 否则每次重启配对码都变,用户得反复找新码(这正是"配对码错误"的根因)。 let pin = { let mut guard = coord.inner.remote_pin.lock(); if guard.is_none() { @@ -1244,7 +1292,6 @@ impl Coordinator { } guard.clone().unwrap_or_default() }; - log::info!("[remote-input] 当前配对码 = {pin}(在手机上输入这个)"); let port = prefs.remote_input_port; match crate::remote_server::start(crate::remote_server::RemoteServerConfig { port, @@ -1274,6 +1321,21 @@ impl Coordinator { }); } + pub fn switch_to_previous_style_pack(&self) { + switch_to_previous_style(&self.inner); + } + + pub async fn open_qa_from_overlay(&self) -> Result<(), String> { + log::info!("[coord] overlay QA open requested"); + open_qa_panel(&self.inner); + begin_qa_session(&self.inner).await + } + + pub async fn finalize_qa_from_overlay(&self) -> Result<(), String> { + log::info!("[coord] overlay QA finalize requested"); + finalize_dictation_as_qa_question(&self.inner).await + } + /// 返回当前听写阶段(read-only 快照),供 CLI 入口在 dispatch toggle 时决策。 /// 与原热键边沿走的 `handle_pressed` 分支完全相同的判定逻辑:Idle → start, /// Listening → stop。可用于桌面快捷键 → CLI 转发的备用触发路径。 @@ -1288,6 +1350,14 @@ impl Coordinator { handle_qa_hotkey_pressed(&self.inner).await; } + pub async fn qa_toggle_recording(&self) { + handle_qa_option_edge(&self.inner).await; + } + + pub async fn qa_submit_text(&self, text: String) -> Result<(), String> { + submit_qa_text_question(&self.inner, text).await + } + pub fn set_shortcut_recording_active(&self, active: bool) { self.inner .shortcut_recording_active @@ -1370,6 +1440,68 @@ impl Coordinator { .map_err(|e| e.to_string()) } + pub async fn retranscribe_pcm(&self, pcm: Vec) -> Result { + let inner = &self.inner; + let active_asr = CredentialsVault::get_active_asr(); + let start = build_qa_asr_start(inner, &active_asr).await?; + start.open_streaming_session().await?; + let consumer = start.recorder_consumer(); + consumer.consume_pcm_chunk(&pcm); + let timeout = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + let raw = match start.active_asr() { + ActiveAsr::Volcengine(asr) => { + asr.send_last_frame().await.map_err(|e| e.to_string())?; + tokio::time::timeout(timeout, asr.await_final_result()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())? + } + ActiveAsr::Bailian(asr) => { + asr.send_last_frame().await.map_err(|e| e.to_string())?; + tokio::time::timeout(timeout, asr.await_final_result()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())? + } + ActiveAsr::Whisper(w) => tokio::time::timeout(timeout, w.transcribe()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())?, + ActiveAsr::Mimo(m) => tokio::time::timeout(timeout, m.transcribe()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())?, + #[cfg(target_os = "windows")] + ActiveAsr::FoundryLocalWhisper(local) => local + .transcribe(foundry_audio_transcribe_timeout_duration()) + .await + .map_err(|e| e.to_string())?, + #[cfg(target_os = "windows")] + ActiveAsr::SherpaOnnxLocal(local) => local + .transcribe(sherpa_audio_transcribe_timeout_duration()) + .await + .map_err(|e| e.to_string())?, + #[cfg(target_os = "macos")] + ActiveAsr::Local(local) => { + let dur = + local_qwen_transcribe_timeout((local.buffer_duration_ms() as f64) / 1000.0); + inner.local_asr_cache.touch(); + let out = tokio::time::timeout(dur, local.transcribe()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())?; + schedule_local_asr_release(inner); + out + } + #[cfg(target_os = "macos")] + ActiveAsr::AppleSpeech(local) => tokio::time::timeout(timeout, local.transcribe()) + .await + .map_err(|_| "重新转录超时".to_string())? + .map_err(|e| e.to_string())?, + }; + Ok(raw.text) + } + pub fn preview_style_pack_runtime( &self, style_pack: &crate::types::StylePack, @@ -1420,56 +1552,5459 @@ impl Coordinator { } } -// ─────────────────────────── hotkey bridging ─────────────────────────── - -// ─────────────────────────── session lifecycle ─────────────────────────── - -// ─────────────────────────── helpers ─────────────────────────── - -#[cfg(any(debug_assertions, test))] -fn hotkey_injection_dry_run_enabled() -> bool { - std::env::var_os("OPENLESS_HOTKEY_INJECTION_DRY_RUN").is_some() +fn raw_style_pack_uses_llm(pack: &crate::types::StylePack) -> bool { + !(pack.kind == crate::types::StylePackKind::Builtin + && pack.id == crate::types::BUILTIN_STYLE_PACK_RAW_ID + && pack.prompt == crate::types::StyleSystemPrompts::default().raw) } -#[cfg(any(debug_assertions, test))] -fn debug_transcript_override_text() -> Option { - let path = std::env::var_os("OPENLESS_DEBUG_TRANSCRIPT_FILE")?; - let text = std::fs::read_to_string(path).ok()?; - let trimmed = text.trim().to_string(); - if trimmed.is_empty() { - None - } else { - Some(trimmed) - } +fn raw_mode_uses_llm(style_system_prompt: &str) -> bool { + style_system_prompt != crate::types::StyleSystemPrompts::default().raw } -#[cfg(test)] -mod tests; - -/// 检查 begin_session 的 await 间隙是否被 cancel_session 打断。 -/// 必须在持有 state lock 的瞬间读,结果一拿就过期,所以用 helper 名字提醒只在 -/// 「准备做下一步副作用前」用。 -fn startup_race_status_for_starting( - inner: &Arc, - captured_session_id: SessionId, -) -> StartupRaceStatus { - let state = inner.state.lock(); - startup_race_status(&state, captured_session_id) -} +// ─────────────────────────── hotkey bridging ─────────────────────────── -fn set_phase_idle_if_session_matches(inner: &Arc, session_id: SessionId) { - let mut state = inner.state.lock(); - if state.session_id == session_id { - state.phase = SessionPhase::Idle; - } -} +fn hotkey_supervisor_loop(inner: Arc) { + let mut attempts: u32 = 0; + let capability = HotkeyMonitor::capability(); + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + let prefs = inner.prefs.get(); -/// 清远程音频源标志(幂等)。必须在远程会话生命周期的**每个**终结点调用: -/// 残留的 `remote_source_active=true` 会让下一次本地听写误走远程分支 -/// (跳过 cpal、挂上 sink 等手机 PCM),本地录音从此失效。 -/// 终结点:stop/cancel_remote_dictation、start 失败回滚、cancel_session、 -/// pending_stop 的延迟 end_session(finish_starting_session)。 -pub(crate) fn clear_remote_source_flags(inner: &Inner) { - inner.remote_source_active.store(false, Ordering::SeqCst); - *inner.remote_audio_sink.lock() = None; + if inner.hotkey.lock().is_some() { + return; + } + // Linux: 启动前检查 fcitx5 插件是否可用 + #[cfg(target_os = "linux")] + if !crate::linux_fcitx::available() { + *inner.hotkey_status.lock() = HotkeyStatus { + adapter: capability.adapter, + state: HotkeyStatusState::Failed, + message: Some("fcitx5 插件不可用 — 请确保 fcitx5 已安装且在运行".into()), + last_error: Some(crate::types::HotkeyInstallError { + code: "fcitx5_unavailable".into(), + message: "fcitx5 插件 DBus 接口无响应".into(), + }), + }; + log::warn!("[hotkey-supervisor] fcitx5 plugin unavailable, retrying..."); + attempts += 1; + std::thread::sleep(std::time::Duration::from_secs(3)); + continue; + } + *inner.hotkey_status.lock() = HotkeyStatus { + adapter: capability.adapter, + state: HotkeyStatusState::Starting, + message: Some(format!("正在安装全局快捷键监听(第 {} 次)", attempts + 1)), + last_error: None, + }; + let trigger = crate::shortcut_binding::legacy_modifier_trigger(&prefs.dictation_hotkey) + .unwrap_or(crate::types::HotkeyTrigger::Custom); + let binding = crate::types::HotkeyBinding { + trigger, + mode: prefs.hotkey.mode, + keys: None, + }; + let (tx, rx) = mpsc::channel::(); + #[cfg(target_os = "linux")] + let (fcitx_tx, fcitx_binding) = (tx.clone(), binding.clone()); + match HotkeyMonitor::start(binding, tx) { + Ok(monitor) => { + let adapter = monitor.kind(); + *inner.hotkey.lock() = Some(monitor); + if let Some(monitor) = inner.hotkey.lock().as_ref() { + let (qa_trigger, translation_trigger) = modifier_shortcut_triggers(&inner); + monitor.update_modifier_shortcuts(qa_trigger, translation_trigger); + } + *inner.hotkey_status.lock() = HotkeyStatus { + adapter, + state: HotkeyStatusState::Installed, + message: Some(format!("{} 已安装", adapter.display_name())), + last_error: None, + }; + log::info!( + "[coord] hotkey listener installed (after {} attempt(s))", + attempts + 1 + ); + let inner_clone = Arc::clone(&inner); + std::thread::Builder::new() + .name("openless-hotkey-bridge".into()) + .spawn(move || hotkey_bridge_loop(inner_clone, rx)) + .ok(); + // Linux: 启动 fcitx5 插件信号监听作为热键源。 + #[cfg(target_os = "linux")] + { + let (qa_trigger, translation_trigger) = modifier_shortcut_triggers(&inner); + let custom_key = custom_dictation_key_string(&inner); + crate::linux_fcitx::start_dictation_signal_listener( + fcitx_tx, + fcitx_binding.clone(), + qa_trigger, + translation_trigger, + custom_key, + ); + if fcitx_binding.trigger == crate::types::HotkeyTrigger::Custom { + sync_custom_dictation_to_plugin(&inner); + } else { + crate::linux_fcitx::sync_binding_to_plugin(&fcitx_binding); + } + } + return; + } + Err(e) => { + attempts += 1; + let error_message = e.message.clone(); + *inner.hotkey_status.lock() = HotkeyStatus { + adapter: capability.adapter, + state: HotkeyStatusState::Failed, + message: Some(error_message.clone()), + last_error: Some(e), + }; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] hotkey listener attempt #{attempts} failed: {}; retrying in 3s", + error_message + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + } + } + } +} + +// ─────────────────────────── QA hotkey supervisor ─────────────────────────── + +fn qa_hotkey_supervisor_loop(inner: Arc) { + let mut attempts: u32 = 0; + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + // 用户已经把 QA 关掉就睡着等 prefs 改动;改动通过 update_qa_hotkey_binding 唤醒。 + let binding = match inner.prefs.get().qa_hotkey.clone() { + Some(b) => b, + None => { + inner.qa_hotkey.lock().take(); + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + }; + if crate::shortcut_binding::legacy_modifier_trigger(&binding).is_some() { + inner.qa_hotkey.lock().take(); + if let Some(monitor) = inner.hotkey.lock().as_ref() { + let (qa_trigger, translation_trigger) = modifier_shortcut_triggers(&inner); + monitor.update_modifier_shortcuts(qa_trigger, translation_trigger); + } + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + if inner.qa_hotkey.lock().is_some() { + // 已注册成功 → 不重复装;睡 5s 复查( binding 变化由 update 路径手动触发 )。 + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + // global-hotkey crate 在 macOS 走 Carbon RegisterEventHotKey,要求 manager + // 在主线程构造,否则 register() 看起来 Ok 但事件根本不会派发——这是 issue #118 + // PR #119 第一版漏掉的关键步骤,导致用户按了 hotkey 完全无反应。这里通过 + // run_on_main_thread 把 QaHotkeyMonitor::start 跳到主线程跑,结果再回 channel。 + let app = inner.app.lock().clone(); + let app = match app { + Some(a) => a, + None => { + // 启动期 AppHandle 还没 bind,再等。 + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + }; + + let (tx, rx) = mpsc::channel::(); + let (init_tx, init_rx) = mpsc::sync_channel::>(1); + let binding_for_main = binding.clone(); + let _ = app.run_on_main_thread(move || { + let result = QaHotkeyMonitor::start(binding_for_main, tx); + let _ = init_tx.send(result); + }); + + // run_on_main_thread 是 fire-and-forget;等主线程跑完结果回来。给 5s 上限避免 + // 主线程繁忙时 supervisor 永久阻塞。 + let init_result = match init_rx.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(r) => r, + Err(_) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] QA hotkey 第 {attempts} 次注册超时(主线程未回执);3s 后重试" + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + continue; + } + }; + + match init_result { + Ok(monitor) => { + *inner.qa_hotkey.lock() = Some(monitor); + log::info!( + "[coord] QA hotkey listener installed on main thread (after {} attempt(s))", + attempts + 1 + ); + let inner_clone = Arc::clone(&inner); + std::thread::Builder::new() + .name("openless-qa-hotkey-bridge".into()) + .spawn(move || qa_hotkey_bridge_loop(inner_clone, rx)) + .ok(); + attempts = 0; + } + Err(e) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!("[coord] QA hotkey 第 {attempts} 次注册失败: {e}; 3s 后重试"); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + } + } + } +} + +fn qa_hotkey_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + let inner_cloned = Arc::clone(&inner); + match evt { + QaHotkeyEvent::Pressed => { + async_runtime::spawn(async move { handle_qa_hotkey_pressed(&inner_cloned).await }); + } + } + } +} + +// ─────────────────────────── combo hotkey supervisor ─────────────────────────── + +// ─────────────────────── coding agent hotkey supervisor ─────────────────────── + +fn coding_agent_hotkey_supervisor_loop(inner: Arc) { + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + update_coding_agent_hotkey_binding_now(&inner); + std::thread::sleep(std::time::Duration::from_secs(5)); + } +} + +fn update_coding_agent_hotkey_binding_now(inner: &Arc) { + #[cfg(not(target_os = "macos"))] + { + // Less Computer is intentionally macOS-only for now; keep Windows/Linux hidden and inert. + take_coding_agent_hotkeys_on_main_thread(inner); + return; + } + + #[cfg(target_os = "macos")] + { + let prefs = inner.prefs.get(); + let Some(binding) = prefs.coding_agent_voice_hotkey.clone() else { + take_coding_agent_hotkeys_on_main_thread(inner); + log::info!("[less-computer] hotkey disabled"); + return; + }; + if !prefs.coding_agent_enabled || is_unconfigured_shortcut(&binding) { + take_coding_agent_hotkeys_on_main_thread(inner); + return; + } + + if let Some(modifier_binding) = less_computer_modifier_binding(&binding) { + take_coding_agent_combo_hotkey_on_main_thread(inner); + if let Some(monitor) = inner.coding_agent_modifier_hotkey.lock().as_ref() { + monitor.update_binding(modifier_binding); + return; + } + let (tx, rx) = mpsc::channel::(); + match HotkeyMonitor::start(modifier_binding, tx) { + Ok(monitor) => { + *inner.coding_agent_modifier_hotkey.lock() = Some(monitor); + log::info!( + "[less-computer] modifier hotkey installed ({})", + binding.display_label() + ); + let bridge_inner = Arc::clone(inner); + std::thread::Builder::new() + .name("openless-less-computer-modifier-bridge".into()) + .spawn(move || less_computer_modifier_bridge_loop(bridge_inner, rx)) + .ok(); + } + Err(e) => log::warn!("[less-computer] modifier hotkey install failed: {e}"), + } + return; + } + + inner.coding_agent_modifier_hotkey.lock().take(); + let app = match inner.app.lock().clone() { + Some(app) => app, + None => { + log::warn!("[less-computer] AppHandle 未 bind,跳过组合键注册"); + return; + } + }; + let inner_clone = Arc::clone(inner); + let binding_for_main = binding.clone(); + let _ = app.run_on_main_thread(move || { + if let Some(monitor) = inner_clone.coding_agent_combo_hotkey.lock().as_ref() { + if let Err(e) = monitor.update_binding(binding_for_main.clone()) { + log::warn!("[less-computer] combo hotkey update failed: {e}"); + } + return; + } + let (tx, rx) = mpsc::channel::(); + match ComboHotkeyMonitor::start(binding_for_main.clone(), tx) { + Ok(monitor) => { + *inner_clone.coding_agent_combo_hotkey.lock() = Some(monitor); + log::info!( + "[less-computer] combo hotkey installed ({})", + binding_for_main.display_label() + ); + let bridge_inner = Arc::clone(&inner_clone); + std::thread::Builder::new() + .name("openless-less-computer-combo-bridge".into()) + .spawn(move || less_computer_combo_bridge_loop(bridge_inner, rx)) + .ok(); + } + Err(e) => log::warn!("[less-computer] combo hotkey install failed: {e}"), + } + }); + } +} + +#[cfg(target_os = "macos")] +fn less_computer_modifier_binding( + binding: &crate::types::ShortcutBinding, +) -> Option { + let trigger = crate::shortcut_binding::legacy_modifier_trigger(binding)?; + Some(crate::types::HotkeyBinding { + trigger, + mode: crate::types::HotkeyMode::Hold, + keys: None, + }) +} + +fn less_computer_modifier_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + let inner_cloned = Arc::clone(&inner); + match evt { + HotkeyEvent::Pressed => { + async_runtime::block_on(async { + handle_less_computer_pressed(&inner_cloned).await + }); + } + HotkeyEvent::Released => { + async_runtime::block_on(async { + handle_less_computer_released(&inner_cloned).await + }); + } + HotkeyEvent::Cancelled => cancel_session(&inner_cloned), + HotkeyEvent::TranslationModifierPressed | HotkeyEvent::QaShortcutPressed => {} + } + } +} + +fn less_computer_combo_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + let inner_cloned = Arc::clone(&inner); + match evt { + ComboHotkeyEvent::Pressed => { + async_runtime::block_on(async { + handle_less_computer_pressed(&inner_cloned).await + }); + } + ComboHotkeyEvent::Released => { + async_runtime::block_on(async { + handle_less_computer_released(&inner_cloned).await + }); + } + } + } +} + +async fn handle_less_computer_pressed(inner: &Arc) { + let prefs = inner.prefs.get(); + if !prefs.coding_agent_enabled { + return; + } + if !matches!(inner.state.lock().phase, SessionPhase::Idle) { + log::info!("[less-computer] press ignored: dictation session already active"); + return; + } + if !matches!(inner.qa_state.lock().phase, QaPhase::Idle) { + log::info!("[less-computer] press ignored: QA session active"); + return; + } + + if begin_session(inner).await.is_err() { + return; + } + let started = { + let mut state = inner.state.lock(); + if matches!( + state.phase, + SessionPhase::Starting | SessionPhase::Listening + ) { + state.voice_agent = true; + log::info!( + "[less-computer] voice session started (session={:?})", + state.session_id + ); + true + } else { + false + } + }; + // 一按下键(开始录音)就点亮整屏彩虹描边,贯穿 录音 → 处理 → 出结果,完成/关闭才熄灭。 + if started { + if let Some(app) = inner.app.lock().clone() { + crate::show_less_computer_glow(&app); + } + } +} + +async fn handle_less_computer_released(inner: &Arc) { + let (phase, voice_agent) = { + let state = inner.state.lock(); + (state.phase, state.voice_agent) + }; + if !voice_agent { + return; + } + match phase { + SessionPhase::Listening => { + let _ = end_session(inner).await; + // 收尾后熄灭整屏描边。正常路径 run_voice_agent_transcript 已熄过、这里兜底; + // 空转写/出错路径不进 run_voice_agent_transcript,全靠这里熄,否则描边卡住不灭。 + if let Some(app) = inner.app.lock().clone() { + crate::hide_less_computer_glow(&app); + } + } + SessionPhase::Starting => { + // 握手中松手:排队;正常路径真正收尾在 begin 续流的 end_session → run_voice_agent_transcript 熄灭。 + request_stop_during_starting(inner, "less-computer release edge"); + // 但若初始化失败永远到不了 Listening(不会进 run_voice_agent_transcript), + // 描边会永久卡屏 → 这里兜底熄灭。Listening 分支已有熄灭逻辑,故只在 Starting 加。 + if let Some(app) = inner.app.lock().clone() { + crate::hide_less_computer_glow(&app); + } + } + _ => {} + } +} + +fn take_coding_agent_hotkeys_on_main_thread(inner: &Arc) { + inner.coding_agent_modifier_hotkey.lock().take(); + take_coding_agent_combo_hotkey_on_main_thread(inner); +} + +fn take_coding_agent_combo_hotkey_on_main_thread(inner: &Arc) { + let app = inner.app.lock().clone(); + if let Some(app) = app { + let inner = Arc::clone(inner); + let _ = app.run_on_main_thread(move || { + inner.coding_agent_combo_hotkey.lock().take(); + }); + } else { + inner.coding_agent_combo_hotkey.lock().take(); + } +} + +/// 快取用:抓当前选中文本 → Claude 润色 → 回插(替换选区)。全程胶囊反馈。 +async fn handle_coding_agent_quick(inner: &Arc) { + let prefs = inner.prefs.get(); + if !prefs.coding_agent_enabled { + return; + } + let selection = tauri::async_runtime::spawn_blocking(crate::selection::capture_selection) + .await + .ok() + .flatten(); + let source_text = match selection { + Some(ctx) => ctx.text, + None => { + log::info!("[coding-agent] 快取用:没有选中文本"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some("请先选中文本,再按快捷键".to_string()), + None, + ); + return; + } + }; + + log::info!( + "[coding-agent] 快取用:润色 {} 字", + source_text.chars().count() + ); + emit_capsule( + inner, + CapsuleState::Polishing, + 0.0, + 0, + Some("Claude 润色中…".to_string()), + None, + ); + + let prompt = format!( + "请润色下面这段文字,使其更通顺自然、表达更清晰,保持原意、语言和事实不变。\ + 直接输出润色后的文本,不要加任何解释、前缀或引号:\n\n{source_text}" + ); + + // 纯文本润色:不需要任何工具 → plan 只读、无 guard、便宜快、最可靠。 + let mut req = crate::coding_agent::CodingAgentRequest::new("quick-polish", prompt); + req.model = prefs + .coding_agent_model + .clone() + .filter(|m| !m.trim().is_empty()) + .or_else(|| Some("sonnet".to_string())); + req.permission_mode = crate::coding_agent::CodingAgentPermissionMode::Plan; + req.allowed_tools = Vec::new(); + req.max_budget_usd = Some(0.2); + req.timeout_secs = 60; + req.session_persistence = false; + + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + let cancel = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let run = async_runtime::spawn(async move { + crate::coding_agent::run_claude_agent("claude", req, tx, cancel).await + }); + + let mut final_text = String::new(); + let mut error_msg: Option = None; + while let Some(ev) = rx.recv().await { + match ev { + crate::coding_agent::CodingAgentEvent::Completed { text, .. } => final_text = text, + crate::coding_agent::CodingAgentEvent::Error { message, .. } => { + error_msg = Some(message) + } + _ => {} + } + } + let run_result = run.await; + + let final_text = final_text.trim().to_string(); + if final_text.is_empty() { + let msg = error_msg + .or_else(|| match run_result { + Ok(Err(e)) => Some(e.to_string()), + _ => None, + }) + .unwrap_or_else(|| "Claude 无结果(确认已登录 claude 且额度充足)".to_string()); + log::warn!("[coding-agent] 快取用失败: {msg}"); + emit_capsule(inner, CapsuleState::Error, 0.0, 0, Some(msg), None); + return; + } + + let inserted = final_text.chars().count() as u32; + let inner2 = Arc::clone(inner); + let restore = prefs.restore_clipboard_after_paste; + let paste_shortcut = prefs.paste_shortcut; + let _ = tauri::async_runtime::spawn_blocking(move || { + inner2.inserter.insert(&final_text, restore, paste_shortcut) + }) + .await; + log::info!("[coding-agent] 快取用:已回插 {inserted} 字"); + emit_capsule(inner, CapsuleState::Done, 0.0, 0, None, Some(inserted)); +} + +fn combo_hotkey_supervisor_loop(inner: Arc) { + let mut attempts: u32 = 0; + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + // 读当前 prefs + let prefs = inner.prefs.get(); + if crate::shortcut_binding::legacy_modifier_trigger(&prefs.dictation_hotkey).is_some() { + // 不是 Custom → 睡着等 prefs 改动 + take_combo_hotkey_on_main_thread(&inner); + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + let binding = prefs.dictation_hotkey.clone(); + if is_unconfigured_shortcut(&binding) { + take_combo_hotkey_on_main_thread(&inner); + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + if inner.combo_hotkey.lock().is_some() { + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + let app = inner.app.lock().clone(); + let app = match app { + Some(a) => a, + None => { + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + }; + + let (tx, rx) = mpsc::channel::(); + let (init_tx, init_rx) = + mpsc::sync_channel::>(1); + let binding_for_main = binding.clone(); + let _ = app.run_on_main_thread(move || { + let result = ComboHotkeyMonitor::start(binding_for_main, tx); + let _ = init_tx.send(result); + }); + + let init_result = match init_rx.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(r) => r, + Err(_) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] combo hotkey 第 {attempts} 次注册超时(主线程未回执);3s 后重试" + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + continue; + } + }; + + match init_result { + Ok(monitor) => { + *inner.combo_hotkey.lock() = Some(monitor); + log::info!( + "[coord] combo hotkey listener installed on main thread (after {} attempt(s))", + attempts + 1 + ); + let inner_clone = Arc::clone(&inner); + std::thread::Builder::new() + .name("openless-combo-hotkey-bridge".into()) + .spawn(move || combo_hotkey_bridge_loop(inner_clone, rx)) + .ok(); + attempts = 0; + } + Err(e) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!("[coord] combo hotkey 第 {attempts} 次注册失败: {e}; 3s 后重试"); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + } + } + } +} + +fn combo_hotkey_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + let inner_cloned = Arc::clone(&inner); + match evt { + // P0 #468/#475: 同 hotkey_bridge_loop —— Pressed/Released 必须串行 await, + // 否则 latch 竞态导致 combo 快捷键二次按键失效。 + ComboHotkeyEvent::Pressed => { + async_runtime::block_on(async { + handle_pressed_edge(&inner_cloned).await; + }); + } + ComboHotkeyEvent::Released => { + async_runtime::block_on(async { + handle_released_edge(&inner_cloned).await; + }); + } + } + } +} + +fn translation_hotkey_supervisor_loop(inner: Arc) { + let mut attempts: u32 = 0; + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + let binding = inner.prefs.get().translation_hotkey; + if is_builtin_translation_shift(&binding) + || crate::shortcut_binding::legacy_modifier_trigger(&binding).is_some() + { + take_translation_hotkey_on_main_thread(&inner); + if let Some(monitor) = inner.hotkey.lock().as_ref() { + let (qa_trigger, translation_trigger) = modifier_shortcut_triggers(&inner); + monitor.update_modifier_shortcuts(qa_trigger, translation_trigger); + } + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + if inner.translation_hotkey.lock().is_some() { + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + let app = match inner.app.lock().clone() { + Some(a) => a, + None => { + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + }; + + let (tx, rx) = mpsc::channel::(); + let (init_tx, init_rx) = + mpsc::sync_channel::>(1); + let binding_for_main = binding.clone(); + let _ = app.run_on_main_thread(move || { + let result = ComboHotkeyMonitor::start(binding_for_main, tx); + let _ = init_tx.send(result); + }); + + let init_result = match init_rx.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(r) => r, + Err(_) => { + attempts += 1; + std::thread::sleep(std::time::Duration::from_secs(3)); + continue; + } + }; + + match init_result { + Ok(monitor) => { + *inner.translation_hotkey.lock() = Some(monitor); + let inner_clone = Arc::clone(&inner); + std::thread::Builder::new() + .name("openless-translation-hotkey-bridge".into()) + .spawn(move || translation_hotkey_bridge_loop(inner_clone, rx)) + .ok(); + attempts = 0; + } + Err(e) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] translation hotkey 第 {attempts} 次注册失败: {e}; 3s 后重试" + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + } + } + } +} + +fn update_translation_hotkey_on_main_thread( + inner: Arc, + binding: crate::types::ShortcutBinding, +) -> Result<(), ComboHotkeyError> { + if let Some(monitor) = inner.translation_hotkey.lock().as_ref() { + return monitor.update_binding(binding); + } + let (tx, rx) = mpsc::channel::(); + let monitor = ComboHotkeyMonitor::start(binding, tx)?; + *inner.translation_hotkey.lock() = Some(monitor); + let bridge_inner = Arc::clone(&inner); + std::thread::Builder::new() + .name("openless-translation-hotkey-bridge".into()) + .spawn(move || translation_hotkey_bridge_loop(bridge_inner, rx)) + .map_err(|e| ComboHotkeyError::RegisterFailed(format!("spawn bridge thread: {e}")))?; + Ok(()) +} + +fn translation_hotkey_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + if matches!(evt, ComboHotkeyEvent::Pressed) { + mark_translation_modifier_seen(&inner); + } + } +} + +fn action_hotkey_supervisor_loop(inner: Arc, kind: ActionHotkeyKind) { + let mut attempts: u32 = 0; + loop { + if inner.shutdown.load(Ordering::SeqCst) { + return; + } + // None = 用户主动停用:反注册并睡着等 prefs 改动(由 update 路径唤醒)。 + let Some(binding) = action_hotkey_binding(&inner, kind) else { + take_action_hotkey_on_main_thread(&inner, kind); + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + }; + if is_modifier_only_shortcut(&binding) { + take_action_hotkey_on_main_thread(&inner, kind); + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + if action_hotkey_slot(&inner, kind).lock().is_some() { + std::thread::sleep(std::time::Duration::from_secs(5)); + continue; + } + + let app = match inner.app.lock().clone() { + Some(a) => a, + None => { + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + }; + + let (tx, rx) = mpsc::channel::(); + let (init_tx, init_rx) = + mpsc::sync_channel::>(1); + let binding_for_main = binding.clone(); + let _ = app.run_on_main_thread(move || { + let result = ComboHotkeyMonitor::start(binding_for_main, tx); + let _ = init_tx.send(result); + }); + + let init_result = match init_rx.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(r) => r, + Err(_) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] action hotkey {kind:?} 第 {attempts} 次注册超时;3s 后重试" + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + continue; + } + }; + + match init_result { + Ok(monitor) => { + *action_hotkey_slot(&inner, kind).lock() = Some(monitor); + log::info!( + "[coord] action hotkey {kind:?} listener installed after {} attempt(s)", + attempts + 1 + ); + let inner_clone = Arc::clone(&inner); + std::thread::Builder::new() + .name(action_hotkey_bridge_thread_name(kind).into()) + .spawn(move || action_hotkey_bridge_loop(inner_clone, rx, kind)) + .ok(); + attempts = 0; + } + Err(e) => { + attempts += 1; + if attempts <= 3 || attempts % 10 == 0 { + log::warn!( + "[coord] action hotkey {kind:?} 第 {attempts} 次注册失败: {e}; 3s 后重试" + ); + } + std::thread::sleep(std::time::Duration::from_secs(3)); + } + } + } +} + +fn action_hotkey_bridge_loop( + inner: Arc, + rx: mpsc::Receiver, + kind: ActionHotkeyKind, +) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + if matches!(evt, ComboHotkeyEvent::Pressed) { + handle_action_hotkey_pressed(&inner, kind); + } + } +} + +fn handle_action_hotkey_pressed(inner: &Arc, kind: ActionHotkeyKind) { + match kind { + ActionHotkeyKind::SwitchStyle => switch_to_previous_style(inner), + ActionHotkeyKind::OpenApp => { + if let Some(app) = inner.app.lock().clone() { + let app_for_main = app.clone(); + let _ = app.run_on_main_thread(move || { + crate::show_main_window(&app_for_main); + }); + } + } + } +} + +fn switch_to_previous_style(inner: &Arc) { + let mut prefs = inner.prefs.get(); + let packs = match inner.style_packs.list() { + Ok(packs) => packs, + Err(error) => { + log::warn!("[coord] switch style hotkey failed to load style packs: {error}"); + return; + } + }; + let enabled: Vec = + packs.into_iter().filter(|pack| pack.enabled).collect(); + if enabled.len() <= 1 { + log::info!("[coord] switch style hotkey ignored: enabled style count <= 1"); + return; + } + let current_index = enabled + .iter() + .position(|pack| pack.id == prefs.active_style_pack_id) + .unwrap_or(0); + let next_index = if current_index == 0 { + enabled.len() - 1 + } else { + current_index - 1 + }; + prefs.active_style_pack_id = enabled[next_index].id.clone(); + sync_style_pack_preferences(&mut prefs, &enabled); + if let Err(e) = inner.prefs.set(prefs.clone()) { + log::warn!("[coord] switch style hotkey 保存失败: {e}"); + } else { + log::info!( + "[coord] switch style hotkey changed active style pack to {}", + prefs.active_style_pack_id + ); + if let Some(app) = inner.app.lock().clone() { + let _ = app.emit("prefs:changed", &prefs); + let _ = app.emit_to("main", "prefs:changed", &prefs); + let app_for_main = app.clone(); + let _ = app.run_on_main_thread(move || { + if let Err(err) = crate::refresh_tray_microphone_menu(&app_for_main) { + log::warn!("[tray] refresh style menu after switch style hotkey failed: {err}"); + } + }); + } + } +} + +fn take_combo_hotkey_on_main_thread(inner: &Arc) { + let app = inner.app.lock().clone(); + if let Some(app) = app { + let inner = Arc::clone(inner); + let _ = app.run_on_main_thread(move || { + inner.combo_hotkey.lock().take(); + }); + } else { + inner.combo_hotkey.lock().take(); + } +} + +fn take_translation_hotkey_on_main_thread(inner: &Arc) { + let app = inner.app.lock().clone(); + if let Some(app) = app { + let inner = Arc::clone(inner); + let _ = app.run_on_main_thread(move || { + inner.translation_hotkey.lock().take(); + }); + } else { + inner.translation_hotkey.lock().take(); + } +} + +fn take_action_hotkey_on_main_thread(inner: &Arc, kind: ActionHotkeyKind) { + let app = inner.app.lock().clone(); + if let Some(app) = app { + let inner = Arc::clone(inner); + let _ = app.run_on_main_thread(move || { + action_hotkey_slot(&inner, kind).lock().take(); + }); + } else { + action_hotkey_slot(inner, kind).lock().take(); + } +} + +fn action_hotkey_slot( + inner: &Arc, + kind: ActionHotkeyKind, +) -> &Mutex> { + match kind { + ActionHotkeyKind::SwitchStyle => &inner.switch_style_hotkey, + ActionHotkeyKind::OpenApp => &inner.open_app_hotkey, + } +} + +fn action_hotkey_binding( + inner: &Arc, + kind: ActionHotkeyKind, +) -> Option { + let prefs = inner.prefs.get(); + match kind { + ActionHotkeyKind::SwitchStyle => prefs.switch_style_hotkey, + ActionHotkeyKind::OpenApp => prefs.open_app_hotkey, + } +} + +fn is_modifier_only_shortcut(binding: &crate::types::ShortcutBinding) -> bool { + binding.modifiers.is_empty() + && (binding.primary.eq_ignore_ascii_case("shift") + || crate::shortcut_binding::legacy_modifier_trigger(binding).is_some()) +} + +fn is_unconfigured_shortcut(binding: &crate::types::ShortcutBinding) -> bool { + binding.primary.trim().is_empty() +} + +fn action_hotkey_bridge_thread_name(kind: ActionHotkeyKind) -> &'static str { + match kind { + ActionHotkeyKind::SwitchStyle => "openless-switch-style-hotkey-bridge", + ActionHotkeyKind::OpenApp => "openless-open-app-hotkey-bridge", + } +} + +fn is_builtin_translation_shift(binding: &crate::types::ShortcutBinding) -> bool { + binding.modifiers.is_empty() && binding.primary.eq_ignore_ascii_case("shift") +} + +/// Linux: 从 prefs 读取自定义组合键,同步到 fcitx5 插件。 +#[cfg(target_os = "linux")] +fn custom_dictation_key_string(inner: &Arc) -> Option { + let prefs = inner.prefs.get(); + let key_string = crate::linux_fcitx::binding_to_fcitx_key_string(&prefs.dictation_hotkey); + if key_string.is_empty() { + None + } else { + Some(key_string) + } +} + +#[cfg(target_os = "linux")] +fn sync_custom_dictation_to_plugin(inner: &Arc) { + let prefs = inner.prefs.get(); + let dictation = &prefs.dictation_hotkey; + let key_string = crate::linux_fcitx::binding_to_fcitx_key_string(dictation); + if key_string.is_empty() { + return; + } + match crate::linux_fcitx::set_custom_dictation_trigger(&key_string) { + Ok(()) => log::info!( + "[fcitx] Synced custom dictation trigger '{}' to plugin", + key_string + ), + Err(e) => log::warn!("[fcitx] Failed to sync custom dictation trigger: {e}"), + } +} + +fn modifier_shortcut_triggers( + inner: &Arc, +) -> ( + Option, + Option, +) { + let prefs = inner.prefs.get(); + let qa_trigger = prefs + .qa_hotkey + .as_ref() + .and_then(crate::shortcut_binding::legacy_modifier_trigger); + let translation_trigger = if is_builtin_translation_shift(&prefs.translation_hotkey) { + None + } else { + crate::shortcut_binding::legacy_modifier_trigger(&prefs.translation_hotkey) + }; + (qa_trigger, translation_trigger) +} + +fn mark_translation_modifier_seen(inner: &Arc) { + let phase = inner.state.lock().phase; + if matches!(phase, SessionPhase::Starting | SessionPhase::Listening) { + inner + .translation_modifier_seen + .store(true, Ordering::SeqCst); + log::info!("[coord] translation modifier seen during {phase:?}"); + } +} + +fn hotkey_bridge_loop(inner: Arc, rx: mpsc::Receiver) { + while let Ok(evt) = rx.recv() { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + continue; + } + let inner_cloned = Arc::clone(&inner); + match evt { + // P0 #468/#475: Pressed/Released 必须串行处理,否则在 Windows 上 WH_KEYBOARD_LL + // 边沿间隔微秒级 → 两个独立 spawn 的 task 被 work-stealing 调度器并行执行 → + // `hotkey_trigger_held` latch 翻转顺序错乱 → 下次按键被静默吞掉 + // (UI 关不掉 / 录音停不下来)。改为 bridge 线程内 block_on 顺序 await, + // recv 的 FIFO 顺序就是 handler 执行顺序。 + // 注意:handle_pressed_edge / handle_released_edge 内部走 .await(含网络 + // 握手),会暂时阻塞本 bridge 线程;Hold 模式短按时 Released 会排队在 channel + // 里直到 begin_session 完成,但 SessionPhase::Starting 已经有 + // request_stop_during_starting 兜底,begin_session 完成进 Listening 后 + // bridge 立刻 recv Released → end_session,行为正确,仅有短暂 stop 延迟。 + HotkeyEvent::Pressed => { + async_runtime::block_on(async { + handle_pressed_edge(&inner_cloned).await; + }); + } + HotkeyEvent::Released => { + async_runtime::block_on(async { + handle_released_edge(&inner_cloned).await; + }); + } + HotkeyEvent::Cancelled => { + cancel_session(&inner_cloned); + } + HotkeyEvent::TranslationModifierPressed => { + let translation_hotkey = inner_cloned.prefs.get().translation_hotkey; + if is_builtin_translation_shift(&translation_hotkey) + || crate::shortcut_binding::legacy_modifier_trigger(&translation_hotkey) + .is_some() + { + mark_translation_modifier_seen(&inner_cloned); + } + } + HotkeyEvent::QaShortcutPressed => { + async_runtime::spawn(async move { handle_qa_hotkey_pressed(&inner_cloned).await }); + } + } + } +} + +fn reset_shortcut_held_state(inner: &Arc) { + inner.hotkey_trigger_held.store(false, Ordering::SeqCst); + if let Some(monitor) = inner.hotkey.lock().as_ref() { + monitor.reset_held_state(); + } + let prefs = inner.prefs.get(); + if let Some(binding) = prefs.qa_hotkey.as_ref() { + if crate::shortcut_binding::legacy_modifier_trigger(binding).is_none() { + if let Some(monitor) = inner.qa_hotkey.lock().as_ref() { + if let Err(e) = monitor.update_binding(binding.clone()) { + log::warn!("[coord] reset QA hotkey latch failed: {e}"); + } + } + } + } + if !is_builtin_translation_shift(&prefs.translation_hotkey) + && crate::shortcut_binding::legacy_modifier_trigger(&prefs.translation_hotkey).is_none() + { + if let Some(monitor) = inner.translation_hotkey.lock().as_ref() { + if let Err(e) = monitor.update_binding(prefs.translation_hotkey.clone()) { + log::warn!("[coord] reset translation hotkey latch failed: {e}"); + } + } + } + if let Some(switch_style) = prefs.switch_style_hotkey.as_ref() { + if !is_modifier_only_shortcut(switch_style) { + if let Some(monitor) = inner.switch_style_hotkey.lock().as_ref() { + if let Err(e) = monitor.update_binding(switch_style.clone()) { + log::warn!("[coord] reset switch-style hotkey latch failed: {e}"); + } + } + } + } + if let Some(open_app) = prefs.open_app_hotkey.as_ref() { + if !is_modifier_only_shortcut(open_app) { + if let Some(monitor) = inner.open_app_hotkey.lock().as_ref() { + if let Err(e) = monitor.update_binding(open_app.clone()) { + log::warn!("[coord] reset open-app hotkey latch failed: {e}"); + } + } + } + } +} + +async fn handle_window_hotkey_event( + inner: &Arc, + event_type: String, + key: String, + code: String, + repeat: bool, +) -> Result<(), String> { + if inner.shortcut_recording_active.load(Ordering::SeqCst) { + return Ok(()); + } + if event_type == "keydown" && key == "Escape" { + // Esc 路由(issue #161):QA 浮窗可见时优先取消 QA(不动 dictation); + // 否则走 dictation 取消通路。之前无条件 cancel_session 导致 QA 浮窗 + // 按 Esc 杀的是 dictation 而 QA 流还在烧 token。 + let qa_active = { + let st = inner.qa_state.lock(); + st.panel_visible || st.phase != QaPhase::Idle + }; + if qa_active { + close_qa_panel(inner); + } else { + cancel_session(inner); + } + return Ok(()); + } + + #[cfg(not(target_os = "windows"))] + { + let _ = (inner, event_type, key, code, repeat); + Ok(()) + } + + #[cfg(target_os = "windows")] + { + if !window_hotkey_fallback_enabled() { + if event_type == "keydown" && !repeat { + log::info!( + "[window-hotkey] ignored because Windows lifecycle owner is the low-level hook" + ); + } + return Ok(()); + } + + let Some(trigger) = + crate::shortcut_binding::legacy_modifier_trigger(&inner.prefs.get().dictation_hotkey) + else { + return Ok(()); + }; + if !window_key_matches_trigger(trigger, &key, &code) { + return Ok(()); + } + + match event_type.as_str() { + "keydown" => { + if repeat { + return Ok(()); + } + log::info!( + "[window-hotkey] pressed trigger={trigger:?} code={code} repeat={repeat}" + ); + handle_pressed_edge(inner).await; + } + "keyup" => { + log::info!("[window-hotkey] released trigger={trigger:?} code={code}"); + handle_released_edge(inner).await; + } + _ => {} + } + Ok(()) + } +} + +fn window_hotkey_fallback_enabled() -> bool { + crate::types::HotkeyCapability::current().explicit_fallback_available +} + +#[cfg(any(target_os = "windows", test))] +fn window_key_matches_trigger(trigger: crate::types::HotkeyTrigger, key: &str, code: &str) -> bool { + use crate::types::HotkeyTrigger; + + match trigger { + HotkeyTrigger::RightControl => key == "Control" && code == "ControlRight", + HotkeyTrigger::LeftControl => key == "Control" && code == "ControlLeft", + HotkeyTrigger::RightOption | HotkeyTrigger::RightAlt => { + (key == "Alt" || key == "AltGraph") && code == "AltRight" + } + HotkeyTrigger::LeftOption => (key == "Alt" || key == "AltGraph") && code == "AltLeft", + HotkeyTrigger::RightCommand => key == "Meta" && code == "MetaRight", + HotkeyTrigger::Fn => key == "Control" && code == "ControlRight", + // MediaPlayPause 走 WH_KEYBOARD_LL,不走 window hotkey fallback + HotkeyTrigger::MediaPlayPause => false, + // Custom 走 global-hotkey crate,不走 window hotkey fallback + HotkeyTrigger::Custom => false, + } +} + +// ─────────────────────────── session lifecycle ─────────────────────────── + +/// QA 录音 runtime error 监听器。镜像 `spawn_recorder_error_monitor` 的语义但走 QA +/// 收尾路径(`finish_qa_with_error` 替代 `abort_recording_with_error`)。 +/// 用 qa_state.session_id 守卫 stale 事件。详见 issue #168。 +fn spawn_qa_recorder_error_monitor(inner: &Arc, rx: mpsc::Receiver) { + let captured_session_id = inner.qa_state.lock().session_id; + let inner = Arc::clone(inner); + std::thread::Builder::new() + .name("openless-qa-recorder-error-monitor".into()) + .spawn(move || { + if let Ok(err) = rx.recv() { + let current_session_id = inner.qa_state.lock().session_id; + if captured_session_id != current_session_id { + log::warn!( + "[coord] QA recorder error from stale session {} dropped (current={}, err={})", + captured_session_id, + current_session_id, + err + ); + return; + } + log::error!("[coord] QA recorder runtime error: {err}"); + finish_qa_with_error(&inner, format!("录音设备异常: {err}")); + } + }) + .ok(); +} + +#[cfg(target_os = "windows")] +fn store_prepared_windows_ime_session( + slots: &mut Vec, + session_id: SessionId, + prepared: PreparedWindowsImeSession, +) { + slots.retain(|slot| slot.session_id != session_id); + slots.push(PreparedWindowsImeSessionSlot { + session_id, + prepared, + }); +} + +#[cfg(target_os = "windows")] +fn take_matching_prepared_windows_ime_session( + slots: &mut Vec, + session_id: SessionId, +) -> Option { + let index = slots + .iter() + .position(|slot| slot.session_id == session_id)?; + Some(slots.remove(index).prepared) +} + +#[cfg(target_os = "windows")] +fn take_current_prepared_windows_ime_session_for_restore( + slots: &mut Vec, + session_id: SessionId, + current_session_id: SessionId, +) -> Option { + let prepared = take_matching_prepared_windows_ime_session(slots, session_id)?; + if current_session_id == session_id { + Some(prepared) + } else { + None + } +} + +#[cfg(target_os = "windows")] +fn restore_prepared_windows_ime_session(inner: &Arc, session_id: SessionId) { + let state = inner.state.lock(); + let prepared = { + let mut slot = inner.prepared_windows_ime_session.lock(); + take_current_prepared_windows_ime_session_for_restore( + &mut slot, + session_id, + state.session_id, + ) + }; + if let Some(prepared) = prepared { + inner.windows_ime.restore_session(prepared); + } +} + +#[cfg(not(target_os = "windows"))] +fn restore_prepared_windows_ime_session(_inner: &Arc, _session_id: SessionId) {} + +#[cfg(target_os = "windows")] +async fn insert_with_windows_ime_first( + inner: &Arc, + session_id: SessionId, + polished: &str, + restore_clipboard: bool, + allow_non_tsf_insertion_fallback: bool, + paste_shortcut: PasteShortcut, + ime_target: Option, +) -> InsertStatus { + let prepared = { + let mut slot = inner.prepared_windows_ime_session.lock(); + take_matching_prepared_windows_ime_session(&mut slot, session_id) + }; + let Some(prepared) = prepared else { + log::warn!("[windows-ime] no prepared TSF session for this dictation"); + if should_try_non_tsf_insertion_fallback( + allow_non_tsf_insertion_fallback, + InsertStatus::Failed, + ) { + return insert_via_non_tsf_fallback(inner, polished, restore_clipboard, paste_shortcut); + } + log::warn!("[windows-ime] non-TSF insertion fallback is disabled; failing insert"); + return InsertStatus::Failed; + }; + + let request = crate::windows_ime_ipc::ImeSubmitRequest { + session_id: Uuid::new_v4().to_string(), + text: polished.to_string(), + created_at: Utc::now().to_rfc3339(), + target: ime_target, + }; + + let ime_status = match inner.windows_ime.submit_prepared(&prepared, request).await { + Ok(status) => status, + Err(error) => { + log::warn!("[windows-ime] TSF submit failed: {error}"); + InsertStatus::Failed + } + }; + inner.windows_ime.restore_session(prepared); + + if ime_status == InsertStatus::Inserted { + ime_status + } else if should_try_non_tsf_insertion_fallback(allow_non_tsf_insertion_fallback, ime_status) { + insert_via_non_tsf_fallback(inner, polished, restore_clipboard, paste_shortcut) + } else { + log::warn!("[windows-ime] TSF did not insert; non-TSF insertion fallback is disabled"); + InsertStatus::Failed + } +} + +#[cfg(target_os = "windows")] +fn should_try_non_tsf_insertion_fallback( + allow_non_tsf_insertion_fallback: bool, + ime_status: InsertStatus, +) -> bool { + allow_non_tsf_insertion_fallback && ime_status != InsertStatus::Inserted +} + +#[cfg(target_os = "windows")] +fn insert_via_non_tsf_fallback( + inner: &Arc, + polished: &str, + _restore_clipboard: bool, + _paste_shortcut: PasteShortcut, +) -> InsertStatus { + let status = finish_non_tsf_insertion_fallback( + || inner.inserter.insert_via_unicode_keystrokes(polished), + || inner.inserter.copy_fallback(polished), + ); + + match status { + InsertStatus::Inserted => { + log::warn!( + "[windows-ime] TSF unavailable; inserted via paced Unicode SendInput fallback" + ); + } + InsertStatus::CopiedFallback => { + log::warn!( + "[windows-ime] TSF unavailable; Unicode SendInput failed, left text on clipboard" + ); + } + InsertStatus::PasteSent | InsertStatus::Failed => { + log::warn!( + "[windows-ime] TSF unavailable; Unicode SendInput fallback failed and copy fallback failed" + ); + } + } + + status +} + +#[cfg(any(target_os = "windows", test))] +fn finish_non_tsf_insertion_fallback( + mut unicode_fallback: U, + mut copy_only_fallback: C, +) -> InsertStatus +where + U: FnMut() -> InsertStatus, + C: FnMut() -> InsertStatus, +{ + match unicode_fallback() { + InsertStatus::Inserted => InsertStatus::Inserted, + InsertStatus::PasteSent | InsertStatus::CopiedFallback | InsertStatus::Failed => { + match copy_only_fallback() { + InsertStatus::CopiedFallback => InsertStatus::CopiedFallback, + // TextInserter::copy_fallback is copy-only: success is CopiedFallback. + // Treat any other status as failure so this helper never invents an insert. + InsertStatus::Inserted | InsertStatus::PasteSent | InsertStatus::Failed => { + InsertStatus::Failed + } + } + } + } +} + +#[cfg(test)] +mod non_tsf_fallback_tests { + use super::finish_non_tsf_insertion_fallback; + use crate::types::InsertStatus; + + #[test] + fn unicode_fallback_runs_before_copy_fallback() { + let mut copy_called = false; + let status = finish_non_tsf_insertion_fallback( + || InsertStatus::Inserted, + || { + copy_called = true; + InsertStatus::CopiedFallback + }, + ); + + assert_eq!(status, InsertStatus::Inserted); + assert!(!copy_called); + } + + #[test] + fn copy_fallback_runs_after_unicode_failure() { + let mut copy_called = false; + let status = finish_non_tsf_insertion_fallback( + || InsertStatus::Failed, + || { + copy_called = true; + InsertStatus::CopiedFallback + }, + ); + + assert_eq!(status, InsertStatus::CopiedFallback); + assert!(copy_called); + } + + #[test] + fn double_failure_does_not_pretend_text_was_copied() { + let mut copy_called = false; + let status = finish_non_tsf_insertion_fallback( + || InsertStatus::Failed, + || { + copy_called = true; + InsertStatus::Failed + }, + ); + + assert_eq!(status, InsertStatus::Failed); + assert!(copy_called); + } +} + +// ─────────────────────────── helpers ─────────────────────────── + +#[cfg(any(debug_assertions, test))] +fn hotkey_injection_dry_run_enabled() -> bool { + std::env::var_os("OPENLESS_HOTKEY_INJECTION_DRY_RUN").is_some() +} + +#[cfg(any(debug_assertions, test))] +fn debug_transcript_override_text() -> Option { + let path = std::env::var_os("OPENLESS_DEBUG_TRANSCRIPT_FILE")?; + let text = std::fs::read_to_string(path).ok()?; + let trimmed = text.trim().to_string(); + if trimmed.is_empty() { + None + } else { + Some(trimmed) + } +} + +fn ensure_microphone_permission(_inner: &Arc) -> Result<(), String> { + use crate::permissions::{self, PermissionStatus}; + + #[cfg(target_os = "windows")] + { + if permissions::windows_microphone_access_explicitly_denied() { + return Err("需要麦克风权限,当前状态: Denied".to_string()); + } + return Ok(()); + } + + let status = permissions::check_microphone(); + if matches!( + status, + PermissionStatus::Granted | PermissionStatus::NotApplicable + ) { + return Ok(()); + } + + // 听写路径不抢前台焦点:缺 mic 权限时直接请求系统授权,不再先 show_main_window。 + // 用户在设置页手动点“请求权限”仍走 request_microphone_from_foreground,那是显式操作。 + // 这里若系统不弹框,后续会通过 capsule error 引导用户主动去权限页处理。详见 #166。 + let requested = permissions::request_microphone(); + if matches!( + requested, + PermissionStatus::Granted | PermissionStatus::NotApplicable + ) { + Ok(()) + } else { + Err(format!("需要麦克风权限,当前状态: {requested:?}")) + } +} + +fn ensure_asr_credentials() -> Result<(), String> { + let active_asr = CredentialsVault::get_active_asr(); + + // 本地 Qwen3-ASR 没有"凭据"概念,但需要:(a) macOS 平台 (b) 模型已下载。 + if crate::asr::local::is_local_qwen3(&active_asr) { + #[cfg(not(target_os = "macos"))] + { + return Err("本地 ASR 当前仅支持 macOS(Windows 见 issue #256)".to_string()); + } + #[cfg(target_os = "macos")] + { + return ensure_local_qwen3_model_ready(); + } + } + + if crate::asr::local::foundry::is_foundry_local_whisper(&active_asr) { + #[cfg(not(target_os = "windows"))] + { + return Err("Foundry Local Whisper 当前仅支持 Windows".to_string()); + } + #[cfg(target_os = "windows")] + { + return Ok(()); + } + } + + if crate::asr::local::sherpa::is_sherpa_onnx_local(&active_asr) { + #[cfg(not(target_os = "windows"))] + { + return Err("sherpa-onnx local ASR 当前仅支持 Windows".to_string()); + } + #[cfg(target_os = "windows")] + { + return Ok(()); + } + } + + if is_whisper_compatible_provider(&active_asr) || is_bailian_provider(&active_asr) { + let api_key = CredentialsVault::get(CredentialAccount::AsrApiKey) + .ok() + .flatten() + .unwrap_or_default(); + if api_key.trim().is_empty() { + return Err("请先在设置中填写 ASR 服务商 API Key".to_string()); + } + return Ok(()); + } + + let creds = read_volc_credentials(); + if creds.app_id.trim().is_empty() || creds.access_token.trim().is_empty() { + Err("请先在设置中填写火山引擎 ASR App Key 和 Access Key".to_string()) + } else { + Ok(()) + } +} + +#[cfg(test)] +fn is_keyless_local_asr_provider(id: &str) -> bool { + if crate::asr::local::is_local_qwen3(id) { + return true; + } + #[cfg(target_os = "macos")] + if crate::asr::local::is_apple_speech(id) { + return true; + } + #[cfg(target_os = "windows")] + { + crate::asr::local::foundry::is_foundry_local_whisper(id) + || crate::asr::local::sherpa::is_sherpa_onnx_local(id) + } + #[cfg(not(target_os = "windows"))] + { + let _ = id; + false + } +} + +#[cfg(target_os = "macos")] +fn ensure_local_qwen3_model_ready() -> Result<(), String> { + let prefs = || -> Result { + // 这里没法拿到 inner,直接读 preferences.json 即可(Coordinator 写盘后总是同步的)。 + crate::persistence::PreferencesStore::new() + .map_err(|e| e.to_string()) + .map(|s| s.get()) + }()?; + let model_id = crate::asr::local::ModelId::from_str(&prefs.local_asr_active_model) + .ok_or_else(|| format!("未知的本地模型 id: {}", prefs.local_asr_active_model))?; + if !crate::asr::local::models::is_downloaded(model_id) { + return Err(format!( + "本地模型 {} 未下载完整,请到 设置 → 模型设置 中下载", + model_id.as_str() + )); + } + Ok(()) +} + +/// 一次 dictation 结束后,按 prefs.local_asr_keep_loaded_secs 决定何时释放 +/// 内存里的 Qwen3-ASR 引擎。0 = 立即释放;其它值 = sleep N 秒后看 last_used。 +/// 多次会话叠加多个 sleep 任务,每个独立 check:只要中间又被使用过就跳过释放。 +fn schedule_local_asr_release(inner: &Arc) { + let keep_secs = inner.prefs.get().local_asr_keep_loaded_secs; + let cache = Arc::clone(&inner.local_asr_cache); + if keep_secs == 0 { + cache.release_now(); + return; + } + let dur = std::time::Duration::from_secs(keep_secs as u64); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(dur).await; + cache.release_if_idle(dur); + }); +} + +#[cfg(target_os = "windows")] +fn foundry_local_asr_release_keep_secs(inner: &Arc) -> u32 { + inner.prefs.get().foundry_local_asr_keep_loaded_secs +} + +#[cfg(target_os = "windows")] +#[derive(Clone, Copy)] +enum AsrReleaseSession { + Dictation(SessionId), + Qa(SessionId), +} + +#[cfg(target_os = "windows")] +fn asr_release_session_is_current(inner: &Arc, session: AsrReleaseSession) -> bool { + match session { + AsrReleaseSession::Dictation(session_id) => inner.state.lock().session_id == session_id, + AsrReleaseSession::Qa(session_id) => inner.qa_state.lock().session_id == session_id, + } +} + +#[cfg(target_os = "windows")] +fn schedule_foundry_local_asr_release(inner: &Arc, session: AsrReleaseSession) { + let keep_secs = foundry_local_asr_release_keep_secs(inner); + let runtime = Arc::clone(&inner.foundry_local_runtime); + let inner = Arc::clone(inner); + tauri::async_runtime::spawn(async move { + if keep_secs > 0 { + tokio::time::sleep(std::time::Duration::from_secs(keep_secs as u64)).await; + } + if !asr_release_session_is_current(&inner, session) { + return; + } + if let Err(error) = runtime.release_now().await { + log::warn!("[foundry-asr] scheduled release failed: {error:#}"); + } + }); +} + +#[cfg(target_os = "windows")] +fn sherpa_onnx_release_keep_secs(inner: &Arc) -> u32 { + inner.prefs.get().sherpa_onnx_keep_loaded_secs +} + +/// 与 `schedule_foundry_local_asr_release` 同形:session_id 老旧则不释放, +/// 避免下一轮 session 立即重加载同一个 offline batch 模型。 +#[cfg(target_os = "windows")] +fn schedule_sherpa_onnx_release(inner: &Arc, session: AsrReleaseSession) { + let keep_secs = sherpa_onnx_release_keep_secs(inner); + let runtime = Arc::clone(&inner.sherpa_onnx_runtime); + let inner = Arc::clone(inner); + tauri::async_runtime::spawn(async move { + if keep_secs > 0 { + tokio::time::sleep(std::time::Duration::from_secs(keep_secs as u64)).await; + } + if !asr_release_session_is_current(&inner, session) { + return; + } + if let Err(error) = runtime.release_now().await { + log::warn!("[sherpa-asr] scheduled release failed: {error:#}"); + } + }); +} + +#[cfg(target_os = "macos")] +async fn build_local_qwen3( + inner: &Arc, +) -> anyhow::Result> { + let prefs = inner.prefs.get(); + let model_id = crate::asr::local::ModelId::from_str(&prefs.local_asr_active_model) + .ok_or_else(|| anyhow::anyhow!("未知本地模型 id: {}", prefs.local_asr_active_model))?; + let dir = crate::asr::local::models::model_dir(model_id)?; + let app = inner + .app + .lock() + .clone() + .ok_or_else(|| anyhow::anyhow!("AppHandle 未绑定"))?; + // 走缓存:如果已有同 id 的引擎在内存里就直接复用,避免每次会话都重加载 + // 1.2GB+ 模型。第一次加载阻塞数秒,spawn_blocking 不卡 tokio runtime。 + let cache = Arc::clone(&inner.local_asr_cache); + let mid = model_id.as_str().to_string(); + let engine = tauri::async_runtime::spawn_blocking(move || cache.get_or_load(&mid, &dir)) + .await + .map_err(|e| anyhow::anyhow!("spawn_blocking join failed: {e:#}"))??; + Ok(Arc::new(crate::asr::local::LocalQwenAsr::new(app, engine))) +} + +#[cfg(target_os = "macos")] +fn build_apple_speech() -> Arc { + Arc::new(crate::asr::local::AppleSpeechAsr::new()) +} + +/// `whisper` 是 OpenAI 原生;`siliconflow` / `zhipu` / `groq` 都暴露 +/// OpenAI 兼容的 `/audio/transcriptions`,统一走 `WhisperBatchASR`。 +/// 新增 OpenAI 兼容 ASR 时只需在这里加一项。 +/// +/// 注:DashScope 的 Qwen3-ASR-Flash 不在此列——它用 MultiModalConversation +/// (messages=[{content:[{audio:...}]}]) 协议,不是 Whisper multipart,需要 +/// 单独 ASR 客户端,留给 V2。 +fn is_whisper_compatible_provider(id: &str) -> bool { + matches!( + id, + "whisper" | "siliconflow" | "zhipu" | "groq" | "openrouter" + ) +} + +/// 该 provider 的请求体编码方式。OpenRouter 的 `/audio/transcriptions` 是 +/// `application/json` + base64 音频(issue #582),其余兼容厂商沿用 multipart。 +fn whisper_request_format(provider_id: &str) -> crate::asr::whisper::AsrRequestFormat { + match provider_id { + "openrouter" => crate::asr::whisper::AsrRequestFormat::OpenRouterJson, + _ => crate::asr::whisper::AsrRequestFormat::Multipart, + } +} + +/// 该 provider 的 `/audio/transcriptions` 是否支持 `response_format=verbose_json` +/// 并返回带 `no_speech_prob` / `avg_logprob` / `compression_ratio` 的 segments, +/// 用于幻听过滤。 +/// +/// - `whisper`(OpenAI)/ `groq`:原生 Whisper,完整支持,过滤有效。 +/// - `siliconflow`:模型是 SenseVoice / TeleSpeech,文档无 `response_format`, +/// 发送 verbose_json 可能被拒,**保持关闭**走旧的 `json`。 +/// - `zhipu`(GLM-ASR):虽接受 verbose_json,但不产出上述指标,过滤是空转; +/// 为最小化行为变更,这里也**保持关闭**,仅对确证有收益的 whisper/groq 开启。 +fn whisper_supports_verbose_json(provider_id: &str) -> bool { + matches!(provider_id, "whisper" | "groq") +} + +fn is_bailian_provider(id: &str) -> bool { + id == crate::asr::bailian::PROVIDER_ID +} + +fn is_mimo_provider(id: &str) -> bool { + id == crate::asr::mimo::PROVIDER_ID +} + +fn apply_chinese_script_preference(text: &str, pref: ChineseScriptPreference) -> String { + if text.is_empty() { + return String::new(); + } + let config = match pref { + ChineseScriptPreference::Simplified => Some(BuiltinConfig::T2s), + ChineseScriptPreference::Traditional => Some(BuiltinConfig::S2t), + ChineseScriptPreference::Auto => None, + }; + let Some(config) = config else { + return text.to_string(); + }; + match OpenCC::from_config(config) { + Ok(converter) => converter.convert(text), + Err(err) => { + log::warn!("[coord] OpenCC init failed, skip script conversion: {err}"); + text.to_string() + } + } +} + +enum QaAsrStart { + Volcengine { + asr: Arc, + bridge: Arc, + }, + Bailian { + asr: Arc, + bridge: Arc, + }, + Ready { + active: ActiveAsr, + consumer: Arc, + }, +} + +impl QaAsrStart { + fn active_asr(&self) -> ActiveAsr { + match self { + QaAsrStart::Volcengine { asr, .. } => ActiveAsr::Volcengine(Arc::clone(asr)), + QaAsrStart::Bailian { asr, .. } => ActiveAsr::Bailian(Arc::clone(asr)), + QaAsrStart::Ready { active, .. } => active.clone(), + } + } + + fn recorder_consumer(&self) -> Arc { + match self { + QaAsrStart::Volcengine { bridge, .. } => Arc::clone(bridge) as _, + QaAsrStart::Bailian { bridge, .. } => Arc::clone(bridge) as _, + QaAsrStart::Ready { consumer, .. } => Arc::clone(consumer), + } + } + + async fn open_streaming_session(&self) -> Result<(), String> { + match self { + QaAsrStart::Volcengine { asr, bridge } => { + asr.open_session().await.map_err(|e| e.to_string())?; + let target: Arc = Arc::clone(asr) as _; + let flushed = bridge.attach(target); + log::info!("[coord] QA ASR connected; flushed {flushed} deferred audio bytes"); + Ok(()) + } + QaAsrStart::Bailian { asr, bridge } => { + asr.open_session().await.map_err(|e| e.to_string())?; + let target: Arc = Arc::clone(asr) as _; + let flushed = bridge.attach(target); + log::info!( + "[coord] QA Bailian ASR connected; flushed {flushed} deferred audio bytes" + ); + Ok(()) + } + QaAsrStart::Ready { .. } => Ok(()), + } + } +} + +async fn build_qa_asr_start(inner: &Arc, active_asr: &str) -> Result { + #[cfg(target_os = "windows")] + if foundry::is_foundry_local_whisper(active_asr) { + let prefs = inner.prefs.get(); + let model_alias = if foundry::model_alias_is_known(&prefs.foundry_local_asr_model) { + prefs.foundry_local_asr_model.clone() + } else { + foundry::DEFAULT_MODEL_ALIAS.to_string() + }; + let language_hint = prefs.foundry_local_asr_language_hint.trim().to_string(); + let language_hint = if language_hint.is_empty() { + None + } else { + Some(language_hint) + }; + let local = Arc::new(FoundryLocalWhisperAsr::new( + Arc::clone(&inner.foundry_local_runtime), + model_alias, + prefs.foundry_local_runtime_source.clone(), + language_hint, + )); + let active = ActiveAsr::FoundryLocalWhisper(Arc::clone(&local)); + let consumer: Arc = local; + return Ok(QaAsrStart::Ready { active, consumer }); + } + + #[cfg(target_os = "windows")] + if sherpa::is_sherpa_onnx_local(active_asr) { + let prefs = inner.prefs.get(); + let model_alias = if sherpa::model_alias_is_known(&prefs.sherpa_onnx_model) { + prefs.sherpa_onnx_model.clone() + } else { + sherpa::DEFAULT_MODEL_ALIAS.to_string() + }; + let language_hint = prefs.sherpa_onnx_language_hint.trim().to_string(); + let language_hint = if language_hint.is_empty() { + None + } else { + Some(language_hint) + }; + let token_handler = inner.app.lock().clone().map(|app| { + Arc::new(move |piece: String| { + if let Err(error) = app.emit("local-asr-token", piece) { + log::warn!("[sherpa-asr] emit token failed: {error}"); + } + }) as crate::asr::local::sherpa_provider::SherpaTokenHandler + }); + let local = SherpaOnnxAsr::new_for_model( + Arc::clone(&inner.sherpa_onnx_runtime), + model_alias, + language_hint, + token_handler, + ) + .await + .map_err(|e| format!("sherpa-onnx init failed: {e}"))?; + let local = Arc::new(local); + let active = ActiveAsr::SherpaOnnxLocal(Arc::clone(&local)); + let consumer: Arc = local; + return Ok(QaAsrStart::Ready { active, consumer }); + } + + #[cfg(target_os = "macos")] + if crate::asr::local::is_local_qwen3(active_asr) { + let local = build_local_qwen3(inner) + .await + .map_err(|e| format!("local ASR init failed: {e}"))?; + let active = ActiveAsr::Local(Arc::clone(&local)); + let consumer: Arc = local; + return Ok(QaAsrStart::Ready { active, consumer }); + } + + #[cfg(target_os = "macos")] + if crate::asr::local::is_apple_speech(active_asr) { + let local = build_apple_speech(); + let active = ActiveAsr::AppleSpeech(Arc::clone(&local)); + let consumer: Arc = local; + return Ok(QaAsrStart::Ready { active, consumer }); + } + + match active_asr_provider_kind(active_asr) { + ActiveAsrProviderKind::Bailian => Ok(QaAsrStart::Bailian { + asr: Arc::new(BailianRealtimeASR::new(read_bailian_credentials())), + bridge: Arc::new(DeferredAsrBridge::new()), + }), + ActiveAsrProviderKind::Mimo => { + let (api_key, base_url, model) = read_mimo_credentials(); + let mimo = Arc::new(MimoBatchASR::new(api_key, base_url, model)); + let active = ActiveAsr::Mimo(Arc::clone(&mimo)); + let consumer: Arc = mimo; + Ok(QaAsrStart::Ready { active, consumer }) + } + ActiveAsrProviderKind::WhisperCompatible => { + let (api_key, base_url, model) = read_whisper_credentials(); + let whisper_prompt = + crate::asr::whisper::build_prompt_from_phrases(&enabled_phrases(inner)); + let whisper = Arc::new( + WhisperBatchASR::new( + api_key, + base_url, + model, + whisper_prompt, + batch_asr_chunk_limit_ms(active_asr), + whisper_supports_verbose_json(active_asr), + ) + .with_request_format(whisper_request_format(active_asr)), + ); + let active = ActiveAsr::Whisper(Arc::clone(&whisper)); + let consumer: Arc = whisper; + Ok(QaAsrStart::Ready { active, consumer }) + } + ActiveAsrProviderKind::Volcengine => Ok(QaAsrStart::Volcengine { + asr: Arc::new(VolcengineStreamingASR::new( + read_volc_credentials(), + enabled_hotwords(inner), + )), + bridge: Arc::new(DeferredAsrBridge::new()), + }), + } +} + +/// 润色文本;失败时返回原文 + 失败原因,调用方据此弹错误胶囊 + 写历史 error_code。 +/// 之前固定返回 String,调用方拿不到失败信号 → 用户感知"为什么风格设置没生效"。issue #57。 +/// 流式润色的三态结果。让上层(dictation pipeline)能区分「已经流出去了」、 +/// 「降级到一次性」和「真失败了走 raw 兜底」三种 case。 +pub enum StreamingPolishOutcome { + /// 流式润色成功,`String` 是已经一边流一边交给 `on_delta` 的全部文本(用于写 + /// history、做词条命中统计)。调用方不应再 `inserter.insert(&text)`,因为字符 + /// 已经通过键盘事件落到光标处。 + Streamed(String), + /// 当前配置不支持流式:用户没开 streaming_insert / Gemini provider / Codex + /// provider / Raw 模式 / 翻译模式 / 不是 macOS。调用方应回到现有的 + /// `polish_or_passthrough` 一次性路径,跟历史行为完全一致。 + UnsupportedFallback, + /// 流式过程中失败(HTTP / 解析 / 空流等)。`String` 是失败原因,调用方应当 + /// 走 raw 兜底(同 `polish_or_passthrough` 失败分支的语义)。 + Failed(String), +} + +/// 流式润色入口。在不支持流式的所有 case 都返回 `UnsupportedFallback`,让调用方 +/// 透明降级。不修改任何持久化 / 焦点 / 光标状态。 +/// +/// `on_delta` 每收到一个 SSE chunk 就被调用一次(同步),调用方负责把 chunk 实际 +/// 模拟键盘事件落到光标 —— 见 `coordinator/dictation.rs` 的流式分支。 +/// `should_cancel` 用户取消时返回 true,立即 break SSE 读循环避免烧 quota。 +pub async fn polish_or_passthrough_streaming( + raw: &RawTranscript, + mode: PolishMode, + hotwords: &[String], + style_system_prompt: &str, + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + prior_turns: &[(String, String)], + on_delta: F, + should_cancel: C, +) -> StreamingPolishOutcome +where + F: Fn(&str) + Send + Sync, + C: Fn() -> bool + Send + Sync, +{ + if mode == PolishMode::Raw && !raw_mode_uses_llm(style_system_prompt) { + log::info!("[coord] streaming polish skipped: mode=Raw, fall back to one-shot"); + return StreamingPolishOutcome::UnsupportedFallback; + } + let active_llm = CredentialsVault::get_active_llm(); + if active_llm == "gemini" { + log::info!( + "[coord] streaming polish skipped: active LLM provider=gemini (v1 not implemented), fall back to one-shot" + ); + return StreamingPolishOutcome::UnsupportedFallback; + } + let provider = match build_active_llm_provider(llm_thinking_enabled) { + Ok(p) => p, + Err(e) => { + log::error!("[coord] streaming polish: build provider failed: {e}"); + return StreamingPolishOutcome::Failed(e.to_string()); + } + }; + if !provider.supports_streaming_polish() { + log::info!( + "[coord] streaming polish skipped: provider does not support streaming (likely codex OAuth), fall back to one-shot" + ); + return StreamingPolishOutcome::UnsupportedFallback; + } + log::info!( + "[coord] streaming polish START: provider=openai-compatible mode={:?} raw_chars={} prior_turns={}", + mode, + raw.text.chars().count(), + prior_turns.len() + ); + match provider + .polish_streaming( + &raw.text, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + prior_turns, + on_delta, + should_cancel, + ) + .await + { + Ok(text) => { + log::info!( + "[coord] streaming polish OK: final_chars={}", + text.chars().count() + ); + StreamingPolishOutcome::Streamed(text) + } + Err(e) => { + let reason = e.to_string(); + log::error!("[coord] streaming polish FAILED: {reason}"); + StreamingPolishOutcome::Failed(reason) + } + } +} + +async fn polish_or_passthrough( + raw: &RawTranscript, + mode: PolishMode, + hotwords: &[String], + style_system_prompt: &str, + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + prior_turns: &[(String, String)], +) -> (String, Option) { + if mode == PolishMode::Raw && !raw_mode_uses_llm(style_system_prompt) { + return (raw.text.clone(), None); + } + match polish_text( + &raw.text, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + ) + .await + { + Ok(s) => (s, None), + Err(e) => { + let reason = e.to_string(); + log::error!("[coord] polish failed, falling back to raw: {reason}"); + (raw.text.clone(), Some(reason)) + } + } +} + +async fn polish_text( + raw: &str, + mode: PolishMode, + hotwords: &[String], + style_system_prompt: &str, + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + prior_turns: &[(String, String)], +) -> anyhow::Result { + // 谷歌 Gemini 分支:所有 LLM provider 共用 ark.* 凭据槽,唯独 Gemini 走原生 + // generateContent / 自带 thinkingConfig 控制;其余 provider 走 OpenAI + // 兼容协议,并在该路径里按 provider/channel 下发对应的思考开关。 + let active_llm = CredentialsVault::get_active_llm(); + if active_llm == "gemini" { + let (api_key, model, base_url) = read_gemini_credentials()?; + let provider = GeminiProvider::new( + GeminiConfig::new(api_key, model, base_url).with_thinking_enabled(llm_thinking_enabled), + ); + return Ok(provider + .polish( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + prior_turns, + ) + .await?); + } + + let provider = build_active_llm_provider(llm_thinking_enabled)?; + Ok(provider + .polish( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + prior_turns, + ) + .await?) +} + +/// 专用翻译(仅翻译、不润色、单轮)。现作为"润色+翻译"合成调用解析失败时的兜底—— +/// 模型没按两段格式输出时,退回这里拿一段干净译文,而不是把畸形输出当译文插入。 +async fn translate_text( + raw: &str, + target_language: &str, + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, +) -> anyhow::Result { + // 见 polish_text 顶部注释——同样的 Gemini / OpenAI-compatible 路由逻辑。 + let active_llm = CredentialsVault::get_active_llm(); + if active_llm == "gemini" { + let (api_key, model, base_url) = read_gemini_credentials()?; + let provider = GeminiProvider::new( + GeminiConfig::new(api_key, model, base_url).with_thinking_enabled(llm_thinking_enabled), + ); + return Ok(provider + .translate_to( + raw, + target_language, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + ) + .await?); + } + + let provider = build_active_llm_provider(llm_thinking_enabled)?; + Ok(provider + .translate_to( + raw, + target_language, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + ) + .await?) +} + +/// "润色+翻译"单次调用的两段哨兵。模型按 `SRC\n源文\nTGT\n译文` 输出,解析器据此切分。 +/// 这两个串必须与 build_polish_translate_system_prompt 写给模型的完全一致。 +const POLISH_TRANSLATE_SRC_MARKER: &str = "[[OPENLESS_POLISHED_SOURCE]]"; +const POLISH_TRANSLATE_TGT_MARKER: &str = "[[OPENLESS_TRANSLATION]]"; + +/// 合成"先润色源文、再翻译"的系统提示词:在原翻译 prompt 之上追加"额外输出润色后源文" +/// 与严格两段格式(覆盖原 prompt 末尾的"只输出译文")。译文仍是要插入用户光标的主产物, +/// 故完整保留原翻译规则;润色后的源文只作对话上下文用,轻量清理即可。 +fn build_polish_translate_system_prompt(target_language: &str) -> String { + let base = crate::polish::prompts::translate_system_prompt(target_language); + format!( + "{base}\n\n\ + # 额外输出:润色后的源文(仅用于对话上下文,不展示给用户)\n\ + 在译文之前,先把上面的原始转写**按它本来的语言**润色一遍:去掉口癖(嗯 / 那个 / um)、\ + 补必要标点、纠正明显的识别错误,但**不翻译、不改写风格、不增删意思**。\n\n\ + # 输出格式(覆盖上面\u{201C}只输出译文\u{201D}的说明,严格遵守)\n\ + 严格按下面两段输出,两个标记必须原样出现、各占一行,标记之外不要有任何多余文字:\n\ + {src}\n\ + (这里放润色后的源文,保持原语言)\n\ + {tgt}\n\ + (这里放翻译成\u{300C}{lang}\u{300D}的译文)", + base = base, + src = POLISH_TRANSLATE_SRC_MARKER, + tgt = POLISH_TRANSLATE_TGT_MARKER, + lang = target_language, + ) +} + +/// 解析"润色+翻译"单次调用输出 → Some((润色后源文, 译文))。 +/// 找到译文标记且译文非空 → Some((源文, 译文)):源文标记缺失 / 源文段为空时源文为 None, +/// 译文取标记之后的干净正文。**没有译文标记、或译文段为空(模型截断 / 只吐了标记)→ None**, +/// 表示没拿到可信译文,交由调用方退回专用翻译——避免把空串当"成功译文"插进光标而丢字。 +fn split_polish_translate_output(raw: &str) -> Option<(Option, String)> { + let tgt_idx = raw.find(POLISH_TRANSLATE_TGT_MARKER)?; + let translation = raw[tgt_idx + POLISH_TRANSLATE_TGT_MARKER.len()..] + .trim() + .to_string(); + if translation.is_empty() { + return None; + } + let before_tgt = &raw[..tgt_idx]; + let source = before_tgt + .find(POLISH_TRANSLATE_SRC_MARKER) + .map(|i| { + before_tgt[i + POLISH_TRANSLATE_SRC_MARKER.len()..] + .trim() + .to_string() + }) + .filter(|s| !s.is_empty()); + Some((source, translation)) +} + +/// 翻译路径——单次 LLM 调用同时润色源文 + 翻译。和 polish 一样失败时返回原文 + 失败原因, +/// 避免"不丢字"约定被违反(CLAUDE.md)。返回 (要插入的译文, 润色后源文供上下文用, 失败原因)。 +#[allow(clippy::too_many_arguments)] +async fn polish_and_translate_or_passthrough( + raw: &RawTranscript, + target_language: &str, + mode: PolishMode, + hotwords: &[String], + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + prior_turns: &[(String, String)], +) -> (String, Option, Option) { + let system_prompt = build_polish_translate_system_prompt(target_language); + match polish_text( + &raw.text, + mode, + hotwords, + &system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + ) + .await + { + Ok(out) => match split_polish_translate_output(&out) { + Some((source, translation)) => (translation, source, None), + None => { + // 模型没按两段格式输出:退回专用翻译拿一段干净译文,避免把畸形输出插进光标。 + // 此时无可信源文,这条翻译历史不参与后续普通润色上下文。 + log::warn!( + "[coord] polish+translate output missing markers; falling back to plain translate" + ); + match translate_text( + &raw.text, + target_language, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + ) + .await + { + Ok(translation) => (translation, None, None), + Err(e) => { + let reason = e.to_string(); + log::error!("[coord] fallback translate failed, using raw: {reason}"); + (raw.text.clone(), None, Some(reason)) + } + } + } + }, + Err(e) => { + let reason = e.to_string(); + log::error!("[coord] polish+translate failed, falling back to raw: {reason}"); + (raw.text.clone(), None, Some(reason)) + } + } +} + +fn read_whisper_credentials() -> (String, String, String) { + let api_key = CredentialsVault::get(CredentialAccount::AsrApiKey) + .ok() + .flatten() + .unwrap_or_default(); + let base_url = CredentialsVault::get(CredentialAccount::AsrEndpoint) + .ok() + .flatten() + .unwrap_or_default(); + let model = CredentialsVault::get(CredentialAccount::AsrModel) + .ok() + .flatten() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "whisper-1".to_string()); + (api_key, base_url, model) +} + +fn read_mimo_credentials() -> (String, String, String) { + let api_key = CredentialsVault::get(CredentialAccount::AsrApiKey) + .ok() + .flatten() + .unwrap_or_default(); + let base_url = CredentialsVault::get(CredentialAccount::AsrEndpoint) + .ok() + .flatten() + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| crate::asr::mimo::DEFAULT_ENDPOINT.to_string()); + let model = CredentialsVault::get(CredentialAccount::AsrModel) + .ok() + .flatten() + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| crate::asr::mimo::DEFAULT_MODEL.to_string()); + (api_key, base_url, model) +} + +fn read_bailian_credentials() -> BailianCredentials { + let api_key = CredentialsVault::get(CredentialAccount::AsrApiKey) + .ok() + .flatten() + .unwrap_or_default(); + let endpoint = CredentialsVault::get(CredentialAccount::AsrEndpoint) + .ok() + .flatten() + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| crate::asr::bailian::DEFAULT_ENDPOINT.to_string()); + let model = CredentialsVault::get(CredentialAccount::AsrModel) + .ok() + .flatten() + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| crate::asr::bailian::DEFAULT_MODEL.to_string()); + let vocabulary_id = CredentialsVault::get(CredentialAccount::AsrVocabularyId) + .ok() + .flatten() + .filter(|s| !s.trim().is_empty()); + BailianCredentials { + api_key, + endpoint, + model, + vocabulary_id, + } +} + +fn read_volc_credentials() -> VolcengineCredentials { + let app_id = CredentialsVault::get(CredentialAccount::VolcengineAppKey) + .ok() + .flatten() + .unwrap_or_default(); + let access_token = CredentialsVault::get(CredentialAccount::VolcengineAccessKey) + .ok() + .flatten() + .unwrap_or_default(); + let resource_id = CredentialsVault::get(CredentialAccount::VolcengineResourceId) + .ok() + .flatten() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| VolcengineCredentials::default_resource_id().to_string()); + VolcengineCredentials { + app_id, + access_token, + resource_id, + } +} + +fn enabled_hotwords(inner: &Arc) -> Vec { + inner + .vocab + .list() + .unwrap_or_default() + .into_iter() + .map(|e| DictionaryHotword { + phrase: e.phrase, + enabled: e.enabled, + }) + .collect() +} + +// ─────────────────────────── QA session lifecycle ─────────────────────────── + +async fn finalize_dictation_as_qa_question(inner: &Arc) -> Result<(), String> { + log::info!("[coord] QA finalize from overlay: capturing selection before opening panel"); + let selection = capture_selection(); + let selection_preview_text = selection.as_ref().map(|s| s.text.clone()); + + log::info!("[coord] QA finalize from overlay: opening panel and waiting for ASR result"); + open_qa_panel(inner); + { + let mut state = inner.qa_state.lock(); + state.phase = QaPhase::Processing; + state.cancelled = false; + state.session_id = new_session_id(); + state.front_app = capture_frontmost_app(); + state.selection = selection; + } + inner.qa_stream_cancelled.store(false, Ordering::SeqCst); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "loading", + "selection_preview": selection_preview_text, + "messages": messages, + }), + ); + } + + let raw = match take_current_dictation_transcript_for_qa(inner).await { + Ok(Some(raw)) => raw, + Ok(None) => { + log::info!("[coord] QA finalize from overlay: no transcript produced"); + finish_qa_idle_silently(inner); + return Ok(()); + } + Err(error) => { + finish_qa_with_error(inner, error.clone()); + return Err(error); + } + }; + log::info!( + "[coord] QA finalize from overlay: transcript ready chars={} duration_ms={}", + raw.text.chars().count(), + raw.duration_ms + ); + answer_qa_question_text(inner, raw.text.trim().to_string(), raw.duration_ms).await +} + +async fn submit_qa_text_question(inner: &Arc, text: String) -> Result<(), String> { + let question = text.trim().to_string(); + if question.is_empty() { + return Ok(()); + } + + { + let mut state = inner.qa_state.lock(); + if !state.panel_visible { + state.panel_visible = true; + state.messages.clear(); + state.front_app = capture_frontmost_app(); + state.qa_focus_target = capture_focus_target(); + } + if state.phase != QaPhase::Idle { + return Err("QA is busy".to_string()); + } + state.phase = QaPhase::Processing; + state.cancelled = false; + state.session_id = new_session_id(); + if state.selection.is_none() { + state.selection = capture_selection(); + } + } + inner.qa_stream_cancelled.store(false, Ordering::SeqCst); + + let selection_preview_text = inner + .qa_state + .lock() + .selection + .as_ref() + .map(|selection| selection.text.clone()); + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "thinking", + "selection_preview": selection_preview_text, + "messages": messages, + }), + ); + } + + answer_qa_question_text(inner, question, 0).await +} + +async fn take_current_dictation_transcript_for_qa( + inner: &Arc, +) -> Result, String> { + wait_for_dictation_listening(inner).await?; + + let current_session_id = { + let mut state = inner.state.lock(); + let Some(session_id) = start_processing_if_listening(&mut state) else { + return Ok(None); + }; + session_id + }; + + let elapsed = inner.state.lock().started_at.elapsed().as_millis() as u64; + emit_capsule(inner, CapsuleState::Transcribing, 0.0, elapsed, None, None); + + if let Some(rec) = take_recorder_for_session(inner, current_session_id) { + rec.stop(); + release_recording_mute(inner, "dictation"); + } + + let Some(asr) = take_asr_for_session(inner, current_session_id) else { + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(None); + }; + + let mut raw = match transcribe_overlay_dictation_asr(inner, current_session_id, asr).await { + Ok(raw) => raw, + Err(error) => { + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + finish_qa_with_error(inner, format!("识别失败: {error}")); + return Err(error); + } + }; + + if inner.state.lock().cancelled { + log::info!("[coord] overlay QA: cancel detected after ASR — discarding transcript"); + restore_prepared_windows_ime_session(inner, current_session_id); + { + let mut state = inner.state.lock(); + state.phase = SessionPhase::Idle; + state.focus_target = None; + } + return Ok(None); + } + + #[cfg(any(debug_assertions, test))] + if raw.text.trim().is_empty() { + if let Some(debug_text) = debug_transcript_override_text() { + raw.text = debug_text; + } + } + + if raw.text.trim().is_empty() { + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + finish_qa_idle_silently(inner); + return Ok(None); + } + + if let Ok(rules) = inner.correction_rules.list() { + let corrected = apply_correction_rules(&raw.text, &rules); + if corrected != raw.text { + raw.text = corrected; + } + } + + restore_prepared_windows_ime_session(inner, current_session_id); + { + let mut state = inner.state.lock(); + state.phase = SessionPhase::Idle; + state.focus_target = None; + } + Ok(Some(raw)) +} + +async fn wait_for_dictation_listening(inner: &Arc) -> Result<(), String> { + const MAX_WAIT_MS: u64 = 3_000; + const STEP_MS: u64 = 20; + let deadline = std::time::Instant::now() + std::time::Duration::from_millis(MAX_WAIT_MS); + + loop { + let phase = { inner.state.lock().phase }; + match phase { + SessionPhase::Starting if std::time::Instant::now() < deadline => { + tokio::time::sleep(std::time::Duration::from_millis(STEP_MS)).await; + } + SessionPhase::Starting => { + return Err("dictation startup timed out before QA finalize".to_string()); + } + _ => return Ok(()), + } + } +} + +async fn transcribe_overlay_dictation_asr( + _inner: &Arc, + _current_session_id: SessionId, + asr: ActiveAsr, +) -> Result { + let uses_global_timeout = asr_transcribe_uses_global_timeout(&asr); + match asr { + ActiveAsr::Volcengine(asr) => { + debug_assert!(uses_global_timeout); + if let Err(error) = asr.send_last_frame().await { + log::error!("[coord] overlay QA: send last frame failed: {error}"); + } + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => { + asr.cancel(); + Err("global timeout".to_string()) + } + } + } + ActiveAsr::Bailian(asr) => { + debug_assert!(uses_global_timeout); + if let Err(error) = asr.send_last_frame().await { + log::error!("[coord] overlay QA: Bailian send last frame failed: {error}"); + } + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => { + asr.cancel(); + Err("bailian global timeout".to_string()) + } + } + } + ActiveAsr::Whisper(whisper) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, whisper.transcribe()).await { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => Err("whisper global timeout".to_string()), + } + } + ActiveAsr::Mimo(mimo) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, mimo.transcribe()).await { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => Err("mimo global timeout".to_string()), + } + } + #[cfg(target_os = "windows")] + ActiveAsr::FoundryLocalWhisper(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(foundry_audio_transcribe_timeout_duration()) + .await + { + Ok(raw) => { + schedule_foundry_local_asr_release( + _inner, + AsrReleaseSession::Dictation(_current_session_id), + ); + Ok(raw) + } + Err(error) => { + schedule_foundry_local_asr_release( + _inner, + AsrReleaseSession::Dictation(_current_session_id), + ); + Err(error.to_string()) + } + } + } + #[cfg(target_os = "windows")] + ActiveAsr::SherpaOnnxLocal(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(sherpa_audio_transcribe_timeout_duration()) + .await + { + Ok(raw) => { + schedule_sherpa_onnx_release( + _inner, + AsrReleaseSession::Dictation(_current_session_id), + ); + Ok(raw) + } + Err(error) => { + schedule_sherpa_onnx_release( + _inner, + AsrReleaseSession::Dictation(_current_session_id), + ); + Err(error.to_string()) + } + } + } + #[cfg(target_os = "macos")] + ActiveAsr::Local(local) => { + debug_assert!(uses_global_timeout); + let audio_secs = (local.buffer_duration_ms() as f64) / 1000.0; + let timeout_duration = local_qwen_transcribe_timeout(audio_secs); + let result = tokio::time::timeout(timeout_duration, local.transcribe()).await; + _inner.local_asr_cache.touch(); + schedule_local_asr_release(_inner); + match result { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => Err("local qwen transcribe timeout".to_string()), + } + } + #[cfg(target_os = "macos")] + ActiveAsr::AppleSpeech(local) => { + debug_assert!(uses_global_timeout); + match tokio::time::timeout( + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS), + local.transcribe(), + ) + .await + { + Ok(Ok(raw)) => Ok(raw), + Ok(Err(error)) => Err(error.to_string()), + Err(_) => Err("apple speech transcribe timeout".to_string()), + } + } + } +} + +async fn answer_qa_question_text( + inner: &Arc, + question: String, + duration_ms: u64, +) -> Result<(), String> { + if question.trim().is_empty() { + finish_qa_idle_silently(inner); + return Ok(()); + } + + let user_content = { + let st = inner.qa_state.lock(); + let is_first_turn = st.messages.is_empty(); + let sel_text = st + .selection + .as_ref() + .map(|s| s.text.clone()) + .unwrap_or_default(); + if is_first_turn && !sel_text.trim().is_empty() { + format!( + "# 选区原文\n{}\n\n# 我的问题\n{}", + sel_text.trim(), + question + ) + } else { + question.clone() + } + }; + + inner + .qa_state + .lock() + .messages + .push(crate::types::QaChatMessage { + role: "user".to_string(), + content: user_content, + }); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "thinking", + "messages": messages, + }), + ); + } + + emit_capsule(inner, CapsuleState::Polishing, 0.0, 0, None, None); + + let prefs = inner.prefs.get(); + let working_languages = prefs.working_languages.clone(); + let chinese_script_preference = prefs.chinese_script_preference; + let output_language_preference = prefs.output_language_preference; + let llm_thinking_enabled = prefs.llm_thinking_enabled; + let (messages_for_llm, front_app) = { + let st = inner.qa_state.lock(); + (st.messages.clone(), st.front_app.clone()) + }; + + let captured_session_id = inner.qa_state.lock().session_id; + let inner_for_delta = Arc::clone(inner); + let on_delta = move |chunk: &str| { + let cur_id = inner_for_delta.qa_state.lock().session_id; + if cur_id != captured_session_id { + return; + } + if let Some(app) = inner_for_delta.app.lock().clone() { + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "answer_delta", + "chunk": chunk, + }), + ); + } + }; + + let cancel_flag = Arc::clone(&inner.qa_stream_cancelled); + let should_cancel = move || cancel_flag.load(Ordering::Relaxed); + + let answer = match answer_chat_dispatch( + &messages_for_llm, + &working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app.as_deref(), + on_delta, + should_cancel, + ) + .await + { + Ok(answer) => answer, + Err(error) => { + inner.qa_state.lock().messages.pop(); + finish_qa_with_error(inner, format!("回答失败: {error}")); + return Err(error.to_string()); + } + }; + + if inner.qa_state.lock().cancelled { + inner.qa_state.lock().messages.pop(); + finish_qa_idle_silently(inner); + return Ok(()); + } + + inner + .qa_state + .lock() + .messages + .push(crate::types::QaChatMessage { + role: "assistant".to_string(), + content: answer.clone(), + }); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "answer", + "messages": messages, + }), + ); + } + + emit_capsule(inner, CapsuleState::Idle, 0.0, 0, None, None); + + if prefs.qa_save_history { + let session = DictationSession { + id: Uuid::new_v4().to_string(), + created_at: Utc::now().to_rfc3339(), + raw_transcript: question.clone(), + final_text: answer, + mode: PolishMode::Raw, + style_pack_id: None, + translation_active: false, + polish_source: None, + app_bundle_id: None, + app_name: front_app, + insert_status: InsertStatus::CopiedFallback, + error_code: Some("qaSession".to_string()), + duration_ms: Some(duration_ms), + dictionary_entry_count: None, + has_audio_recording: None, + }; + let prefs_snapshot = inner.prefs.get(); + if let Err(error) = inner.history.append_with_retention( + session, + prefs_snapshot.history_retention_days, + prefs_snapshot.history_max_entries, + ) { + log::error!("[coord] overlay QA history append failed: {error}"); + } + } + + inner.qa_state.lock().phase = QaPhase::Idle; + Ok(()) +} + +/// 划词语音问答会话(issue #118)。 +/// +/// 与 dictation 完全分离: +/// - 不进 SessionPhase(互不抢锁) +/// - 不写 history.json(除非 prefs.qa_save_history=true 才旁路写一条 placeholder) +/// - 用独立的 qa_recorder + qa_asr,复用现有 Volcengine ASR 通路 +async fn begin_qa_session(inner: &Arc) -> Result<(), String> { + { + let mut state = inner.qa_state.lock(); + if !state.panel_visible { + // 防御:浮窗没开就被叫到这里说明路由错了,直接退出。 + return Ok(()); + } + if state.phase != QaPhase::Idle { + return Ok(()); + } + state.phase = QaPhase::Recording; + state.cancelled = false; + state.session_id = new_session_id(); + state.front_app = capture_frontmost_app(); + state.selection = None; + } + // 重置 SSE 取消标志:上一轮可能 set 过的 true 留着会让本轮流式立即 break。 + inner.qa_stream_cancelled.store(false, Ordering::SeqCst); + + // 抓选区。每轮按 Option 都重新抓一次:用户多轮提问中可以重新选别处文字。 + // + // - macOS:浮窗走 orderFrontRegardless,不成为 key window,原 app 仍是 frontmost, + // AX/Cmd+C fallback 都能拿到。 + // - Windows:#466 修复后 show_qa_window_no_activate 主动抓焦点,QA 此刻已是前台, + // simulate_copy 会跑在 QA 自己 webview 上 → 抓不到。focus-dance 上半场:把焦点临时 + // 还给"用户原 app 的 HWND"。 + // + // 多轮场景的目标刷新:用户开 QA 后可能 Alt+Tab 切到别的 app 选新文字。如果还死认 + // open_qa_panel 时记下的初始 HWND,会把焦点抢回错的 app(pr_agent stale-focus 关注点)。 + // 策略:每轮先看当前前台是不是本进程的窗口(QA / capsule / main)—— 是 → 用户没切 + // 走,沿用 saved;不是 → 用户切到了真正的外部 app,刷新 saved 为当前 HWND。 + // 抓完选区后下半场再把焦点交还 QA,让 ESC/X 继续可用。 + #[cfg(target_os = "windows")] + { + // 合并两次 lock:原来分 lock #1 写 + lock #2 读,两者之间 close_qa_panel 在别的 + // 线程把 qa_focus_target 清成 None 会被覆盖回旧 HWND。Cloud 评审指出的 TOCTOU。 + // 单次加锁里既写最新外部前台、再读出来交给后面的 restore_focus_target_if_possible + // —— capture_external_focus_target() 内部只调 GetForegroundWindow / pid 查询, + // 不会反向取 qa_state 锁,持锁期间调用安全。 + let saved_target = { + let mut state = inner.qa_state.lock(); + if let Some(current_external) = capture_external_focus_target() { + state.qa_focus_target = Some(current_external); + } + state.qa_focus_target + }; + let _ = restore_focus_target_if_possible(saved_target); + } + let selection = capture_selection(); + #[cfg(target_os = "windows")] + if let Some(app) = inner.app.lock().clone() { + crate::refocus_qa_window(&app); + } + let selection_preview_text = selection.as_ref().map(|s| s.text.clone()); + inner.qa_state.lock().selection = selection.clone(); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "recording", + "selection_preview": selection_preview_text, + "messages": messages, + }), + ); + } + + // 2. QA 与 dictation 使用同一个 active ASR 入口。不要回退火山,否则用户配置 + // 百炼 / Whisper / 本地 ASR 后,浮窗仍会偷偷走另一套凭据。 + let active_asr = CredentialsVault::get_active_asr(); + if let Err(message) = ensure_asr_credentials() { + log::warn!("[coord] QA: active ASR credentials missing: {message}"); + finish_qa_with_error(inner, format!("缺少 ASR 凭据:{message}")); + return Err(message); + } + + if let Err(message) = ensure_microphone_permission(inner) { + log::warn!("[coord] QA: microphone permission gate failed: {message}"); + finish_qa_with_error(inner, message.clone()); + return Err(message); + } + + let qa_asr = match build_qa_asr_start(inner, &active_asr).await { + Ok(qa_asr) => qa_asr, + Err(message) => { + log::error!("[coord] QA active ASR init failed: {message}"); + finish_qa_with_error(inner, format!("ASR 初始化失败: {message}")); + return Err(message); + } + }; + let consumer = qa_asr.recorder_consumer(); + *inner.qa_asr.lock() = Some(qa_asr.active_asr()); + + // QA recorder 不需要 RMS 节流到胶囊;前端 QA 浮窗有自己的电平视图, + // Android 的 QA 面板嵌在 main WebView;桌面端仍发给独立 qa 窗口。 + let inner_for_level = Arc::clone(inner); + let last_emit_at = Arc::new(Mutex::new(None::)); + const LEVEL_EMIT_MIN_INTERVAL_MS: u64 = 33; + let level_handler: Arc = Arc::new(move |level| { + let phase = inner_for_level.qa_state.lock().phase; + if phase != QaPhase::Recording { + return; + } + let now = Instant::now(); + { + let mut last = last_emit_at.lock(); + if let Some(prev) = *last { + if now.duration_since(prev).as_millis() < LEVEL_EMIT_MIN_INTERVAL_MS as u128 { + return; + } + } + *last = Some(now); + } + if let Some(app) = inner_for_level.app.lock().clone() { + let _ = app.emit_to( + qa_event_target(), + "qa:level", + serde_json::json!({ "level": level }), + ); + } + // 同步把电平推给底部胶囊,让 QA 录音也有跟主听写一致的可视反馈。 + emit_capsule( + &inner_for_level, + CapsuleState::Recording, + level, + 0, + None, + None, + ); + }); + + let microphone_device_name = selected_microphone_device_name(inner); + stop_microphone_preview_monitor(inner, "QA recorder"); + acquire_recording_mute(inner, "qa").await; + // QA 默认不留痕(qa_save_history 默认 false),录音文件归档也跟着不开。 + // 调试 QA 麦克风请用主听写路径。 + match Recorder::start(microphone_device_name, consumer, level_handler, None) { + Ok((rec, runtime_errors, archive_active)) => { + // QA 路径不写 dictation 的 history,但仍把 archive 状态归零,避免 dictation + // 接力时读到上一个 QA session 的过期值。 + inner + .audio_archive_active + .store(archive_active, std::sync::atomic::Ordering::Relaxed); + *inner.qa_recorder.lock() = Some(rec); + // QA 也跟主听写一样监听 cpal runtime error。设备中途消失 / panic 时 + // 不能让 QA 永远卡在 Recording 没反馈。详见 issue #168。 + spawn_qa_recorder_error_monitor(inner, runtime_errors); + } + Err(e) => { + log::error!("[coord] QA recorder start failed: {e}"); + if let Some(asr) = inner.qa_asr.lock().take() { + cancel_active_asr(asr); + } + release_recording_mute(inner, "qa"); + finish_qa_with_error(inner, format!("录音启动失败: {e}")); + return Err(e.to_string()); + } + } + + if let Err(e) = qa_asr.open_streaming_session().await { + log::error!("[coord] QA: open ASR session failed: {e}"); + stop_qa_recorder(inner); + if let Some(asr) = inner.qa_asr.lock().take() { + cancel_active_asr(asr); + } + finish_qa_with_error(inner, format!("ASR 连接失败: {e}")); + return Err(e); + } + + // cancel race:在 await 期间用户可能 dismiss 了浮窗。 + if inner.qa_state.lock().cancelled { + log::info!("[coord] QA cancel raced during open_session — aborting begin"); + if let Some(asr) = inner.qa_asr.lock().take() { + cancel_active_asr(asr); + } + stop_qa_recorder(inner); + inner.qa_state.lock().phase = QaPhase::Idle; + return Ok(()); + } + + // 显式弹胶囊到 Recording。level_handler 后续会持续推电平,胶囊里"录音中…" + // 的视觉反馈跟主听写完全一致。 + emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); + + Ok(()) +} + +async fn end_qa_session(inner: &Arc) -> Result<(), String> { + { + let mut state = inner.qa_state.lock(); + if state.phase != QaPhase::Recording { + return Ok(()); + } + state.phase = QaPhase::Processing; + } + + // 胶囊进入 Transcribing:用户视觉上看到"识别中"。 + emit_capsule(inner, CapsuleState::Transcribing, 0.0, 0, None, None); + + if let Some(app) = inner.app.lock().clone() { + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ "kind": "loading" }), + ); + } + + stop_qa_recorder(inner); + + let asr = match inner.qa_asr.lock().take() { + Some(a) => a, + None => { + inner.qa_state.lock().phase = QaPhase::Idle; + return Ok(()); + } + }; + + #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] + let qa_session_id = inner.qa_state.lock().session_id; + let uses_global_timeout = asr_transcribe_uses_global_timeout(&asr); + let raw = match asr { + ActiveAsr::Volcengine(asr) => { + debug_assert!(uses_global_timeout); + if let Err(e) = asr.send_last_frame().await { + log::error!("[coord] QA: send last frame failed: {e}"); + } + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA: await final failed: {e}"); + finish_qa_with_error(inner, format!("识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] QA: 全局超时 {} 秒 - 强制恢复", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + asr.cancel(); + finish_qa_with_error(inner, "识别超时".to_string()); + return Err("global timeout".to_string()); + } + } + } + ActiveAsr::Bailian(asr) => { + debug_assert!(uses_global_timeout); + if let Err(e) = asr.send_last_frame().await { + log::error!("[coord] QA: Bailian send last frame failed: {e}"); + } + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA: Bailian await final failed: {e}"); + finish_qa_with_error(inner, format!("识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] QA: Bailian 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + asr.cancel(); + finish_qa_with_error(inner, "识别超时".to_string()); + return Err("bailian global timeout".to_string()); + } + } + } + ActiveAsr::Whisper(w) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, w.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA: whisper transcribe failed: {e}"); + finish_qa_with_error(inner, format!("识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] QA: whisper 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + finish_qa_with_error(inner, "识别超时".to_string()); + return Err("whisper global timeout".to_string()); + } + } + } + ActiveAsr::Mimo(m) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, m.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA: MiMo ASR transcribe failed: {e}"); + finish_qa_with_error(inner, format!("识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] QA: MiMo ASR 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + finish_qa_with_error(inner, "识别超时".to_string()); + return Err("mimo global timeout".to_string()); + } + } + } + #[cfg(target_os = "windows")] + ActiveAsr::FoundryLocalWhisper(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(foundry_audio_transcribe_timeout_duration()) + .await + { + Ok(r) => { + schedule_foundry_local_asr_release(inner, AsrReleaseSession::Qa(qa_session_id)); + r + } + Err(e) => { + schedule_foundry_local_asr_release(inner, AsrReleaseSession::Qa(qa_session_id)); + if inner.qa_state.lock().cancelled { + log::info!( + "[coord] QA Foundry Local Whisper transcribe cancelled — discarding transcript" + ); + finish_qa_idle_silently(inner); + return Ok(()); + } + log::error!("[coord] QA Foundry Local Whisper transcribe failed: {e:#}"); + finish_qa_with_error(inner, format!("本地识别失败: {e}")); + return Err(e.to_string()); + } + } + } + #[cfg(target_os = "windows")] + ActiveAsr::SherpaOnnxLocal(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(sherpa_audio_transcribe_timeout_duration()) + .await + { + Ok(r) => { + schedule_sherpa_onnx_release(inner, AsrReleaseSession::Qa(qa_session_id)); + r + } + Err(e) => { + schedule_sherpa_onnx_release(inner, AsrReleaseSession::Qa(qa_session_id)); + if inner.qa_state.lock().cancelled { + log::info!( + "[coord] QA sherpa-onnx transcribe cancelled — discarding transcript" + ); + finish_qa_idle_silently(inner); + return Ok(()); + } + log::error!("[coord] QA sherpa-onnx transcribe failed: {e:#}"); + finish_qa_with_error(inner, format!("本地识别失败: {e}")); + return Err(e.to_string()); + } + } + } + #[cfg(target_os = "macos")] + ActiveAsr::Local(local) => { + debug_assert!(uses_global_timeout); + let audio_secs = (local.buffer_duration_ms() as f64) / 1000.0; + let timeout_duration = local_qwen_transcribe_timeout(audio_secs); + log::info!( + "[coord] QA local Qwen3-ASR transcribe: audio={:.2}s timeout={}s", + audio_secs, + timeout_duration.as_secs() + ); + let result = tokio::time::timeout(timeout_duration, local.transcribe()).await; + inner.local_asr_cache.touch(); + schedule_local_asr_release(inner); + match result { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA local Qwen3-ASR transcribe failed: {e:#}"); + finish_qa_with_error(inner, format!("本地识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] QA local Qwen3-ASR transcribe timeout after {}s", + timeout_duration.as_secs() + ); + finish_qa_with_error(inner, "本地识别超时".to_string()); + return Err("local qwen transcribe timeout".to_string()); + } + } + } + #[cfg(target_os = "macos")] + ActiveAsr::AppleSpeech(local) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, local.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] QA Apple Speech transcribe failed: {e:#}"); + finish_qa_with_error(inner, format!("本地识别失败: {e}")); + return Err(e.to_string()); + } + Err(_) => { + log::error!("[coord] QA Apple Speech transcribe timeout"); + finish_qa_with_error(inner, "本地识别超时".to_string()); + return Err("apple speech transcribe timeout".to_string()); + } + } + } + }; + + // cancel race:用户在 transcribe 中按 Esc / dismiss → 静默退出。 + if inner.qa_state.lock().cancelled { + log::info!("[coord] QA cancel detected after ASR — discarding transcript"); + finish_qa_idle_silently(inner); + return Ok(()); + } + + let question = raw.text.trim().to_string(); + if question.is_empty() { + // 静默录音:不调 LLM,不弹错误,直接关浮窗。 + log::info!("[coord] QA: empty transcript → silent dismiss"); + finish_qa_idle_silently(inner); + return Ok(()); + } + + // 拼这一轮的 user 消息:第一轮(messages 还空)把选区原文嵌进去; + // 之后的轮次只送提问,让 LLM 顺着上下文回答。详见 issue #118 v2。 + let user_content = { + let st = inner.qa_state.lock(); + let is_first_turn = st.messages.is_empty(); + let sel_text = st + .selection + .as_ref() + .map(|s| s.text.clone()) + .unwrap_or_default(); + if is_first_turn && !sel_text.trim().is_empty() { + format!( + "# 选区原文\n{}\n\n# 我的问题\n{}", + sel_text.trim(), + question + ) + } else { + question.clone() + } + }; + + inner + .qa_state + .lock() + .messages + .push(crate::types::QaChatMessage { + role: "user".to_string(), + content: user_content, + }); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "thinking", + "messages": messages, + }), + ); + } + + // 胶囊:思考阶段(复用 dictation 的 Polishing 状态——视觉上是"润色中",QA 借用一下)。 + emit_capsule(inner, CapsuleState::Polishing, 0.0, 0, None, None); + + let prefs = inner.prefs.get(); + let working_languages = prefs.working_languages.clone(); + let chinese_script_preference = prefs.chinese_script_preference; + let output_language_preference = prefs.output_language_preference; + let llm_thinking_enabled = prefs.llm_thinking_enabled; + let (messages_for_llm, front_app) = { + let st = inner.qa_state.lock(); + (st.messages.clone(), st.front_app.clone()) + }; + + // 流式回调:每个 SSE delta 立刻推一帧 qa:state{kind:"answer_delta"} 给前端, + // 浮窗里气泡边收边长。最终的 messages 由 answer 事件统一下发(保证一致性)。 + // + // session_id 守卫(issue #161):闭包捕获本会话 id;用户取消 → 关浮窗 → 开新浮窗 + // 开新一轮时,旧的 in-flight LLM 流仍可能 emit chunk,必须在 emit 前比对当前 + // qa_state.session_id == 捕获 id,否则跳过——避免旧会话的字漏进新气泡。 + let captured_session_id = inner.qa_state.lock().session_id; + let inner_for_delta = Arc::clone(inner); + let on_delta = move |chunk: &str| { + let cur_id = inner_for_delta.qa_state.lock().session_id; + if cur_id != captured_session_id { + return; // 旧 session 漏来的 chunk,丢弃 + } + if let Some(app) = inner_for_delta.app.lock().clone() { + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "answer_delta", + "chunk": chunk, + }), + ); + } + }; + + // SSE 流取消旗标:cancel_qa_session / close_qa_panel 会 set true, + // polish 的 SSE loop 每帧检查 → break,释放 HTTP body。详见 issue #161。 + let cancel_flag = Arc::clone(&inner.qa_stream_cancelled); + let should_cancel = move || cancel_flag.load(Ordering::Relaxed); + + let answer = match answer_chat_dispatch( + &messages_for_llm, + &working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app.as_deref(), + on_delta, + should_cancel, + ) + .await + { + Ok(s) => s, + Err(e) => { + log::error!("[coord] QA: LLM answer failed: {e}"); + // 把刚 push 的 user 消息回滚,避免 retry 重复 + inner.qa_state.lock().messages.pop(); + finish_qa_with_error(inner, format!("回答失败: {e}")); + return Err(e.to_string()); + } + }; + + if inner.qa_state.lock().cancelled { + log::info!("[coord] QA cancel detected before answer — discarding"); + // 同样回滚未配对的 user 消息 + inner.qa_state.lock().messages.pop(); + finish_qa_idle_silently(inner); + return Ok(()); + } + + inner + .qa_state + .lock() + .messages + .push(crate::types::QaChatMessage { + role: "assistant".to_string(), + content: answer.clone(), + }); + + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "answer", + "messages": messages, + }), + ); + } + + // 胶囊直接收掉。QA 不走 insertion,没"已粘贴 N 字"语义;浮窗里答案就是用户的反馈。 + // (之前用 Done 状态会被 capsule UI 错误地渲染上一次 dictation 残留的 message/insertedChars。) + emit_capsule(inner, CapsuleState::Idle, 0.0, 0, None, None); + + // 可选:写一条 history(QA 类型)。当前 DictationSession schema 不能直接表达 + // "QuestionAnswer" 类型,因此简单做法:勾选 qa_save_history 时写一条 + // mode=Raw、error_code=Some("qaSession") 的 placeholder,避免污染 schema 同时 + // 让用户能在历史里翻到这次问答的字面值。详见 issue #118。 + if prefs.qa_save_history { + let session = DictationSession { + id: Uuid::new_v4().to_string(), + created_at: Utc::now().to_rfc3339(), + raw_transcript: question.clone(), + final_text: answer.clone(), + mode: PolishMode::Raw, + style_pack_id: None, + translation_active: false, + polish_source: None, + app_bundle_id: None, + app_name: front_app.clone(), + insert_status: InsertStatus::CopiedFallback, + error_code: Some("qaSession".to_string()), + duration_ms: Some(raw.duration_ms), + dictionary_entry_count: None, + has_audio_recording: None, + }; + let prefs_snapshot = inner.prefs.get(); + if let Err(e) = inner.history.append_with_retention( + session, + prefs_snapshot.history_retention_days, + prefs_snapshot.history_max_entries, + ) { + log::error!("[coord] QA history append failed: {e}"); + } + } + + inner.qa_state.lock().phase = QaPhase::Idle; + Ok(()) +} + +/// 把出错状态送到前端浮窗 + 胶囊错误闪一下 + 复位 phase。 +/// 浮窗保持可见(v2:错误后用户可以再按 Option 重试);messages 一并送过去 +/// 让前端继续渲染历史对话。 +fn finish_qa_with_error(inner: &Arc, message: String) { + stop_qa_recorder(inner); + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "error", + "error": message, + "messages": messages, + }), + ); + } + emit_capsule(inner, CapsuleState::Error, 0.0, 0, Some(message), None); + schedule_capsule_idle(inner, 1500); + let mut state = inner.qa_state.lock(); + state.phase = QaPhase::Idle; + state.cancelled = false; +} + +/// 静默收尾:发 idle 事件给前端,phase 复位。**不关浮窗**(v2:浮窗只在用户 +/// Esc/X 或再按 QA hotkey 时才关);多轮对话历史保留。胶囊也即刻收掉。 +fn finish_qa_idle_silently(inner: &Arc) { + if let Some(app) = inner.app.lock().clone() { + let messages = inner.qa_state.lock().messages.clone(); + let _ = app.emit_to( + qa_event_target(), + "qa:state", + serde_json::json!({ + "kind": "idle", + "messages": messages, + }), + ); + } + emit_capsule(inner, CapsuleState::Idle, 0.0, 0, None, None); + let mut state = inner.qa_state.lock(); + state.phase = QaPhase::Idle; + state.cancelled = false; + state.selection = None; +} + +fn cancel_qa_session(inner: &Arc) { + let phase = inner.qa_state.lock().phase; + if phase == QaPhase::Idle { + return; + } + inner.qa_state.lock().cancelled = true; + // SSE 流取消旗标——polish::chat_completion_history_streaming 的 loop 每帧检查 + // 这个 flag,true 时立即 break 不再 drain HTTP body,避免取消后 LLM 仍烧 token。 + // 详见 issue #161。 + inner.qa_stream_cancelled.store(true, Ordering::SeqCst); + stop_qa_recorder(inner); + if let Some(asr) = inner.qa_asr.lock().take() { + cancel_active_asr(asr); + } + // Processing 阶段保持 phase 让 end_qa_session 自然走完 cancel 检查; + // 否则直接复位。 + if phase != QaPhase::Processing { + inner.qa_state.lock().phase = QaPhase::Idle; + } + log::info!("[coord] QA session cancelled (was {phase:?})"); +} + +async fn answer_chat_dispatch( + messages: &[crate::types::QaChatMessage], + working_languages: &[String], + chinese_script_preference: ChineseScriptPreference, + output_language_preference: OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + on_delta: F, + should_cancel: C, +) -> anyhow::Result +where + F: Fn(&str) + Send + Sync, + C: Fn() -> bool + Send + Sync, +{ + // 见 polish_text 顶部注释——同样的 Gemini / OpenAI-compatible 路由逻辑, + // QA 流式回答走 Gemini 原生 :streamGenerateContent?alt=sse。 + let active_llm = CredentialsVault::get_active_llm(); + if active_llm == "gemini" { + let (api_key, model, base_url) = read_gemini_credentials()?; + let provider = GeminiProvider::new( + GeminiConfig::new(api_key, model, base_url).with_thinking_enabled(llm_thinking_enabled), + ); + return Ok(provider + .answer_chat_streaming( + messages, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + on_delta, + should_cancel, + ) + .await?); + } + + let provider = build_active_llm_provider(llm_thinking_enabled)?; + Ok(provider + .answer_chat_streaming( + messages, + working_languages, + chinese_script_preference, + output_language_preference, + front_app, + on_delta, + should_cancel, + ) + .await?) +} + +/// 读 Gemini 凭据。所有 LLM provider 共用 ark.* 槽位(persistence 没做 per-provider +/// 隔离),所以这里也是从 `ArkApiKey` / `ArkModelId` / `ArkEndpoint` 三个槽读, +/// 但回退默认值改成谷歌的:base_url 默认 `https://generativelanguage.googleapis.com/v1beta`, +/// 模型默认 `gemini-2.5-flash`。Settings.tsx::onLlmProviderChange 在用户切到 gemini +/// 时会强制把 endpoint/model 覆盖为这两个默认值,所以 99% 情况下槽里读出来就是 +/// 这两个;这里的 `unwrap_or_else` 是给极端情况兜底(如旧版本切换 bug 留下的脏数据)。 +/// +/// base_url 末尾去掉 `/`,让 `llm_gemini::generate_content_url` 拼接稳定。 +/// 不去 `/chat/completions` 后缀——OpenAI 兼容路径才会有那个后缀,原生 Gemini 不会。 +fn read_gemini_credentials() -> anyhow::Result<(String, String, String)> { + let api_key = CredentialsVault::get(CredentialAccount::ArkApiKey)?.unwrap_or_default(); + let model = CredentialsVault::get(CredentialAccount::ArkModelId)? + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| "gemini-2.5-flash".to_string()); + let base_url = CredentialsVault::get(CredentialAccount::ArkEndpoint)? + .filter(|s| !s.trim().is_empty()) + .unwrap_or_else(|| "https://generativelanguage.googleapis.com/v1beta".to_string()); + if api_key.trim().is_empty() { + anyhow::bail!("API Key 为空"); + } + let base_url = base_url.trim_end_matches('/').to_string(); + Ok((api_key, model, base_url)) +} + +fn build_active_llm_provider(llm_thinking_enabled: bool) -> anyhow::Result { + let active = CredentialsVault::get_active_llm(); + let model = + CredentialsVault::get(CredentialAccount::ArkModelId)?.filter(|s| !s.trim().is_empty()); + if active == CODEX_OAUTH_PROVIDER_ID { + let config = + CodexOAuthConfig::new(model.unwrap_or_else(|| CODEX_DEFAULT_MODEL.to_string())) + .with_thinking_enabled(llm_thinking_enabled); + return Ok(ActiveLLMProvider::Codex(CodexOAuthLLMProvider::new(config))); + } + + let api_key = CredentialsVault::get(CredentialAccount::ArkApiKey)?.unwrap_or_default(); + let model = model.unwrap_or_else(|| "deepseek-v3-2".to_string()); + let endpoint = resolve_ark_endpoint(&api_key)?; + let base_url = endpoint + .trim_end_matches("/chat/completions") + .trim_end_matches('/') + .to_string(); + let config = OpenAICompatibleConfig::new(active, "OpenLess LLM", base_url, api_key, model) + .with_thinking_enabled(llm_thinking_enabled); + Ok(ActiveLLMProvider::OpenAI(OpenAICompatibleLLMProvider::new( + config, + ))) +} + +fn resolve_ark_endpoint(api_key: &str) -> anyhow::Result { + let endpoint = CredentialsVault::get(CredentialAccount::ArkEndpoint)?.filter(|s| !s.is_empty()); + resolve_ark_endpoint_with_policy(api_key, endpoint) +} + +fn resolve_ark_endpoint_with_policy( + api_key: &str, + endpoint: Option, +) -> anyhow::Result { + if api_key.trim().is_empty() && endpoint.is_none() { + anyhow::bail!("API Key 为空"); + } + Ok(endpoint + .unwrap_or_else(|| "https://ark.cn-beijing.volces.com/api/v3/chat/completions".to_string())) +} + +#[cfg(test)] +mod tests { + use super::dictation::abort_recording_with_error; + use super::*; + use crate::types::{HotkeyMode, HotkeyTrigger}; + use once_cell::sync::Lazy; + + static ENV_LOCK: Lazy> = Lazy::new(|| tokio::sync::Mutex::new(())); + + fn session_id(n: u128) -> SessionId { + Uuid::from_u128(n) + } + + #[test] + fn split_polish_translate_parses_both_sections() { + let out = format!( + "{POLISH_TRANSLATE_SRC_MARKER}\n你好,世界。\n{POLISH_TRANSLATE_TGT_MARKER}\nHello, world." + ); + let (source, translation) = split_polish_translate_output(&out).expect("both markers"); + assert_eq!(source.as_deref(), Some("你好,世界。")); + assert_eq!(translation, "Hello, world."); + } + + #[test] + fn split_polish_translate_no_translation_marker_returns_none_for_fallback() { + // 完全没有译文标记 → None,调用方据此退回专用翻译拿干净译文。 + assert_eq!(split_polish_translate_output(" Hello, world. "), None); + } + + #[test] + fn split_polish_translate_empty_translation_returns_none_for_fallback() { + // 有译文标记但内容为空(截断 / 只吐标记)→ None,避免空串当成功译文插入光标。 + let out = + format!("{POLISH_TRANSLATE_SRC_MARKER}\n你好。\n{POLISH_TRANSLATE_TGT_MARKER}\n "); + assert_eq!(split_polish_translate_output(&out), None); + } + + #[test] + fn split_polish_translate_only_translation_marker_keeps_clean_translation() { + let out = format!("noise{POLISH_TRANSLATE_TGT_MARKER}\nHola"); + let (source, translation) = split_polish_translate_output(&out).expect("tgt marker"); + assert_eq!(source, None); + assert_eq!(translation, "Hola"); + } + + #[test] + fn split_polish_translate_empty_source_section_is_none() { + let out = format!("{POLISH_TRANSLATE_SRC_MARKER}\n \n{POLISH_TRANSLATE_TGT_MARKER}\nHi"); + let (source, translation) = split_polish_translate_output(&out).expect("tgt marker"); + assert_eq!(source, None); + assert_eq!(translation, "Hi"); + } + + #[test] + fn build_polish_translate_prompt_contains_markers_and_target() { + let p = build_polish_translate_system_prompt("日本語"); + assert!(p.contains(POLISH_TRANSLATE_SRC_MARKER)); + assert!(p.contains(POLISH_TRANSLATE_TGT_MARKER)); + assert!(p.contains("日本語")); + } + + #[tokio::test] + async fn hotkey_injection_gate_logs_pressed_and_cancels() { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::Info) + .is_test(false) + .try_init(); + let _guard = ENV_LOCK.lock().await; + std::env::set_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN", "1"); + + let coordinator = Coordinator::new(); + coordinator.inject_hotkey_click_for_dev().await.unwrap(); + + assert_eq!(coordinator.inner.state.lock().phase, SessionPhase::Idle); + std::env::remove_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN"); + } + + /// 复现并验证目标 2(a):按下 Less Computer 键必须弹出可见胶囊。 + /// 这里直接驱动 bridge 会调用的 handler,断言 begin_session 确实下发了可见胶囊。 + #[tokio::test] + async fn less_computer_press_emits_visible_capsule() { + let _guard = ENV_LOCK.lock().await; + std::env::set_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN", "1"); + + let coordinator = Coordinator::new(); + { + let mut prefs = coordinator.inner.prefs.get(); + prefs.coding_agent_enabled = true; + coordinator.inner.prefs.set(prefs).unwrap(); + } + // 前置:还没弹过任何胶囊。 + assert!(coordinator.inner.last_capsule_state.lock().is_none()); + + // 等价于「按下 Less Computer 键」:bridge_loop 收到 Pressed 后就是调这个 handler。 + super::handle_less_computer_pressed(&coordinator.inner).await; + + assert_eq!( + *coordinator.inner.last_capsule_state.lock(), + Some(CapsuleState::Recording), + "按下 Less Computer 键必须进入录音并弹出可见胶囊" + ); + std::env::remove_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN"); + } + + #[tokio::test] + async fn begin_session_dry_run_enters_listening_and_clears_stale_edges() { + let _guard = ENV_LOCK.lock().await; + std::env::set_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN", "1"); + + let coordinator = Coordinator::new(); + let old_session_id = coordinator.inner.state.lock().session_id; + { + let mut state = coordinator.inner.state.lock(); + state.pending_stop = true; + state.cancelled = true; + } + + coordinator.start_dictation().await.unwrap(); + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, SessionPhase::Listening); + assert!(!state.pending_stop); + assert!(!state.cancelled); + assert_ne!(state.session_id, old_session_id); + + std::env::remove_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN"); + } + + #[tokio::test] + async fn begin_session_ignores_non_idle_phase() { + let _guard = ENV_LOCK.lock().await; + std::env::set_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN", "1"); + + let coordinator = Coordinator::new(); + let old_session_id = { + let mut state = coordinator.inner.state.lock(); + state.phase = SessionPhase::Processing; + state.session_id = session_id(99); + state.session_id + }; + + coordinator.start_dictation().await.unwrap(); + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, SessionPhase::Processing); + assert_eq!(state.session_id, old_session_id); + + std::env::remove_var("OPENLESS_HOTKEY_INJECTION_DRY_RUN"); + } + + #[test] + fn window_key_matcher_mirrors_windows_trigger_aliases() { + let cases = [ + (HotkeyTrigger::RightControl, "Control", "ControlRight"), + (HotkeyTrigger::LeftControl, "Control", "ControlLeft"), + (HotkeyTrigger::RightOption, "Alt", "AltRight"), + (HotkeyTrigger::RightAlt, "AltGraph", "AltRight"), + (HotkeyTrigger::RightCommand, "Meta", "MetaRight"), + (HotkeyTrigger::LeftOption, "Alt", "AltLeft"), + // Mirrors Windows trigger_to_vk_code aliases. + (HotkeyTrigger::Fn, "Control", "ControlRight"), + ]; + for (trigger, key, code) in cases { + assert!( + window_key_matches_trigger(trigger, key, code), + "{trigger:?} should match {key}/{code}" + ); + } + + assert!(!window_key_matches_trigger( + HotkeyTrigger::RightControl, + "Control", + "ControlLeft" + )); + assert!(!window_key_matches_trigger( + HotkeyTrigger::LeftOption, + "Alt", + "AltRight" + )); + assert!(!window_key_matches_trigger(HotkeyTrigger::Fn, "Fn", "Fn")); + } + + #[test] + fn windows_local_providers_are_keyless_and_not_whisper_compatible() { + #[cfg(target_os = "windows")] + assert!(is_keyless_local_asr_provider( + crate::asr::local::foundry::PROVIDER_ID + )); + #[cfg(target_os = "windows")] + assert!(is_keyless_local_asr_provider( + crate::asr::local::sherpa::PROVIDER_ID + )); + #[cfg(not(target_os = "windows"))] + assert!(!is_keyless_local_asr_provider( + crate::asr::local::foundry::PROVIDER_ID + )); + #[cfg(not(target_os = "windows"))] + assert!(!is_keyless_local_asr_provider( + crate::asr::local::sherpa::PROVIDER_ID + )); + assert!(!is_whisper_compatible_provider( + crate::asr::local::foundry::PROVIDER_ID + )); + assert!(!is_whisper_compatible_provider( + crate::asr::local::sherpa::PROVIDER_ID + )); + assert!(!is_whisper_compatible_provider( + crate::asr::mimo::PROVIDER_ID + )); + } + + #[test] + fn verbose_json_enabled_only_for_whisper_family() { + // verbose_json + 幻听过滤只对返回完整 Whisper 指标的 provider 开启。 + assert!(whisper_supports_verbose_json("whisper")); + assert!(whisper_supports_verbose_json("groq")); + // SiliconFlow(SenseVoice/TeleSpeech) / Zhipu(GLM-ASR) 保持旧的 json 行为。 + assert!(!whisper_supports_verbose_json("siliconflow")); + assert!(!whisper_supports_verbose_json("zhipu")); + } + + #[test] + fn openrouter_is_whisper_compatible_json_provider() { + use crate::asr::whisper::AsrRequestFormat; + // issue #582:OpenRouter 走 whisper 兼容路由,但请求体是 JSON+base64。 + assert!(is_whisper_compatible_provider("openrouter")); + assert_eq!( + whisper_request_format("openrouter"), + AsrRequestFormat::OpenRouterJson + ); + // 其余兼容厂商保持 multipart。 + assert_eq!( + whisper_request_format("whisper"), + AsrRequestFormat::Multipart + ); + assert_eq!(whisper_request_format("groq"), AsrRequestFormat::Multipart); + // OpenRouter 的 JSON 协议不吃 response_format,verbose_json 保持关闭。 + assert!(!whisper_supports_verbose_json("openrouter")); + // base64 膨胀,长录音保守按 30s 切分。 + assert_eq!(batch_asr_chunk_limit_ms("openrouter"), Some(30_000)); + } + + #[test] + fn qa_asr_provider_kind_tracks_active_provider() { + assert_eq!( + active_asr_provider_kind(crate::asr::bailian::PROVIDER_ID), + ActiveAsrProviderKind::Bailian + ); + assert_eq!( + active_asr_provider_kind("whisper"), + ActiveAsrProviderKind::WhisperCompatible + ); + assert_eq!( + active_asr_provider_kind(crate::asr::mimo::PROVIDER_ID), + ActiveAsrProviderKind::Mimo + ); + assert_eq!( + active_asr_provider_kind("volcengine"), + ActiveAsrProviderKind::Volcengine + ); + } + + #[cfg(target_os = "windows")] + #[test] + fn coordinator_shares_app_foundry_runtime() { + let runtime = Arc::new(crate::asr::local::FoundryLocalRuntime::new()); + let coordinator = Coordinator::new_with_foundry_runtime(Arc::clone(&runtime)); + + assert!(Arc::ptr_eq( + &runtime, + &coordinator.inner.foundry_local_runtime + )); + } + + #[cfg(target_os = "windows")] + #[test] + fn foundry_transcribe_skips_global_timeout_for_first_run_provisioning() { + let provider = Arc::new(crate::asr::local::FoundryLocalWhisperAsr::new( + Arc::new(crate::asr::local::FoundryLocalRuntime::new()), + crate::asr::local::foundry::DEFAULT_MODEL_ALIAS.to_string(), + "auto".to_string(), + None, + )); + let active_asr = ActiveAsr::FoundryLocalWhisper(provider); + + assert!(!asr_transcribe_uses_global_timeout(&active_asr)); + } + + #[cfg(target_os = "windows")] + #[test] + fn foundry_audio_transcribe_timeout_is_separate_from_prepare() { + let timeout = foundry_audio_transcribe_timeout_duration(); + + assert_eq!( + timeout, + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) + ); + } + + #[test] + fn local_qwen_timeout_floors_at_global_timeout_for_short_audio() { + // 5s 录音:5 × 0.6 = 3, +10 = 13, max(15) = 15。短录音保留 15s 兜底。 + assert_eq!( + local_qwen_transcribe_timeout(5.0), + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) + ); + } + + #[test] + fn local_qwen_timeout_scales_with_audio_duration() { + // 60s 录音:60 × 0.6 = 36, +10 = 46s。覆盖 RTF ≈ 0.5 的边界。 + assert_eq!( + local_qwen_transcribe_timeout(60.0), + std::time::Duration::from_secs(46) + ); + } + + #[test] + fn local_qwen_timeout_ceils_partial_seconds() { + // 10.1s 录音:10.1 × 0.6 = 6.06, ceil = 7, +10 = 17, max(15) = 17。 + assert_eq!( + local_qwen_transcribe_timeout(10.1), + std::time::Duration::from_secs(17) + ); + } + + #[test] + fn local_qwen_timeout_handles_zero_duration() { + // 0 时长(空 buffer 边界):0 × 0.6 = 0, +10 = 10, max(15) = 15。 + assert_eq!( + local_qwen_transcribe_timeout(0.0), + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) + ); + } + + #[cfg(target_os = "windows")] + #[test] + fn foundry_release_uses_foundry_keep_loaded_preference() { + let runtime = Arc::new(crate::asr::local::FoundryLocalRuntime::new()); + let coordinator = Coordinator::new_with_foundry_runtime(runtime); + let mut prefs = coordinator.inner.prefs.get(); + prefs.local_asr_keep_loaded_secs = 3; + prefs.foundry_local_asr_keep_loaded_secs = 7; + coordinator.inner.prefs.set(prefs).unwrap(); + + assert_eq!(foundry_local_asr_release_keep_secs(&coordinator.inner), 7); + } + + #[cfg(target_os = "windows")] + #[test] + fn foundry_release_guard_rejects_stale_dictation_session() { + let runtime = Arc::new(crate::asr::local::FoundryLocalRuntime::new()); + let coordinator = Coordinator::new_with_foundry_runtime(runtime); + let old_session_id = coordinator.inner.state.lock().session_id; + + assert!(asr_release_session_is_current( + &coordinator.inner, + AsrReleaseSession::Dictation(old_session_id) + )); + + coordinator.inner.state.lock().session_id = new_session_id(); + + assert!(!asr_release_session_is_current( + &coordinator.inner, + AsrReleaseSession::Dictation(old_session_id) + )); + } + + #[cfg(target_os = "windows")] + #[test] + fn local_asr_release_guard_rejects_stale_qa_session() { + let runtime = Arc::new(crate::asr::local::FoundryLocalRuntime::new()); + let coordinator = Coordinator::new_with_foundry_runtime(runtime); + let old_session_id = coordinator.inner.qa_state.lock().session_id; + + assert!(asr_release_session_is_current( + &coordinator.inner, + AsrReleaseSession::Qa(old_session_id) + )); + + coordinator.inner.qa_state.lock().session_id = new_session_id(); + + assert!(!asr_release_session_is_current( + &coordinator.inner, + AsrReleaseSession::Qa(old_session_id) + )); + } + + #[test] + fn resolve_ark_endpoint_rejects_blank_key_without_custom_endpoint() { + assert_eq!( + resolve_ark_endpoint_with_policy("", None) + .unwrap_err() + .to_string(), + "API Key 为空" + ); + } + + #[test] + fn resolve_ark_endpoint_allows_blank_key_with_custom_endpoint() { + let endpoint = resolve_ark_endpoint_with_policy( + "", + Some("https://example.com/v1/chat/completions".to_string()), + ) + .unwrap(); + assert_eq!(endpoint, "https://example.com/v1/chat/completions"); + } + + #[test] + fn deferred_asr_bridge_flushes_startup_audio_before_live_chunks() { + #[derive(Default)] + struct RecordingConsumer { + bytes: Mutex>, + } + + impl crate::asr::AudioConsumer for RecordingConsumer { + fn consume_pcm_chunk(&self, pcm: &[u8]) { + self.bytes.lock().extend_from_slice(pcm); + } + } + + let bridge = DeferredAsrBridge::new(); + crate::recorder::AudioConsumer::consume_pcm_chunk(&bridge, &[1, 2]); + crate::recorder::AudioConsumer::consume_pcm_chunk(&bridge, &[3, 4]); + + let target = Arc::new(RecordingConsumer::default()); + let target_for_attach: Arc = target.clone(); + assert_eq!(bridge.attach(target_for_attach), 4); + + crate::recorder::AudioConsumer::consume_pcm_chunk(&bridge, &[5, 6]); + assert_eq!(&*target.bytes.lock(), &[1, 2, 3, 4, 5, 6]); + } + + #[tokio::test] + async fn manual_stop_during_starting_is_queued() { + let coordinator = Coordinator::new(); + { + let mut state = coordinator.inner.state.lock(); + state.phase = SessionPhase::Starting; + state.pending_stop = false; + } + + coordinator.stop_dictation().await.unwrap(); + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, SessionPhase::Starting); + assert!(state.pending_stop); + } + + #[tokio::test] + async fn stop_dictation_from_listening_without_asr_returns_idle() { + let coordinator = Coordinator::new(); + { + let mut state = coordinator.inner.state.lock(); + state.phase = SessionPhase::Listening; + state.session_id = session_id(123); + } + + coordinator.stop_dictation().await.unwrap(); + + assert_eq!(coordinator.inner.state.lock().phase, SessionPhase::Idle); + } + + #[test] + fn cancel_session_state_machine_is_table_driven() { + let cases = [ + (SessionPhase::Idle, SessionPhase::Idle, false), + (SessionPhase::Starting, SessionPhase::Idle, true), + (SessionPhase::Listening, SessionPhase::Idle, true), + (SessionPhase::Processing, SessionPhase::Processing, true), + (SessionPhase::Inserting, SessionPhase::Inserting, false), + ]; + + for (initial, expected_phase, expected_cancelled) in cases { + let coordinator = Coordinator::new(); + { + let mut state = coordinator.inner.state.lock(); + state.phase = initial; + state.cancelled = false; + state.focus_target = Some(1); + } + + coordinator.cancel_dictation(); + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, expected_phase, "initial={initial:?}"); + assert_eq!(state.cancelled, expected_cancelled, "initial={initial:?}"); + if matches!(initial, SessionPhase::Starting | SessionPhase::Listening) { + assert!(state.focus_target.is_none(), "initial={initial:?}"); + } + } + } + + #[test] + fn recorder_runtime_error_aborts_active_session() { + let coordinator = Coordinator::new(); + { + let mut state = coordinator.inner.state.lock(); + state.phase = SessionPhase::Listening; + state.cancelled = false; + } + + abort_recording_with_error(&coordinator.inner, "录音中断: stream failed".to_string()); + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, SessionPhase::Idle); + assert!(state.cancelled); + assert!(coordinator.inner.recorder.lock().is_none()); + assert!(coordinator.inner.asr.lock().is_none()); + } + + #[test] + fn abort_recording_keeps_session_non_idle_until_restore_can_run() { + let mut state = SessionState::default(); + state.phase = SessionPhase::Listening; + state.cancelled = false; + state.session_id = session_id(7); + + let abort = begin_recording_abort_before_restore(&mut state).unwrap(); + + assert_eq!(abort.session_id, session_id(7)); + assert!(state.cancelled); + assert_eq!(state.phase, SessionPhase::Listening); + + publish_abort_idle_after_restore(&mut state, abort.session_id); + + assert_eq!(state.phase, SessionPhase::Idle); + } + + #[tokio::test] + async fn pressed_edge_during_inserting_does_not_start_new_session() { + let coordinator = Coordinator::new(); + { + let mut state = coordinator.inner.state.lock(); + state.phase = SessionPhase::Inserting; + state.session_id = session_id(41); + } + + handle_pressed_edge(&coordinator.inner).await; + + let state = coordinator.inner.state.lock(); + assert_eq!(state.phase, SessionPhase::Inserting); + assert_eq!(state.session_id, session_id(41)); + } + + #[tokio::test] + async fn repeated_pressed_edge_during_hold_session_does_not_restart() { + let coordinator = Coordinator::new(); + coordinator + .inner + .prefs + .set(crate::types::UserPreferences { + hotkey: crate::types::HotkeyBinding { + trigger: HotkeyTrigger::RightControl, + mode: HotkeyMode::Hold, + keys: None, + }, + ..Default::default() + }) + .unwrap(); + coordinator.inner.state.lock().phase = SessionPhase::Listening; + coordinator + .inner + .hotkey_trigger_held + .store(true, Ordering::SeqCst); + + handle_pressed_edge(&coordinator.inner).await; + + assert_eq!( + coordinator.inner.state.lock().phase, + SessionPhase::Listening + ); + assert!(coordinator.inner.hotkey_trigger_held.load(Ordering::SeqCst)); + } + + #[test] + fn enabling_shortcut_recording_clears_dictation_hold_latch() { + let coordinator = Coordinator::new(); + coordinator + .inner + .hotkey_trigger_held + .store(true, Ordering::SeqCst); + + coordinator.set_shortcut_recording_active(true); + + assert!(!coordinator.inner.hotkey_trigger_held.load(Ordering::SeqCst)); + } + + #[test] + fn window_hotkey_fallback_is_disabled_when_no_explicit_fallback_is_advertised() { + assert_eq!( + window_hotkey_fallback_enabled(), + crate::types::HotkeyCapability::current().explicit_fallback_available + ); + } + + #[test] + fn capsule_show_strategy_matches_platform_activation_contract() { + // 平台列表必须与 capsule_show_strategy_for_platform 的 cfg 完全一致: + // 改实现里的 #[cfg] 时,一并改这两个 #[cfg],否则 Linux CI 直接红 + // (fcitx5 PR #451 把 Linux 加进 NoActivate 但漏改本测试,CI 失败)。 + #[cfg(any(target_os = "macos", target_os = "windows"))] + assert_eq!( + capsule_show_strategy_for_platform(), + CapsuleShowStrategy::NoActivate + ); + + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + assert_eq!( + capsule_show_strategy_for_platform(), + CapsuleShowStrategy::FallbackShow + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn prepared_windows_ime_slot_is_taken_only_for_matching_session() { + let mut slots = vec![PreparedWindowsImeSessionSlot { + session_id: session_id(2), + prepared: PreparedWindowsImeSession::unavailable(), + }]; + + assert!(take_matching_prepared_windows_ime_session(&mut slots, session_id(1)).is_none()); + assert_eq!( + slots.iter().map(|slot| slot.session_id).collect::>(), + vec![session_id(2)] + ); + + assert!(take_matching_prepared_windows_ime_session(&mut slots, session_id(2)).is_some()); + assert!(slots.is_empty()); + } + + #[test] + #[cfg(target_os = "windows")] + fn prepared_windows_ime_sessions_keep_overlapping_snapshots() { + let mut slots = Vec::new(); + store_prepared_windows_ime_session( + &mut slots, + session_id(1), + PreparedWindowsImeSession::unavailable(), + ); + store_prepared_windows_ime_session( + &mut slots, + session_id(2), + PreparedWindowsImeSession::unavailable(), + ); + + assert_eq!( + slots.iter().map(|slot| slot.session_id).collect::>(), + vec![session_id(1), session_id(2)] + ); + + assert!(take_matching_prepared_windows_ime_session(&mut slots, session_id(1)).is_some()); + assert_eq!( + slots.iter().map(|slot| slot.session_id).collect::>(), + vec![session_id(2)] + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn stale_prepared_windows_ime_restore_discards_old_snapshot_without_restoring() { + let mut slots = Vec::new(); + store_prepared_windows_ime_session( + &mut slots, + session_id(1), + PreparedWindowsImeSession::unavailable(), + ); + store_prepared_windows_ime_session( + &mut slots, + session_id(2), + PreparedWindowsImeSession::unavailable(), + ); + + assert!(take_current_prepared_windows_ime_session_for_restore( + &mut slots, + session_id(1), + session_id(2) + ) + .is_none()); + assert_eq!( + slots.iter().map(|slot| slot.session_id).collect::>(), + vec![session_id(2)] + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn non_tsf_insertion_fallback_gate_blocks_only_when_disabled() { + assert!(should_try_non_tsf_insertion_fallback( + true, + InsertStatus::CopiedFallback + )); + assert!(should_try_non_tsf_insertion_fallback( + true, + InsertStatus::Failed + )); + assert!(!should_try_non_tsf_insertion_fallback( + true, + InsertStatus::Inserted + )); + assert!(!should_try_non_tsf_insertion_fallback( + false, + InsertStatus::CopiedFallback + )); + assert!(!should_try_non_tsf_insertion_fallback( + false, + InsertStatus::Failed + )); + } + + #[test] + fn focus_restore_failure_uses_specific_error_code_when_insert_fails() { + assert_eq!( + dictation_error_code(InsertStatus::Failed, false, false, false), + Some("focusRestoreFailed") + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn missing_windows_hwnd_is_not_present() { + use windows::Win32::Foundation::HWND; + + assert!(!windows_hwnd_is_present(HWND::default())); + } + + #[test] + #[cfg(target_os = "windows")] + fn tsf_required_failure_keeps_tsf_error_when_focus_was_ready() { + assert_eq!( + dictation_error_code(InsertStatus::Failed, false, true, false), + Some("windowsImeTsfRequired") + ); + } + + #[test] + fn startup_race_check_treats_newer_session_as_stale() { + let mut state = SessionState::default(); + state.phase = SessionPhase::Starting; + state.cancelled = false; + state.session_id = session_id(2); + + assert_eq!( + startup_race_status(&state, session_id(1)), + StartupRaceStatus::StaleContinuation + ); + } + + #[test] + fn startup_race_check_is_table_driven_for_begin_session_edges() { + let cases = [ + ( + SessionPhase::Starting, + false, + session_id(7), + StartupRaceStatus::ActiveStarting, + ), + ( + SessionPhase::Starting, + true, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Idle, + false, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Listening, + false, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Starting, + false, + session_id(8), + StartupRaceStatus::StaleContinuation, + ), + ]; + + for (phase, cancelled, actual_session_id, expected) in cases { + let mut state = SessionState::default(); + state.phase = phase; + state.cancelled = cancelled; + state.session_id = actual_session_id; + + assert_eq!( + startup_race_status(&state, session_id(7)), + expected, + "phase={phase:?} cancelled={cancelled} actual_session={actual_session_id}" + ); + } + } + + #[test] + fn begin_recording_abort_is_noop_after_prior_cancel_or_idle() { + let cases = [ + (SessionPhase::Idle, false), + (SessionPhase::Processing, false), + (SessionPhase::Listening, true), + ]; + + for (phase, cancelled) in cases { + let mut state = SessionState::default(); + state.phase = phase; + state.cancelled = cancelled; + + assert!(begin_recording_abort_before_restore(&mut state).is_none()); + assert_eq!(state.phase, phase); + assert_eq!(state.cancelled, cancelled); + } + } + + #[test] + fn stale_startup_cleanup_keeps_newer_asr_resource() { + let coordinator = Coordinator::new(); + let newer_asr = Arc::new(WhisperBatchASR::new( + "key".to_string(), + "http://localhost".to_string(), + "model".to_string(), + None, + None, + false, + )); + *coordinator.inner.asr.lock() = Some(SessionResource::new( + session_id(2), + ActiveAsr::Whisper(Arc::clone(&newer_asr)), + )); + + discard_startup_resources_for_session(&coordinator.inner, session_id(1)); + + assert_eq!( + coordinator + .inner + .asr + .lock() + .as_ref() + .map(|resource| resource.session_id), + Some(session_id(2)) + ); + + discard_startup_resources_for_session(&coordinator.inner, session_id(2)); + + assert!(coordinator.inner.asr.lock().is_none()); + } +} + +fn enabled_phrases(inner: &Arc) -> Vec { + inner + .vocab + .list() + .unwrap_or_default() + .into_iter() + .filter(|e| e.enabled) + .map(|e| e.phrase) + .collect() +} + +/// 终止态(Done / Cancelled / Error)后延迟 N ms 把胶囊改回 Idle,让浮窗自动消失。 +/// 用户点 ✕ / ✓ / 中途出错 / 按 Esc 都走这里,统一 2 秒。 +const CAPSULE_AUTO_HIDE_DELAY_MS: u64 = 2000; + +/// Toggle 模式下,end_session 将 phase 设为 Idle 后在此时间内禁止新的 begin_session。 +/// 避免用户三连按时第 3 次按下误激活新听写(此时胶囊仍在离场动画周期内)。 +/// 值取 capsule EXIT_ANIM_MS (360ms) + 余量 ≈ 600ms。 +const POST_SESSION_COOLDOWN_MS: u64 = 600; + +/// Coordinator 全局超时保护:防止 ASR await_final_result() 永远挂起。 +/// 设置为 15 秒(比 ASR 的 12 秒 FINAL_RESULT_TIMEOUT 稍长), +/// 只在 ASR 超时机制失效时作为最后的防线触发。 +const COORDINATOR_GLOBAL_TIMEOUT_SECS: u64 = 15; + +#[cfg(target_os = "windows")] +fn foundry_audio_transcribe_timeout_duration() -> std::time::Duration { + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) +} + +/// 本地 Qwen3-ASR 的动态转写超时。固定 15 秒在长录音(≥ 30s)+ 慢机器 +/// (RTF ≈ 0.3–0.5)上必然超时把整段内容丢掉。改用 max(15, ceil(audio_s +/// × 0.6) + 10):基础保留 15s 兜住短录音;长录音按音频长度的 0.6 倍 + +/// 10s 余量,覆盖 RTF ≤ 0.5 的机器。 +fn local_qwen_transcribe_timeout(audio_secs: f64) -> std::time::Duration { + let secs = ((audio_secs * 0.6).ceil() as u64) + .saturating_add(10) + .max(COORDINATOR_GLOBAL_TIMEOUT_SECS); + std::time::Duration::from_secs(secs) +} + +/// sherpa-onnx offline batch 暂与 Foundry 同档;后续按 Windows 真机 CPU/模型 +/// 实测结果再调整。 +#[cfg(target_os = "windows")] +fn sherpa_audio_transcribe_timeout_duration() -> std::time::Duration { + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) +} + +pub(crate) fn validate_llm_endpoint(raw: &str) -> anyhow::Result<()> { + use std::net::IpAddr; + + let url = + url::Url::parse(raw).map_err(|e| anyhow::anyhow!("LLM endpoint 不是合法 URL:{e}"))?; + let host = url + .host_str() + .ok_or_else(|| anyhow::anyhow!("LLM endpoint 缺少主机名"))? + .to_ascii_lowercase(); + + const METADATA_HOSTS: [&str; 2] = ["metadata.google.internal", "169.254.169.254"]; + if METADATA_HOSTS.iter().any(|m| host.contains(m)) { + anyhow::bail!("LLM endpoint 指向云元数据服务,已拒绝:{host}"); + } + + let scheme = url.scheme(); + let bare_host = host + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + .unwrap_or(host.as_str()); + + let Ok(ip) = bare_host.parse::() else { + if bare_host == "localhost" { + return Ok(()); + } + if scheme != "https" { + anyhow::bail!("LLM endpoint 必须使用 https(仅 localhost / 局域网允许 http):{raw}"); + } + return Ok(()); + }; + + let canonical = match ip { + IpAddr::V6(v6) => v6.to_ipv4_mapped().map(IpAddr::V4).unwrap_or(ip), + v4 => v4, + }; + + let is_lan = match canonical { + IpAddr::V4(v4) => ip_v4_is_lan(v4), + IpAddr::V6(v6) => ip_v6_is_lan(v6), + }; + if is_lan { + return Ok(()); + } + + let is_blocked = match canonical { + IpAddr::V4(v4) => ip_v4_is_blocked(v4), + IpAddr::V6(v6) => ip_v6_is_blocked(v6), + }; + if is_blocked { + anyhow::bail!("LLM endpoint 指向保留/危险地址,已拒绝(防 SSRF):{ip}"); + } + + if scheme != "https" { + anyhow::bail!("LLM endpoint 必须使用 https(仅 localhost / 局域网允许 http):{raw}"); + } + + Ok(()) +} + +fn ip_v4_is_lan(ip: std::net::Ipv4Addr) -> bool { + ip.is_loopback() || ip.is_private() +} + +fn ip_v4_is_blocked(ip: std::net::Ipv4Addr) -> bool { + let octets = ip.octets(); + let is_cgnat = octets[0] == 100 && (64..=127).contains(&octets[1]); + ip.is_link_local() || ip.is_unspecified() || ip.is_broadcast() || is_cgnat +} + +fn ip_v6_is_lan(ip: std::net::Ipv6Addr) -> bool { + let segs = ip.segments(); + let is_ula = (segs[0] & 0xfe00) == 0xfc00; + ip.is_loopback() || is_ula +} + +fn ip_v6_is_blocked(ip: std::net::Ipv6Addr) -> bool { + let segs = ip.segments(); + let is_link_local = (segs[0] & 0xffc0) == 0xfe80; + ip.is_unspecified() || is_link_local +} + +/// 检查 begin_session 的 await 间隙是否被 cancel_session 打断。 +/// 必须在持有 state lock 的瞬间读,结果一拿就过期,所以用 helper 名字提醒只在 +/// 「准备做下一步副作用前」用。 +fn startup_race_status_for_starting( + inner: &Arc, + captured_session_id: SessionId, +) -> StartupRaceStatus { + let state = inner.state.lock(); + startup_race_status(&state, captured_session_id) +} + +fn set_phase_idle_if_session_matches(inner: &Arc, session_id: SessionId) { + let mut state = inner.state.lock(); + if state.session_id == session_id { + state.phase = SessionPhase::Idle; + } +} + +fn schedule_capsule_idle(inner: &Arc, delay_ms: u64) { + let inner_clone = Arc::clone(inner); + async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await; + // 必须 dictation **和** QA 同时空闲才能隐藏胶囊。否则旧 dictation Done timer + // 的尾巴会在新 QA 录音/思考中把胶囊意外收掉(issue #118 v2 复现)。 + let dictation_idle = inner_clone.state.lock().phase == SessionPhase::Idle; + let qa_idle = inner_clone.qa_state.lock().phase == QaPhase::Idle; + if dictation_idle && qa_idle { + emit_capsule(&inner_clone, CapsuleState::Idle, 0.0, 0, None, None); + } + }); +} + +/// 与 capture_focus_target 类似,但前台窗口属于本进程(即用户停在 QA / capsule / main +/// 等自家窗口)时返回 None,让 caller 区分"用户没切到别处" vs "用户切到了另一个真正的 +/// 外部 app"。issue #466 多轮场景下用来刷新 qa_focus_target。 +#[cfg(target_os = "windows")] +fn capture_external_focus_target() -> Option { + use windows::Win32::System::Threading::GetCurrentProcessId; + use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowThreadProcessId}; + + unsafe { + let hwnd = GetForegroundWindow(); + if hwnd.0.is_null() { + return None; + } + let mut pid: u32 = 0; + GetWindowThreadProcessId(hwnd, Some(&mut pid)); + if pid == GetCurrentProcessId() { + return None; + } + Some(hwnd.0 as usize) + } +} + +#[cfg(not(target_os = "windows"))] +fn capture_external_focus_target() -> Option { + None +} + +#[cfg(target_os = "windows")] +fn capture_focus_target() -> Option { + use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; + + let foreground = unsafe { GetForegroundWindow() }; + if foreground.0.is_null() { + None + } else { + Some(foreground.0 as usize) + } +} + +#[cfg(not(target_os = "windows"))] +fn capture_focus_target() -> Option { + None +} + +/// 捕获用户开始 dictation 时的前台 app 标签("localizedName (bundle.id)"),用作 LLM +/// polish/translate 的上下文前提,让模型按 app 调风格。详见 issue #116。 +/// +/// macOS 走 NSWorkspace.frontmostApplication(公开 API,无需额外权限); +/// Windows 复用前台 HWND 拿窗口标题;Linux/其他平台返回 None。 +#[cfg(target_os = "macos")] +fn capture_frontmost_app() -> Option { + use objc2::msg_send; + use objc2::runtime::{AnyClass, AnyObject}; + + unsafe { + let cls = AnyClass::get("NSWorkspace")?; + let workspace: *mut AnyObject = msg_send![cls, sharedWorkspace]; + if workspace.is_null() { + return None; + } + let app: *mut AnyObject = msg_send![workspace, frontmostApplication]; + if app.is_null() { + return None; + } + let name_obj: *mut AnyObject = msg_send![app, localizedName]; + let bundle_obj: *mut AnyObject = msg_send![app, bundleIdentifier]; + let name = nsstring_to_string(name_obj); + let bundle = nsstring_to_string(bundle_obj); + match (name, bundle) { + (Some(n), Some(b)) => Some(format!("{n} ({b})")), + (Some(n), None) => Some(n), + (None, Some(b)) => Some(b), + (None, None) => None, + } + } +} + +#[cfg(target_os = "macos")] +unsafe fn nsstring_to_string(ns_string: *mut objc2::runtime::AnyObject) -> Option { + use objc2::msg_send; + if ns_string.is_null() { + return None; + } + let utf8: *const std::os::raw::c_char = unsafe { msg_send![ns_string, UTF8String] }; + if utf8.is_null() { + return None; + } + let cstr = unsafe { std::ffi::CStr::from_ptr(utf8) }; + let s = cstr.to_string_lossy().into_owned(); + if s.is_empty() { + None + } else { + Some(s) + } +} + +#[cfg(target_os = "windows")] +fn capture_frontmost_app() -> Option { + use windows::Win32::UI::WindowsAndMessaging::{ + GetForegroundWindow, GetWindowTextLengthW, GetWindowTextW, + }; + + unsafe { + let hwnd = GetForegroundWindow(); + if hwnd.0.is_null() { + return None; + } + let len = GetWindowTextLengthW(hwnd); + if len <= 0 { + return None; + } + let mut buf = vec![0u16; (len + 1) as usize]; + let copied = GetWindowTextW(hwnd, &mut buf); + if copied <= 0 { + return None; + } + let title = String::from_utf16_lossy(&buf[..copied as usize]); + if title.is_empty() { + None + } else { + Some(title) + } + } +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +fn capture_frontmost_app() -> Option { + None +} + +#[cfg(target_os = "windows")] +fn restore_focus_target_if_possible(target: Option) -> bool { + use std::ffi::c_void; + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::{ + GetForegroundWindow, IsIconic, IsWindow, SetForegroundWindow, ShowWindow, SW_RESTORE, + }; + + let Some(raw_target) = target else { + log::warn!("[coord] no original Windows insertion target captured"); + return false; + }; + let hwnd = HWND(raw_target as *mut c_void); + if hwnd.0.is_null() { + return false; + } + if !unsafe { IsWindow(hwnd).as_bool() } { + log::warn!("[coord] original Windows insertion target is no longer a valid window"); + return false; + } + + let foreground = unsafe { GetForegroundWindow() }; + if foreground == hwnd { + return true; + } + + if unsafe { IsIconic(hwnd).as_bool() } { + let _ = unsafe { ShowWindow(hwnd, SW_RESTORE) }; + } + let _ = unsafe { SetForegroundWindow(hwnd) }; + std::thread::sleep(std::time::Duration::from_millis(60)); + + let foreground = unsafe { GetForegroundWindow() }; + if foreground != hwnd { + log::warn!("[coord] failed to restore original Windows insertion target before paste"); + return false; + } + true +} + +#[cfg(not(target_os = "windows"))] +fn restore_focus_target_if_possible(_target: Option) -> bool { + true +} + +#[cfg(target_os = "windows")] +fn windows_hwnd_is_present(hwnd: windows::Win32::Foundation::HWND) -> bool { + hwnd != windows::Win32::Foundation::HWND::default() +} + +#[cfg(target_os = "windows")] +fn capture_ime_submit_target() -> Option { + use windows::Win32::UI::WindowsAndMessaging::{ + GetForegroundWindow, GetGUIThreadInfo, GetWindowThreadProcessId, GUITHREADINFO, + }; + + let foreground = unsafe { GetForegroundWindow() }; + if !windows_hwnd_is_present(foreground) { + return None; + } + + let mut foreground_process_id = 0; + let foreground_thread_id = + unsafe { GetWindowThreadProcessId(foreground, Some(&mut foreground_process_id)) }; + if foreground_thread_id == 0 { + return None; + } + + let mut gui_info = GUITHREADINFO { + cbSize: std::mem::size_of::() as u32, + ..Default::default() + }; + let target_window = if unsafe { GetGUIThreadInfo(foreground_thread_id, &mut gui_info).is_ok() } + && windows_hwnd_is_present(gui_info.hwndFocus) + { + gui_info.hwndFocus + } else { + foreground + }; + + let mut process_id = 0; + let thread_id = unsafe { GetWindowThreadProcessId(target_window, Some(&mut process_id)) }; + if process_id == 0 || thread_id == 0 { + return None; + } + + Some(ImeSubmitTarget { + process_id, + thread_id, + }) +} + +// Windows topmost overlay 的已知 OS 级限制(issue #457): +// `SetWindowPos(HWND_TOPMOST)` 让 capsule 在普通桌面合成、最大化窗口、borderless +// windowed fullscreen 上正常叠加;但**对独占全屏(exclusive fullscreen)DirectX / +// OpenGL 应用无效** —— 那条路径绕过桌面合成器,标准 topmost 窗口不参与合成 → +// 用户看不见 capsule。这是 OS 层面的限制,用户空间无法绕过(除非接入 DirectX +// overlay,工程量与风险都不在 surgical 修复范围内)。 +// +// 用户侧 workaround:把游戏切到 borderless windowed fullscreen(Minecraft Java 默认 +// 即是;F11 在不同版本表现不一致,按设置里的「全屏」选项决定)。 +// +// 相关 UIPI 限制:若游戏以管理员身份运行而 OpenLess 不是,`WH_KEYBOARD_LL` 收不到 +// 游戏的按键 → hotkey 完全不触发。这里跟 SetWindowPos 路径无关,但同源不可绕过。 +#[cfg(target_os = "windows")] +fn show_capsule_window_no_activate( + _app: &AppHandle, + window: &tauri::WebviewWindow, +) -> bool { + use raw_window_handle::{HasWindowHandle, RawWindowHandle}; + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::{ + SetWindowPos, ShowWindow, HWND_TOPMOST, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, + SWP_SHOWWINDOW, SW_SHOWNOACTIVATE, + }; + + let Ok(handle) = window.window_handle() else { + // #470 诊断 v2:Win32 show 路径最可能的暗点之一。此前静默 return, + // 无法观测「胶囊完全不显示」是否卡在这里。 + log::warn!( + "[capsule] no_activate failed: window_handle() unavailable — Win32 show skipped" + ); + return false; + }; + let RawWindowHandle::Win32(raw) = handle.as_raw() else { + log::warn!("[capsule] no_activate failed: non-Win32 RawWindowHandle — Win32 show skipped"); + return false; + }; + let hwnd = HWND(raw.hwnd.get() as *mut _); + + let _ = unsafe { ShowWindow(hwnd, SW_SHOWNOACTIVATE) }; + let _ = unsafe { + SetWindowPos( + hwnd, + HWND_TOPMOST, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW, + ) + }; + true +} + +#[cfg(target_os = "macos")] +fn show_capsule_window_no_activate( + _app: &AppHandle, + window: &tauri::WebviewWindow, +) -> bool { + use objc2::msg_send; + use objc2::runtime::AnyObject; + + let Ok(handle) = window.ns_window() else { + return false; + }; + let ns_window = handle as *mut AnyObject; + if ns_window.is_null() { + return false; + } + + // emit_capsule 已经把窗口操作 marshal 到 Tauri 主线程;这里不能再调用 + // window.show()/set_focus()/NSApp.activate,否则 AeroSpace 会把 workspace 切回 + // OpenLess 主窗口所在空间。先让胶囊加入所有 Spaces,再用 + // orderFrontRegardless 做无激活展示。 + if let Err(e) = window.set_visible_on_all_workspaces(true) { + log::warn!("[capsule] set visible on all macOS Spaces failed: {e}"); + } + + unsafe { + const NS_WINDOW_COLLECTION_BEHAVIOR_CAN_JOIN_ALL_SPACES: usize = 1 << 0; + const NS_WINDOW_COLLECTION_BEHAVIOR_FULL_SCREEN_AUXILIARY: usize = 1 << 8; + let behavior: usize = msg_send![ns_window, collectionBehavior]; + let behavior = behavior + | NS_WINDOW_COLLECTION_BEHAVIOR_CAN_JOIN_ALL_SPACES + | NS_WINDOW_COLLECTION_BEHAVIOR_FULL_SCREEN_AUXILIARY; + let _: () = msg_send![ns_window, setCollectionBehavior: behavior]; + let _: () = msg_send![ns_window, orderFrontRegardless]; + } + true +} + +#[cfg(target_os = "linux")] +fn show_capsule_window_no_activate( + _app: &AppHandle, + _window: &tauri::WebviewWindow, +) -> bool { + true +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] +fn show_capsule_window_no_activate( + _app: &AppHandle, + _window: &tauri::WebviewWindow, +) -> bool { + false +} + +#[cfg(target_os = "windows")] +fn hide_capsule_window_if_present() { + use std::iter::once; + use windows::core::PCWSTR; + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::{ + FindWindowW, SetWindowPos, ShowWindow, HWND_NOTOPMOST, SWP_HIDEWINDOW, SWP_NOACTIVATE, + SWP_NOMOVE, SWP_NOSIZE, SW_HIDE, + }; + + let title: Vec = "OpenLess Capsule".encode_utf16().chain(once(0)).collect(); + let hwnd = match unsafe { FindWindowW(PCWSTR::null(), PCWSTR(title.as_ptr())) } { + Ok(hwnd) => hwnd, + Err(_) => return, + }; + if hwnd == HWND::default() || hwnd.0.is_null() { + return; + } + + let _ = unsafe { ShowWindow(hwnd, SW_HIDE) }; + let _ = unsafe { + SetWindowPos( + hwnd, + HWND_NOTOPMOST, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW, + ) + }; +} + +#[cfg(not(target_os = "windows"))] +fn hide_capsule_window_if_present() {} + +fn emit_capsule( + inner: &Arc, + state: CapsuleState, + level: f32, + elapsed_ms: u64, + message: Option, + inserted_chars: Option, +) { + // 在 app 句柄校验之前记录,便于无 GUI 的测试断言「按下热键 → 弹了哪种胶囊」。 + *inner.last_capsule_state.lock() = Some(state); + let app_opt = inner.app.lock().clone(); + let Some(app) = app_opt else { return }; + let translation = inner.translation_modifier_seen.load(Ordering::SeqCst); + let operating = inner.state.lock().voice_agent; + let payload = CapsulePayload { + state, + level, + elapsed_ms, + message, + inserted_chars, + translation, + operating, + }; + + #[cfg(target_os = "android")] + crate::android::notify_capsule_state(&payload); + + // visible / translation 是「这一帧 capsule:state event 的 payload」内容 —— + // 必须在 call-site(即音频线程触发 emit_capsule 时)就算定,否则 main thread + // 闭包里读到的将是「下一帧」的 state,跟实际下发给 JS 的 payload 不一致。 + let visible = !matches!(state, CapsuleState::Idle); + + // Linux: 通过 fcitx5 插件在候选词列表下方显示听写状态,不干扰输入法预编辑。 + // 只在文本变化时调用 DBus,避免录音中 ~30Hz 的音频电平回调重复调用。 + #[cfg(target_os = "linux")] + { + use std::sync::Mutex; + static LAST_AUX: Mutex> = Mutex::new(None); + + let aux = match state { + CapsuleState::Idle => None, + CapsuleState::Recording => Some("🎤 收音中..."), + CapsuleState::Transcribing => Some("🔄 识别中..."), + CapsuleState::Polishing => Some("✨ 润色中..."), + CapsuleState::Done => Some("✅ 已插入"), + CapsuleState::Cancelled => Some("— 已取消"), + CapsuleState::Error => Some("❌ 出错"), + }; + + let mut last = LAST_AUX.lock().unwrap(); + if aux != last.as_deref() { + *last = aux.map(String::from); + // 代数计数器:每次状态变化 +1,retry 线程只在自己代数仍为最新时生效。 + // 避免 Recording→Idle→Recording 快速切换时多个 retry 重复触发。 + static RETRY_GEN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + // fetch_add 返回旧值,所以 latest_gen > gen+1 才表示"在我之后又发生了变更"。 + let gen = RETRY_GEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match aux { + Some(t) => { + log::info!("[capsule] set_aux_down: {t} gen={gen}"); + let text = t.to_string(); + std::thread::spawn(move || { + let current = LAST_AUX.lock().unwrap().clone(); + if current.as_deref() != Some(&text) { + log::info!( + "[capsule] set_aux_down skipped: state changed to {current:?}" + ); + return; + } + if let Err(e) = crate::linux_fcitx::set_aux_down(&text) { + log::warn!("[capsule] set_aux_down failed: {e}"); + } + }); + // 终态(Done/Cancelled/Error)3 秒后自动清除,避免一直跟随焦点。 + if matches!( + state, + CapsuleState::Done | CapsuleState::Cancelled | CapsuleState::Error + ) { + let text = t.to_string(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(3)); + let latest_gen = RETRY_GEN.load(std::sync::atomic::Ordering::SeqCst); + if latest_gen > gen + 1 { + return; + } + let current = LAST_AUX.lock().unwrap().clone(); + if current.as_deref() != Some(&text) { + return; + } + log::info!("[capsule] auto-clear terminal state: {text}"); + let _ = crate::linux_fcitx::set_aux_down(""); + *LAST_AUX.lock().unwrap() = None; + }); + } + } + None => { + log::info!("[capsule] clear_aux_down gen={gen}"); + std::thread::spawn(move || { + let latest_gen = RETRY_GEN.load(std::sync::atomic::Ordering::SeqCst); + if latest_gen > gen + 1 { + log::info!( + "[capsule] clear_aux_down skipped: gen {gen}, latest {latest_gen}" + ); + return; + } + let current = LAST_AUX.lock().unwrap().clone(); + if current.is_some() { + log::info!( + "[capsule] clear_aux_down skipped: state changed to {current:?}" + ); + return; + } + if let Err(e) = crate::linux_fcitx::clear_aux_down() { + log::warn!("[capsule] clear_aux_down failed: {e}"); + } + }); + } + } + } + } + + // emit_capsule 会被 cpal process_callback(音频回调线程)调用 ~30 Hz —— 在该 + // 线程上调用 NSWindow / HWND API 会撞 macOS dispatch_assert_queue_fail SIGTRAP + // 或者 Win32 SendMessage 死锁。把 window.show/hide + 位置调整 marshal 到主线程; + // app.emit_to 走 Tauri 内部事件总线,本身线程安全,保留同步调用。详见 audit 3.2.2。 + // + // show_capsule(用户偏好)在主线程执行时再读 —— 用户可以在录音过程中改设置, + // 闭包入队到真正跑之间窗口上限是一两帧(~16-33ms),用最新值消除 stale-pref + // 闪烁。pr_agent 关注点 — 见 audit follow-up。 + let inner_for_main = Arc::clone(inner); + let app_for_main = app.clone(); + let _ = app.run_on_main_thread(move || { + let Some(window) = app_for_main.get_webview_window("capsule") else { + // #470 诊断 v2:比 A/B/C 更靠前的暗点 A0 —— capsule webview 句柄取不到 + // (窗口未创建/已销毁)。此前静默 return,无法观测。一次性 warn。 + if !CAPSULE_WINDOW_MISSING_LOGGED.swap(true, Ordering::SeqCst) { + log::warn!( + "[capsule] capsule webview window not found — emit_capsule show path skipped (state={})", + capsule_state_log_name(state) + ); + } + return; + }; + let show_capsule = inner_for_main.prefs.get().show_capsule; + // Linux: 不操作胶囊窗口(不 show/hide,不 reposition)。 + // 文字通过 fcitx5 插件直接 commit,用户始终在目标 app 中。 + #[cfg(target_os = "linux")] + { + return; + } + #[cfg(not(target_os = "linux"))] + { + + // 三平台统一:Done / Cancelled / Error 状态保留 ~1.5s toast + // (schedule_capsule_idle 之后会回 Idle 隐藏)。 + // Windows 上 linger 的真实问题(截图选中 / 死区 / 拖拽卡顿)由 #140 加的 + // `hide_capsule_window_if_present()` Win32 hard-hide 在 visible=false 分支 + // 处理,不依赖把 Done/Cancelled/Error 打成 invisible。详见 PR #140 评论。 + maybe_position_capsule_bottom_center(&inner_for_main, &window, translation); + if show_capsule && visible { + // 用户报"看不到胶囊"时第一时间能在 log 里确认:胶囊路径有跑、show_capsule + // 开关是 true、当前进入 visible 帧 —— 排除 prefs 没存住 / emit_capsule 没触 + // 发 / state 一直 Idle 这几类常见 root cause。issue #470。 + if !CAPSULE_FIRST_SHOW_LOGGED.swap(true, Ordering::SeqCst) { + log::info!( + "[capsule] first show this session: show_capsule=true visible=true state={}", + capsule_state_log_name(state) + ); + } + show_capsule_window_for_recording(&app_for_main, &window); + // macOS/Windows 优先走 no-activate show,避免录音胶囊抢走当前工作 app 焦点。 + // 若 fallback 到 show(),OpenLess 已是前台 app 时再把 key window 还给 main。 + #[cfg(target_os = "macos")] + crate::restore_main_window_key_if_active(&app_for_main); + } else { + // show_capsule 开关被用户关掉但本次确实想显示(visible=true)的情况: + // 一次性 info log,让用户报"胶囊没显示"时能在日志里一眼看到根因 —— 维护者 + // 不必再让用户"去打开设置确认"。issue #470。 + if !show_capsule + && visible + && !CAPSULE_SUPPRESSED_BY_TOGGLE_LOGGED.swap(true, Ordering::SeqCst) + { + log::info!( + "[capsule] suppressed by user toggle: show_capsule=false visible=true state={}", + capsule_state_log_name(state) + ); + } + hide_capsule_window_if_present(); + let _ = window.hide(); + } + } + }); + + let _ = app.emit_to("capsule", "capsule:state", &payload); + // 主窗口也需要 capsule:state 事件:AudioCueListener 用它触发录音提示音。 + // Linux 上胶囊隐藏时提示音仍应工作,所以同时发给 main 窗口。 + let _ = app.emit_to("main", "capsule:state", &payload); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct CapsuleLayoutState { + translation_active: bool, + monitor_x: i32, + monitor_y: i32, + monitor_width: u32, + monitor_height: u32, + scale_bits: u64, +} + +/// 返回胶囊「应该摆放到的显示器」的标识信息。 +/// +/// 它看的显示器必须和 `position_capsule_bottom_center` 实际定位用的一致: +/// Windows 看「正在输入的 App 所在显示器」,其它平台看胶囊自己的显示器。 +/// 这是「是否需要重新定位」去重缓存(`maybe_position_capsule_bottom_center`) +/// 的 key,如果这里看错了显示器,就会出现「输入焦点移到另一块屏、胶囊却没 +/// 跟过去」的 bug。 +fn capsule_layout_snapshot( + window: &tauri::WebviewWindow, + translation_active: bool, +) -> Option { + // Windows:以「正在输入的 App 所在显示器」为基准。若用胶囊自己的 + // current_monitor,输入焦点切到另一块屏时胶囊仍在原屏 → 误判「没变化」 + // → 跳过重新定位。 + #[cfg(target_os = "windows")] + { + if let Some(mon) = crate::foreground_window_monitor() { + return Some(CapsuleLayoutState { + translation_active, + monitor_x: mon.left, + monitor_y: mon.top, + monitor_width: (mon.right - mon.left).max(0) as u32, + monitor_height: (mon.bottom - mon.top).max(0) as u32, + scale_bits: mon.scale.to_bits(), + }); + } + // 仅当 Win32 取不到前台显示器时,落回下面的 current_monitor。 + } + let monitor = window.current_monitor().ok().flatten()?; + Some(CapsuleLayoutState { + translation_active, + monitor_x: monitor.position().x, + monitor_y: monitor.position().y, + monitor_width: monitor.size().width, + monitor_height: monitor.size().height, + scale_bits: monitor.scale_factor().to_bits(), + }) +} + +fn maybe_position_capsule_bottom_center( + inner: &Arc, + window: &tauri::WebviewWindow, + translation_active: bool, +) { + let Some(next) = capsule_layout_snapshot(window, translation_active) else { + return; + }; + { + let last = inner.capsule_layout.lock(); + if last.as_ref() == Some(&next) { + return; + } + } + if crate::position_capsule_bottom_center(window, translation_active).is_ok() { + let mut last = inner.capsule_layout.lock(); + *last = Some(next); + } +} + +// ─────────────────────────── audio bridge ─────────────────────────── + +struct DeferredAsrBridge { + state: Mutex, +} + +struct DeferredAsrState { + target: Option>, + pending_audio: Vec, + attaching: bool, +} + +impl DeferredAsrBridge { + fn new() -> Self { + Self { + state: Mutex::new(DeferredAsrState { + target: None, + pending_audio: Vec::new(), + attaching: false, + }), + } + } + + fn attach(&self, target: Arc) -> usize { + let mut flushed_bytes = 0; + { + let mut state = self.state.lock(); + state.attaching = true; + } + + loop { + let pending = { + let mut state = self.state.lock(); + if state.pending_audio.is_empty() { + state.target = Some(Arc::clone(&target)); + state.attaching = false; + return flushed_bytes; + } + std::mem::take(&mut state.pending_audio) + }; + flushed_bytes += pending.len(); + target.consume_pcm_chunk(&pending); + } + } +} + +impl crate::recorder::AudioConsumer for DeferredAsrBridge { + fn consume_pcm_chunk(&self, pcm: &[u8]) { + let target = { + let mut state = self.state.lock(); + if state.attaching { + state.pending_audio.extend_from_slice(pcm); + return; + } + if let Some(target) = state.target.as_ref() { + Some(Arc::clone(target)) + } else { + state.pending_audio.extend_from_slice(pcm); + None + } + }; + + if let Some(target) = target { + target.consume_pcm_chunk(pcm); + } + } } diff --git a/openless-all/app/src-tauri/src/coordinator/dictation.rs b/openless-all/app/src-tauri/src/coordinator/dictation.rs index 0eff9c51..cd5e263c 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation.rs @@ -1,14 +1,462 @@ -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use crate::coordinator_state::request_stop_during_starting_state; +use crate::correction::apply_correction_rules; use crate::types::HotkeyMode; use super::qa::handle_qa_option_edge; +use super::resources::*; use super::*; /// 同一个 hotkey 边沿之间的最小间隔。低于此阈值的连按整体作为误触丢弃 —— /// 避免微动开关回弹 / 用户手抖双击造成的空转写报错和 ASR session 抢资源。 const HOTKEY_DEBOUNCE: std::time::Duration = std::time::Duration::from_millis(250); +const STREAMING_INSERT_FLUSH_INTERVAL: std::time::Duration = std::time::Duration::from_millis(12); + +/// Less Computer 浮窗的 Tauri 事件名(前端 LessComputerPanel 订阅)。 +const LESS_COMPUTER_EVENT: &str = "less-computer:event"; + +/// Less Computer 内联审批:等待用户决断的 token → oneshot sender 注册表。 +/// +/// 无头 `claude -p` 没有 mid-run 的 `--permission-prompt-tool` 通道(v2.1.165 不支持), +/// 所以护栏拦截发生在「整轮跑完、护栏 deny 生效」之后。这个注册表是审批 UI 的实回路: +/// 后端发 `approval` 事件后把一个 oneshot 接收端挂在这里,等前端 `less_computer_approve` +/// 命令按 token 解析出用户决断(true=Approve / false=Deny)。 +static LESS_COMPUTER_APPROVALS: std::sync::OnceLock< + std::sync::Mutex>>, +> = std::sync::OnceLock::new(); + +fn less_computer_approvals( +) -> &'static std::sync::Mutex>> +{ + LESS_COMPUTER_APPROVALS.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) +} + +/// 前端 `less_computer_approve` 命令调到这里:按 token 解析等待中的审批。 +/// token 不存在(已超时 / 已解析)时静默忽略。 +pub(super) fn resolve_less_computer_approval(token: &str, approved: bool) { + let sender = less_computer_approvals() + .lock() + .ok() + .and_then(|mut m| m.remove(token)); + if let Some(tx) = sender { + let _ = tx.send(approved); + log::info!("[less-computer] 审批 token={token} approved={approved}"); + } else { + log::info!("[less-computer] 审批 token={token} 已失效(超时/重复)"); + } +} + +/// 往 Less Computer 浮窗发一条事件(macOS only;前端按 `kind` 渲染聊天结构)。 +fn emit_less_computer(inner: &Arc, payload: serde_json::Value) { + if let Some(app) = inner.app.lock().clone() { + let _ = app.emit_to("less-computer", LESS_COMPUTER_EVENT, payload); + } +} + +/// 跑流式润色路径(opt-in,跨平台)。 +/// +/// 平台差异: +/// - **macOS**:`switch_to_ascii` 切到 ABC 输入源(规避 CJK / 日文 IME 拦截 Unicode 事件), +/// session 结束 `restore_input_source` 切回。`type_unicode_chunk` 走 CGEvent FFI。 +/// - **Windows**:`switch_to_ascii` 是 no-op(SendInput Unicode 绕过 TSF); +/// `type_unicode_chunk` 走 `SendInput(KEYEVENTF_UNICODE)`。 +/// - **Linux(实验)**:`switch_to_ascii` 是 no-op;`type_unicode_chunk` 走 enigo +/// `Keyboard::text`。X11 / XTest 稳定。 +/// +/// 通用流程: +/// 1. `switch_to_ascii`(macOS)/ no-op(其他);失败则降级回一次性 `polish_or_passthrough`。 +/// 2. 起一个 `spawn_blocking` 后台任务,从 mpsc 收 SSE delta,按 12ms flush window +/// 合并后调 `type_unicode_chunk` 模拟键盘事件落到光标处。串行有序,无竞态。 +/// 3. 调 `polish_or_passthrough_streaming`,`on_delta` 把 chunk 塞进 mpsc。 +/// 4. 流结束 / 失败 / 取消 → drop mpsc 发送端 → typer 任务 drain 完剩余 delta 退出 → +/// `restore_input_source` 恢复用户原输入源(macOS 才有意义,其他平台 no-op)。 +/// 5. 返回 `(polished, polish_error, already_streamed)`: +/// - 成功:`(text, None, true)` — 字符已经在屏幕上,调用方应当跳过 `inserter.insert` +/// - 失败:`(raw_text, Some(reason), false)` — 流式过程出错,调用方走 raw 一次性兜底 +/// - 不支持:`run_streaming_polish` 内部直接调 `polish_or_passthrough` 透明降级 +/// +/// **不在流式路径里做**:`apply_chinese_script_preference` / `apply_correction_rules` +/// 这两步在 v1 跳过 —— 字符已经一边流一边落出去了,不好回退。需要的话只能关 toggle 走 +/// 一次性路径。 +#[allow(clippy::too_many_arguments)] +async fn run_streaming_polish( + inner: &Arc, + raw: &RawTranscript, + mode: PolishMode, + hotwords: &[String], + style_system_prompt: &str, + working_languages: &[String], + chinese_script_preference: crate::types::ChineseScriptPreference, + output_language_preference: crate::types::OutputLanguagePreference, + llm_thinking_enabled: bool, + front_app: Option<&str>, + prior_turns: &[(String, String)], +) -> (String, Option, bool) { + log::info!( + "[coord] streaming_insert path ENTER (raw_chars={})", + raw.text.chars().count() + ); + + let app = inner.app.lock().clone(); + let Some(app) = app else { + log::warn!("[coord] streaming_insert: no AppHandle in Inner; fall back to one-shot"); + let (p, e) = polish_or_passthrough( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + ) + .await; + return (p, e, false); + }; + + // 1. 切到 ABC 输入源。失败则降级 —— 流式路径上 CJK IME 拦截不是可恢复错误。 + log::info!("[coord] streaming_insert: switching input source to ABC"); + let prev_ime = match crate::unicode_keystroke::switch_to_ascii(&app).await { + Ok(prev) => { + log::info!( + "[coord] streaming_insert: switched to ABC (had_previous={})", + prev.is_some() + ); + prev + } + Err(e) => { + log::warn!( + "[coord] streaming_insert: switch_to_ascii failed: {e}; fall back to one-shot" + ); + let (p, err) = polish_or_passthrough( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + ) + .await; + return (p, err, false); + } + }; + + // 2. 起 typer 后台任务:从 mpsc 收 delta,串行调 type_unicode_chunk。 + // 同时累积 typed_text:屏幕上真正落字的内容,用于(a)SSE 中途失败时让 history + // 与用户实际看到的内容一致;(b)pr-agent #412 反馈 \"saved output diverges + // from what the user actually sees\"。 + let (tx, rx) = std::sync::mpsc::channel::(); + let typer_handle = tokio::task::spawn_blocking(move || { + drain_streaming_insert_deltas(rx, STREAMING_INSERT_FLUSH_INTERVAL) + }); + + // 3. 调流式润色,on_delta 塞 mpsc;should_cancel 检查 dictation 取消旗。 + let inner_for_cancel = Arc::clone(inner); + let should_cancel = move || inner_for_cancel.state.lock().cancelled; + let outcome = super::polish_or_passthrough_streaming( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + move |delta: &str| { + let _ = tx.send(delta.to_string()); + }, + should_cancel, + ) + .await; + // tx 已经被 move 进 on_delta 闭包;闭包随 polish_or_passthrough_streaming 返回 + // 而 drop,typer 那侧 blocking_recv 拿到 None 自然退出。 + + // 4. 等 typer 把缓冲 drain 完,拿到实际落字的全文 + 第一条失败原因。 + let (typed_text, typer_failure) = typer_handle.await.unwrap_or_else(|e| { + log::error!("[coord] streaming_insert: typer task join failed: {e}"); + (String::new(), Some(format!("typer join: {e}"))) + }); + let typed_chars = typed_text.chars().count(); + log::info!("[coord] streaming_insert: typer drained, typed {typed_chars} chars"); + + // 5. 无论流是否成功,都恢复用户原输入源。 + log::info!("[coord] streaming_insert: restoring input source"); + if let Err(e) = crate::unicode_keystroke::restore_input_source(&app, prev_ime).await { + log::warn!("[coord] streaming_insert: restore_input_source failed: {e}"); + } else { + log::info!("[coord] streaming_insert: input source restored"); + } + + // 6. 把 outcome 翻译成 (polished, polish_error, already_streamed)。 + match outcome { + super::StreamingPolishOutcome::Streamed(text) => { + log::info!( + "[coord] streaming_insert SUCCESS: polished_chars={} typed_chars={} typer_err={:?}", + text.chars().count(), + typed_chars, + typer_failure + ); + // 边界 case:polish 成功但 typer 在第一字就失败(最常见:session 开始时 + // 已处于 Secure Input;或 SendInput / enigo 拒绝)。屏幕上一字未见, + // already_streamed=true 会让上层跳过 inserter,最终用户看不到任何内容。 + // 这里显式回退到一次性兜底,让正常 inserter 路径写出 polish 结果。 + // pr-agent #412 反馈 \"Missing fallback\"。 + if typed_chars == 0 { + if let Some(reason) = typer_failure { + log::warn!( + "[coord] streaming_insert: zero chars typed despite polish success ({reason}); falling back to one-shot inserter" + ); + return (text, Some(reason), false); + } + } + // 先确定 final_text —— typer 中途失败时屏幕只有 typed_text 这一段, + // history 记完整 polish 反而会让用户复盘困惑。让 history / clipboard / + // 后续逻辑统统用 final_text,三处保持一致。 + // pr-agent #412 反馈 \"Clipboard Mismatch\":之前先写 text 到剪贴板再 + // 决定 typer 是否中途失败,导致 Cmd+V 粘出用户屏幕上没见过的内容。 + let (final_text, polish_err) = match typer_failure { + Some(e) => (typed_text, Some(format!("typing partially failed: {e}"))), + None => (text, None), + }; + // 把 final_text 写回剪贴板(默认 on,macOS/Windows 适用)。 + // Linux:fcitx5 插件已直写文字到目标 app,跳过剪贴板避免破坏用户数据。 + // Android/iOS:无 arboard 剪贴板路径,v1 依赖 IME commit。 + #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "ios")))] + if inner.prefs.get().streaming_insert_save_clipboard { + match arboard::Clipboard::new() { + Ok(mut cb) => match cb.set_text(final_text.clone()) { + Ok(()) => log::info!( + "[coord] streaming_insert: final text written to clipboard ({} chars)", + final_text.chars().count() + ), + Err(e) => { + log::warn!("[coord] streaming_insert: clipboard set_text failed: {e}") + } + }, + Err(e) => { + log::warn!("[coord] streaming_insert: clipboard handle init failed: {e}") + } + } + } else { + log::info!("[coord] streaming_insert: clipboard save skipped (pref off)"); + } + (final_text, polish_err, true) + } + super::StreamingPolishOutcome::UnsupportedFallback => { + log::info!( + "[coord] streaming_insert: dispatch reported unsupported, fall back to one-shot" + ); + let (p, e) = polish_or_passthrough( + raw, + mode, + hotwords, + style_system_prompt, + working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app, + prior_turns, + ) + .await; + (p, e, false) + } + super::StreamingPolishOutcome::Failed(reason) => { + log::warn!( + "[coord] streaming_insert FAILED: {reason}; typed {typed_chars} chars before failure" + ); + // 流式失败但已经流了一部分 chars:用户屏幕上有半截 polish。history 应当 + // 跟屏幕一致 —— 记 typed_text 而不是 raw.text,否则保存内容跟用户看见的 + // 内容会分叉(pr-agent #412 \"Wrong final text\" 反馈)。 + // 一字都没流时 typed_text 是空串,回到 raw 一次性兜底。 + if typed_chars > 0 { + ( + typed_text, + Some(format!( + "streaming polish failed mid-stream after {typed_chars} chars: {reason}" + )), + true, + ) + } else { + (raw.text.clone(), Some(reason), false) + } + } + } +} + +fn drain_streaming_insert_deltas( + rx: std::sync::mpsc::Receiver, + flush_interval: std::time::Duration, +) -> (String, Option) { + drain_streaming_insert_deltas_with(rx, flush_interval, flush_streaming_insert_buffer) +} + +fn drain_streaming_insert_deltas_with( + rx: std::sync::mpsc::Receiver, + flush_interval: std::time::Duration, + mut flush_pending: F, +) -> (String, Option) +where + F: FnMut(&mut String, &mut String) -> Option, +{ + let mut typed_text = String::new(); + let mut first_failure: Option = None; + let mut pending = String::new(); + while let Ok(delta) = rx.recv() { + pending.push_str(&delta); + let flush_at = std::time::Instant::now() + flush_interval; + loop { + let now = std::time::Instant::now(); + if now >= flush_at { + break; + } + match rx.recv_timeout(flush_at.duration_since(now)) { + Ok(delta) => pending.push_str(&delta), + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => break, + Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { + first_failure = flush_pending(&mut pending, &mut typed_text); + return (typed_text, first_failure); + } + } + } + first_failure = flush_pending(&mut pending, &mut typed_text); + if first_failure.is_some() { + // 一旦类型链路出错(如 Secure Input 启用),后续 delta 全部丢弃,但仍 + // 把 mpsc drain 完,避免发送端阻塞。 + while rx.recv().is_ok() {} + break; + } + } + if first_failure.is_none() { + first_failure = flush_pending(&mut pending, &mut typed_text); + } + (typed_text, first_failure) +} + +fn flush_streaming_insert_buffer(pending: &mut String, typed_text: &mut String) -> Option { + flush_streaming_insert_buffer_with( + pending, + typed_text, + crate::unicode_keystroke::type_unicode_chunk, + ) +} + +fn flush_streaming_insert_buffer_with( + pending: &mut String, + typed_text: &mut String, + mut type_chunk: F, +) -> Option +where + F: FnMut(&str) -> Result, +{ + if pending.is_empty() { + return None; + } + let delta = std::mem::take(pending); + let delta_chars = delta.chars().count(); + match type_chunk(&delta) { + Ok(typed_chars) => { + let appended = append_typed_prefix(typed_text, &delta, typed_chars); + if appended < delta_chars { + let reason = format!( + "type_unicode_chunk typed only {appended}/{delta_chars} chars without error" + ); + log::error!( + "[coord] streaming_insert: {reason} at typed={} chars; \ + dropping remaining deltas", + typed_text.chars().count() + ); + Some(reason) + } else { + None + } + } + Err(e) => { + append_typed_prefix(typed_text, &delta, e.typed_chars()); + log::error!( + "[coord] streaming_insert: type_unicode_chunk failed at typed={} chars: {e}; \ + dropping remaining deltas", + typed_text.chars().count() + ); + Some(e.to_string()) + } + } +} + +fn finalize_polished_text( + polished: String, + translation_active: bool, + _raw_uses_llm: bool, + mode: PolishMode, + polish_error: &Option, + chinese_script_preference: crate::types::ChineseScriptPreference, + correction_rules: &[crate::types::CorrectionRule], + already_streamed: bool, +) -> String { + if already_streamed { + return polished; + } + let should_force_script = if translation_active { + polish_error.is_some() + } else { + mode == PolishMode::Raw || polish_error.is_some() + }; + let polished = if should_force_script { + apply_chinese_script_preference(&polished, chinese_script_preference) + } else { + polished + }; + if correction_rules.is_empty() { + polished + } else { + let corrected = apply_correction_rules(&polished, correction_rules); + if corrected != polished { + log::info!( + "[coord] correction rules adjusted final text ({} → {} chars)", + polished.chars().count(), + corrected.chars().count() + ); + } + corrected + } +} + +fn streaming_insert_eligible( + streaming_insert_enabled: bool, + translation_active: bool, + mode: PolishMode, + raw_uses_llm: bool, +) -> bool { + streaming_insert_enabled && !translation_active && (mode != PolishMode::Raw || raw_uses_llm) +} + +fn default_done_message(status: InsertStatus, polish_failed: bool) -> Option { + if polish_failed { + // polish 失败优先告知用户,即使 insert 成功也要让用户知道这版是原文 + Some("润色失败,已插入原文".to_string()) + } else { + match status { + InsertStatus::Inserted => None, + InsertStatus::PasteSent => Some("已尝试粘贴".to_string()), + InsertStatus::CopiedFallback => Some(if cfg!(target_os = "windows") { + "已复制,请 Ctrl+V".to_string() + } else { + "已复制,请粘贴".to_string() + }), + InsertStatus::Failed => Some("插入失败".to_string()), + } + } +} pub(super) async fn handle_pressed_edge(inner: &Arc) { let was_held = inner.hotkey_trigger_held.swap(true, Ordering::SeqCst); @@ -124,13 +572,1976 @@ pub(super) async fn handle_released(inner: &Arc) { } } +/// Less Computer 收尾:把转写当作指令交给无头 Claude,结果以胶囊展示(不插入到光标)。 +async fn run_voice_agent_transcript( + inner: &Arc, + _session_id: SessionId, + transcript: String, + elapsed: u64, +) -> Result<(), String> { + log::info!( + "[coord] Cloud Agent 语音:指令 {} 字", + transcript.chars().count() + ); + // 胶囊保留「处理中」反馈(用户熟悉的小录音条状态机);聊天浮窗承载完整对话。 + emit_capsule( + inner, + CapsuleState::Polishing, + 0.0, + elapsed, + Some("Claude 处理中…".to_string()), + None, + ); + + // 聊天浮窗:显示窗口 + 落用户气泡(语音指令转写)。macOS only(helper 内部 gating)。 + if let Some(app) = inner.app.lock().clone() { + crate::show_less_computer_window(&app); + // 全屏彩虹描边已在按下键时(handle_less_computer_pressed)点亮,这里不重复。 + } + // 连续对话:浮窗里已有进行中的会话 → 本轮 `claude --continue` 续上下文;否则是新会话(fresh)。 + // dismiss 关窗会把标志复位为 false。 + let continue_session = inner + .less_computer_conversation + .swap(true, Ordering::SeqCst); + emit_less_computer( + inner, + serde_json::json!({ "kind": "user", "text": transcript, "fresh": !continue_session }), + ); + + let prefs = inner.prefs.get(); + // 工作目录:用户设的 workdir,否则 $HOME。--add-dir 把文件作用域限定在此。 + let cwd = prefs + .coding_agent_workdir + .clone() + .filter(|d| !d.trim().is_empty()) + .map(std::path::PathBuf::from) + .or_else(|| std::env::var("HOME").ok().map(std::path::PathBuf::from)); + // 运行前 git 快照(cwd 是 git 仓库才有效;非仓库无副作用),便于回滚文件改动。 + if let Some(dir) = &cwd { + if let Some(sha) = crate::coding_agent::create_git_snapshot(dir) { + log::info!("[less-computer] 运行前 git 快照 {sha}(git stash apply 可回滚)"); + } + } + + // 钳制:语音 → shell 这条全自动路径禁止 bypassPermissions 绕过护栏(无人审、动手即生效)。 + // 即便用户在偏好里设了 bypass,这里也降级为 acceptEdits(仍带 deny 护栏)。 + let mode = match coding_agent_mode_from_pref(&prefs.coding_agent_permission_mode) { + crate::coding_agent::CodingAgentPermissionMode::BypassPermissions => { + log::warn!( + "[less-computer] 语音 Agent 路径禁止 bypassPermissions,已降级为 acceptEdits(保留护栏)" + ); + crate::coding_agent::CodingAgentPermissionMode::AcceptEdits + } + other => other, + }; + let model = prefs + .coding_agent_model + .clone() + .filter(|m| !m.trim().is_empty()) + .or_else(|| Some("sonnet".to_string())); + let prompt = crate::coding_agent::autonomous_prompt(&transcript); + + // 第一轮:默认护栏(高风险全 deny)。运行后若检测到护栏拦截,弹审批卡; + // 用户 Approve 则在第二轮把该高风险模式从 deny 移除 + 加进 allowed,重跑一次。 + let outcome = run_less_computer_once( + inner, + &prompt, + cwd.as_deref(), + mode, + model.as_deref(), + &[], + continue_session, + ) + .await; + + let final_outcome = match maybe_request_approval(inner, &outcome).await { + Some(approved_pattern) => { + log::info!("[less-computer] 审批通过,放行高风险模式后重跑:{approved_pattern}"); + run_less_computer_once( + inner, + &prompt, + cwd.as_deref(), + mode, + model.as_deref(), + &[approved_pattern], + continue_session, + ) + .await + } + None => outcome, + }; + + inner.state.lock().phase = SessionPhase::Idle; + // 工作结束:熄灭全屏彩虹描边(聊天浮窗保留,等用户读完/关闭)。 + if let Some(app) = inner.app.lock().clone() { + crate::hide_less_computer_glow(&app); + } + + match final_outcome { + LessComputerOutcome::Done { text, cost_usd } => { + let text = text.trim().to_string(); + if text.is_empty() { + let msg = "Claude 无结果(确认已登录 claude 且额度充足)".to_string(); + emit_less_computer( + inner, + serde_json::json!({ "kind": "error", "message": msg }), + ); + emit_capsule(inner, CapsuleState::Error, 0.0, elapsed, Some(msg), None); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("voice agent empty".to_string()); + } + log::info!("[coord] Cloud Agent 语音:返回 {} 字", text.chars().count()); + emit_less_computer( + inner, + serde_json::json!({ "kind": "completed", "text": text, "costUsd": cost_usd }), + ); + emit_capsule(inner, CapsuleState::Done, 0.0, elapsed, Some(text), None); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + Ok(()) + } + LessComputerOutcome::Failed { message } => { + log::warn!("[coord] Cloud Agent 语音失败: {message}"); + emit_less_computer( + inner, + serde_json::json!({ "kind": "error", "message": message }), + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(message), + None, + ); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + Err("voice agent failed".to_string()) + } + LessComputerOutcome::Cancelled => { + log::info!("[coord] Cloud Agent 语音已取消"); + emit_less_computer(inner, serde_json::json!({ "kind": "cancelled" })); + emit_capsule(inner, CapsuleState::Cancelled, 0.0, elapsed, None, None); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + Err("voice agent cancelled".to_string()) + } + } +} + +/// 一轮无头 Less Computer 运行的结果。 +enum LessComputerOutcome { + Done { text: String, cost_usd: Option }, + Failed { message: String }, + Cancelled, +} + +/// 跑一轮无头 Claude(「放行 + 护栏」),把 Delta/ToolUse 实时 stream 到聊天浮窗, +/// 终局收敛为 [`LessComputerOutcome`]。`extra_allow_patterns` 为审批通过后放行的 +/// 高风险子串(如 "git push --force"):从 deny 清单剔除 + 作为 `Bash(:*)` 加进 allowed。 +async fn run_less_computer_once( + inner: &Arc, + prompt: &str, + cwd: Option<&std::path::Path>, + mode: crate::coding_agent::CodingAgentPermissionMode, + model: Option<&str>, + extra_allow_patterns: &[String], + continue_session: bool, +) -> LessComputerOutcome { + // 护栏 deny:默认全量;审批放行的模式从 deny 中剔除。 + // 审批 UI 只回传命中的单个高风险子串,但同一风险有等价写法(如 --force / -f)。 + // 按「风险等价组」整组放行:只放行被点那一个会让等价写法仍卡在 deny(deny 优先级高于 + // allow)→ 命令仍被拦。见 guard::risk_equivalent_patterns。 + let mut deny = crate::coding_agent::guard::default_deny_rules(); + let approved_patterns: Vec = extra_allow_patterns + .iter() + .flat_map(|p| { + let group = crate::coding_agent::guard::risk_equivalent_patterns(p); + if group.is_empty() { + vec![p.clone()] + } else { + group.into_iter().map(|s| s.to_string()).collect() + } + }) + .collect(); + let allow_rules: Vec = approved_patterns + .iter() + .map(|p| format!("Bash({p}:*)")) + .collect(); + if !allow_rules.is_empty() { + deny.retain(|d| !allow_rules.iter().any(|a| a == d)); + } + let settings_json = serde_json::json!({ + "permissions": { "defaultMode": mode.as_cli_arg(), "deny": deny } + }); + let settings_path = std::env::temp_dir().join(format!( + "openless-less-computer-guard-{}.json", + uuid::Uuid::new_v4() + )); + // fail-closed:序列化或写入失败时立即中止,绝不在「无护栏」下把无效路径交给 + // `claude -p --settings`(找不到文件 = 完全裸跑)。宁可不跑也不裸跑。 + let settings_bytes = match serde_json::to_vec_pretty(&settings_json) { + Ok(b) => b, + Err(e) => { + log::warn!("[less-computer] 序列化护栏配置失败: {e}"); + return LessComputerOutcome::Failed { + message: "护栏配置写入失败,已中止(拒绝在无护栏下执行)".into(), + }; + } + }; + if let Err(e) = std::fs::write(&settings_path, settings_bytes) { + log::warn!("[less-computer] 写护栏配置失败: {e}"); + return LessComputerOutcome::Failed { + message: "护栏配置写入失败,已中止(拒绝在无护栏下执行)".into(), + }; + } + + let mut req = crate::coding_agent::CodingAgentRequest::new("less-computer", prompt.to_string()); + req.cwd = cwd.map(|p| p.to_path_buf()); + req.model = model.map(|m| m.to_string()); + req.permission_mode = mode; + // 写护栏成功后才设置:写失败已在上面 fail-closed 返回,不会带无效路径裸跑。 + req.settings_json_path = Some(settings_path.clone()); + // 去掉 WebFetch:无出站白名单时它是 prompt 注入 SSRF 面(诱导拉取内网/元数据端点)。 + // 保留 WebSearch(走搜索引擎,不直接抓任意 URL)。 + req.allowed_tools = vec![ + "Bash".into(), + "Read".into(), + "Edit".into(), + "Write".into(), + "Glob".into(), + "Grep".into(), + "WebSearch".into(), + ]; + req.allowed_tools.extend(allow_rules); + // 真实任务(开应用、多步操作、读写文件)常超过 120s/0.5$ → 老是「运行超时」。放宽到 + // 5 分钟 / 2$,给多步任务足够空间;仍有硬上限兜底,不会无限跑/烧钱。 + req.max_budget_usd = Some(2.0); + req.timeout_secs = 300; + // 连续对话需要保留会话:本轮保存(供下轮 --continue),第二轮起带 --continue 续上下文。 + req.session_persistence = true; + req.continue_session = continue_session; + + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + let cancel = Arc::new(AtomicBool::new(false)); + let cancel_for_runner = Arc::clone(&cancel); + let run = async_runtime::spawn(async move { + crate::coding_agent::run_claude_agent("claude", req, tx, cancel_for_runner).await + }); + let cancel_for_watcher = Arc::clone(&cancel); + let inner_for_cancel = Arc::clone(inner); + let cancel_watcher = async_runtime::spawn(async move { + loop { + if cancel_for_watcher.load(Ordering::Relaxed) { + return; + } + if inner_for_cancel.state.lock().cancelled { + cancel_for_watcher.store(true, Ordering::Relaxed); + return; + } + tokio::time::sleep(std::time::Duration::from_millis(120)).await; + } + }); + + let mut final_text = String::new(); + let mut cost_usd: Option = None; + let mut error_msg: Option = None; + let mut cancelled = false; + while let Some(ev) = rx.recv().await { + use crate::coding_agent::CodingAgentEvent as E; + match ev { + E::Started { .. } => { + emit_less_computer(inner, serde_json::json!({ "kind": "started" })); + } + E::Delta { text, .. } => { + emit_less_computer(inner, serde_json::json!({ "kind": "delta", "text": text })); + } + E::ToolUse { name, .. } => { + emit_less_computer(inner, serde_json::json!({ "kind": "tool", "name": name })); + } + E::Completed { + text, cost_usd: c, .. + } => { + final_text = text; + cost_usd = c; + } + E::Error { message, .. } => error_msg = Some(message), + E::Cancelled { .. } => cancelled = true, + } + } + let run_result = run.await; + cancel.store(true, Ordering::Relaxed); + let _ = cancel_watcher.await; + let _ = std::fs::remove_file(&settings_path); + + if cancelled + || matches!( + &run_result, + Ok(Err(crate::coding_agent::CodingAgentError::Cancelled)) + ) + { + return LessComputerOutcome::Cancelled; + } + + let trimmed = final_text.trim().to_string(); + if !trimmed.is_empty() { + LessComputerOutcome::Done { + text: trimmed, + cost_usd, + } + } else { + let message = error_msg + .or_else(|| match run_result { + Ok(Err(e)) => Some(e.to_string()), + _ => None, + }) + .unwrap_or_else(|| "Claude 无结果(确认已登录 claude 且额度充足)".to_string()); + LessComputerOutcome::Failed { message } + } +} + +/// 护栏拦截探测 + 内联审批(best-effort)。 +/// +/// 无头 `claude -p`(v2.1.165)没有 mid-run 的 `--permission-prompt-tool` 通道,所以 +/// 我们只能在「一轮跑完」后判断护栏是否拦了高风险动作:扫描终局文本里是否提到某个 +/// 高风险模式 + 权限/拒绝/blocked 关键词。命中则发 `approval` 事件、挂一个 oneshot 等 +/// 用户决断(前端 Approve/Deny → `less_computer_approve` 命令解析)。 +/// +/// 返回 `Some(pattern)` 表示用户 Approve 了某高风险模式 → 调用方应放行该模式重跑一轮; +/// `None` 表示无需审批 / 用户 Deny / 超时。**注意**这是「重跑放行」而非真正的 mid-run +/// 续跑——headless 下没有干净的 mid-run round-trip,详见 report。 +async fn maybe_request_approval( + inner: &Arc, + outcome: &LessComputerOutcome, +) -> Option { + let text = match outcome { + LessComputerOutcome::Done { text, .. } => text.as_str(), + LessComputerOutcome::Failed { message } => message.as_str(), + LessComputerOutcome::Cancelled => return None, + }; + let lowered = text.to_lowercase(); + // 必须同时出现「拒绝/权限/blocked」语义 + 某个已知高风险模式,才认为是护栏拦截, + // 避免把正常提到 "rm" 的回答误判成审批请求。 + let mentions_block = [ + "denied", + "permission", + "not allowed", + "blocked", + "拒绝", + "权限", + "被拦", + ] + .iter() + .any(|kw| lowered.contains(kw)); + if !mentions_block { + return None; + } + let hit = crate::coding_agent::guard::HIGH_RISK_PATTERNS + .iter() + .find(|(pat, _)| lowered.contains(*pat))?; + let (pattern, reason) = (hit.0.to_string(), hit.1.to_string()); + + // 挂 oneshot 等用户决断。 + let token = uuid::Uuid::new_v4().to_string(); + let (tx, rx) = tokio::sync::oneshot::channel::(); + if let Ok(mut map) = less_computer_approvals().lock() { + map.insert(token.clone(), tx); + } + emit_less_computer( + inner, + serde_json::json!({ + "kind": "approval", + "token": token, + "command": pattern, + "reason": reason, + }), + ); + + // 等用户点 Approve/Deny;90s 无响应按 Deny 处理并清理注册表项。 + let approved = match tokio::time::timeout(std::time::Duration::from_secs(90), rx).await { + Ok(Ok(v)) => v, + _ => { + less_computer_approvals() + .lock() + .ok() + .map(|mut m| m.remove(&token)); + false + } + }; + if approved { + Some(pattern) + } else { + None + } +} + +/// 把 prefs 里的权限模式字符串映射成枚举;未知值回落到 acceptEdits(放行+护栏的默认)。 +fn coding_agent_mode_from_pref(s: &str) -> crate::coding_agent::CodingAgentPermissionMode { + use crate::coding_agent::CodingAgentPermissionMode as M; + match s.trim() { + "plan" => M::Plan, + "default" => M::Default, + "bypassPermissions" => M::BypassPermissions, + _ => M::AcceptEdits, + } +} + +pub(super) fn request_stop_during_starting(inner: &Arc, reason: &str) { + { + let mut state = inner.state.lock(); + if !request_stop_during_starting_state(&mut state) { + return; + } + } + log::info!("[coord] {reason} during Starting — queued"); + stop_recorder_if_pending_start_stop(inner); +} + +pub(super) async fn begin_session(inner: &Arc) -> Result<(), String> { + let current_session_id = { + let mut state = inner.state.lock(); + let Some(session_id) = + begin_session_state(&mut state, capture_focus_target(), capture_frontmost_app()) + else { + return Ok(()); + }; + if let Some(label) = state.front_app.as_deref() { + log::info!("[coord] front_app captured: {label}"); + } + session_id + }; + #[cfg(target_os = "windows")] + { + let prepared = inner.windows_ime.prepare_session(); + let mut slots = inner.prepared_windows_ime_session.lock(); + store_prepared_windows_ime_session(&mut slots, current_session_id, prepared); + } + // 翻译模式标志重置;hotkey 监听器在 Shift down 时再 set true。 + inner + .translation_modifier_seen + .store(false, Ordering::SeqCst); + + #[cfg(any(debug_assertions, test))] + if hotkey_injection_dry_run_enabled() { + emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); + inner.state.lock().phase = SessionPhase::Listening; + log::info!("[coord] session started (hotkey-injection dry-run)"); + return Ok(()); + } + + if let Err(message) = ensure_asr_credentials() { + log::warn!("[coord] ASR credential gate failed: {message}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(message.clone()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + return Err(message); + } + + let active_asr = CredentialsVault::get_active_asr(); + + if let Err(message) = ensure_microphone_permission(inner) { + log::warn!("[coord] microphone permission gate failed: {message}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(message.clone()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(message); + } + + // 不在这里 emit Recording capsule —— 让 start_recorder_for_starting 在 + // Recorder::start 成功后再发,确保「用户看到录音条」时 mic 已经在 capture。 + // 之前在这一行就 emit 会让用户看到录音条后立刻开口,但 mic 还在 cpal init + // 窗口(50-200ms)内 → 开头几个字物理上录不到。详见 issue 备注。 + #[cfg(target_os = "windows")] + if foundry::is_foundry_local_whisper(&active_asr) { + let prefs = inner.prefs.get(); + let model_alias = if foundry::model_alias_is_known(&prefs.foundry_local_asr_model) { + prefs.foundry_local_asr_model.clone() + } else { + foundry::DEFAULT_MODEL_ALIAS.to_string() + }; + let language_hint = prefs.foundry_local_asr_language_hint.trim().to_string(); + let language_hint = if language_hint.is_empty() { + None + } else { + Some(language_hint) + }; + let local = Arc::new(FoundryLocalWhisperAsr::new( + Arc::clone(&inner.foundry_local_runtime), + model_alias, + prefs.foundry_local_runtime_source.clone(), + language_hint, + )); + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::FoundryLocalWhisper(Arc::clone(&local)), + ); + let consumer: Arc = local; + start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer) + .await?; + return Ok(()); + } + + // Windows sherpa-onnx-local:与 Foundry 同形分支,复用 Recorder / + // ActiveAsr / start_recorder_and_enter_listening。offline 模型走 batch; + // online 模型在 provider 内部 worker 中边录边解码,并通过 local-asr-token + // 推 partial 给前端胶囊。 + #[cfg(target_os = "windows")] + if sherpa::is_sherpa_onnx_local(&active_asr) { + let prefs = inner.prefs.get(); + let model_alias = if sherpa::model_alias_is_known(&prefs.sherpa_onnx_model) { + prefs.sherpa_onnx_model.clone() + } else { + sherpa::DEFAULT_MODEL_ALIAS.to_string() + }; + let language_hint = prefs.sherpa_onnx_language_hint.trim().to_string(); + let language_hint = if language_hint.is_empty() { + None + } else { + Some(language_hint) + }; + let token_handler = inner.app.lock().clone().map(|app| { + Arc::new(move |piece: String| { + if let Err(error) = app.emit("local-asr-token", piece) { + log::warn!("[sherpa-asr] emit token failed: {error}"); + } + }) as crate::asr::local::sherpa_provider::SherpaTokenHandler + }); + let local = match SherpaOnnxAsr::new_for_model( + Arc::clone(&inner.sherpa_onnx_runtime), + model_alias, + language_hint, + token_handler, + ) + .await + { + Ok(local) => Arc::new(local), + Err(e) => { + log::error!("[coord] sherpa-onnx init failed: {e:#}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(format!("本地模型初始化失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(format!("sherpa-onnx init failed: {e}")); + } + }; + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::SherpaOnnxLocal(Arc::clone(&local)), + ); + let consumer: Arc = local; + start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer) + .await?; + return Ok(()); + } + + #[cfg(target_os = "macos")] + if crate::asr::local::is_local_qwen3(&active_asr) { + let local = match build_local_qwen3(inner).await { + Ok(l) => l, + Err(e) => { + log::error!("[coord] 本地 Qwen3-ASR 初始化失败: {e:#}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(format!("本地模型初始化失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(format!("local ASR init failed: {e}")); + } + }; + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::Local(Arc::clone(&local)), + ); + let consumer: Arc = local; + start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer) + .await?; + return Ok(()); + } + + if is_bailian_provider(&active_asr) { + let asr = Arc::new(BailianRealtimeASR::new(read_bailian_credentials())); + let bridge = Arc::new(DeferredAsrBridge::new()); + let consumer: Arc = bridge.clone(); + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::Bailian(Arc::clone(&asr)), + ); + start_recorder_for_starting(inner, current_session_id, &active_asr, consumer).await?; + + if let Err(e) = asr.open_session().await { + log::error!("[coord] open Bailian ASR session failed: {e}"); + match startup_race_status_for_starting(inner, current_session_id) { + StartupRaceStatus::StaleContinuation => { + log::info!( + "[coord] stale Bailian ASR open_session error from session {current_session_id} — ignoring" + ); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::CancelRaced => { + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::ActiveStarting => { + asr.cancel(); + } + } + discard_startup_resources_for_session(inner, current_session_id); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(format!("ASR 连接失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + match startup_race_status_for_starting(inner, current_session_id) { + StartupRaceStatus::ActiveStarting => {} + StartupRaceStatus::CancelRaced => { + log::info!("[coord] cancel raced during Bailian ASR open_session — aborting begin"); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::StaleContinuation => { + log::info!( + "[coord] stale Bailian ASR open_session continuation from session {current_session_id} — ignoring" + ); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + return Ok(()); + } + } + let target: Arc = asr; + let flushed_bytes = bridge.attach(target); + log::info!("[coord] Bailian ASR connected; flushed {flushed_bytes} deferred audio bytes"); + finish_starting_session(inner, current_session_id).await; + } else if is_mimo_provider(&active_asr) { + let (api_key, base_url, model) = read_mimo_credentials(); + let mimo = Arc::new(MimoBatchASR::new(api_key, base_url, model)); + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::Mimo(Arc::clone(&mimo)), + ); + let consumer: Arc = mimo; + start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer) + .await?; + } else if is_whisper_compatible_provider(&active_asr) { + let (api_key, base_url, model) = read_whisper_credentials(); + // 用户辞書の有効フレーズを Whisper の `prompt` に流し込む。固有名詞や + // 専門用語の同音・近形誤認識を ASR 段階で抑える。Polish LLM 側には + // 既に system prompt として注入済みだが、Whisper 出力が大きく崩れる + // と Polish でも救えない(特に CJK で顕著)。Volcengine ASR は元々 + // hotword を受け取っており、UI 説明文も「ASR ホットワードと後処理 + // モデルのコンテキスト両方に渡される」と明示しているので、Whisper + // 互換プロバイダにも揃えるのが筋。 + let whisper_prompt = + crate::asr::whisper::build_prompt_from_phrases(&enabled_phrases(inner)); + let whisper = Arc::new( + WhisperBatchASR::new( + api_key, + base_url, + model, + whisper_prompt, + batch_asr_chunk_limit_ms(&active_asr), + whisper_supports_verbose_json(&active_asr), + ) + .with_request_format(whisper_request_format(&active_asr)), + ); + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::Whisper(Arc::clone(&whisper)), + ); + let consumer: Arc = whisper; + start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer) + .await?; + } else { + let hotwords = enabled_hotwords(inner); + let creds = read_volc_credentials(); + let asr = Arc::new(VolcengineStreamingASR::new(creds, hotwords)); + let bridge = Arc::new(DeferredAsrBridge::new()); + let consumer: Arc = bridge.clone(); + store_asr_for_session( + inner, + current_session_id, + ActiveAsr::Volcengine(Arc::clone(&asr)), + ); + start_recorder_for_starting(inner, current_session_id, &active_asr, consumer).await?; + + if let Err(e) = asr.open_session().await { + log::error!("[coord] open ASR session failed: {e}"); + match startup_race_status_for_starting(inner, current_session_id) { + StartupRaceStatus::StaleContinuation => { + log::info!( + "[coord] stale ASR open_session error from session {current_session_id} — ignoring" + ); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::CancelRaced => { + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::ActiveStarting => {} + } + discard_startup_resources_for_session(inner, current_session_id); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(format!("ASR 连接失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + // open_session.await 期间用户可能按了 Esc / 改变心意。如果 cancel_session + // 已触发(cancelled=true 或 phase 被改回 Idle),别再装 ASR,直接善后。 + // audit HIGH #1。 + match startup_race_status_for_starting(inner, current_session_id) { + StartupRaceStatus::ActiveStarting => {} + StartupRaceStatus::CancelRaced => { + log::info!("[coord] cancel raced during ASR open_session — aborting begin"); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + StartupRaceStatus::StaleContinuation => { + log::info!( + "[coord] stale ASR open_session continuation from session {current_session_id} — ignoring" + ); + asr.cancel(); + discard_startup_resources_for_session(inner, current_session_id); + restore_prepared_windows_ime_session(inner, current_session_id); + return Ok(()); + } + } + let target: Arc = asr; + let flushed_bytes = bridge.attach(target); + log::info!("[coord] ASR connected; flushed {flushed_bytes} deferred audio bytes"); + finish_starting_session(inner, current_session_id).await; + } + + Ok(()) +} + +pub(super) async fn start_recorder_for_starting( + inner: &Arc, + session_id: SessionId, + active_asr: &str, + consumer: Arc, +) -> Result<(), String> { + let inner_for_level = Arc::clone(inner); + // 节流:电平回调本身约 185 Hz(cpal 默认音频块),全部转发到前端会让 CSS + // transition 互相覆盖、视觉上"被平均"成静止。限制为 ~30 Hz(33ms 最少间隔), + // 配合 CSS 短 transition 让每次 emit 完整可见。 + let last_emit_at = Arc::new(Mutex::new(None::)); + const LEVEL_EMIT_MIN_INTERVAL_MS: u64 = 33; + let level_handler: Arc = Arc::new(move |level| { + let phase = inner_for_level.state.lock().phase; + if phase != SessionPhase::Listening && phase != SessionPhase::Starting { + return; + } + let now = Instant::now(); + { + let mut last = last_emit_at.lock(); + if let Some(prev) = *last { + if now.duration_since(prev).as_millis() < LEVEL_EMIT_MIN_INTERVAL_MS as u128 { + return; + } + } + *last = Some(now); + } + let elapsed = inner_for_level + .state + .lock() + .started_at + .elapsed() + .as_millis() as u64; + emit_capsule( + &inner_for_level, + CapsuleState::Recording, + level, + elapsed, + None, + None, + ); + }); + + let microphone_device_name = selected_microphone_device_name(inner); + stop_microphone_preview_monitor(inner, "dictation recorder"); + acquire_recording_mute(inner, "dictation").await; + let audio_archive_path = if inner.prefs.get().record_audio_for_debug { + // 用 coordinator 的 SessionId 作为文件名,跟 history 那条记录 id 对齐(见 + // 下游 polish 收尾时 `history_session_id = current_session_id.to_string()`)。 + // 顺手把超龄 / 超量录音清理一下,避免 debug 开关常开时磁盘膨胀。 + let prefs = inner.prefs.get(); + let _ = crate::persistence::prune_recordings( + prefs.history_retention_days, + prefs.audio_recording_max_entries, + ); + crate::persistence::recording_path_for_session(&session_id.to_string()).ok() + } else { + None + }; + match Recorder::start( + microphone_device_name, + consumer, + level_handler, + audio_archive_path, + ) { + Ok((rec, runtime_errors, archive_active)) => { + // 把 archive 实际创建状态存到 Inner,让 history 写入路径(含 empty-transcript + // 失败分支)读真实情况,而不是 prefs 开关。修 pr_agent "Wrong Flag" 反馈。 + inner + .audio_archive_active + .store(archive_active, std::sync::atomic::Ordering::Relaxed); + store_recorder_for_session(inner, session_id, rec); + spawn_recorder_error_monitor(inner, runtime_errors); + // 不在这里 emit Recording capsule。 + // Recorder::start Ok 仅代表 cpal Stream::play 完成,不代表 audio + // 线程已经在向 consumer 推 PCM —— macOS CoreAudio AudioUnit 启动到 + // 第一帧 process_callback 中间有 50–200 ms 间隙(Windows 类似)。 + // 之前在这里立即 emit Recording 会让用户「看到录音条」就开口,但前几个 + // 字落在 cpal init 窗口里被吞,反映为短录音漏首字(用户报告)。 + // + // 现改为:level_handler 第一次被触发时才 emit Recording capsule。 + // recorder.rs::process_callback 的顺序是 consume_pcm_chunk → level_handler, + // 所以 level_handler 第一次执行 == PCM 已经真实流到 consumer。从这一刻 + // 起用户说什么都被录到。capsule 自然就晚 50–200 ms 出现,但出现 == + // mic 真的在录,匹配「麦先录、UI 再弹」的预期。 + // + // 原本的竞态保护交还给两条已有路径: + // - stop_recorder_if_pending_start_stop:短按时把 capsule 切到 + // Transcribing;recorder 已 stop,level_handler 不会再发火。 + // - level_handler 内部 phase 检查:cancel / 错误使 phase 不在 + // {Starting, Listening} 时直接 return,不会在错误状态上盖 + // Recording。 + stop_recorder_if_pending_start_stop(inner); + log::info!("[coord] recorder started (asr={active_asr}, phase=Starting)"); + } + Err(e) => { + log::error!("[coord] recorder start failed: {e}"); + cancel_asr_for_session(inner, session_id); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + 0, + Some(format!("录音启动失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, session_id); + release_recording_mute(inner, "dictation"); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + } + + Ok(()) +} + +pub(super) fn spawn_recorder_error_monitor(inner: &Arc, rx: mpsc::Receiver) { + // 捕获当前 session_id:err 来时若 id 已经不一致说明是上一 session 的迟到事件, + // 不能去 abort 当前 active 的新 session(它录得好好的)。 + let captured_session_id = inner.state.lock().session_id; + let inner = Arc::clone(inner); + std::thread::Builder::new() + .name("openless-recorder-error-monitor".into()) + .spawn(move || { + if let Ok(err) = rx.recv() { + let current_session_id = inner.state.lock().session_id; + if captured_session_id != current_session_id { + log::warn!( + "[coord] recorder error from stale session {} dropped (current={}, err={})", + captured_session_id, + current_session_id, + err + ); + return; + } + log::error!("[coord] recorder runtime error: {err}"); + abort_recording_with_error(&inner, format!("录音中断: {err}")); + } + }) + .ok(); +} + +pub(super) fn abort_recording_with_error(inner: &Arc, message: String) { + let Some(abort) = ({ + let mut state = inner.state.lock(); + begin_recording_abort_before_restore(&mut state) + }) else { + return; + }; + + discard_startup_resources_for_session(inner, abort.session_id); + restore_prepared_windows_ime_session(inner, abort.session_id); + { + let mut state = inner.state.lock(); + publish_abort_idle_after_restore(&mut state, abort.session_id); + } + + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + abort.elapsed, + Some(message), + None, + ); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); +} + +pub(super) async fn start_recorder_and_enter_listening( + inner: &Arc, + session_id: SessionId, + active_asr: &str, + consumer: Arc, +) -> Result<(), String> { + start_recorder_for_starting(inner, session_id, active_asr, consumer).await?; + finish_starting_session(inner, session_id).await; + Ok(()) +} + +pub(super) async fn finish_starting_session(inner: &Arc, session_id: SessionId) { + // audit HIGH #1:转 Listening 之前在同一 lock 内检查 cancel race。 + // 之前是无条件 phase=Listening,会把 cancel_session 在 await 期间设的 Idle + // 反向覆盖回 Listening → 用户的 cancel 边沿被吞掉。 + let outcome = { + let mut state = inner.state.lock(); + finish_starting_session_state(&mut state, session_id) + }; + match outcome { + BeginOutcome::StaleContinuation => { + log::info!( + "[coord] stale recorder/ASR startup continuation from session {session_id} — ignoring" + ); + discard_startup_resources_for_session(inner, session_id); + restore_prepared_windows_ime_session(inner, session_id); + } + BeginOutcome::CancelRaced => { + log::info!("[coord] cancel raced during recorder/ASR startup — aborting begin"); + discard_startup_resources_for_session(inner, session_id); + restore_prepared_windows_ime_session(inner, session_id); + set_phase_idle_if_session_matches(inner, session_id); + } + BeginOutcome::Started | BeginOutcome::PendingStop => { + log::info!("[coord] session started"); + if matches!(outcome, BeginOutcome::PendingStop) { + log::info!("[coord] applying pending_stop edge → end_session immediately"); + let _ = end_session(inner).await; + } + } + } +} + +pub(super) async fn end_session(inner: &Arc) -> Result<(), String> { + let current_session_id = { + let mut state = inner.state.lock(); + let Some(session_id) = start_processing_if_listening(&mut state) else { + return Ok(()); + }; + session_id + }; + + let elapsed = inner.state.lock().started_at.elapsed().as_millis() as u64; + emit_capsule(inner, CapsuleState::Transcribing, 0.0, elapsed, None, None); + + if let Some(rec) = take_recorder_for_session(inner, current_session_id) { + rec.stop(); + release_recording_mute(inner, "dictation"); + } + + let asr_opt = take_asr_for_session(inner, current_session_id); + let asr = match asr_opt { + Some(a) => a, + None => { + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + return Ok(()); + } + }; + + let uses_global_timeout = asr_transcribe_uses_global_timeout(&asr); + let raw = match asr { + ActiveAsr::Volcengine(asr) => { + debug_assert!(uses_global_timeout); + if let Err(e) = asr.send_last_frame().await { + log::error!("[coord] send last frame failed: {e}"); + } + // 添加全局超时保护:防止 await_final_result() 永远挂起 + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] await final failed: {e}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + // 全局超时:最后的防线 + log::error!( + "[coord] 全局超时 {} 秒 - 强制恢复", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + // 清理 ASR session,避免资源泄漏 + asr.cancel(); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("global timeout".to_string()); + } + } + } + ActiveAsr::Whisper(w) => { + debug_assert!(uses_global_timeout); + // Whisper 也添加类似的超时保护 + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, w.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] whisper transcribe failed: {e}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] whisper 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("whisper global timeout".to_string()); + } + } + } + ActiveAsr::Mimo(m) => { + debug_assert!(uses_global_timeout); + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, m.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] MiMo ASR transcribe failed: {e}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] MiMo ASR 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("mimo global timeout".to_string()); + } + } + } + ActiveAsr::Bailian(asr) => { + debug_assert!(uses_global_timeout); + if let Err(e) = asr.send_last_frame().await { + log::error!("[coord] Bailian send last frame failed: {e}"); + } + let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS); + match tokio::time::timeout(timeout_duration, asr.await_final_result()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] Bailian await final failed: {e}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] Bailian 全局超时 {} 秒", + COORDINATOR_GLOBAL_TIMEOUT_SECS + ); + asr.cancel(); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("bailian global timeout".to_string()); + } + } + } + #[cfg(target_os = "windows")] + ActiveAsr::FoundryLocalWhisper(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(foundry_audio_transcribe_timeout_duration()) + .await + { + Ok(r) => { + schedule_foundry_local_asr_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + r + } + Err(e) => { + if inner.state.lock().cancelled { + log::info!( + "[coord] Foundry Local Whisper transcribe cancelled — discarding transcript" + ); + schedule_foundry_local_asr_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + log::error!("[coord] Foundry Local Whisper transcribe failed: {e:#}"); + schedule_foundry_local_asr_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("本地识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + } + } + // Windows sherpa-onnx offline batch:停止录音后整段转写,再复用现有 + // polish / insert / history 收尾路径。 + #[cfg(target_os = "windows")] + ActiveAsr::SherpaOnnxLocal(local) => { + debug_assert!(!uses_global_timeout); + match local + .transcribe(sherpa_audio_transcribe_timeout_duration()) + .await + { + Ok(r) => { + schedule_sherpa_onnx_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + r + } + Err(e) => { + if inner.state.lock().cancelled { + log::info!( + "[coord] sherpa-onnx transcribe cancelled — discarding transcript" + ); + schedule_sherpa_onnx_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + log::error!("[coord] sherpa-onnx transcribe failed: {e:#}"); + schedule_sherpa_onnx_release( + inner, + AsrReleaseSession::Dictation(current_session_id), + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("本地识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + } + } + #[cfg(target_os = "macos")] + ActiveAsr::Local(local) => { + debug_assert!(uses_global_timeout); + // 缓存命中时 transcribe 不含 load 时间;冷启动 load 已在 build_local_qwen3 + // 提前完成。但 transcribe 本身受音频长度影响:用户实测 RTF ≈ 0.3,慢机 + // 可达 0.5;15s 固定超时在 ≥ 30s 录音上会把整段结果丢掉。改用动态 + // 超时 max(15, ceil(audio_s × 0.6) + 10),公式与单测见 + // `local_qwen_transcribe_timeout`。 + let audio_secs = (local.buffer_duration_ms() as f64) / 1000.0; + let timeout_duration = local_qwen_transcribe_timeout(audio_secs); + log::info!( + "[coord] local Qwen3-ASR transcribe: audio={:.2}s timeout={}s", + audio_secs, + timeout_duration.as_secs() + ); + let result = tokio::time::timeout(timeout_duration, local.transcribe()).await; + inner.local_asr_cache.touch(); + schedule_local_asr_release(inner); + match result { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + log::error!("[coord] local Qwen3-ASR transcribe failed: {e:#}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("本地识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] local Qwen3-ASR 动态超时 {}s(音频 {:.2}s)", + timeout_duration.as_secs(), + audio_secs + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("local global timeout".to_string()); + } + } + } + // Apple Speech:系统语音识别,无模型加载耗时。批处理 transcribe 受音频 + // 长度影响,沿用 local_qwen_transcribe_timeout 的动态超时公式。 + #[cfg(target_os = "macos")] + ActiveAsr::AppleSpeech(local) => { + debug_assert!(uses_global_timeout); + let audio_secs = (local.buffer_duration_ms() as f64) / 1000.0; + let timeout_duration = local_qwen_transcribe_timeout(audio_secs); + log::info!( + "[coord] Apple Speech transcribe: audio={:.2}s timeout={}s", + audio_secs, + timeout_duration.as_secs() + ); + match tokio::time::timeout(timeout_duration, local.transcribe()).await { + Ok(Ok(r)) => r, + Ok(Err(e)) => { + if inner.state.lock().cancelled { + log::info!( + "[coord] Apple Speech transcribe cancelled - discarding transcript" + ); + restore_prepared_windows_ime_session(inner, current_session_id); + set_phase_idle_if_session_matches(inner, current_session_id); + return Ok(()); + } + log::error!("[coord] Apple Speech transcribe failed: {e:#}"); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some(format!("本地识别失败: {e}")), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err(e.to_string()); + } + Err(_) => { + log::error!( + "[coord] Apple Speech 动态超时 {}s(音频 {:.2}s)", + timeout_duration.as_secs(), + audio_secs + ); + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("识别超时".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("apple-speech global timeout".to_string()); + } + } + } + }; + + // ASR 完成后 cancel 检查:用户在 transcribe 进行中按 Esc 时,这里就会命中。 + // 优先级高于 empty 检查 — 用户取消 → 静默丢弃,不写失败历史也不弹错误胶囊。 + if inner.state.lock().cancelled { + log::info!("[coord] cancel detected after ASR — discarding transcript"); + restore_prepared_windows_ime_session(inner, current_session_id); + // PR #387 的「cancel 后清 focus_target」契约要在 Processing 路径上也成立。 + // cancel_session 在 Processing 阶段故意跳过 finish_cancel_session_state(让 + // 这里收尾),但此前的 end_session 没把 focus_target 清掉。logic-review + // 2026-05-10 P3 (🚩) 把这条补完。 + { + let mut state = inner.state.lock(); + state.phase = SessionPhase::Idle; + state.focus_target = None; + } + return Ok(()); + } + + // ASR 返回空转写护栏(来自 PR #66):写一条 emptyTranscript 失败历史 + 错误胶囊, + // 与 main 上其它 error 路径保持一致(带 schedule_capsule_idle 让胶囊自动消失)。 + let mut raw = raw; + + #[cfg(any(debug_assertions, test))] + if raw.text.trim().is_empty() { + if let Some(debug_text) = debug_transcript_override_text() { + log::info!( + "[coord] using debug transcript override (chars={})", + debug_text.chars().count() + ); + raw.text = debug_text; + } + } + + if raw.text.trim().is_empty() { + let session = DictationSession { + id: Uuid::new_v4().to_string(), + created_at: Utc::now().to_rfc3339(), + raw_transcript: raw.text.clone(), + final_text: String::new(), + mode: inner.prefs.get().default_mode, + style_pack_id: None, + translation_active: false, + polish_source: None, + app_bundle_id: None, + app_name: None, + insert_status: InsertStatus::Failed, + error_code: Some("emptyTranscript".to_string()), + duration_ms: Some(raw.duration_ms), + dictionary_entry_count: Some(enabled_phrases(inner).len() as u32), + // empty-transcript(ASR 没识别到任何文字)也保留 wav 标记——这是用户最想 + // 通过原始录音定位"是不是麦克风太小声 / ASR 模型问题"的场景。修 pr_agent + // "Missing Audio" 反馈。 + has_audio_recording: Some(inner.audio_archive_active.load(Ordering::Relaxed)), + }; + let prefs_snapshot = inner.prefs.get(); + if let Err(e) = inner.history.append_with_retention( + session, + prefs_snapshot.history_retention_days, + prefs_snapshot.history_max_entries, + ) { + log::error!("[coord] history append failed: {e}"); + } + emit_capsule( + inner, + CapsuleState::Error, + 0.0, + elapsed, + Some("没有识别到语音".to_string()), + None, + ); + restore_prepared_windows_ime_session(inner, current_session_id); + inner.state.lock().phase = SessionPhase::Idle; + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + return Err("ASR returned empty transcript".to_string()); + } + + let correction_rules = match inner.correction_rules.list() { + Ok(rules) => rules, + Err(e) => { + log::warn!("[coord] load correction rules failed: {e}; continue without correction"); + Vec::new() + } + }; + let front_app = inner.state.lock().front_app.clone(); + if !correction_rules.is_empty() { + let corrected = apply_correction_rules(&raw.text, &correction_rules); + if corrected != raw.text { + log::info!( + "[coord] correction rules adjusted raw transcript ({} → {} chars)", + raw.text.chars().count(), + corrected.chars().count() + ); + raw.text = corrected; + } + } + + // Cloud Agent 语音分流:长按升级的会话不走润色/插入,转写交给 Claude 跑任务、结果弹胶囊。 + if inner.state.lock().voice_agent { + return run_voice_agent_transcript(inner, current_session_id, raw.text.clone(), elapsed) + .await; + } + + emit_capsule(inner, CapsuleState::Polishing, 0.0, elapsed, None, None); + + let prefs = inner.prefs.get(); + let pack = match inner + .style_packs + .get_or_default_active(&prefs.active_style_pack_id) + { + Ok(pack) => pack, + Err(error) => { + log::warn!( + "[coord] active style pack unavailable, falling back to builtin light: {error}" + ); + crate::types::builtin_style_pack_for_mode(PolishMode::Light) + } + }; + let mode = pack.base_mode; + let hotword_strs = enabled_phrases(inner); + let working_languages = prefs.working_languages.clone(); + let chinese_script_preference = prefs.chinese_script_preference; + let output_language_preference = prefs.output_language_preference; + let llm_thinking_enabled = prefs.llm_thinking_enabled; + let style_system_prompt = pack.prompt.clone(); + let raw_uses_llm = mode == PolishMode::Raw && super::raw_style_pack_uses_llm(&pack); + let translation_target = prefs.translation_target_language.trim().to_string(); + let translation_active = + inner.translation_modifier_seen.load(Ordering::SeqCst) && !translation_target.is_empty(); + log::info!( + "[style-pack] runtime dispatch session_id={} active_pack={} kind={:?} mode={:?} raw_chars={} prompt_chars={} raw_uses_llm={} translation_active={} hotwords={} working_languages={:?}", + current_session_id, + pack.id, + pack.kind, + mode, + raw.text.chars().count(), + style_system_prompt.chars().count(), + raw_uses_llm, + translation_active, + hotword_strs.len(), + working_languages + ); + // 对话感知 polish:拉最近 N 分钟的会话作为 LLM 上下文。翻译现在也走"润色+翻译"单次 + // LLM 调用,所以翻译路径同样需要上下文;只有 Raw 且不走 LLM 才没意义。窗口=0 时为空 Vec。 + // 只复用同一 active style pack 的历史;翻译历史按当前是否翻译决定喂译文还是润色后源文 + // (见 eligible_polish_context_turns)。 + let polish_context_window_minutes = prefs.polish_context_window_minutes; + let prior_turns: Vec<(String, String)> = if (translation_active + || mode != PolishMode::Raw + || raw_uses_llm) + && polish_context_window_minutes > 0 + { + match inner + .history + .recent_within_minutes(polish_context_window_minutes) + { + Ok(sessions) => eligible_polish_context_turns(sessions, &pack.id, translation_active), + Err(e) => { + log::warn!("[coord] fetch polish context failed: {e}; fall back to single-turn"); + Vec::new() + } + } + } else { + Vec::new() + }; + // 流式插入 opt-in 路径:开关打开 + 非翻译 + 非 Raw 模式 → 进入流式分支。 + // 任何不满足都走原一次性 polish_or_passthrough 路径,行为跟历史完全一致。 + let streaming_eligible = streaming_insert_eligible( + prefs.streaming_insert, + translation_active, + mode, + raw_uses_llm, + ); + log::info!( + "[coord] polish dispatch: translation={translation_active} mode={mode:?} streaming_eligible={streaming_eligible}" + ); + + // Linux: emit_capsule(Polishing) 已通过 fcitx5 auxDown 显示 "✨ 润色中...", + // 无需在此重复调用。 + + // 翻译会话润色后的源语言文本(译文前的中间产物),仅翻译路径解析成功时有值, + // 写进 history 供后续普通润色轮复用(剔除译文、避免外语污染)。 + let mut polish_source: Option = None; + let (polished, polish_error, already_streamed) = if translation_active { + log::info!( + "[coord] translation mode → target=\u{300C}{}\u{300D} working={:?} front_app={:?}", + translation_target, + working_languages, + front_app + ); + let (p, src, e) = polish_and_translate_or_passthrough( + &raw, + &translation_target, + mode, + &hotword_strs, + &working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app.as_deref(), + &prior_turns, + ) + .await; + polish_source = src; + (p, e, false) + } else if streaming_eligible { + run_streaming_polish( + inner, + &raw, + mode, + &hotword_strs, + &style_system_prompt, + &working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app.as_deref(), + &prior_turns, + ) + .await + } else { + let (p, e) = polish_or_passthrough( + &raw, + mode, + &hotword_strs, + &style_system_prompt, + &working_languages, + chinese_script_preference, + output_language_preference, + llm_thinking_enabled, + front_app.as_deref(), + &prior_turns, + ) + .await; + (p, e, false) + }; + + let polished = finalize_polished_text( + polished, + translation_active, + raw_uses_llm, + mode, + &polish_error, + chinese_script_preference, + &correction_rules, + already_streamed, + ); + // 原子化最后一次 cancel 检查 + 转 Inserting: + // 在同一 lock 内决定「丢弃」还是「进入 Inserting」。一旦设到 Inserting, + // cancel_session 就拒绝介入(Cmd+V 已发出,撤销不掉)。这是 audit HIGH #2 的修复, + // 之前 check 与 inserter.insert 之间有窗口期。 + // + // 流式路径例外:`already_streamed = true` 表示字符已经一边流一边落到光标了, + // 撤销不掉。即使 cancel 旗在中途被立起来,也只能尊重「已经发生」的事实,进入 + // Inserting 状态完成 history / vocab 等收尾工作。 + let proceed_to_insert = { + let mut state = inner.state.lock(); + if state.cancelled && !already_streamed { + state.phase = SessionPhase::Idle; + false + } else { + state.phase = SessionPhase::Inserting; + true + } + }; + if !proceed_to_insert { + log::info!( + "[coord] cancel detected before insert — discarding output (chars={})", + polished.chars().count() + ); + restore_prepared_windows_ime_session(inner, current_session_id); + return Ok(()); + } + + let focus_target = inner.state.lock().focus_target; + let focus_ready_for_paste = restore_focus_target_if_possible(focus_target); + let prefs = inner.prefs.get(); + let restore_clipboard = prefs.restore_clipboard_after_paste; + let allow_non_tsf_insertion_fallback = prefs.allow_non_tsf_insertion_fallback; + let paste_shortcut = prefs.paste_shortcut; + // 流式路径下,字符已经通过 Unicode keystroke 落到光标处,跳过 inserter.insert。 + let status = if already_streamed { + log::info!( + "[coord] insertion skipped: {} chars already streamed via unicode_keystroke (polish_error={:?})", + polished.chars().count(), + polish_error + ); + InsertStatus::Inserted + } else { + #[cfg(target_os = "android")] + { + crate::android::android_insert_with_strategy( + &inner.inserter, + &polished, + inner.prefs.get().android_insert_strategy, + ) + } + #[cfg(not(target_os = "android"))] + if focus_ready_for_paste { + #[cfg(target_os = "windows")] + { + let ime_target = capture_ime_submit_target(); + insert_with_windows_ime_first( + inner, + current_session_id, + &polished, + restore_clipboard, + allow_non_tsf_insertion_fallback, + paste_shortcut, + ime_target, + ) + .await + } + #[cfg(not(target_os = "windows"))] + { + inner + .inserter + .insert(&polished, restore_clipboard, paste_shortcut) + } + } else { + #[cfg(target_os = "linux")] + { + // Linux: fcitx5 commitString 无需窗口焦点,始终尝试插入。 + inner + .inserter + .insert(&polished, restore_clipboard, paste_shortcut) + } + #[cfg(not(target_os = "linux"))] + { + log::warn!( + "[coord] original insertion target is not foreground; copied output without paste" + ); + if allow_non_tsf_insertion_fallback { + inner.inserter.copy_fallback(&polished) + } else { + InsertStatus::Failed + } + } + } + }; + restore_prepared_windows_ime_session(inner, current_session_id); + let inserted_chars = polished.chars().count() as u32; + + // 累计每条 enabled 词条在最终文本中的命中次数。 + // 用 polished(最终插入的文本)扫描,与用户实际看到的输出一致。 + let total_hits: u64 = match inner.vocab.record_hits(&polished) { + Ok(n) => n, + Err(e) => { + log::error!("[coord] record_hits failed: {e}"); + 0 + } + }; + // 词汇本页面在打开时通常需要立即看到 hits 增长,否则用户得手动切走再切回来才刷新。 + // 命中数 > 0 时通知前端:Vocab 页面订阅 vocab:updated 即时 listVocab() 重新加载。 + if total_hits > 0 { + if let Some(app) = inner.app.lock().clone() { + let _ = app.emit("vocab:updated", total_hits); + } + } + + // polish 失败时在 history 里标记 polishFailed,让用户能在历史详情看到为什么这次输出 + // 不是预期的 mode 风格。即使失败也不丢词 — final_text 仍是原文(保留"用户的话不丢"语义)。 + let error_code = dictation_error_code( + status, + polish_error.is_some(), + focus_ready_for_paste, + allow_non_tsf_insertion_fallback, + ) + .map(str::to_string); + let tsf_required_insert_failed = error_code.as_deref() == Some("windowsImeTsfRequired"); + + // 与 coordinator 内部 SessionId 对齐:方便 recorder 旁路写盘的 `.wav` + // 跟 history 这条 DictationSession.id 同名,前端凭 id 就能找到对应录音文件。 + let history_session_id = current_session_id.to_string(); + let history_created_at = Utc::now().to_rfc3339(); + let prefs_snapshot = inner.prefs.get(); + let session = DictationSession { + id: history_session_id.clone(), + created_at: history_created_at.clone(), + raw_transcript: raw.text.clone(), + final_text: polished.clone(), + mode, + style_pack_id: Some(pack.id.clone()), + translation_active, + polish_source, + app_bundle_id: None, + app_name: None, + insert_status: status, + error_code, + duration_ms: Some(raw.duration_ms), + // 历史详情页的"X 个热词"显示:用本次实际命中次数(每个匹配实例算一次), + // 比"启用词条总数"更能反映本段口述命中了多少。u64 → u32 截断对单段听写足够。 + dictionary_entry_count: Some(total_hits.min(u32::MAX as u64) as u32), + // 用 begin_session 时 Recorder::start 返回的实际写盘状态,而不是 prefs 开关—— + // 开关打开但路径创建失败时这里是 false,避免前端渲染播放按钮后端 404。 + has_audio_recording: Some(inner.audio_archive_active.load(Ordering::Relaxed)), + }; + if let Err(e) = inner.history.append_with_retention( + session, + prefs_snapshot.history_retention_days, + prefs_snapshot.history_max_entries, + ) { + log::error!("[coord] history append failed: {e}"); + } + let done_message = if tsf_required_insert_failed { + Some("TSF 未上屏,已禁止非 TSF 兜底".to_string()) + } else { + default_done_message(status, polish_error.is_some()) + }; + + emit_capsule( + inner, + CapsuleState::Done, + 0.0, + elapsed, + done_message, + Some(inserted_chars), + ); + + { + let mut state = inner.state.lock(); + state.phase = SessionPhase::Idle; + state.focus_target = None; + } + // Toggle 模式冷却:设冷却时间戳,POST_SESSION_COOLDOWN_MS 内禁止新的 activate。 + // 覆盖胶囊离场动画周期,避免三连按第 3 次误激活(issue #545)。 + { + let now = std::time::Instant::now(); + *inner.session_cooldown_until.lock() = + Some(now + std::time::Duration::from_millis(POST_SESSION_COOLDOWN_MS)); + } + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + + Ok(()) +} + +pub(super) fn dictation_error_code( + status: InsertStatus, + polish_failed: bool, + focus_ready_for_paste: bool, + allow_non_tsf_insertion_fallback: bool, +) -> Option<&'static str> { + if !focus_ready_for_paste && status == InsertStatus::Failed { + Some("focusRestoreFailed") + } else if cfg!(target_os = "windows") + && focus_ready_for_paste + && !allow_non_tsf_insertion_fallback + && status == InsertStatus::Failed + { + Some("windowsImeTsfRequired") + } else if polish_failed { + Some("polishFailed") + } else { + None + } +} + +pub(super) fn cancel_session(inner: &Arc) { + let Some(decision) = ({ + let mut state = inner.state.lock(); + let phase = state.phase; + let decision = begin_cancel_session_state(&mut state); + if phase == SessionPhase::Inserting { + log::info!("[coord] cancel ignored — already in Inserting phase, can't undo paste"); + } + decision + }) else { + return; + }; + + stop_recorder_for_session(inner, decision.session_id); + cancel_asr_for_session(inner, decision.session_id); + restore_prepared_windows_ime_session(inner, decision.session_id); + // Processing 阶段保持 phase=Processing 让 end_session 自己走完检查 + 收尾; + // 其他阶段直接转 Idle。 + if decision.phase != SessionPhase::Processing { + let mut state = inner.state.lock(); + finish_cancel_session_state(&mut state, decision); + // 只有真正把 phase 设为 Idle 时才设冷却(避免离场动画期间误激活)。 + let now = std::time::Instant::now(); + *inner.session_cooldown_until.lock() = + Some(now + std::time::Duration::from_millis(POST_SESSION_COOLDOWN_MS)); + } + emit_capsule(inner, CapsuleState::Cancelled, 0.0, 0, None, None); + log::info!("[coord] session cancelled (was {:?})", decision.phase); + schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); + // 取消时也熄灭整屏彩虹描边(dictation session 没开描边,hide 是无害 no-op)。 + if let Some(app) = inner.app.lock().clone() { + crate::hide_less_computer_glow(&app); + } +} + +fn append_typed_prefix(target: &mut String, delta: &str, typed_chars: usize) -> usize { + let mut end = 0; + let mut appended = 0; + for (idx, ch) in delta.char_indices().take(typed_chars) { + end = idx + ch.len_utf8(); + appended += 1; + } + target.push_str(&delta[..end]); + appended +} + +fn eligible_polish_context_turns( + sessions: Vec, + active_style_pack_id: &str, + current_translation_active: bool, +) -> Vec<(String, String)> { + sessions + .into_iter() + // 只取实际成功润色过的会话作为上下文:失败的会话 final_text 是 raw 兜底, + // 喂回 LLM 会让模型以为"上一轮我什么都没做"——没意义且占 token。 + // 这条同时保证下面 filter_map 里翻译历史的 final_text 一定是真译文(而非 passthrough + // 原文)——失败 / 兜底的翻译会话 error_code 非空,已在此被滤掉。 + .filter(|s| s.error_code.is_none() && !s.final_text.trim().is_empty()) + // 风格包切换 = 上下文边界。旧历史没有 style_pack_id,无法证明同源,保守排除。 + .filter(|s| s.style_pack_id.as_deref() == Some(active_style_pack_id)) + // 翻译历史按"下一轮是否也翻译"决定喂哪一段,既保留对话连续性又不让译文串味: + // - 当前是翻译轮 → 喂译文(final_text),保持目标语言一致; + // - 当前是普通轮 → 喂润色后的源文(polish_source),把译文剔除掉;源文缺失(解析 + // 失败 / 旧历史)则整条跳过——宁可少一条上下文,也不让外语译文混进普通润色。 + // - 普通历史无论当前轮是什么,都喂 final_text(本就是源语言润色结果)。 + .filter_map(|s| { + if s.translation_active && !current_translation_active { + s.polish_source + .filter(|src| !src.trim().is_empty()) + .map(|src| (s.raw_transcript, src)) + } else { + Some((s.raw_transcript, s.final_text)) + } + }) + .collect() +} + #[cfg(test)] mod tests { - use super::batch_asr_chunk_limit_ms; - use crate::coordinator::{ - append_typed_prefix, default_done_message, drain_streaming_insert_deltas_with, - eligible_polish_context_turns, finalize_polished_text, flush_streaming_insert_buffer_with, - streaming_insert_eligible, + use super::{ + append_typed_prefix, batch_asr_chunk_limit_ms, default_done_message, + drain_streaming_insert_deltas_with, eligible_polish_context_turns, finalize_polished_text, + flush_streaming_insert_buffer_with, streaming_insert_eligible, }; use crate::types::{ ChineseScriptPreference, CorrectionRule, DictationSession, InsertStatus, PolishMode, @@ -341,50 +2752,9 @@ mod tests { false, PolishMode::Light, false, - crate::types::ChineseScriptPreference::Auto, )); } - // issue #622:非 Auto 字形偏好必须关闭流式,改走会做字形转换的一次性路径。 - #[test] - fn streaming_insert_ineligible_when_chinese_script_forced() { - for pref in [ - crate::types::ChineseScriptPreference::Traditional, - crate::types::ChineseScriptPreference::Simplified, - ] { - assert!(!streaming_insert_eligible( - true, - false, - PolishMode::Light, - false, - pref, - )); - } - } - - // issue #622:非 Auto + 成功 LLM 润色(非流式、无 error)时,最终插入文字 - // 必须套用字形转换,不能只靠 prompt 指示。覆盖 issue 给出的两个验收用例。 - #[test] - fn finalize_forces_traditional_even_on_successful_polish() { - let cases = [ - ("你知道你今天想要做什么吗?", "你知道你今天想要做什麼嗎?"), - ("所以你已经考过了吗?", "所以你已經考過了嗎?"), - ]; - for (input, expected) in cases { - let out = finalize_polished_text( - input.to_string(), - false, // translation_active - true, // raw_uses_llm - PolishMode::Light, // 成功润色路径(非 Raw、非 error) - &None, // 无 polish_error - crate::types::ChineseScriptPreference::Traditional, - &[], // 无 correction rules - false, // already_streamed - ); - assert_eq!(out, expected); - } - } - #[test] fn batch_asr_chunk_limit_applies_only_to_zhipu() { assert_eq!(batch_asr_chunk_limit_ms("zhipu"), Some(30_000)); @@ -463,4 +2833,9 @@ mod tests { fn platform_type_error() -> crate::unicode_keystroke::TypeError { crate::unicode_keystroke::TypeError::EnigoText("fail".into()) } + + #[cfg(target_os = "android")] + fn platform_type_error() -> crate::unicode_keystroke::TypeError { + crate::unicode_keystroke::TypeError::Unavailable + } } diff --git a/openless-all/app/src-tauri/src/coordinator/qa.rs b/openless-all/app/src-tauri/src/coordinator/qa.rs index 971622d4..a97473ce 100644 --- a/openless-all/app/src-tauri/src/coordinator/qa.rs +++ b/openless-all/app/src-tauri/src/coordinator/qa.rs @@ -8,7 +8,7 @@ use crate::types::CapsuleState; use super::{ begin_qa_session, cancel_qa_session, capture_focus_target, capture_frontmost_app, emit_capsule, - end_qa_session, Inner, + end_qa_session, qa_event_target, Inner, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -108,7 +108,7 @@ pub(super) fn open_qa_panel(inner: &Arc) { if let Some(app) = inner.app.lock().clone() { crate::show_qa_window(&app, "idle"); let _ = app.emit_to( - "qa", + qa_event_target(), "qa:state", serde_json::json!({ "kind": "idle", diff --git a/openless-all/app/src-tauri/src/coordinator/resources.rs b/openless-all/app/src-tauri/src/coordinator/resources.rs index 71183687..03184636 100644 --- a/openless-all/app/src-tauri/src/coordinator/resources.rs +++ b/openless-all/app/src-tauri/src/coordinator/resources.rs @@ -104,14 +104,21 @@ pub(super) fn selected_microphone_device_name(inner: &Arc) -> Option, owner: &str) { - let Some(app) = inner.app.lock().as_ref().cloned() else { - return; - }; - let state = app.state::(); - let recorder = state.lock().take(); - if let Some(recorder) = recorder { - log::info!("[recorder] stopping microphone preview monitor before {owner}"); - recorder.stop(); + #[cfg(mobile)] + { + let _ = (inner, owner); + } + #[cfg(not(mobile))] + { + let Some(app) = inner.app.lock().as_ref().cloned() else { + return; + }; + let state = app.state::(); + let recorder = state.lock().take(); + if let Some(recorder) = recorder { + log::info!("[recorder] stopping microphone preview monitor before {owner}"); + recorder.stop(); + } } } diff --git a/openless-all/app/src-tauri/src/external_url.rs b/openless-all/app/src-tauri/src/external_url.rs new file mode 100644 index 00000000..0fdebeab --- /dev/null +++ b/openless-all/app/src-tauri/src/external_url.rs @@ -0,0 +1,70 @@ +pub fn open_external_url(url: &str) -> Result<(), String> { + let parsed = url::Url::parse(url).map_err(|error| format!("invalid URL: {error}"))?; + match parsed.scheme() { + "http" | "https" => {} + scheme => return Err(format!("unsupported URL scheme: {scheme}")), + } + + platform_open_external_url(parsed.as_str()) +} + +#[cfg(target_os = "android")] +fn platform_open_external_url(url: &str) -> Result<(), String> { + use jni::objects::{JObject, JValue}; + + let android_context = ndk_context::android_context(); + let vm = unsafe { + jni::JavaVM::from_raw(android_context.vm().cast()) + .map_err(|error| format!("attach Android JVM: {error}"))? + }; + let mut env = vm + .attach_current_thread() + .map_err(|error| format!("attach Android thread: {error}"))?; + let context = unsafe { JObject::from_raw(android_context.context() as jni::sys::jobject) }; + + let action = env + .new_string("android.intent.action.VIEW") + .map_err(|error| format!("create Intent action: {error}"))?; + let url = env + .new_string(url) + .map_err(|error| format!("create URL string: {error}"))?; + let uri = env + .call_static_method( + "android/net/Uri", + "parse", + "(Ljava/lang/String;)Landroid/net/Uri;", + &[JValue::Object(&JObject::from(url))], + ) + .and_then(|value| value.l()) + .map_err(|error| format!("parse URL into Android Uri: {error}"))?; + let intent = env + .new_object( + "android/content/Intent", + "(Ljava/lang/String;Landroid/net/Uri;)V", + &[JValue::Object(&JObject::from(action)), JValue::Object(&uri)], + ) + .map_err(|error| format!("create Android Intent: {error}"))?; + + // Context may be an application context; NEW_TASK keeps startActivity valid there. + env.call_method( + &intent, + "addFlags", + "(I)Landroid/content/Intent;", + &[JValue::Int(0x10000000)], + ) + .map_err(|error| format!("set Android Intent flags: {error}"))?; + env.call_method( + &context, + "startActivity", + "(Landroid/content/Intent;)V", + &[JValue::Object(&intent)], + ) + .map_err(|error| format!("start Android URL activity: {error}"))?; + + Ok(()) +} + +#[cfg(not(target_os = "android"))] +fn platform_open_external_url(_url: &str) -> Result<(), String> { + Err("native external URL fallback is only wired on Android".to_string()) +} diff --git a/openless-all/app/src-tauri/src/insertion.rs b/openless-all/app/src-tauri/src/insertion.rs index 3ecdc4e9..d4c5ac18 100644 --- a/openless-all/app/src-tauri/src/insertion.rs +++ b/openless-all/app/src-tauri/src/insertion.rs @@ -5,15 +5,19 @@ //! - macOS:用 CoreGraphics CGEvent 直接 post Cmd+V。 //! - Windows / Linux:用 enigo 按 `PasteShortcut` 模拟。 +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] use std::sync::atomic::{AtomicU64, Ordering}; +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] use std::time::Duration; +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] use once_cell::sync::Lazy; +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] use parking_lot::Mutex; use crate::types::{InsertStatus, PasteShortcut}; -/// 粘贴完成到尝试恢复剪贴板之间的延迟,给目标应用读取剪贴板留出时间。 +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] const CLIPBOARD_RESTORE_DELAY: Duration = Duration::from_millis(750); pub struct TextInserter; @@ -56,7 +60,7 @@ impl TextInserter { insert_with_clipboard_restore(text, restore_clipboard_after_paste, paste_shortcut) } - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] pub fn insert_via_clipboard_fallback( &self, text: &str, @@ -83,37 +87,35 @@ impl TextInserter { } } - /// macOS 路径:保存原剪贴板 → 写转写文字 → post Cmd+V → 按需恢复原剪贴板。 - /// `paste_shortcut` 在 macOS 不使用(固定 Cmd+V),仅为对齐跨平台签名。 + /// macOS 路径:写剪贴板 + post Cmd+V。两个 `_` 参数仅为对齐跨平台签名。 #[cfg(target_os = "macos")] pub fn insert( &self, text: &str, restore_clipboard_after_paste: bool, - _paste_shortcut: PasteShortcut, + paste_shortcut: PasteShortcut, ) -> InsertStatus { if text.is_empty() { return InsertStatus::CopiedFallback; } - // issue #525:先记下用户原剪贴板,粘贴成功后再恢复,避免覆盖用户手动复制的内容。 - // 此前 macOS 完全不实现恢复(恢复机制曾被 cfg(not macos) 排除),导致设置里的 - // 「恢复剪贴板」开关在 macOS 上无效。 - let restore_plan = match copy_to_clipboard_with_restore_plan(text) { - Ok(plan) => plan, - Err(err) => { - log::error!("[insertion] clipboard write failed: {}", err); - return InsertStatus::Failed; - } - }; - if let Err(err) = simulate_paste() { - log::warn!("[insertion] simulated paste failed: {}", err); - // 粘贴失败:把转写文字留在剪贴板供用户手动粘贴,不恢复。 - return InsertStatus::CopiedFallback; + if !copy_to_clipboard(text) { + return InsertStatus::Failed; } - if restore_clipboard_after_paste { - schedule_clipboard_restore(restore_plan); + macos_insert_status_after_paste(simulate_paste()) + } + + /// Android:跨应用输入由 dictation 流程按用户策略处理;通用插入只写剪贴板兜底。 + #[cfg(target_os = "android")] + pub fn insert( + &self, + text: &str, + _restore_clipboard_after_paste: bool, + _paste_shortcut: PasteShortcut, + ) -> InsertStatus { + if text.is_empty() { + return InsertStatus::CopiedFallback; } - insertion_success_status() + self.copy_fallback(text) } /// 只写剪贴板、不模拟粘贴。用于目标控件活跃状态无法验证时的兜底路径。 @@ -155,29 +157,45 @@ where } } +#[cfg(target_os = "macos")] +fn macos_insert_status_after_paste(result: Result<(), String>) -> InsertStatus { + match result { + Ok(()) => insertion_success_status(), + Err(err) => { + log::warn!("[insertion] simulated paste failed: {}", err); + InsertStatus::CopiedFallback + } + } +} + impl Default for TextInserter { fn default() -> Self { Self::new() } } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] #[derive(Debug)] struct ClipboardRestorePlan { inserted_text: String, previous_text: Option, } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] #[derive(Debug, Clone)] struct PendingClipboardRestore { latest_restore_id: u64, original_text: Option, } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] static NEXT_CLIPBOARD_RESTORE_ID: AtomicU64 = AtomicU64::new(1); +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] static PENDING_CLIPBOARD_RESTORE: Lazy>> = Lazy::new(|| Mutex::new(None)); +#[cfg(not(any(target_os = "android", target_os = "ios")))] fn copy_to_clipboard(text: &str) -> bool { let mut clipboard = match arboard::Clipboard::new() { Ok(c) => c, @@ -193,6 +211,28 @@ fn copy_to_clipboard(text: &str) -> bool { true } +#[cfg(any(target_os = "android", target_os = "ios"))] +fn copy_to_clipboard(text: &str) -> bool { + #[cfg(target_os = "android")] + { + return crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::copy_to_clipboard(env, context, text) + }) + .unwrap_or_else(|error| { + log::error!("[insertion] android clipboard failed: {error}"); + false + }); + } + + #[cfg(target_os = "ios")] + { + let _ = text; + log::warn!("[insertion] mobile clipboard fallback unavailable"); + false + } +} + +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn copy_to_clipboard_with_restore_plan(text: &str) -> Result { let mut clipboard = arboard::Clipboard::new().map_err(|e| e.to_string())?; let previous_text = match clipboard.get_text() { @@ -214,7 +254,7 @@ fn copy_to_clipboard_with_restore_plan(text: &str) -> Result) -> (u64, Option) { let restore_id = NEXT_CLIPBOARD_RESTORE_ID.fetch_add(1, Ordering::SeqCst); let original_text = { @@ -264,6 +306,7 @@ fn remember_pending_clipboard_restore(previous_text: Option) -> (u64, Op (restore_id, original_text) } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn restore_clipboard_after_delay( plan: ClipboardRestorePlan, original_text: Option, @@ -314,6 +357,7 @@ fn restore_clipboard_after_delay( clear_pending_clipboard_restore(restore_id); } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn is_latest_clipboard_restore(restore_id: u64) -> bool { matches!( PENDING_CLIPBOARD_RESTORE.lock().as_ref(), @@ -321,6 +365,7 @@ fn is_latest_clipboard_restore(restore_id: u64) -> bool { ) } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn clear_pending_clipboard_restore(restore_id: u64) { let mut pending = PENDING_CLIPBOARD_RESTORE.lock(); if matches!(pending.as_ref(), Some(batch) if batch.latest_restore_id == restore_id) { @@ -328,6 +373,7 @@ fn clear_pending_clipboard_restore(restore_id: u64) { } } +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn should_restore_clipboard(current_text: Option<&str>, inserted_text: &str) -> bool { matches!(current_text, Some(current) if current == inserted_text) } @@ -344,7 +390,7 @@ fn simulate_paste() -> Result<(), String> { } /// 把 `PasteShortcut` 拆成 `(modifiers, primary)`,顺序决定按下/释放顺序。 -#[cfg(not(target_os = "macos"))] +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn paste_keys(shortcut: PasteShortcut) -> (Vec, enigo::Key) { use enigo::Key; match shortcut { @@ -354,7 +400,7 @@ fn paste_keys(shortcut: PasteShortcut) -> (Vec, enigo::Key) { } } -#[cfg(not(target_os = "macos"))] +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn simulate_paste(shortcut: PasteShortcut) -> Result<(), String> { use enigo::{Direction, Enigo, Keyboard, Settings}; let (modifiers, primary) = paste_keys(shortcut); @@ -398,7 +444,7 @@ fn insertion_success_status() -> InsertStatus { InsertStatus::Inserted } -#[cfg(not(target_os = "macos"))] +#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn insertion_success_status() -> InsertStatus { InsertStatus::PasteSent } @@ -548,6 +594,7 @@ mod tests { use std::time::Duration; #[test] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn restore_only_when_clipboard_still_holds_inserted_text() { assert!(should_restore_clipboard( Some("dictated text"), @@ -562,7 +609,7 @@ mod tests { /// 配置的快捷键必须真实映射到对应按键。只比较 modifier 数 + 主键,规避 enigo 内部 PartialEq。 #[test] - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn paste_keys_match_configured_shortcut() { use enigo::Key; @@ -591,7 +638,7 @@ mod tests { inserter.insert("", true, PasteShortcut::CtrlV), InsertStatus::CopiedFallback ); - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] { assert_eq!( inserter.insert_via_clipboard_fallback("", true, PasteShortcut::CtrlV), @@ -677,7 +724,7 @@ mod tests { } #[test] - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn pending_clipboard_restore_keeps_first_original_until_latest_restore() { *PENDING_CLIPBOARD_RESTORE.lock() = None; @@ -699,7 +746,7 @@ mod tests { } #[test] - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] fn clipboard_restore_skips_when_clipboard_no_longer_matches_inserted_text() { assert!(should_restore_clipboard( Some("dictated text"), @@ -714,17 +761,15 @@ mod tests { #[test] #[cfg(target_os = "macos")] - fn macos_paste_success_reports_inserted_and_guards_restore() { - // 粘贴成功 → Inserted;恢复仅在剪贴板仍是刚插入的转写文字时进行(issue #525)。 - assert_eq!(insertion_success_status(), InsertStatus::Inserted); - assert!(should_restore_clipboard( - Some("dictated text"), - "dictated text" - )); - assert!(!should_restore_clipboard( - Some("user changed clipboard"), - "dictated text" - )); + fn macos_direct_write_or_paste_failure_keeps_copied_fallback_available() { + assert_eq!( + macos_insert_status_after_paste(Ok(())), + InsertStatus::Inserted + ); + assert_eq!( + macos_insert_status_after_paste(Err("AX direct write unavailable".to_string())), + InsertStatus::CopiedFallback + ); } #[test] diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 9f6bc010..021ee9fa 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -14,35 +14,70 @@ //! - coordinator: dictation state machine glue //! - commands: Tauri IPC surface +mod android; mod asr; mod audio_mute; mod cli; mod coding_agent; +#[cfg(not(mobile))] +mod combo_hotkey; +#[cfg(mobile)] +#[path = "mobile_stubs/combo_hotkey.rs"] mod combo_hotkey; mod commands; mod coordinator; mod coordinator_state; mod correction; +mod external_url; +#[cfg(not(mobile))] mod global_hotkey_runtime; +#[cfg(not(mobile))] +#[path = "hotkey.rs"] +mod hotkey; +#[cfg(mobile)] +#[path = "mobile_stubs/hotkey.rs"] mod hotkey; mod insertion; #[cfg(target_os = "linux")] mod linux_fcitx; mod llm_gemini; +#[cfg(mobile)] +mod mobile_runtime; mod net; mod permissions; mod persistence; mod polish; +#[cfg(not(mobile))] +mod qa_hotkey; +#[cfg(mobile)] +#[path = "mobile_stubs/qa_hotkey.rs"] mod qa_hotkey; mod recorder; +#[cfg(not(mobile))] mod remote_server; +#[cfg(not(mobile))] +#[path = "selection.rs"] mod selection; +#[cfg(mobile)] +#[path = "mobile_stubs/selection.rs"] +mod selection; +#[cfg(not(mobile))] +mod shortcut_binding; +#[cfg(mobile)] +#[path = "mobile_stubs/shortcut_binding.rs"] mod shortcut_binding; mod types; +#[cfg(not(mobile))] +mod unicode_keystroke; +#[cfg(mobile)] +#[path = "mobile_stubs/unicode_keystroke.rs"] mod unicode_keystroke; +#[cfg(target_os = "windows")] mod windows_ime_ipc; mod windows_ime_profile; +#[cfg(target_os = "windows")] mod windows_ime_protocol; +#[cfg(target_os = "windows")] mod windows_ime_session; use std::sync::atomic::{AtomicBool, Ordering}; @@ -58,10 +93,13 @@ const OPENLESS_BUNDLE_ID: &str = "com.openless.app"; /// 第一次 show 时把 QA 浮窗摆到屏幕底部居中;之后的 show 不再 reposition, /// 让用户拖动后的位置在 hide → show 之间得以保持。详见 issue #118 v2。 static QA_WINDOW_POSITIONED: AtomicBool = AtomicBool::new(false); +#[cfg(not(mobile))] static TRAY_MICROPHONE_WATCHER_STOPPING: AtomicBool = AtomicBool::new(false); +#[cfg(not(mobile))] use tauri::menu::{ CheckMenuItemBuilder, Menu, MenuBuilder, MenuItemBuilder, Submenu, SubmenuBuilder, }; +#[cfg(not(mobile))] use tauri::tray::{MouseButton, TrayIconBuilder, TrayIconEvent}; use tauri::{ AppHandle, Emitter, LogicalPosition, LogicalSize, Manager, PhysicalPosition, PhysicalSize, @@ -72,6 +110,267 @@ use crate::types::PolishMode; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(mobile)] + { + mobile_runtime::run(); + return; + } + #[cfg(not(mobile))] + run_desktop(); +} + +macro_rules! app_invoke_handler_desktop { + () => { + tauri::generate_handler![ + commands::get_settings, + commands::get_default_style_system_prompts, + commands::set_settings, + commands::get_remote_input_status, + commands::list_local_ips, + commands::regenerate_remote_pin, + commands::set_remote_locale, + commands::get_update_channel, + commands::set_update_channel, + commands::fetch_latest_beta_release, + commands::app_check_update_with_channel, + commands::check_network, + commands::get_hotkey_status, + commands::get_hotkey_capability, + commands::set_shortcut_recording_active, + commands::get_windows_ime_status, + commands::get_platform_capabilities, + commands::get_android_overlay_status, + commands::request_android_overlay_permission, + commands::show_android_overlay, + commands::hide_android_overlay, + commands::get_android_accessibility_status, + commands::request_android_accessibility_permission, + commands::open_external_url, + commands::list_microphone_devices, + commands::start_microphone_level_monitor, + commands::stop_microphone_level_monitor, + commands::get_credentials, + commands::set_credential, + commands::list_history, + commands::delete_history_entry, + commands::clear_history, + commands::read_audio_recording, + commands::retranscribe_recording, + commands::marketplace_list, + commands::marketplace_detail, + commands::marketplace_install, + commands::marketplace_upload, + commands::marketplace_like, + commands::marketplace_my_likes, + commands::marketplace_my_packs, + commands::marketplace_delete, + commands::github_device_flow_start, + commands::github_device_flow_poll, + commands::list_vocab, + commands::add_vocab, + commands::remove_vocab, + commands::set_vocab_enabled, + commands::list_correction_rules, + commands::add_correction_rule, + commands::remove_correction_rule, + commands::set_correction_rule_enabled, + commands::list_vocab_presets, + commands::save_vocab_presets, + commands::start_dictation, + commands::stop_dictation, + commands::cancel_dictation, + coding_agent::commands::coding_agent_detect, + coding_agent::commands::coding_agent_run_test, + coding_agent::commands::coding_agent_cancel_test, + coding_agent::commands::coding_agent_command_risk, + commands::handle_window_hotkey_event, + #[cfg(debug_assertions)] + commands::inject_hotkey_click_for_dev, + commands::repolish, + commands::list_style_packs, + commands::create_style_pack_from_template, + commands::save_style_pack, + commands::preview_style_pack_runtime, + commands::set_active_style_pack, + commands::set_style_pack_enabled, + commands::reset_builtin_style_pack, + commands::delete_style_pack, + commands::import_style_pack_from_zip, + commands::export_style_pack_to_zip, + commands::set_default_polish_mode, + commands::set_style_enabled, + commands::check_accessibility_permission, + commands::request_accessibility_permission, + commands::check_microphone_permission, + commands::request_microphone_permission, + commands::open_system_settings, + commands::trigger_microphone_prompt, + commands::read_credential, + commands::set_active_asr_provider, + commands::set_active_llm_provider, + commands::get_qa_hotkey_label, + commands::set_qa_hotkey, + commands::validate_shortcut_binding, + commands::set_dictation_hotkey, + commands::set_translation_hotkey, + commands::set_switch_style_hotkey, + commands::set_open_app_hotkey, + commands::qa_window_dismiss, + commands::qa_window_pin, + commands::less_computer_window_dismiss, + commands::less_computer_window_resize, + commands::less_computer_approve, + commands::validate_combo_hotkey, + commands::set_combo_hotkey, + commands::validate_provider_credentials, + commands::list_provider_models, + commands::local_asr_get_settings, + commands::local_asr_storage_settings, + commands::local_asr_set_models_base_dir, + commands::local_asr_set_active_model, + commands::local_asr_set_mirror, + commands::local_asr_list_models, + commands::local_asr_fetch_remote_info, + commands::local_asr_download_model, + commands::local_asr_cancel_download, + commands::local_asr_delete_model, + commands::local_asr_model_dir, + commands::local_asr_reveal_model_dir, + commands::local_asr_reveal_models_root, + commands::local_asr_test_model, + commands::local_asr_engine_status, + commands::local_asr_release_engine, + commands::local_asr_preload, + commands::local_asr_set_keep_loaded_secs, + commands::foundry_local_asr_status, + commands::foundry_local_asr_catalog, + commands::foundry_local_asr_set_model, + commands::foundry_local_asr_set_language_hint, + commands::foundry_local_asr_set_runtime_source, + commands::foundry_local_asr_prepare, + commands::foundry_local_asr_cancel_prepare, + commands::foundry_local_asr_release, + commands::foundry_local_asr_model_dir, + commands::foundry_local_asr_delete_model, + commands::foundry_local_asr_reveal_model_dir, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_status, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_catalog, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_fetch_remote_info, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_download_model, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_cancel_download, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_set_model, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_set_language_hint, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_prepare, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_cancel_prepare, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_release, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_model_dir, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_delete_model, + #[cfg(target_os = "windows")] + commands::sherpa_onnx_asr_reveal_model_dir, + commands::export_error_log, + restart_app, + ] + }; +} + +/// Android/iOS: only commands usable without desktop hotkeys, tray, updater, or local ASR. +#[macro_export] +macro_rules! app_invoke_handler_mobile { + () => { + tauri::generate_handler![ + $crate::commands::get_settings, + $crate::commands::get_default_style_system_prompts, + $crate::commands::set_settings, + $crate::commands::check_network, + $crate::commands::get_platform_capabilities, + $crate::commands::get_android_overlay_status, + $crate::commands::request_android_overlay_permission, + $crate::commands::show_android_overlay, + $crate::commands::hide_android_overlay, + $crate::commands::get_android_accessibility_status, + $crate::commands::request_android_accessibility_permission, + $crate::commands::open_external_url, + $crate::commands::list_microphone_devices, + $crate::commands::start_microphone_level_monitor, + $crate::commands::stop_microphone_level_monitor, + $crate::commands::get_credentials, + $crate::commands::set_credential, + $crate::commands::read_credential, + $crate::commands::set_active_asr_provider, + $crate::commands::set_active_llm_provider, + $crate::commands::validate_provider_credentials, + $crate::commands::list_provider_models, + $crate::commands::list_history, + $crate::commands::delete_history_entry, + $crate::commands::clear_history, + $crate::commands::read_audio_recording, + $crate::commands::retranscribe_recording, + $crate::commands::marketplace_list, + $crate::commands::marketplace_detail, + $crate::commands::marketplace_install, + $crate::commands::marketplace_upload, + $crate::commands::marketplace_like, + $crate::commands::marketplace_my_likes, + $crate::commands::marketplace_my_packs, + $crate::commands::marketplace_delete, + $crate::commands::github_device_flow_start, + $crate::commands::github_device_flow_poll, + $crate::commands::list_vocab, + $crate::commands::add_vocab, + $crate::commands::remove_vocab, + $crate::commands::set_vocab_enabled, + $crate::commands::list_correction_rules, + $crate::commands::add_correction_rule, + $crate::commands::remove_correction_rule, + $crate::commands::set_correction_rule_enabled, + $crate::commands::list_vocab_presets, + $crate::commands::save_vocab_presets, + $crate::commands::start_dictation, + $crate::commands::stop_dictation, + $crate::commands::cancel_dictation, + $crate::commands::qa_window_dismiss, + $crate::commands::qa_window_pin, + $crate::commands::qa_toggle_recording, + $crate::commands::qa_submit_text, + $crate::commands::repolish, + $crate::commands::list_style_packs, + $crate::commands::create_style_pack_from_template, + $crate::commands::save_style_pack, + $crate::commands::preview_style_pack_runtime, + $crate::commands::set_active_style_pack, + $crate::commands::set_style_pack_enabled, + $crate::commands::reset_builtin_style_pack, + $crate::commands::delete_style_pack, + $crate::commands::import_style_pack_from_zip, + $crate::commands::export_style_pack_to_zip, + $crate::commands::set_default_polish_mode, + $crate::commands::set_style_enabled, + $crate::commands::check_accessibility_permission, + $crate::commands::request_accessibility_permission, + $crate::commands::check_microphone_permission, + $crate::commands::request_microphone_permission, + $crate::commands::open_system_settings, + $crate::commands::trigger_microphone_prompt, + $crate::commands::export_error_log, + $crate::restart_app, + ] + }; +} + +#[cfg(not(mobile))] +fn run_desktop() { let foundry_local_runtime = Arc::new(asr::local::FoundryLocalRuntime::new()); let sherpa_onnx_runtime = Arc::new(asr::local::SherpaOnnxRuntime::new()); let sherpa_download_manager = @@ -328,8 +627,6 @@ pub fn run() { let app_handle = app.handle().clone(); coordinator.bind_app(app_handle); coordinator.start_hotkey_listener(); - // 远程输入:按 prefs 启动局域网录音服务(未启用时为 no-op)。 - coordinator.refresh_remote_server(); // QA / custom combo hotkeys use `global-hotkey` (Carbon on macOS). // Start those after RunEvent::Ready, when the AppKit event loop is live. if std::env::var("OPENLESS_SHOW_MAIN_ON_START").ok().as_deref() == Some("1") { @@ -346,159 +643,7 @@ pub fn run() { Ok(()) }) - .invoke_handler(tauri::generate_handler![ - commands::get_settings, - commands::get_default_style_system_prompts, - commands::set_settings, - commands::get_remote_input_status, - commands::list_local_ips, - commands::regenerate_remote_pin, - commands::set_remote_locale, - commands::get_update_channel, - commands::set_update_channel, - commands::fetch_latest_beta_release, - commands::app_check_update_with_channel, - commands::check_network, - commands::get_hotkey_status, - commands::get_hotkey_capability, - commands::set_shortcut_recording_active, - commands::get_windows_ime_status, - commands::list_microphone_devices, - commands::start_microphone_level_monitor, - commands::stop_microphone_level_monitor, - commands::get_credentials, - commands::set_credential, - commands::list_history, - commands::delete_history_entry, - commands::clear_history, - commands::read_audio_recording, - commands::retranscribe_recording, - commands::marketplace_list, - commands::marketplace_detail, - commands::marketplace_install, - commands::marketplace_upload, - commands::marketplace_like, - commands::marketplace_my_likes, - commands::marketplace_my_packs, - commands::marketplace_delete, - commands::github_device_flow_start, - commands::github_device_flow_poll, - commands::list_vocab, - commands::add_vocab, - commands::remove_vocab, - commands::set_vocab_enabled, - commands::list_correction_rules, - commands::add_correction_rule, - commands::remove_correction_rule, - commands::set_correction_rule_enabled, - commands::list_vocab_presets, - commands::save_vocab_presets, - commands::start_dictation, - commands::stop_dictation, - commands::cancel_dictation, - coding_agent::commands::coding_agent_detect, - coding_agent::commands::coding_agent_run_test, - coding_agent::commands::coding_agent_cancel_test, - coding_agent::commands::coding_agent_command_risk, - commands::handle_window_hotkey_event, - #[cfg(debug_assertions)] - commands::inject_hotkey_click_for_dev, - commands::repolish, - commands::list_style_packs, - commands::create_style_pack_from_template, - commands::save_style_pack, - commands::preview_style_pack_runtime, - commands::set_active_style_pack, - commands::set_style_pack_enabled, - commands::reset_builtin_style_pack, - commands::delete_style_pack, - commands::import_style_pack_from_zip, - commands::export_style_pack_to_zip, - commands::set_default_polish_mode, - commands::set_style_enabled, - commands::check_accessibility_permission, - commands::request_accessibility_permission, - commands::check_microphone_permission, - commands::request_microphone_permission, - commands::open_system_settings, - commands::trigger_microphone_prompt, - commands::read_credential, - commands::set_active_asr_provider, - commands::set_active_llm_provider, - commands::get_qa_hotkey_label, - commands::set_qa_hotkey, - commands::validate_shortcut_binding, - commands::set_dictation_hotkey, - commands::set_translation_hotkey, - commands::set_switch_style_hotkey, - commands::set_open_app_hotkey, - commands::qa_window_dismiss, - commands::qa_window_pin, - commands::less_computer_window_dismiss, - commands::less_computer_window_resize, - commands::less_computer_approve, - commands::validate_combo_hotkey, - commands::set_combo_hotkey, - commands::validate_provider_credentials, - commands::list_provider_models, - commands::local_asr_get_settings, - commands::local_asr_storage_settings, - commands::local_asr_set_models_base_dir, - commands::local_asr_set_active_model, - commands::local_asr_set_mirror, - commands::local_asr_list_models, - commands::local_asr_fetch_remote_info, - commands::local_asr_download_model, - commands::local_asr_cancel_download, - commands::local_asr_delete_model, - commands::local_asr_model_dir, - commands::local_asr_reveal_model_dir, - commands::local_asr_reveal_models_root, - commands::local_asr_test_model, - commands::local_asr_engine_status, - commands::local_asr_release_engine, - commands::local_asr_preload, - commands::local_asr_set_keep_loaded_secs, - commands::foundry_local_asr_status, - commands::foundry_local_asr_catalog, - commands::foundry_local_asr_set_model, - commands::foundry_local_asr_set_language_hint, - commands::foundry_local_asr_set_runtime_source, - commands::foundry_local_asr_prepare, - commands::foundry_local_asr_cancel_prepare, - commands::foundry_local_asr_release, - commands::foundry_local_asr_model_dir, - commands::foundry_local_asr_delete_model, - commands::foundry_local_asr_reveal_model_dir, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_status, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_catalog, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_fetch_remote_info, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_download_model, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_cancel_download, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_set_model, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_set_language_hint, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_prepare, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_cancel_prepare, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_release, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_model_dir, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_delete_model, - #[cfg(target_os = "windows")] - commands::sherpa_onnx_asr_reveal_model_dir, - commands::export_error_log, - restart_app, - ]) + .invoke_handler(app_invoke_handler_desktop!()) .build(tauri::generate_context!()) .expect("error while building tauri application") .run(|app, event| match event { @@ -539,21 +684,25 @@ pub fn run() { }); } +#[cfg(not(mobile))] struct MicrophoneTrayMenu { submenu: Submenu, items: Vec, } +#[cfg(not(mobile))] struct StyleTrayMenu { submenu: Submenu, } +#[cfg(not(mobile))] struct TrayMenu { menu: Menu, microphone_items: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg(not(mobile))] struct TrayPolishModeMenuEntry { id: String, label: &'static str, @@ -562,9 +711,13 @@ struct TrayPolishModeMenuEntry { } fn tray_style_menu_enabled() -> bool { - cfg!(target_os = "windows") + #[cfg(all(not(mobile), target_os = "windows"))] + return true; + #[cfg(not(all(not(mobile), target_os = "windows")))] + false } +#[cfg(not(mobile))] fn tray_polish_mode_menu_entries(selected: PolishMode) -> Vec { [ (PolishMode::Raw, "style-raw"), @@ -582,6 +735,7 @@ fn tray_polish_mode_menu_entries(selected: PolishMode) -> Vec Option { match id { "style-raw" => Some(PolishMode::Raw), @@ -592,6 +746,7 @@ fn parse_tray_polish_mode_id(id: &str) -> Option { } } +#[cfg(not(mobile))] fn build_tray_menu>( app: &M, coordinator: &Arc, @@ -617,6 +772,7 @@ fn build_tray_menu>( }) } +#[cfg(not(mobile))] fn build_style_tray_menu>( app: &M, coordinator: &Arc, @@ -639,6 +795,7 @@ fn build_style_tray_menu>( }) } +#[cfg(not(mobile))] fn build_microphone_tray_menu>( app: &M, coordinator: &Arc, @@ -697,6 +854,7 @@ fn build_microphone_tray_menu>( }) } +#[cfg(not(mobile))] pub(crate) fn refresh_tray_microphone_menu(app: &AppHandle) -> tauri::Result<()> { let coordinator = app.state::>(); let tray_menu = build_tray_menu(app, &coordinator)?; @@ -708,6 +866,7 @@ pub(crate) fn refresh_tray_microphone_menu(app: &AppHandle) -> tauri::Result<()> Ok(()) } +#[cfg(not(mobile))] fn microphone_device_signature() -> Option> { match recorder::list_input_devices() { Ok(devices) => Some( @@ -723,6 +882,7 @@ fn microphone_device_signature() -> Option> { } } +#[cfg(not(mobile))] fn start_tray_microphone_watcher(app: AppHandle) { TRAY_MICROPHONE_WATCHER_STOPPING.store(false, Ordering::Relaxed); if let Err(err) = std::thread::Builder::new() @@ -756,6 +916,7 @@ fn start_tray_microphone_watcher(app: AppHandle) { } } +#[cfg(not(mobile))] fn handle_microphone_tray_menu_event(app: &AppHandle, id: &str) { let tray_items = app.state::(); let items = tray_items.lock(); @@ -775,6 +936,7 @@ fn handle_microphone_tray_menu_event(app: &AppHandle, id: &str) { commands::sync_tray_microphone_selection(&items, &selected.device_name); } +#[cfg(not(mobile))] fn handle_style_tray_menu_event(app: &AppHandle, id: &str) -> bool { let Some(mode) = parse_tray_polish_mode_id(id) else { return false; @@ -790,6 +952,11 @@ fn handle_style_tray_menu_event(app: &AppHandle, id: &str) -> bool { true } +#[cfg(mobile)] +pub(crate) fn refresh_tray_microphone_menu(_app: &AppHandle) -> tauri::Result<()> { + Ok(()) +} + /// 把 Win11 原生标题栏底色刷成白色,与应用 sidebar 视觉统一。需要 Win11 22H2+ /// (Build 22621+) 才支持 `DWMWA_CAPTION_COLOR`(35);老 Windows 上 DwmSetWindowAttribute /// 返回错误,仅打 warn 不阻塞启动。 @@ -955,7 +1122,7 @@ pub fn log_dir_path() -> std::path::PathBuf { .join("Logs"); } } - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))] { if let Ok(home) = std::env::var("HOME") { return std::path::PathBuf::from(home) @@ -965,6 +1132,12 @@ pub fn log_dir_path() -> std::path::PathBuf { .join("logs"); } } + #[cfg(target_os = "android")] + { + if let Ok(dir) = std::env::var("TAURI_ANDROID_APP_DATA_DIR") { + return std::path::PathBuf::from(dir).join("logs"); + } + } std::env::temp_dir().join("OpenLess") } @@ -972,6 +1145,7 @@ pub(crate) fn show_main_window(app: &AppHandle) { activate_window_mode(app); if let Some(w) = app.get_webview_window("main") { let _ = w.show(); + #[cfg(not(mobile))] let _ = w.unminimize(); let _ = w.set_focus(); } @@ -1215,35 +1389,6 @@ fn bottom_visual_position( (x, y) } -/// 把窗口左上角 `(x, y)`(同 area 同坐标系,physical px)夹到给定矩形内, -/// **保证整窗(含自身 w×h)落在 area 内可见**。area 为工作区时即可避开任务栏。 -/// -/// 纯函数,无 Win32 依赖,便于单测多显示器 / 负原点 / 异常 DPI 输入。issue #470: -/// 此前 Windows 分支只夹上边(`y.max(mon.top)`),左/右/下未夹,多屏负坐标下胶囊 -/// 可能被算到屏外却无任何观测。这里四边都夹。 -/// -/// area 比窗口还小时(`area_right - w < area_left`),`max_x` 退化为 `area_left`, -/// `clamp` 把左上角收回 area 左上角,保证至少左上角可见、不溢出为负超界。 -#[cfg_attr(not(target_os = "windows"), allow(dead_code))] -fn clamp_to_monitor( - x: i32, - y: i32, - w: i32, - h: i32, - area_left: i32, - area_top: i32, - area_right: i32, - area_bottom: i32, -) -> (i32, i32) { - // 右/下边界 = area 右下角减去窗口自身尺寸,确保整窗可见。 - // 用 saturating_sub 防 area_right/area_bottom 为极小(含 i32::MIN 近邻)时减法溢出。 - let max_x = area_right.saturating_sub(w).max(area_left); - let max_y = area_bottom.saturating_sub(h).max(area_top); - let clamped_x = x.clamp(area_left, max_x); - let clamped_y = y.clamp(area_top, max_y); - (clamped_x, clamped_y) -} - /// 把 QA 浮窗放到屏幕底部居中、紧贴胶囊上方。tauri 启动期 + show 之前都会调一次, /// 防止用户切换显示器后位置错乱。 fn position_qa_window(window: &tauri::WebviewWindow) -> tauri::Result<()> { @@ -1269,21 +1414,36 @@ fn position_qa_window(window: &tauri::WebviewWindow) -> ta /// 显示 QA 窗口并发一条状态事件(前端订阅 `qa:state`)。 /// `content_kind` 是不透明字符串("loading" / "answer" / "idle" 等), -/// 让前端 React 视图自行决定渲染哪一种。 -/// -/// ## 跨端焦点契约(#164 / #466,三端**有意**不同,勿盲目对齐) -/// - **macOS**:`orderFrontRegardless` —— 窗口可见但不成为 key window,frontmost -/// 始终是用户原 app,AX / Cmd+C fallback 能直接读到选区。**全程不抢焦点**。 -/// - **Windows**:`show_qa_window_no_activate` 实际是 `show()` + `set_focus()`, -/// 出现的那一帧会短暂抢前台。这是 #466 对 #164 的**有意取舍**:WebView2 子窗口有 -/// 独立 focus 模型,不主动抓焦点则 QA webview 收不到键盘事件 → ESC 到不了 React -/// 监听、X 按钮 first-click 被 OS 当激活点击吃掉。代价由 `coordinator/qa.rs` 的 -/// focus-dance 补偿:抓选区前用 `qa_focus_target` 把焦点临时还给用户原 app, -/// `simulate_copy` 跑完再 `refocus_qa_window` 收回。**移除 set_focus 会同时回归 -/// #164 的反面(ESC/X 失效),别删。** 详见 `show_qa_window_no_activate` 内注释。 -/// - **Linux**:`window.show()`。qa 窗口静态配置 `focus: false`(tauri.conf.json), -/// Tauri 将其建成非激活窗口,因此 show() **不抢焦点**,与 macOS 契约一致。 +/// 让前端 React 视图自行决定渲染哪一种。**不**抢前台 app 焦点(保证 Cmd+C +/// fallback 仍能从原 app 拿到选区)。 pub(crate) fn show_qa_window(app: &AppHandle, content_kind: &str) { + #[cfg(target_os = "android")] + { + const FLAG_ACTIVITY_NEW_TASK: i32 = 0x10000000; + const FLAG_ACTIVITY_REORDER_TO_FRONT: i32 = 0x00020000; + const FLAG_ACTIVITY_SINGLE_TOP: i32 = 0x20000000; + let flags = + FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REORDER_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP; + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::start_activity_class_with_flags( + env, + context, + "com.openless.app.MainActivity", + flags, + ) + }) { + Ok(()) => log::info!("[qa] android requested MainActivity foreground for QA"), + Err(error) => log::warn!("[qa] android failed to foreground MainActivity: {error}"), + } + log::info!("[qa] android emit qa:state to main kind={content_kind}"); + let _ = app.emit_to( + "main", + "qa:state", + serde_json::json!({ "kind": content_kind }), + ); + return; + } + let Some(window) = app.get_webview_window("qa") else { log::info!("[qa] show 跳过:qa 窗口不存在 (content_kind={content_kind})"); return; @@ -1335,8 +1495,6 @@ pub(crate) fn show_qa_window(app: &AppHandle, content_kind log::warn!("[qa] show fallback failed: {e}"); } } - // Linux:qa 窗口静态配置 focus:false → Tauri 建成非激活窗口,window.show() 不抢 - // 焦点,与 macOS「不抢焦点」契约一致(无需 Windows 那套 set_focus + focus-dance)。 #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))] if let Err(e) = window.show() { log::warn!("[qa] show failed: {e}"); @@ -1378,6 +1536,12 @@ fn make_qa_window_draggable_macos(window: &tauri::WebviewWind /// 隐藏 QA 窗口。供 commands::qa_window_dismiss / coordinator session 收尾共用。 pub(crate) fn hide_qa_window(app: &AppHandle) { + #[cfg(target_os = "android")] + { + let _ = app.emit_to("main", "qa:dismiss", serde_json::json!({})); + return; + } + if let Some(window) = app.get_webview_window("qa") { let _ = window.hide(); } @@ -1491,22 +1655,18 @@ pub(crate) fn show_less_computer_glow(app: &AppHandle) { .ok() .flatten() .or_else(|| app.primary_monitor().ok().flatten()); - // 逻辑坐标的「铺满整屏」矩形 (x, y, w, h)。f64 元组可 Copy:既在 show 前先铺一次, - // 也在主线程 realize(orderFront)后再铺一次(见下,修首次半屏 bug)。 - let bounds: Option<(f64, f64, f64, f64)> = monitor.map(|m| { - let scale = m.scale_factor(); - let size = m.size(); - let pos = m.position(); - ( + if let Some(monitor) = monitor { + let scale = monitor.scale_factor(); + let size = monitor.size(); + let pos = monitor.position(); + let _ = window.set_position(tauri::LogicalPosition::new( pos.x as f64 / scale, pos.y as f64 / scale, + )); + let _ = window.set_size(tauri::LogicalSize::new( size.width as f64 / scale, size.height as f64 / scale, - ) - }); - if let Some((x, y, w, h)) = bounds { - let _ = window.set_position(tauri::LogicalPosition::new(x, y)); - let _ = window.set_size(tauri::LogicalSize::new(w, h)); + )); } // 点击穿透:纯视觉浮层,绝不拦截鼠标。 let _ = window.set_ignore_cursor_events(true); @@ -1534,14 +1694,6 @@ pub(crate) fn show_less_computer_glow(app: &AppHandle) { let _ = window_clone.show(); } } - // 首次使用彩虹边框只画半屏并卡住:glow 窗口 conf 初始 800×600 且 visible:false, - // 首次 show 前从未 realize —— current_monitor() 取不到 / show 前的 set_size 没贴住整屏, - // webview 首帧按 800×600 画出半屏描边。这里在 realize(orderFront)之后**再铺满一次**, - // 强制 webview 按整屏重排重绘。后续使用窗口已 realize,show 前那次就够、不闪。 - if let Some((x, y, w, h)) = bounds { - let _ = window_clone.set_position(tauri::LogicalPosition::new(x, y)); - let _ = window_clone.set_size(tauri::LogicalSize::new(w, h)); - } }); } @@ -1645,12 +1797,6 @@ pub(crate) struct ForegroundMonitor { pub(crate) top: i32, pub(crate) right: i32, pub(crate) bottom: i32, - /// 工作区矩形(physical px,去掉任务栏)。多端一致:胶囊优先夹到工作区内, - /// 避免压住任务栏。取不到时回退为整屏矩形。issue #470。 - pub(crate) work_left: i32, - pub(crate) work_top: i32, - pub(crate) work_right: i32, - pub(crate) work_bottom: i32, /// 该显示器的有效 DPI 缩放(1.0 = 96dpi)。 pub(crate) scale: f64, } @@ -1688,10 +1834,6 @@ pub(crate) fn foreground_window_monitor() -> Option { top: mi.rcMonitor.top, right: mi.rcMonitor.right, bottom: mi.rcMonitor.bottom, - work_left: mi.rcWork.left, - work_top: mi.rcWork.top, - work_right: mi.rcWork.right, - work_bottom: mi.rcWork.bottom, scale: (dpi_x as f64 / 96.0).max(0.1), }) } @@ -1724,24 +1866,15 @@ pub(crate) fn position_capsule_bottom_center( let offset_from_bottom = (capsule_visual_height(translation_active) + 80.0 + bounds.bottom_inset) * scale; let y = ((mon.bottom as f64) - offset_from_bottom).round() as i32; - - // #470:四边都夹到「工作区」内(去掉任务栏),保证整窗可见。GetMonitorInfoW - // 取不到 rcWork 时(理论上不会,rcWork 总随 rcMonitor 一同填)退回整屏矩形。 - let (work_l, work_t, work_r, work_b) = - if mon.work_right > mon.work_left && mon.work_bottom > mon.work_top { - (mon.work_left, mon.work_top, mon.work_right, mon.work_bottom) - } else { - (mon.left, mon.top, mon.right, mon.bottom) - }; - let (clamped_x, clamped_y) = - clamp_to_monitor(x, y, phys_w, phys_h, work_l, work_t, work_r, work_b); + let clamped_y = y.max(mon.top); + // #470 诊断 v2:当前只夹了上边(.max(mon.top)),未夹下/左/右。多显示器、 + // 负坐标或异常 DPI 下胶囊可能被算到屏幕外却无任何观测。记录显示器几何与 + // 最终落点,用于证伪/证实「胶囊定位到屏幕外」(C 子嫌疑)。 log::debug!( - "[capsule] win position: mon=({},{})..({},{}) work=({},{})..({},{}) scale={:.2} size=({}x{}) -> raw=({},{}) clamped=({},{})", - mon.left, mon.top, mon.right, mon.bottom, - work_l, work_t, work_r, work_b, - scale, phys_w, phys_h, x, y, clamped_x, clamped_y + "[capsule] win position: mon=({},{})..({},{}) scale={:.2} size=({}x{}) -> x={} y={} clamped_y={}", + mon.left, mon.top, mon.right, mon.bottom, scale, phys_w, phys_h, x, y, clamped_y ); - window.set_position(PhysicalPosition::new(clamped_x, clamped_y))?; + window.set_position(PhysicalPosition::new(x, clamped_y))?; return Ok(()); } // 仅当 Win32 取不到前台显示器时,落回下面的 current_monitor 逻辑。 @@ -1822,7 +1955,7 @@ fn capsule_height_for_qa() -> f64 { mod tests { use super::{ bottom_center_position, bottom_visual_position, capsule_height_for_qa, - capsule_visual_height, capsule_window_bounds, clamp_to_monitor, logical_monitor_frame, + capsule_visual_height, capsule_window_bounds, logical_monitor_frame, parse_tray_polish_mode_id, rotate_log_if_too_large, tray_polish_mode_menu_entries, tray_style_menu_enabled, LogicalMonitorFrame, LOG_ROTATE_LIMIT_BYTES, }; @@ -1971,63 +2104,6 @@ mod tests { assert_eq!(pos, (610.0, -176.0)); } - // ---- #470: capsule 四边 clamp(纯函数,合成多显示器 / 负原点 / 1.5x DPI 输入)---- - - #[test] - fn clamp_to_monitor_leaves_on_screen_position_untouched() { - // 1080p 主屏正中偏下,整窗本就可见 → 原样返回。 - let (x, y) = clamp_to_monitor(800, 900, 264, 126, 0, 0, 1920, 1040); - assert_eq!((x, y), (800, 900)); - } - - #[test] - fn clamp_to_monitor_pulls_back_off_screen_right_and_bottom() { - // x/y 算到了屏幕右下外侧 → 收回到「右下角减去窗口尺寸」,整窗仍可见。 - let (x, y) = clamp_to_monitor(2000, 1200, 264, 126, 0, 0, 1920, 1040); - assert_eq!((x, y), (1920 - 264, 1040 - 126)); - // 整窗右/下边界都落在 area 内。 - assert!(x + 264 <= 1920); - assert!(y + 126 <= 1040); - } - - #[test] - fn clamp_to_monitor_pulls_back_when_right_edge_overflows_inside_area() { - // 左上角 x=1800 本在 area 内,但 x+w=2064 越过右边界 1920 → - // 应被左移到「右边界 - 窗口宽」,整窗右缘恰好贴住 area_right。 - let (x, _y) = clamp_to_monitor(1800, 900, 264, 126, 0, 0, 1920, 1040); - assert_eq!(x, 1920 - 264); - assert!(x + 264 <= 1920); - } - - #[test] - fn clamp_to_monitor_pushes_into_negative_origin_left_monitor() { - // 副屏在主屏左侧(负 X 原点),落点算到了副屏左外侧 → 夹回 area_left。 - // 1.5x DPI 下尺寸偏大,但 area 仍宽于窗口,左上角夹到 (-2560, top)。 - let (x, y) = clamp_to_monitor(-3000, -100, 294, 138, -2560, 0, 0, 1440); - assert_eq!(x, -2560); - assert_eq!(y, 0); - // 右/下仍在 area 内。 - assert!(x >= -2560 && x + 294 <= 0); - assert!(y >= 0 && y + 138 <= 1440); - } - - #[test] - fn clamp_to_monitor_respects_work_area_above_taskbar() { - // 工作区底部 = 1040(任务栏占了 1040..1080)。落点本在任务栏区域(y=1030), - // 应被夹到「工作区底 - 窗口高」之上,胶囊整窗不压任务栏。 - let (_x, y) = clamp_to_monitor(800, 1030, 264, 126, 0, 0, 1920, 1040); - assert_eq!(y, 1040 - 126); - assert!(y + 126 <= 1040); - } - - #[test] - fn clamp_to_monitor_degrades_gracefully_when_window_wider_than_area() { - // 病态输入:area 比窗口还窄(罕见,但要保证不 panic、不溢出为负超界)。 - // max_x 钳到 area_left,clamp 把左上角收回 area_left。 - let (x, y) = clamp_to_monitor(500, 500, 800, 600, 0, 0, 400, 300); - assert_eq!((x, y), (0, 0)); - } - #[test] fn oversized_log_rotates_to_single_archive() { let dir = std::env::temp_dir().join(format!("openless-log-rotate-{}", std::process::id())); diff --git a/openless-all/app/src-tauri/src/mobile_runtime.rs b/openless-all/app/src-tauri/src/mobile_runtime.rs new file mode 100644 index 00000000..f2f672be --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_runtime.rs @@ -0,0 +1,79 @@ +//! Minimal Tauri mobile runtime — single main window, no tray/hotkey/updater. + +use std::sync::Arc; + +use tauri::{AppHandle, Manager, RunEvent}; + +use crate::coordinator::Coordinator; + +pub fn run() { + let coordinator = Arc::new(Coordinator::new()); + + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()) + .manage(coordinator.clone()) + .setup(move |app| { + crate::init_file_logger(); + log::info!("=== OpenLess mobile 启动 ==="); + initialize_android_ndk_context_for_audio(); + + if let Some(main) = app.get_webview_window("main") { + let _ = main.show(); + } + if let Some(qa) = app.get_webview_window("qa") { + let _ = qa.hide(); + } + + coordinator.bind_app(app.handle().clone()); + #[cfg(target_os = "android")] + { + crate::android::register_android_coordinator(coordinator.clone()); + coordinator.apply_android_overlay_on_startup(); + } + Ok(()) + }) + .invoke_handler(crate::app_invoke_handler_mobile!()) + .build(tauri::generate_context!()) + .expect("error while building tauri mobile application") + .run(|app, event| match event { + RunEvent::Exit => { + let coordinator = app.state::>(); + coordinator.stop_hotkey_listener(); + } + _ => {} + }); +} + +#[allow(dead_code)] +pub(crate) fn show_main_window(app: &AppHandle) { + if let Some(w) = app.get_webview_window("main") { + let _ = w.show(); + let _ = w.set_focus(); + } +} + +#[cfg(target_os = "android")] +fn initialize_android_ndk_context_for_audio() { + static INIT: std::sync::Once = std::sync::Once::new(); + + INIT.call_once(|| { + let Some(context) = tao::platform::android::prelude::main_android_context() else { + log::warn!("[android] tao Android context unavailable; audio backend may fail"); + return; + }; + + let result = std::panic::catch_unwind(|| unsafe { + ndk_context::initialize_android_context(context.java_vm, context.context_jobject); + }); + + if result.is_ok() { + log::info!("[android] initialized ndk-context for audio backend"); + } else { + log::warn!("[android] ndk-context was already initialized or rejected initialization"); + } + }); +} + +#[cfg(not(target_os = "android"))] +fn initialize_android_ndk_context_for_audio() {} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/combo_hotkey.rs b/openless-all/app/src-tauri/src/mobile_stubs/combo_hotkey.rs new file mode 100644 index 00000000..3a7907ef --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/combo_hotkey.rs @@ -0,0 +1,46 @@ +//! Mobile stub — combo hotkeys are unavailable on Android/iOS. + +use std::sync::mpsc::Sender; + +use crate::types::ShortcutBinding; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ComboHotkeyEvent { + Pressed, + Released, +} + +#[derive(Debug, thiserror::Error)] +pub enum ComboHotkeyError { + #[error("不支持的修饰键: {0}")] + UnsupportedModifier(String), + #[error("不支持的主键: {0}")] + UnsupportedKey(String), + #[error("注册全局快捷键失败: {0}")] + RegisterFailed(String), + #[error("初始化全局快捷键管理器失败: {0}")] + ManagerInitFailed(String), +} + +pub struct ComboHotkeyMonitor; + +impl ComboHotkeyMonitor { + pub fn start( + _binding: ShortcutBinding, + _tx: Sender, + ) -> Result { + Err(ComboHotkeyError::RegisterFailed( + "Combo hotkeys are not available on mobile".into(), + )) + } + + pub fn update_binding(&self, _binding: ShortcutBinding) -> Result<(), ComboHotkeyError> { + Ok(()) + } +} + +pub fn validate_binding(_binding: &ShortcutBinding) -> Result<(), ComboHotkeyError> { + Err(ComboHotkeyError::RegisterFailed( + "Combo hotkeys are not available on mobile".into(), + )) +} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/hotkey.rs b/openless-all/app/src-tauri/src/mobile_stubs/hotkey.rs new file mode 100644 index 00000000..59c6c7e7 --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/hotkey.rs @@ -0,0 +1,49 @@ +//! Mobile stub — global hotkeys are unavailable on Android/iOS. + +use std::sync::mpsc::Sender; + +use crate::types::{ + HotkeyAdapterKind, HotkeyBinding, HotkeyCapability, HotkeyInstallError, HotkeyTrigger, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HotkeyEvent { + Pressed, + Released, + Cancelled, + TranslationModifierPressed, + QaShortcutPressed, +} + +pub struct HotkeyMonitor; + +impl HotkeyMonitor { + pub fn start( + _binding: HotkeyBinding, + _tx: Sender, + ) -> Result { + Err(HotkeyInstallError { + code: "unavailable".into(), + message: "Global hotkeys are not available on mobile".into(), + }) + } + + pub fn update_binding(&self, _binding: HotkeyBinding) {} + + pub fn update_modifier_shortcuts( + &self, + _qa_trigger: Option, + _translation_trigger: Option, + ) { + } + + pub fn kind(&self) -> HotkeyAdapterKind { + HotkeyAdapterKind::Unavailable + } + + pub fn reset_held_state(&self) {} + + pub fn capability() -> HotkeyCapability { + HotkeyCapability::current() + } +} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/qa_hotkey.rs b/openless-all/app/src-tauri/src/mobile_stubs/qa_hotkey.rs new file mode 100644 index 00000000..b94c7acb --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/qa_hotkey.rs @@ -0,0 +1,33 @@ +//! Mobile stub — QA hotkeys are unavailable on Android/iOS. + +use std::sync::mpsc::Sender; + +use crate::types::ShortcutBinding; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum QaHotkeyEvent { + Pressed, +} + +#[derive(Debug, thiserror::Error)] +pub enum QaHotkeyError { + #[error("注册 QA 快捷键失败: {0}")] + RegisterFailed(String), +} + +pub struct QaHotkeyMonitor; + +impl QaHotkeyMonitor { + pub fn start( + _binding: ShortcutBinding, + _tx: Sender, + ) -> Result { + Err(QaHotkeyError::RegisterFailed( + "QA hotkeys are not available on mobile".into(), + )) + } + + pub fn update_binding(&self, _binding: ShortcutBinding) -> Result<(), QaHotkeyError> { + Ok(()) + } +} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/selection.rs b/openless-all/app/src-tauri/src/mobile_stubs/selection.rs new file mode 100644 index 00000000..ef48faa6 --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/selection.rs @@ -0,0 +1,54 @@ +//! Mobile selection capture. + +const SELECTION_MAX_CHARS: usize = 4000; +const SELECTION_TRUNCATE_HEAD: usize = 2000; +const SELECTION_TRUNCATE_TAIL: usize = 2000; +const SELECTION_TRUNCATED_MARKER: &str = "\n[…truncated…]\n"; + +#[derive(Debug, Clone)] +pub struct SelectionContext { + pub text: String, + pub source_app: Option, +} + +#[cfg(target_os = "android")] +pub fn capture_selection() -> Option { + let text = match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::accessibility_selected_text(env, context) + }) { + Ok(Some(text)) => text, + Ok(None) => return None, + Err(error) => { + log::warn!("[selection] Android accessibility selection read failed: {error}"); + return None; + } + }; + let trimmed = text.trim(); + if trimmed.is_empty() { + return None; + } + log::info!( + "[selection] Android accessibility read OK ({} chars)", + trimmed.chars().count() + ); + Some(SelectionContext { + text: truncate_selection(trimmed), + source_app: Some("Android accessibility".to_string()), + }) +} + +#[cfg(not(target_os = "android"))] +pub fn capture_selection() -> Option { + None +} + +fn truncate_selection(text: &str) -> String { + let total: usize = text.chars().count(); + if total <= SELECTION_MAX_CHARS { + return text.to_string(); + } + let head: String = text.chars().take(SELECTION_TRUNCATE_HEAD).collect(); + let tail_start = total.saturating_sub(SELECTION_TRUNCATE_TAIL); + let tail: String = text.chars().skip(tail_start).collect(); + format!("{head}{SELECTION_TRUNCATED_MARKER}{tail}") +} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/shortcut_binding.rs b/openless-all/app/src-tauri/src/mobile_stubs/shortcut_binding.rs new file mode 100644 index 00000000..7946a44d --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/shortcut_binding.rs @@ -0,0 +1,38 @@ +//! Mobile stub — shortcut binding validation is unavailable on mobile. + +use crate::types::{HotkeyTrigger, ShortcutBinding}; + +#[derive(Debug, thiserror::Error)] +pub enum ShortcutBindingError { + #[error("快捷键在移动端不可用")] + Unavailable, +} + +pub fn validate_binding(_binding: &ShortcutBinding) -> Result<(), ShortcutBindingError> { + Err(ShortcutBindingError::Unavailable) +} + +pub fn parse_global_hotkey(_binding: &ShortcutBinding) -> Result<(), ShortcutBindingError> { + Err(ShortcutBindingError::Unavailable) +} + +pub fn legacy_modifier_trigger(_binding: &ShortcutBinding) -> Option { + None +} + +pub fn binding_from_legacy_trigger(trigger: HotkeyTrigger) -> ShortcutBinding { + let primary = match trigger { + HotkeyTrigger::RightOption | HotkeyTrigger::RightAlt => "RightOption", + HotkeyTrigger::LeftOption => "LeftOption", + HotkeyTrigger::RightControl => "RightControl", + HotkeyTrigger::LeftControl => "LeftControl", + HotkeyTrigger::RightCommand => "RightCommand", + HotkeyTrigger::Fn => "Fn", + HotkeyTrigger::MediaPlayPause => "MediaPlayPause", + HotkeyTrigger::Custom => "RightOption", + }; + ShortcutBinding { + primary: primary.into(), + modifiers: Vec::new(), + } +} diff --git a/openless-all/app/src-tauri/src/mobile_stubs/unicode_keystroke.rs b/openless-all/app/src-tauri/src/mobile_stubs/unicode_keystroke.rs new file mode 100644 index 00000000..9adc27ed --- /dev/null +++ b/openless-all/app/src-tauri/src/mobile_stubs/unicode_keystroke.rs @@ -0,0 +1,40 @@ +//! Mobile stub — unicode keystroke streaming is unavailable on mobile. + +use tauri::{AppHandle, Runtime}; + +#[derive(Debug, thiserror::Error)] +pub enum TypeError { + #[allow(dead_code)] + #[error("{source} after {typed_chars} chars were sent")] + Partial { + typed_chars: usize, + #[source] + source: Box, + }, + #[error("unicode keystroke unavailable on mobile")] + Unavailable, +} + +impl TypeError { + pub fn typed_chars(&self) -> usize { + match self { + TypeError::Partial { typed_chars, .. } => *typed_chars, + _ => 0, + } + } +} + +pub async fn switch_to_ascii(_app: &AppHandle) -> Result, TypeError> { + Err(TypeError::Unavailable) +} + +pub async fn restore_input_source( + _app: &AppHandle, + _previous: Option<()>, +) -> Result<(), TypeError> { + Ok(()) +} + +pub fn type_unicode_chunk(_text: &str) -> Result { + Err(TypeError::Unavailable) +} diff --git a/openless-all/app/src-tauri/src/permissions.rs b/openless-all/app/src-tauri/src/permissions.rs index ef1452d4..c525a92d 100644 --- a/openless-all/app/src-tauri/src/permissions.rs +++ b/openless-all/app/src-tauri/src/permissions.rs @@ -244,9 +244,54 @@ mod platform { } } -// ─────────────────────────── Windows / 其他 ─────────────────────────── +// ─────────────────────────── Android ─────────────────────────── -#[cfg(not(target_os = "macos"))] +#[cfg(target_os = "android")] +mod platform { + use super::PermissionStatus; + + pub fn check_accessibility() -> PermissionStatus { + PermissionStatus::NotApplicable + } + + pub fn request_accessibility() -> PermissionStatus { + PermissionStatus::NotApplicable + } + + pub fn check_microphone() -> PermissionStatus { + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::check_self_permission( + env, + context, + "android.permission.RECORD_AUDIO", + ) + }) { + Ok(true) => PermissionStatus::Granted, + Ok(false) => PermissionStatus::Denied, + Err(error) => { + log::warn!("[mic] Android RECORD_AUDIO permission check failed: {error}"); + PermissionStatus::NotDetermined + } + } + } + + pub fn request_microphone() -> PermissionStatus { + match crate::android::jni::android::with_android_env(|env, context| { + crate::android::jni::android::request_record_audio_permission(env, context) + }) { + Ok(true) => PermissionStatus::Granted, + Ok(false) => PermissionStatus::NotDetermined, + Err(error) => { + log::warn!("[mic] Android RECORD_AUDIO permission request failed: {error}"); + check_microphone() + } + } + } +} + +// ─────────────────────────── Windows / Linux / 其他 ─────────────────────────── + +#[cfg(all(not(target_os = "macos"), not(target_os = "android")))] mod platform { use super::PermissionStatus; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; diff --git a/openless-all/app/src-tauri/src/persistence.rs b/openless-all/app/src-tauri/src/persistence.rs index 15157ca2..901950f3 100644 --- a/openless-all/app/src-tauri/src/persistence.rs +++ b/openless-all/app/src-tauri/src/persistence.rs @@ -48,10 +48,8 @@ const LEGACY_CREDS_FILE: &str = "credentials.json"; const KEYRING_CREDENTIALS_ACCOUNT: &str = "credentials.v1"; const KEYRING_CREDENTIALS_CHUNK_PREFIX: &str = "credentials.v1.chunk."; -/// HMAC 密钥长度(字节)。 -const HISTORY_HMAC_KEY_LEN: usize = 32; -/// history HMAC sidecar 文件后缀:`history.json.hmac`。 -const HISTORY_HMAC_SUFFIX: &str = ".hmac"; +#[cfg(target_os = "android")] +const ANDROID_CREDENTIALS_FILE: &str = "credentials.enc.json"; // Windows Credential Manager caps one credential blob at 2560 bytes. keyring stores // passwords as UTF-16 on Windows, so keep each JSON chunk comfortably below that. const KEYRING_CHUNK_MAX_UTF16_UNITS: usize = 1000; @@ -95,17 +93,6 @@ fn store_credentials_cache(root: &CredsRoot) { #[cfg(test)] fn reset_credentials_cache_for_tests() { *credentials_cache().lock() = None; - *credentials_manifest_cache().lock() = None; -} - -/// issue #602:进程内缓存「上次成功读/写的 chunk manifest」。save_credentials 用它 -/// 替代保存前的 keychain manifest 读 —— macOS 上这次读本身就要过 ACL 检查(弹窗)。 -/// None = 本进程还没成功读/写过 manifest(冷启动或 keyring 不可用),此时才回 -/// keychain 读真实 manifest,保证 UUID-generation 旧 chunks 的清理信息不丢。 -static CREDENTIALS_MANIFEST_CACHE: OnceLock>> = OnceLock::new(); - -fn credentials_manifest_cache() -> &'static Mutex> { - CREDENTIALS_MANIFEST_CACHE.get_or_init(|| Mutex::new(None)) } // ───────────────────────── path helpers ───────────────────────── @@ -126,7 +113,7 @@ fn data_dir() -> Result { Ok(PathBuf::from(appdata).join("OpenLess")) } - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))] { if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { if !xdg.is_empty() { @@ -139,6 +126,14 @@ fn data_dir() -> Result { .join("share") .join("OpenLess")) } + + #[cfg(target_os = "android")] + { + if let Ok(dir) = std::env::var("TAURI_ANDROID_APP_DATA_DIR") { + return Ok(PathBuf::from(dir).join("OpenLess")); + } + Ok(std::env::temp_dir().join("OpenLess")) + } } fn ensure_dir(dir: &Path) -> Result<()> { @@ -448,31 +443,6 @@ fn atomic_write(path: &Path, contents: &[u8]) -> Result<()> { Ok(()) } -/// 与 `atomic_write` 相同,但(unix)在 rename **之前**把 tmp 文件设 0o600。 -/// -/// issue #609 M-01:原先「rename 后再 chmod」之间存在一段世界可读窗口(tmp 按 -/// umask 创建,目标文件 rename 后到收紧权限前可被同机其他用户读到)。在 rename -/// 前对 tmp chmod,保证目标文件一出现就已是 0o600,无暴露窗口。仅用于 history.json -/// 与其 sidecar(含明文/完整性数据),其余配置走普通 `atomic_write`。 -fn atomic_write_private(path: &Path, contents: &[u8]) -> Result<()> { - if let Some(parent) = path.parent() { - ensure_dir(parent)?; - } - let file_name = path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_default(); - let tmp_path = path.with_file_name(format!("{file_name}.tmp-{}", Uuid::new_v4().simple())); - fs::write(&tmp_path, contents) - .with_context(|| format!("write tmp failed: {}", tmp_path.display()))?; - restrict_file_permissions_best_effort(&tmp_path); - if let Err(err) = fs::rename(&tmp_path, path) { - let _ = fs::remove_file(&tmp_path); - return Err(err).with_context(|| format!("rename failed: {}", path.display())); - } - Ok(()) -} - fn read_or_default Deserialize<'de> + Default>(path: &Path) -> Result { if !path.exists() { return Ok(T::default()); @@ -680,15 +650,54 @@ fn credentials_path() -> Result { } } +#[cfg(not(target_os = "android"))] fn keyring_entry() -> Result { keyring_entry_for(KEYRING_CREDENTIALS_ACCOUNT) } +#[cfg(not(target_os = "android"))] fn keyring_entry_for(account: &str) -> Result { keyring::Entry::new(CredentialsVault::SERVICE_NAME, account) .context("open system credential vault") } +#[cfg(target_os = "android")] +fn android_credentials_path() -> Result { + Ok(data_dir()?.join(ANDROID_CREDENTIALS_FILE)) +} + +#[cfg(target_os = "android")] +fn load_android_credentials() -> Result> { + let path = android_credentials_path()?; + if !path.exists() { + return Ok(None); + } + let bytes = fs::read(&path).with_context(|| format!("read failed: {}", path.display()))?; + if bytes.is_empty() { + return Ok(None); + } + // Stub: base64 envelope — replace with Keystore-backed AES when JNI lands. + use base64::Engine; + let decoded = base64::engine::general_purpose::STANDARD + .decode(bytes) + .context("decode android credentials envelope")?; + let root = + serde_json::from_slice::(&decoded).context("parse android credentials json")?; + Ok(Some(root)) +} + +#[cfg(target_os = "android")] +fn save_android_credentials(root: &CredsRoot) -> Result<()> { + let cleaned = clean_credentials(root); + let json = serde_json::to_string(&cleaned).context("encode credentials failed")?; + use base64::Engine; + let encoded = base64::engine::general_purpose::STANDARD.encode(json.as_bytes()); + let path = android_credentials_path()?; + ensure_dir(path.parent().unwrap_or_else(|| Path::new(".")))?; + fs::write(&path, encoded).with_context(|| format!("write failed: {}", path.display()))?; + Ok(()) +} + fn clean_credentials(root: &CredsRoot) -> CredsRoot { let mut cleaned = root.clone(); cleaned.providers.asr.retain(|_, v| !v.is_empty()); @@ -733,7 +742,7 @@ fn remove_legacy_credentials_file_best_effort() { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct CredsChunkManifest { openless_credentials_storage: String, version: u32, @@ -773,23 +782,6 @@ fn chunk_json_payload(json: &str) -> Vec { chunks } -/// issue #602:给定「上次成功落盘的 JSON」与「本次要写的各 chunk」,返回每个新 chunk -/// 是否可跳过重写(与同号旧 chunk 逐字节一致)。注意 chunk 按偏移切分:靠前字段的 -/// 变长改动会移动后续所有 chunk 边界(全部重写,等同旧行为);等长改动/无改动则只 -/// 写真正变化的 chunk。previous_json=None(冷缓存/旧 UUID 代际)→ 全部重写。 -fn chunk_skip_mask(previous_json: Option<&str>, new_chunks: &[String]) -> Vec { - let prev_chunks = previous_json.map(chunk_json_payload); - new_chunks - .iter() - .enumerate() - .map(|(index, chunk)| { - prev_chunks - .as_ref() - .is_some_and(|prev| prev.get(index) == Some(chunk)) - }) - .collect() -} - fn read_chunk_manifest(json: &str) -> Option { let manifest = serde_json::from_str::(json).ok()?; if manifest.openless_credentials_storage == "chunked" && manifest.version == 1 { @@ -799,6 +791,7 @@ fn read_chunk_manifest(json: &str) -> Option { } } +#[cfg(not(target_os = "android"))] fn get_keyring_password(account: &str) -> Result> { match keyring_entry_for(account)?.get_password() { Ok(value) => Ok(Some(value)), @@ -809,6 +802,7 @@ fn get_keyring_password(account: &str) -> Result> { } } +#[cfg(not(target_os = "android"))] fn delete_keyring_password(account: &str) { match keyring_entry_for(account).and_then(|entry| { entry @@ -819,169 +813,7 @@ fn delete_keyring_password(account: &str) { } } -// ───────────── issue #609 F-03:history.json HMAC 完整性 ───────────── - -/// 进程内缓存的 history HMAC 密钥,避免每次读写都打 keyring。 -static HISTORY_HMAC_KEY: OnceLock>> = OnceLock::new(); - -/// 取(必要时生成)history HMAC 密钥。 -/// -/// 首次用时从 keyring 读 32 字节 hex;不存在则用 OS CSPRNG 生成并写回 keyring。 -/// keyring 不可用(如 Linux 无 secret service)→ 返回 None,调用方据此**退化为不 -/// 校验**(保持可用,但不提供完整性保证);用 `OnceLock` 把这个状态固化到进程。 -/// -/// issue #609 M-03:keyring 不可用时只能放弃完整性校验,这里 `log::warn!` 一次。 -/// **后续可做**:keyring 缺失时把 HMAC 密钥落到一个 0o600 文件里兜底(当前留作 -/// future work,因为文件密钥与明文 history 同目录,威胁模型收益有限)。 -fn history_hmac_key() -> Option> { - HISTORY_HMAC_KEY - .get_or_init(|| match load_or_create_history_hmac_key() { - Ok(key) => Some(key), - Err(e) => { - log::warn!( - "[history] 完整性校验未激活(keyring 不可用),本次运行跳过 HMAC 校验(仍按内容读写):{e}" - ); - None - } - }) - .clone() -} - -/// history HMAC 密钥 / enrolled 标志的 0o600 文件路径。 -/// -/// **为什么从 keyring 迁到文件**(原 #609 F-03 放 keychain):macOS 上每个钥匙串条目各自 ACL, -/// 且 ad-hoc 签名下「始终允许」不持久、条目删建即清空 ACL —— 导致用户**每次听写后反复弹钥匙串 -/// 授权**(凭据 3 次之外又多出 HMAC 密钥 + enrolled 共 2 次)。HMAC 密钥与明文 history 同目录、 -/// at-rest 防护同为 OS 文件权限(M-01),放文件与放 keychain 安全**收益对等**,却彻底消除钥匙串 -/// 弹窗。真正的 API 密钥仍留在 keychain(CredentialsVault)。 -fn history_hmac_key_path() -> Result { - Ok(data_dir()?.join("history_hmac.key")) -} - -fn history_hmac_enrolled_path() -> Result { - Ok(data_dir()?.join(".history_hmac_enrolled.v1")) -} - -fn load_or_create_history_hmac_key() -> Result> { - let _guard = credentials_lock().lock(); - let key_path = history_hmac_key_path()?; - if let Ok(hex_key) = fs::read_to_string(&key_path) { - let trimmed = hex_key.trim(); - if !trimmed.is_empty() { - let key = - decode_hex(trimmed).with_context(|| "history HMAC key file is not valid hex")?; - if key.len() == HISTORY_HMAC_KEY_LEN { - return Ok(key); - } - log::warn!("[history] HMAC 密钥文件长度异常,重新生成"); - } - } - // 密钥文件不存在 = 首次启用文件存储(含从旧 keychain 版升级)。生成新 key;旧 history 可能 - // 是用「旧 keychain key」签的 —— 新 key 会让其 HMAC 不匹配而被误判篡改清空。迁移:删掉旧 - // sidecar 与 enrolled 文件,让旧 history 走 legacy 路径被重新接受 + 用新 key 补签。全程不读 - // 旧 keychain,零钥匙串弹窗、不丢历史。 - let mut key = vec![0u8; HISTORY_HMAC_KEY_LEN]; - getrandom::fill(&mut key).map_err(|e| anyhow!("OS CSPRNG 生成 HMAC 密钥失败:{e}"))?; - atomic_write_private(&key_path, encode_hex(&key).as_bytes()) - .context("写入 history HMAC 密钥文件失败")?; - if let Ok(dir) = data_dir() { - let _ = fs::remove_file(history_hmac_sidecar_path(&dir.join(HISTORY_FILE))); - } - if let Ok(enrolled) = history_hmac_enrolled_path() { - let _ = fs::remove_file(enrolled); - } - Ok(key) -} - -/// issue #609 C-01:「是否已启用 HMAC」标志的抽象,便于单测注入内存实现。 -/// -/// 标志存 keyring(不是文件),因为它本身要抗删除:sidecar 是文件、易删, -/// 一旦攻击者删掉 sidecar,靠这个 keyring 标志判定「本应有 sidecar 却没了」= -/// 篡改,而不是误当 legacy 接受。 -trait HmacEnrollment { - /// 标志已置位(曾经写过 sidecar)。keyring 不可读时返回 false(退化)。 - fn is_enrolled(&self) -> bool; - /// 置位标志(幂等)。keyring 不可写时静默忽略(best-effort)。 - fn set_enrolled(&self); -} - -/// 生产实现:enrolled 标志落 0o600 文件(不再用 keychain,原因见 history_hmac_key_path 注释: -/// 避免 ad-hoc 签名下每次听写反复弹钥匙串)。标志文件存在 = 已启用(曾写过 sidecar)。 -/// 读不到 / 写不了都按「未启用」处理(退化与 M-03 一致)。 -struct FileEnrollment; - -impl HmacEnrollment for FileEnrollment { - fn is_enrolled(&self) -> bool { - history_hmac_enrolled_path() - .map(|p| p.exists()) - .unwrap_or(false) - } - - fn set_enrolled(&self) { - let Ok(path) = history_hmac_enrolled_path() else { - return; - }; - // 已置位则不重复写(幂等)。 - if path.exists() { - return; - } - if let Err(e) = atomic_write_private(&path, b"1") { - log::warn!("[history] 置位 HMAC enrolled 标志文件失败:{e}"); - } - } -} - -fn encode_hex(bytes: &[u8]) -> String { - use std::fmt::Write; - let mut s = String::with_capacity(bytes.len() * 2); - for b in bytes { - // write! 到 String 不会失败;直接吞掉 Result 即可。 - let _ = write!(s, "{b:02x}"); - } - s -} - -fn decode_hex(s: &str) -> Result> { - let s = s.trim(); - if s.len() % 2 != 0 { - return Err(anyhow!("hex 长度为奇数")); - } - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| anyhow!("hex 解析失败:{e}"))) - .collect() -} - -/// 计算 `HMAC-SHA256(key, bytes)`,返回 hex。 -fn compute_history_hmac(key: &[u8], bytes: &[u8]) -> String { - use hmac::{Hmac, Mac}; - // 标准 HMAC:new_from_slice 接受任意长度 key(内部按 RFC2104 处理 key padding)。 - let mut mac = as Mac>::new_from_slice(key).expect("HMAC 接受任意长度 key"); - mac.update(bytes); - encode_hex(&mac.finalize().into_bytes()) -} - -fn history_hmac_sidecar_path(history_path: &Path) -> PathBuf { - let mut name = history_path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_else(|| HISTORY_FILE.to_string()); - name.push_str(HISTORY_HMAC_SUFFIX); - history_path.with_file_name(name) -} - -/// 常量时间比较两段 hex HMAC,避免计时侧信道。两者都是定长 hex,长度不等直接 false。 -/// issue #609 H-02:用 `subtle::ConstantTimeEq` 而非手写 XOR 循环,避免编译器把 -/// 短路优化引回去(手写循环不保证不被向量化/提前退出)。 -fn hmac_hex_eq(a: &str, b: &str) -> bool { - use subtle::ConstantTimeEq; - let (a, b) = (a.trim().as_bytes(), b.trim().as_bytes()); - if a.len() != b.len() { - return false; - } - a.ct_eq(b).into() -} - +#[cfg(not(target_os = "android"))] fn load_keyring_credentials() -> Result> { let Some(json_or_manifest) = get_keyring_password(KEYRING_CREDENTIALS_ACCOUNT)? else { return Ok(None); @@ -989,8 +821,6 @@ fn load_keyring_credentials() -> Result> { let manifest = read_chunk_manifest(&json_or_manifest) .ok_or_else(|| anyhow!("invalid system credential vault manifest"))?; - // issue #602:manifest 刚从 keychain 读出,进缓存 —— 后续 save 不必再读一次。 - *credentials_manifest_cache().lock() = Some(manifest.clone()); let mut json = String::new(); for index in 0..manifest.chunks { let account = chunk_account(manifest.generation.as_deref(), index); @@ -1004,6 +834,7 @@ fn load_keyring_credentials() -> Result> { .context("decode system credential vault payload") } +#[cfg(not(target_os = "android"))] fn load_legacy_keyring_credentials() -> CredsRoot { match load_legacy_keyring_credentials_for_update() { Ok(root) => root, @@ -1014,6 +845,7 @@ fn load_legacy_keyring_credentials() -> CredsRoot { } } +#[cfg(not(target_os = "android"))] fn load_legacy_keyring_credentials_for_update() -> Result { let mut root = CredsRoot::default(); for account in CredentialAccount::all() { @@ -1027,6 +859,7 @@ fn load_legacy_keyring_credentials_for_update() -> Result { Ok(clean_credentials(&root)) } +#[cfg(not(target_os = "android"))] fn remove_legacy_keyring_credentials() { for account in CredentialAccount::all() { delete_keyring_password(account.keyring_account()); @@ -1048,9 +881,12 @@ fn load_legacy_sources_without_migration() -> CredsRoot { return legacy; } - let legacy_vault = load_legacy_keyring_credentials(); - if legacy_vault_has_credentials(&legacy_vault) { - return legacy_vault; + #[cfg(not(target_os = "android"))] + { + let legacy_vault = load_legacy_keyring_credentials(); + if legacy_vault_has_credentials(&legacy_vault) { + return legacy_vault; + } } CredsRoot::default() @@ -1069,15 +905,19 @@ fn migrate_legacy_sources() -> CredsRoot { fn migrate_legacy_sources_for_update() -> Result { if let Some(legacy) = load_legacy_credentials() { save_credentials(&legacy)?; + #[cfg(not(target_os = "android"))] remove_legacy_keyring_credentials(); return Ok(legacy); } - let legacy_vault = load_legacy_keyring_credentials_for_update()?; - if legacy_vault_has_credentials(&legacy_vault) { - save_credentials(&legacy_vault)?; - remove_legacy_keyring_credentials(); - return Ok(legacy_vault); + #[cfg(not(target_os = "android"))] + { + let legacy_vault = load_legacy_keyring_credentials_for_update()?; + if legacy_vault_has_credentials(&legacy_vault) { + save_credentials(&legacy_vault)?; + remove_legacy_keyring_credentials(); + return Ok(legacy_vault); + } } Ok(CredsRoot::default()) @@ -1087,6 +927,22 @@ fn load_credentials() -> CredsRoot { if let Some(cached) = credentials_cache().lock().as_ref().cloned() { return cached; } + + #[cfg(target_os = "android")] + { + let root = match load_android_credentials() { + Ok(Some(root)) => root, + Ok(None) => CredsRoot::default(), + Err(e) => { + log::warn!("[vault] android credential read failed: {e}"); + CredsRoot::default() + } + }; + store_credentials_cache(&root); + return root; + } + + #[cfg(not(target_os = "android"))] match load_keyring_credentials() { Ok(Some(root)) => { // 不在这里调 remove_legacy_keyring_credentials() —— 它内部对每个 @@ -1123,6 +979,18 @@ fn load_credentials_for_update() -> Result { if let Some(cached) = credentials_cache().lock().as_ref().cloned() { return Ok(cached); } + + #[cfg(target_os = "android")] + { + let root = match load_android_credentials()? { + Some(root) => root, + None => CredsRoot::default(), + }; + store_credentials_cache(&root); + return Ok(root); + } + + #[cfg(not(target_os = "android"))] match load_keyring_credentials() { Ok(Some(root)) => { // 同 load_credentials:不再每次 update 都尝试 delete legacy keyring @@ -1146,96 +1014,70 @@ fn load_credentials_for_update() -> Result { fn save_credentials(root: &CredsRoot) -> Result<()> { let cleaned = clean_credentials(root); - let json = serde_json::to_string(&cleaned).context("encode credentials failed")?; - // issue #602:上次成功读/写的 manifest 有进程缓存时不再回 keychain 读 —— - // macOS 上这次读本身就要过 ACL 检查(一次弹窗)。冷路径才读真实 manifest。 - let previous_manifest = credentials_manifest_cache().lock().clone().or_else(|| { - get_keyring_password(KEYRING_CREDENTIALS_ACCOUNT) + + #[cfg(target_os = "android")] + { + save_android_credentials(&cleaned)?; + store_credentials_cache(&cleaned); + return Ok(()); + } + + #[cfg(not(target_os = "android"))] + { + let json = serde_json::to_string(&cleaned).context("encode credentials failed")?; + let previous_manifest = get_keyring_password(KEYRING_CREDENTIALS_ACCOUNT) .ok() .flatten() - .and_then(|value| read_chunk_manifest(&value)) - }); - let chunks = chunk_json_payload(&json); - - // issue #602:切换供应商等小改动会触发整套「重写所有 chunks + manifest」,每个 - // keychain 条目各自 ACL、各弹一次「OpenLess 想访问钥匙串」。用进程缓存里上次 - // 成功落盘的 root 反推各 chunk 旧内容,内容没变的 chunk 跳过重写。仅当旧 - // manifest 已是稳定名(generation=None)时可跳 —— UUID 代际的旧 chunk 账户名 - // 不同,内容相同也必须写到新稳定名。缓存序列化顺序偶有差异时只会多写(回到 - // 旧行为),不会漏写。 - let previous_json: Option = match &previous_manifest { - Some(m) if m.generation.is_none() => credentials_cache() - .lock() - .as_ref() - .and_then(|prev| serde_json::to_string(prev).ok()), - _ => None, - }; - let skip = chunk_skip_mask(previous_json.as_deref(), &chunks); - - // 先写所有 chunks(稳定名),再写 manifest —— 保证 partial-write 不会让 - // manifest 指向不完整 chunks。stable name 让 macOS Keychain ACL 一次允许后 - // 长期有效,不再因 UUID 轮换反复弹窗(这是 PR #277 早期 UUID-rotation - // 设计的回退)。 - let mut chunks_written = 0usize; - for (index, chunk) in chunks.iter().enumerate() { - if skip[index] { - continue; - } - let account = chunk_account(None, index); - keyring_entry_for(&account)? - .set_password(chunk) - .with_context(|| format!("write system credential vault chunk {index}"))?; - chunks_written += 1; - } + .and_then(|value| read_chunk_manifest(&value)); + let chunks = chunk_json_payload(&json); - let manifest = CredsChunkManifest { - openless_credentials_storage: "chunked".to_string(), - version: 1, - generation: None, - chunks: chunks.len(), - }; - // manifest 内容只由 chunks 数决定:数量没变且旧 manifest 已是稳定名时内容 - // 逐字节一致,跳过重写(又省一次 ACL 弹窗)。 - let manifest_unchanged = previous_manifest - .as_ref() - .is_some_and(|m| m.generation.is_none() && m.chunks == chunks.len()); - if !manifest_unchanged { + // 先写所有 chunks(稳定名),再写 manifest —— 保证 partial-write 不会让 + // manifest 指向不完整 chunks。stable name 让 macOS Keychain ACL 一次允许后 + // 长期有效,不再因 UUID 轮换反复弹窗(这是 PR #277 早期 UUID-rotation + // 设计的回退)。 + for (index, chunk) in chunks.iter().enumerate() { + let account = chunk_account(None, index); + keyring_entry_for(&account)? + .set_password(chunk) + .with_context(|| format!("write system credential vault chunk {index}"))?; + } + + let manifest = CredsChunkManifest { + openless_credentials_storage: "chunked".to_string(), + version: 1, + generation: None, + chunks: chunks.len(), + }; let manifest_json = serde_json::to_string(&manifest).context("encode credential manifest failed")?; keyring_entry()? .set_password(&manifest_json) .context("write system credential vault manifest")?; - } - log::info!( - "[vault] save_credentials: {chunks_written}/{} chunks written, manifest {}", - chunks.len(), - if manifest_unchanged { "unchanged" } else { "rewritten" } - ); - - // 清理旧 chunks: - // 1) 旧 manifest 用 UUID generation → 那一代 chunks 全删(迁移到 stable name) - // 2) 旧 manifest 也是 stable name,但 chunks 数量比这次多 → 删多余的 idx - if let Some(previous) = previous_manifest { - match previous.generation.as_deref() { - Some(prev_gen) => { - for index in 0..previous.chunks { - delete_keyring_password(&chunk_account(Some(prev_gen), index)); + + // 清理旧 chunks: + // 1) 旧 manifest 用 UUID generation → 那一代 chunks 全删(迁移到 stable name) + // 2) 旧 manifest 也是 stable name,但 chunks 数量比这次多 → 删多余的 idx + if let Some(previous) = previous_manifest { + match previous.generation.as_deref() { + Some(prev_gen) => { + for index in 0..previous.chunks { + delete_keyring_password(&chunk_account(Some(prev_gen), index)); + } } - } - None => { - for index in chunks.len()..previous.chunks { - delete_keyring_password(&chunk_account(None, index)); + None => { + for index in chunks.len()..previous.chunks { + delete_keyring_password(&chunk_account(None, index)); + } } } } - } - remove_legacy_credentials_file_best_effort(); - // 写完成功后立刻刷新 process cache —— 同进程后续读不再回 Keychain。 - // 见 CREDENTIALS_CACHE 的 doc。 - store_credentials_cache(&cleaned); - *credentials_manifest_cache().lock() = Some(manifest); - Ok(()) + remove_legacy_credentials_file_best_effort(); + // 写完成功后立刻刷新 process cache —— 同进程后续读不再回 Keychain。 + // 见 CREDENTIALS_CACHE 的 doc。 + store_credentials_cache(&cleaned); + Ok(()) + } } fn lookup_account(root: &CredsRoot, account: CredentialAccount) -> Option { @@ -1308,20 +1150,6 @@ fn write_account(root: &mut CredsRoot, account: CredentialAccount, value: Option // ───────────────────────── HistoryStore ───────────────────────── -/// 听写历史(`history.json`)。 -/// -/// **At-rest 行为(issue #609 F-03 / F-04)**: -/// - 内容以**明文 JSON** 落盘(`DictationSession[]`),便于用户导出/审阅。 -/// - 机密性靠 **OS 文件系统权限**:unix 下写入后收紧到 `0o600`(仅属主可读写); -/// Windows 走 `%APPDATA%` 的 per-user ACL。 -/// - 完整性靠 **HMAC-SHA256**(F-03):密钥 32 字节随机、存**同目录 0o600 文件** -/// (`history_hmac.key`,原 keyring 版因 ad-hoc 签名反复弹钥匙串而迁出,密钥与明文 -/// history 同目录、安全收益对等);每次写入算 HMAC 写 sidecar `history.json.hmac`; -/// 读取时校验,不匹配则 fail-safe 返回空历史,绝不把被篡改的历史喂给下游 LLM。 -/// -/// **已知残留**:尚未做**完整静态加密(at-rest encryption)**——本地能读文件的 -/// 攻击者仍可读到明文历史(但无法在不被发现的情况下篡改)。完整加密(用 keyring -/// 派生密钥加密整个文件)留待后续,见 issue #609 F-04(明确允许"clearly document")。 pub struct HistoryStore { path: PathBuf, lock: Mutex<()>, @@ -1411,9 +1239,6 @@ impl HistoryStore { self.write_locked(&sessions) } - /// 原地替换 id 匹配的历史条目(保持原位置)。用于「重新转录」成功后回写 - /// rawTranscript / finalText / error_code(issue #613)。找不到对应 id 时返回 - /// `Ok(false)`,调用方据此提示「历史条目已不存在」。 pub fn update_entry(&self, updated: DictationSession) -> Result { let _guard = self.lock.lock(); let mut sessions = self.read_locked()?; @@ -1430,142 +1255,13 @@ impl HistoryStore { self.write_locked(&Vec::::new()) } - /// issue #609 F-03:读 history 前先做 HMAC 完整性校验。 - /// - /// - HMAC 密钥不可用(keyring 缺失等)→ 退化为不校验,按内容直接读(保持可用)。 - /// - 文件不存在 / 为空 → 空历史(正常首次启动)。 - /// - sidecar 存在且 HMAC 不匹配 → **判定被投毒/损坏,fail-safe 返回空历史**并 - /// log::warn,绝不把被篡改的历史喂给下游 LLM(对话感知 polish)。 - /// - sidecar 缺失但**标志未置位** → 真正的 legacy 文件:接受当前内容、补写 sidecar、 - /// 置位 enrolled 标志完成迁移(issue #609 C-01)。 - /// - sidecar 缺失但**标志已置位** → 攻击者删了 sidecar 想伪装 legacy:fail-safe 返回空。 fn read_locked(&self) -> Result> { - read_history_with_key(&self.path, history_hmac_key().as_deref(), &FileEnrollment) + read_or_default::>(&self.path) } - /// issue #609 F-03/F-04/C-01:写 history 后算 HMAC 写 sidecar;unix 下把两文件都设 - /// 0o600;首次写顺带置位 enrolled 标志。 fn write_locked(&self, sessions: &[DictationSession]) -> Result<()> { let json = serde_json::to_vec_pretty(sessions).context("encode history failed")?; - write_history_with_key( - &self.path, - &json, - history_hmac_key().as_deref(), - &FileEnrollment, - ) - } -} - -/// 读 history 并按 `key` 做 HMAC 完整性校验(纯函数,便于单测)。 -/// -/// `key == None`:无密钥,退化为不校验,按内容直接读(与历史行为一致)。 -/// 详细语义见 `HistoryStore::read_locked` 的文档。 -fn read_history_with_key( - path: &Path, - key: Option<&[u8]>, - enrollment: &dyn HmacEnrollment, -) -> Result> { - let Some(key) = key else { - return read_or_default::>(path); - }; - // TOCTOU 收口(rust):不先 exists() 再 read(),直接 read(),NotFound 当空历史。 - let bytes = match fs::read(path) { - Ok(bytes) => bytes, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), - Err(e) => return Err(e).with_context(|| format!("read failed: {}", path.display())), - }; - if bytes.is_empty() { - return Ok(Vec::new()); - } - let sidecar = history_hmac_sidecar_path(path); - let expected = compute_history_hmac(key, &bytes); - match fs::read_to_string(&sidecar) { - Ok(stored) => { - if !hmac_hex_eq(stored.trim(), &expected) { - // 投毒/损坏:fail-safe,不解析、不喂下游。 - log::warn!( - "[history] HMAC 校验失败(疑似被篡改或损坏),fail-safe 返回空历史:{}", - path.display() - ); - return Ok(Vec::new()); - } - serde_json::from_slice::>(&bytes) - .with_context(|| format!("decode failed: {}", path.display())) - } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - // issue #609 C-01:sidecar 缺失要分两种情况。 - if enrollment.is_enrolled() { - // 标志已置位:本应有 sidecar 却没了 → 攻击者删 sidecar 想伪装 legacy - // 绕过 HMAC。fail-safe:返回空历史,绝不接受、绝不补签。 - log::warn!( - "[history] HMAC 已启用但 sidecar 缺失(疑似被删除以绕过完整性校验),fail-safe 返回空历史:{}", - path.display() - ); - return Ok(Vec::new()); - } - // 真正的 legacy:老用户的 history.json 从没带过 .hmac → 接受、补写 sidecar、 - // 置位 enrolled 标志完成迁移。此后再缺 sidecar 就会落到上面的 fail-safe。 - log::info!( - "[history] 未发现 HMAC sidecar 且未启用,视为 legacy 文件,接受并补写完整性标记" - ); - if let Err(err) = write_hmac_sidecar(&sidecar, &expected) { - log::warn!("[history] 迁移补写 HMAC sidecar 失败:{err}"); - } else { - enrollment.set_enrolled(); - } - serde_json::from_slice::>(&bytes) - .with_context(|| format!("decode failed: {}", path.display())) - } - Err(e) => { - Err(e).with_context(|| format!("read HMAC sidecar failed: {}", sidecar.display())) - } - } -} - -/// 写 history JSON、收紧权限、按 `key` 写 HMAC sidecar(纯函数,便于单测)。 -/// -/// issue #609 C-01:成功写出 sidecar 后**幂等置位 enrolled 标志**——这样此后任何 -/// sidecar 缺失都会被读路径判定为篡改,而不是误当 legacy 接受。 -fn write_history_with_key( - path: &Path, - json: &[u8], - key: Option<&[u8]>, - enrollment: &dyn HmacEnrollment, -) -> Result<()> { - // M-01:history.json 明文存储,at-rest 防护靠 OS 文件系统权限——unix 在 rename - // **之前**就把 tmp 文件设 0o600,消除「rename 后再 chmod」之间的世界可读窗口。 - atomic_write_private(path, json)?; - if let Some(key) = key { - let sidecar = history_hmac_sidecar_path(path); - let mac = compute_history_hmac(key, json); - match write_hmac_sidecar(&sidecar, &mac) { - Ok(()) => enrollment.set_enrolled(), - // sidecar 写失败不阻断主写入(数据已落盘),但记一笔——也不置位标志, - // 让下次读仍能走 legacy 迁移补写,而不是误判篡改。 - Err(e) => log::warn!("[history] 写 HMAC sidecar 失败:{e}"), - } - } - Ok(()) -} - -/// 写 HMAC sidecar 文件并(unix)在 rename 前设 0o600(M-01:无 umask 暴露窗口)。 -fn write_hmac_sidecar(sidecar: &Path, hmac_hex: &str) -> Result<()> { - atomic_write_private(sidecar, hmac_hex.as_bytes()) -} - -/// issue #609 F-04:unix 下把文件权限收紧到 0o600(仅属主可读写)。 -/// Windows / 其他平台 no-op(依赖用户目录 ACL)。best-effort:失败只 warn。 -fn restrict_file_permissions_best_effort(path: &Path) { - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - if let Err(e) = fs::set_permissions(path, fs::Permissions::from_mode(0o600)) { - log::warn!("[history] 设置 {} 权限 0o600 失败:{e}", path.display()); - } - } - #[cfg(not(unix))] - { - let _ = path; + atomic_write(&self.path, &json) } } @@ -2835,217 +2531,14 @@ impl CredentialsVault { #[cfg(test)] mod tests { use super::{ - chunk_json_payload, chunk_skip_mask, compute_history_hmac, decode_hex, encode_hex, - history_hmac_sidecar_path, hmac_hex_eq, list_vocab_presets, read_history_with_key, - read_preferences, save_vocab_presets, sync_style_pack_preferences, - validate_correction_rule_syntax, write_history_with_key, HmacEnrollment, + chunk_json_payload, list_vocab_presets, read_preferences, save_vocab_presets, + sync_style_pack_preferences, validate_correction_rule_syntax, KEYRING_CHUNK_MAX_UTF16_UNITS, }; use crate::types::{builtin_style_packs, CustomStylePrompts, VocabPreset, VocabPresetStore}; - use std::cell::Cell; use std::fs; use std::path::PathBuf; - /// 内存版 enrolled 标志,单测注入,不打真 keyring。 - #[derive(Default)] - struct MemEnrollment { - enrolled: Cell, - } - - impl MemEnrollment { - fn new() -> Self { - Self::default() - } - fn enrolled() -> Self { - let m = Self::default(); - m.enrolled.set(true); - m - } - } - - impl HmacEnrollment for MemEnrollment { - fn is_enrolled(&self) -> bool { - self.enrolled.get() - } - fn set_enrolled(&self) { - self.enrolled.set(true); - } - } - - fn history_test_dir() -> PathBuf { - let dir = - std::env::temp_dir().join(format!("openless-history-hmac-{}", uuid::Uuid::new_v4())); - fs::create_dir_all(&dir).expect("create temp dir"); - dir - } - - // 一条最小但合法的 DictationSession JSON 数组,足够 serde 往返。 - // DictationSession 是 camelCase 序列化。 - const SAMPLE_HISTORY_JSON: &str = r#"[{ - "id": "s1", - "createdAt": "2026-06-07T00:00:00Z", - "rawTranscript": "你好", - "finalText": "你好。", - "mode": "light", - "appBundleId": null, - "appName": null, - "insertStatus": "inserted", - "errorCode": null, - "durationMs": null, - "dictionaryEntryCount": null - }]"#; - - #[test] - fn hex_roundtrip() { - let bytes = [0u8, 1, 15, 16, 255, 128, 42]; - assert_eq!(decode_hex(&encode_hex(&bytes)).unwrap(), bytes); - assert_eq!(encode_hex(&[0xab, 0xcd]), "abcd"); - assert!(decode_hex("xyz").is_err()); - } - - #[test] - fn hmac_hex_eq_constant_time_matches() { - let key = b"k"; - let a = compute_history_hmac(key, b"hello"); - let b = compute_history_hmac(key, b"hello"); - let c = compute_history_hmac(key, b"world"); - assert!(hmac_hex_eq(&a, &b)); - assert!(!hmac_hex_eq(&a, &c)); - assert!(!hmac_hex_eq(&a, "deadbeef")); - } - - #[test] - fn history_write_then_read_passes_verification() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [7u8; 32]; - let enr = MemEnrollment::new(); - write_history_with_key(&path, SAMPLE_HISTORY_JSON.as_bytes(), Some(&key), &enr).unwrap(); - // sidecar 应存在,且写入后置位 enrolled 标志。 - assert!(history_hmac_sidecar_path(&path).exists()); - assert!(enr.is_enrolled(), "首次写 sidecar 后必须置位 enrolled 标志"); - let sessions = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert_eq!(sessions.len(), 1); - assert_eq!(sessions[0].id, "s1"); - } - - #[test] - fn history_tampered_bytes_fail_safe_to_empty() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [9u8; 32]; - let enr = MemEnrollment::new(); - write_history_with_key(&path, SAMPLE_HISTORY_JSON.as_bytes(), Some(&key), &enr).unwrap(); - // 攻击者篡改 history.json,但 sidecar 仍是旧 HMAC → 校验失败。 - let tampered = SAMPLE_HISTORY_JSON.replace("你好。", "被注入的内容"); - fs::write(&path, tampered.as_bytes()).unwrap(); - let sessions = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert!( - sessions.is_empty(), - "篡改后必须 fail-safe 返回空历史,不喂下游" - ); - } - - #[test] - fn history_legacy_without_sidecar_is_accepted_and_migrated() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [3u8; 32]; - // 老用户:只有 history.json,没有 .hmac,且 enrolled 标志未置位。 - fs::write(&path, SAMPLE_HISTORY_JSON.as_bytes()).unwrap(); - let sidecar = history_hmac_sidecar_path(&path); - assert!(!sidecar.exists()); - let enr = MemEnrollment::new(); - // 首次读:接受并补写 sidecar,置位标志。 - let sessions = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert_eq!(sessions.len(), 1); - assert!( - sidecar.exists(), - "legacy 读取后必须补写 HMAC sidecar 完成迁移" - ); - assert!( - enr.is_enrolled(), - "legacy 迁移后必须置位 enrolled 标志(C-01)" - ); - // 迁移后再读,HMAC 已匹配,仍正常返回。 - let again = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert_eq!(again.len(), 1); - } - - /// issue #609 C-01 核心回归:enrolled 已置位后攻击者删 sidecar 想伪装 legacy, - /// 必须 fail-safe 返回空(不再误当 legacy 接受+补签)。 - #[test] - fn history_enrolled_then_sidecar_deleted_fails_safe_not_legacy() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [5u8; 32]; - let enr = MemEnrollment::new(); - // 正常写入:sidecar 生成,标志置位。 - write_history_with_key(&path, SAMPLE_HISTORY_JSON.as_bytes(), Some(&key), &enr).unwrap(); - let sidecar = history_hmac_sidecar_path(&path); - assert!(sidecar.exists()); - assert!(enr.is_enrolled()); - // 攻击者篡改 history.json 并删除 sidecar,企图把篡改内容伪装成 legacy。 - let tampered = SAMPLE_HISTORY_JSON.replace("你好。", "被注入的内容"); - fs::write(&path, tampered.as_bytes()).unwrap(); - fs::remove_file(&sidecar).unwrap(); - let sessions = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert!( - sessions.is_empty(), - "enrolled 后 sidecar 缺失必须判定篡改、fail-safe 返回空,不接受、不补签" - ); - // 关键:不得偷偷补回 sidecar(不补签)。 - assert!(!sidecar.exists(), "fail-safe 路径不得为攻击者补写 sidecar"); - } - - /// enrolled 已置位、sidecar 缺失但 history 也未篡改 —— 仍按篡改处理(fail-safe)。 - /// 因为读路径无法区分「无害删除」与「篡改后删除」,一律保守。 - #[test] - fn history_enrolled_sidecar_missing_is_failsafe_even_if_content_intact() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [6u8; 32]; - let enr = MemEnrollment::enrolled(); - // history 内容合法,但 sidecar 从未写入(已 enrolled)。 - fs::write(&path, SAMPLE_HISTORY_JSON.as_bytes()).unwrap(); - let sessions = read_history_with_key(&path, Some(&key), &enr).unwrap(); - assert!(sessions.is_empty(), "enrolled + 无 sidecar → fail-safe"); - } - - #[test] - fn history_no_key_reads_without_verification() { - let dir = history_test_dir(); - let path = dir.join("history.json"); - fs::write(&path, SAMPLE_HISTORY_JSON.as_bytes()).unwrap(); - // key=None:退化为不校验,按内容读。 - let sessions = read_history_with_key(&path, None, &MemEnrollment::new()).unwrap(); - assert_eq!(sessions.len(), 1); - } - - #[cfg(unix)] - #[test] - fn history_write_sets_0600_permissions() { - use std::os::unix::fs::PermissionsExt; - let dir = history_test_dir(); - let path = dir.join("history.json"); - let key = [1u8; 32]; - write_history_with_key( - &path, - SAMPLE_HISTORY_JSON.as_bytes(), - Some(&key), - &MemEnrollment::new(), - ) - .unwrap(); - let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777; - assert_eq!(mode, 0o600, "history.json 应为 0o600"); - let sidecar_mode = fs::metadata(history_hmac_sidecar_path(&path)) - .unwrap() - .permissions() - .mode() - & 0o777; - assert_eq!(sidecar_mode, 0o600, "sidecar 应为 0o600"); - } - #[test] fn credential_payload_chunks_stay_under_windows_blob_limit() { let payload = format!( @@ -3062,41 +2555,6 @@ mod tests { .all(|chunk| chunk.encode_utf16().count() <= KEYRING_CHUNK_MAX_UTF16_UNITS)); } - #[test] - fn chunk_skip_mask_skips_unchanged_and_rewrites_changed() { - // issue #602:内容完全一致 → 全部跳过(no-op save 不再碰 keychain chunks)。 - let json = format!( - "{}{}", - "a".repeat(KEYRING_CHUNK_MAX_UTF16_UNITS), - "b".repeat(KEYRING_CHUNK_MAX_UTF16_UNITS) - ); - let chunks = chunk_json_payload(&json); - assert_eq!(chunks.len(), 2); - assert!(chunk_skip_mask(Some(&json), &chunks).iter().all(|s| *s)); - - // 等长改动只落在第 2 个 chunk → 第 1 个跳过、第 2 个重写。 - let changed = format!( - "{}{}", - "a".repeat(KEYRING_CHUNK_MAX_UTF16_UNITS), - "c".repeat(KEYRING_CHUNK_MAX_UTF16_UNITS) - ); - let changed_chunks = chunk_json_payload(&changed); - assert_eq!( - chunk_skip_mask(Some(&json), &changed_chunks), - vec![true, false] - ); - - // 冷缓存(None)→ 全部重写,等同旧行为。 - assert!(chunk_skip_mask(None, &chunks).iter().all(|s| !*s)); - - // 旧内容更短 → 超出部分无旧 chunk 可比,必须写。 - let shorter = "a".repeat(KEYRING_CHUNK_MAX_UTF16_UNITS); - assert_eq!( - chunk_skip_mask(Some(&shorter), &chunks), - vec![true, false] - ); - } - #[test] fn legacy_streaming_insert_false_is_migrated_and_marker_is_persisted() { let tmp: PathBuf = diff --git a/openless-all/app/src-tauri/src/recorder.rs b/openless-all/app/src-tauri/src/recorder.rs index a943b783..a87e1ee9 100644 --- a/openless-all/app/src-tauri/src/recorder.rs +++ b/openless-all/app/src-tauri/src/recorder.rs @@ -311,7 +311,8 @@ fn build_input_stream( .map_err(|e| classify_default_config_err(e.to_string()))?; let sample_format = supported.sample_format(); - let config: StreamConfig = supported.config(); + let default_config: StreamConfig = supported.config(); + let config = stable_input_config_for_platform(&default_config); let input_sr = config.sample_rate.0; let channels = config.channels as usize; @@ -324,21 +325,59 @@ fn build_input_stream( ); let state = Arc::new(StreamState::new()); - let stream = build_stream_for_format( + let stream = match build_stream_for_format( &device, &config, sample_format, - consumer, - level_handler, - archiver, + Arc::clone(&consumer), + Arc::clone(&level_handler), + archiver.clone(), Arc::clone(&state), input_sr, channels, - runtime_error_tx, - )?; + runtime_error_tx.clone(), + ) { + Ok(stream) => stream, + Err(err) if config != default_config => { + log::warn!( + "[recorder] stable input config failed; falling back to default config: {err}" + ); + build_stream_for_format( + &device, + &default_config, + sample_format, + consumer, + level_handler, + archiver, + Arc::clone(&state), + default_config.sample_rate.0, + default_config.channels as usize, + runtime_error_tx, + )? + } + Err(err) => return Err(err), + }; Ok((stream, state)) } +#[cfg(target_os = "android")] +fn stable_input_config_for_platform(default_config: &StreamConfig) -> StreamConfig { + let mut config = default_config.clone(); + if config.channels > 1 { + log::info!( + "[recorder] android forcing mono input channels: {} -> 1", + config.channels + ); + config.channels = 1; + } + config +} + +#[cfg(not(target_os = "android"))] +fn stable_input_config_for_platform(default_config: &StreamConfig) -> StreamConfig { + default_config.clone() +} + fn select_input_device( host: &cpal::Host, microphone_device_name: Option<&str>, diff --git a/openless-all/app/src-tauri/src/remote_server/mod.rs b/openless-all/app/src-tauri/src/remote_server/mod.rs index d6eb39c4..347cc263 100644 --- a/openless-all/app/src-tauri/src/remote_server/mod.rs +++ b/openless-all/app/src-tauri/src/remote_server/mod.rs @@ -299,30 +299,47 @@ fn build_router(state: Arc) -> Router { .route("/", get(index_handler)) .route( "/app.js", - get(|| async { ([(axum::http::header::CONTENT_TYPE, HEADER_JS)], assets::APP_JS) }), + get(|| async { + ( + [(axum::http::header::CONTENT_TYPE, HEADER_JS)], + assets::APP_JS, + ) + }), ) .route( "/style.css", get(|| async { - ([(axum::http::header::CONTENT_TYPE, HEADER_CSS)], assets::STYLE_CSS) + ( + [(axum::http::header::CONTENT_TYPE, HEADER_CSS)], + assets::STYLE_CSS, + ) }), ) .route( "/icon.png", get(|| async { - ([(axum::http::header::CONTENT_TYPE, "image/png")], assets::ICON_PNG) + ( + [(axum::http::header::CONTENT_TYPE, "image/png")], + assets::ICON_PNG, + ) }), ) .route( "/mic.png", get(|| async { - ([(axum::http::header::CONTENT_TYPE, "image/png")], assets::MIC_PNG) + ( + [(axum::http::header::CONTENT_TYPE, "image/png")], + assets::MIC_PNG, + ) }), ) .route( "/done.png", get(|| async { - ([(axum::http::header::CONTENT_TYPE, "image/png")], assets::DONE_PNG) + ( + [(axum::http::header::CONTENT_TYPE, "image/png")], + assets::DONE_PNG, + ) }), ) // 证书下载:手机在浏览器打开它即可下载并安装信任(iOS Safari 的 wss 不复用 @@ -331,7 +348,10 @@ fn build_router(state: Arc) -> Router { "/cert.cer", get(|State(state): State>| async move { ( - [(axum::http::header::CONTENT_TYPE, "application/x-x509-ca-cert")], + [( + axum::http::header::CONTENT_TYPE, + "application/x-x509-ca-cert", + )], state.cert_der.clone(), ) }), @@ -548,19 +568,25 @@ async fn handle_ws(mut socket: WebSocket, state: Arc, peer_ip: IpAddr) match authed { AuthResult::Ok => { log::info!("[remote-input] 配对成功,进入录音会话"); - let _ = socket.send(send_json(&serde_json::json!({"type":"auth","ok":true}))).await; + let _ = socket + .send(send_json(&serde_json::json!({"type":"auth","ok":true}))) + .await; } AuthResult::BadPin => { log::warn!("[remote-input] 配对码错误,已拒绝"); let _ = socket - .send(send_json(&serde_json::json!({"type":"auth","ok":false,"reason":"bad-pin"}))) + .send(send_json( + &serde_json::json!({"type":"auth","ok":false,"reason":"bad-pin"}), + )) .await; return; } AuthResult::Locked => { log::warn!("[remote-input] 配对已锁定(连续错误过多),已拒绝"); let _ = socket - .send(send_json(&serde_json::json!({"type":"auth","ok":false,"reason":"locked"}))) + .send(send_json( + &serde_json::json!({"type":"auth","ok":false,"reason":"locked"}), + )) .await; return; } @@ -666,7 +692,9 @@ async fn handle_control(txt: &str, state: &Arc, socket: &mut WebSocket) Err(reason) => { log::warn!("[remote-input] 开始录音被拒:{reason}"); let _ = socket - .send(send_json(&serde_json::json!({"type":"busy","reason":reason}))) + .send(send_json( + &serde_json::json!({"type":"busy","reason":reason}), + )) .await; } } diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index 7df37f60..8794081e 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -3,6 +3,22 @@ use serde::{Deserialize, Serialize}; +#[path = "android/types.rs"] +pub mod android_types; + +use android_types::{ + default_android_insert_strategy, default_android_overlay_activation_mode, + default_android_overlay_cancel_swipe_direction, default_android_overlay_left_swipe_action, + default_android_overlay_size_dp, default_android_overlay_trigger, + normalize_android_insert_strategy, normalize_android_overlay_size_dp, +}; +pub use android_types::{ + AndroidAccessibilityState, AndroidAccessibilityStatus, AndroidInsertStrategy, + AndroidOverlayActivationMode, AndroidOverlayCancelSwipeDirection, + AndroidOverlayLeftSwipeAction, AndroidOverlayPermissionState, AndroidOverlayStatus, + AndroidOverlayTrigger, +}; + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] #[derive(Default)] @@ -636,6 +652,18 @@ pub struct UserPreferences { /// 热键 2:快取用键(选中→Claude→回插)。默认 `None`(用户自配)。 #[serde(default)] pub coding_agent_quick_hotkey: Option, + /// 局域网远程输入服务开关。桌面端启动 HTTPS+WS 服务,手机浏览器推 PCM 到电脑。 + #[serde(default)] + pub remote_input_enabled: bool, + /// 局域网远程输入服务端口。 + #[serde(default = "default_remote_input_port")] + pub remote_input_port: u16, + /// 当前远程输入 PIN。真实运行时 PIN 另有进程内/磁盘路径维护,此字段保留 wire 兼容。 + #[serde(default)] + pub remote_input_pin: String, + /// 远程输入默认按钮模式。 + #[serde(default = "default_remote_input_mode")] + pub remote_input_default_mode: String, /// 本地 Qwen3-ASR 当前激活的模型 id("qwen3-asr-0.6b" / "qwen3-asr-1.7b")。 /// 仅在 active_asr_provider == "local-qwen3" 时有意义。 #[serde(default = "default_local_asr_model")] @@ -749,19 +777,28 @@ pub struct UserPreferences { /// 上传 / 点赞需要带这个 header;空时上传被后端 401。 #[serde(default)] pub marketplace_dev_login: String, - /// ── 远程输入(局域网手机录音)──────────────────────────────── - /// 是否启用远程输入 HTTPS+WS 服务。默认 false(关闭,按需手动开启)。 - #[serde(default)] - pub remote_input_enabled: bool, - /// 远程输入服务监听端口(HTTPS)。默认 8443。 - #[serde(default = "default_remote_input_port")] - pub remote_input_port: u16, - /// 远程输入配对码(6 位数字)。空 = server 首次启动时随机生成并回写。 - #[serde(default)] - pub remote_input_pin: String, - /// 手机录音页默认交互方式:"toggle"(点击切换)/ "hold"(按住说话)。 - #[serde(default = "default_remote_input_mode")] - pub remote_input_default_mode: String, + /// Android: text insertion strategy for cross-app dictation results. + #[serde(default = "default_android_insert_strategy")] + pub android_insert_strategy: AndroidInsertStrategy, + /// Android: when to show the floating overlay control. + #[serde(default = "default_android_overlay_trigger")] + pub android_overlay_trigger: AndroidOverlayTrigger, + /// Android: how the floating overlay enters the armed interaction state. + #[serde(default = "default_android_overlay_activation_mode")] + pub android_overlay_activation_mode: AndroidOverlayActivationMode, + /// Android: action performed by left swiping while the overlay is armed. + #[serde(default = "default_android_overlay_left_swipe_action")] + pub android_overlay_left_swipe_action: AndroidOverlayLeftSwipeAction, + /// Android: vertical swipe direction that cancels recording. + #[serde(default = "default_android_overlay_cancel_swipe_direction")] + pub android_overlay_cancel_swipe_direction: AndroidOverlayCancelSwipeDirection, + /// Android: floating overlay control diameter in dp. + #[serde(default = "default_android_overlay_size_dp")] + pub android_overlay_size_dp: u32, +} + +fn default_local_asr_model() -> String { + "qwen3-asr-0.6b".into() } fn default_remote_input_port() -> u16 { @@ -772,10 +809,6 @@ fn default_remote_input_mode() -> String { "toggle".into() } -fn default_local_asr_model() -> String { - "qwen3-asr-0.6b".into() -} - fn default_history_retention_days() -> u32 { 7 } @@ -871,6 +904,14 @@ struct UserPreferencesWire { coding_agent_panel_hotkey: Option, #[serde(default)] coding_agent_quick_hotkey: Option, + #[serde(default)] + remote_input_enabled: bool, + #[serde(default = "default_remote_input_port")] + remote_input_port: u16, + #[serde(default)] + remote_input_pin: String, + #[serde(default = "default_remote_input_mode")] + remote_input_default_mode: String, #[serde(default = "default_local_asr_model")] local_asr_active_model: String, #[serde(default = "default_local_asr_mirror")] @@ -919,14 +960,18 @@ struct UserPreferencesWire { marketplace_base_url: String, #[serde(default)] marketplace_dev_login: String, - #[serde(default)] - remote_input_enabled: bool, - #[serde(default = "default_remote_input_port")] - remote_input_port: u16, - #[serde(default)] - remote_input_pin: String, - #[serde(default = "default_remote_input_mode")] - remote_input_default_mode: String, + #[serde(default = "default_android_insert_strategy")] + android_insert_strategy: AndroidInsertStrategy, + #[serde(default = "default_android_overlay_trigger")] + android_overlay_trigger: AndroidOverlayTrigger, + #[serde(default = "default_android_overlay_activation_mode")] + android_overlay_activation_mode: AndroidOverlayActivationMode, + #[serde(default = "default_android_overlay_left_swipe_action")] + android_overlay_left_swipe_action: AndroidOverlayLeftSwipeAction, + #[serde(default = "default_android_overlay_cancel_swipe_direction")] + android_overlay_cancel_swipe_direction: AndroidOverlayCancelSwipeDirection, + #[serde(default = "default_android_overlay_size_dp")] + android_overlay_size_dp: u32, } impl Default for UserPreferencesWire { @@ -970,6 +1015,10 @@ impl Default for UserPreferencesWire { coding_agent_voice_hotkey: prefs.coding_agent_voice_hotkey, coding_agent_panel_hotkey: prefs.coding_agent_panel_hotkey, coding_agent_quick_hotkey: prefs.coding_agent_quick_hotkey, + remote_input_enabled: prefs.remote_input_enabled, + remote_input_port: prefs.remote_input_port, + remote_input_pin: prefs.remote_input_pin, + remote_input_default_mode: prefs.remote_input_default_mode, local_asr_active_model: prefs.local_asr_active_model, local_asr_mirror: prefs.local_asr_mirror, local_asr_keep_loaded_secs: prefs.local_asr_keep_loaded_secs, @@ -994,10 +1043,12 @@ impl Default for UserPreferencesWire { audio_recording_max_entries: prefs.audio_recording_max_entries, marketplace_base_url: prefs.marketplace_base_url, marketplace_dev_login: prefs.marketplace_dev_login, - remote_input_enabled: prefs.remote_input_enabled, - remote_input_port: prefs.remote_input_port, - remote_input_pin: prefs.remote_input_pin, - remote_input_default_mode: prefs.remote_input_default_mode, + android_insert_strategy: prefs.android_insert_strategy, + android_overlay_trigger: prefs.android_overlay_trigger, + android_overlay_activation_mode: prefs.android_overlay_activation_mode, + android_overlay_left_swipe_action: prefs.android_overlay_left_swipe_action, + android_overlay_cancel_swipe_direction: prefs.android_overlay_cancel_swipe_direction, + android_overlay_size_dp: prefs.android_overlay_size_dp, } } } @@ -1058,6 +1109,10 @@ impl<'de> Deserialize<'de> for UserPreferences { coding_agent_voice_hotkey: wire.coding_agent_voice_hotkey, coding_agent_panel_hotkey: wire.coding_agent_panel_hotkey, coding_agent_quick_hotkey: wire.coding_agent_quick_hotkey, + remote_input_enabled: wire.remote_input_enabled, + remote_input_port: wire.remote_input_port, + remote_input_pin: wire.remote_input_pin, + remote_input_default_mode: wire.remote_input_default_mode, custom_combo_hotkey: wire.custom_combo_hotkey, translation_hotkey: wire .translation_hotkey @@ -1094,10 +1149,16 @@ impl<'de> Deserialize<'de> for UserPreferences { audio_recording_max_entries: wire.audio_recording_max_entries, marketplace_base_url: wire.marketplace_base_url, marketplace_dev_login: wire.marketplace_dev_login, - remote_input_enabled: wire.remote_input_enabled, - remote_input_port: wire.remote_input_port, - remote_input_pin: wire.remote_input_pin, - remote_input_default_mode: wire.remote_input_default_mode, + android_insert_strategy: normalize_android_insert_strategy( + wire.android_insert_strategy, + ), + android_overlay_trigger: wire.android_overlay_trigger.normalized(), + android_overlay_activation_mode: wire.android_overlay_activation_mode, + android_overlay_left_swipe_action: wire.android_overlay_left_swipe_action, + android_overlay_cancel_swipe_direction: wire.android_overlay_cancel_swipe_direction, + android_overlay_size_dp: normalize_android_overlay_size_dp( + wire.android_overlay_size_dp, + ), }) } } @@ -1232,15 +1293,7 @@ const OUTPUT_BLOCK: &str = "# 输出\n\ /// 自带 # 角色 + {{HOTWORDS}} + 八节主体(结构化判断、双层格式、首行收尾、ASR 纠错、 /// 原样保留、禁止事项、输出),因此 Structured 模式跳过标准 ROLE_BLOCK / COMMON_RULES / /// OUTPUT_BLOCK wrapper,避免与 v2 内的同名段落重复。 -const STRUCTURED_BUILTIN_PROMPT: &str = r#"# ⚡ 第一指令(高于一切,先执行再看细则) - -先数原文里有几件「可区分的事项」: -- **≥2 件 → 必须输出编号清单**(行首 1. 2. 3.),**禁止**把多件事揉成一整段。≥3 件还要按主题归类、子项另起一行用 (a) (b)。 -- 恰好 1 件 → 才输出连贯段落。 - -判断依据是「事项数」,**不是**原文有没有标点 / 换行 / 已经编号。只要有 2 件以上事项却揉进一段话 = 直接失败。最终形态照本文末尾「# 示例」里的样子输出。 - -# 角色 +const STRUCTURED_BUILTIN_PROMPT: &str = r#"# 角色 你是「清晰结构」整理器。用户输入来自语音识别(ASR),常带错别字、同音字、英文术语音译、断句缺失、语序混乱、口语化表达等问题。 @@ -1802,6 +1855,10 @@ impl Default for UserPreferences { coding_agent_voice_hotkey: default_coding_agent_voice_hotkey(), coding_agent_panel_hotkey: default_coding_agent_panel_hotkey(), coding_agent_quick_hotkey: None, + remote_input_enabled: false, + remote_input_port: default_remote_input_port(), + remote_input_pin: String::new(), + remote_input_default_mode: default_remote_input_mode(), local_asr_active_model: default_local_asr_model(), local_asr_mirror: default_local_asr_mirror(), local_asr_keep_loaded_secs: default_local_asr_keep_loaded_secs(), @@ -1826,10 +1883,13 @@ impl Default for UserPreferences { audio_recording_max_entries: None, marketplace_base_url: String::new(), marketplace_dev_login: String::new(), - remote_input_enabled: false, - remote_input_port: default_remote_input_port(), - remote_input_pin: String::new(), - remote_input_default_mode: default_remote_input_mode(), + android_insert_strategy: default_android_insert_strategy(), + android_overlay_trigger: default_android_overlay_trigger(), + android_overlay_activation_mode: default_android_overlay_activation_mode(), + android_overlay_left_swipe_action: default_android_overlay_left_swipe_action(), + android_overlay_cancel_swipe_direction: default_android_overlay_cancel_swipe_direction( + ), + android_overlay_size_dp: default_android_overlay_size_dp(), } } } @@ -2038,6 +2098,8 @@ pub enum HotkeyAdapterKind { MacEventTap, WindowsLowLevel, Fcitx5, + /// Mobile platforms do not expose desktop global hotkey adapters. + Unavailable, } impl HotkeyAdapterKind { @@ -2046,6 +2108,7 @@ impl HotkeyAdapterKind { HotkeyAdapterKind::MacEventTap => "macOS Event Tap", HotkeyAdapterKind::WindowsLowLevel => "Windows 低层键盘 hook", HotkeyAdapterKind::Fcitx5 => "fcitx5 输入法插件", + HotkeyAdapterKind::Unavailable => "不可用", } } } @@ -2201,6 +2264,21 @@ pub struct HotkeyCapability { impl HotkeyCapability { pub fn current() -> Self { + #[cfg(mobile)] + { + return Self { + adapter: HotkeyAdapterKind::Unavailable, + available_triggers: Vec::new(), + requires_accessibility_permission: false, + supports_modifier_only_trigger: false, + supports_side_specific_modifiers: false, + explicit_fallback_available: false, + status_hint: Some( + "移动端不支持全局热键;请使用应用内录音按钮或悬浮窗(需授权)。".into(), + ), + }; + } + #[cfg(target_os = "macos")] { Self { @@ -2245,7 +2323,7 @@ impl HotkeyCapability { }; } - #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))] + #[cfg(all(not(target_os = "macos"), not(target_os = "windows"), not(mobile)))] { Self { adapter: HotkeyAdapterKind::Fcitx5, @@ -2307,6 +2385,68 @@ pub struct WindowsImeStatus { pub dll_path: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct PlatformCapabilities { + pub platform: String, + pub supports_ime_input: bool, + pub supports_overlay: bool, + pub supports_desktop_hotkey: bool, + pub supports_tray: bool, + pub supports_local_asr: bool, + pub supports_in_app_dictation: bool, + pub supports_auto_update: bool, +} + +impl PlatformCapabilities { + pub fn current() -> Self { + #[cfg(target_os = "android")] + { + Self { + platform: "android".to_string(), + supports_ime_input: false, + supports_overlay: true, + supports_desktop_hotkey: false, + supports_tray: false, + supports_local_asr: false, + supports_in_app_dictation: true, + supports_auto_update: false, + } + } + + #[cfg(all( + any(target_os = "android", target_os = "ios"), + not(target_os = "android") + ))] + { + Self { + platform: "mobile".to_string(), + supports_ime_input: false, + supports_overlay: false, + supports_desktop_hotkey: false, + supports_tray: false, + supports_local_asr: false, + supports_in_app_dictation: false, + supports_auto_update: false, + } + } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + Self { + platform: "desktop".to_string(), + supports_ime_input: cfg!(target_os = "windows"), + supports_overlay: true, + supports_desktop_hotkey: true, + supports_tray: true, + supports_local_asr: cfg!(any(target_os = "macos", target_os = "windows")), + supports_in_app_dictation: false, + supports_auto_update: true, + } + } + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum HotkeyStatusState { diff --git a/openless-all/app/src-tauri/tauri.android.conf.json b/openless-all/app/src-tauri/tauri.android.conf.json new file mode 100644 index 00000000..05ae8f0a --- /dev/null +++ b/openless-all/app/src-tauri/tauri.android.conf.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "identifier": "com.openless.app", + "app": { + "windows": [ + { + "label": "main", + "title": "OpenLess", + "width": 1240, + "height": 800, + "minWidth": 360, + "minHeight": 640, + "resizable": true, + "decorations": true, + "visible": true + } + ] + } +} diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index 8411fed7..6545e9a3 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -128,6 +128,9 @@ "infoPlist": "Info.plist", "entitlements": "Entitlements.plist" }, + "android": { + "minSdkVersion": 26 + }, "windows": { "nsis": { "installMode": "perMachine", diff --git a/openless-all/app/src/App.tsx b/openless-all/app/src/App.tsx index bb40bc2b..79c60f76 100644 --- a/openless-all/app/src/App.tsx +++ b/openless-all/app/src/App.tsx @@ -9,9 +9,12 @@ import { checkMicrophonePermission, getHotkeyStatus, getSettings, + getPlatformCapabilities, handleWindowHotkeyEvent, isTauri, + qaWindowDismiss, } from './lib/ipc'; +import type { PlatformCapabilities } from './lib/types'; import { isWindowHotkeyKeyboardCandidate, windowMouseHotkeyCode, @@ -30,6 +33,7 @@ interface AppProps { } type Gate = 'onboarding' | 'ready'; +const ANDROID_SETUP_WIZARD_COMPLETE_KEY = 'openless.androidSetupWizardComplete'; export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, forcedOs }: AppProps) { if (isCapsule) { @@ -48,6 +52,65 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force const os = forcedOs ?? detectOS(); // Windows 启动不应被权限探测阻塞首屏。 const [gate, setGate] = useState('ready'); + const [platformCaps, setPlatformCaps] = useState(null); + const [mobileQaOpen, setMobileQaOpen] = useState(false); + const completeOnboarding = () => { + if (platformCaps?.platform === 'android') { + localStorage.setItem(ANDROID_SETUP_WIZARD_COMPLETE_KEY, '1'); + } + setGate('ready'); + }; + useEffect(() => { + if (!isTauri) return; + void getPlatformCapabilities().then(setPlatformCaps); + }, []); + + useEffect(() => { + if (!isTauri || platformCaps?.platform !== 'android') return; + let unlistenState: (() => void) | undefined; + let unlistenDismiss: (() => void) | undefined; + let cancelled = false; + (async () => { + try { + const { listen } = await import('@tauri-apps/api/event'); + const stateHandle = await listen('qa:state', () => { + console.info('[qa] android qa:state received; opening embedded panel'); + setMobileQaOpen(true); + }); + const dismissHandle = await listen('qa:dismiss', () => { + console.info('[qa] android qa:dismiss received; closing embedded panel'); + setMobileQaOpen(false); + }); + if (cancelled) { + stateHandle(); + dismissHandle(); + } else { + unlistenState = stateHandle; + unlistenDismiss = dismissHandle; + } + } catch (error) { + console.warn('[qa] mobile route listener setup failed', error); + } + })(); + return () => { + cancelled = true; + unlistenState?.(); + unlistenDismiss?.(); + }; + }, [platformCaps?.platform]); + + useEffect(() => { + if (!mobileQaOpen || platformCaps?.platform !== 'android') return; + window.history.pushState({ openlessQa: true }, '', window.location.href); + const onPopState = () => { + setMobileQaOpen(false); + void qaWindowDismiss().catch(error => console.warn('[qa] mobile back dismiss failed', error)); + }; + window.addEventListener('popstate', onPopState); + return () => { + window.removeEventListener('popstate', onPopState); + }; + }, [mobileQaOpen, platformCaps?.platform]); useEffect(() => { if (!isTauri) return; @@ -94,13 +157,30 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force if (!isTauri) return; let cancelled = false; - if (os === 'win') { - // 超时保护:50 次 × 200ms = 10s。hotkey hook 永远 starting(被反作弊 / EDR - // / UAC 拦)时不让 UI 死锁灰屏,过 10s 强 setGate('ready') 让用户进 - // Permissions 页看 hotkey_status.lastError 处理。详见 issue #163。 - const POLL_INTERVAL_MS = 200; - const POLL_MAX_ATTEMPTS = 50; - const pollHotkeyStatus = async () => { + void (async () => { + const caps = await getPlatformCapabilities(); + if (cancelled) return; + + if (caps.platform === 'android') { + if (localStorage.getItem(ANDROID_SETUP_WIZARD_COMPLETE_KEY) !== '1') { + setGate('onboarding'); + return; + } + const m = await checkMicrophonePermission(); + if (cancelled) return; + // notDetermined is non-blocking on Android — show grant flow in-app instead + // of trapping users on onboarding while JNI/runtime permission is pending. + const blocked = m === 'denied' || m === 'restricted'; + setGate(blocked ? 'onboarding' : 'ready'); + return; + } + + if (os === 'win') { + // 超时保护:50 次 × 200ms = 10s。hotkey hook 永远 starting(被反作弊 / EDR + // / UAC 拦)时不让 UI 死锁灰屏,过 10s 强 setGate('ready') 让用户进 + // Permissions 页看 hotkey_status.lastError 处理。详见 issue #163。 + const POLL_INTERVAL_MS = 200; + const POLL_MAX_ATTEMPTS = 50; let attempts = 0; while (!cancelled && attempts < POLL_MAX_ATTEMPTS) { attempts += 1; @@ -118,19 +198,9 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force ); setGate('ready'); } - }; - void pollHotkeyStatus().catch(error => { - console.warn('[startup] hotkey status polling failed', error); - if (!cancelled) { - setGate('ready'); - } - }); - return () => { - cancelled = true; - }; - } + return; + } - (async () => { const [a, m] = await Promise.all([ checkAccessibilityPermission(), checkMicrophonePermission(), @@ -139,7 +209,13 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force const aOk = a === 'granted' || a === 'notApplicable'; const mOk = m === 'granted' || m === 'notApplicable'; setGate(aOk && mOk ? 'ready' : 'onboarding'); - })(); + })().catch(error => { + console.warn('[startup] permission gate failed', error); + if (!cancelled) { + setGate('ready'); + } + }); + return () => { cancelled = true; }; @@ -180,8 +256,25 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force return ( - {gate === 'onboarding' ? setGate('ready')} /> : } - {gate === 'ready' && } + {platformCaps?.platform === 'android' && ( +

    + { + setMobileQaOpen(false); + if (window.history.state?.openlessQa === true) { + window.history.back(); + } + }} + /> +
    + )} + {!mobileQaOpen && (gate === 'onboarding' ? ( + + ) : ( + + ))} + {gate === 'ready' && platformCaps?.supportsAutoUpdate === true && } ); } diff --git a/openless-all/app/src/components/AudioCue.tsx b/openless-all/app/src/components/AudioCue.tsx index 5ad4f2b2..e3f5f4ad 100644 --- a/openless-all/app/src/components/AudioCue.tsx +++ b/openless-all/app/src/components/AudioCue.tsx @@ -1,9 +1,9 @@ // 录音提示音:监听 capsule:state 事件,在"开始录音"边沿播放合成提示音。 // 独立组件,不依赖胶囊窗口显示——Linux 上胶囊隐藏也能正常工作。 -// 全平台通用,在 FloatingShellBody 中渲染。 +// Android Web Audio 输出会触发部分设备的录音输入路由切换,移动端禁用。 import { useEffect, useRef } from 'react'; -import { isTauri } from '../lib/ipc'; +import { isAndroid, isTauri } from '../lib/ipc'; import { playRecordStartCue, primeAudioCue, stopAudioCue } from '../lib/audioCue'; import type { CapsuleState, UserPreferences } from '../lib/types'; @@ -18,10 +18,11 @@ interface CapsulePayload { export function AudioCueListener() { const audioCueEnabledRef = useRef(true); const prevStateRef = useRef('idle' as CapsuleState); + const audioCueRuntimeEnabled = !isAndroid(); // 读取设置(默认开启) useEffect(() => { - if (!isTauri) return; + if (!isTauri || !audioCueRuntimeEnabled) return; let cancelled = false; (async () => { try { @@ -33,11 +34,11 @@ export function AudioCueListener() { } })(); return () => { cancelled = true; }; - }, []); + }, [audioCueRuntimeEnabled]); // 监听设置变更 useEffect(() => { - if (!isTauri) return; + if (!isTauri || !audioCueRuntimeEnabled) return; let unlisten: (() => void) | undefined; let cancelled = false; (async () => { @@ -48,17 +49,17 @@ export function AudioCueListener() { }).then(fn => { if (!cancelled) unlisten = fn; }).catch(() => {}); })(); return () => { cancelled = true; unlisten?.(); }; - }, []); + }, [audioCueRuntimeEnabled]); // 预热 AudioContext useEffect(() => { - if (!isTauri) return; + if (!isTauri || !audioCueRuntimeEnabled) return; primeAudioCue(); - }, []); + }, [audioCueRuntimeEnabled]); // 监听 capsule 状态边沿 useEffect(() => { - if (!isTauri) return; + if (!isTauri || !audioCueRuntimeEnabled) return; let unlisten: (() => void) | undefined; let cancelled = false; (async () => { @@ -75,7 +76,7 @@ export function AudioCueListener() { }).then(fn => { if (!cancelled) unlisten = fn; }).catch(() => {}); })(); return () => { cancelled = true; unlisten?.(); }; - }, []); + }, [audioCueRuntimeEnabled]); return null; } diff --git a/openless-all/app/src/components/AutoUpdate.tsx b/openless-all/app/src/components/AutoUpdate.tsx index c6a5f096..0d0cfa0e 100644 --- a/openless-all/app/src/components/AutoUpdate.tsx +++ b/openless-all/app/src/components/AutoUpdate.tsx @@ -2,30 +2,21 @@ // 状态机 + 对话框 UI。两边各自调用 useAutoUpdate(),dialog 渲染条件相同。 // // 渠道感知:check 不再走 plugin-updater 的 JS check()(它只看 tauri.conf 配的 -// Stable manifest URL),改为 invoke('app_check_update_with_channel')。 +// Stable manifest URL),改为 appCheckUpdateWithChannel()(ipc 层按 +// supportsAutoUpdate 在 Android 上 no-op)。 // Rust 那边按 prefs.update_channel 决定 manifest URL;返回的 metadata 直接 // `new Update(metadata)` 复用 plugin 的 download / install / close 实现, // 我们不重复造下载和签名校验。 import { useEffect, useRef, useState } from 'react'; -import { invoke } from '@tauri-apps/api/core'; import type { DownloadEvent } from '@tauri-apps/plugin-updater'; import { Update } from '@tauri-apps/plugin-updater'; import { useTranslation } from 'react-i18next'; -import { isTauri, restartApp, type UpdateChannel } from '../lib/ipc'; +import { appCheckUpdateWithChannel, isTauri, restartApp, type AppUpdateMetadata, type UpdateChannel } from '../lib/ipc'; import { Btn } from '../pages/_atoms'; const UPDATE_CHECK_TIMEOUT_MS = 15_000; -interface AppUpdateMetadata { - rid: number; - currentVersion: string; - version: string; - date?: string | null; - body?: string | null; - rawJson: Record; -} - export type UpdateStatus = | 'idle' | 'checking' @@ -102,10 +93,10 @@ export function useAutoUpdate(): UseAutoUpdate { } // Rust 侧按 update_channel 拼 manifest URL:Stable → tauri.conf 默认; // Beta → fetch_latest_beta_release 拼出 -beta manifest URL 后再 check。 - const metadata = await invoke('app_check_update_with_channel', { - timeoutMs: UPDATE_CHECK_TIMEOUT_MS, - channel: channel ?? null, - }); + const metadata = await appCheckUpdateWithChannel( + UPDATE_CHECK_TIMEOUT_MS, + channel ?? null, + ); if (!metadata) { setStatus('none'); return; diff --git a/openless-all/app/src/components/AutoUpdateGate.tsx b/openless-all/app/src/components/AutoUpdateGate.tsx index a1953dac..7ff1b6b7 100644 --- a/openless-all/app/src/components/AutoUpdateGate.tsx +++ b/openless-all/app/src/components/AutoUpdateGate.tsx @@ -2,8 +2,10 @@ // 受 prefs.autoUpdateCheck 开关控制;关闭时只走 Settings → 关于 的手动按钮。 // 找到新版本时直接挂 UpdateDialog;不弹自定义通知,沿用既有 dialog 视觉。 -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { isDialogStatus, UpdateDialog, useAutoUpdate } from './AutoUpdate'; +import { getPlatformCapabilities } from '../lib/ipc'; +import type { PlatformCapabilities } from '../lib/types'; import { useHotkeySettings } from '../state/HotkeySettingsContext'; const AUTO_CHECK_INTERVAL_MS = 60 * 60 * 1000; @@ -12,7 +14,12 @@ const STARTUP_DELAY_MS = 4_000; export function AutoUpdateGate() { const { prefs } = useHotkeySettings(); const u = useAutoUpdate(); - const enabled = prefs?.autoUpdateCheck ?? true; + const [platformCaps, setPlatformCaps] = useState(null); + const enabled = (prefs?.autoUpdateCheck ?? true) && platformCaps?.supportsAutoUpdate === true; + + useEffect(() => { + void getPlatformCapabilities().then(setPlatformCaps); + }, []); // 用 ref 保持 tick 闭包始终读到最新的 useAutoUpdate 返回值。 // 之前直接捕获 `u` 会让 60min interval 触发时读旧 status 闭包——例如用户已经 @@ -43,6 +50,8 @@ export function AutoUpdateGate() { }; }, [enabled]); + if (platformCaps?.supportsAutoUpdate !== true) return null; + if (!isDialogStatus(u.status)) return null; return ( (); const [providerPromptOpen, setProviderPromptOpen] = useState(false); const [hotkeyModePromptOpen, setHotkeyModePromptOpen] = useState(false); + const mobileLayout = useMobileLayout(); // tab 切换的 cross-fade:旧页 blur+fade out(180ms),结束后挂载新页(走 ol-page-slide enter)。 // displayTab 是实际渲染的 tab,currentTab 是用户点中的目标 tab。 @@ -96,12 +98,17 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia [t], ); const Page = (NAV.find((n) => n.id === displayTab) ?? NAV[0]).cmp; + const activeNav = NAV.find(n => n.id === currentTab) ?? NAV[0]; // sidebar nav 滑动指示器:测量当前 active button 的 offsetTop / height, // 用一个 absolute pill 平滑滑过去,而不是每个按钮各自瞬切背景色。 const navItemRefs = useRef>([]); const [pillRect, setPillRect] = useState<{ top: number; height: number } | null>(null); useLayoutEffect(() => { + if (mobileLayout) { + setPillRect(null); + return; + } if (settingsOpen) { setPillRect(null); return; @@ -114,11 +121,13 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia const el = navItemRefs.current[idx]; if (!el) return; setPillRect({ top: el.offsetTop, height: el.offsetHeight }); - }, [currentTab, settingsOpen, NAV]); + }, [currentTab, settingsOpen, NAV, mobileLayout]); useEffect(() => { let cancelled = false; (async () => { + const caps = await getPlatformCapabilities(); + if (cancelled || caps.platform === 'android') return; const credentials = await getCredentials(); const promptDeferredValue = window.sessionStorage.getItem(PROVIDER_SETUP_PROMPT_DEFERRED_KEY); if (!cancelled && shouldShowProviderSetupPrompt(credentials, promptDeferredValue)) { @@ -131,11 +140,18 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia }, []); useEffect(() => { - const acknowledgedValue = window.localStorage.getItem(HOTKEY_MODE_MIGRATION_ACK_KEY); - const deferredValue = window.sessionStorage.getItem(HOTKEY_MODE_MIGRATION_DEFERRED_KEY); - if (shouldShowHotkeyModeMigrationPrompt(acknowledgedValue, deferredValue)) { - setHotkeyModePromptOpen(true); - } + let cancelled = false; + void getPlatformCapabilities().then((caps) => { + if (cancelled || caps.platform === 'android') return; + const acknowledgedValue = window.localStorage.getItem(HOTKEY_MODE_MIGRATION_ACK_KEY); + const deferredValue = window.sessionStorage.getItem(HOTKEY_MODE_MIGRATION_DEFERRED_KEY); + if (shouldShowHotkeyModeMigrationPrompt(acknowledgedValue, deferredValue)) { + setHotkeyModePromptOpen(true); + } + }); + return () => { + cancelled = true; + }; }, []); // 之前监听的 NAVIGATE_LOCAL_ASR_EVENT 已无意义——「模型设置」独立 tab 已下线, @@ -181,37 +197,68 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia }; return ( -
    +
    {/* Main shell — flush with the frosted backplate (no separate float). */}
    - {/* Sidebar — 透明地坐在外层磨砂底板上,让 LOGO/导航/快捷键/BETA/footer 共用同一片磨砂玻璃 */} - + )} - {/* Main content — Linux 禁用透明窗口后使用不透明面;其他平台保留玻璃层。 - 悬浮台到右边 / 下边的间距相等(都 8px),左侧贴 sidebar(0)。 */} -
    + {/* Main content — Linux 禁用透明窗口后使用不透明面;其他平台保留玻璃层。 */} +
    + + {mobileLayout && ( + + )}
    {/* Settings modal — rendered inside this window */} @@ -416,12 +446,205 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia color: var(--ol-ink-3); font-weight: 500; } + .ol-aura-sidebar { + padding: 14px 12px 14px; + background: var(--ol-sidebar-bg); + border-right: 1px solid var(--ol-sidebar-border); + } + .ol-aura-sidebar-brand { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 10px 16px; + margin-bottom: 6px; + border-radius: 0; + background: var(--ol-sidebar-brand-bg); + border: 1px solid var(--ol-sidebar-brand-border); + box-shadow: none; + } + .ol-aura-sidebar-brand-mark { + width: 26px; + height: 26px; + border-radius: 8px; + box-shadow: none; + box-sizing: border-box; + padding: 3px; + object-fit: contain; + } + .ol-aura-sidebar-brand-title { + font-size: 14px; + font-weight: 600; + font-family: var(--ol-font-display); + color: var(--ol-ink); + } + .ol-aura-sidebar-brand-kicker { + font-size: 10.5px; + color: var(--ol-ink-4); + font-family: var(--ol-font-mono); + letter-spacing: .08em; + } + .ol-aura-sidebar-pill { + background: var(--ol-sidebar-pill-bg); + border-radius: 12px; + border: 1px solid var(--ol-sidebar-pill-border); + box-shadow: none; + } + .ol-aura-sidebar-nav-btn { + padding: 8px 12px; + border-radius: 12px; + border: 0; + background: transparent; + font-family: inherit; + font-size: 13px; + cursor: default; + transition: color 0.16s var(--ol-motion-quick), background 0.16s var(--ol-motion-quick); + text-align: left; + position: relative; + z-index: 1; + } + .ol-aura-sidebar-footer { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px 10px 0; + margin-top: 10px; + border-top: 1px solid var(--ol-sidebar-footer-border); + } + .ol-aura-sidebar-version { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + padding: 10px 12px; + font-family: var(--ol-font-sans); + font-size: 11px; + color: var(--ol-ink-4); + background: var(--ol-sidebar-version-bg); + border: 1px solid var(--ol-sidebar-version-border); + border-radius: var(--ol-pill-radius); + box-shadow: none; + } + .ol-aura-beta-tag { + display: inline-block; + padding: 2px 8px; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--ol-blue); + background: rgba(37,99,235,0.10); + border-radius: 999px; + } + .ol-aura-sidebar-settings { + padding: 10px 12px; + border-radius: 12px; + border: 1px solid var(--ol-sidebar-settings-border); + background: var(--ol-sidebar-settings-bg); + box-shadow: none; + } + .ol-aura-sidebar-settings.ol-nav-btn-active { + background: var(--ol-sidebar-settings-active-bg); + box-shadow: none; + } + .ol-aura-console-main { + border-radius: ${mobileLayout ? '0' : 'var(--ol-panel-radius)'}; + } + .ol-aura-mobile-topbar { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: calc(10px + env(safe-area-inset-top, 0px)) 14px 10px; + border-bottom: 1px solid var(--ol-sidebar-border); + background: var(--ol-sidebar-bg); + } + .ol-aura-mobile-brand { + min-width: 0; + display: flex; + align-items: center; + gap: 10px; + } + .ol-aura-mobile-brand-mark { + width: 30px; + height: 30px; + border-radius: 8px; + flex-shrink: 0; + box-sizing: border-box; + padding: 3px; + object-fit: contain; + } + .ol-aura-mobile-brand-title { + font-size: 14px; + font-weight: 700; + color: var(--ol-ink); + line-height: 1.15; + } + .ol-aura-mobile-brand-section { + margin-top: 2px; + font-size: 11px; + color: var(--ol-ink-4); + font-family: var(--ol-font-mono); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .ol-aura-mobile-settings { + width: 36px; + height: 36px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 12px; + color: var(--ol-ink-3); + background: var(--ol-sidebar-settings-bg); + border: 1px solid var(--ol-sidebar-settings-border); + } + .ol-aura-mobile-settings-active { + color: var(--ol-ink); + background: var(--ol-sidebar-settings-active-bg); + } + .ol-aura-mobile-nav { + flex-shrink: 0; + display: grid; + grid-template-columns: repeat(${NAV.length}, minmax(0, 1fr)); + gap: 2px; + padding: 7px 8px calc(7px + env(safe-area-inset-bottom, 0px)); + border-top: 1px solid var(--ol-sidebar-border); + background: var(--ol-sidebar-bg); + } + .ol-aura-mobile-nav-btn { + min-width: 0; + height: 50px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + border-radius: 12px; + color: var(--ol-ink-4); + font-size: 10px; + font-weight: 600; + line-height: 1.1; + } + .ol-aura-mobile-nav-btn span { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .ol-aura-mobile-nav-btn-active { + color: var(--ol-ink); + background: var(--ol-sidebar-pill-bg); + border: 1px solid var(--ol-sidebar-pill-border); + } .ol-nav-btn.ol-nav-btn-active { color: var(--ol-ink); font-weight: 600; } .ol-nav-btn:not(.ol-nav-btn-active):hover { - background: rgba(0,0,0,0.04); + background: var(--ol-nav-hover-bg); color: var(--ol-ink); } @keyframes ol-page-slide { @@ -457,7 +680,7 @@ function ProviderSetupPrompt({ onLater, onOpenSettings }: { onLater: () => void; alignItems: 'center', justifyContent: 'center', padding: 28, - background: 'rgba(15,17,22,0.28)', + background: 'var(--ol-overlay-bg)', backdropFilter: 'blur(6px) saturate(140%)', WebkitBackdropFilter: 'blur(6px) saturate(140%)', animation: 'ol-prompt-fade 0.2s var(--ol-motion-soft)', @@ -522,7 +745,7 @@ function ProviderSetupPrompt({ onLater, onOpenSettings }: { onLater: () => void; borderRadius: 8, border: 0, background: 'var(--ol-ink)', - color: '#fff', + color: 'var(--ol-on-accent)', fontFamily: 'inherit', fontSize: 12.5, fontWeight: 500, @@ -550,7 +773,7 @@ function HotkeyModeMigrationPrompt({ onLater, onOpenSettings }: { onLater: () => alignItems: 'center', justifyContent: 'center', padding: 28, - background: 'rgba(15,17,22,0.28)', + background: 'var(--ol-overlay-bg)', backdropFilter: 'blur(6px) saturate(140%)', WebkitBackdropFilter: 'blur(6px) saturate(140%)', animation: 'ol-prompt-fade 0.2s var(--ol-motion-soft)', @@ -615,7 +838,7 @@ function HotkeyModeMigrationPrompt({ onLater, onOpenSettings }: { onLater: () => borderRadius: 8, border: 0, background: 'var(--ol-ink)', - color: '#fff', + color: 'var(--ol-on-accent)', fontFamily: 'inherit', fontSize: 12.5, fontWeight: 500, diff --git a/openless-all/app/src/components/Onboarding.tsx b/openless-all/app/src/components/Onboarding.tsx index 2d23ed21..bb518005 100644 --- a/openless-all/app/src/components/Onboarding.tsx +++ b/openless-all/app/src/components/Onboarding.tsx @@ -1,26 +1,280 @@ -// Onboarding.tsx — 首次运行权限引导。 -// -// 触发条件:App.tsx 启动检查 accessibility + microphone,任一未授权则渲染本组件而非主 Shell。 -// 与 Swift `Sources/OpenLessApp/Onboarding/` 同语义,但简化为单页三步。 +// Onboarding.tsx — first-run permission and service setup. -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; +import { AndroidPermissionsPanel } from '@android/components/AndroidPermissionsPanel'; +import { checkAndroidMicrophoneAccess, requestAndroidMicrophoneAccess } from '@android/lib/androidMicrophonePermission'; import { checkAccessibilityPermission, checkMicrophonePermission, + getPlatformCapabilities, openSystemSettings, requestAccessibilityPermission, requestMicrophonePermission, } from '../lib/ipc'; import { getHotkeyTriggerLabel } from '../lib/hotkey'; -import type { PermissionStatus } from '../lib/types'; +import type { PermissionStatus, PlatformCapabilities } from '../lib/types'; import { useHotkeySettings } from '../state/HotkeySettingsContext'; +import { ProvidersSection } from '../pages/settings/ProvidersSection'; interface OnboardingProps { onComplete: () => void; } +type AndroidStepId = + | 'microphone' + | 'accessibility' + | 'overlayPermission' + | 'overlayConfig' + | 'asr' + | 'llm'; + export function Onboarding({ onComplete }: OnboardingProps) { + const { t } = useTranslation(); + const [platformCaps, setPlatformCaps] = useState(null); + + useEffect(() => { + void getPlatformCapabilities().then(setPlatformCaps); + }, []); + + if (!platformCaps) { + return ; + } + + if (platformCaps.platform === 'android') { + return ; + } + + return ; +} + +function AndroidOnboarding({ onComplete }: OnboardingProps) { + const { t } = useTranslation(); + const [stepIndex, setStepIndex] = useState(0); + + const steps = useMemo>( + () => [ + { + id: 'microphone', + title: t('onboarding.androidSteps.microphoneTitle'), + desc: t('onboarding.androidSteps.microphoneDesc'), + }, + { + id: 'accessibility', + title: t('onboarding.androidSteps.accessibilityTitle'), + desc: t('onboarding.androidSteps.accessibilityDesc'), + }, + { + id: 'overlayPermission', + title: t('onboarding.androidSteps.overlayPermissionTitle'), + desc: t('onboarding.androidSteps.overlayPermissionDesc'), + }, + { + id: 'overlayConfig', + title: t('onboarding.androidSteps.overlayConfigTitle'), + desc: t('onboarding.androidSteps.overlayConfigDesc'), + }, + { + id: 'asr', + title: t('onboarding.androidSteps.asrTitle'), + desc: t('onboarding.androidSteps.asrDesc'), + }, + { + id: 'llm', + title: t('onboarding.androidSteps.llmTitle'), + desc: t('onboarding.androidSteps.llmDesc'), + }, + ], + [t], + ); + + const current = steps[stepIndex] ?? steps[0]; + const isFirst = stepIndex === 0; + const isLast = stepIndex === steps.length - 1; + + const goNext = () => { + if (isLast) { + onComplete(); + return; + } + setStepIndex((value) => Math.min(value + 1, steps.length - 1)); + }; + + return ( + +
    + + +
    + {steps.map((step, index) => ( +
    + ))} +
    + +
    +
    +
    + {t('onboarding.androidStepCounter', { current: stepIndex + 1, total: steps.length })} +
    +
    {current.title}
    +
    + {current.desc} +
    +
    + + +
    + +
    + + +
    + + +
    + + ); +} + +function AndroidStepContent({ step }: { step: AndroidStepId }) { + if (step === 'microphone') { + return ; + } + if (step === 'accessibility') { + return ; + } + if (step === 'overlayPermission') { + return ; + } + if (step === 'overlayConfig') { + return ; + } + if (step === 'asr') { + return ; + } + return ; +} + +function AndroidMicrophoneStep() { + const { t } = useTranslation(); + const [status, setStatus] = useState('notDetermined'); + const [busy, setBusy] = useState(false); + + const refresh = async () => { + setStatus(await checkAndroidMicrophoneAccess()); + }; + + useEffect(() => { + void refresh(); + const id = window.setInterval(refresh, 3000); + const onFocus = () => { void refresh(); }; + window.addEventListener('focus', onFocus); + return () => { + window.clearInterval(id); + window.removeEventListener('focus', onFocus); + }; + }, []); + + const request = async () => { + setBusy(true); + try { + if (status === 'denied' || status === 'restricted') { + await openSystemSettings('microphone'); + } else { + setStatus(await requestAndroidMicrophoneAccess()); + } + await refresh(); + } finally { + setBusy(false); + } + }; + + const granted = status === 'granted' || status === 'notApplicable'; + return ( + +
    +
    +
    {t('onboarding.micTitle')}
    +
    + {t('onboarding.micDesc')} +
    +
    + +
    + +
    + ); +} + +function DesktopOnboarding({ + onComplete, + platformCaps: _platformCaps, +}: OnboardingProps & { platformCaps: PlatformCapabilities }) { const { t } = useTranslation(); const [accessibility, setAccessibility] = useState('notDetermined'); const [microphone, setMicrophone] = useState('notDetermined'); @@ -28,6 +282,8 @@ export function Onboarding({ onComplete }: OnboardingProps) { const refreshTimeoutRef = useRef(null); const { capability } = useHotkeySettings(); + const requiresAccessibility = !!capability?.requiresAccessibilityPermission; + const refresh = async () => { const [a, m] = await Promise.all([ checkAccessibilityPermission(), @@ -35,7 +291,9 @@ export function Onboarding({ onComplete }: OnboardingProps) { ]); setAccessibility(a); setMicrophone(m); - if ((a === 'granted' || a === 'notApplicable') && (m === 'granted' || m === 'notApplicable')) { + const aOk = !requiresAccessibility || a === 'granted' || a === 'notApplicable'; + const mOk = m === 'granted' || m === 'notApplicable'; + if (aOk && mOk) { onComplete(); } }; @@ -43,7 +301,6 @@ export function Onboarding({ onComplete }: OnboardingProps) { useEffect(() => { refresh(); const id = window.setInterval(refresh, 1000); - // 用户从系统设置切回来时立刻刷新 const onFocus = () => refresh(); window.addEventListener('focus', onFocus); return () => { @@ -51,7 +308,7 @@ export function Onboarding({ onComplete }: OnboardingProps) { window.removeEventListener('focus', onFocus); if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current); }; - }, []); + }, [requiresAccessibility]); const onGrantAccessibility = async () => { setBusy(true); @@ -83,74 +340,45 @@ export function Onboarding({ onComplete }: OnboardingProps) { }; return ( -
    +
    -
    -
    - OL -
    -
    -
    {t('onboarding.welcome')}
    -
    - {t('onboarding.intro')} -
    -
    -
    + - + {requiresAccessibility && ( + + )} -
    +
    {t('onboarding.footerHint')}
    + + ); +} + +function OnboardingLoading({ label }: { label: string }) { + return ( +
    + {label}
    ); } +function OnboardingSurface({ children }: { children: ReactNode }) { + return ( +
    + {children} +
    + ); +} + +function BrandHeader({ title, desc, compact = false }: { title: string; desc: string; compact?: boolean }) { + return ( +
    + OpenLess +
    +
    {title}
    +
    + {desc} +
    +
    +
    + ); +} + +function AndroidStepCard({ children }: { children: ReactNode }) { + return ( +
    + {children} +
    + ); +} + +function StatusBadge({ granted, label }: { granted: boolean; label: string }) { + return ( + + {label} + + ); +} + interface StepProps { index: number; title: string; @@ -255,3 +571,54 @@ function PermissionStep({ index, title, desc, status, actionLabel, onAction, dis
    ); } + +const primaryButtonStyle = { + flex: 1, + minHeight: 42, + padding: '10px 14px', + fontSize: 13, + fontWeight: 600, + fontFamily: 'inherit', + border: 0, + borderRadius: 10, + background: 'var(--ol-ink)', + color: '#fff', + cursor: 'default', +} as const; + +const secondaryButtonStyle = { + flex: 1, + minHeight: 42, + padding: '10px 14px', + fontSize: 13, + fontWeight: 600, + fontFamily: 'inherit', + border: '0.5px solid var(--ol-line-strong)', + borderRadius: 10, + background: 'var(--ol-surface)', + color: 'var(--ol-ink-2)', + cursor: 'default', +} as const; + +const plainButtonStyle = { + width: '100%', + padding: '10px 14px', + fontSize: 12.5, + fontWeight: 500, + fontFamily: 'inherit', + border: 0, + borderRadius: 8, + background: 'transparent', + color: 'var(--ol-ink-4)', + cursor: 'default', +} as const; + +const footerHintStyle = { + marginTop: 18, + padding: '12px 14px', + borderRadius: 8, + background: 'var(--ol-surface-2)', + fontSize: 11.5, + color: 'var(--ol-ink-3)', + lineHeight: 1.6, +} as const; diff --git a/openless-all/app/src/components/SettingsModal.tsx b/openless-all/app/src/components/SettingsModal.tsx index 7cd7917e..f55a682f 100644 --- a/openless-all/app/src/components/SettingsModal.tsx +++ b/openless-all/app/src/components/SettingsModal.tsx @@ -2,21 +2,20 @@ // // 重构(2026-05):原本是「外层弹窗侧栏 + 设置页内层侧栏」双层嵌套,用户点 // 「设置」还要再面对第二个侧栏。现在拍平成单层 —— 通用 / 服务 / 隐私 / 高级 / -// 个性化 / 关于 六个 tab + 帮助外链组。每个 tab 的内容见 pages/settings/。 +// 个性化 / 关于 六个 tab。每个 tab 的内容见 pages/settings/。 // // 设计原则:每个可见控件都必须可用。没有后端支撑的占位(账号 / 主题切换 等) // 不在此弹窗出现。 import { useLayoutEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Icon } from './Icon'; import { SavedToast } from './SavedToast'; import { useSavedToastListener } from '../lib/savedEvent'; -import { openExternal } from '../lib/ipc'; import type { OS } from './WindowChrome'; import { GeneralTab, ServicesTab, PrivacyTab, AdvancedTab } from '../pages/settings/tabs'; import { AboutSection } from '../pages/settings/AboutSection'; +import { useMobileLayout } from '../lib/useMobileLayout'; // 稳定 tab ID(与 i18n key `modal.sections.*` 一致)。 export type SettingsSectionId = @@ -35,14 +34,8 @@ interface SettingsModalProps { interface ModalNavItem { id: string; icon: string; - external?: boolean; - href?: string; } -const HELP_URL = 'https://github.com/appergb/openless#readme'; -const RELEASE_NOTES_URL = 'https://github.com/appergb/openless/releases'; - -// 第一组:可选中的 tab;第二组:外部链接(永远不 active)。 const TAB_ITEMS: ModalNavItem[] = [ { id: 'general', icon: 'settings' }, { id: 'services', icon: 'cloud' }, @@ -50,71 +43,82 @@ const TAB_ITEMS: ModalNavItem[] = [ { id: 'advanced', icon: 'bolt' }, { id: 'about', icon: 'info' }, ]; -const LINK_ITEMS: ModalNavItem[] = [ - { id: 'helpCenter', icon: 'help', external: true, href: HELP_URL }, - { id: 'releaseNotes', icon: 'doc', external: true, href: RELEASE_NOTES_URL }, -]; export function SettingsModal({ os: _os, onClose, initialSettingsSection }: SettingsModalProps) { const { t } = useTranslation(); const [section, setSection] = useState(initialSettingsSection ?? 'general'); const savedToast = useSavedToastListener(); + const mobile = useMobileLayout(); // 与 sidebar nav 一致的滑动指示器:仅 tab 组有 pill;外链组永远不画 pill。 const tabRefs = useRef>([]); const [pillRect, setPillRect] = useState<{ top: number; height: number } | null>(null); useLayoutEffect(() => { + if (mobile) { + setPillRect(null); + return; + } const idx = TAB_ITEMS.findIndex(it => it.id === section); const el = tabRefs.current[idx]; if (!el) return; setPillRect({ top: el.offsetTop, height: el.offsetHeight }); - }, [section]); + }, [section, mobile]); - // issue #580:用 Portal 渲染到 document.body,脱离页面 overflow:hidden 容器的 - // stacking context。否则 WebKitGTK(Debian/KDE Wayland)下页面自绘滚动条 - // (.ol-thinscroll) 不创建独立合成层,z-index 无法隔离,滚动时会盖在弹窗之上。 - // 配合 position:fixed 覆盖整窗。 - return createPortal( + return (
    e.stopPropagation()} style={{ - width: '100%', maxWidth: 880, height: '100%', maxHeight: 600, - background: 'var(--ol-surface)', - borderRadius: 14, - border: '0.5px solid rgba(0,0,0,.08)', - boxShadow: '0 30px 80px -20px rgba(15,17,22,.35), 0 0 0 0.5px rgba(0,0,0,.06)', - display: 'flex', overflow: 'hidden', + width: '100%', + maxWidth: 920, + height: '100%', + maxHeight: mobile ? 'none' : 620, + display: 'flex', + flexDirection: mobile ? 'column' : 'row', + overflow: 'hidden', animation: 'ol-modal-card-in 0.24s var(--ol-motion-spring)', position: 'relative', }}> {/* ─── 单层侧栏 ────────────────────────────────────────────── */} {/* ─── 内容区 ────────────────────────────────────────────── 父容器 overflow:hidden + 列向 flex;关闭按钮、section 标题固定在头部, 只有最里层的 scroll wrapper 真正滚动。 */} -
    +
    {/* "已保存" toast:right:54 避开 28×28 关闭按钮 + 12px gap。 */} -

    +

    {t(`modal.sections.${section}`)}

    + style={{ + flex: 1, + minHeight: 0, + overflow: 'auto', + padding: mobile + ? '8px 14px calc(18px + env(safe-area-inset-bottom, 0px))' + : '10px 28px 28px', + }}> {/* key=section 让切 tab 时整块重挂载,ol-tab-fade 轻微淡入。 */}
    -
    , - document.body, + +
    ); } const navBtnStyle = { display: 'flex', alignItems: 'center', gap: 10, - padding: '7px 10px', - borderRadius: 8, border: 0, - background: 'transparent', - fontFamily: 'inherit', fontSize: 13, - cursor: 'default', textAlign: 'left' as const, - position: 'relative' as const, - zIndex: 1, - transition: 'color 0.16s var(--ol-motion-quick), background 0.16s var(--ol-motion-quick)', }; diff --git a/openless-all/app/src/components/WindowChrome.tsx b/openless-all/app/src/components/WindowChrome.tsx index 2e74b212..4e75e03a 100644 --- a/openless-all/app/src/components/WindowChrome.tsx +++ b/openless-all/app/src/components/WindowChrome.tsx @@ -1,6 +1,6 @@ import { type CSSProperties, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -export type OS = 'mac' | 'win' | 'linux'; +export type OS = 'mac' | 'win' | 'linux' | 'android'; export function detectOS(): OS { if (typeof navigator === 'undefined') return 'mac'; @@ -9,6 +9,7 @@ export function detectOS(): OS { ).userAgentData?.platform ?? ''; const hints = `${navigator.userAgent || ''} ${navigator.platform || ''} ${uaDataPlatform}`; if (/Mac|iPhone|iPad|iPod/.test(hints)) return 'mac'; + if (/Android/i.test(hints)) return 'android'; if (/Windows|Win32|Win64/.test(hints)) return 'win'; if (/Linux|X11|Wayland/.test(hints)) return 'linux'; return 'mac'; @@ -33,8 +34,8 @@ export function WindowChrome({ }: WindowChromeProps) { // Windows: decorations:true 时外层不画圆角/边框/阴影/标题栏,避免与原生窗口重叠。 // Linux: decorations:false 时外层画 14px 圆角 + 自定义标题栏。 - const shellRadius = os === 'mac' ? 0 : os === 'win' ? 0 : 14; - const consoleRadius = os === 'mac' ? 20 : os === 'win' ? WIN_CONSOLE_RADIUS : 14; + const shellRadius = os === 'mac' ? 0 : os === 'win' || os === 'android' ? 0 : 14; + const consoleRadius = os === 'mac' ? 20 : os === 'win' ? WIN_CONSOLE_RADIUS : os === 'android' ? 0 : 14; const titlebarHeight = os === 'mac' ? MAC_TITLEBAR_HEIGHT : os === 'linux' ? LINUX_TITLEBAR_HEIGHT : 0; // macOS / Windows 共用半透明玻璃 background + backdropFilter。 diff --git a/openless-all/app/src/components/ui/Row.tsx b/openless-all/app/src/components/ui/Row.tsx index a2723017..867693ac 100644 --- a/openless-all/app/src/components/ui/Row.tsx +++ b/openless-all/app/src/components/ui/Row.tsx @@ -1,6 +1,7 @@ // Row — two-column row used in the Settings modal sub-sections. import type { ReactNode } from 'react'; +import { useMobileLayout } from '../../lib/useMobileLayout'; interface RowProps { label: string; @@ -9,13 +10,14 @@ interface RowProps { } export function Row({ label, desc, children }: RowProps) { + const mobile = useMobileLayout(); return ( -
    -
    +
    +
    {label}
    {desc &&
    {desc}
    }
    -
    {children}
    +
    {children}
    ); } diff --git a/openless-all/app/src/components/ui/SelectLite.tsx b/openless-all/app/src/components/ui/SelectLite.tsx index 4822e50a..ec3ebebe 100644 --- a/openless-all/app/src/components/ui/SelectLite.tsx +++ b/openless-all/app/src/components/ui/SelectLite.tsx @@ -29,6 +29,7 @@ import { } from 'react'; import { createPortal } from 'react-dom'; import { Icon } from '../Icon'; +import { useMobileLayout } from '../../lib/useMobileLayout'; export interface SelectOption { value: string; @@ -81,6 +82,7 @@ export function SelectLite({ ariaLabel, onOpenChange, }: SelectLiteProps) { + const mobile = useMobileLayout(); const [open, setOpen] = useState(false); // leaving 让 popover 在卸载前播完 exit keyframe(用户报"没有收缩动画"——之前直接 unmount) const [leaving, setLeaving] = useState(false); @@ -264,6 +266,14 @@ export function SelectLite({ const triggerStyle: CSSProperties = { ...DEFAULT_TRIGGER_STYLE, ...style, + boxSizing: 'border-box', + ...(mobile + ? { + width: style?.width ?? '100%', + minWidth: 0, + maxWidth: '100%', + } + : null), opacity: disabled ? 0.5 : 1, cursor: disabled ? 'not-allowed' : 'default', }; diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 2a8f3cce..fbbc2fc5 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -49,6 +49,11 @@ export const en: typeof zhCN = { emptyTitle: 'Press {{recordHotkey}} to ask', emptyDesc: 'Select text in any app, press {{recordHotkey}} once to start recording, press it again to submit. Answers appear here. You can ask follow-up questions in the same panel.', recordingHint: 'Recording… press {{recordHotkey}} again to submit', + mobileRecordLabel: 'record button', + mobileRecordStart: 'Start recording', + mobileRecordStop: 'Stop and submit', + composerPlaceholder: 'Type a question. Enter to send, Shift+Enter for a new line', + composerSend: 'Send', statusIdle: 'Press {{recordHotkey}} to ask', statusRecording: 'Recording', statusThinking: 'Thinking', @@ -223,6 +228,28 @@ export const en: typeof zhCN = { actionRequestMic: 'Request access', accessibilityHint: 'After granting, you must **fully quit OpenLess** and reopen it (a macOS TCC requirement).', footerHint: 'This onboarding closes automatically once both permissions are granted. If it persists, quit OpenLess from the menu bar and relaunch.', + androidContinue: 'Continue to app', + androidFooterHint: 'Microphone access is required for dictation. Tap Request access above, or continue and grant it later from Overview.', + androidTitle: 'Set up OpenLess', + androidIntro: 'Complete mobile permissions and services step by step.', + androidStepCounter: 'Step {{current}} of {{total}}', + androidBack: 'Back', + androidNext: 'Next', + androidFinish: 'Finish and enter', + androidSteps: { + microphoneTitle: 'Microphone permission', + microphoneDesc: 'Show the Android system permission sheet and allow OpenLess to record voice.', + accessibilityTitle: 'Accessibility service', + accessibilityDesc: 'Paste recognition results back into the active input field and help detect the input context.', + overlayPermissionTitle: 'Floating window permission', + overlayPermissionDesc: 'Allow OpenLess to show the recording control over other apps.', + overlayConfigTitle: 'Floating window settings', + overlayConfigDesc: 'Configure visibility, activation, swipe actions, and button size.', + asrTitle: 'ASR cloud service', + asrDesc: 'Configure the speech-to-text provider, key, endpoint, and model.', + llmTitle: 'LLM service', + llmDesc: 'Configure the language model used for polishing, translation, and Q&A.', + }, }, overview: { kicker: 'DASHBOARD', @@ -258,6 +285,20 @@ export const en: typeof zhCN = { recentLoadFailed: 'Could not load recent transcripts. Please retry.', historyRetry: 'Retry', weekDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + inAppDictation: { + title: 'In-app dictation', + start: 'Start recording', + stop: 'Stop recording', + idle: 'Tap to start recording', + recording: 'Recording…', + processing: 'Processing…', + }, + androidMicBanner: { + title: 'Microphone permission needed', + desc: 'Grant microphone access to use in-app dictation and voice input.', + grant: 'Request access', + openSettings: 'Open settings', + }, }, history: { kicker: 'HISTORY', @@ -775,6 +816,7 @@ export const en: typeof zhCN = { disable: 'Disable', confirmHint: 'Click ✓ on the capsule', notSupported: 'Not yet supported', + androidReadOnly: 'Global shortcuts are not available on Android. Use the record button on the overview page.', }, permissions: { title: 'Permissions', @@ -798,6 +840,7 @@ export const en: typeof zhCN = { indeterminate: 'Undetermined', openSystem: 'Open System Settings', grant: 'Grant', + rerunAndroidSetup: 'Run setup again', hotkeyInstalled: 'Installed', hotkeyStarting: 'Installing…', hotkeyFailed: 'Listener failed', @@ -805,6 +848,65 @@ export const en: typeof zhCN = { windowsImeDesc: 'Temporarily switches to the OpenLess TSF IME during voice sessions to avoid clipboard insertion limits.', windowsImeInstalled: 'Installed', windowsImeUnavailable: 'Unavailable', + androidImeLabel: 'Input method (IME)', + androidImeSelected: 'Selected', + androidImeEnabled: 'Enabled', + androidImeDisabled: 'Not enabled', + androidOverlayLabel: 'Floating overlay', + androidAccessibilityLabel: 'Accessibility service', + androidAccessibilityImpact: 'Enable it to output results to the current input field without switching keyboards. If disabled, results are copied to the clipboard for manual paste.', + androidInsertStrategyLabel: 'Text insertion strategy', + androidOverlayTriggerLabel: 'Overlay visibility', + androidOverlayActivationModeLabel: 'Overlay activation', + androidOverlayLeftSwipeActionLabel: 'Left swipe action', + androidOverlayCancelSwipeDirectionLabel: 'Cancel swipe direction', + androidOverlaySizeLabel: 'Overlay size', + androidOverlaySizeHint: 'Adjusts the floating button diameter and keeps its current position.', + androidInsertStrategy: { + accessibility: 'Auto output to input field', + clipboard: 'Clipboard only', + }, + androidInsertStrategyHint: { + accessibility: 'Requires accessibility; falls back to clipboard when unavailable.', + clipboard: 'No accessibility permission required; copies only for manual paste.', + }, + androidOverlayTrigger: { + background: 'When app is backgrounded', + keyboard: 'When keyboard appears', + always: 'Always visible', + }, + androidOverlayTriggerHint: { + background: 'Simple and battery-friendly; no overlay while typing in other apps.', + keyboard: 'This mode is shelved. Existing settings are moved back to background.', + always: 'Always available, but permanently on screen.', + }, + androidOverlayTriggerDisabled: { + keyboard: 'Keyboard-triggered display is shelved. Overlay gestures will replace keyboard detection.', + }, + androidOverlayActivationMode: { + tap: 'Tap to arm', + long_press: 'Long press to arm', + }, + androidOverlayActivationModeHint: { + tap: 'First tap arms the overlay; second tap starts normal dictation.', + long_press: 'Hold to arm the overlay; release stops the current recording or QA turn.', + }, + androidOverlayLeftSwipeAction: { + translation: 'Translation dictation', + style_pack: 'Switch style pack', + }, + androidOverlayLeftSwipeActionHint: { + translation: 'Left swipe while armed starts translation dictation.', + style_pack: 'Left swipe while armed switches to the previous style pack.', + }, + androidOverlayCancelSwipeDirection: { + up: 'Swipe up', + down: 'Swipe down', + }, + androidOverlayCancelSwipeDirectionHint: { + up: 'Swipe up while recording to cancel without transcription or insertion.', + down: 'Swipe down while recording to cancel without transcription or insertion.', + }, windowsIme: { installed: 'Installed. Voice input temporarily switches to the OpenLess IME.', notInstalled: 'Not installed. OpenLess is using the clipboard/WM_PASTE fallback.', @@ -1002,6 +1104,7 @@ export const en: typeof zhCN = { macEventTap: 'macOS Event Tap', windowsLowLevel: 'Windows low-level keyboard hook', fcitx5: 'fcitx5 input method plugin', + unavailable: 'Unavailable', }, }, localAsr: { diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 46bd7e03..5942e5a6 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -51,6 +51,11 @@ export const ja: typeof zhCN = { emptyTitle: '{{recordHotkey}} を押して質問を開始', emptyDesc: '任意のアプリでテキストを選択した後、{{recordHotkey}} を 1 回押して録音を開始し、もう 1 回押して送信します。回答はここに表示され、続けて追加質問が可能です。', recordingHint: '録音中… {{recordHotkey}} をもう一度押して終了し、質問します', + mobileRecordLabel: '録音ボタン', + mobileRecordStart: '録音を開始', + mobileRecordStop: '終了して送信', + composerPlaceholder: '質問を入力。Enter で送信、Shift+Enter で改行', + composerSend: '送信', statusIdle: '{{recordHotkey}} で質問', statusRecording: '録音中', statusThinking: '思考中', @@ -225,6 +230,28 @@ export const ja: typeof zhCN = { actionRequestMic: '許可ダイアログを表示', accessibilityHint: '許可後は **OpenLess を完全に終了** してから再起動してください(macOS TCC の仕様)。', footerHint: 'すべての権限が揃うとこのガイドは自動で閉じます。閉じない場合はメニューバーの OpenLess → 終了 から再起動してください。', + androidContinue: 'アプリに進む', + androidFooterHint: '音声入力にはマイク権限が必要です。上の「許可ダイアログを表示」をタップするか、先にアプリへ進み、概要ページで後から許可してください。', + androidTitle: 'OpenLess を設定', + androidIntro: 'モバイル権限とサービス設定を順番に完了します。', + androidStepCounter: '{{current}} / {{total}}', + androidBack: '戻る', + androidNext: '次へ', + androidFinish: '完了して開始', + androidSteps: { + microphoneTitle: 'マイク権限', + microphoneDesc: 'Android のシステム権限カードを表示し、OpenLess の録音を許可します。', + accessibilityTitle: 'アクセシビリティサービス', + accessibilityDesc: '認識結果を現在の入力欄へ貼り付け、入力環境の検出を補助します。', + overlayPermissionTitle: 'フローティングウィンドウ権限', + overlayPermissionDesc: '他のアプリ上に録音コントロールを表示できるようにします。', + overlayConfigTitle: 'フローティングウィンドウ設定', + overlayConfigDesc: '表示タイミング、起動方法、スワイプ操作、ボタンサイズを設定します。', + asrTitle: 'ASR クラウドサービス', + asrDesc: '音声認識サービスのプロバイダー、キー、エンドポイント、モデルを設定します。', + llmTitle: 'LLM サービス', + llmDesc: '整文、翻訳、Q&A に使う言語モデルサービスを設定します。', + }, }, overview: { kicker: 'DASHBOARD', @@ -260,6 +287,20 @@ export const ja: typeof zhCN = { recentLoadFailed: '最近の認識を読み込めません。再試行してください。', historyRetry: '再試行', weekDays: ['日', '月', '火', '水', '木', '金', '土'], + inAppDictation: { + title: 'アプリ内音声入力', + start: '録音開始', + stop: '録音停止', + idle: 'タップして録音開始', + recording: '録音中…', + processing: '処理中…', + }, + androidMicBanner: { + title: 'マイク権限が必要です', + desc: 'マイクを許可すると、アプリ内音声入力が使えます。', + grant: '許可ダイアログを表示', + openSettings: '設定を開く', + }, }, history: { kicker: 'HISTORY', @@ -777,6 +818,7 @@ export const ja: typeof zhCN = { disable: '無効化', confirmHint: '右側の ✓ をクリック', notSupported: '未対応', + androidReadOnly: 'Android ではグローバルショートカットは使えません。概要ページの録音ボタンを使ってください。', }, permissions: { title: '権限', @@ -800,6 +842,7 @@ export const ja: typeof zhCN = { indeterminate: '未確定', openSystem: 'システム設定を開く', grant: '許可する', + rerunAndroidSetup: 'セットアップを再実行', hotkeyInstalled: 'インストール済み', hotkeyStarting: 'インストール中…', hotkeyFailed: '監視失敗', @@ -807,6 +850,31 @@ export const ja: typeof zhCN = { windowsImeDesc: '音声セッション中に OpenLess TSF IME へ一時的に切り替え、クリップボード入力の制限を回避します。', windowsImeInstalled: 'インストール済み', windowsImeUnavailable: '利用不可', + androidImeLabel: '入力メソッド (IME)', + androidImeSelected: '選択中', + androidImeEnabled: '有効', + androidImeDisabled: '無効', + androidOverlayLabel: 'フローティングオーバーレイ', + androidAccessibilityLabel: 'アクセシビリティ', + androidAccessibilityImpact: '有効にすると、キーボードを切り替えずに現在の入力欄へ結果を出力します。無効の場合はクリップボードへコピーし、手動で貼り付けます。', + androidInsertStrategyLabel: '挿入方式', + androidOverlayTriggerLabel: '表示タイミング', + androidOverlayActivationModeLabel: '起動方法', + androidOverlayLeftSwipeActionLabel: '左スワイプ動作', + androidOverlayCancelSwipeDirectionLabel: 'キャンセル方向', + androidOverlaySizeLabel: 'オーバーレイサイズ', + androidOverlaySizeHint: 'フローティングボタンの直径を調整し、現在位置を保持します。', + androidInsertStrategy: { accessibility: '入力欄へ自動出力', clipboard: 'クリップボードのみ' }, + androidInsertStrategyHint: { accessibility: 'アクセシビリティが必要です。使えない場合はクリップボードにコピーします。', clipboard: 'アクセシビリティ権限は不要です。コピー後に手動で貼り付けます。' }, + androidOverlayTrigger: { background: 'バックグラウンド', keyboard: 'キーボード表示時', always: '常時' }, + androidOverlayTriggerHint: { background: 'シンプル', keyboard: 'このモードは保留中です。既存設定はバックグラウンドに戻します。', always: '常に表示' }, + androidOverlayTriggerDisabled: { keyboard: 'キーボード表示時の表示は保留中です。今後はフローティングウィンドウのジェスチャーで置き換えます。' }, + androidOverlayActivationMode: { tap: 'タップで起動', long_press: '長押しで起動' }, + androidOverlayActivationModeHint: { tap: '1回目のタップで待機状態に入り、2回目のタップで通常の音声入力を開始します。', long_press: '押している間だけ待機状態に入り、離すと現在の録音またはQAターンを終了します。' }, + androidOverlayLeftSwipeAction: { translation: '翻訳入力', style_pack: 'スタイルパック切替' }, + androidOverlayLeftSwipeActionHint: { translation: '待機状態で左スワイプすると翻訳入力を開始します。', style_pack: '待機状態で左スワイプすると前のスタイルパックへ切り替えます。' }, + androidOverlayCancelSwipeDirection: { up: '上へスワイプ', down: '下へスワイプ' }, + androidOverlayCancelSwipeDirectionHint: { up: '録音中に上へスワイプすると、文字起こしや挿入をせずにキャンセルします。', down: '録音中に下へスワイプすると、文字起こしや挿入をせずにキャンセルします。' }, windowsIme: { installed: 'インストール済み。音声入力時に OpenLess IME へ一時的に切り替えます。', notInstalled: '未インストール。OpenLess は現在クリップボード / WM_PASTE フォールバックを使用しています。', @@ -1004,6 +1072,7 @@ export const ja: typeof zhCN = { macEventTap: 'macOS Event Tap', windowsLowLevel: 'Windows 低レベルキーボードフック', fcitx5: 'fcitx5 インプットメソッドプラグイン', + unavailable: '利用不可', }, }, localAsr: { diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index ff86d915..07030b0c 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -51,6 +51,11 @@ export const ko: typeof zhCN = { emptyTitle: '{{recordHotkey}} 를 눌러 질문 시작', emptyDesc: '아무 앱에서 텍스트를 선택한 후 {{recordHotkey}} 를 한 번 눌러 녹음을 시작하고, 다시 한 번 눌러 종료 후 제출합니다. 답변이 여기에 표시되며 연속해서 후속 질문이 가능합니다.', recordingHint: '녹음 중… {{recordHotkey}} 를 다시 눌러 종료하고 질문', + mobileRecordLabel: '녹음 버튼', + mobileRecordStart: '녹음 시작', + mobileRecordStop: '종료하고 제출', + composerPlaceholder: '질문을 입력하세요. Enter로 보내고 Shift+Enter로 줄바꿈', + composerSend: '보내기', statusIdle: '{{recordHotkey}} 로 질문', statusRecording: '녹음 중', statusThinking: '생각 중', @@ -225,6 +230,28 @@ export const ko: typeof zhCN = { actionRequestMic: '권한 대화상자 표시', accessibilityHint: '허용 후에는 **OpenLess 를 완전히 종료** 한 다음 다시 실행해야 합니다(macOS TCC 규칙).', footerHint: '모든 권한이 부여되면 이 가이드는 자동으로 닫힙니다. 닫히지 않으면 메뉴 막대의 OpenLess → 종료 후 앱을 다시 실행해 주세요.', + androidContinue: '앱으로 계속', + androidFooterHint: '받아쓰기에는 마이크 권한이 필요합니다. 위의 권한 요청을 탭하거나, 앱으로 먼저 들어가 개요 페이지에서 나중에 허용할 수 있습니다.', + androidTitle: 'OpenLess 설정', + androidIntro: '모바일 권한과 서비스 설정을 단계별로 완료합니다.', + androidStepCounter: '{{current}} / {{total}} 단계', + androidBack: '이전', + androidNext: '다음', + androidFinish: '완료하고 시작', + androidSteps: { + microphoneTitle: '마이크 권한', + microphoneDesc: 'Android 시스템 권한 카드를 표시하고 OpenLess 녹음을 허용합니다.', + accessibilityTitle: '접근성 서비스', + accessibilityDesc: '인식 결과를 현재 입력란에 붙여넣고 입력 환경 감지를 보조합니다.', + overlayPermissionTitle: '플로팅 창 권한', + overlayPermissionDesc: '다른 앱 위에 녹음 제어 버튼을 표시할 수 있게 합니다.', + overlayConfigTitle: '플로팅 창 설정', + overlayConfigDesc: '표시 시점, 활성화 방식, 스와이프 동작, 버튼 크기를 설정합니다.', + asrTitle: 'ASR 클라우드 서비스', + asrDesc: '음성 인식 서비스의 공급자, 키, 엔드포인트, 모델을 설정합니다.', + llmTitle: 'LLM 서비스', + llmDesc: '문장 다듬기, 번역, Q&A에 사용할 언어 모델 서비스를 설정합니다.', + }, }, overview: { kicker: 'DASHBOARD', @@ -260,6 +287,20 @@ export const ko: typeof zhCN = { recentLoadFailed: '최근 인식 기록을 불러올 수 없습니다. 다시 시도해 주세요.', historyRetry: '다시 시도', weekDays: ['일', '월', '화', '수', '목', '금', '토'], + inAppDictation: { + title: '앱 내 받아쓰기', + start: '녹음 시작', + stop: '녹음 중지', + idle: '탭하여 녹음 시작', + recording: '녹음 중…', + processing: '처리 중…', + }, + androidMicBanner: { + title: '마이크 권한이 필요합니다', + desc: '마이크를 허용하면 앱 내 받아쓰기와 음성 입력을 사용할 수 있습니다.', + grant: '권한 요청', + openSettings: '설정 열기', + }, }, history: { kicker: 'HISTORY', @@ -777,6 +818,7 @@ export const ko: typeof zhCN = { disable: '비활성화', confirmHint: '오른쪽 ✓ 클릭', notSupported: '지원되지 않음', + androidReadOnly: 'Android에서는 전역 단축키를 사용할 수 없습니다. 개요 페이지의 녹음 버튼을 사용하세요.', }, permissions: { title: '권한', @@ -800,6 +842,7 @@ export const ko: typeof zhCN = { indeterminate: '미결정', openSystem: '시스템 설정 열기', grant: '허용', + rerunAndroidSetup: '설정 마법사 다시 실행', hotkeyInstalled: '설치됨', hotkeyStarting: '설치 중…', hotkeyFailed: '감지 실패', @@ -807,6 +850,31 @@ export const ko: typeof zhCN = { windowsImeDesc: '음성 세션 동안 OpenLess TSF 입력기로 일시적으로 전환하여 클립보드 입력 제한을 회피하기 위해 사용.', windowsImeInstalled: '설치됨', windowsImeUnavailable: '사용 불가', + androidImeLabel: '입력기 (IME)', + androidImeSelected: '선택됨', + androidImeEnabled: '활성화됨', + androidImeDisabled: '비활성', + androidOverlayLabel: '플로팅 오버레이', + androidAccessibilityLabel: '접근성 서비스', + androidAccessibilityImpact: '켜면 키보드를 전환하지 않고 현재 입력칸에 결과를 출력합니다. 끄면 클립보드에 복사되며 직접 붙여넣어야 합니다.', + androidInsertStrategyLabel: '텍스트 삽입 방식', + androidOverlayTriggerLabel: '오버레이 표시', + androidOverlayActivationModeLabel: '오버레이 활성화', + androidOverlayLeftSwipeActionLabel: '왼쪽 스와이프 동작', + androidOverlayCancelSwipeDirectionLabel: '취소 스와이프 방향', + androidOverlaySizeLabel: '오버레이 크기', + androidOverlaySizeHint: '플로팅 버튼 지름을 조정하고 현재 위치를 유지합니다.', + androidInsertStrategy: { accessibility: '입력칸에 자동 출력', clipboard: '클립보드만' }, + androidInsertStrategyHint: { accessibility: '접근성 서비스가 필요합니다. 사용할 수 없으면 클립보드에 복사합니다.', clipboard: '접근성 권한이 필요 없으며 직접 붙여넣습니다.' }, + androidOverlayTrigger: { background: '백그라운드', keyboard: '키보드 표시 시', always: '항상' }, + androidOverlayTriggerHint: { background: '단순', keyboard: '이 모드는 보류되었습니다. 기존 설정은 백그라운드로 되돌립니다.', always: '항상 표시' }, + androidOverlayTriggerDisabled: { keyboard: '키보드 표시 감지는 보류되었습니다. 이후 오버레이 제스처로 대체합니다.' }, + androidOverlayActivationMode: { tap: '탭으로 활성화', long_press: '길게 눌러 활성화' }, + androidOverlayActivationModeHint: { tap: '첫 탭은 대기 상태로 전환하고, 두 번째 탭은 일반 받아쓰기를 시작합니다.', long_press: '누르고 있는 동안 대기 상태가 되며, 손을 떼면 현재 녹음 또는 QA 턴을 종료합니다.' }, + androidOverlayLeftSwipeAction: { translation: '번역 받아쓰기', style_pack: '스타일 팩 전환' }, + androidOverlayLeftSwipeActionHint: { translation: '대기 상태에서 왼쪽으로 밀면 번역 받아쓰기를 시작합니다.', style_pack: '대기 상태에서 왼쪽으로 밀면 이전 스타일 팩으로 전환합니다.' }, + androidOverlayCancelSwipeDirection: { up: '위로 스와이프', down: '아래로 스와이프' }, + androidOverlayCancelSwipeDirectionHint: { up: '녹음 중 위로 밀면 전사와 삽입 없이 취소합니다.', down: '녹음 중 아래로 밀면 전사와 삽입 없이 취소합니다.' }, windowsIme: { installed: '설치됨. 음성 입력 시 OpenLess 입력기로 일시 전환됩니다.', notInstalled: '설치되지 않음. OpenLess 는 현재 클립보드 / WM_PASTE 폴백을 사용합니다.', @@ -1004,6 +1072,7 @@ export const ko: typeof zhCN = { macEventTap: 'macOS Event Tap', windowsLowLevel: 'Windows 저수준 키보드 후크', fcitx5: 'fcitx5 입력기 플러그인', + unavailable: '사용 불가', }, }, localAsr: { diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index b0d9b60f..db8f3c3a 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -47,6 +47,11 @@ export const zhCN = { emptyTitle: '按 {{recordHotkey}} 开始提问', emptyDesc: '在任意 app 选中一段文字后,按一次 {{recordHotkey}} 开始录音,再按一次结束并提交。回答会显示在这里,可以连续多轮追问。', recordingHint: '录音中…再按一次 {{recordHotkey}} 结束并提问', + mobileRecordLabel: '录音按钮', + mobileRecordStart: '开始录音', + mobileRecordStop: '结束并提交', + composerPlaceholder: '输入问题,Enter 发送,Shift+Enter 换行', + composerSend: '发送', statusIdle: '按 {{recordHotkey}} 提问', statusRecording: '录音中', statusThinking: '思考中', @@ -221,6 +226,28 @@ export const zhCN = { actionRequestMic: '弹出授权', accessibilityHint: '授权后必须**完全退出 OpenLess** 再重新打开(macOS TCC 规则)。', footerHint: '授权全部完成后此引导自动关闭。如果一直不消失,从菜单栏 OpenLess → 退出,重新打开 App。', + androidContinue: '先进入应用', + androidFooterHint: '听写需要麦克风权限。可点击上方「弹出授权」,或先进入应用后在概览页继续授权。', + androidTitle: '配置 OpenLess', + androidIntro: '按步骤完成移动端权限和服务配置。', + androidStepCounter: '第 {{current}} / {{total}} 项', + androidBack: '上一步', + androidNext: '下一步', + androidFinish: '完成并进入', + androidSteps: { + microphoneTitle: '麦克风权限', + microphoneDesc: '调用 Android 系统授权卡片,允许 OpenLess 录制语音。', + accessibilityTitle: '无障碍服务', + accessibilityDesc: '用于把识别结果粘贴回当前输入框,并辅助检测输入环境。', + overlayPermissionTitle: '悬浮窗权限', + overlayPermissionDesc: '允许 OpenLess 在其他应用上显示录音控制按钮。', + overlayConfigTitle: '悬浮窗配置', + overlayConfigDesc: '设置悬浮窗显示时机、触发方式、滑动动作和按钮大小。', + asrTitle: 'ASR 云服务', + asrDesc: '配置语音转文字服务的供应商、密钥、接口地址和模型。', + llmTitle: 'LLM 服务', + llmDesc: '配置文本润色、翻译和问答使用的语言模型服务。', + }, }, overview: { kicker: 'DASHBOARD', @@ -256,6 +283,20 @@ export const zhCN = { recentLoadFailed: '无法读取最近识别,请重试。', historyRetry: '重试', weekDays: ['日', '一', '二', '三', '四', '五', '六'], + inAppDictation: { + title: '应用内录音', + start: '开始录音', + stop: '停止录音', + idle: '点击开始录音', + recording: '录音中…', + processing: '处理中…', + }, + androidMicBanner: { + title: '需要麦克风权限', + desc: '授权麦克风后可使用应用内录音与语音输入。', + grant: '弹出授权', + openSettings: '打开系统设置', + }, }, history: { kicker: 'HISTORY', @@ -773,6 +814,7 @@ export const zhCN = { disable: '停用', confirmHint: '点击右侧 ✓', notSupported: '暂未支持', + androidReadOnly: 'Android 不支持全局快捷键,请在概览页使用录音按钮。', }, permissions: { title: '权限', @@ -796,6 +838,7 @@ export const zhCN = { indeterminate: '未确定', openSystem: '打开系统设置', grant: '授权', + rerunAndroidSetup: '重新运行设置向导', hotkeyInstalled: '已安装', hotkeyStarting: '安装中…', hotkeyFailed: '监听失败', @@ -803,6 +846,65 @@ export const zhCN = { windowsImeDesc: '语音输入时临时切到 OpenLess TSF,绕过剪贴板限制。', windowsImeInstalled: '已安装', windowsImeUnavailable: '不可用', + androidImeLabel: '输入法 (IME)', + androidImeSelected: '已选中', + androidImeEnabled: '已启用', + androidImeDisabled: '未启用', + androidOverlayLabel: '悬浮窗', + androidAccessibilityLabel: '无障碍服务', + androidAccessibilityImpact: '开启后可在不切换键盘的情况下把结果输出到当前输入框;不开启时仍会复制到剪贴板,需要手动粘贴。', + androidInsertStrategyLabel: '文本插入策略', + androidOverlayTriggerLabel: '悬浮窗显示时机', + androidOverlayActivationModeLabel: '悬浮窗激活方式', + androidOverlayLeftSwipeActionLabel: '左滑动作', + androidOverlayCancelSwipeDirectionLabel: '取消录音滑向', + androidOverlaySizeLabel: '悬浮窗大小', + androidOverlaySizeHint: '调整悬浮按钮直径,保存后在当前悬浮窗上生效并保留位置。', + androidInsertStrategy: { + accessibility: '自动输出到输入框', + clipboard: '仅剪贴板', + }, + androidInsertStrategyHint: { + accessibility: '需要开启无障碍服务;不可用时会复制到剪贴板。', + clipboard: '不需要无障碍权限,结果只复制到剪贴板,由你手动粘贴。', + }, + androidOverlayTrigger: { + background: '应用退到后台', + keyboard: '弹出键盘时', + always: '始终显示', + }, + androidOverlayTriggerHint: { + background: '省电、实现简单;其他 App 输入时不会自动出现。', + keyboard: '该模式已暂缓,历史配置会自动改为“应用退到后台”。', + always: '入口始终可见,但会一直占屏。', + }, + androidOverlayTriggerDisabled: { + keyboard: '“弹出键盘时”暂缓开放,后续将以悬浮窗手势替代键盘检测。', + }, + androidOverlayActivationMode: { + tap: '点按激活', + long_press: '长按激活', + }, + androidOverlayActivationModeHint: { + tap: '第一次点按进入激活态,第二次点按开始普通听写。', + long_press: '按住进入激活态;松开时结束当前录音或问答轮次。', + }, + androidOverlayLeftSwipeAction: { + translation: '翻译听写', + style_pack: '切换风格包', + }, + androidOverlayLeftSwipeActionHint: { + translation: '激活态左滑后按翻译模式录音。', + style_pack: '激活态左滑后切换到上一个风格包。', + }, + androidOverlayCancelSwipeDirection: { + up: '向上滑', + down: '向下滑', + }, + androidOverlayCancelSwipeDirectionHint: { + up: '录音中向上滑取消本次听写,不转写、不插入。', + down: '录音中向下滑取消本次听写,不转写、不插入。', + }, windowsIme: { installed: '已安装,按需切到 OpenLess 输入法。', notInstalled: '未安装,走剪贴板 / WM_PASTE 兜底。', @@ -1000,6 +1102,7 @@ export const zhCN = { macEventTap: 'macOS Event Tap', windowsLowLevel: 'Windows 低层键盘 hook', fcitx5: 'fcitx5 输入法插件', + unavailable: '不可用', }, }, localAsr: { diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index c61ed06e..523716fd 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -49,6 +49,11 @@ export const zhTW: typeof zhCN = { emptyTitle: '按 {{recordHotkey}} 開始提問', emptyDesc: '在任意 app 選中一段文字後,按一次 {{recordHotkey}} 開始錄音,再按一次結束並提交。回答會顯示在這裏,可以連續多輪追問。', recordingHint: '錄音中…再按一次 {{recordHotkey}} 結束並提問', + mobileRecordLabel: '錄音按鈕', + mobileRecordStart: '開始錄音', + mobileRecordStop: '結束並提交', + composerPlaceholder: '輸入問題,Enter 發送,Shift+Enter 換行', + composerSend: '發送', statusIdle: '按 {{recordHotkey}} 提問', statusRecording: '錄音中', statusThinking: '思考中', @@ -223,6 +228,28 @@ export const zhTW: typeof zhCN = { actionRequestMic: '彈出授權', accessibilityHint: '授權後必須**完全退出 OpenLess** 再重新打開(macOS TCC 規則)。', footerHint: '授權全部完成後此引導自動關閉。如果一直不消失,從菜單欄 OpenLess → 退出,重新打開 App。', + androidContinue: '先進入應用', + androidFooterHint: '聽寫需要麥克風權限。可點擊上方「彈出授權」,或先進入應用後在概覽頁繼續授權。', + androidTitle: '配置 OpenLess', + androidIntro: '按步驟完成移動端權限和服務配置。', + androidStepCounter: '第 {{current}} / {{total}} 項', + androidBack: '上一步', + androidNext: '下一步', + androidFinish: '完成並進入', + androidSteps: { + microphoneTitle: '麥克風權限', + microphoneDesc: '調用 Android 系統授權卡片,允許 OpenLess 錄製語音。', + accessibilityTitle: '無障礙服務', + accessibilityDesc: '用於把識別結果貼回當前輸入框,並輔助檢測輸入環境。', + overlayPermissionTitle: '懸浮窗權限', + overlayPermissionDesc: '允許 OpenLess 在其他應用上顯示錄音控制按鈕。', + overlayConfigTitle: '懸浮窗配置', + overlayConfigDesc: '設置懸浮窗顯示時機、觸發方式、滑動動作和按鈕大小。', + asrTitle: 'ASR 雲服務', + asrDesc: '配置語音轉文字服務的供應商、密鑰、接口地址和模型。', + llmTitle: 'LLM 服務', + llmDesc: '配置文本潤色、翻譯和問答使用的語言模型服務。', + }, }, overview: { kicker: 'DASHBOARD', @@ -258,6 +285,20 @@ export const zhTW: typeof zhCN = { recentLoadFailed: '無法讀取最近識別,請重試。', historyRetry: '重試', weekDays: ['日', '一', '二', '三', '四', '五', '六'], + inAppDictation: { + title: '應用內錄音', + start: '開始錄音', + stop: '停止錄音', + idle: '點擊開始錄音', + recording: '錄音中…', + processing: '處理中…', + }, + androidMicBanner: { + title: '需要麥克風權限', + desc: '授權麥克風後可使用應用內錄音與語音輸入。', + grant: '彈出授權', + openSettings: '打開系統設置', + }, }, history: { kicker: 'HISTORY', @@ -775,6 +816,7 @@ export const zhTW: typeof zhCN = { disable: '停用', confirmHint: '點擊右側 ✓', notSupported: '暫未支持', + androidReadOnly: 'Android 不支援全域快捷鍵,請在概覽頁使用錄音按鈕。', }, permissions: { title: '權限', @@ -798,6 +840,7 @@ export const zhTW: typeof zhCN = { indeterminate: '未確定', openSystem: '打開系統設置', grant: '授權', + rerunAndroidSetup: '重新執行設定向導', hotkeyInstalled: '已安裝', hotkeyStarting: '安裝中…', hotkeyFailed: '監聽失敗', @@ -805,6 +848,31 @@ export const zhTW: typeof zhCN = { windowsImeDesc: '用於在語音會話期間臨時切換到 OpenLess TSF 輸入法,避免剪貼板插入限制。', windowsImeInstalled: '已安裝', windowsImeUnavailable: '不可用', + androidImeLabel: '輸入法 (IME)', + androidImeSelected: '已選中', + androidImeEnabled: '已啟用', + androidImeDisabled: '未啟用', + androidOverlayLabel: '懸浮窗', + androidAccessibilityLabel: '無障礙服務', + androidAccessibilityImpact: '開啟後可在不切換鍵盤的情況下把結果輸出到目前輸入框;未開啟時仍會複製到剪貼簿,需要手動貼上。', + androidInsertStrategyLabel: '文字插入策略', + androidOverlayTriggerLabel: '懸浮窗顯示時機', + androidOverlayActivationModeLabel: '懸浮窗啟用方式', + androidOverlayLeftSwipeActionLabel: '左滑動作', + androidOverlayCancelSwipeDirectionLabel: '取消錄音滑向', + androidOverlaySizeLabel: '懸浮窗大小', + androidOverlaySizeHint: '調整懸浮按鈕直徑,儲存後在目前懸浮窗上生效並保留位置。', + androidInsertStrategy: { accessibility: '自動輸出到輸入框', clipboard: '僅剪貼簿' }, + androidInsertStrategyHint: { accessibility: '需要開啟無障礙服務;不可用時會複製到剪貼簿。', clipboard: '不需要無障礙權限,只複製到剪貼簿,由你手動貼上。' }, + androidOverlayTrigger: { background: '退到背景', keyboard: '鍵盤彈出時', always: '常駐' }, + androidOverlayTriggerHint: { background: '省電', keyboard: '此模式已暫緩,既有設定會改回退到背景。', always: '一直佔屏' }, + androidOverlayTriggerDisabled: { keyboard: '「鍵盤彈出時」暫緩開放,後續將以懸浮窗手勢取代鍵盤偵測。' }, + androidOverlayActivationMode: { tap: '點按啟用', long_press: '長按啟用' }, + androidOverlayActivationModeHint: { tap: '第一次點按進入啟用狀態,第二次點按開始普通聽寫。', long_press: '按住進入啟用狀態;放開時結束目前錄音或問答輪次。' }, + androidOverlayLeftSwipeAction: { translation: '翻譯聽寫', style_pack: '切換風格包' }, + androidOverlayLeftSwipeActionHint: { translation: '啟用狀態左滑後按翻譯模式錄音。', style_pack: '啟用狀態左滑後切換到上一個風格包。' }, + androidOverlayCancelSwipeDirection: { up: '向上滑', down: '向下滑' }, + androidOverlayCancelSwipeDirectionHint: { up: '錄音中向上滑取消本次聽寫,不轉寫、不插入。', down: '錄音中向下滑取消本次聽寫,不轉寫、不插入。' }, windowsIme: { installed: '已安裝。語音輸入時會臨時切換到 OpenLess 輸入法。', notInstalled: '未安裝。OpenLess 正在使用剪貼板 / WM_PASTE 兜底。', @@ -1002,6 +1070,7 @@ export const zhTW: typeof zhCN = { macEventTap: 'macOS Event Tap', windowsLowLevel: 'Windows 低層鍵盤 hook', fcitx5: 'fcitx5 輸入法插件', + unavailable: '不可用', }, }, localAsr: { diff --git a/openless-all/app/src/lib/androidMicrophonePermission.ts b/openless-all/app/src/lib/androidMicrophonePermission.ts new file mode 100644 index 00000000..74490bd6 --- /dev/null +++ b/openless-all/app/src/lib/androidMicrophonePermission.ts @@ -0,0 +1 @@ +export * from '@android/lib/androidMicrophonePermission'; diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index 22201685..95c67847 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -15,6 +15,7 @@ import type { HotkeyStatus, MicrophoneDevice, PermissionStatus, + PlatformCapabilities, CodingAgentPermissionMode, PolishMode, QaHotkeyBinding, @@ -29,13 +30,16 @@ import type { VocabPresetStore, WindowsImeStatus, } from "./types" -export type { UpdateChannel } from "./types" +export type { UpdateChannel, PlatformCapabilities } from "./types" import { OL_DATA } from "./mockData" import { defaultAppShortcutModifiers, defaultQaShortcut, formatComboLabel, } from "./hotkey" +import { + getPlatformCapabilities as loadPlatformCapabilities, +} from "./platform" declare global { interface Window { @@ -47,6 +51,56 @@ const isTauri = globalThis.window !== undefined && "__TAURI_INTERNALS__" in globalThis.window +let platformCapsPromise: Promise | null = null + +async function platformCapabilities(): Promise { + platformCapsPromise ??= loadPlatformCapabilities() + return platformCapsPromise +} + +export async function getPlatformCapabilities(): Promise { + return platformCapabilities() +} + +export { + getAndroidOverlayStatus, + requestAndroidOverlayPermission, + showAndroidOverlay, + hideAndroidOverlay, + getAndroidAccessibilityStatus, + requestAndroidAccessibilityPermission, +} from '../../android/frontend/lib/androidIpc'; + +export { isAndroid, isDesktop, isMobile } from "./platform" + +const androidHotkeyCapability: HotkeyCapability = { + adapter: "unavailable", + availableTriggers: [], + requiresAccessibilityPermission: false, + supportsModifierOnlyTrigger: false, + supportsSideSpecificModifiers: false, + explicitFallbackAvailable: false, + statusHint: + "移动端不支持全局热键;请使用应用内录音按钮或悬浮窗(需授权)。", +} + +const androidHotkeyStatus: HotkeyStatus = { + adapter: "unavailable", + state: "failed", + message: "移动端不支持全局热键", + lastError: { + code: "unavailable", + message: "Global hotkeys are not available on mobile", + }, +} + +const androidWindowsImeStatus: WindowsImeStatus = { + state: "notWindows", + usingTsfBackend: false, + message: "Not available on Android", + dllPath: null, +} + export async function invokeOrMock( cmd: string, args: Record | undefined, @@ -136,6 +190,12 @@ let mockSettings: UserPreferences = { remoteInputPort: 8443, remoteInputPin: "000000", remoteInputDefaultMode: "toggle", + androidInsertStrategy: "accessibility", + androidOverlayTrigger: "background", + androidOverlayActivationMode: "tap", + androidOverlayLeftSwipeAction: "translation", + androidOverlayCancelSwipeDirection: "up", + androidOverlaySizeDp: 72, } const mockFullStylePrompts: StyleSystemPrompts = { @@ -579,40 +639,95 @@ export interface LatestBetaRelease { publishedAt: string } +export interface AppUpdateMetadata { + rid: number + currentVersion: string + version: string + date?: string | null + body?: string | null + rawJson: Record +} + export function getUpdateChannel(): Promise { - return invokeOrMock( - "get_update_channel", - undefined, - () => "stable" as UpdateChannel, - ) + return platformCapabilities().then((caps) => { + if (!caps.supportsAutoUpdate) { + return "stable" as UpdateChannel + } + return invokeOrMock( + "get_update_channel", + undefined, + () => "stable" as UpdateChannel, + ) + }) } export function setUpdateChannel(channel: UpdateChannel): Promise { - return invokeOrMock("set_update_channel", { channel }, () => undefined) + return platformCapabilities().then((caps) => { + if (!caps.supportsAutoUpdate) { + return undefined + } + return invokeOrMock("set_update_channel", { channel }, () => undefined) + }) } export function fetchLatestBetaRelease(): Promise { - return invokeOrMock("fetch_latest_beta_release", undefined, () => null) + return platformCapabilities().then((caps) => { + if (!caps.supportsAutoUpdate) { + return null + } + return invokeOrMock("fetch_latest_beta_release", undefined, () => null) + }) +} + +export function appCheckUpdateWithChannel( + timeoutMs: number, + channel?: UpdateChannel | null, +): Promise { + return platformCapabilities().then((caps) => { + if (!caps.supportsAutoUpdate) { + return null + } + return invokeOrMock( + "app_check_update_with_channel", + { timeoutMs, channel: channel ?? null }, + () => null, + ) + }) } export function getHotkeyStatus(): Promise { - return invokeOrMock("get_hotkey_status", undefined, () => mockHotkeyStatus) + return platformCapabilities().then((caps) => { + if (!caps.supportsDesktopHotkey) { + return androidHotkeyStatus + } + return invokeOrMock("get_hotkey_status", undefined, () => mockHotkeyStatus) + }) } export function getHotkeyCapability(): Promise { - return invokeOrMock( - "get_hotkey_capability", - undefined, - () => mockHotkeyCapability, - ) + return platformCapabilities().then((caps) => { + if (!caps.supportsDesktopHotkey) { + return androidHotkeyCapability + } + return invokeOrMock( + "get_hotkey_capability", + undefined, + () => mockHotkeyCapability, + ) + }) } export function getWindowsImeStatus(): Promise { - return invokeOrMock( - "get_windows_ime_status", - undefined, - () => mockWindowsImeStatus, - ) + return platformCapabilities().then((caps) => { + if (caps.platform === "android") { + return androidWindowsImeStatus + } + return invokeOrMock( + "get_windows_ime_status", + undefined, + () => mockWindowsImeStatus, + ) + }) } export interface NetworkCheckResult { @@ -849,11 +964,16 @@ export function handleWindowHotkeyEvent( code: string, repeat: boolean, ): Promise { - return invokeOrMock( - "handle_window_hotkey_event", - { event_type: eventType, key, code, repeat }, - () => undefined, - ) + return platformCapabilities().then((caps) => { + if (!caps.supportsDesktopHotkey) { + return undefined + } + return invokeOrMock( + "handle_window_hotkey_event", + { event_type: eventType, key, code, repeat }, + () => undefined, + ) + }) } // ── Polish ───────────────────────────────────────────────────────────── @@ -1093,7 +1213,7 @@ export function requestMicrophonePermission(): Promise { } export function openSystemSettings( - pane: "accessibility" | "microphone", + pane: "accessibility" | "microphone" | "overlay", ): Promise { return invokeOrMock("open_system_settings", { pane }, () => undefined) } @@ -1127,6 +1247,14 @@ export function qaWindowPin(pinned: boolean): Promise { return invokeOrMock("qa_window_pin", { pinned }, () => undefined) } +export function qaToggleRecording(): Promise { + return invokeOrMock("qa_toggle_recording", undefined, () => undefined) +} + +export function qaSubmitText(text: string): Promise { + return invokeOrMock("qa_submit_text", { text }, () => undefined) +} + // ── Less Computer 浮窗 ──────────────────────────────────────────────── /** 用户点 ✕ / 按 Esc 关闭 Less Computer 浮窗(隐藏窗口)。 */ export function lessComputerWindowDismiss(): Promise { @@ -1203,8 +1331,21 @@ export async function openExternal(url: string): Promise { window.open(url, "_blank", "noopener,noreferrer") return } - const { open } = await import("@tauri-apps/plugin-shell") - await open(url) + try { + const { open } = await import("@tauri-apps/plugin-shell") + await open(url) + return + } catch (error) { + console.warn("[external-open] shell plugin failed", error) + } + try { + const { invoke } = await import("@tauri-apps/api/core") + await invoke("open_external_url", { url }) + return + } catch (error) { + console.warn("[external-open] native fallback failed", error) + } + window.open(url, "_blank", "noopener,noreferrer") } /** diff --git a/openless-all/app/src/lib/platform.ts b/openless-all/app/src/lib/platform.ts new file mode 100644 index 00000000..03badcdb --- /dev/null +++ b/openless-all/app/src/lib/platform.ts @@ -0,0 +1,122 @@ +// Platform capability detection for desktop vs Android APK targets. +// Prefers Tauri `get_platform_capabilities`; falls back to UA / OS heuristics. + +import { detectOS } from '../components/WindowChrome'; +import type { PlatformCapabilities, PlatformKind } from './types'; + +export type { PlatformCapabilities, PlatformKind }; + +let cachedCapabilities: PlatformCapabilities | null = null; + +function detectAndroidFromUa(): boolean { + if (typeof navigator === 'undefined') return false; + const uaDataPlatform = + (navigator as Navigator & { userAgentData?: { platform?: string } }) + .userAgentData?.platform ?? ''; + const hints = `${navigator.userAgent || ''} ${navigator.platform || ''} ${uaDataPlatform}`; + return /Android/i.test(hints); +} + +function detectIosFromUa(): boolean { + if (typeof navigator === 'undefined') return false; + const uaDataPlatform = + (navigator as Navigator & { userAgentData?: { platform?: string } }) + .userAgentData?.platform ?? ''; + const hints = `${navigator.userAgent || ''} ${navigator.platform || ''} ${uaDataPlatform}`; + return /iPhone|iPad|iPod/i.test(hints); +} + +/** Unavailable capability flags for iOS / other non-Android mobile targets. */ +const MOBILE_UNAVAILABLE: PlatformCapabilities = { + platform: 'mobile', + supportsDesktopHotkey: false, + supportsTray: false, + supportsOverlay: false, + supportsImeInput: false, + supportsLocalAsr: false, + supportsInAppDictation: false, + supportsAutoUpdate: false, +}; + +export function isAndroid(): boolean { + if (cachedCapabilities) return cachedCapabilities.platform === 'android'; + return detectOS() === 'android' || detectAndroidFromUa(); +} + +export function isMobile(): boolean { + if (cachedCapabilities) { + return ( + cachedCapabilities.platform === 'mobile' || + cachedCapabilities.platform === 'android' + ); + } + return isAndroid() || detectIosFromUa(); +} + +export function isDesktop(): boolean { + if (cachedCapabilities) return cachedCapabilities.platform === 'desktop'; + return !isMobile(); +} + +export function inferPlatformCapabilities(): PlatformCapabilities { + if (isAndroid()) { + return { + platform: 'android', + supportsDesktopHotkey: false, + supportsTray: false, + supportsOverlay: true, + supportsImeInput: false, + supportsLocalAsr: false, + supportsInAppDictation: true, + supportsAutoUpdate: false, + }; + } + + if (detectIosFromUa()) { + return MOBILE_UNAVAILABLE; + } + + const os = detectOS(); + return { + platform: 'desktop', + supportsDesktopHotkey: true, + supportsTray: true, + supportsOverlay: true, + supportsImeInput: os === 'win', + supportsLocalAsr: os === 'mac' || os === 'win', + supportsInAppDictation: false, + supportsAutoUpdate: true, + }; +} + +export async function getPlatformCapabilities(): Promise { + if (cachedCapabilities) return cachedCapabilities; + + const isTauri = + globalThis.window !== undefined && + '__TAURI_INTERNALS__' in globalThis.window; + + if (!isTauri) { + cachedCapabilities = inferPlatformCapabilities(); + return cachedCapabilities; + } + + try { + const { invoke } = await import('@tauri-apps/api/core'); + cachedCapabilities = await invoke( + 'get_platform_capabilities', + ); + return cachedCapabilities; + } catch (err) { + console.warn( + '[platform] get_platform_capabilities unavailable; using inferred defaults', + err, + ); + cachedCapabilities = inferPlatformCapabilities(); + return cachedCapabilities; + } +} + +export function getCachedPlatformCapabilities(): PlatformCapabilities | null { + return cachedCapabilities; +} diff --git a/openless-all/app/src/lib/stylePrefs.test.ts b/openless-all/app/src/lib/stylePrefs.test.ts index fd503121..96aea01b 100644 --- a/openless-all/app/src/lib/stylePrefs.test.ts +++ b/openless-all/app/src/lib/stylePrefs.test.ts @@ -84,6 +84,12 @@ const previousPrefs: UserPreferences = { remoteInputPort: 8443, remoteInputPin: '000000', remoteInputDefaultMode: 'toggle', + androidInsertStrategy: 'accessibility', + androidOverlayTrigger: 'background', + androidOverlayActivationMode: 'tap', + androidOverlayLeftSwipeAction: 'translation', + androidOverlayCancelSwipeDirection: 'up', + androidOverlaySizeDp: 72, }; const nextPrefs: UserPreferences = { diff --git a/openless-all/app/src/lib/types.ts b/openless-all/app/src/lib/types.ts index 408bce47..4cbf63d2 100644 --- a/openless-all/app/src/lib/types.ts +++ b/openless-all/app/src/lib/types.ts @@ -2,6 +2,26 @@ // All keys are camelCase (Rust serializes with #[serde(rename_all = "camelCase")]). // PolishMode is an exception — Rust uses lowercase serialization. +import type { + AndroidAccessibilityStatus, + AndroidInsertStrategy, + AndroidOverlayActivationMode, + AndroidOverlayCancelSwipeDirection, + AndroidOverlayLeftSwipeAction, + AndroidOverlayStatus, + AndroidOverlayTrigger, +} from '../../android/frontend/lib/androidTypes'; + +export type { + AndroidAccessibilityStatus, + AndroidInsertStrategy, + AndroidOverlayActivationMode, + AndroidOverlayCancelSwipeDirection, + AndroidOverlayLeftSwipeAction, + AndroidOverlayStatus, + AndroidOverlayTrigger, +}; + export type PolishMode = 'raw' | 'light' | 'structured' | 'formal'; export type InsertStatus = 'inserted' | 'pasteSent' | 'copiedFallback' | 'failed'; @@ -78,7 +98,7 @@ export interface HotkeyBinding { keys?: HotkeyKey[] | null; } -export type HotkeyAdapterKind = 'macEventTap' | 'windowsLowLevel' | 'fcitx5'; +export type HotkeyAdapterKind = 'macEventTap' | 'windowsLowLevel' | 'fcitx5' | 'unavailable'; export interface HotkeyCapability { adapter: HotkeyAdapterKind; @@ -346,6 +366,18 @@ export interface UserPreferences { remoteInputPin: string; /** 手机录音页默认交互方式:'toggle'(点击切换)/ 'hold'(按住说话)。 */ remoteInputDefaultMode: 'toggle' | 'hold'; + /** Android: cross-app dictation insert strategy. */ + androidInsertStrategy: AndroidInsertStrategy; + /** Android: floating overlay visibility trigger mode. */ + androidOverlayTrigger: AndroidOverlayTrigger; + /** Android: how the floating overlay enters the armed interaction state. */ + androidOverlayActivationMode: AndroidOverlayActivationMode; + /** Android: action performed by left swiping while the overlay is armed. */ + androidOverlayLeftSwipeAction: AndroidOverlayLeftSwipeAction; + /** Android: vertical swipe direction that cancels recording. */ + androidOverlayCancelSwipeDirection: AndroidOverlayCancelSwipeDirection; + /** Android: floating overlay control diameter in dp. */ + androidOverlaySizeDp: number; } export interface MarketplaceListItem { @@ -495,3 +527,18 @@ export type PermissionStatus = | 'notDetermined' | 'restricted' | 'notApplicable'; + +/** Runtime platform kind returned by `get_platform_capabilities`. */ +export type PlatformKind = 'desktop' | 'android' | 'mobile'; + +/** Feature flags for desktop vs Android APK UI gating. Mirrors src-tauri PlatformCapabilities. */ +export interface PlatformCapabilities { + platform: PlatformKind; + supportsDesktopHotkey: boolean; + supportsTray: boolean; + supportsOverlay: boolean; + supportsImeInput: boolean; + supportsLocalAsr: boolean; + supportsInAppDictation: boolean; + supportsAutoUpdate: boolean; +} diff --git a/openless-all/app/src/lib/useMobileLayout.ts b/openless-all/app/src/lib/useMobileLayout.ts new file mode 100644 index 00000000..77a00995 --- /dev/null +++ b/openless-all/app/src/lib/useMobileLayout.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; +import { detectOS } from '../components/WindowChrome'; + +function shouldUseMobileLayout(breakpoint: number): boolean { + if (typeof window === 'undefined') return false; + const osQuery = new URLSearchParams(window.location.search).get('os'); + return osQuery === 'android' || detectOS() === 'android' || window.innerWidth < breakpoint; +} + +export function useMobileLayout(breakpoint = 720): boolean { + const [mobile, setMobile] = useState(() => shouldUseMobileLayout(breakpoint)); + + useEffect(() => { + const sync = () => setMobile(shouldUseMobileLayout(breakpoint)); + sync(); + window.addEventListener('resize', sync); + window.addEventListener('orientationchange', sync); + return () => { + window.removeEventListener('resize', sync); + window.removeEventListener('orientationchange', sync); + }; + }, [breakpoint]); + + return mobile; +} diff --git a/openless-all/app/src/pages/History.tsx b/openless-all/app/src/pages/History.tsx index cf821862..371c7d42 100644 --- a/openless-all/app/src/pages/History.tsx +++ b/openless-all/app/src/pages/History.tsx @@ -10,6 +10,7 @@ import { clearHistory, deleteHistoryEntry, listHistory, readAudioRecording, retr import type { DictationSession, PolishMode } from '../lib/types'; import { useHotkeySettings } from '../state/HotkeySettingsContext'; import { Btn, Card, PageHeader, Pill } from './_atoms'; +import { useMobileLayout } from '../lib/useMobileLayout'; function useFilters(): Array<{ id: 'all' | PolishMode; label: string }> { const { t } = useTranslation(); @@ -35,6 +36,7 @@ function useModeLabel(): Record { export function History() { const { t } = useTranslation(); const os = detectOS(); + const mobile = useMobileLayout(); const FILTERS = useFilters(); const MODE_LABEL = useModeLabel(); const [filter, setFilter] = useState<'all' | PolishMode>('all'); @@ -222,8 +224,16 @@ export function History() {
    } /> -
    - +
    +
    setFilter(f.id)} style={{ padding: '3px 9px', fontSize: 11, borderRadius: 999, - border: '0.5px solid ' + (filter === f.id ? 'var(--ol-ink)' : 'var(--ol-line-strong)'), - background: filter === f.id ? 'var(--ol-ink)' : 'transparent', - color: filter === f.id ? '#fff' : 'var(--ol-ink-3)', + border: '0.5px solid ' + (filter === f.id ? 'var(--ol-pill-selected-border)' : 'var(--ol-line-strong)'), + background: filter === f.id ? 'var(--ol-pill-selected-bg)' : 'transparent', + color: filter === f.id ? 'var(--ol-pill-selected-ink)' : 'var(--ol-ink-3)', cursor: 'default', fontFamily: 'inherit', fontWeight: 500, transition: 'background 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick), border-color 0.16s var(--ol-motion-quick)', }} @@ -316,16 +326,16 @@ export function History() {
    - + {item ? ( <> -
    -
    +
    +
    {formatTime(item.createdAt)} {MODE_LABEL[item.mode]} {formatDuration(item.durationMs, t)}
    -
    +
    void onCopy()}>{justCopied ? t('common.copied') : t('common.copy')} {/* issue #613:失败条目(有错误码)且录音仍在时,提供「重新转录」。 */} {item.errorCode && item.hasAudioRecording && !audioMissingIds.has(item.id) && ( @@ -352,7 +362,7 @@ export function History() { key={item.id} /> )} -
    +
    {t('history.rawLabel')}

    diff --git a/openless-all/app/src/pages/Overview.tsx b/openless-all/app/src/pages/Overview.tsx index 84be5e92..8dadbc82 100644 --- a/openless-all/app/src/pages/Overview.tsx +++ b/openless-all/app/src/pages/Overview.tsx @@ -4,10 +4,27 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Icon } from '../components/Icon'; import { formatComboLabel } from '../lib/hotkey'; -import { getCredentials, listHistory } from '../lib/ipc'; -import type { CredentialsStatus, DictationSession, PolishMode } from '../lib/types'; +import { + checkMicrophonePermission, + getCredentials, + getPlatformCapabilities, + listHistory, + startDictation, + stopDictation, +} from '../lib/ipc'; +import type { + CapsulePayload, + CapsuleState, + CredentialsStatus, + DictationSession, + PermissionStatus, + PlatformCapabilities, + PolishMode, +} from '../lib/types'; import { useHotkeySettings } from '../state/HotkeySettingsContext'; import { Btn, Card, PageHeader, Pill } from './_atoms'; +import { useMobileLayout } from '../lib/useMobileLayout'; +import { checkAndroidMicrophoneAccess, requestAndroidMicrophoneAccess } from '../lib/androidMicrophonePermission'; function useModeLabels(): Record { const { t } = useTranslation(); @@ -35,7 +52,6 @@ const ASR_NAME_KEY_BY_ID: Record = { 'foundry-local-whisper': 'asrFoundryLocalWhisper', 'sherpa-onnx-local': 'asrSherpaOnnxLocal', 'local-qwen3': 'asrLocalQwen3', - 'apple-speech': 'asrAppleSpeech', }; const LLM_NAME_KEY_BY_ID: Record = { @@ -54,6 +70,7 @@ const LLM_NAME_KEY_BY_ID: Record = { export function Overview({ onOpenHistory }: OverviewProps) { const { t } = useTranslation(); + const mobile = useMobileLayout(); const modeLabel = useModeLabels(); const [history, setHistory] = useState([]); const [historyError, setHistoryError] = useState(false); @@ -171,9 +188,19 @@ export function Overview({ onOpenHistory }: OverviewProps) { return ( <> - + -

    + + + +
    -
    +
    0 ? t('overview.metricAvgTrend') : t('overview.metricNoData')} /> @@ -198,8 +225,8 @@ export function Overview({ onOpenHistory }: OverviewProps) { {/* 底部一行 = flex:1 撑满剩余高度(父 wrapper 是 display:flex/column)。 只有「最近识别」内部允许滚动;其他卡片按内容自然高度,不破裂底部圆角。 issue #243 follow-up:去掉外层 overflow 后底部圆角被裁的视觉问题。 */} -
    - +
    +
    {t('overview.weekTitle')} {t('overview.weekUnit')} @@ -216,12 +243,12 @@ export function Overview({ onOpenHistory }: OverviewProps) {
    - +
    {t('overview.recentTitle')} {t('overview.recentAll')}
    -
    +
    {historyError ? (
    {t('overview.recentLoadFailed')} @@ -255,10 +282,11 @@ interface ProviderCardProps { function ProviderCard({ kind, name, subname, status }: ProviderCardProps) { const { t } = useTranslation(); + const mobile = useMobileLayout(); // ASR 卡用 mic 图标,其他用 sparkle —— 通过比较译文判断会随语言改变,故改用本地化无关的字面量比较。 const isAsr = kind === t('overview.asrKind'); return ( - +
    -
    +
    {kind} {status === 'configured' && ( @@ -303,13 +331,14 @@ interface MetricProps { } function Metric({ icon, label, value, trend, accent }: MetricProps) { + const mobile = useMobileLayout(); return ( - +
    {label}
    -
    {value}
    +
    {value}
    {trend || ' '}
    ); @@ -344,9 +373,10 @@ function WeekChart({ data }: { data: number[] }) { function RecentRow({ session, modeLabel }: { session: DictationSession; modeLabel: Record }) { const { t } = useTranslation(); + const mobile = useMobileLayout(); return ( -
    -
    +
    +
    {formatTime(session.createdAt)} @@ -387,3 +417,189 @@ function weekDayLabels(names: string[]): string[] { } return out; } + +function AndroidMicGrantBanner() { + const { t } = useTranslation(); + const mobile = useMobileLayout(); + const [platformCaps, setPlatformCaps] = useState(null); + const [microphone, setMicrophone] = useState('loading'); + const [busy, setBusy] = useState(false); + + useEffect(() => { + void getPlatformCapabilities().then(setPlatformCaps); + }, []); + + const refreshMic = useCallback(async () => { + if (platformCaps?.platform === 'android') { + setMicrophone(await checkAndroidMicrophoneAccess()); + return; + } + setMicrophone(await checkMicrophonePermission()); + }, [platformCaps?.platform]); + + useEffect(() => { + if (platformCaps?.platform !== 'android') return; + void refreshMic(); + const onFocus = () => { void refreshMic(); }; + window.addEventListener('focus', onFocus); + return () => window.removeEventListener('focus', onFocus); + }, [platformCaps?.platform, refreshMic]); + + if (platformCaps?.platform !== 'android') return null; + if (microphone === 'loading' || microphone === 'granted' || microphone === 'notApplicable') { + return null; + } + + const onGrant = async () => { + setBusy(true); + try { + setMicrophone(await requestAndroidMicrophoneAccess()); + } finally { + setBusy(false); + void refreshMic(); + } + }; + + return ( + +
    + +
    +
    + {t('overview.androidMicBanner.title')} +
    +
    + {t('overview.androidMicBanner.desc')} +
    +
    +
    + void onGrant()} disabled={busy} style={{ justifyContent: 'center', width: mobile ? '100%' : undefined }}> + {t('overview.androidMicBanner.grant')} + +
    + ); +} + +function InAppDictationControl() { + const { t } = useTranslation(); + const mobile = useMobileLayout(); + const [platformCaps, setPlatformCaps] = useState(null); + const [capsuleState, setCapsuleState] = useState('idle'); + const [busy, setBusy] = useState(false); + + useEffect(() => { + void getPlatformCapabilities().then(setPlatformCaps); + }, []); + + useEffect(() => { + if (!platformCaps?.supportsInAppDictation) return; + let cancelled = false; + let unlisten: (() => void) | undefined; + void (async () => { + try { + const { listen } = await import('@tauri-apps/api/event'); + const handle = await listen('capsule:state', (event) => { + if (cancelled) return; + setCapsuleState(event.payload.state); + }); + if (cancelled) { + handle(); + } else { + unlisten = handle; + } + } catch { + // browser dev mock + } + })(); + return () => { + cancelled = true; + unlisten?.(); + }; + }, [platformCaps?.supportsInAppDictation]); + + if (!platformCaps?.supportsInAppDictation) { + return null; + } + + const recording = capsuleState === 'recording'; + const processing = capsuleState === 'transcribing' || capsuleState === 'polishing'; + const statusLabel = recording + ? t('overview.inAppDictation.recording') + : processing + ? t('overview.inAppDictation.processing') + : t('overview.inAppDictation.idle'); + + const onToggle = async () => { + if (busy || processing) return; + setBusy(true); + try { + if (recording) { + await stopDictation(); + } else { + if (platformCaps?.platform === 'android') { + const current = await checkAndroidMicrophoneAccess(); + const status = current === 'granted' + ? current + : await requestAndroidMicrophoneAccess(); + if (status !== 'granted') return; + } + await startDictation(); + } + } catch (error) { + console.error('[overview] in-app dictation toggle failed', error); + } finally { + setBusy(false); + } + }; + + return ( + + +
    +
    + {t('overview.inAppDictation.title')} +
    +
    + {statusLabel} +
    +
    + {!mobile && {statusLabel}} +
    + ); +} diff --git a/openless-all/app/src/pages/QaPanel.tsx b/openless-all/app/src/pages/QaPanel.tsx index 184e2fc3..847581ac 100644 --- a/openless-all/app/src/pages/QaPanel.tsx +++ b/openless-all/app/src/pages/QaPanel.tsx @@ -12,8 +12,16 @@ import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; -import { getSettings, isTauri, qaWindowDismiss, qaWindowPin } from '../lib/ipc'; -import type { QaChatMessage, QaStatePayload, UserPreferences } from '../lib/types'; +import { + getPlatformCapabilities, + getSettings, + isTauri, + qaSubmitText, + qaToggleRecording, + qaWindowDismiss, + qaWindowPin, +} from '../lib/ipc'; +import type { PlatformCapabilities, QaChatMessage, QaStatePayload, UserPreferences } from '../lib/types'; import { getHotkeyBindingLabel } from '../lib/hotkey'; import { renderQaMarkdown, renderQaPlainText } from '../lib/qaMarkdown'; @@ -21,17 +29,24 @@ const SELECTION_PREVIEW_MAX = 60; type Status = 'idle' | 'recording' | 'thinking' | 'error'; -export function QaPanel() { +interface QaPanelProps { + embedded?: boolean; + onRequestClose?: () => void; +} + +export function QaPanel({ embedded = false, onRequestClose }: QaPanelProps = {}) { const { t, i18n } = useTranslation(); const [messages, setMessages] = useState([]); const [status, setStatus] = useState('idle'); const [errorMsg, setErrorMsg] = useState(''); const [selectionPreview, setSelectionPreview] = useState(''); + const [composerText, setComposerText] = useState(''); const [pinned, setPinned] = useState(false); /** 流式 LLM 答案:answer_delta 累积、answer 事件来时清空(最终内容已落到 messages)。 */ const [streamingAnswer, setStreamingAnswer] = useState(''); /** 录音电平:0..1。后端每帧 33ms 通过 qa:level emit。详见 issue #162。 */ const [level, setLevel] = useState(0); + const [platformCaps, setPlatformCaps] = useState(null); /** 用户当前的录音热键 label(如 "右 Option" / "Right Alt")。issue #205: * 原版硬编码 "Option",Windows 用户没这个键,文案失真。读 prefs 后由 i18n * 插值动态显示,平台与用户配置都能跟上。 */ @@ -40,6 +55,10 @@ export function QaPanel() { ); const tRef = useRef(t); tRef.current = t; + const embeddedRef = useRef(embedded); + embeddedRef.current = embedded; + const onRequestCloseRef = useRef(onRequestClose); + onRequestCloseRef.current = onRequestClose; // ── 后端事件订阅(mount 时订阅一次,永不重订阅)────────────────── useEffect(() => { @@ -75,14 +94,18 @@ export function QaPanel() { // ASR 在 finalize、user message 还没 push 的过渡帧。提前切到 thinking // 视图避免 UI 卡 recording 几百 ms 反馈缺失。详见 issue #161。 setStatus('thinking'); - setSelectionPreview(''); + if (payload.selection_preview != null) { + setSelectionPreview(payload.selection_preview); + } setErrorMsg(''); setStreamingAnswer(''); setLevel(0); break; case 'thinking': setStatus('thinking'); - setSelectionPreview(''); + if (payload.selection_preview != null) { + setSelectionPreview(payload.selection_preview); + } setErrorMsg(''); setStreamingAnswer(''); setLevel(0); @@ -95,7 +118,6 @@ export function QaPanel() { break; case 'answer': setStatus('idle'); - setSelectionPreview(''); setErrorMsg(''); // messages 已被上面的 setMessages 落定,清掉流式 buffer 避免和最终气泡重影。 setStreamingAnswer(''); @@ -111,7 +133,13 @@ export function QaPanel() { }); const dismissHandle = await listen('qa:dismiss', () => { setPinned(false); - void qaWindowDismiss(); + setSelectionPreview(''); + setComposerText(''); + if (embeddedRef.current) { + onRequestCloseRef.current?.(); + } else { + void qaWindowDismiss(); + } }); // qa:level — 录音电平,节流 ~33ms/帧。详见 issue #162。 const levelHandle = await listen<{ level: number }>('qa:level', event => { @@ -153,11 +181,12 @@ export function QaPanel() { if (event.key === 'Escape') { event.preventDefault(); void qaWindowDismiss(); + onRequestClose?.(); } }; window.addEventListener('keydown', onKey, true); return () => window.removeEventListener('keydown', onKey, true); - }, []); + }, [onRequestClose]); // ── 读取用户当前的录音热键 label,给 i18n 插值用(issue #205)。 // QaPanel 跑在独立 webview(label="qa"),没有 HotkeySettingsContext @@ -182,6 +211,25 @@ export function QaPanel() { }; }, [i18n.language]); + useEffect(() => { + let cancelled = false; + void getPlatformCapabilities() + .then(caps => { + if (!cancelled) setPlatformCaps(caps); + }) + .catch(err => { + console.warn('[QaPanel] load platform capabilities failed', err); + }); + return () => { + cancelled = true; + }; + }, []); + + const mobileRecordButton = platformCaps?.supportsDesktopHotkey === false; + const recordControlLabel = mobileRecordButton + ? t('qa.mobileRecordLabel') + : recordHotkeyLabel; + const onTogglePin = () => { const next = !pinned; setPinned(next); @@ -190,6 +238,18 @@ export function QaPanel() { const onClose = () => { void qaWindowDismiss(); + onRequestClose?.(); + }; + + const onSubmitText = () => { + const text = composerText.trim(); + if (!text || status === 'thinking' || status === 'recording') return; + setComposerText(''); + void qaSubmitText(text).catch(error => { + console.error('[QaPanel] qa_submit_text failed', error); + setErrorMsg(error instanceof Error ? error.message : String(error)); + setStatus('error'); + }); }; // ── 自动滚动到底(新消息进来时)──────────────────────────────────── @@ -201,18 +261,18 @@ export function QaPanel() { }, [messages, status]); return ( -
    - +
    +
    {messages.length === 0 && status === 'idle' && ( - + )} {messages.length === 0 && status === 'recording' && ( )} @@ -222,7 +282,7 @@ export function QaPanel() { t={t} preview={selectionPreview} level={level} - recordHotkey={recordHotkeyLabel} + recordHotkey={recordControlLabel} /> )} {streamingAnswer && ( @@ -232,10 +292,27 @@ export function QaPanel() { )} {status === 'error' && ( - + )}
    - + {mobileRecordButton && ( + { + if (status === 'thinking') return; + void qaToggleRecording(); + }} + /> + )} + +
    ); } @@ -246,9 +323,10 @@ interface ToolbarProps { pinned: boolean; onTogglePin: () => void; onClose: () => void; + embedded?: boolean; } -function Toolbar({ pinned, onTogglePin, onClose }: ToolbarProps) { +function Toolbar({ pinned, onTogglePin, onClose, embedded = false }: ToolbarProps) { const { t } = useTranslation(); // 拖动 (issue #205): // - macOS: lib.rs::make_qa_window_draggable_macos 在 NSWindow 层把整窗口设 @@ -258,7 +336,7 @@ function Toolbar({ pinned, onTogglePin, onClose }: ToolbarProps) { // 两条路径并存不冲突;data-tauri-drag-region 放在 toolbar 的空白 spacer 上,IconBtn // 作为 button 子元素仍然正常 click。 return ( -
    +
    ['t']; + onClick: () => void; +}) { + const disabled = status === 'thinking'; + const recording = status === 'recording'; + return ( +
    + +
    + ); +} + +function QaComposer({ + value, + disabled, + t, + onChange, + onSubmit, +}: { + value: string; + disabled: boolean; + t: ReturnType['t']; + onChange: (value: string) => void; + onSubmit: () => void; +}) { + const canSubmit = value.trim().length > 0 && !disabled; + return ( +
    +

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 diff --git a/openless-all/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/openless-all/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png index 7e77d7df7f25932cddaa82531bba8331fd0403db..bf5c0c5f4baa1c53fbd33d87e068286dc640ff74 100644 GIT binary patch literal 18904 zcmc$FWm}tF&~5NQkzg(E-s0|3+zAvYP`tPncXumJao6JRZpGcDPzV;>_2fC%IqzS1 zKSaLdzIOJW*)wa+S`n(sG8kw?XaE2JLrzu_4EtRE?*&ALy+=?mLjeG}963pG4fjl_ zk6R0^RMxF--cx4ASr%NVQCa|ME=`2Ib2wUC{ti>c?|kd~an$JCma4j{HZ#X>U%!cK z;o)hi4d6Q?qvFs~|1$D70w&t=<+Yc0iI!KWxMUrPKqdiBm@3^x#%$l~fMD z9sCnwlOZ&xD$fXDr!2%YL=YpR3tlfsYWd3e0~hklA~8a(>5k?Pk{DQIy5fw1=$Zd8 zNC#VqP{e+KAI6cm)sSa4igx^nT&MxSbADr}rJdfzg)4fj4ggWXo>QIF7Q_BbX7~YH zViEA#d)^udLGQ&e?(HaKh>Xom>cUp7&P!5Cb5NUaFep4kD%$oWibvP@^N3P}hY`qT zYsxt2>95C7^y~;i{HREfdxM$t&RzMMsR)lgc)*|TJ>}7E@N}x`pR^<#FdE!TevGr1 z@_c*F&(b(Fe?&2c@6lj1#t>EcS8>KmsVIxXrxUkp7nypQ4xP%TPw4$~sFe1U!4#3{ zX#O<8(+R4bC`FWT(-go6Fq(WBIZws}Ckit1sZW(`eHYYSp3#a(G4oODJ2*iE0QkhlV)Fvl4`+`;6Du0N5O;oy_y8=)M+h4 z^%fn*kG#1_uTTXQQ7uaWW?k|{;pErGvA^%k4a~%0`-WK6~ zNbjf%wohh`*Tey!I3V~Z0x8`x?l!;0c%e93e=y{cmFAeS;JQV2aFnPD2lX))&HMK5 z7qyyNyQt0?#d~a`rH=A@6#G31rhAFHHAbxVPjvbv9qn@UiF7|xLv>G2xgu|`3zOJQ zq7@jIfRwRV{pX>+iKaf3y*Qt>=B*mP16XpCCR1TY%xE7I{p*}8LPL>BcNIV6PQiaN zi=LIVnWU>5{AWUWD+Y3WFM@|%PdwWWuFr7&(XT4+ahVN45qA);)YMHuuk)F)4-olj zC{HYm*u&?q0O(K!Vsj3;>(e*dqUQ+j9y`nF|FVA4596?lZy5V%O^+uRt~_hc<1xwe zJL}B`1zAw^YOTW_ums14*@JwLOfKAgT?~;4#Q&(tCJ`RJdrv(5FEaYyWiS2LNT5kY z@P2DN0So(vJ95%&td$}CEPk0e-haK* zrPVr5wj$CA;M|Q!hPNn!NB(=ZeSjCbFmrEo7wK?JBH_EKc<@j#D4SQ_%0A;nKEzS5 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 literal 29136 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 literal 12852 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 up4e6oON{xVyVM6f4@|TAbkSL4G`6 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