From 80a3792576b1d6f9bc2ab3a74faa771bf57580c2 Mon Sep 17 00:00:00 2001 From: Xavier Karma Date: Fri, 6 Nov 2026 11:51:44 +0530 Subject: [PATCH] chore: import vocalinux project Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept. --- .gitignore | 3 + AGENTS.md | 265 + CHANGELOG.md | 51 + CNAME | 1 + CODE_OF_CONDUCT.md | 127 + CONTRIBUTING.md | 289 + LICENSE | 661 + MANIFEST.in | 4 + README.md | 469 +- SECURITY.md | 89 + SUPPORT.md | 40 + codecov.yml | 18 + docs/ARCHITECTURE.md | 111 + docs/AUR.md | 116 + docs/CUSTOM_DICTIONARY_DESIGN.md | 125 + docs/DISTRO_COMPATIBILITY.md | 563 + docs/FLATPAK_REMOTE.md | 141 + docs/GATEWAY_EMBED.md | 77 + docs/HTTP_REMOTE.md | 282 + docs/INSTALL.md | 348 + docs/INSTALL_MANUAL.md | 194 + docs/MODEL_SELECTION.md | 61 + docs/README.md | 35 + docs/RELEASE_PROCESS.md | 551 + docs/SNAP.md | 81 + docs/TROUBLESHOOTING.md | 175 + docs/UPDATE.md | 486 + docs/USER_GUIDE.md | 371 + docs/agents/domain.md | 51 + docs/agents/issue-tracker.md | 40 + docs/agents/triage-labels.md | 15 + docs/benchmarks/README.md | 56 + docs/benchmarks/gpu_fallback.svg | 1356 ++ docs/benchmarks/settings.svg | 1370 ++ docs/benchmarks/silence_timeout.svg | 1372 ++ docs/benchmarks/typing.svg | 1360 ++ docs/images/microphone-indicator.gif | Bin 0 -> 347517 bytes flake.lock | 27 + flake.nix | 60 + install.d/desktop.sh | 164 + install.d/interactive.sh | 337 + install.d/models.sh | 451 + install.d/package_map.sh | 129 + install.d/system_dependencies.sh | 888 ++ install.sh | 3235 +++++ justfile | 317 + packaging/appimage/boot-test.sh | 158 + packaging/appimage/build.sh | 802 ++ packaging/appimage/container-build.sh | 38 + packaging/appimage/docker-build.sh | 40 + packaging/appimage/native-cache-id.sh | 49 + packaging/appimage/native-cache.sh | 137 + packaging/appimage/tool_checksums.txt | 47 + packaging/aur/build-test.sh | 230 + packaging/aur/vocalinux-bin/LICENSE | 661 + packaging/aur/vocalinux-bin/PKGBUILD | 61 + packaging/aur/vocalinux-bin/vocalinux.desktop | 13 + packaging/aur/vocalinux-bin/vocalinux.svg | 9 + packaging/aur/vocalinux/PKGBUILD | 82 + packaging/flatpak/README.md | 190 + .../flatpak/com.vocalinux.Vocalinux.desktop | 14 + .../com.vocalinux.Vocalinux.metainfo.xml | 194 + packaging/flatpak/com.vocalinux.Vocalinux.yml | 139 + packaging/flatpak/index.html | 52 + .../flatpak/python3-build-dependencies.yaml | 11 + packaging/flatpak/python3-dependencies.yaml | 216 + .../intltool/intltool-0.51.json | 20 + .../intltool-perl5.26-regex-fixes.patch | 59 + ...001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch | 50 + ...0001-Make-introspection-configurable.patch | 43 + .../libayatana-appindicator-gtk3.json | 121 + packaging/native/build.sh | 220 + packaging/native/container-build.sh | 13 + packaging/native/docker-build.sh | 26 + packaging/native/nfpm.yaml | 114 + packaging/native/smoke-test.sh | 97 + packaging/native/tool_checksums.txt | 23 + packaging/nix/README.md | 81 + packaging/nix/home-manager.nix | 25 + packaging/nix/module.nix | 40 + packaging/nix/package.nix | 165 + packaging/nix/pywhispercpp.nix | 109 + pyproject.toml | 243 + requirements/appimage-tools.in | 10 + requirements/appimage-tools.txt | 44 + requirements/appimage.in | 10 + requirements/appimage.txt | 5 + requirements/dev.txt | 1080 ++ requirements/faster-whisper.txt | 722 + requirements/installer-build.txt | 109 + requirements/parakeet.txt | 539 + requirements/release-build.txt | 444 + requirements/runtime.txt | 481 + requirements/vad.txt | 526 + requirements/vosk.txt | 697 + requirements/whisper.in | 17 + requirements/whisper.txt | 785 ++ resources/icons/scalable/discord.svg | 4 + resources/icons/scalable/github.svg | 4 + resources/icons/scalable/mail.svg | 4 + resources/icons/scalable/platform-android.svg | 1 + resources/icons/scalable/platform-apple.svg | 1 + resources/icons/scalable/platform-home.svg | 4 + resources/icons/scalable/platform-linux.svg | 1 + resources/icons/scalable/platform-server.svg | 1 + resources/icons/scalable/platform-windows.svg | 1 + .../scalable/vocalinux-microphone-off.svg | 12 + .../scalable/vocalinux-microphone-process.svg | 12 + .../icons/scalable/vocalinux-microphone.svg | 12 + resources/icons/scalable/vocalinux.svg | 9 + resources/icons/scalable/x.svg | 4 + resources/promo/vocalinux-cover.png | Bin 0 -> 6795225 bytes resources/sounds/chirp_start.wav | Bin 0 -> 15920 bytes resources/sounds/chirp_stop.wav | Bin 0 -> 15920 bytes resources/sounds/drop_start.wav | Bin 0 -> 39734 bytes resources/sounds/drop_stop.wav | Bin 0 -> 44144 bytes resources/sounds/ember_start.wav | Bin 0 -> 44144 bytes resources/sounds/ember_stop.wav | Bin 0 -> 44144 bytes resources/sounds/error.wav | Bin 0 -> 61782 bytes resources/sounds/flick_start.wav | Bin 0 -> 28268 bytes resources/sounds/flick_stop.wav | Bin 0 -> 28268 bytes resources/sounds/glass_start.wav | Bin 0 -> 22812 bytes resources/sounds/glass_stop.wav | Bin 0 -> 24176 bytes resources/sounds/lift_start.wav | Bin 0 -> 52964 bytes resources/sounds/lift_stop.wav | Bin 0 -> 52964 bytes resources/sounds/scale_start.wav | Bin 0 -> 35324 bytes resources/sounds/scale_stop.wav | Bin 0 -> 35324 bytes resources/sounds/soft_start.wav | Bin 0 -> 7100 bytes resources/sounds/soft_stop.wav | Bin 0 -> 7982 bytes resources/sounds/start_recording.wav | Bin 0 -> 52964 bytes resources/sounds/step_start.wav | Bin 0 -> 23858 bytes resources/sounds/step_stop.wav | Bin 0 -> 23858 bytes resources/sounds/stop_recording.wav | Bin 0 -> 52964 bytes resources/sounds/voca_start.wav | Bin 0 -> 19448 bytes resources/sounds/voca_stop.wav | Bin 0 -> 19448 bytes scripts/build_reproducible_wheel.py | 331 + scripts/check-system-deps.sh | 210 + scripts/check_build_deps.py | 212 + scripts/check_exports.py | 131 + scripts/distro-package-map.yaml | 202 + scripts/generate-model-checksums.py | 377 + scripts/generate_distro_package_map.py | 246 + scripts/generate_sounds.py | 238 + scripts/install-remote-test.sh | 327 + scripts/install-test.sh | 194 + scripts/installer_requirements.py | 50 + scripts/prepare_aur_source.py | 172 + scripts/sync_flatpak_deps.py | 355 + scripts/test_remote_server.py | 219 + scripts/verify_release.py | 262 + snap/gui/vocalinux.desktop | 15 + snap/gui/vocalinux.png | Bin 0 -> 26912 bytes snap/local/alsa/asound.conf | 16 + snap/local/build.py | 165 + snap/local/check_runtime.py | 148 + snap/local/smoke-test.sh | 22 + snap/snapcraft.yaml | 163 + src/vocalinux/__init__.py | 20 + src/vocalinux/audio/__init__.py | 1 + src/vocalinux/audio/capture.py | 1207 ++ src/vocalinux/audio/errors.py | 5 + src/vocalinux/audio/pipewire.py | 572 + src/vocalinux/audio/playback_ducker.py | 816 ++ src/vocalinux/audio/pulse_sources.py | 316 + src/vocalinux/auto_pause_monitor.py | 255 + src/vocalinux/common_types.py | 284 + src/vocalinux/custom_dictionary.py | 734 + src/vocalinux/dbus_service.py | 208 + src/vocalinux/gateway_embed/__init__.py | 28 + src/vocalinux/gateway_embed/health.py | 58 + src/vocalinux/gateway_embed/manager.py | 445 + src/vocalinux/gateway_embed/pairing.py | 146 + src/vocalinux/gateway_embed/paths_embed.py | 66 + src/vocalinux/gateway_embed/preset.py | 49 + src/vocalinux/gateway_embed/runner.py | 483 + src/vocalinux/gateway_embed/runtime.py | 142 + src/vocalinux/gateway_embed/sandbox.py | 57 + src/vocalinux/gateway_embed/status.py | 51 + src/vocalinux/gateway_embed/urls.py | 184 + src/vocalinux/main.py | 1381 ++ src/vocalinux/model_keepalive.py | 185 + src/vocalinux/post_processor.py | 70 + src/vocalinux/resources/__init__.py | 1 + .../resources/icons/scalable/discord.svg | 4 + .../resources/icons/scalable/github.svg | 4 + .../resources/icons/scalable/mail.svg | 4 + .../icons/scalable/platform-android.svg | 1 + .../icons/scalable/platform-apple.svg | 1 + .../icons/scalable/platform-home.svg | 4 + .../icons/scalable/platform-linux.svg | 1 + .../icons/scalable/platform-server.svg | 1 + .../icons/scalable/platform-windows.svg | 1 + .../scalable/vocalinux-microphone-off.svg | 12 + .../scalable/vocalinux-microphone-process.svg | 12 + .../icons/scalable/vocalinux-microphone.svg | 12 + .../resources/icons/scalable/vocalinux.svg | 9 + src/vocalinux/resources/icons/scalable/x.svg | 4 + .../resources/sounds/chirp_start.wav | Bin 0 -> 15920 bytes src/vocalinux/resources/sounds/chirp_stop.wav | Bin 0 -> 15920 bytes src/vocalinux/resources/sounds/drop_start.wav | Bin 0 -> 39734 bytes src/vocalinux/resources/sounds/drop_stop.wav | Bin 0 -> 44144 bytes .../resources/sounds/ember_start.wav | Bin 0 -> 44144 bytes src/vocalinux/resources/sounds/ember_stop.wav | Bin 0 -> 44144 bytes src/vocalinux/resources/sounds/error.wav | Bin 0 -> 61782 bytes .../resources/sounds/flick_start.wav | Bin 0 -> 28268 bytes src/vocalinux/resources/sounds/flick_stop.wav | Bin 0 -> 28268 bytes .../resources/sounds/glass_start.wav | Bin 0 -> 22812 bytes src/vocalinux/resources/sounds/glass_stop.wav | Bin 0 -> 24176 bytes src/vocalinux/resources/sounds/lift_start.wav | Bin 0 -> 52964 bytes src/vocalinux/resources/sounds/lift_stop.wav | Bin 0 -> 52964 bytes .../resources/sounds/scale_start.wav | Bin 0 -> 35324 bytes src/vocalinux/resources/sounds/scale_stop.wav | Bin 0 -> 35324 bytes src/vocalinux/resources/sounds/soft_start.wav | Bin 0 -> 7100 bytes src/vocalinux/resources/sounds/soft_stop.wav | Bin 0 -> 7982 bytes .../resources/sounds/start_recording.wav | Bin 0 -> 52964 bytes src/vocalinux/resources/sounds/step_start.wav | Bin 0 -> 23858 bytes src/vocalinux/resources/sounds/step_stop.wav | Bin 0 -> 23858 bytes .../resources/sounds/stop_recording.wav | Bin 0 -> 52964 bytes src/vocalinux/resources/sounds/voca_start.wav | Bin 0 -> 19448 bytes src/vocalinux/resources/sounds/voca_stop.wav | Bin 0 -> 19448 bytes src/vocalinux/single_instance.py | 100 + src/vocalinux/speech_recognition/__init__.py | 15 + .../speech_recognition/command_phrases.py | 213 + .../speech_recognition/command_processor.py | 406 + src/vocalinux/speech_recognition/data/LICENSE | 25 + .../speech_recognition/data/silero_vad.onnx | Bin 0 -> 2327524 bytes .../speech_recognition/diarization.py | 210 + .../speech_recognition/engines/__init__.py | 69 + .../engines/faster_whisper_engine.py | 187 + .../speech_recognition/recognition_manager.py | 5691 ++++++++ .../speech_recognition/silero_vad.py | 104 + src/vocalinux/suspend_handler.py | 126 + src/vocalinux/text_injection/__init__.py | 13 + .../text_injection/focused_window.py | 481 + src/vocalinux/text_injection/ibus_engine.py | 1882 +++ .../text_injection/remote_desktop_portal.py | 788 ++ src/vocalinux/text_injection/text_injector.py | 3489 +++++ src/vocalinux/ui/__init__.py | 11 + src/vocalinux/ui/action_handler.py | 110 + src/vocalinux/ui/audio_feedback.py | 331 + src/vocalinux/ui/autostart_manager.py | 103 + src/vocalinux/ui/config_manager.py | 1009 ++ src/vocalinux/ui/dictation_overlay.py | 609 + src/vocalinux/ui/dictation_pad.py | 741 + src/vocalinux/ui/first_run_dialog.py | 135 + .../ui/keyboard_backends/__init__.py | 237 + src/vocalinux/ui/keyboard_backends/base.py | 591 + .../ui/keyboard_backends/evdev_backend.py | 2276 ++++ .../ui/keyboard_backends/layout_key_map.py | 473 + .../ui/keyboard_backends/pynput_backend.py | 423 + src/vocalinux/ui/keyboard_shortcuts.py | 412 + src/vocalinux/ui/logging_dialog.py | 839 ++ src/vocalinux/ui/logging_manager.py | 307 + src/vocalinux/ui/notifications.py | 154 + src/vocalinux/ui/settings_about.py | 544 + src/vocalinux/ui/settings_dialog.py | 10137 ++++++++++++++ src/vocalinux/ui/settings_engines.py | 50 + src/vocalinux/ui/transcript_dialog.py | 287 + src/vocalinux/ui/transcription_history.py | 504 + src/vocalinux/ui/tray_indicator.py | 1983 +++ src/vocalinux/ui/tray_menu_model.py | 172 + src/vocalinux/utils/__init__.py | 7 + .../utils/faster_whisper_model_info.py | 276 + src/vocalinux/utils/gtk_color_scheme.py | 148 + src/vocalinux/utils/host_process.py | 57 + src/vocalinux/utils/model_checksums.py | 224 + src/vocalinux/utils/model_checksums.txt | 165 + src/vocalinux/utils/model_choice.py | 58 + src/vocalinux/utils/parakeet_model_info.py | 222 + src/vocalinux/utils/paths.py | 61 + src/vocalinux/utils/proxy.py | 363 + src/vocalinux/utils/pywhispercpp_loader.py | 126 + src/vocalinux/utils/resource_manager.py | 224 + src/vocalinux/utils/snap.py | 22 + src/vocalinux/utils/system_language.py | 427 + src/vocalinux/utils/update_checker.py | 348 + src/vocalinux/utils/update_monitor.py | 233 + src/vocalinux/utils/vosk_model_info.py | 385 + src/vocalinux/utils/whisper_model_info.py | 79 + src/vocalinux/utils/whispercpp_model_info.py | 728 + src/vocalinux/version.py | 12 + tests/conftest.py | 314 + tests/distro-testing-checklist.md | 314 + tests/test_action_handler.py | 141 + tests/test_appimage_native_cache.py | 373 + tests/test_appimage_packaging.py | 412 + tests/test_audio_feedback.py | 718 + tests/test_aur_gate.py | 175 + tests/test_aur_packaging.py | 483 + tests/test_auto_pause_monitor.py | 713 + tests/test_autostart_manager.py | 70 + tests/test_autostart_manager_ext.py | 190 + tests/test_bark.py | 55 + tests/test_cancel_transcription.py | 945 ++ tests/test_capitalize_sentences.py | 117 + tests/test_capture_cancellation.py | 362 + tests/test_capture_recovery.py | 387 + tests/test_capture_shutdown.py | 2021 +++ tests/test_check_build_deps.py | 250 + tests/test_clipboard_restore.py | 1028 ++ tests/test_command_processor.py | 627 + tests/test_common_types.py | 74 + tests/test_common_types_enums_protocols.py | 319 + tests/test_common_types_imports.py | 125 + tests/test_config_manager.py | 913 ++ tests/test_config_manager_ext.py | 307 + tests/test_configurable_hotkeys.py | 437 + tests/test_cross_distro_compatibility.py | 274 + tests/test_custom_dictionary.py | 1106 ++ tests/test_dbus_activation.py | 316 + tests/test_dependency_exports.py | 534 + tests/test_diarization.py | 352 + tests/test_dictation_overlay.py | 1239 ++ tests/test_dictation_pad.py | 1033 ++ tests/test_distro_package_map.py | 204 + tests/test_download_error_reporting.py | 153 + tests/test_download_finishing_feedback.py | 218 + tests/test_engine_availability.py | 43 + tests/test_engine_display_names.py | 18 + tests/test_evdev_backend_ext.py | 3595 +++++ tests/test_faster_whisper_engine.py | 583 + tests/test_first_run_dialog_ext.py | 316 + tests/test_flatpak_packaging.py | 384 + tests/test_focused_window.py | 397 + tests/test_follow_keyboard_layout.py | 353 + tests/test_gateway_embed.py | 978 ++ tests/test_gtk_color_scheme.py | 136 + tests/test_host_process.py | 96 + tests/test_ibus_active_engine_guard.py | 128 + tests/test_ibus_engine.py | 2808 ++++ tests/test_ibus_engine_core.py | 1521 +++ tests/test_ibus_engine_utils.py | 453 + tests/test_ibus_xkb_wayland.py | 484 + tests/test_injection_modifier_wait.py | 258 + tests/test_installer_cuda_diagnostics.py | 113 + ...t_installer_cuda_diagnostics_behavioral.py | 301 + tests/test_installer_engine_config.py | 238 + tests/test_installer_gate.py | 385 + tests/test_installer_modules.py | 73 + tests/test_installer_pinned_dependencies.py | 366 + tests/test_installer_python_selection.py | 354 + tests/test_installer_review_notes.py | 166 + tests/test_installer_tty_prompts.py | 156 + tests/test_installer_upgrade.py | 464 + tests/test_installer_vulkan_detection.py | 326 + tests/test_integration_branches.py | 1582 +++ tests/test_keyboard_backends_ext.py | 218 + tests/test_keyboard_base_ext.py | 367 + tests/test_keyboard_init_ext.py | 658 + tests/test_keyboard_shortcuts.py | 730 + tests/test_language_list_filtering.py | 272 + tests/test_language_shortcuts.py | 1086 ++ tests/test_layout_key_map.py | 603 + tests/test_logging_dialog.py | 342 + tests/test_logging_manager.py | 700 + tests/test_main.py | 2473 ++++ tests/test_main_args_deps.py | 649 + tests/test_main_edge_cases.py | 469 + tests/test_misc_edge_cases.py | 163 + tests/test_model_checksums.py | 711 + tests/test_model_deletion.py | 149 + tests/test_model_keepalive.py | 156 + tests/test_model_missing_offer.py | 794 ++ tests/test_native_packaging.py | 391 + tests/test_nightly_versioning.py | 108 + tests/test_notifications.py | 152 + tests/test_orukeet.py | 63 + tests/test_paste_shortcut.py | 296 + tests/test_paths.py | 71 + tests/test_pipewire_capture.py | 686 + tests/test_playback_ducker.py | 791 ++ tests/test_post_processor.py | 111 + tests/test_prepare_aur_source.py | 265 + tests/test_proxy.py | 289 + tests/test_pulse_sources.py | 735 + tests/test_pynput_backend_ext.py | 571 + tests/test_python_version_policy.py | 128 + tests/test_recognition_manager.py | 1379 ++ tests/test_recognition_manager_advanced.py | 1296 ++ tests/test_recognition_manager_core.py | 1004 ++ .../test_recognition_manager_device_config.py | 2651 ++++ ...est_recognition_manager_download_buffer.py | 1354 ++ tests/test_recognition_manager_downloads.py | 1499 ++ tests/test_recognition_manager_helpers.py | 542 + tests/test_recognition_manager_init.py | 370 + tests/test_recognition_manager_remote_api.py | 850 ++ tests/test_recognition_manager_silero.py | 539 + tests/test_recommendation_is_actionable.py | 135 + tests/test_reconfigure_concurrency.py | 559 + tests/test_reconfigure_model_handoff.py | 292 + tests/test_reconfigure_worker_handoff.py | 190 + tests/test_record_during_reload.py | 305 + tests/test_release_integrity.py | 347 + tests/test_remote_desktop_portal.py | 1172 ++ tests/test_reproducible_wheel.py | 375 + tests/test_resource_manager.py | 105 + tests/test_resource_manager_ext.py | 189 + tests/test_settings_dialog.py | 1877 +++ tests/test_settings_mode_change.py | 72 + tests/test_settings_model_state.py | 1911 +++ tests/test_settings_shortcuts.py | 391 + tests/test_settings_test_dictation.py | 831 ++ tests/test_shared_config_manager.py | 98 + tests/test_shortcut_capture.py | 543 + tests/test_silero_vad.py | 243 + tests/test_simple_model_settings.py | 796 ++ tests/test_single_instance_ext.py | 201 + tests/test_snap_build.py | 497 + tests/test_snap_packaging.py | 234 + tests/test_sound_effects_sensitivity.py | 97 + tests/test_speech_recognition.py | 582 + tests/test_speech_recognition_init.py | 67 + tests/test_start_stop_race.py | 159 + tests/test_suspend_handler.py | 863 ++ tests/test_sync_flatpak_deps.py | 79 + tests/test_system_default_capture.py | 223 + tests/test_system_language.py | 359 + tests/test_text_injector.py | 3859 ++++++ tests/test_text_injector_ext.py | 2024 +++ tests/test_transcription_history.py | 517 + tests/test_tray_indicator.py | 1586 +++ tests/test_tray_indicator_ext.py | 718 + tests/test_tray_menu_model.py | 302 + tests/test_uninstall_cwd_safety.py | 425 + tests/test_unused_downloads_height.py | 326 + tests/test_update_checker.py | 429 + tests/test_update_monitor.py | 270 + tests/test_verify_release.py | 327 + tests/test_version.py | 64 + tests/test_vosk_memory_guard.py | 201 + tests/test_whisper_model_info.py | 169 + tests/test_whisper_support.py | 175 + tests/test_whispercpp_model_info_ext.py | 1356 ++ tests/test_whispercpp_variant_resolution.py | 391 + tests/test_window_type_hints.py | 51 + tests/test_workflow_action_pins.py | 84 + tests/test_xkb_layout.py | 156 + uninstall.sh | 625 + uv.lock | 3562 +++++ vocalinux.desktop | 13 + web/.browserslistrc | 12 + web/.eslintrc.json | 8 + web/.swcrc | 27 + web/AGENTS.md | 50 + web/DESIGN.md | 162 + web/Dockerfile | 39 + web/PRODUCT.md | 83 + web/README.md | 90 + web/components.json | 21 + web/jest.config.ts | 25 + web/jest.setup.ts | 18 + web/next-env.d.ts | 6 + web/next.config.js | 101 + web/package-lock.json | 11375 ++++++++++++++++ web/package.json | 65 + web/postcss.config.js | 7 + web/prettier.config.js | 6 + .../.well-known/org.flathub.VerifiedApps.txt | 0 web/public/_headers | 53 + web/public/apple-touch-icon.png | Bin 0 -> 14194 bytes web/public/brand/paper-dots.svg | 5 + web/public/brand/platforms/android.svg | 1 + web/public/brand/platforms/apple.svg | 1 + web/public/brand/platforms/linux.svg | 1 + web/public/brand/platforms/server.svg | 1 + web/public/brand/platforms/windows.svg | 1 + web/public/brand/social/discord.svg | 4 + web/public/brand/social/github.svg | 4 + web/public/brand/social/mail.svg | 4 + web/public/brand/social/x.svg | 4 + web/public/brand/tux-classic.svg | 438 + web/public/brand/tux.md | 16 + web/public/brand/tux.svg | 20 + web/public/brand/vocalinux-mark-circle.svg | 9 + web/public/favicon-16x16.png | Bin 0 -> 788 bytes web/public/favicon-32x32.png | Bin 0 -> 1723 bytes web/public/favicon-96x96.png | Bin 0 -> 6102 bytes web/public/favicon.ico | Bin 0 -> 5430 bytes web/public/icon-192x192.png | Bin 0 -> 12705 bytes web/public/icon-512x512.png | Bin 0 -> 39431 bytes web/public/og-image.png | Bin 0 -> 83784 bytes web/public/og-image.webp | Bin 0 -> 31566 bytes web/public/robots.txt | 15 + web/public/screenshots/00-transcription.png | Bin 0 -> 942530 bytes web/public/screenshots/02-system-tray.png | Bin 0 -> 22828 bytes web/public/screenshots/03-log-viewer.png | Bin 0 -> 495814 bytes web/public/screenshots/05-about-view.png | Bin 0 -> 233621 bytes .../06-dictation-overlay-listening.png | Bin 0 -> 4706 bytes .../07-dictation-overlay-processing.png | Bin 0 -> 2181 bytes .../screenshots/dark/00-transcription.png | Bin 0 -> 936365 bytes .../screenshots/dark/02-system-tray.png | Bin 0 -> 23288 bytes web/public/screenshots/dark/03-log-viewer.png | Bin 0 -> 483503 bytes web/public/screenshots/dark/05-about-view.png | Bin 0 -> 228079 bytes .../screenshots/dark/settings-advanced.png | Bin 0 -> 234863 bytes .../screenshots/dark/settings-audio.png | Bin 0 -> 172367 bytes .../screenshots/dark/settings-general.png | Bin 0 -> 164712 bytes .../screenshots/dark/settings-performance.png | Bin 0 -> 280349 bytes .../screenshots/dark/settings-recognition.png | Bin 0 -> 240784 bytes .../dark/settings-speech-engine.png | Bin 0 -> 226304 bytes web/public/screenshots/settings-advanced.png | Bin 0 -> 243902 bytes web/public/screenshots/settings-audio.png | Bin 0 -> 177070 bytes web/public/screenshots/settings-general.png | Bin 0 -> 167990 bytes .../screenshots/settings-performance.png | Bin 0 -> 287764 bytes .../screenshots/settings-recognition.png | Bin 0 -> 247242 bytes .../screenshots/settings-speech-engine.png | Bin 0 -> 248793 bytes web/public/site.webmanifest | 53 + web/public/sitemap.xml | 286 + web/public/vocalinux-192x192.png | Bin 0 -> 12705 bytes web/public/vocalinux-192x192.webp | Bin 0 -> 3838 bytes web/public/vocalinux-32x32.png | Bin 0 -> 1723 bytes web/public/vocalinux-32x32.webp | Bin 0 -> 648 bytes web/public/vocalinux-512x512.png | Bin 0 -> 39431 bytes web/public/vocalinux-512x512.webp | Bin 0 -> 10870 bytes web/public/vocalinux-64x64.png | Bin 0 -> 3827 bytes web/public/vocalinux-64x64.webp | Bin 0 -> 1242 bytes web/public/vocalinux-96x96.png | Bin 0 -> 6102 bytes web/public/vocalinux-96x96.webp | Bin 0 -> 1890 bytes web/public/vocalinux-microphone-off.svg | 12 + web/public/vocalinux-microphone-process.svg | 12 + web/public/vocalinux-microphone.svg | 12 + web/public/vocalinux.png | Bin 0 -> 39431 bytes web/public/vocalinux.svg | 11 + web/public/vocalinux.webp | Bin 0 -> 10870 bytes web/scripts/render-social-assets.mjs | 163 + web/scripts/viewport-qa.mjs | 175 + web/src/app/__tests__/headers.test.ts | 99 + web/src/app/__tests__/lazy-loading.test.tsx | 114 + web/src/app/__tests__/next.config.test.ts | 87 + .../app/__tests__/screenshots-page.test.ts | 109 + web/src/app/__tests__/seo-assets.test.ts | 106 + web/src/app/__tests__/viewport-layout.test.ts | 159 + web/src/app/advanced-settings/page.tsx | 245 + web/src/app/alternatives/page.tsx | 307 + web/src/app/autostart/page.tsx | 315 + web/src/app/changelog/page.tsx | 688 + web/src/app/compare/page.tsx | 356 + web/src/app/custom-dictionary/page.tsx | 451 + web/src/app/desktop-reliability/page.tsx | 302 + web/src/app/dictation-pad/page.tsx | 432 + web/src/app/faq/page.tsx | 255 + web/src/app/faster-whisper/page.tsx | 617 + web/src/app/flatpak/page.tsx | 482 + web/src/app/for-developers/page.tsx | 300 + web/src/app/global-shortcuts/page.tsx | 460 + web/src/app/gnome-kde/page.tsx | 259 + web/src/app/gpu-acceleration/page.tsx | 358 + web/src/app/hyprland-sway/page.tsx | 513 + web/src/app/install/[distro]/page.tsx | 670 + web/src/app/install/nixos/page.tsx | 418 + web/src/app/install/page.tsx | 236 + web/src/app/languages/page.tsx | 488 + web/src/app/layout.tsx | 203 + web/src/app/local-gateway/page.tsx | 470 + web/src/app/multilingual-dictation/page.tsx | 495 + web/src/app/not-found.tsx | 88 + web/src/app/offline/page.tsx | 307 + web/src/app/open-source/page.tsx | 327 + web/src/app/page.tsx | 1103 ++ web/src/app/parakeet/page.tsx | 661 + web/src/app/pipewire/page.tsx | 446 + web/src/app/privacy/page.tsx | 297 + web/src/app/remote-api/page.tsx | 280 + web/src/app/rsi-prevention/page.tsx | 284 + web/src/app/screenshots/page.tsx | 196 + web/src/app/shortcuts/page.tsx | 276 + web/src/app/transcribe-audio-files/page.tsx | 407 + web/src/app/troubleshooting/page.tsx | 351 + web/src/app/use-cases/page.tsx | 225 + web/src/app/voice-activity-detection/page.tsx | 235 + web/src/app/voice-typing-vscode/page.tsx | 273 + web/src/app/vs-nerd-dictation/page.tsx | 218 + web/src/app/wayland/page.tsx | 323 + web/src/app/whisper-model-guide/page.tsx | 277 + web/src/app/writers/page.tsx | 262 + .../__tests__/not-found-extras.test.tsx | 35 + .../__tests__/optimized-image.test.tsx | 137 + .../__tests__/screenshot-gallery.test.tsx | 280 + web/src/components/code-block.tsx | 79 + web/src/components/icons.tsx | 33 + web/src/components/logo.tsx | 18 + web/src/components/not-found-extras.tsx | 76 + web/src/components/optimized-image.tsx | 78 + web/src/components/screenshot-gallery.tsx | 410 + web/src/components/seo-subpage-shell.tsx | 124 + web/src/components/site-chrome.tsx | 264 + web/src/components/terminal-block.tsx | 64 + web/src/components/ui/button.tsx | 31 + web/src/lib/__tests__/seo.test.ts | 23 + web/src/lib/api/util.ts | 594 + web/src/lib/seo.ts | 94 + web/src/lib/site-nav.ts | 110 + web/src/lib/utils.ts | 6 + web/src/styles/globals.css | 2023 +++ web/src/types/react-syntax-highlighter.d.ts | 24 + web/src/types/tailwind.d.ts | 4 + web/tailwind.config.ts | 122 + web/tsconfig.json | 59 + 597 files changed, 206764 insertions(+), 11 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 CNAME create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100644 codecov.yml create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/AUR.md create mode 100644 docs/CUSTOM_DICTIONARY_DESIGN.md create mode 100644 docs/DISTRO_COMPATIBILITY.md create mode 100644 docs/FLATPAK_REMOTE.md create mode 100644 docs/GATEWAY_EMBED.md create mode 100644 docs/HTTP_REMOTE.md create mode 100644 docs/INSTALL.md create mode 100644 docs/INSTALL_MANUAL.md create mode 100644 docs/MODEL_SELECTION.md create mode 100644 docs/README.md create mode 100644 docs/RELEASE_PROCESS.md create mode 100644 docs/SNAP.md create mode 100644 docs/TROUBLESHOOTING.md create mode 100644 docs/UPDATE.md create mode 100644 docs/USER_GUIDE.md create mode 100644 docs/agents/domain.md create mode 100644 docs/agents/issue-tracker.md create mode 100644 docs/agents/triage-labels.md create mode 100644 docs/benchmarks/README.md create mode 100644 docs/benchmarks/gpu_fallback.svg create mode 100644 docs/benchmarks/settings.svg create mode 100644 docs/benchmarks/silence_timeout.svg create mode 100644 docs/benchmarks/typing.svg create mode 100644 docs/images/microphone-indicator.gif create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 install.d/desktop.sh create mode 100644 install.d/interactive.sh create mode 100644 install.d/models.sh create mode 100644 install.d/package_map.sh create mode 100644 install.d/system_dependencies.sh create mode 100755 install.sh create mode 100644 justfile create mode 100755 packaging/appimage/boot-test.sh create mode 100755 packaging/appimage/build.sh create mode 100755 packaging/appimage/container-build.sh create mode 100755 packaging/appimage/docker-build.sh create mode 100755 packaging/appimage/native-cache-id.sh create mode 100755 packaging/appimage/native-cache.sh create mode 100644 packaging/appimage/tool_checksums.txt create mode 100644 packaging/aur/build-test.sh create mode 100644 packaging/aur/vocalinux-bin/LICENSE create mode 100644 packaging/aur/vocalinux-bin/PKGBUILD create mode 100644 packaging/aur/vocalinux-bin/vocalinux.desktop create mode 100644 packaging/aur/vocalinux-bin/vocalinux.svg create mode 100644 packaging/aur/vocalinux/PKGBUILD create mode 100644 packaging/flatpak/README.md create mode 100644 packaging/flatpak/com.vocalinux.Vocalinux.desktop create mode 100644 packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml create mode 100644 packaging/flatpak/com.vocalinux.Vocalinux.yml create mode 100644 packaging/flatpak/index.html create mode 100644 packaging/flatpak/python3-build-dependencies.yaml create mode 100644 packaging/flatpak/python3-dependencies.yaml create mode 100644 packaging/flatpak/shared-modules/intltool/intltool-0.51.json create mode 100644 packaging/flatpak/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch create mode 100644 packaging/flatpak/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch create mode 100644 packaging/flatpak/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch create mode 100644 packaging/flatpak/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json create mode 100755 packaging/native/build.sh create mode 100755 packaging/native/container-build.sh create mode 100755 packaging/native/docker-build.sh create mode 100644 packaging/native/nfpm.yaml create mode 100755 packaging/native/smoke-test.sh create mode 100644 packaging/native/tool_checksums.txt create mode 100644 packaging/nix/README.md create mode 100644 packaging/nix/home-manager.nix create mode 100644 packaging/nix/module.nix create mode 100644 packaging/nix/package.nix create mode 100644 packaging/nix/pywhispercpp.nix create mode 100644 pyproject.toml create mode 100644 requirements/appimage-tools.in create mode 100644 requirements/appimage-tools.txt create mode 100644 requirements/appimage.in create mode 100644 requirements/appimage.txt create mode 100644 requirements/dev.txt create mode 100644 requirements/faster-whisper.txt create mode 100644 requirements/installer-build.txt create mode 100644 requirements/parakeet.txt create mode 100644 requirements/release-build.txt create mode 100644 requirements/runtime.txt create mode 100644 requirements/vad.txt create mode 100644 requirements/vosk.txt create mode 100644 requirements/whisper.in create mode 100644 requirements/whisper.txt create mode 100644 resources/icons/scalable/discord.svg create mode 100644 resources/icons/scalable/github.svg create mode 100644 resources/icons/scalable/mail.svg create mode 100644 resources/icons/scalable/platform-android.svg create mode 100644 resources/icons/scalable/platform-apple.svg create mode 100644 resources/icons/scalable/platform-home.svg create mode 100644 resources/icons/scalable/platform-linux.svg create mode 100644 resources/icons/scalable/platform-server.svg create mode 100644 resources/icons/scalable/platform-windows.svg create mode 100644 resources/icons/scalable/vocalinux-microphone-off.svg create mode 100644 resources/icons/scalable/vocalinux-microphone-process.svg create mode 100644 resources/icons/scalable/vocalinux-microphone.svg create mode 100644 resources/icons/scalable/vocalinux.svg create mode 100644 resources/icons/scalable/x.svg create mode 100644 resources/promo/vocalinux-cover.png create mode 100644 resources/sounds/chirp_start.wav create mode 100644 resources/sounds/chirp_stop.wav create mode 100644 resources/sounds/drop_start.wav create mode 100644 resources/sounds/drop_stop.wav create mode 100644 resources/sounds/ember_start.wav create mode 100644 resources/sounds/ember_stop.wav create mode 100644 resources/sounds/error.wav create mode 100644 resources/sounds/flick_start.wav create mode 100644 resources/sounds/flick_stop.wav create mode 100644 resources/sounds/glass_start.wav create mode 100644 resources/sounds/glass_stop.wav create mode 100644 resources/sounds/lift_start.wav create mode 100644 resources/sounds/lift_stop.wav create mode 100644 resources/sounds/scale_start.wav create mode 100644 resources/sounds/scale_stop.wav create mode 100644 resources/sounds/soft_start.wav create mode 100644 resources/sounds/soft_stop.wav create mode 100644 resources/sounds/start_recording.wav create mode 100644 resources/sounds/step_start.wav create mode 100644 resources/sounds/step_stop.wav create mode 100644 resources/sounds/stop_recording.wav create mode 100644 resources/sounds/voca_start.wav create mode 100644 resources/sounds/voca_stop.wav create mode 100755 scripts/build_reproducible_wheel.py create mode 100755 scripts/check-system-deps.sh create mode 100755 scripts/check_build_deps.py create mode 100755 scripts/check_exports.py create mode 100755 scripts/distro-package-map.yaml create mode 100755 scripts/generate-model-checksums.py create mode 100755 scripts/generate_distro_package_map.py create mode 100755 scripts/generate_sounds.py create mode 100755 scripts/install-remote-test.sh create mode 100755 scripts/install-test.sh create mode 100755 scripts/installer_requirements.py create mode 100644 scripts/prepare_aur_source.py create mode 100755 scripts/sync_flatpak_deps.py create mode 100755 scripts/test_remote_server.py create mode 100755 scripts/verify_release.py create mode 100644 snap/gui/vocalinux.desktop create mode 100644 snap/gui/vocalinux.png create mode 100644 snap/local/alsa/asound.conf create mode 100644 snap/local/build.py create mode 100644 snap/local/check_runtime.py create mode 100644 snap/local/smoke-test.sh create mode 100644 snap/snapcraft.yaml create mode 100644 src/vocalinux/__init__.py create mode 100644 src/vocalinux/audio/__init__.py create mode 100644 src/vocalinux/audio/capture.py create mode 100644 src/vocalinux/audio/errors.py create mode 100644 src/vocalinux/audio/pipewire.py create mode 100644 src/vocalinux/audio/playback_ducker.py create mode 100644 src/vocalinux/audio/pulse_sources.py create mode 100644 src/vocalinux/auto_pause_monitor.py create mode 100644 src/vocalinux/common_types.py create mode 100644 src/vocalinux/custom_dictionary.py create mode 100644 src/vocalinux/dbus_service.py create mode 100644 src/vocalinux/gateway_embed/__init__.py create mode 100644 src/vocalinux/gateway_embed/health.py create mode 100644 src/vocalinux/gateway_embed/manager.py create mode 100644 src/vocalinux/gateway_embed/pairing.py create mode 100644 src/vocalinux/gateway_embed/paths_embed.py create mode 100644 src/vocalinux/gateway_embed/preset.py create mode 100644 src/vocalinux/gateway_embed/runner.py create mode 100644 src/vocalinux/gateway_embed/runtime.py create mode 100644 src/vocalinux/gateway_embed/sandbox.py create mode 100644 src/vocalinux/gateway_embed/status.py create mode 100644 src/vocalinux/gateway_embed/urls.py create mode 100644 src/vocalinux/main.py create mode 100644 src/vocalinux/model_keepalive.py create mode 100644 src/vocalinux/post_processor.py create mode 100644 src/vocalinux/resources/__init__.py create mode 100644 src/vocalinux/resources/icons/scalable/discord.svg create mode 100644 src/vocalinux/resources/icons/scalable/github.svg create mode 100644 src/vocalinux/resources/icons/scalable/mail.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-android.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-apple.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-home.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-linux.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-server.svg create mode 100644 src/vocalinux/resources/icons/scalable/platform-windows.svg create mode 100644 src/vocalinux/resources/icons/scalable/vocalinux-microphone-off.svg create mode 100644 src/vocalinux/resources/icons/scalable/vocalinux-microphone-process.svg create mode 100644 src/vocalinux/resources/icons/scalable/vocalinux-microphone.svg create mode 100644 src/vocalinux/resources/icons/scalable/vocalinux.svg create mode 100644 src/vocalinux/resources/icons/scalable/x.svg create mode 100644 src/vocalinux/resources/sounds/chirp_start.wav create mode 100644 src/vocalinux/resources/sounds/chirp_stop.wav create mode 100644 src/vocalinux/resources/sounds/drop_start.wav create mode 100644 src/vocalinux/resources/sounds/drop_stop.wav create mode 100644 src/vocalinux/resources/sounds/ember_start.wav create mode 100644 src/vocalinux/resources/sounds/ember_stop.wav create mode 100644 src/vocalinux/resources/sounds/error.wav create mode 100644 src/vocalinux/resources/sounds/flick_start.wav create mode 100644 src/vocalinux/resources/sounds/flick_stop.wav create mode 100644 src/vocalinux/resources/sounds/glass_start.wav create mode 100644 src/vocalinux/resources/sounds/glass_stop.wav create mode 100644 src/vocalinux/resources/sounds/lift_start.wav create mode 100644 src/vocalinux/resources/sounds/lift_stop.wav create mode 100644 src/vocalinux/resources/sounds/scale_start.wav create mode 100644 src/vocalinux/resources/sounds/scale_stop.wav create mode 100644 src/vocalinux/resources/sounds/soft_start.wav create mode 100644 src/vocalinux/resources/sounds/soft_stop.wav create mode 100644 src/vocalinux/resources/sounds/start_recording.wav create mode 100644 src/vocalinux/resources/sounds/step_start.wav create mode 100644 src/vocalinux/resources/sounds/step_stop.wav create mode 100644 src/vocalinux/resources/sounds/stop_recording.wav create mode 100644 src/vocalinux/resources/sounds/voca_start.wav create mode 100644 src/vocalinux/resources/sounds/voca_stop.wav create mode 100644 src/vocalinux/single_instance.py create mode 100644 src/vocalinux/speech_recognition/__init__.py create mode 100644 src/vocalinux/speech_recognition/command_phrases.py create mode 100644 src/vocalinux/speech_recognition/command_processor.py create mode 100644 src/vocalinux/speech_recognition/data/LICENSE create mode 100644 src/vocalinux/speech_recognition/data/silero_vad.onnx create mode 100644 src/vocalinux/speech_recognition/diarization.py create mode 100644 src/vocalinux/speech_recognition/engines/__init__.py create mode 100644 src/vocalinux/speech_recognition/engines/faster_whisper_engine.py create mode 100644 src/vocalinux/speech_recognition/recognition_manager.py create mode 100644 src/vocalinux/speech_recognition/silero_vad.py create mode 100644 src/vocalinux/suspend_handler.py create mode 100644 src/vocalinux/text_injection/__init__.py create mode 100644 src/vocalinux/text_injection/focused_window.py create mode 100644 src/vocalinux/text_injection/ibus_engine.py create mode 100644 src/vocalinux/text_injection/remote_desktop_portal.py create mode 100644 src/vocalinux/text_injection/text_injector.py create mode 100644 src/vocalinux/ui/__init__.py create mode 100644 src/vocalinux/ui/action_handler.py create mode 100644 src/vocalinux/ui/audio_feedback.py create mode 100644 src/vocalinux/ui/autostart_manager.py create mode 100644 src/vocalinux/ui/config_manager.py create mode 100644 src/vocalinux/ui/dictation_overlay.py create mode 100644 src/vocalinux/ui/dictation_pad.py create mode 100644 src/vocalinux/ui/first_run_dialog.py create mode 100644 src/vocalinux/ui/keyboard_backends/__init__.py create mode 100644 src/vocalinux/ui/keyboard_backends/base.py create mode 100644 src/vocalinux/ui/keyboard_backends/evdev_backend.py create mode 100644 src/vocalinux/ui/keyboard_backends/layout_key_map.py create mode 100644 src/vocalinux/ui/keyboard_backends/pynput_backend.py create mode 100644 src/vocalinux/ui/keyboard_shortcuts.py create mode 100644 src/vocalinux/ui/logging_dialog.py create mode 100644 src/vocalinux/ui/logging_manager.py create mode 100644 src/vocalinux/ui/notifications.py create mode 100644 src/vocalinux/ui/settings_about.py create mode 100644 src/vocalinux/ui/settings_dialog.py create mode 100644 src/vocalinux/ui/settings_engines.py create mode 100644 src/vocalinux/ui/transcript_dialog.py create mode 100644 src/vocalinux/ui/transcription_history.py create mode 100644 src/vocalinux/ui/tray_indicator.py create mode 100644 src/vocalinux/ui/tray_menu_model.py create mode 100644 src/vocalinux/utils/__init__.py create mode 100644 src/vocalinux/utils/faster_whisper_model_info.py create mode 100644 src/vocalinux/utils/gtk_color_scheme.py create mode 100644 src/vocalinux/utils/host_process.py create mode 100644 src/vocalinux/utils/model_checksums.py create mode 100644 src/vocalinux/utils/model_checksums.txt create mode 100644 src/vocalinux/utils/model_choice.py create mode 100644 src/vocalinux/utils/parakeet_model_info.py create mode 100644 src/vocalinux/utils/paths.py create mode 100644 src/vocalinux/utils/proxy.py create mode 100644 src/vocalinux/utils/pywhispercpp_loader.py create mode 100644 src/vocalinux/utils/resource_manager.py create mode 100644 src/vocalinux/utils/snap.py create mode 100644 src/vocalinux/utils/system_language.py create mode 100644 src/vocalinux/utils/update_checker.py create mode 100644 src/vocalinux/utils/update_monitor.py create mode 100644 src/vocalinux/utils/vosk_model_info.py create mode 100644 src/vocalinux/utils/whisper_model_info.py create mode 100644 src/vocalinux/utils/whispercpp_model_info.py create mode 100644 src/vocalinux/version.py create mode 100644 tests/conftest.py create mode 100644 tests/distro-testing-checklist.md create mode 100644 tests/test_action_handler.py create mode 100644 tests/test_appimage_native_cache.py create mode 100644 tests/test_appimage_packaging.py create mode 100644 tests/test_audio_feedback.py create mode 100644 tests/test_aur_gate.py create mode 100644 tests/test_aur_packaging.py create mode 100644 tests/test_auto_pause_monitor.py create mode 100644 tests/test_autostart_manager.py create mode 100644 tests/test_autostart_manager_ext.py create mode 100644 tests/test_bark.py create mode 100644 tests/test_cancel_transcription.py create mode 100644 tests/test_capitalize_sentences.py create mode 100644 tests/test_capture_cancellation.py create mode 100644 tests/test_capture_recovery.py create mode 100644 tests/test_capture_shutdown.py create mode 100644 tests/test_check_build_deps.py create mode 100644 tests/test_clipboard_restore.py create mode 100644 tests/test_command_processor.py create mode 100644 tests/test_common_types.py create mode 100644 tests/test_common_types_enums_protocols.py create mode 100644 tests/test_common_types_imports.py create mode 100644 tests/test_config_manager.py create mode 100644 tests/test_config_manager_ext.py create mode 100644 tests/test_configurable_hotkeys.py create mode 100644 tests/test_cross_distro_compatibility.py create mode 100644 tests/test_custom_dictionary.py create mode 100644 tests/test_dbus_activation.py create mode 100644 tests/test_dependency_exports.py create mode 100644 tests/test_diarization.py create mode 100644 tests/test_dictation_overlay.py create mode 100644 tests/test_dictation_pad.py create mode 100644 tests/test_distro_package_map.py create mode 100644 tests/test_download_error_reporting.py create mode 100644 tests/test_download_finishing_feedback.py create mode 100644 tests/test_engine_availability.py create mode 100644 tests/test_engine_display_names.py create mode 100644 tests/test_evdev_backend_ext.py create mode 100644 tests/test_faster_whisper_engine.py create mode 100644 tests/test_first_run_dialog_ext.py create mode 100644 tests/test_flatpak_packaging.py create mode 100644 tests/test_focused_window.py create mode 100644 tests/test_follow_keyboard_layout.py create mode 100644 tests/test_gateway_embed.py create mode 100644 tests/test_gtk_color_scheme.py create mode 100644 tests/test_host_process.py create mode 100644 tests/test_ibus_active_engine_guard.py create mode 100644 tests/test_ibus_engine.py create mode 100644 tests/test_ibus_engine_core.py create mode 100644 tests/test_ibus_engine_utils.py create mode 100644 tests/test_ibus_xkb_wayland.py create mode 100644 tests/test_injection_modifier_wait.py create mode 100644 tests/test_installer_cuda_diagnostics.py create mode 100644 tests/test_installer_cuda_diagnostics_behavioral.py create mode 100644 tests/test_installer_engine_config.py create mode 100644 tests/test_installer_gate.py create mode 100644 tests/test_installer_modules.py create mode 100644 tests/test_installer_pinned_dependencies.py create mode 100644 tests/test_installer_python_selection.py create mode 100644 tests/test_installer_review_notes.py create mode 100644 tests/test_installer_tty_prompts.py create mode 100644 tests/test_installer_upgrade.py create mode 100644 tests/test_installer_vulkan_detection.py create mode 100644 tests/test_integration_branches.py create mode 100644 tests/test_keyboard_backends_ext.py create mode 100644 tests/test_keyboard_base_ext.py create mode 100644 tests/test_keyboard_init_ext.py create mode 100644 tests/test_keyboard_shortcuts.py create mode 100644 tests/test_language_list_filtering.py create mode 100644 tests/test_language_shortcuts.py create mode 100644 tests/test_layout_key_map.py create mode 100644 tests/test_logging_dialog.py create mode 100644 tests/test_logging_manager.py create mode 100644 tests/test_main.py create mode 100644 tests/test_main_args_deps.py create mode 100644 tests/test_main_edge_cases.py create mode 100644 tests/test_misc_edge_cases.py create mode 100644 tests/test_model_checksums.py create mode 100644 tests/test_model_deletion.py create mode 100644 tests/test_model_keepalive.py create mode 100644 tests/test_model_missing_offer.py create mode 100644 tests/test_native_packaging.py create mode 100644 tests/test_nightly_versioning.py create mode 100644 tests/test_notifications.py create mode 100644 tests/test_orukeet.py create mode 100644 tests/test_paste_shortcut.py create mode 100644 tests/test_paths.py create mode 100644 tests/test_pipewire_capture.py create mode 100644 tests/test_playback_ducker.py create mode 100644 tests/test_post_processor.py create mode 100644 tests/test_prepare_aur_source.py create mode 100644 tests/test_proxy.py create mode 100644 tests/test_pulse_sources.py create mode 100644 tests/test_pynput_backend_ext.py create mode 100644 tests/test_python_version_policy.py create mode 100644 tests/test_recognition_manager.py create mode 100644 tests/test_recognition_manager_advanced.py create mode 100644 tests/test_recognition_manager_core.py create mode 100644 tests/test_recognition_manager_device_config.py create mode 100644 tests/test_recognition_manager_download_buffer.py create mode 100644 tests/test_recognition_manager_downloads.py create mode 100644 tests/test_recognition_manager_helpers.py create mode 100644 tests/test_recognition_manager_init.py create mode 100644 tests/test_recognition_manager_remote_api.py create mode 100644 tests/test_recognition_manager_silero.py create mode 100644 tests/test_recommendation_is_actionable.py create mode 100644 tests/test_reconfigure_concurrency.py create mode 100644 tests/test_reconfigure_model_handoff.py create mode 100644 tests/test_reconfigure_worker_handoff.py create mode 100644 tests/test_record_during_reload.py create mode 100644 tests/test_release_integrity.py create mode 100644 tests/test_remote_desktop_portal.py create mode 100644 tests/test_reproducible_wheel.py create mode 100644 tests/test_resource_manager.py create mode 100644 tests/test_resource_manager_ext.py create mode 100644 tests/test_settings_dialog.py create mode 100644 tests/test_settings_mode_change.py create mode 100644 tests/test_settings_model_state.py create mode 100644 tests/test_settings_shortcuts.py create mode 100644 tests/test_settings_test_dictation.py create mode 100644 tests/test_shared_config_manager.py create mode 100644 tests/test_shortcut_capture.py create mode 100644 tests/test_silero_vad.py create mode 100644 tests/test_simple_model_settings.py create mode 100644 tests/test_single_instance_ext.py create mode 100644 tests/test_snap_build.py create mode 100644 tests/test_snap_packaging.py create mode 100644 tests/test_sound_effects_sensitivity.py create mode 100644 tests/test_speech_recognition.py create mode 100644 tests/test_speech_recognition_init.py create mode 100644 tests/test_start_stop_race.py create mode 100644 tests/test_suspend_handler.py create mode 100644 tests/test_sync_flatpak_deps.py create mode 100644 tests/test_system_default_capture.py create mode 100644 tests/test_system_language.py create mode 100644 tests/test_text_injector.py create mode 100644 tests/test_text_injector_ext.py create mode 100644 tests/test_transcription_history.py create mode 100644 tests/test_tray_indicator.py create mode 100644 tests/test_tray_indicator_ext.py create mode 100644 tests/test_tray_menu_model.py create mode 100644 tests/test_uninstall_cwd_safety.py create mode 100644 tests/test_unused_downloads_height.py create mode 100644 tests/test_update_checker.py create mode 100644 tests/test_update_monitor.py create mode 100644 tests/test_verify_release.py create mode 100644 tests/test_version.py create mode 100644 tests/test_vosk_memory_guard.py create mode 100644 tests/test_whisper_model_info.py create mode 100644 tests/test_whisper_support.py create mode 100644 tests/test_whispercpp_model_info_ext.py create mode 100644 tests/test_whispercpp_variant_resolution.py create mode 100644 tests/test_window_type_hints.py create mode 100644 tests/test_workflow_action_pins.py create mode 100644 tests/test_xkb_layout.py create mode 100755 uninstall.sh create mode 100644 uv.lock create mode 100644 vocalinux.desktop create mode 100644 web/.browserslistrc create mode 100644 web/.eslintrc.json create mode 100644 web/.swcrc create mode 100644 web/AGENTS.md create mode 100644 web/DESIGN.md create mode 100644 web/Dockerfile create mode 100644 web/PRODUCT.md create mode 100644 web/README.md create mode 100644 web/components.json create mode 100644 web/jest.config.ts create mode 100644 web/jest.setup.ts create mode 100644 web/next-env.d.ts create mode 100644 web/next.config.js create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/postcss.config.js create mode 100644 web/prettier.config.js create mode 100644 web/public/.well-known/org.flathub.VerifiedApps.txt create mode 100644 web/public/_headers create mode 100644 web/public/apple-touch-icon.png create mode 100644 web/public/brand/paper-dots.svg create mode 100644 web/public/brand/platforms/android.svg create mode 100644 web/public/brand/platforms/apple.svg create mode 100644 web/public/brand/platforms/linux.svg create mode 100644 web/public/brand/platforms/server.svg create mode 100644 web/public/brand/platforms/windows.svg create mode 100644 web/public/brand/social/discord.svg create mode 100644 web/public/brand/social/github.svg create mode 100644 web/public/brand/social/mail.svg create mode 100644 web/public/brand/social/x.svg create mode 100644 web/public/brand/tux-classic.svg create mode 100644 web/public/brand/tux.md create mode 100644 web/public/brand/tux.svg create mode 100644 web/public/brand/vocalinux-mark-circle.svg create mode 100644 web/public/favicon-16x16.png create mode 100644 web/public/favicon-32x32.png create mode 100644 web/public/favicon-96x96.png create mode 100644 web/public/favicon.ico create mode 100644 web/public/icon-192x192.png create mode 100644 web/public/icon-512x512.png create mode 100644 web/public/og-image.png create mode 100644 web/public/og-image.webp create mode 100644 web/public/robots.txt create mode 100644 web/public/screenshots/00-transcription.png create mode 100644 web/public/screenshots/02-system-tray.png create mode 100644 web/public/screenshots/03-log-viewer.png create mode 100644 web/public/screenshots/05-about-view.png create mode 100644 web/public/screenshots/06-dictation-overlay-listening.png create mode 100644 web/public/screenshots/07-dictation-overlay-processing.png create mode 100644 web/public/screenshots/dark/00-transcription.png create mode 100644 web/public/screenshots/dark/02-system-tray.png create mode 100644 web/public/screenshots/dark/03-log-viewer.png create mode 100644 web/public/screenshots/dark/05-about-view.png create mode 100644 web/public/screenshots/dark/settings-advanced.png create mode 100644 web/public/screenshots/dark/settings-audio.png create mode 100644 web/public/screenshots/dark/settings-general.png create mode 100644 web/public/screenshots/dark/settings-performance.png create mode 100644 web/public/screenshots/dark/settings-recognition.png create mode 100644 web/public/screenshots/dark/settings-speech-engine.png create mode 100644 web/public/screenshots/settings-advanced.png create mode 100644 web/public/screenshots/settings-audio.png create mode 100644 web/public/screenshots/settings-general.png create mode 100644 web/public/screenshots/settings-performance.png create mode 100644 web/public/screenshots/settings-recognition.png create mode 100644 web/public/screenshots/settings-speech-engine.png create mode 100644 web/public/site.webmanifest create mode 100644 web/public/sitemap.xml create mode 100644 web/public/vocalinux-192x192.png create mode 100644 web/public/vocalinux-192x192.webp create mode 100644 web/public/vocalinux-32x32.png create mode 100644 web/public/vocalinux-32x32.webp create mode 100644 web/public/vocalinux-512x512.png create mode 100644 web/public/vocalinux-512x512.webp create mode 100644 web/public/vocalinux-64x64.png create mode 100644 web/public/vocalinux-64x64.webp create mode 100644 web/public/vocalinux-96x96.png create mode 100644 web/public/vocalinux-96x96.webp create mode 100644 web/public/vocalinux-microphone-off.svg create mode 100644 web/public/vocalinux-microphone-process.svg create mode 100644 web/public/vocalinux-microphone.svg create mode 100644 web/public/vocalinux.png create mode 100644 web/public/vocalinux.svg create mode 100644 web/public/vocalinux.webp create mode 100644 web/scripts/render-social-assets.mjs create mode 100644 web/scripts/viewport-qa.mjs create mode 100644 web/src/app/__tests__/headers.test.ts create mode 100644 web/src/app/__tests__/lazy-loading.test.tsx create mode 100644 web/src/app/__tests__/next.config.test.ts create mode 100644 web/src/app/__tests__/screenshots-page.test.ts create mode 100644 web/src/app/__tests__/seo-assets.test.ts create mode 100644 web/src/app/__tests__/viewport-layout.test.ts create mode 100644 web/src/app/advanced-settings/page.tsx create mode 100644 web/src/app/alternatives/page.tsx create mode 100644 web/src/app/autostart/page.tsx create mode 100644 web/src/app/changelog/page.tsx create mode 100644 web/src/app/compare/page.tsx create mode 100644 web/src/app/custom-dictionary/page.tsx create mode 100644 web/src/app/desktop-reliability/page.tsx create mode 100644 web/src/app/dictation-pad/page.tsx create mode 100644 web/src/app/faq/page.tsx create mode 100644 web/src/app/faster-whisper/page.tsx create mode 100644 web/src/app/flatpak/page.tsx create mode 100644 web/src/app/for-developers/page.tsx create mode 100644 web/src/app/global-shortcuts/page.tsx create mode 100644 web/src/app/gnome-kde/page.tsx create mode 100644 web/src/app/gpu-acceleration/page.tsx create mode 100644 web/src/app/hyprland-sway/page.tsx create mode 100644 web/src/app/install/[distro]/page.tsx create mode 100644 web/src/app/install/nixos/page.tsx create mode 100644 web/src/app/install/page.tsx create mode 100644 web/src/app/languages/page.tsx create mode 100644 web/src/app/layout.tsx create mode 100644 web/src/app/local-gateway/page.tsx create mode 100644 web/src/app/multilingual-dictation/page.tsx create mode 100644 web/src/app/not-found.tsx create mode 100644 web/src/app/offline/page.tsx create mode 100644 web/src/app/open-source/page.tsx create mode 100644 web/src/app/page.tsx create mode 100644 web/src/app/parakeet/page.tsx create mode 100644 web/src/app/pipewire/page.tsx create mode 100644 web/src/app/privacy/page.tsx create mode 100644 web/src/app/remote-api/page.tsx create mode 100644 web/src/app/rsi-prevention/page.tsx create mode 100644 web/src/app/screenshots/page.tsx create mode 100644 web/src/app/shortcuts/page.tsx create mode 100644 web/src/app/transcribe-audio-files/page.tsx create mode 100644 web/src/app/troubleshooting/page.tsx create mode 100644 web/src/app/use-cases/page.tsx create mode 100644 web/src/app/voice-activity-detection/page.tsx create mode 100644 web/src/app/voice-typing-vscode/page.tsx create mode 100644 web/src/app/vs-nerd-dictation/page.tsx create mode 100644 web/src/app/wayland/page.tsx create mode 100644 web/src/app/whisper-model-guide/page.tsx create mode 100644 web/src/app/writers/page.tsx create mode 100644 web/src/components/__tests__/not-found-extras.test.tsx create mode 100644 web/src/components/__tests__/optimized-image.test.tsx create mode 100644 web/src/components/__tests__/screenshot-gallery.test.tsx create mode 100644 web/src/components/code-block.tsx create mode 100644 web/src/components/icons.tsx create mode 100644 web/src/components/logo.tsx create mode 100644 web/src/components/not-found-extras.tsx create mode 100644 web/src/components/optimized-image.tsx create mode 100644 web/src/components/screenshot-gallery.tsx create mode 100644 web/src/components/seo-subpage-shell.tsx create mode 100644 web/src/components/site-chrome.tsx create mode 100644 web/src/components/terminal-block.tsx create mode 100644 web/src/components/ui/button.tsx create mode 100644 web/src/lib/__tests__/seo.test.ts create mode 100644 web/src/lib/api/util.ts create mode 100644 web/src/lib/seo.ts create mode 100644 web/src/lib/site-nav.ts create mode 100644 web/src/lib/utils.ts create mode 100644 web/src/styles/globals.css create mode 100644 web/src/types/react-syntax-highlighter.d.ts create mode 100644 web/src/types/tailwind.d.ts create mode 100644 web/tailwind.config.ts create mode 100644 web/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8dd9f22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Python bytecode caches +__pycache__/ +*.pyc diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..868c5c7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,265 @@ +# AGENTS.md — Vocalinux + +Voice dictation for Linux: GTK 3 tray app (Python) plus a Next.js marketing site in `web/`. Default speech engine is **whisper.cpp** (`pywhispercpp`); OpenAI Whisper, Vosk, and a user-configured remote API are optional. Do not invent features, user counts, or privacy claims. + +## Commit attribution + +Agents must not add themselves as commit co-authors or add `Co-authored-by` +trailers for agents. + +## Critical: git worktrees for every branch and PR + +Never create a branch, commit, or open a pull request in the primary checkout. Always use a linked git worktree so the main working tree stays on `main` and stays clean. Do not `git switch` / `git checkout` a feature branch in the primary directory, and do not leave it dirty. + +```bash +git fetch origin +git worktree add /tmp/vocalinux- -b / origin/main + +# All edits, commits, and `gh pr create` happen inside that worktree. + +git worktree remove /tmp/vocalinux- +git worktree prune +``` + +Rules: + +- One worktree per branch, one branch per PR +- Place worktrees **outside** the primary working tree (`/tmp/vocalinux-` or a sibling directory such as `../.worktrees/vocalinux-`) +- Never run two tasks in the same worktree +- Never commit directly to `main` +- Clean up the worktree after the PR is pushed + +## Toolchain + +| Piece | Current | +|---|---| +| Python | `>=3.11` (`requires-python` in `pyproject.toml`; CI: 3.11–3.14). Floor, classifiers, CI matrix and `install.sh` must agree — `tests/test_python_version_policy.py` | +| GTK | Runtime `venv/` uses distro `python3-gi` (PyGObject), never pip-install it; uv builds the locked PyGObject in the separate dev `.venv/` on supported distros | +| uv | `>=0.12,<0.14` (`[tool.uv]` in `pyproject.toml`). `uv.lock` is the source of truth; CI runs `uv sync --locked` / `uv run --locked`, which fails on drift | +| just | https://just.systems or distro package `just` | +| Format / lint / types | Black + isort (line length 100), flake8 (`E9,F63,F7,F82` only), mypy `src/` (targets 3.11 and runs as a CI lint gate). The tools live in the `lint` dependency group, not the `dev` extra, so CI can install them without building the project | +| Website | Next.js / TypeScript in `web/` — see `web/AGENTS.md` | + +Two virtualenvs, on purpose: + +- **`.venv/`** — uv's, for dev tooling. `just deps` checks native headers before syncing it (on Fedora 40+, install `glib2-devel`, `cairo-gobject-devel`, `gobject-introspection-devel`, `libffi-devel` and the PortAudio headers); every other Python `just` recipe uses `uv run --no-sync` so it does not prune extras that `just deps-all` installed. No activation needed. +- **`venv/`** — what `install.sh` builds for the app itself, from the *system* Python so distro `gi` is importable. + +Never run `install.sh` from an activated `.venv`: it drops an inherited `VIRTUAL_ENV` from `PATH` and picks `$SYSTEM_PYTHON` (default `/usr/bin/python3`) precisely because a venv built from uv's interpreter cannot see distro PyGObject. + +## Setup + +```bash +./install.sh --dev # system deps + venv + editable install + tests +# non-interactive / no TTY: +./install.sh --dev --auto +./install.sh --dev --auto --no-rebuild-whispercpp # skip cmake/Vulkan rebuild + +source venv/bin/activate +``` + +Manual venv (must see distro `gi`): + +```bash +uv venv --system-site-packages --python /usr/bin/python3 +source venv/bin/activate +# exclude pygobject from uv sync/export: +# --no-install-package pygobject / --no-emit-package pygobject +uv pip install -e ".[dev,vad]" +``` + +`install.sh` flags: `--engine=whisper_cpp|whisper|vosk|remote_api`, `--test`, `--skip-models`, `--venv-dir=PATH`. Default engine is `whisper_cpp`. + +## Commands + +```bash +just lint # flake8 (critical) + black --check + isort --check +just format # black + isort +just typecheck # mypy src/ (CI lint gate) +just test # pytest -v +just test-cov # pytest --cov=src --cov-report=html +just deps # sync .venv with dev+vad extras and the lint group +just deps-all # also whisper/vosk/docs; later recipes use --no-sync so they keep it +just lock # regenerate uv.lock, requirements/*.txt and the Flatpak dep manifest +just lock-check # fail if uv.lock is stale vs pyproject.toml +just distro-packages # regenerate install.d/package_map.sh from YAML +just distro-packages-check # fail if the generated package map is stale +just flatpak-deps # regenerate the Flatpak dep manifest from runtime + [vad] (needs PyPI) +just flatpak-deps-check # fail if that manifest is behind the export +just model-checksums # refresh pinned model digests after adding a model +just appimage # build the AppImage in its pinned base image (needs docker) +just appimage-boot fedora:42 # boot that AppImage in a distro container +just native-packages # build the .deb and .rpm in the pinned base image (needs docker) +just native-smoke fedora:42 # install the built package in a distro container +just aur-gate # build canonical sdist + unchanged AUR fixture on current Arch (needs uv/docker) +just install-gate debian:12 # run install.sh unattended in a distro container +just remote-install-gate debian:12 # remote curl|bash path end to end against HEAD (needs docker) +just verify-release # check a published release as published (needs gh) +just reproducible-wheel # independent locked wheel builds; stages verified dist/ (needs uv) +just pre-commit # pre-commit run --all-files +just run-debug # vocalinux --debug +just run-source-debug +``` + +```bash +pytest +pytest tests/test_command_processor.py +pytest tests/test_command_processor.py::TestCommandProcessor::test_initialization +pytest -m "not slow" +pytest -m "not integration" +python -m vocalinux.main --debug +python -m vocalinux.main --transcribe-file path/to/audio.wav # diarized file transcription +``` + +Website: `web/AGENTS.md`, `web/PRODUCT.md`, `web/DESIGN.md`. Do not duplicate site commands here. + +## Dependencies (uv) + +Use **uv 0.13.3** for `just lock` and `just export-check`, matching CI. Other versions in the supported contributor range can order equivalent platform markers differently; export verification compares files byte-for-byte. Update the CI pins and regenerate exports together when changing the generation version. + +`uv.lock` is authoritative. `just lock` regenerates it, the hash-pinned `requirements/*.txt` exports, and `packaging/flatpak/python3-dependencies.yaml`. The AppImage build installs from `runtime`/`vad` plus `appimage.txt`; `install.sh` consumes the runtime and selected extras with `--require-hashes --no-deps --no-build-isolation`, then installs the project with `--no-deps --no-build-isolation`. **Do not edit `requirements/*.txt` by hand**, nor the url/sha256 pairs in `packaging/flatpak/python3-dependencies.yaml`. Change `pyproject.toml` (or `requirements/whisper.in` for the Whisper engine, `requirements/appimage*.in` for the AppImage), run `just lock`, and commit the lock plus the exports with the manifest change. + +| Constraint | Rule | +|---|---| +| PyGObject | The runtime installer uses distro `python3-gi` via `--system-site-packages`; do not pip-install it into `venv/`. The dev `.venv/` instead builds the locked version with uv when glib 2.80+ and native headers are available. uv-managed interpreters do not see distro `gi` unless the venv is created with `--system-site-packages` | +| `[vosk]` extra | Wheel-only on PyPI (no sdist). Never part of a source-buildable lock. `install.sh --engine=vosk` installs it | +| Whisper CPU torch | `requirements/whisper.txt` is compiled from `requirements/whisper.in`. Pin `torch` to a `+cpu` local version — PyPI CUDA wheels win resolution regardless of index order. OpenAI Whisper does not use `torchaudio`, and the CPU index can lag torch | +| pywhispercpp | Pinned in `install.sh` as `PYWHISPERCPP_VERSION` (keep in sync with `uv.lock`) | +| Installer build tools | `[dependency-groups].installer-build` exports to `requirements/installer-build.txt`. Bootstrap uses hashed wheels on new and reused venvs; source builds use these tools without build isolation. Its setuptools `>=83` floor keeps the pinned installer tools patched; do not add it to `[build-system]` (Arch). Backend rebuilds select pywhispercpp's complete hash block from `runtime.txt` via `scripts/installer_requirements.py`, with `--no-deps` so they cannot replace the runtime | +| Release build tools | `[dependency-groups].release-build` exports to `requirements/release-build.txt`. `scripts/build_reproducible_wheel.py` builds in two fresh source trees/venvs with hashed tools and `--no-isolation`, compares wheel bytes, and stages only canonical distributions in `dist/`. These build-only venvs exclude system site-packages: host setuptools plugins must not influence locked builds, and packaging does not import GTK. Repeat runs clean recognized diagnostics and staged archives matching retained canonical digests; staged copies are deleted first so interrupted cleanup remains retryable. Unrelated/modified files are preserved and block cleanup. Never wipe all of `build/`. PR and release CI use Python 3.11 and pinned uv. Failed build outputs/logs stay under `build/reproducible-wheel/`, excluded from release artifacts | +| AUR source publication | Use the canonical release sdist, not a GitHub tag archive. `scripts/prepare_aur_source.py` (run with the locked `release-build` group) requires its published checksum and provenance from the exact release workflow/tag/commit before preparing a pinned PKGBUILD. Release CI builds/installs that unchanged recipe on current Arch; publication consumes only the gated artifact with `updpkgsums: false`. PR/local fixtures must explicitly use `--local-unattested` and are never publication inputs. `just aur-gate` keeps distributions, diagnostics and tooling in its own temporary directory, leaving shared packaging outputs and dev environments untouched; failures retain that directory and print its path. `vocalinux-bin` publication depends only on release creation and both AppImages, not the source gate or global checksums. Existing AUR provider stubs/pinned import smoke remain limited as documented in `docs/AUR.md` | +| Installer Whisper export | Compiled universally from Python 3.11, constrained by `runtime.txt` and `installer-build.txt` to keep shared pins consistent. Uses a `+cpu` torch pin and best-match index resolution so the CPU index's older copies of shared packages do not downgrade the runtime | +| Remote installer | After cloning, hand off to the tagged `install.sh` with the original arguments and remote venv path. The installer and its exports must come from the same revision, including when the tag predates the pinned installer | +| `[vad]` extra | `onnxruntime` for Silero VAD | +| One export per extra | Every extra in `pyproject.toml` needs a hash-pinned `requirements/.txt` (underscores become dashes), written by a `uv export` line in `just lock`. `install.sh` installs six of them, so an extra with no export is an install path with nothing pinned, which is how `[parakeet]`, `[faster_whisper]` and `[vosk]` reached users. `tests/test_dependency_exports.py` enumerates the extras and the installer's call sites rather than trusting the recipe to stay complete; `docs` is the one exemption and it carries its reason in `EXPORT_EXEMPT` | +| Exports match the lock | `just export-check` re-runs every `uv export` line in `just lock` and fails if a committed export is behind `uv.lock`. It reads those lines out of the recipe, so an export added there is checked without touching `scripts/check_exports.py`, and it runs under `UV_OFFLINE=1`: `uv export` reads the lock and nothing else. The lint job runs it, because an export named in the recipe and never regenerated is a snapshot, not a pin. `uv pip compile` targets are out of scope by design (they resolve against live indexes) | +| AppImage PyGObject | Pinned separately in `requirements/appimage.in`, and below 3.52: the AppImage bundles its own interpreter and builds PyGObject against the base image's girepository-1.0, while uv.lock's 3.56 needs girepository-2.0 (glib 2.80+) | +| AppImage build inputs | Base image, tooling, interpreter, shaderc and the Vulkan headers are pinned in `packaging/appimage/tool_checksums.txt`. Build with `just appimage` (docker) — building on the host ships the host's glibc, which is what kept the AppImage off Debian 12 | +| AppImage boot matrix | `packaging/appimage/boot-test.sh` runs the finished AppImage in distro containers, and the matrix in `unified-pipeline.yml` must keep distros both older and newer than the build image: the old ones prove the glibc floor, the new ones catch a bundle that breaks the host binaries it spawns. A distro added there needs its package recipe in the script — `tests/test_appimage_packaging.py` checks both | +| Native packages (.deb / .rpm) | Thin packages built by nfpm in the pinned Debian 12 image (`packaging/native/tool_checksums.txt` pins the image and the nfpm releases). Python, GTK and PyGObject come from the distro; only `pywhispercpp` and `pynput` are vendored under `/usr/lib/vocalinux/vendor` (unpackaged on Debian/Fedora). `packaging/native/smoke-test.sh` installs the package per-distro and runs `vocalinux --version` plus an import smoke — `tests/test_native_packaging.py` keeps the deps lists, vendored set and CI matrix consistent offline | +| Snap Python inputs | `snap/local/build.py` uses `requirements/installer-build.txt` in an isolated, build-only venv and installs `requirements/runtime.txt` with hashes, no dependency resolution or build isolation. The project installs without dependencies or index access; never strip PyGObject from `pyproject.toml`. GI comes from the GNOME extension. Keep portable CPU flags and force the pinned pywhispercpp sdist without a wheel cache. PR/release CI installs the real artifact via `snap/local/smoke-test.sh` before publication. Native Ubuntu/GNOME/Snapcraft inputs remain channel-controlled, not byte-for-byte reproducible — see `docs/SNAP.md` | +| Flatpak deps | `packaging/flatpak/python3-dependencies.yaml` is generated from `requirements/vad.txt` (runtime dependencies plus the `[vad]` extra) by `scripts/sync_flatpak_deps.py` (`just flatpak-deps`, which `just lock` runs itself). It resolves each artifact by the digest uv already recorded, so the Flatpak downloads the bytes in `uv.lock`; `onnxruntime` is pinned to CPython wheels for each Flatpak architecture and the GNOME 50 Python version. The project installs there with `pip3 install --no-deps`, so nothing inside the build evaluates `pyproject.toml` — `tests/test_flatpak_packaging.py` checks the invariant offline | +| Speech models | Verified against digests pinned in `src/vocalinux/utils/model_checksums.txt` before install, by both `install.sh` and the runtime downloaders. **Fails closed** — an unpinned model is refused. Regenerate with `just model-checksums` (never by hand); `tests/test_model_checksums.py` fails if it falls behind. whisper.cpp URLs use a pinned Hugging Face commit, never `main` | + +Optional extras: `vosk`, `whisper`, `vad`, `dev`. + +## Layout + +``` +src/vocalinux/ +├── main.py, version.py, common_types.py, dbus_service.py, post_processor.py +├── audio/ +│ ├── capture.py # PortAudio capture sources and device handling +│ ├── pipewire.py # PipeWire system-audio capture +│ └── playback_ducker.py # lower default sink while dictating +├── single_instance.py # $XDG_DATA_HOME/vocalinux/instance.lock +├── auto_pause_monitor.py # unload model while configured apps run +├── model_keepalive.py # idle unload +├── suspend_handler.py # logind PrepareForSleep +├── custom_dictionary.py # terms bias + transcript correction file contracts +├── gateway_embed/ # optional local VocaGateway lifecycle +├── speech_recognition/ +│ ├── recognition_manager.py # VAD, segmentation, and engine dispatch +│ ├── engines/ # engine implementations and availability +│ ├── command_processor.py # voice commands +│ ├── command_phrases.py # voice command phrases and localized forms +│ ├── diarization.py # TinyDiarize per-speaker file transcription (--transcribe-file / tray) +│ ├── silero_vad.py +│ └── data/ # bundled silero_vad.onnx +├── text_injection/ +│ ├── text_injector.py # DesktopEnvironment backend selection +│ ├── focused_window.py # focused application detection +│ ├── remote_desktop_portal.py # Wayland Remote Desktop Portal support +│ └── ibus_engine.py # Wayland IBus injection +├── ui/ +│ ├── tray_indicator.py, settings_dialog.py, settings_about.py +│ ├── settings_engines.py, first_run_dialog.py, dictation_overlay.py +│ ├── dictation_pad.py +│ ├── transcript_dialog.py # speaker-tagged transcript viewer/export +│ ├── config_manager.py, action_handler.py, audio_feedback.py +│ ├── autostart_manager.py, keyboard_shortcuts.py, notifications.py +│ ├── logging_dialog.py, logging_manager.py, transcription_history.py +│ └── keyboard_backends/ # pynput (X11), evdev (Wayland) +├── utils/ +│ ├── paths.py, resource_manager.py, host_process.py +│ ├── update_checker.py, update_monitor.py, gtk_color_scheme.py +│ ├── pywhispercpp_loader.py # bundled-lib preloader for source-built installs +│ └── whispercpp_model_info.py, vosk_model_info.py +└── resources/ # SVG icons + WAV cues (also repo resources/) +``` + +Also: `tests/`, `docs/`, `packaging/` (AppImage, AUR, Flatpak, native .deb/.rpm), `scripts/`, +`install.sh` (bootstrap and orchestration), `install.d/` (sourced installer modules), +`uninstall.sh`, `web/`. `scripts/distro-package-map.yaml` owns installer package +names; `install.d/package_map.sh` is generated by `just distro-packages`. + +| Task | Start here | +|---|---| +| Voice command | `speech_recognition/command_processor.py` | +| Engine / models | `speech_recognition/recognition_manager.py` | +| Text injection | `text_injection/text_injector.py`, `focused_window.py`, `remote_desktop_portal.py`, `ibus_engine.py` | +| Settings | `ui/config_manager.py`, `ui/settings_dialog.py` | +| Hotkeys | `ui/keyboard_shortcuts.py`, `ui/keyboard_backends/` | + +## Style + +- Type hints on all function signatures. `Protocol` interfaces live in `common_types.py` +- Imports: stdlib → third-party → local (`isort` black profile) +- Names: `PascalCase` classes, `snake_case` functions, `UPPER_SNAKE_CASE` constants, `_private` methods +- `logger = logging.getLogger(__name__)` per module +- Triple-quoted docstrings on modules, classes, and public functions +- Specific exceptions; log errors with context +- `subprocess` calls that run a **host** binary pass `env=host_env()` (`utils/host_process.py`): a host binary inheriting the AppImage's library paths dies on the wrong GLib. The one spawn of our own interpreter (`start_engine_process`) passes the environment untouched — it needs the bundle. `tests/test_host_process.py` walks the AST and fails on either mistake +- Tests: `tests/test_*.py`, `test_*` functions, `unittest.TestCase` or pytest; `pytest-mock` via `mocker` +- Markers: `@pytest.mark.slow`, `integration`, `audio` (hardware). Default timeout 10s (`pytest-timeout`) + +## Git and PRs + +Conventional Commits: `type(scope): short description` with optional body and `Fixes #123`. + +Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. + +``` +feat(commands): add "select all" voice command +fix(tray): resolve icon not updating on Wayland +docs(readme): update installation instructions +``` + +Branch prefixes: `feature/`, `fix/`, `docs/`, `refactor/`, `test/`, `release/` (e.g. `release/v0.7.0-beta`). + +- Never push to `main`. Never commit on `main` +- Every change goes through a PR (including docs) +- Wait for CI (`.github/workflows/unified-pipeline.yml`) before asking for merge +- Squash merge +- **Do not merge the PR yourself** +- Fill `.github/PULL_REQUEST_TEMPLATE.md` + +Releases: follow `docs/RELEASE_PROCESS.md`. Version source of truth is `src/vocalinux/version.py`; sync README, `docs/INSTALL.md`, `docs/UPDATE.md`, `web/src/app/page.tsx`, `web/package.json`. Prep on `release/vX.Y.Z-PHASE`; tag **after** merge. + +## Website + +Site-only work: `web/AGENTS.md` (commands, layout map). Product claims: `web/PRODUCT.md`. Visual system: `web/DESIGN.md`. Git/worktree/PR rules are this file — do not duplicate them under `web/`. + +## Runtime (any Linux dev machine) + +- Create runtime/dev venvs with `--system-site-packages`. Build-only venvs for `scripts/build_reproducible_wheel.py`, `just aur-gate` preparation and `snap/local/build.py` are the exception: exclude host packages to keep the locked tools isolated; the Snap runtime gets GI from its GNOME command chain. Never prune the developer environment to run a packaging gate +- Kill the app by PID, never `pkill -f vocalinux` (matches editors, shells, and cwd paths) +- Stale instance: `$XDG_DATA_HOME/vocalinux/instance.lock` (default `~/.local/share/vocalinux/instance.lock`) +- Default dictation shortcut: hold Right Alt (push-to-talk). Existing `~/.config/vocalinux/config.json` wins +- Headless speech smoke: `pywhispercpp.model.Model` + `CommandProcessor` (first run may download the tiny model) + +Update this file when commands, layout, or agent rules change. + +## Agent skills + +### Issue tracker + +Issues live in Gitea at git.bongbetic.com, managed with the tea CLI. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Default five-role vocabulary (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix). See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context: one GLOSSARY.md plus docs/adr/ at the repo root. See `docs/agents/domain.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..352102c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +Release history for Vocalinux. + +## Where to read notes + +| Source | Contents | +|--------|----------| +| [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases) | Canonical release notes per tag | +| [docs/UPDATE.md](docs/UPDATE.md) | Upgrade steps plus retained release notes for recent versions | +| [vocalinux.com/changelog](https://vocalinux.com/changelog) | Shorter website changelog | + +## Current stable + +**[v0.18.5](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.5)** (2026-10-27) + +Patch on the stable line: a stop that races a recording start can no longer leave the microphone recording with no way to halt it, and a stalled microphone read can no longer hang Stop forever, keyboards survive suspend and the GNOME lock screen again because the evdev grab is released when forwarding cannot run, Dictation Pad and Settings no longer bind to other windows or travel workspaces together on Wayland, Ctrl+mouse-wheel zoom works again, the installer gains an upgrade mode that keeps your existing configuration, paste-injection copies are marked sensitive on wl-clipboard 2.2+ so clipboard managers that honor the hint keep them out of history, and AUR source publication is gated on attested release sdists. + +See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0185) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.5). + +**[v0.18.4](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.4)** (2026-10-21) + +Patch on the stable line: long push-to-talk dictations are queued in segments instead of being cut off at the buffer cap, Settings never downloads or switches a model without an explicit confirmation and a cancelled download stays cancelled, Test Dictation keeps its own result while the Dictation Pad is open, the GPU picker follows ggml's device list and CPU runs use most of your cores, the recording hotkey cancels a transcription in progress, the Flatpak bundles onnxruntime for Silero VAD and uses the X11 listener on X11 sessions, Wayland injection fixes cover the RemoteDesktop portal, XWayland apps outside IBus and the first recording after launch, Parakeet installs work again, update checks can be turned off, and Slovenian is a supported language. + +See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0184) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.4). + +**[v0.18.3](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.3)** (2026-10-17) + +Patch on the stable line: "new line" and "new paragraph" voice commands now insert real line breaks standalone and mid-utterance, hotkeys work again on GNOME Shell X11 and quitting no longer collapses multi-layout keyboards, every packaged channel builds the bundled engine at the portable x86-64 baseline so older CPUs stop crashing, the Snap package drops the `uinput` plug the Store denied so revisions publish again, the Flatpak remote publish warns loudly instead of skipping silently, and Transcribe File's modal download dialog releases its grab so the transcript's buttons are clickable. + +See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0183) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.3). + +**[v0.18.2](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.2)** (2026-10-12) + +Patch on the stable line: the dictation overlay orb now pulses and ripples with live mic level, Nix users get a flake package with NixOS and Home Manager modules plus Vulkan GPU inference for whisper.cpp, held keyboards are left ungrabbed at startup so typing during launch cannot strand a key, the bundled engine no longer crashes on older CPUs, and the piped installer keeps working when it needs to prompt you. + +See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0182) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.2). + +**[v0.18.1](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.1)** (2026-10-08) + +Patch on the stable line: a Settings Proxy page for model downloads and update checks behind SOCKS5/HTTP proxies, opt-in JSONL transcript persistence so a failed injection no longer loses the text, .deb and .rpm release packages for Debian and Fedora, a slimmer tray menu (Start on Login and About now live only in Settings), and reliability fixes for the Dictation Pad on Wayland, evdev keyboard discovery, KDE Plasma 6 letter case, a dead Initial Prompt field in Settings, save dialogs that could double the `.txt` extension, and a Speech Model picker that stages changes until you confirm the download. + +See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0181) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.1). + +## Earlier versions + +Browse tags on [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases). Version history used for maintainer planning also appears in [docs/RELEASE_PROCESS.md](docs/RELEASE_PROCESS.md). + +## Format + +We follow [Semantic Versioning](https://semver.org/). Pre-releases use suffixes such as `-alpha`, `-beta`, and `-rc.N`. Maintainer release steps: [docs/RELEASE_PROCESS.md](docs/RELEASE_PROCESS.md). diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..25761b0 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +vocalinux.com \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8343bc7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Contributor Covenant Code of Conduct + +## Our pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +jatinkrmalik@gmail.com. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community impact:** Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence:** A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community impact:** A violation through a single incident or series of +actions. + +**Consequence:** A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary ban + +**Community impact:** A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence:** A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent ban + +**Community impact:** Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence:** A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org), version 2.1, +available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cf71026 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,289 @@ +# Contributing to Vocalinux + +Thanks for your interest in contributing. This guide covers setup, style, testing, and pull requests. + +## Code of conduct + +Participation is covered by our [Code of Conduct](CODE_OF_CONDUCT.md). Be respectful and constructive. + +## Ways to contribute + +- **Report bugs** - [Open an issue](https://github.com/VocaHQ/vocalinux/issues/new?template=bug_report.md) +- **Suggest features** - [Feature request](https://github.com/VocaHQ/vocalinux/issues/new?template=feature_request.md) or [Discussions](https://github.com/VocaHQ/vocalinux/discussions) +- **Improve documentation** - Fixes and clarity are always useful +- **Fix bugs or add features** - Check [open issues](https://github.com/VocaHQ/vocalinux/issues), especially [`good first issue`](https://github.com/VocaHQ/vocalinux/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) + +## Development setup + +### Automated (recommended) + +```bash +git clone https://github.com/YOUR-USERNAME/vocalinux.git +cd vocalinux +./install.sh --dev +``` + +This installs system dependencies, creates `venv/` with an editable install and dev +tools, and runs the test suite. See [The two environments](#the-two-environments). + +### Manual setup + +1. Fork and clone the repository. + +2. Install system dependencies (examples): + + **Ubuntu 24.04+:** + ```bash + sudo apt update + sudo apt install -y python3-pip python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libgirepository1.0-dev \ + build-essential pkg-config libcairo2-dev libffi-dev \ + python3-dev portaudio19-dev python3-venv xdotool + ``` + + **Debian 12 (installer environment only):** + ```bash + sudo apt install -y python3-pip python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository1.0-dev libcairo2-dev \ + build-essential pkg-config libffi-dev \ + python3-dev portaudio19-dev python3-venv xdotool + ``` + + **Debian 13+:** + ```bash + sudo apt install -y python3-pip python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libcairo2-dev \ + build-essential pkg-config libffi-dev \ + python3-dev portaudio19-dev python3-venv xdotool + ``` + + **Fedora 40+:** + ```bash + sudo dnf install -y python3-pip python3-gobject gtk3 ibus-devel \ + gobject-introspection-devel glib2-devel python3-devel portaudio-devel \ + cairo-devel cairo-gobject-devel libffi-devel python3-virtualenv pkg-config cmake \ + gcc-c++ make xdotool + ``` + + AppIndicator (system tray): + ```bash + # Older Ubuntu: + sudo apt install -y gir1.2-appindicator3-0.1 + # Debian 12+ or newer Ubuntu: + sudo apt install -y gir1.2-ayatanaappindicator3-0.1 + ``` + + **macOS (tooling only):** `brew install portaudio pkg-config`; use + `xcode-select --install` if a C compiler is missing. PyGObject/pycairo and their + header checks apply only on Linux, which is required to run the desktop app. + +3. Create the environment and install: + + ```bash + # Requires uv (https://docs.astral.sh/uv/); creates .venv/ with dev + vad extras + just deps + ``` + + Missing native build headers are reported with install commands before syncing. + For older Linux releases, use the installer environment described below. + +4. Run: + + ```bash + just run-source-debug + ``` + +5. Optional pre-commit hooks: + + ```bash + uv run --no-sync pre-commit install + ``` + + Hooks are optional. CI runs the same checks. + +### The two environments + +The repository uses two virtual environments on purpose. Do not merge them: + +| Directory | Created by | Python | Used for | +|-----------|-----------|--------|----------| +| `.venv/` | `just deps` (uv) | pinned in `.python-version` | dev tooling: pytest, black, mypy, `just` recipes | +| `venv/` | `./install.sh` | the system Python | running the installed app, which needs distro PyGObject | + +- On Linux, `.venv/` builds the locked PyGObject, requiring glib 2.80+ and the + headers above. On Debian 12 or Fedora 39 and older, use `./install.sh --dev`, + then `venv/bin/pytest` or `venv/bin/python -m vocalinux.main --debug` instead. +- `venv/` uses distro PyGObject via `--system-site-packages`. The installer ignores + an activated venv and rebuilds one created by the wrong interpreter. It uses + `/usr/bin/python3` unless overridden, e.g. `SYSTEM_PYTHON=/usr/bin/python3.12 ./install.sh`. + +`just appimage` bundles its own Python/PyGObject in the pinned Ubuntu Docker image. +For GI failures, check `Checking bundled PyGObject after linuxdeploy` and the smoke +test's path diagnostics; installing `python3-gobject` on the host won't fix the bundle. + +## Making changes + +### Branch naming + +```bash +git checkout main +git pull origin main +git checkout -b feature/your-feature-name +# or fix/, docs/, refactor/, test/ +``` + +Never push directly to `main`. Open a pull request for every change. + +### Code style + +| Tool | Role | +|------|------| +| Black | Formatting (line length 100) | +| isort | Import sorting (black profile) | +| flake8 | Linting | +| mypy | Type checking (`just typecheck`) | + +```bash +just format # black + isort +just lint # flake8 + black --check + isort --check +just typecheck # mypy src/ +``` + +### Project structure + +``` +vocalinux/ +├── src/vocalinux/ # Application +│ ├── main.py +│ ├── speech_recognition/ # Engines + command processor +│ ├── text_injection/ # X11 / Wayland injection +│ ├── ui/ # GTK tray, settings, config +│ └── utils/ +├── tests/ +├── resources/ # Icons and sounds +├── docs/ +├── install.d/ # Installer modules, including generated package data +├── packaging/ # AppImage, AUR, Flatpak +├── scripts/ # Maintenance tools and distro package-map source +├── snap/ # Snap recipe +└── web/ # Marketing site (Next.js) +``` + +| Task | Start here | +|------|------------| +| Voice command | `src/vocalinux/speech_recognition/command_processor.py` | +| UI | `src/vocalinux/ui/` | +| Recognition engines | `src/vocalinux/speech_recognition/recognition_manager.py` | +| Text injection | `src/vocalinux/text_injection/text_injector.py` | +| Settings / config | `src/vocalinux/ui/config_manager.py`, `settings_dialog.py` | +| Installer package names | `scripts/distro-package-map.yaml` | + +### Installer package map + +`scripts/distro-package-map.yaml` is the source of truth for package names, +alternatives, and conditional package probes used by `install.sh`. Do not edit +the generated `install.d/package_map.sh` directly. After changing the YAML, run: + +```bash +just distro-packages +just distro-packages-check +``` + +Commit both the YAML and generated shell module. Package-manager control flow +and installation policy remain handwritten in +`install.d/system_dependencies.sh`; user systems never parse the YAML. The +remote bootstrap's pre-checkout `git` installation is the deliberate exception +because the generated module is not available yet. + +## Testing + +```bash +just test +just test-cov +uv run --extra dev --extra vad --group lint pytest tests/test_command_processor.py +``` + +- Place tests in `tests/` as `test_*.py` +- Aim for solid coverage on new code (roughly 80%+) +- Use `pytest-mock` via the `mocker` fixture + +```python +def test_command_processor_new_line(mocker): + """Test that 'new line' command returns correct action.""" + processor = CommandProcessor() + result = processor.process("new line") + assert result.action == "new_line" +``` + +### Remote API test server + +```bash +python scripts/test_remote_server.py +python scripts/test_remote_server.py --port 9000 +python scripts/test_remote_server.py --delay 2 +``` + +Endpoints: + +- whisper.cpp style: `http://localhost:8080/inference` +- OpenAI-compatible: `http://localhost:8080/v1/audio/transcriptions` + +In Settings → Speech Model, select **Remote API**, set the server URL, choose the endpoint format, and use **Test Connection**. + +## Pull requests + +> Automated agents may append three robot emoji (🤖) to the PR title to opt into the agent fast-track merge path when that process is active. + +### Before you open a PR + +- [ ] Style checks pass (`just lint`, `just typecheck`) +- [ ] Tests pass (`just test`) +- [ ] Docs updated when behavior or install steps change +- [ ] Commits use [Conventional Commits](https://www.conventionalcommits.org/) + +``` +type(scope): short description + +Longer description if needed. + +Fixes #123 +``` + +Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` + +Examples: + +``` +feat(commands): add "select all" voice command +fix(tray): resolve icon not updating on Wayland +docs(readme): update installation instructions +``` + +### Submit + +1. Push your branch to your fork +2. Open a PR against `main` and fill out the template +3. Link related issues +4. Wait for CI; address review feedback +5. Maintainers squash-merge when approved + +## Releases + +Maintainers follow [docs/RELEASE_PROCESS.md](docs/RELEASE_PROCESS.md) (version files, docs, website, tag, automated publish). Do not tag releases from feature branches. + +## Community + +- [SUPPORT.md](SUPPORT.md) - where to get help +- [Discord](https://discord.gg/t6muquAJbm) - fastest place to talk with maintainers and other contributors +- [GitHub Discussions](https://github.com/VocaHQ/vocalinux/discussions) +- [GitHub Issues](https://github.com/VocaHQ/vocalinux/issues) +- [@vocahq on X](https://x.com/vocahq) + +## License + +Contributions are licensed under the [GNU Affero General Public License v3.0](LICENSE) +(AGPL-3.0), matching the other [VocaHQ](https://github.com/VocaHQ) distribution +projects ([VocaMac](https://github.com/VocaHQ/vocamac), +[VocaPhone](https://github.com/VocaHQ/vocaphone), +[VocaGateway](https://github.com/VocaHQ/vocagateway)). By opening a pull request, you +agree that your contribution may be distributed under that license. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..88fc4f3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include vocalinux.desktop +include resources/icons/scalable/*.svg +include resources/sounds/*.wav +recursive-include src/vocalinux/resources * diff --git a/README.md b/README.md index a3f50c0..acc93f6 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,463 @@ -# ci-template +
-Gitea template repo. Create a new repo with **Use this template** (tick "Git Content") to start with the Bongbetic CI -quality gates already in place: `.gitea/workflows/ci.yml`, `.jscpd.json`, `CI.md`. +Vocalinux -After creating the repo: +# Vocalinux -1. Add your language linter steps in the `lint` job (marker `# @@LINT_STEPS@@` in `ci.yml`). Snippets for Python, - Node and Rust live in the `ci-tools` repo under `snippets/`. -2. Add the secrets listed in `CI.md`. -3. After the first CI run, require `CI / security (pull_request)` and `CI / lint (pull_request)` on `main` - (`ci-tools/bin/new-repo-ci.sh --protect-only `, or Settings, Branches). +**Voice dictation for Linux** -For repos that already exist, use `ci-tools/bin/new-repo-ci.sh`, which opens a PR instead. +[![Ubuntu](https://img.shields.io/badge/Ubuntu-24.04+-E95420?logo=ubuntu&logoColor=white)](docs/DISTRO_COMPATIBILITY.md) +[![Debian](https://img.shields.io/badge/Debian-12+-A81D33?logo=debian&logoColor=white)](docs/DISTRO_COMPATIBILITY.md) +[![Fedora](https://img.shields.io/badge/Fedora-42+-51A2DA?logo=fedora&logoColor=white)](docs/DISTRO_COMPATIBILITY.md) +[![Arch](https://img.shields.io/badge/Arch-rolling-1793D1?logo=archlinux&logoColor=white)](docs/DISTRO_COMPATIBILITY.md) +[![openSUSE](https://img.shields.io/badge/openSUSE-Tumbleweed-73BA25?logo=opensuse&logoColor=white)](docs/DISTRO_COMPATIBILITY.md) -Delete this README in the new repo. +[![Privacy: on-device](https://img.shields.io/badge/privacy-on--device-success)](https://github.com/VocaHQ/vocalinux#privacy-and-security) +[![X11 & Wayland](https://img.shields.io/badge/display-X11%20%7C%20Wayland-lightgrey)](https://github.com/VocaHQ/vocalinux#features) +[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) +[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) +[![GitHub release](https://img.shields.io/github/v/release/VocaHQ/vocalinux)](https://github.com/VocaHQ/vocalinux/releases) + +[![AppImage](https://img.shields.io/badge/AppImage-x86__64%20%7C%20aarch64-2C5BB4)](https://github.com/VocaHQ/vocalinux/releases/latest) +[![.deb](https://img.shields.io/badge/.deb-Debian%20%7C%20Ubuntu-A81D33?logo=debian&logoColor=white)](https://github.com/VocaHQ/vocalinux/releases/latest) +[![.rpm](https://img.shields.io/badge/.rpm-Fedora-EE0000?logo=fedora&logoColor=white)](https://github.com/VocaHQ/vocalinux/releases/latest) +[![Flatpak](https://img.shields.io/badge/Flatpak-vocahq%20remote-4A90D9?logo=flatpak&logoColor=white)](#flatpak-any-distro) +[![Snap](https://img.shields.io/badge/Snap-edge-82BEA0?logo=snapcraft&logoColor=white)](https://snapcraft.io/vocalinux) +[![AUR](https://img.shields.io/aur/version/vocalinux)](https://aur.archlinux.org/packages/vocalinux) +[![PyPI](https://img.shields.io/pypi/v/vocalinux)](https://pypi.org/project/vocalinux/) +[![Nix flake](https://img.shields.io/badge/Nix-flake-5277C3?logo=nixos&logoColor=white)](packaging/nix/README.md) + +[![Discord](https://img.shields.io/discord/1538633755877580810?logo=discord&logoColor=white&label=Discord)](https://discord.gg/t6muquAJbm) +[![VocaHQ](https://img.shields.io/badge/VocaHQ-vocahq.com-1a7f4e)](https://vocahq.com) +[![Follow on X](https://img.shields.io/badge/Follow%20%40vocahq-000000?style=flat&logo=x&logoColor=white)](https://x.com/vocahq) + +[Website](https://vocalinux.com) · [Install](#install) · [Docs](#documentation) · [Releases](https://github.com/VocaHQ/vocalinux/releases) + +
+ +Vocalinux turns speech into typed text in whatever app has focus. It is a free, AGPL-3.0-licensed desktop app for X11 and Wayland. After you download a model, local engines (whisper.cpp by default, plus OpenAI Whisper, Faster Whisper, VOSK, and Parakeet) run speech-to-text on your machine. An optional remote HTTP API is off unless you configure it. + +No Voca account is required. Models download once. After that, speech-to-text stays on your machine. + +**Current release:** [v0.18.5](https://github.com/VocaHQ/vocalinux/releases/tag/v0.18.5). Patch: a stop that races a recording start can no longer leave the mic recording forever, keyboards stay alive through suspend and the GNOME lock screen, Ctrl+mouse-wheel zoom works again, Dictation Pad and Settings stop travelling workspaces together, paste-injection copies are marked sensitive for clipboard history on wl-clipboard 2.2+, and `install.sh` gains an upgrade mode that keeps your existing configuration. Details: [docs/UPDATE.md](docs/UPDATE.md). + +## Features + +- **On-device after model download**: Local engines; speech-to-text stays on your machine +- **X11 and Wayland**: Text injection via xdotool, IBus, wtype, ydotool, or clipboard fallback +- **Several engines**: whisper.cpp (default), OpenAI Whisper, Faster Whisper, VOSK, Parakeet, plus optional remote HTTP API or local VocaGateway from Settings (not on-device) +- **GPU acceleration**: Vulkan for AMD, Intel, and NVIDIA with whisper.cpp +- **Toggle or push-to-talk**: New installs default to hold Right Alt; existing configs keep their shortcut +- **System tray + settings**: Searchable sidebar, Speech Model simple setup with Advanced as an island, status icons, audio feedback +- **Start on login**: XDG autostart (desktop session, not a systemd service) +- **Packaging**: install script, `.deb` / `.rpm`, AppImage, AUR, PyPI, Snap (`--edge`), Flatpak (release bundles and local build; not on Flathub), Nix flake + +## Screenshots + +Vocalinux in action. Full gallery on the [website screenshots page](https://vocalinux.com/screenshots/). + +### Product + + + + + + + + + + +
+ Transcription in Action
+ Real-time voice-to-text transcription +
+ System Tray
+ System tray with listening indicator +
+ About View
+ About & Updates in Settings +
+ Log Viewer
+ Log viewer for debugging +
+ +### Settings + + + + + + + + + + + + + + + + +
+ Speech Engine settings
+ Speech Engine +
+ Recognition settings
+ Recognition +
+ Audio settings
+ Audio +
+ Performance settings
+ Performance +
+ General settings
+ General +
+ Advanced tuning and settings
+ Advanced +
+ Dictation overlay listening
+ Floating overlay while listening (green) +
+ Dictation overlay processing
+ Floating overlay while processing (amber) +
+ +## Install + +### Recommended (interactive installer) + +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +Prefer to review the script first: open `/tmp/vl.sh` before running it, or clone the repo and run `./install.sh` locally. + +The installer detects hardware, recommends an engine, downloads a default model (~74MB for whisper.cpp tiny), installs neural VAD when ONNX Runtime is available, and sets up desktop integration. Typical install time with whisper.cpp is about 1-2 minutes. + +| Engine | When to use | +|--------|-------------| +| **whisper.cpp** (default) | Best default; Vulkan GPU on AMD, Intel, and NVIDIA | +| **Whisper** (OpenAI) | PyTorch path; NVIDIA/CUDA | +| **Faster Whisper** | CPU-friendly Whisper via CTranslate2 / INT8 | +| **VOSK** | Low RAM / minimal footprint | +| **Parakeet** | CPU; NVIDIA NeMo ASR via sherpa-onnx; 25 European languages | +| **Remote API** | Offload to a server you configure | + +Non-interactive options: + +```bash +bash /tmp/vl.sh --auto # whisper.cpp defaults +bash /tmp/vl.sh --auto --engine=whisper # OpenAI Whisper +bash /tmp/vl.sh --auto --engine=faster_whisper # Faster Whisper (CPU) +bash /tmp/vl.sh --auto --engine=vosk # VOSK only +bash /tmp/vl.sh --auto --engine=parakeet # Parakeet (CPU) +``` + +For a specific release tag, see [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases) or `./install.sh --tag=v0.18.5`. + +### Arch Linux (AUR) + +```bash +yay -S vocalinux +``` + +See [docs/AUR.md](docs/AUR.md). + +### AppImage + +Download the `x86_64` or `aarch64` AppImage from [Releases](https://github.com/VocaHQ/vocalinux/releases), mark it executable, and run it. Built against glibc 2.35 (Debian 12+, Ubuntu 22.04+, Fedora 36+, Arch, Tumbleweed). Host text-injection tools (`xdotool` on X11; `wtype` / `ydotool` / clipboard tools on Wayland) are still required. Current AppImages rebuild whisper.cpp with Vulkan and use the host GPU driver. Prefer the installer when you want system deps, a CUDA build, and models set up automatically. + +### Distro packages (.deb / .rpm) + +Download the `.deb` (Debian 12+, Ubuntu 24.04+) or `.rpm` (Fedora) matching your CPU from [Releases](https://github.com/VocaHQ/vocalinux/releases) and install with `sudo apt install ./vocalinux_*.deb` or `sudo dnf install ./vocalinux-*.rpm`. Thin packages: Python and GTK come from the distro. No auto-update; CPU whisper.cpp. Details in [docs/INSTALL.md](docs/INSTALL.md). + +### Flatpak (any distro) + +Install and auto-update via the self-hosted VocaHQ remote (`flatpak update` picks up each release): + +```bash +flatpak install https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref +``` + +The `.flatpakref` adds the `vocahq` remote and resolves the GNOME runtime through Flathub. Manually instead: + +```bash +flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo +flatpak install vocahq com.vocalinux.Vocalinux +``` + +Or sideload a GitHub Release bundle (`Vocalinux--x86_64.flatpak` or `-aarch64.flatpak`) after the Flathub GNOME runtime is present — bundles do not auto-update: + +```bash +flatpak install --user ./Vocalinux--x86_64.flatpak +``` + +Local build: + +```bash +flatpak install flathub org.gnome.Platform//50 org.gnome.Sdk//50 +flatpak-builder --user --install --force-clean build-dir \ + packaging/flatpak/com.vocalinux.Vocalinux.yml +flatpak run com.vocalinux.Vocalinux +``` + +Ships whisper.cpp with Vulkan. It is **not on Flathub** (submission [flathub#9368](https://github.com/flathub/flathub/pull/9368) closed 2026-07-23 on policy grounds). Details: [packaging/flatpak/README.md](packaging/flatpak/README.md). + +### Snap (Ubuntu Snap Store) + +Listing: [snapcraft.io/vocalinux](https://snapcraft.io/vocalinux). `stable` is promoted from `candidate` after QA. Tagged releases also attach `vocalinux__amd64.snap` on GitHub. + +```bash +sudo snap install vocalinux --edge +sudo snap connect vocalinux:audio-record # if mic is not auto-connected +sudo snap connect vocalinux:raw-input # global keyboard shortcuts (evdev) +sudo snap connect vocalinux:hardware-observe # list keyboards (/proc/bus/input/devices) +``` + +Native Wayland typing uses the RemoteDesktop portal: the compositor asks once on first dictation, nothing to connect. The recipe ships **no `uinput` plug** and no ydotool; the Store denied the super-privileged grant, which held the 0.17.0+ revisions and left **v0.16.2 (rev 7)** on edge. On KDE Plasma, KWin's portal can mistype letter case ([KDE bug 489021](https://bugs.kde.org/show_bug.cgi?id=489021)); set Virtual Keyboard to "IBus Wayland" so dictation types correctly. + +GitHub `.snap` files through v0.18.2 declare `uinput`, so KDE users who want ydotool can sideload that build and connect the plug manually: + +```bash +sudo snap install --dangerous ./vocalinux_0.18.2_amd64.snap +sudo snap connect vocalinux:uinput # only in the v0.18.x sideload; the recipe no longer ships it +``` + +### Nix / NixOS (flake) + +```bash +nix run github:VocaHQ/vocalinux # try it +nix profile install github:VocaHQ/vocalinux +``` + +The flake also ships `nixosModules.vocalinux` and `homeManagerModules.vocalinux` for declarative installs (`programs.vocalinux.enable = true`). The bundled pywhispercpp builds with Vulkan GPU inference. Details: [packaging/nix/README.md](packaging/nix/README.md). + +### From source + +```bash +git clone https://github.com/VocaHQ/vocalinux.git +cd vocalinux +./install.sh +# or pick the engine up front +./install.sh --engine=whisper_cpp +./install.sh --engine=vosk +./install.sh --engine=parakeet --auto +``` + +### After installation + +```bash +vocalinux # if ~/.local/bin is on PATH +# or +~/.local/share/vocalinux/venv/bin/vocalinux +``` + +You can also launch Vocalinux from your application menu. + +### Nightly builds + +Daily builds from `main` appear on [Releases](https://github.com/VocaHQ/vocalinux/releases). Use the latest stable or beta release for production; nightlies are untested. + +## Requirements + +| | | +|--|--| +| **OS** | Linux (Ubuntu 24.04+, Debian 12+, Fedora 42+, Arch, openSUSE Tumbleweed) | +| **Python** | 3.11 or newer | +| **Display** | X11 or Wayland | +| **Hardware** | Microphone; GPU optional (Vulkan) | + +The distro must ship Python 3.11+ because Vocalinux uses distro PyGObject (`python3-gi`). Ubuntu 22.04 (Python 3.10) and Debian 11 (3.9) are below that floor. Distribution notes: [docs/DISTRO_COMPATIBILITY.md](docs/DISTRO_COMPATIBILITY.md). + +## Usage + +### Dictation + +1. **Push-to-talk (default on new installs):** hold Right Alt (Option on Mac-layout keyboards) and speak +2. Speak into the microphone +3. **Release** to stop, or switch to **Toggle mode** in Settings (double-tap a key to start/stop) + +Existing configs keep their saved shortcut. + +### Voice commands + +English phrases always work. With a non-English recognition language, matching +punctuation / line-break phrases in that language are also recognized +(Italian *virgola* / *punto*, French *virgule* / *point*, etc.). + +| Command | Action | +|---------|--------| +| "new line" | Line break | +| "period" / "full stop" / "dot" | `.` | +| "comma" | `,` | +| "question mark" | `?` | +| "exclamation mark" | `!` | +| "delete that" | Delete last sentence | +| "capitalize" | Capitalize next word | + +### CLI + +```bash +vocalinux --help +vocalinux --version +vocalinux --debug +vocalinux --engine whisper_cpp # default +vocalinux --engine whisper +vocalinux --engine faster_whisper +vocalinux --engine vosk +vocalinux --engine parakeet +vocalinux --engine remote_api +vocalinux --model medium +vocalinux --model medium.en-q5_0 # exact whisper.cpp variant +vocalinux --model large-v3-turbo +vocalinux --wayland +vocalinux --start-minimized +``` + +### Autostart + +**Start on Login** creates an XDG autostart desktop entry (`~/.config/autostart/`). It does not install a systemd unit. Enable from the first-run dialog, tray menu, or Settings. + +### Configuration + +Stored at `~/.config/vocalinux/config.json`. Prefer the Settings dialog for day-to-day changes. **Settings → Speech Model** starts with a simple setup; expand **Advanced** for engine, size, and specialization. Set `post_processing.script_path` to an executable to transform each transcription result (stdin → stdout) before it is injected; leave empty to disable. + +Neural VAD (Silero) is used when `onnxruntime` is available; install via `pip install "vocalinux[vad]"` for manual/PyPI installs. The installer attempts this automatically. + +## Documentation + +| Document | Description | +|----------|-------------| +| [Installation](docs/INSTALL.md) | Installer, AppImage, AUR, Flatpak, Snap, Nix flake, running | +| [Manual / PyPI install](docs/INSTALL_MANUAL.md) | Package lists and pip workflows | +| [Troubleshooting](docs/TROUBLESHOOTING.md) | Common failures | +| [User guide](docs/USER_GUIDE.md) | Dictation, engines, models, tips | +| [Update guide](docs/UPDATE.md) | Upgrade steps and release notes | +| [Changelog](CHANGELOG.md) | Release history pointers | +| [Support](SUPPORT.md) | Where to get help | +| [Distribution compatibility](docs/DISTRO_COMPATIBILITY.md) | Distro matrix and session notes | +| [Remote HTTP API](docs/HTTP_REMOTE.md) | Offload transcription to a server | +| [Local VocaGateway (optional)](docs/GATEWAY_EMBED.md) | Start a local gateway from Settings; not on-device | +| [Contributing](CONTRIBUTING.md) | Dev setup, style, PR process | +| [Security](SECURITY.md) | Supported versions and vulnerability reporting | +| [Docs index](docs/README.md) | Full documentation map | + +## Privacy and security + +- Local engines process audio on-device after you download a model; no Voca account required +- Optional remote API is off by default and only used when you configure a server +- Model downloads are checked against pinned checksums + +Report vulnerabilities privately per [SECURITY.md](SECURITY.md). + +## Development + +```bash +git clone https://github.com/VocaHQ/vocalinux.git +cd vocalinux +./install.sh --dev +source venv/bin/activate +pytest +python -m vocalinux.main --debug +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the two-venv layout, `just` recipes, and PR guidelines. + +## Roadmap + +Shipped: graphical settings, multi-language support, whisper.cpp default, Vulkan GPU, Wayland/IBus, Flatpak packaging, AppImage, `.deb` / `.rpm` packages, in-app update checker, Parakeet and Faster Whisper engines, Snap recipe. + +Planned: + +- [ ] Application-specific voice commands +- [ ] User-customizable voice command map +- [ ] Flathub publication (not currently listed; see #167) + +## Voca ecosystem + +Vocalinux is part of [VocaHQ](https://vocahq.com). On-device speech-to-text first, one app per platform. Optional [VocaGateway](https://vocagateway.vocahq.com) is self-hosted and not on-device. + +| Platform | Project | Website | GitHub | +|----------|---------|---------|--------| +| Linux | **VocaLinux** | [vocalinux.com](https://vocalinux.com) | [VocaHQ/vocalinux](https://github.com/VocaHQ/vocalinux) | +| macOS | **VocaMac** | [vocamac.com](https://vocamac.com) | [VocaHQ/vocamac](https://github.com/VocaHQ/vocamac) | +| Windows | **VocaWin** | [vocawin.com](https://vocawin.com) | [VocaHQ/vocawin](https://github.com/VocaHQ/vocawin) | +| Phone | **VocaPhone** | [vocaphone.vocahq.com](https://vocaphone.vocahq.com) | [VocaHQ/vocaphone](https://github.com/VocaHQ/vocaphone) | +| Gateway | **VocaGateway** | [vocagateway.vocahq.com](https://vocagateway.vocahq.com) | [VocaHQ/vocagateway](https://github.com/VocaHQ/vocagateway) | + +Talk to us: [Discord](https://discord.gg/t6muquAJbm) · [X @vocahq](https://x.com/vocahq) · [hello@vocahq.com](mailto:hello@vocahq.com) + +## Contributing + +Bug reports, docs, and code are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) and [good first issues](https://github.com/VocaHQ/vocalinux/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). + +- [Report a bug](https://github.com/VocaHQ/vocalinux/issues/new?template=bug_report.md) +- [Request a feature](https://github.com/VocaHQ/vocalinux/issues/new?template=feature_request.md) +- [Discussions](https://github.com/VocaHQ/vocalinux/discussions) + +### Contributors + +Thanks to everyone who has contributed code, docs, or fixes: + + + Vocalinux contributors + + +## Repository mirrors + +GitHub is the primary forge for issues, PRs, CI, and releases. + +| Role | URL | +|------|-----| +| Primary | https://github.com/VocaHQ/vocalinux | +| Read-only mirror (Codeberg) | https://codeberg.org/jatinkrmalik/vocalinux | + +Open issues and pull requests on GitHub only. + +## License + +This project is licensed under the **GNU Affero General Public License v3.0** +([AGPL-3.0](LICENSE)), aligning with the other [VocaHQ](https://github.com/VocaHQ) +distribution projects. + +You may use, study, modify, and redistribute the software under AGPL-3.0. If you +run a modified version as a network service, AGPL also requires that you make the +corresponding source available. + +## Star Chart + +[![Star History Chart](https://api.star-history.com/chart?repos=VocaHQ/vocalinux&type=date&legend=top-left&sealed_token=ZWyQQLhSORoR4mKf6UXMGFSCBXRxM_yEZgc8MFCH_ysBjaFUm_OCH-bI3TD7OivczEzm-ADRIpF9xCWFOMHvBPW95eQBxzfRMpNksChz7rN_eiqL7AIMDw)](https://www.star-history.com/?type=date&repos=VocaHQ%2Fvocalinux) + +### Optional Orukeet model + +With the Parakeet engine installed, Settings → Speech Model → Parakeet also +offers `orukeet-v0.1.0`. It uses the existing local sherpa-onnx recognizer for +25 European languages; the recommended Parakeet model and default whisper.cpp +engine stay unchanged. + +The download comes from a pinned revision of +[oruk/orukeet on Hugging Face](https://huggingface.co/oruk/orukeet). Vocalinux +checks every file against its bundled SHA-256 pins and checks the publisher's +release manifest against those same pins. The manifest download participates +in Hugging Face's normal model-download accounting. Cached recognition works +offline and does not send audio or usage events. The model occupies about +641 MiB; its CC BY-SA 4.0 weights license and NVIDIA attribution are included. + +### Optional Underdog Bark 3.0 models + +Settings → Speech Model → Parakeet also offers two builds of +[Underdog Bark 3.0](https://huggingface.co/ConwayResearch/Underdog-Bark-3.0), an +English fine-tune of Parakeet TDT 0.6B v3 (CC BY 4.0): `bark-3.0` (int8, 653 MiB) +and `bark-3.0-int4` (4-bit, 379 MiB, about half the memory, slower). Both come +from a pinned revision of +[velddev/Underdog-Bark-3.0-sherpa-onnx](https://huggingface.co/velddev/Underdog-Bark-3.0-sherpa-onnx), +an unofficial sherpa-onnx conversion not made or endorsed by Conway Research. +They run on the existing sherpa-onnx recognizer, every file is checked against +its bundled SHA-256 pin, and the downloads include the license and attribution +notice. +Choose one for English dictation of conversational speech, `bark-3.0-int4` when +memory is tight; for read or multilingual speech keep `v3-european`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..32ede63 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,89 @@ +# Security policy + +## Supported versions + +Only the current stable minor line receives security fixes: + +| Version | Supported | +| ------- | --------- | +| 0.18.x | Yes | +| 0.17.x | No | +| 0.16.x | No | +| 0.15.x | No | +| older | No | + +Upgrade to the latest release for security and reliability fixes. See [docs/UPDATE.md](docs/UPDATE.md). + +## Reporting a vulnerability + +Do not open a public GitHub issue for security vulnerabilities. + +**Preferred:** email the maintainer at **jatinkrmalik@gmail.com** with subject: + +`Vocalinux Security Vulnerability Report` + +Include: + +- Clear description of the issue +- Steps to reproduce +- Potential impact +- Suggested fix (optional) + +You should receive acknowledgment within 48 hours. We will assess severity, share an expected timeline, coordinate disclosure, and credit you in release notes if you want credit. + +If email is unavailable, use [GitHub private vulnerability reporting](https://github.com/VocaHQ/vocalinux/security/advisories/new) when enabled for the repository. + +## Privacy design + +Vocalinux is built for local-first dictation: + +- Local engines process audio on-device after you download a model +- No Voca account is required for local engines +- No usage telemetry in the installed application +- Models download once and stay in local cache +- Downloads are checked against pinned checksums before install + +### Remote API (optional) + +When the user enables **Remote API**, audio is uploaded to the configured server over HTTP(S). That path is off by default. Operators should use TLS and authentication for any network outside a trusted LAN. See [docs/HTTP_REMOTE.md](docs/HTTP_REMOTE.md). + +### Engines + +| Engine | Processing | Notes | +|--------|------------|--------| +| whisper.cpp (default) | Local | Vulkan GPU optional; models cached under XDG data | +| OpenAI Whisper | Local | PyTorch; NVIDIA/CUDA common | +| Faster Whisper | Local | CTranslate2 / INT8 Whisper on CPU; optional extra | +| VOSK | Local | Lightweight CPU path | +| Parakeet | Local | NVIDIA NeMo ASR via sherpa-onnx; CPU | +| Remote API | User-configured server | Opt-in only | + +### File locations + +| Path | Purpose | Permissions (typical) | +|------|---------|------------------------| +| `~/.config/vocalinux/` | Configuration | User-only (mode 700) | +| `~/.local/share/vocalinux/` | Data and models | User-only (mode 700) | + +### Known limitations + +1. **Text injection** uses tools such as xdotool, wtype, ydotool, or IBus; desktop input APIs have their own privilege model +2. **Global shortcuts** need access to input devices (e.g. evdev on some setups) +3. **Virtual environments** isolate Python packages but do not sandbox the desktop session + +## Keeping installs current + +```bash +# Re-run the official installer (preserves config and models) +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +Or follow [docs/UPDATE.md](docs/UPDATE.md) for source, AUR, AppImage, Snap, and Flatpak paths. + +## Contact + +| Topic | Contact | +|-------|---------| +| Security | jatinkrmalik@gmail.com | +| General bugs | https://github.com/VocaHQ/vocalinux/issues | diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..fce6c01 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,40 @@ +# Support + +Where to get help with Vocalinux. + +## Documentation + +| Topic | Doc | +|-------|-----| +| Install | [docs/INSTALL.md](docs/INSTALL.md) | +| Manual / PyPI install | [docs/INSTALL_MANUAL.md](docs/INSTALL_MANUAL.md) | +| Troubleshooting | [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | +| Day-to-day use | [docs/USER_GUIDE.md](docs/USER_GUIDE.md) | +| Upgrading | [docs/UPDATE.md](docs/UPDATE.md) | +| Distros and sessions | [docs/DISTRO_COMPATIBILITY.md](docs/DISTRO_COMPATIBILITY.md) | +| Remote transcription API | [docs/HTTP_REMOTE.md](docs/HTTP_REMOTE.md) | +| Full index | [docs/README.md](docs/README.md) | +| Website | [vocalinux.com](https://vocalinux.com) | + +## Community + +| Channel | Use for | +|---------|---------| +| [Discord](https://discord.gg/t6muquAJbm) | Fastest place to talk with maintainers | +| [GitHub Discussions](https://github.com/VocaHQ/vocalinux/discussions) | Questions, setup help, ideas | +| [Bug report](https://github.com/VocaHQ/vocalinux/issues/new?template=bug_report.md) | Something is broken | +| [Feature request](https://github.com/VocaHQ/vocalinux/issues/new?template=feature_request.md) | New capability proposals | + +When filing a bug, include OS/distro, `vocalinux --version`, install method, display server (X11/Wayland), engine, and a `vocalinux --debug` snippet. + +## Security + +Do not post vulnerability details in public issues. Follow [SECURITY.md](SECURITY.md). + +## Contributing + +Code and docs: [CONTRIBUTING.md](CONTRIBUTING.md). Community standards: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +## Maintainer contact + +General product questions are best on Discussions or Discord so others can find the answer. Security only: see [SECURITY.md](SECURITY.md). diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..37cdbde --- /dev/null +++ b/codecov.yml @@ -0,0 +1,18 @@ +coverage: + # Badge colors: red below 50%, yellow 50–80%, green at 80%+ (matches project target) + range: 50..80 + round: down + precision: 2 + status: + project: + default: + target: auto + threshold: 0.5% + patch: + default: + target: 80% + +comment: + layout: "diff, flags, files" + behavior: default + require_changes: false diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6587f8b --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,111 @@ +# Architecture + +Vocalinux is a GTK 3 desktop application. `main.py` wires together the +configuration manager, recognition manager, tray UI, D-Bus service, and text +injector. Audio capture and recognition run outside the GTK event loop. + +## Module map + +- `main.py` composes the application services and callbacks. +- `audio/capture.py` and `audio/pipewire.py` provide capture sources. +- `speech_recognition/` owns VAD, segment processing, command handling, and + engine implementations under `engines/`. +- `post_processor.py` runs configured transcript processing scripts. +- `text_injection/` contains `TextInjector`, focused-window detection, IBus, + and Remote Desktop Portal support. +- `ui/` contains the tray, Settings, dictation pad, history, and keyboard + backend modules. +- `dbus_service.py` exposes desktop control methods. `gateway_embed/` manages + the optional local VocaGateway integration. + +## Dictation data flow + +```text +PortAudio or PipeWire + | + v +audio/capture.py and audio/pipewire.py + | + v +recognition_manager.py: VAD and silence segmentation + | | + | Silero VAD, when available + | amplitude VAD otherwise + v +bounded segment queue + | + v +recognition worker -> selected speech engine -> corrections and commands + | + v +text callback -> post_processor.py -> TextInjector backend +``` + +`PortAudioCaptureSource` and the PipeWire source provide mono 16 kHz PCM chunks. +The recognition manager owns dictation segmentation. It uses Silero VAD when +available and amplitude thresholds otherwise, then places finalized segments +on a bounded queue. A recognition worker consumes the queue, dispatches each +segment to the selected engine, applies dictionary corrections and voice +command parsing, and invokes callbacks. + +The text callback submits work to one ordered post-processing worker. That +worker runs the configured post-processing script, then injects text or places +it in the in-app dictation pad. `TextInjector` selects a backend from the +detected `DesktopEnvironment`: X11 uses xdotool, Wayland can use wtype or +ydotool, and the IBus and Remote Desktop Portal paths support their respective +sessions. + +## Threads and state ownership + +- The GTK main thread owns widgets, dialog state, tray state, and the GLib main + loop. UI-facing state callbacks use `GLib.idle_add` where needed; the D-Bus + control service also marshals Toggle, Start, and Stop calls to this loop. +- The audio capture thread reads the capture source, tracks the current audio + buffer and VAD state, and enqueues completed segments. The recognition worker + owns queue consumption and transcription callback execution. +- The recognition manager's model lock protects engine and recognizer access + during transcription, initialization, and cleanup. The segment queue is + thread-safe; capture-buffer handoff uses its buffer lock. +- Text callbacks enqueue work on a single post-processing worker so script + execution and injection stay in spoken order. Clipboard restoration can run + on a separate worker. `TextInjector` protects its injection and clipboard + restore bookkeeping with locks and generation state. +- Keyboard backends use listener or monitor threads for pynput and evdev. + IBus and Remote Desktop Portal injection also use worker threads for their + input and D-Bus work. +- GLib timeouts drive UI polling and progress updates. Recognition and capture + workers use their own queue and event waits rather than GTK timers. + +## Speech engines + +`common_types.SpeechEngine` defines the runtime-checkable engine contract. +`speech_recognition/engines/available_engines()` reports optional engines in +Settings display order without importing the large Whisper packages just to +probe availability. `SettingsDialog` retains a compatibility wrapper for its +legacy engine-to-availability mapping. + +`SpeechRecognitionManager._process_audio_buffer()` uses an engine-name dispatch +table to call the existing per-engine transcription methods. The manager owns +engine selection, model lifecycle, segment language snapshots, and the model +lock. Optional engine packages are loaded only when their engine is selected. + +## Configuration and Settings + +`ui/config_manager.py` defines `DEFAULT_CONFIG`, loads the user's JSON config, +and recursively overlays saved values on those defaults. It persists changes +through `save_config()`. Settings controls generally apply changes as they are +made and use callbacks to update the running engine or related services; values +that require a model reload take effect during that reload. + +The Settings UI is split across `settings_dialog.py`, `settings_about.py`, and +`settings_engines.py`. Other UI responsibilities live in focused modules such +as `tray_indicator.py`, `dictation_overlay.py`, `transcription_history.py`, +`keyboard_shortcuts.py`, and `keyboard_backends/`. + +## Services and optional integrations + +`dbus_service.py` owns the `com.vocalinux.Vocalinux` session-bus name and +provides Toggle, Start, and Stop methods for external controls. +`gateway_embed/` manages the optional local VocaGateway container lifecycle, +status, pairing, and configuration. It is separate from the default +on-device whisper.cpp engine. diff --git a/docs/AUR.md b/docs/AUR.md new file mode 100644 index 0000000..cd9ee3b --- /dev/null +++ b/docs/AUR.md @@ -0,0 +1,116 @@ +# Arch User Repository (AUR) + +## Users + +```bash +yay -S vocalinux # canonical release sdist, built against Arch dependencies +yay -S vocalinux-git # community package; tracks main +``` + +`vocalinux`, `vocalinux-bin` and `vocalinux-git` conflict; install only one. + +`packaging/aur/vocalinux-bin/` builds a `-bin` variant that installs the +prebuilt AppImage from the release — no Python, engine, or GPU dependency +builds, since the bundle carries them (#817). Its publication waits only for +release creation and both AppImages, not the source-package gate or other +packaging jobs. It first pins the per-arch AppImage digests in the PKGBUILD, +then pushes to AUR in the `Publish AUR -bin package` step. + +## Maintainers (one-time setup) + +Release tags prepare and test the source PKGBUILD from +`packaging/aur/vocalinux/PKGBUILD`, then auto-push the verified file when these +secrets exist: + +| Secret | Value | +|--------|--------| +| `AUR_SSH_PRIVATE_KEY` | Private key whose public half is on your AUR account | +| `AUR_USERNAME` | AUR username (git author) | +| `AUR_EMAIL` | AUR account email | + +```bash +# 1. AUR account + SSH pubkey: https://aur.archlinux.org/register +ssh-keygen -t ed25519 -C "vocalinux-aur" -f ./aur_ed25519 -N "" +# upload aur_ed25519.pub on AUR; put aur_ed25519 in AUR_SSH_PRIVATE_KEY + +# 2. Create empty package once +git clone ssh://aur@aur.archlinux.org/vocalinux.git + +# 3. Next v* tag runs Publish to AUR (skipped if secret missing) +``` + +Sources: `packaging/aur/vocalinux/PKGBUILD`. Community git package: https://aur.archlinux.org/packages/vocalinux-git + +## Source archive and trust model + +The source package consumes the **canonical sdist attached to the GitHub release**, +the same bytes published to PyPI, not GitHub's automatically generated tag archive. +The sdist is produced by the locked reproducible-wheel gate. Only wheels are +compared for reproducibility; sdists are built and validated, not compared. + +`scripts/prepare_aur_source.py` verifies the downloaded sdist against the published +`SHA256SUMS` and runs `gh attestation verify`, requiring the VocaHQ/vocalinux +repository, `.github/workflows/release.yml` signer, exact tag ref, and expected +source commit. Missing hashes, mismatches, rejected provenance, or mismatched +tag/package versions stop preparation. It preserves the original archive bytes +and pins their digest in the prepared PKGBUILD. `_tag` retains the release tag; +`_version` is the PEP 440-normalized sdist version, including for prereleases. + +This verification is a **maintainer publication gate**. An AUR user's `makepkg` +checks the pinned SHA-256; it does not independently verify GitHub provenance. +Arch still owns the dependency/toolchain versions: this does not claim that AUR +builds use our Python lock or reproduce the PyPI wheel byte-for-byte. + +## CI gates + +### PR and local source gate + +Python/AUR changes and changes to the source manifest, README or license build the +canonical sdist from the current checkout's reproducible-wheel gate, then prepare +an explicitly `--local-unattested` fixture. The `Build AUR package (gate)` job in +`unified-pipeline.yml` builds and installs its PKGBUILD on `archlinux:latest`. +Run `just aur-gate` locally (needs uv and Docker); it includes the reproducible-wheel +build and uses a fresh temporary directory for the distributions, diagnostics, +tooling and prepared fixture. Existing `dist/`, `build/reproducible-wheel/` and +the developer environment are untouched, so other packaging commands can run +before or after it. Successful runs clean the temporary directory; failed runs +retain it and print its path for diagnosis. + +### Release gate + +After the release checksum/provenance job succeeds, `aur-source-test` downloads +the **published** sdist and manifest, verifies them, and prepares the PKGBUILD. +The same Arch gate makes `makepkg` download the actual source URL afresh +(`AUR_FETCH_SOURCE=1`) and verify its checksum, runs `namcap` on the recipe and +package, installs the package, checks the installed distribution version, and +runs the import smoke. It does not rewrite the source URL or disable source +checksum checks. The tested recipe and downloaded archive must match the +prepared PKGBUILD and provenance-verified sdist byte-for-byte. + +Only after this succeeds is `aur-source-pkgbuild` uploaded for `publish-aur`. +Publication downloads that exact file; there is no version rewrite or +`updpkgsums` afterwards. The gate runs even without AUR credentials; only the +final push is secret-gated. This intentionally makes source publication wait +for the release's checksums and attestations. `vocalinux-bin` uses a separate +AppImage publication path independent of this source gate and the global +checksum job; its PR smoke still uses a stub AppImage. + +Both gates deliberately use rolling `archlinux:latest`: #757 was Arch's +setuptools moving underneath us. `python-pywhispercpp` (virtual, provided by the +AUR backends) and `python-pynput` (AUR) cannot come from the repos, so the gate +satisfies them with a stub for dependency resolution and pip-installs their +pinned versions from `requirements/runtime.txt` for the import smoke. It does +not test those AUR providers or prove dependency completeness in a minimal host. +What those providers actually ship remains a separate monitoring question. + +## Troubleshooting + +**`ERROR Missing dependencies: setuptools<82`** + +The AUR package builds with `python -m build --wheel --no-isolation`, so it uses Arch extra `python-setuptools` (currently 84). Vocalinux 0.16.1 drops the `<82` cap in `pyproject.toml`. Rebuild 0.16.1 or later. + +**`'_pywhispercpp.whisper_full_params' object has no attribute 'context_params'`** + +AUR `python-pywhispercpp-cpu` / `-cuda` / `-rocm` are still 1.4.x. Those wheels accept `context_params` in Python and then crash in the native object. 0.16.1 skips that argument on pywhispercpp older than 1.5 and still loads the model (default GPU, no device picker). PyPI and `install.sh` already use 1.5.0. + +There is no AUR `python-pywhispercpp-vulkan` package. GPU Vulkan 1.5 is the `install.sh` / AppImage path, not the AUR python backends. diff --git a/docs/CUSTOM_DICTIONARY_DESIGN.md b/docs/CUSTOM_DICTIONARY_DESIGN.md new file mode 100644 index 0000000..1a421b3 --- /dev/null +++ b/docs/CUSTOM_DICTIONARY_DESIGN.md @@ -0,0 +1,125 @@ +# Custom Dictionary Support Design + +## Scope + +Custom dictionary support has two separate jobs: + +1. **Custom terms** bias local Whisper, whisper.cpp, and Faster Whisper recognition + toward names and jargon. +2. **Transcript corrections** deterministically replace a known misheard phrase + after transcription. + +They appear together on the **Custom Dictionary** Settings page, but are not +interchangeable. A correction replacement does not implicitly become a prompt +term. That coupling would make recognition bias depend on correction order and +would make the external terms contract surprising. + +## File contracts + +Both files live in VocaLinux's XDG-aware configuration directory: +`$XDG_CONFIG_HOME/vocalinux` (or `~/.config/vocalinux` when unset). + +| File | Contract | Consumer | +| --- | --- | --- | +| `dictionary.txt` | UTF-8 (BOM accepted), one term per line. Blank lines and lines beginning with `#` are ignored. Terms are de-duplicated case-insensitively while retaining the first spelling. | VocaLinux and an external accessibility scanner. | +| `custom-dictionary-corrections.json` | UTF-8 JSON object: `{"version": 1, "corrections": [{"heard": "super base", "replacement": "Supabase"}]}`. | VocaLinux only. | + +The terms file remains deliberately simple and scanner-friendly. Corrections +need two unambiguous fields, a version, and exact replacement casing, so JSON is +used rather than overloading the line file with an ambiguous delimiter grammar. +The UI writes either file by atomic replacement; a failed UI save preserves the +old file and reports an error. + +The application reads both files for each completed transcript. An externally +edited file therefore takes effect on the next dictation segment without an app +restart. Missing, unreadable, malformed, or unsupported correction files are +ignored safely; dictation continues without their entries. + +The terms file may contain any number of lines, but VocaLinux limits a single +prompt to the first 200 valid terms and 2,000 characters. This bounds decoding +work while leaving the scanner's complete line-file contract intact. The Custom +Dictionary page keeps both an add/remove editor and a validated file chooser +for the terms file. + +## Processing and engine behavior + +For every completed audio segment, VocaLinux applies this order: + +1. The selected engine transcribes audio. +2. Live phrase corrections run against the raw transcript. +3. Voice-command interpretation runs on that corrected text. +4. Remaining text and actions are dispatched. + +Corrections run before commands so a correction can prevent a command-like +misrecognition from being acted on. This also means users must not create a +replacement that deliberately creates a voice command unless that is intended. + +Corrections match literal phrases case-insensitively, only where the adjacent +characters are not Unicode word characters (or common combining marks). Longer +phrases win over shorter overlaps; equal-length entries retain JSON order. +Replacement text is inserted exactly as written. Inputs are normalized to NFC +for matching, so composed/decomposed accents are handled consistently. + +| Engine | Custom terms | Corrections | +| --- | --- | --- | +| OpenAI Whisper | `initial_prompt` | Yes | +| whisper.cpp | Advanced initial prompt followed by custom terms; explicit empty prompt clears reused native state | Yes | +| Faster Whisper | `initial_prompt` | Yes | +| VOSK | No prompt-bias API | Yes | +| Parakeet | No prompt-bias API | Yes | +| Remote API | No prompt is added to current request formats | Yes | + +`--dictionary-file PATH` preserves the #767 session-only behavior: `PATH` is +used as the custom terms file, it enables terms for that session, and Settings +does not modify the saved terms file or enablement. Corrections remain loaded +from the standard JSON file. The previous experimental #768 +`text_injection.custom_dictionary` config list is read only as a fallback until +the JSON corrections file exists; it is never silently deleted or migrated. + +The #767 persisted configuration keys are retained unchanged: +`dictionary.enabled`, `dictionary.file_path` (default +`~/.config/vocalinux/dictionary.txt`), and `dictionary.max_words`. Existing +Peony settings therefore continue to select the scanner's `dictionary.txt` +file. New configured paths are accepted only when they can be expanded and are +either absent (so the UI can create them) or readable regular files; a failed +save leaves the prior setting in place. Invalid configured and `--dictionary-file` +paths are safely ignored and shown as unavailable rather than raising. + +## Alternatives rejected + +- **One mixed line file:** cannot express arbitrary phrases and replacements + without reserving/escaping delimiters, which weakens the scanner contract. +- **Corrections in `config.json`:** makes a scanner compete with application + settings writes and offers no durable external-file schema. +- **Post-command correction:** allows command-like transcription errors to be + acted on before they can be fixed. +- **Automatically adding correction replacements to prompts:** rejected because + replacements are not automatically included in the recognition prompt; + corrections work post-transcription on every engine. + +## Human test checklist + +1. Add `VocaLinux`, `PyGObject`, and a non-ASCII term in **Custom terms**; + confirm `dictionary.txt` is UTF-8, one line per term, and an external + edit changes the next Whisper, whisper.cpp, and Faster Whisper dictation segment. +2. Add `super base` → `Supabase`; test lowercase, uppercase, punctuation, an + overlapping short correction, `C++`, and non-ASCII text. Confirm replacement + casing is exact and embedded text (for example `supersize`) is not replaced. +3. With voice commands enabled, add a correction that changes a command-like + misrecognition into ordinary text. Confirm it is injected rather than acted + on. Also verify an intentionally command-like replacement is avoided. +4. Edit each file externally while VocaLinux is running. Test an invalid JSON + correction file and invalid UTF-8 terms file; confirm dictation continues and + the Settings status/save feedback is clear. +5. Test the UI with keyboard only: Add buttons via Enter, empty validation, + update/remove feedback, narrow window, long entries, and high-contrast theme. +6. Test all engines: Whisper, whisper.cpp, and Faster Whisper should use terms and + corrections; VOSK, Parakeet, and remote API should use corrections only, with + the VOSK terms warning. +7. Start with `--dictionary-file /path/to/terms.txt`; confirm the session uses + that file, Settings disables terms editing, saved settings remain unchanged, + and corrections still apply. +8. Start with an unresolved path such as `--dictionary-file ~missing-user/terms.txt` + and configure an unreadable path through the file chooser. Confirm Settings + reports the path safely, dictation continues without terms, and the prior + saved `dictionary.file_path` remains intact. diff --git a/docs/DISTRO_COMPATIBILITY.md b/docs/DISTRO_COMPATIBILITY.md new file mode 100644 index 0000000..1d25ce6 --- /dev/null +++ b/docs/DISTRO_COMPATIBILITY.md @@ -0,0 +1,563 @@ +# Linux distribution compatibility + +How Vocalinux behaves across distributions, what is tested, and what to install manually when needed. + +For install steps, prefer [INSTALL.md](INSTALL.md). Flatpak packaging (useful on immutable or non-standard layouts): [packaging/flatpak/README.md](../packaging/flatpak/README.md). Snap Store listing: [snapcraft.io/vocalinux](https://snapcraft.io/vocalinux) (`--edge`; pack/upload steps in [INSTALL.md](INSTALL.md)). Flatpak is not on Flathub: submission [flathub#9368](https://github.com/flathub/flathub/pull/9368) closed 2026-07-23 on policy grounds; channel tracked in [#167](https://github.com/VocaHQ/vocalinux/issues/167). + +## Cross-distro behavior + +The installer and launch wrappers handle most path differences. + +### GI_TYPELIB_PATH detection + +The installer uses a multi-step approach to detect the correct GI_TYPELIB_PATH: + +1. **Primary Method**: Uses `pkg-config --variable=typelibdir gobject-introspection-1.0` (most reliable) +2. **Fallback Paths**: Checks common distribution-specific paths in priority order: + - Ubuntu/Debian multi-arch: `/usr/lib/x86_64-linux-gnu/girepository-1.0` + - ARM64: `/usr/lib/aarch64-linux-gnu/girepository-1.0` + - Fedora/RHEL: `/usr/lib64/girepository-1.0` + - Arch/Generic: `/usr/lib/girepository-1.0` + - Local installs: `/usr/local/lib/girepository-1.0` + +3. **Architecture Support**: Detected paths for x86_64, ARM64, ARMHF, RISC-V, POWER, and s390x + +### Wrapper Scripts + +The installer creates wrapper scripts (`~/.local/bin/vocalinux` and `~/.local/bin/vocalinux-gui`) that: +- Set the correct `GI_TYPELIB_PATH` environment variable +- Handle input group permissions for Wayland keyboard shortcuts +- Provide seamless cross-distro compatibility + +### Desktop Entry + +The `.desktop` entry is automatically configured with the detected `GI_TYPELIB_PATH`, ensuring the application launches correctly from the application menu regardless of distribution. + +## Officially Supported Distributions + +These distributions are tested and known to work well with Vocalinux. A +derivative is judged by the Python and package repositories it ships rather +than by its own version number, which rarely tracks the base release. In +particular, `install.sh` does not interpret a Debian-based derivative's product +version as a Debian release number. + +| Distribution | Version | Status | Notes | +|--------------|---------|--------|-------| +| Ubuntu | 24.04+ | ✅ Full Support | 22.04 ships Python 3.10, below the 3.11 floor | +| Debian | 12+, Testing | ✅ Full Support | Primary target; use Ayatana AppIndicator | +| Fedora | 42+ | ✅ Good Support | Uses lib64 paths; 42 is the oldest Fedora upstream still supports, and the one in the CI matrix | +| Arch Linux | Rolling | ✅ Good Support | Community tested, rolling release compatible | +| openSUSE | Tumbleweed | ✅ Good Support | Good support with zypper package manager | +| Linux Mint | On Ubuntu 24.04+ | ✅ Good Support | Mint 22 qualifies; Mint 21 is on Ubuntu 22.04, below the floor | +| Pop!_OS | 24.04+ | ✅ Good Support | Uses Ubuntu's own numbering | +| elementary OS | On Ubuntu 24.04+ | ✅ Good Support | elementary 8 qualifies; 7 is on Ubuntu 22.04 | +| Zorin OS | On Ubuntu 24.04+ | ✅ Good Support | Zorin 17 is on Ubuntu 22.04, below the floor | +| Manjaro | Rolling | ✅ Good Support | Arch base, inherits Arch compatibility | +| EndeavourOS | Rolling | ✅ Good Support | Arch base, inherits Arch compatibility | + +## Experimental Support + +These distributions may work but have known limitations or are less tested: + +| Distribution | Status | Known Issues | +|--------------|--------|--------------| +| Gentoo | ⚠️ Experimental | No package manager support, manual install required. Packages are compiled from source which takes longer. | +| Alpine Linux | ⚠️ Experimental | Uses musl libc (not glibc). Some Python packages may not have pre-built wheels and may need to be compiled. | +| Void Linux | ⚠️ Experimental | Untested, manual install required. Uses xbps package manager. | +| Solus | ⚠️ Experimental | Untested, manual install required. Uses eopkg package manager. | +| Mageia | ⚠️ Experimental | Untested, uses dnf/urpmi package managers. | + +## Not Supported + +These distributions are known to be incompatible: + +| Distribution | Status | Reason | +|--------------|--------|--------| +| Debian 11 and earlier | ❌ Not Supported | EOL and below the Python 3.11 floor; the installer requires Debian 12+ | +| NixOS | ❌ Not Supported | Completely different filesystem layout (/nix/store), incompatible with standard installer | + +## Requirements by Distribution + +These are baseline commands for manual installations. The exact inventory used +by `install.sh`, including package alternatives and conditional dependencies, +lives in [`scripts/distro-package-map.yaml`](../scripts/distro-package-map.yaml). +For the complete manual setup sequence, including native build prerequisites, +use [INSTALL_MANUAL.md](INSTALL_MANUAL.md#system-packages-by-distribution). + +### Ubuntu/Debian-based +```bash +sudo apt install -y python3-gi gir1.2-gtk-3.0 gir1.2-ibus-1.0 gir1.2-gdkpixbuf-2.0 \ + portaudio19-dev python3-dev python3-venv pkg-config +``` + +**Note for Debian 12+:** Use `gir1.2-ayatanaappindicator3-0.1` instead of `gir1.2-appindicator3-0.1` + +### Debian-specific notes + +Debian's standard repositories differ from Ubuntu in a few ways that matter for Vocalinux: + +**pywhispercpp build prerequisites.** On a clean Debian install the following packages are not +pulled in transitively (unlike Ubuntu) but are required when `pywhispercpp` must be compiled +from source (e.g. for GPU support): + +```bash +sudo apt install -y libssl-dev autoconf automake libtool patchelf +``` + +The installer now installs these automatically, but if you hit a CMake error like +`Could not find OpenSSL` during a manual reinstall, add the above first. + +**ydotool.** `ydotool` is not packaged in Debian's standard repos. The installer falls back +gracefully to IBus or `wtype` for most Wayland compositors. KDE Plasma Wayland users should +first select **IBus Wayland** in **System Settings -> Keyboard -> Virtual Keyboard**. If you +specifically need `ydotool` as a fallback, compile it from source: + +```bash +sudo apt install -y git cmake libevdev-dev +git clone https://github.com/ReimuNotMoe/ydotool.git /tmp/ydotool +cmake -S /tmp/ydotool -B /tmp/ydotool/build +sudo cmake --build /tmp/ydotool/build --target install +sudo systemctl enable --now ydotoold +``` + +**Scoped source builds.** If you need to force `pywhispercpp` to rebuild from source, use the +package-scoped flag to avoid compiling unrelated deps like NumPy from source (which takes a very +long time and can fail): + +```bash +source ~/.local/share/vocalinux/venv/bin/activate +PYWHISPERCPP_CLEAN=1 pip install --force-reinstall --no-binary=pywhispercpp pywhispercpp +deactivate +``` + +**Verifying libwhisper.so resolution.** If Vocalinux starts with +`libwhisper.so.1: cannot open shared object file`, check for unresolved symbols: + +```bash +ldd $(find ~/.local/share/vocalinux/venv -name '*.so' -path '*/pywhispercpp*' 2>/dev/null | head -1) | grep 'not found' +``` + +If any libraries are listed, re-run the installer with `--rebuild-whispercpp` or switch to +the VOSK engine (`--engine=vosk`) as a fully-packaged alternative. + +### Fedora/RHEL-based +```bash +sudo dnf install -y python3-gobject gtk3-devel ibus-devel cairo-devel gobject-introspection-devel \ + portaudio-devel python3-devel python3-virtualenv pkg-config +``` + +### Arch Linux-based +```bash +sudo pacman -S --needed python-gobject gtk3 ibus gobject-introspection \ + portaudio python pkg-config +``` + +### openSUSE +```bash +PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.version_info.minor}")') + +sudo zypper install -y \ + "${PYVER}-pip" "${PYVER}-gobject" "${PYVER}-gobject-cairo" \ + "${PYVER}-devel" "${PYVER}-virtualenv" \ + gtk3 ibus typelib-1_0-IBus-1_0 \ + typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \ + typelib-1_0-Notify-0_7 libnotify4 \ + gobject-introspection-devel portaudio-devel pkg-config cmake wget curl unzip \ + gcc gcc-c++ make patchelf \ + xdotool wtype + +# Optional: only needed for whisper.cpp Vulkan GPU builds +sudo zypper install -y vulkan-tools vulkan-devel shaderc +``` + +**Note:** openSUSE Tumbleweed often uses versioned Python package names such as +`python313-devel`. The `-devel` suffix means development headers for compiling +native dependencies; it does **not** mean beta or unstable packages. If +`${PYVER}-virtualenv` is unavailable on your snapshot, try `${PYVER}-venv`. + +### Gentoo +```bash +sudo emerge dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator \ + media-libs/portaudio dev-lang/python:3.11 +``` + +**Note:** Gentoo compiles packages from source, which will take longer. + +### Alpine Linux +```bash +sudo apk add py3-gobject3 py3-pip gtk+3.0 py3-cairo portaudio-dev \ + py3-virtualenv pkgconf +``` + +**Note:** Alpine uses musl libc. Some Python packages may need to be compiled from source. + +### Void Linux +```bash +sudo xbps-install -Sy python3-pip python3-gobject gtk+3 libappindicator \ + gobject-introspection portaudio-devel python3-devel pkg-config +``` + +### Solus +```bash +sudo eopkg install python3-pip python3-gobject gtk3 libappindicator \ + gobject-introspection-devel portaudio-devel python3-virtualenv pkg-config +``` + +### Mageia +```bash +sudo dnf install -y python3-gobject gtk3-devel gobject-introspection-devel \ + portaudio-devel python3-devel python3-virtualenv pkg-config +# or +sudo urpmi -y python3-gobject gtk3-devel gobject-introspection-devel \ + portaudio-devel python3-devel python3-virtualenv pkg-config +``` + +## PyPI, pip, and pipx Installation + +The PyPI package is useful when you want a standard Python package install, or +when you want to manage Vocalinux with `pipx`. However, **Vocalinux requires +system desktop packages** that cannot be installed by pip or pipx. + +Use the official installer when possible. It installs the required system +packages, creates the virtual environment, installs Vocalinux, sets up desktop +integration, and downloads the default speech model: + +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +### Important: Install System Packages First + +Before running `pip install vocalinux`, `pip install "vocalinux[vosk]"`, or +`pipx install vocalinux`, you **must** install the required system packages: + +#### Ubuntu/Debian +```bash +sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-ibus-1.0 gir1.2-ayatanaappindicator3-0.1 \ + libgirepository1.0-dev libcairo2-dev portaudio19-dev python3-dev \ + python3-venv pkg-config xdotool wtype wl-clipboard xclip xsel +``` + +On Ubuntu 24.04+ or Pop!_OS, install `libgirepository-2.0-dev` if +`libgirepository1.0-dev` is not available. + +#### Fedora +```bash +sudo dnf install python3-gobject gtk3 gtk3-devel ibus-devel cairo-devel libayatana-appindicator-gtk3 \ + gobject-introspection-devel portaudio-devel python3-devel \ + python3-virtualenv pkg-config xdotool wtype wl-clipboard xclip xsel +``` + +#### Arch Linux +```bash +sudo pacman -S python-gobject gtk3 ibus libayatana-appindicator gobject-introspection \ + python-cairo portaudio python-virtualenv pkg-config xdotool wtype wl-clipboard xclip xsel +``` + +#### openSUSE +```bash +PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.version_info.minor}")') + +sudo zypper install "${PYVER}-gobject" "${PYVER}-gobject-cairo" gtk3 ibus typelib-1_0-IBus-1_0 \ + gcc gcc-c++ make patchelf \ + typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \ + typelib-1_0-Notify-0_7 libnotify4 \ + portaudio-devel "${PYVER}-devel" "${PYVER}-virtualenv" pkg-config xdotool wtype wl-clipboard xclip xsel +``` + +### pip Installation Steps + +1. **Install system packages** (see above for your distribution) + +2. **Create a virtual environment with system site-packages enabled**: + ```bash + python3 -m venv ~/.local/share/vocalinux-pypi/venv --system-site-packages + source ~/.local/share/vocalinux-pypi/venv/bin/activate + pip install --upgrade pip setuptools wheel + ``` + + `--system-site-packages` lets the venv use distro-provided GTK bindings such + as `python3-gi`, which are often more reliable than building PyGObject from + source in a venv. + +3. **Install Vocalinux from PyPI**: + ```bash + pip install vocalinux + # vosk engine: pip install "vocalinux[vosk]" + ``` + +4. **Run Vocalinux**: + ```bash + vocalinux + ``` + +### pipx Installation Steps + +1. **Install system packages** (see above for your distribution) + +2. **Install pipx** (if not already installed): + ```bash + # Ubuntu/Debian + sudo apt install pipx + pipx ensurepath + + # Fedora + sudo dnf install pipx + pipx ensurepath + + # Arch Linux + sudo pacman -S pipx + pipx ensurepath + ``` + +3. **Install Vocalinux**: + ```bash + pipx install vocalinux + # vosk engine: pipx install "vocalinux[vosk]" + ``` + +4. **Run Vocalinux**: + ```bash + vocalinux + ``` + +### Why System Packages Are Required + +Vocalinux uses GTK3 for its GUI, AppIndicator/Ayatana for the system tray icon, +PortAudio for microphone capture, and tools such as `xdotool`, `wtype`, and clipboard utilities (`wl-clipboard`, `xclip`, `xsel`) for +text injection and layout-independent Wayland paste fallback. These are system libraries and desktop tools that must be +installed with your distribution's package manager. Python packages from PyPI +can provide Python bindings, but they do not provide the underlying GTK +typelibs, tray support, audio development libraries, or text injection binaries. + +### Troubleshooting pip/pipx Installation + +If `vocalinux` fails to start: + +1. **Check for missing system packages** - The error message will indicate which packages are missing +2. **Verify GI_TYPELIB_PATH** - In rare cases, you may need to set this environment variable: + ```bash + export GI_TYPELIB_PATH=/usr/lib/x86_64-linux-gnu/girepository-1.0 + vocalinux + ``` +3. **Check package metadata** - Run `pip show vocalinux` or + `pipx runpip vocalinux show vocalinux` to confirm which version was installed. +4. **Check Python dependencies** - Run `pip check` inside the venv, or + `pipx runpip vocalinux check` for pipx. + +If Vocalinux reports that a speech recognition model is missing, open Settings +and download a model, or use the official installer to download the default model +during setup. + +### Recommended Alternative + +For the best experience with automatic dependency handling, use the official installer: +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +This installer automatically detects your distribution and installs all required system packages. + +## Text Injection Tools + +Vocalinux requires text injection tools to work with X11 or Wayland: + +### For X11 +- **xdotool**: Required for X11 sessions + +### For Wayland +- **IBus**: Recommended for KDE Plasma Wayland and generally the most reliable direct text input path +- **wtype**: Recommended for wlroots-style Wayland compositors such as sway +- **ydotool**: Universal alternative that works with both X11 and Wayland. Current distro packages use the per-user `ydotool.service`; source builds can install the system-level `ydotoold.service`. Vocalinux uses clipboard paste with `wl-clipboard`, `xclip`, or `xsel` to avoid non-US layout scrambling. +- **xdotool**: May work via XWayland fallback + +### Installation by Distribution +```bash +# Ubuntu/Debian/Fedora/Arch/openSUSE +sudo apt install xdotool wtype wl-clipboard xclip xsel # Ubuntu/Debian +sudo dnf install xdotool wtype wl-clipboard xclip xsel # Fedora +sudo pacman -S xdotool wtype wl-clipboard xclip xsel # Arch +sudo zypper install xdotool wtype wl-clipboard xclip xsel # openSUSE +``` + +Ubuntu releases which package `ydotool` install a **user** unit named +`ydotool.service`. Enable it only for the account running Vocalinux: + +```bash +sudo apt install ydotool +sudo usermod -aG input $USER # then log out and back in +systemctl --user enable --now ydotool.service +``` + +Do **not** use `sudo systemctl --global enable ydotool.service`. Global user-unit +enablement also starts the input-injection daemon for the display-manager greeter +and other accounts. If it was previously enabled globally, repair the setup with: + +```bash +sudo systemctl --global disable ydotool.service +systemctl --user enable --now ydotool.service +``` + +The command is different for the Debian source-build path documented above: the +upstream install supplies the system unit `ydotoold.service`, so +`sudo systemctl enable --now ydotoold.service` is correct only for that route. + +## Manual Installation + +For unsupported or experimental distributions: + +1. **Run the dependency checker:** + ```bash + bash scripts/check-system-deps.sh + ``` + +2. **Install missing dependencies** using your package manager based on the checker output + +3. **Run the installer with --skip-system-deps:** + ```bash + ./install.sh --skip-system-deps + ``` + +4. **Or install from source in a virtual environment:** + ```bash + python3 -m venv venv + source venv/bin/activate + pip install -e .[whisper] + ``` + +## Desktop Environment Compatibility + +| Desktop Environment | Status | Notes | +|---------------------|--------|-------| +| GNOME | ✅ Full Support | Primary target, uses AppIndicator | +| KDE Plasma | ✅ Full Support | On Wayland, select IBus Wayland in System Settings -> Keyboard -> Virtual Keyboard | +| Xfce | ✅ Full Support | Works with AppIndicator | +| Cinnamon | ✅ Full Support | Works with AppIndicator | +| MATE | ✅ Full Support | Works with AppIndicator | +| LXQt | ✅ Full Support | Works with AppIndicator | +| sway | ✅ Good Support | Wayland native, uses wtype | +| i3 | ✅ Good Support | X11, uses xdotool | +| Hyprland | ⚠️ Experimental | Wayland, may need wtype configuration | + +### Autostart Compatibility + +Vocalinux uses **XDG autostart desktop entries** for "Start on Login". + +- Desktop-session autostart file location: + - `$XDG_CONFIG_HOME/autostart/vocalinux.desktop`, or + - `~/.config/autostart/vocalinux.desktop` (fallback) +- This launches Vocalinux as a user GUI app after login. +- Vocalinux does **not** create a `systemd` service/unit for this feature. + +Practical caveat: + +- Mainstream desktop environments generally honor XDG autostart. +- Minimal/custom WM sessions may need an explicit autostart manager or WM startup hook. + +## Display Server + +| Display Server | Status | Notes | +|----------------|--------|-------| +| X11 | ✅ Full Support | Uses xdotool for text injection | +| Wayland | ✅ Full Support | Uses IBus, wtype, ydotool, or xdotool fallback depending on compositor | +| XWayland | ✅ Full Support | Automatically detected, falls back to xdotool if needed | + +## Architecture Support + +| Architecture | Status | Notes | +|--------------|--------|-------| +| x86_64 (AMD64) | ✅ Full Support | Primary target | +| arm64 (AArch64) | ✅ Good Support | Tested on Raspberry Pi, ARM64 servers | +| armhf | ⚠️ Experimental | Limited testing | +| riscv64 | ⚠️ Experimental | Limited testing, may need to compile dependencies | + +## Reporting Issues + +When reporting issues, please include: + +1. **Distribution information:** + ```bash + cat /etc/os-release + ``` + +2. **Desktop environment:** + ```bash + echo $XDG_CURRENT_DESKTOP + echo $SESSION_TYPE + ``` + +3. **Dependency checker output:** + ```bash + bash scripts/check-system-deps.sh + ``` + +4. **Python version:** + ```bash + python3 --version + ``` + +5. **Error messages or logs:** + - Any error messages from the installer + - Output from running `vocalinux-gui` from terminal + - Relevant journal logs if applicable + +6. **Steps to reproduce:** + - What you were trying to do + - What happened instead + - Expected behavior + +## Testing Checklist + +Use this checklist when testing on a new distribution: + +### Pre-Install +- [ ] Run dependency checker: `bash scripts/check-system-deps.sh` +- [ ] Note missing packages +- [ ] Check desktop environment +- [ ] Check display server (X11/Wayland) + +### Installation +- [ ] Run installer: `./install.sh` +- [ ] Check for errors during installation +- [ ] Verify virtual environment was created +- [ ] Check wrapper scripts exist + +### Post-Install +- [ ] Launch from terminal: `~/.local/bin/vocalinux-gui` +- [ ] Launch from application menu +- [ ] Verify tray icon appears +- [ ] Test keyboard shortcuts (Hold Right Alt by default) +- [ ] Test audio recording +- [ ] Test text injection in different applications +- [ ] Test Settings dialog opens correctly +- [ ] Test model download if using Whisper + +### Known Issues to Watch For +- AppIndicator not showing in tray +- Audio device not detected +- Text injection not working +- Model download failures +- Permission errors with udev/input + +## Contributing Compatibility Fixes + +If you successfully get Vocalinux working on an unsupported or experimental distribution: + +1. Test thoroughly using the checklist above +2. Document any distribution-specific requirements +3. Submit a PR with: + - Distribution detection logic (if needed) + - Package installation commands + - Any patches or workarounds required + - Update to this compatibility document + +## See also + +- [Installation guide](INSTALL.md) +- [Manual / PyPI install](INSTALL_MANUAL.md) +- [Troubleshooting](TROUBLESHOOTING.md) +- [User guide](USER_GUIDE.md) +- [Update guide](UPDATE.md) +- [GitHub issue tracker](https://github.com/VocaHQ/vocalinux/issues) diff --git a/docs/FLATPAK_REMOTE.md b/docs/FLATPAK_REMOTE.md new file mode 100644 index 0000000..5e3833d --- /dev/null +++ b/docs/FLATPAK_REMOTE.md @@ -0,0 +1,141 @@ +# Self-hosted Flatpak remote + +Vocalinux publishes a signed OSTree Flatpak remote on every stable `v*` tag — +the Homebrew-tap analogue to `VocaHQ/homebrew-vocamac` +([issue #785](https://github.com/VocaHQ/vocalinux/issues/785)). The remote +lives in the dedicated repo `VocaHQ/vocalinux-flatpak`, served by GitHub +Pages: + +```bash +flatpak install https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref +flatpak update # picks up each new release +``` + +The `.flatpakref` adds the `vocahq` remote (via `SuggestRemoteName=`) and lets +flatpak resolve `org.gnome.Platform//50` through its `RuntimeRepo=` pointer to +Flathub — one command, no separate Flathub setup. The equivalent manual +steps: + +```bash +flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo +flatpak install vocahq com.vocalinux.Vocalinux +``` + +Only the app is ours; the GNOME runtime still comes from Flathub — +`RuntimeRepo=` is a `.flatpakref` key (it is ignored on `.flatpakrepo`, which +is why the app ref carries it, not the remote descriptor). GitHub Release +`.flatpak` bundles ([#784](https://github.com/VocaHQ/vocalinux/issues/784)) +remain as a no-auto-update fallback, and are the only Flatpak path for +prerelease tags, which never reach the remote. + +## Maintainer setup (one time) + +1. **Repo** — create `VocaHQ/vocalinux-flatpak` (public). +2. **Pages** — Settings → Pages → "Deploy from a branch" → branch `gh-pages`, + folder `/ (root)`. The first publish creates the branch, so Pages can be + pointed at it afterwards; creating an empty `gh-pages` upfront works too. +3. **Signing key** — OSTree remotes sign the summary and commits with GPG: + + ```bash + gpg --quick-gen-key "Vocalinux Flatpak " ed25519 sign never + gpg --list-secret-keys # note the KEYID + gpg --export-secret-keys --armor KEYID # -> FLATPAK_GPG_PRIVATE_KEY + gpg --gen-revoke KEYID > flatpak-remote-revoke.asc # store it somewhere safe + ``` + + A passphrase-less key keeps CI simplest — libostree signs through + gpg-agent, which has no `--passphrase-fd` to reach for. A passphrase works + too: set `FLATPAK_GPG_PASSPHRASE` and the workflow presets it onto the + keygrip via `gpg-preset-passphrase`. +4. **Secrets on `VocaHQ/vocalinux`** (Settings → Secrets and variables → + Actions): + - `FLATPAK_GPG_PRIVATE_KEY` — the armored private key + - `FLATPAK_GPG_PASSPHRASE` — optional + - `FLATPAK_REPO_TOKEN` — a token with Contents: read and write on + `VocaHQ/vocalinux-flatpak`. The fine-grained PAT scoped to just that + repo is the recommended minimum; a broader classic PAT works too. +5. **Variables (optional)** — same settings page, Variables tab: + - `FLATPAK_REMOTE_REPO` — publish target (default + `VocaHQ/vocalinux-flatpak`) + - `FLATPAK_REMOTE_URL` — remote URL baked into the `.flatpakrepo` + (default `https://vocahq.github.io/vocalinux-flatpak/repo`; set + e.g. `https://flatpak.vocalinux.com/repo` for a custom domain) + - `FLATPAKREPO_URL` — where users fetch `vocahq.flatpakrepo` (default + `https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo`) + - `FLATPAKREF_URL` — where users fetch the app `.flatpakref` (default + `https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref`) +6. **Trigger** — push a stable `v*` tag (alpha/beta/rc tags do not publish to + the remote), or re-run `publish-flatpak-remote` on an existing release run + after configuring the secrets. + +Without the secrets the job skips itself (same pattern as `publish-aur`), so +an unconfigured remote never fails a release. + +## How a publish works + +`release.yml → publish-flatpak-remote` runs on stable tags only, after the +GitHub Release exists and the two arch builds finish: + +1. Downloads `Vocalinux--{x86_64,aarch64}.flatpak` from the build + jobs. +2. Probes the remote repo for an existing `gh-pages` branch; only a confirmed + first publish may skip the history checkout — a transient failure aborts + instead of seeding an empty repo over the live remote. It then checks out + `gh-pages` and copies `repo/` over, so existing objects and deltas + survive — clients pull an incremental delta each release, not the whole + app. +3. `flatpak build-import-bundle --gpg-sign` imports each bundle under the + signing key. +4. `flatpak build-update-repo --generate-static-deltas --gpg-sign + --gpg-import` rebuilds the summary, signs it, and generates static deltas. +5. Regenerates `vocahq.flatpakrepo` (URL + base64 public key), + `com.vocalinux.Vocalinux.flatpakref` (the one-command app ref carrying the + `RuntimeRepo=` pointer to Flathub), `vocahq.gpg` (the bare public key, for + the key-rotation path below), and the index page from + `packaging/flatpak/index.html`. +6. Publishes the directory back to `gh-pages` via + `peaceiris/actions-gh-pages`. + +## Verifying a publish + +```bash +flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo +flatpak remote-info vocahq com.vocalinux.Vocalinux # signed commit, both arches +flatpak install vocahq com.vocalinux.Vocalinux +flatpak run com.vocalinux.Vocalinux +# after the next tag: flatpak update pulls the delta +``` + +## Key rotation and loss + +Generate a fresh key, replace `FLATPAK_GPG_PRIVATE_KEY` (and +`FLATPAK_GPG_PASSPHRASE` if used), then re-run `publish-flatpak-remote`. The +published `.flatpakrepo`/`.flatpakref` then carry the new `GPGKey=`, so new +installs trust it immediately. + +Existing installs must import the new key into the remote's trusted keyring — +re-running `remote-add` does **not** replace it (with `--if-not-exists` it +does nothing at all), and `flatpak update` fails verification until the key +is updated. Point users at the public key published beside the repo: + +```bash +curl -fsSL https://vocahq.github.io/vocalinux-flatpak/vocahq.gpg -o /tmp/vocahq.gpg +flatpak remote-modify --gpg-import=/tmp/vocahq.gpg vocahq +flatpak update +``` + +`remote-modify --gpg-import` adds the key — the old one stays trusted, which +is what makes a routine rotation seamless. To *revoke* a leaked key, drop the +remote's keyring instead: delete and re-add the remote (installed apps keep +their `vocahq` origin and update normally afterwards): + +```bash +flatpak remote-delete --force vocahq # --force: the remote is still in use +flatpak remote-add vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo +flatpak update +``` + +Either way is worth a line in that release's notes. The revoke certificate is +for the day the key leaks, not for routine rotation. diff --git a/docs/GATEWAY_EMBED.md b/docs/GATEWAY_EMBED.md new file mode 100644 index 0000000..81c4af1 --- /dev/null +++ b/docs/GATEWAY_EMBED.md @@ -0,0 +1,77 @@ +# Local VocaGateway from Vocalinux (optional) + +Vocalinux can start a **local** [VocaGateway](https://github.com/VocaHQ/vocagateway) +container from **Settings → Speech Model → Advanced**, next to Remote Server. This is an +optional power-user path. It is **not** on-device processing: microphone audio +still leaves the desktop client and is transcribed by the gateway process on +your machine (or LAN). + +The default speech engine remains **whisper.cpp** on-device. Enabling a local +gateway never changes that default by itself. Use **Use this Gateway** only when +you want Vocalinux to talk to the gateway over the existing Remote API settings +(`remote_api_*`, endpoint `/v1/audio/transcriptions`). See [HTTP_REMOTE.md](HTTP_REMOTE.md). + +## Requirements (Linux MVP) + +- **podman** (preferred) or **docker**, with a working `compose` plugin (or + `podman-compose` / `docker-compose`). +- Vocalinux does **not** bundle a container engine inside the AppImage. +- **Flatpak**: the sandbox cannot reach the host container socket. Run the + native package or AppImage, or start VocaGateway on the host yourself and + point Remote Server at it. + +## What Vocalinux does + +1. Pins upstream **VocaGateway `v0.1.0`** (git tag) under your XDG cache and uses + that checkout's `compose.yaml` with Compose project name `vocagateway`. +2. Writes a private bootstrap token under `~/.config/vocalinux/gateway_embed/` + (mode `600`) and a matching Compose `.env`. +3. Runs `compose -f compose.yaml --profile cpu up -d` for the `gateway` service. + The v0.1.0 GitHub release has no published container image assets, so the first + start **builds** the local image `vocagateway:v0.1.0` from that tag (override with + `VOCAGATEWAY_IMAGE`; never `latest`). Later starts reuse the image/volume. +4. Polls `GET /health/live` and `GET /health/ready`, and (when live) authenticated + `GET /v1/admin/pairing` for phone pairing. +5. Stops with `compose down` **without** `-v`, so the named data volume (models, + config) is kept. + +## Status labels + +| Status | Meaning | +| --- | --- | +| Stopped | Not running. | +| Starting | Compose up in progress, or live probe not green yet. | +| Live | Process answers `/health/live`. | +| Pairable | Live, plus a **non-loopback** phone URL and bearer token (QR-safe). | +| Ready | `/health/ready` is 200 (model selected in the gateway WebUI). | +| Error | Start/stop or probe failure (see the subtitle). | + +Pairable can appear before Ready: you can pair a phone while a model is still +downloading. + +## LAN / phone + +By default the published host is `127.0.0.1` (desktop-only). Turn on **Allow LAN +access for Phone** to set `VOCAGATEWAY_PUBLISH_HOST=0.0.0.0` and to advertise a +LAN URL in pairing. Open port `8765` in your firewall only on trusted networks. +Never put `127.0.0.1` / `localhost` in the phone QR. If a LAN toggle fails, +compose keeps the previous bind, Vocalinux restores the embed `.env`, and the +switch snaps back with an error (Stop and Run again, or retry). + +## Tray and Quit + +The tray menu offers **Stop local Gateway** whenever compose is actually running +(Starting, Live, Pairable, Ready, or Error), including a leftover from a previous +Vocalinux session. Vocalinux does not auto-start the gateway on login in v1. + +**Quit does not stop compose.** The pinned compose file uses `restart: +unless-stopped`, so a LAN-published gateway on `0.0.0.0:8765` keeps running after +you quit Vocalinux. The next launch restores the Allow LAN switch from the running +compose publish host. Use **Stop local Gateway** in Settings or the tray when you +want it gone. Do not assume Quit tears it down. + +## Honesty + +VocaGateway is optional self-hosted infrastructure for the Voca family. There is +no Voca account and no hosted Voca cloud. Prefer a trusted LAN, Tailscale, or +HTTPS. Do not expose port 8765 to the public internet. diff --git a/docs/HTTP_REMOTE.md b/docs/HTTP_REMOTE.md new file mode 100644 index 0000000..ab3d65e --- /dev/null +++ b/docs/HTTP_REMOTE.md @@ -0,0 +1,282 @@ +# Remote HTTP transcription + +Vocalinux can offload speech recognition to a remote HTTP server instead of running a local model. Use this when you want faster transcription on a more powerful machine, to share one model across clients, or to keep a laptop free of GPU/model overhead. + +Vocalinux speaks two wire formats out of the box: + +- **OpenAI-compatible**: `POST /v1/audio/transcriptions` (e.g. OpenAI, [Speaches](https://github.com/speaches-ai/speaches), LocalAI, FunASR/SenseVoice) +- **whisper.cpp server**: `POST /inference` (the binary shipped with [whisper.cpp](https://github.com/ggerganov/whisper.cpp)) + +Pick whichever your server exposes. The rest of this guide applies to both. + +> **Tip: share the server with your phone.** These HTTP formats are open enough that the same self-hosted server can also back mobile dictation apps. On Android, apps like *Dictate* and *Transcribro* speak OpenAI-compatible Whisper; on iOS, Shortcuts-based dictation clients can hit the same endpoint. Run one Whisper server, point your laptop and phone at it, and you get consistent dictation everywhere without uploading audio to a third party. + +## Local VocaGateway (optional) + +On Linux you can start an optional local [VocaGateway](https://github.com/VocaHQ/vocagateway) from **Settings → Speech Model → Advanced** (podman-first, docker fallback). That path uses the same Remote API settings described below, with endpoint `/v1/audio/transcriptions`. It is **not** on-device recognition: audio goes to the gateway container on your machine. Details: [GATEWAY_EMBED.md](GATEWAY_EMBED.md). + +## How it works + +When the **Remote API** engine is active, Vocalinux: + +1. Captures microphone audio locally (16 kHz, mono, 16-bit PCM). +2. On end-of-utterance, packages the buffer as a WAV file in memory. +3. Uploads it using the configured HTTP format. +4. Reads the transcribed text from the JSON response and types it into the focused window. + +No audio is written to disk. VAD and segmentation stay local; only the heavy ASR step is remote. + +## Server-side setup + +You need a server that accepts audio uploads and returns text. Three common setups: + +### Option A: whisper.cpp server + +Build and run the bundled HTTP server from the [whisper.cpp](https://github.com/ggerganov/whisper.cpp) repo: + +```bash +git clone https://github.com/ggerganov/whisper.cpp +cd whisper.cpp +make server +./models/download-ggml-model.sh medium.en +./build/bin/whisper-server \ + --model models/ggml-medium.en.bin \ + --host 0.0.0.0 \ + --port 8080 +``` + +The server now listens on `http://:8080`. The endpoint is `/inference`. + +### Option B: OpenAI-compatible server (Speaches, LocalAI, etc.) + +Run any server that implements `POST /v1/audio/transcriptions`. Example with [Speaches](https://github.com/speaches-ai/speaches): + +```bash +docker run --rm -p 8000:8000 ghcr.io/speaches-ai/speaches:latest +``` + +The endpoint is `/v1/audio/transcriptions`. + +### Option C: FunASR/SenseVoice local server + +[FunASR](https://github.com/modelscope/FunASR) can run a local +OpenAI-compatible transcription service, which keeps audio on your own machine +or LAN while letting Vocalinux use SenseVoice or Paraformer-family models: + +```bash +pip install 'torch>=2.13.0' torchaudio +pip install funasr vllm fastapi uvicorn python-multipart +funasr-server --device cuda +``` + +The server listens on `http://localhost:8000` by default and exposes +`POST /v1/audio/transcriptions`. In Vocalinux, set: + +- **Server URL**: `http://localhost:8000` +- **API Endpoint**: `OpenAI/FunASR (/v1/audio/transcriptions)` +- **Model**: `sensevoice` + +Use `--device cpu` for CPU-only testing if your FunASR install supports it. A +remote GPU host works too: bind the server to the LAN interface, open the port, +and point Vocalinux at that host. + +### Network checklist + +- The server must be reachable from the client over HTTP or HTTPS. +- If you bind to `0.0.0.0`, open the port in your firewall (`ufw allow 8080/tcp`, etc.). +- For anything outside a trusted LAN, terminate TLS in front of the server (Caddy, nginx, Traefik) and require an API key. The audio you upload is private speech. +- If you also use the server from a mobile dictation app, terminate TLS even on a LAN. Most Android and iOS apps refuse cleartext HTTP. + +## Client-side setup (Vocalinux) + +### Via the Settings dialog + +The remote engine is a power-user option in the **Speech Engine** settings. + +1. Launch Vocalinux and open **Settings → Speech Engine → Remote Server**. +2. Toggle **Use remote server** on. This overrides whatever local engine is selected above it. +3. Fill in the fields: + - **Server URL**: base URL of the server, e.g. `http://192.168.1.100:8080` (no trailing slash needed; one is stripped automatically). + - **API Key** (optional): sent as `Authorization: Bearer `. Leave blank if your server doesn't require auth. + - **API Endpoint**: pick **Whisper.cpp (`/inference`)** or **OpenAI/FunASR (`/v1/audio/transcriptions`)** to match your server. + - **Model**: model identifier sent to OpenAI-compatible servers. Use `whisper-1` for classic Whisper servers, or `sensevoice` for FunASR/SenseVoice. +4. Click **Test Connection**. Success means the URL is reachable and credentials (if any) are accepted. A failure here is only a warning; Vocalinux will try again on the first transcription. + +Settings auto-save and re-initialise the engine immediately, so you can start dictating as soon as the test passes. Toggling the switch off restores the local engine selected on the Speech Engine page in Settings. + +### Via the config file + +The same options live in `~/.config/vocalinux/config.json`: + +```json +{ + "speech_recognition": { + "engine": "remote_api", + "remote_api_url": "http://192.168.1.100:8080", + "remote_api_key": "", + "remote_api_endpoint": "/inference", + "remote_api_model": "whisper-1" + } +} +``` + +Restart Vocalinux after editing the file by hand. + +FunASR/SenseVoice example: + +```json +{ + "speech_recognition": { + "engine": "remote_api", + "remote_api_url": "http://localhost:8000", + "remote_api_key": "", + "remote_api_endpoint": "/v1/audio/transcriptions", + "remote_api_model": "sensevoice" + } +} +``` + +## Wire protocol reference + +If you're writing a custom server, this is exactly what the client sends. + +### Request (whisper.cpp format, `/inference`) + +``` +POST {server_url}/inference HTTP/1.1 +Authorization: Bearer # only if configured +Content-Type: multipart/form-data; boundary=... + +--boundary +Content-Disposition: form-data; name="file"; filename="audio.wav" +Content-Type: audio/wav + + +--boundary +Content-Disposition: form-data; name="temperature" + +0.0 +--boundary +Content-Disposition: form-data; name="temperature_inc" + +0.2 +--boundary +Content-Disposition: form-data; name="response_format" + +json +--boundary +Content-Disposition: form-data; name="language" + +en +--boundary-- +``` + +### Request (OpenAI-compatible format, `/v1/audio/transcriptions`) + +``` +POST {server_url}/v1/audio/transcriptions HTTP/1.1 +Authorization: Bearer +Content-Type: multipart/form-data; boundary=... + +--boundary +Content-Disposition: form-data; name="file"; filename="audio.wav" +Content-Type: audio/wav + + +--boundary +Content-Disposition: form-data; name="model" + +whisper-1 +--boundary +Content-Disposition: form-data; name="language" + +en +--boundary-- +``` + +### Response (`/inference` and `/v1/audio/transcriptions`) + +```json +{ "text": "the transcribed utterance" } +``` + +For OpenAI-compatible FunASR/SenseVoice wrappers, extra metadata is allowed and +ignored by Vocalinux: + +```json +{ + "text": "the transcribed utterance", + "language": "en", + "emotion": "neutral", + "segments": [ + { "text": "the transcribed utterance", "start": 0.0, "end": 1.4 } + ] +} +``` + +If `text` is not present, Vocalinux also accepts `transcript`, `transcription`, +or a `segments` list with per-segment `text` fields. If a SenseVoice wrapper +returns rich labels inline, for example `<|en|><|NEUTRAL|><|Speech|>hello`, +Vocalinux strips the leading labels before text injection. + +Empty utterances should return `{"text": ""}`. HTTP 4xx/5xx responses are surfaced as transcription errors in the Vocalinux log. + +### Language handling + +- Vocalinux's UI language `auto` is sent as **no `language` field** (server auto-detects). +- `en-us` is normalised to `en`. +- Anything else is forwarded verbatim. + +### Timeouts + +- Connection test: **5 s** +- Transcription request: **30 s** + +A request that exceeds the transcription timeout is logged and dropped; the next utterance is sent fresh. + +## Verifying with `curl` + +Quick sanity check without launching the GUI: + +```bash +# Whisper.cpp server +curl -F file=@sample.wav \ + -F temperature=0.0 \ + -F response_format=json \ + http://192.168.1.100:8080/inference + +# OpenAI-compatible +curl -H "Authorization: Bearer $API_KEY" \ + -F file=@sample.wav \ + -F model=whisper-1 \ + http://192.168.1.100:8000/v1/audio/transcriptions + +# FunASR/SenseVoice +curl -F file=@sample.wav \ + -F model=sensevoice \ + http://localhost:8000/v1/audio/transcriptions +``` + +The multipart endpoints should return JSON with a text field or one of the +compatible response shapes above. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| Connection test reports "failed" | Wrong host/port, firewall, or server not running | Check `curl ` from the client; check server logs | +| HTTP 401/403 | API key missing or wrong | Set the **API Key** field; ensure the server is configured for that key | +| HTTP 404 on first send | Endpoint format mismatch | Switch the **API Endpoint** dropdown to the format your server actually exposes | +| FunASR/SenseVoice server returns model errors | Model field does not match the served model | Try `sensevoice`, `paraformer`, or the exact model configured on the server | +| SenseVoice labels appear in injected text | Wrapper returned labels somewhere other than the leading transcript prefix | Return plain text in `text` and put language/emotion/speech labels in JSON metadata | +| Transcription always empty | Server rejected the WAV (sample rate, channels) | Confirm server accepts 16 kHz mono 16-bit WAV; check server logs | +| First transcription is slow | Cold model load on the server | Warm the server before dictating, or increase the client timeout server-side | +| Garbled text in foreign language | Wrong `language` setting | Set the language explicitly in Settings instead of `auto` | + +Logs live at `~/.local/share/vocalinux/vocalinux.log` (or wherever your `XDG_DATA_HOME` points). Look for lines tagged `Remote API` for client-side detail. + +## Security notes + +- The API key is stored in plain JSON in `~/.config/vocalinux/config.json`. Treat that file the same way you treat any other secret on disk. +- Audio is sent as raw WAV. Anyone on the wire can hear it, so use HTTPS outside a fully trusted network. +- Vocalinux does **not** validate TLS certificate pins; standard system trust is used. Self-signed certs require importing the CA into your system trust store. diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..03bac2b --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,348 @@ +# Installation guide + +How to install Vocalinux on Linux. Short overview: [project README](../README.md). + +| Path | When to use | +|------|-------------| +| [Recommended installer](#recommended-installer) | Most users | +| [AppImage](#appimage) | Portable binary; no system package install | +| [Distro packages (.deb / .rpm)](#distro-packages-deb--rpm) | Debian 12+, Ubuntu 24.04+, Fedora | +| [AUR](#arch-linux-aur) | Arch / Manjaro | +| [Flatpak](#flatpak) | Release `.flatpak` or local build | +| [Snap](#snap-ubuntu-snap-store) | Ubuntu Snap Store (`--edge`) or GitHub `.snap` | +| [Nix](#nix--nixos) | Flake package, Home Manager, or the NixOS module | +| [From source](#from-source) | Contributors or custom trees | +| [Manual / PyPI](INSTALL_MANUAL.md) | Full control or pip-only workflows | +| [Troubleshooting](TROUBLESHOOTING.md) | Tray, audio, injection, models | + +## Recommended installer + +Download, review if you like, then run: + +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +Installs the latest release with **whisper.cpp** by default. For a pinned version, see [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases) or pass `--tag=...`. + +The installer: + +- Installs whisper.cpp (typically ~1-2 minutes; no full PyTorch stack) +- Detects GPU / Vulkan (AMD, Intel, NVIDIA) +- Installs neural VAD when ONNX Runtime is available +- Downloads the default whisper.cpp tiny model (~74MB), verified against pinned checksums +- Sets up desktop integration and launch wrappers + +The source installer uses committed, hash-verified Python dependency exports, +including its pip and source-build tools. GTK/PyGObject still comes from your +distribution, and the venv retains access to system packages. A missing export +or unavailable pinned package causes that installation step to fail; the +existing optional-engine and VAD fallbacks still apply. + +Remote installs run the installer from the selected release tag. Older tags +therefore retain their original dependency-install behavior; downloading a new +bootstrap script does not change an older release's pins. + +### Installer modes + +```bash +./install.sh # Interactive (recommended) +./install.sh --auto # Defaults: whisper.cpp +./install.sh --auto --engine=whisper # OpenAI Whisper +./install.sh --auto --engine=faster_whisper # Faster Whisper (CPU) +./install.sh --auto --engine=vosk # VOSK only +./install.sh --auto --engine=parakeet # Parakeet (CPU) +./install.sh --auto --engine=remote_api # Remote HTTP API +./install.sh --dev # Editable install + test tools +./install.sh --help # Full flag list +``` + +| Engine | When to use | Typical install | +|--------|-------------|-----------------| +| **whisper.cpp** (default) | Best default; Vulkan GPU | ~1-2 min, ~74MB model | +| **Whisper** (OpenAI) | PyTorch / CUDA | ~5-10 min, large download | +| **Faster Whisper** | CPU Whisper via CTranslate2 / INT8 | Model sizes like OpenAI Whisper | +| **VOSK** | Low RAM / minimal | ~30 sec, ~40MB | +| **Parakeet** | CPU; 25 European languages | Model ~639MB | +| **Remote API** | User-configured HTTP server | No local model | + +Useful flags: `--tag=TAG`, `--skip-models`, `--rebuild-whispercpp`, `--no-rebuild-whispercpp`, `--venv-dir=PATH`, `--test`. + +`--engine=NAME` accepts `whisper_cpp` (default), `whisper`, `faster_whisper`, `vosk`, `parakeet`, `remote_api`. + +### What the installer does + +1. Detects the distribution and installs system packages +2. Creates a Python venv from the system interpreter with `--system-site-packages` (for distro GTK / PyGObject) +3. Installs Vocalinux and the chosen speech engine +4. Optionally installs neural VAD (`onnxruntime`) +5. Downloads a default speech model (checksum-verified) +6. Installs icons, desktop entry, and `~/.local/bin` wrappers + +## AppImage + +From [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases): + +```bash +chmod +x Vocalinux-*-x86_64.AppImage # or aarch64 +./Vocalinux-*-x86_64.AppImage +``` + +Built against glibc 2.35, so it starts on Debian 12+, Ubuntu 22.04+, Fedora 36+, Arch, and Tumbleweed. Older bases (RHEL 9, Debian 11, Ubuntu 20.04) are below that AppImage floor. The installer and PyPI package still require a distro that ships Python 3.11+, so they are not a workaround for Debian 11 or Ubuntu 20.04. + +Still needs host text-injection tools (`xdotool` on X11; `wtype` / `ydotool` / clipboard tools on Wayland). Current AppImages rebuild whisper.cpp with Vulkan and use the host GPU driver (`vulkaninfo --summary`). Prefer the installer when you want system deps, a CUDA build, and models set up automatically. + +## Distro packages (.deb / .rpm) + +From [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases), pick the file matching your distro and CPU: + +```bash +# Debian / Ubuntu (amd64; arm64 files are attached too) +sudo apt install ./vocalinux_*_amd64.deb + +# Fedora (x86_64; aarch64 files are attached too) +sudo dnf install ./vocalinux-*.x86_64.rpm +``` + +These are thin packages: Python, GTK, PyGObject and the AppIndicator typelib come from the distribution, and `apt`/`dnf` resolves them. `xdotool`, `wtype`, `ydotool`, the clipboard tools and IBus are `Recommends`, matching the optional feature set of the other paths. Only `pywhispercpp` and `pynput` are vendored inside the package — `pywhispercpp` is packaged by neither distro and `pynput` is missing on Fedora. They land under `/usr/lib/vocalinux/vendor`, on `sys.path` via the `vocalinux` launcher. + +The `.deb` needs a distro that ships Python 3.11+: Debian 12+ and Ubuntu 24.04+ (Ubuntu 22.04 ships 3.10). whisper.cpp runs on CPU in these packages — the PyPI wheel, not the Vulkan build the AppImage carries. There is no auto-update: a new release is a new package install. A PPA and a COPR are not published yet. + +On Wayland, the default Right Alt shortcut reads keyboard devices through evdev, so your user needs `input` group membership or the hotkey is silently disabled: + +```bash +sudo usermod -aG input $USER # then log out and back in +``` + +Joining `input` grants read access to every keyboard and pointer device — X11 needs none of this. The launcher re-execs through `sg input` when `/etc/group` already lists the membership but the current session has not picked it up, so an `usermod` between package install and next login applies without a reboot. The source installer does this step for you; the packages cannot. + +## Arch Linux (AUR) + +```bash +yay -S vocalinux +``` + +See [AUR.md](AUR.md). + +## Flatpak + +Install and auto-update via the self-hosted VocaHQ remote (`flatpak update` picks up each release): + +```bash +flatpak install https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref +``` + +The `.flatpakref` adds the `vocahq` remote and resolves the GNOME runtime through Flathub. Manually instead: + +```bash +flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo +flatpak install vocahq com.vocalinux.Vocalinux +``` + +Or sideload a GitHub Release bundle (`Vocalinux--x86_64.flatpak` or `-aarch64.flatpak`) after the Flathub GNOME runtime is present — bundles do not auto-update: + +```bash +flatpak install --user ./Vocalinux--x86_64.flatpak +``` + +Local build (contributors): + +```bash +flatpak install flathub org.gnome.Platform//50 org.gnome.Sdk//50 +flatpak-builder --user --install --force-clean build-dir \ + packaging/flatpak/com.vocalinux.Vocalinux.yml +flatpak run com.vocalinux.Vocalinux +``` + +Whisper.cpp + Vulkan. It is **not on Flathub** (submission [flathub#9368](https://github.com/flathub/flathub/pull/9368) closed 2026-07-23 on policy grounds). Details: [packaging/flatpak/README.md](../packaging/flatpak/README.md). + +On native X11, hotkeys work without device access (GNOME X11 is the exception: +its X server hides physical keys from the X11 listener, so it also needs +`/dev/input`). On Wayland, the built-in hotkey needs `/dev/input` (`--device=input` on Flatpak 1.15.6 or newer, or +`--device=all` on older versions); otherwise enable External Activation and +bind a desktop shortcut to the D-Bus `Toggle` command in +[Running without device access](../packaging/flatpak/README.md#running-without-device-access). +Desktop shortcuts only toggle dictation, so they cannot support push-to-talk. + +## Snap (Ubuntu Snap Store) + +Listing: [snapcraft.io/vocalinux](https://snapcraft.io/vocalinux) (issue [#48](https://github.com/VocaHQ/vocalinux/issues/48)). Recipe: `snap/snapcraft.yaml`. Tagged `v*` releases attach `vocalinux__amd64.snap` to the GitHub Release and try Snap Store `edge` and `candidate` when credentials are set. `stable` is promoted from `candidate` after QA. + +Build inputs, the PR/release artifact gate, and its verification limits are +documented in [SNAP.md](SNAP.md). + +Store install, once Canonical lists the revision: + +```bash +sudo snap install vocalinux --edge +sudo snap connect vocalinux:audio-record # if mic is not auto-connected +sudo snap connect vocalinux:raw-input # global keyboard shortcuts (evdev) +sudo snap connect vocalinux:hardware-observe # list keyboards (/proc/bus/input/devices) +``` + +Native Wayland typing uses the RemoteDesktop portal: the compositor asks once on first dictation, nothing to connect. The recipe ships **no `uinput` plug** and no ydotool; the Store denied the super-privileged grant ([the request thread](https://forum.snapcraft.io/t/53528)), which held the 0.17.0+ revisions and left **v0.16.2 (rev 7)** on edge. On KDE Plasma, KWin's portal can mistype letter case ([KDE bug 489021](https://bugs.kde.org/show_bug.cgi?id=489021)); set Virtual Keyboard to "IBus Wayland" so dictation types correctly. + +Under strict confinement the snap reaches audio through the PulseAudio/PipeWire server, not the sound card, so Settings → Audio lists the server's microphones (via `pactl`) and records from the one you pick. whisper.cpp in the snap is built without a GPU backend and runs on the CPU, and host tools such as `nvidia-smi` and `vulkaninfo` are not visible inside it. For Vulkan acceleration use the [AppImage](#appimage) or the [installer](#recommended-installer) instead. + +GitHub `.snap` files through v0.18.2 declare `uinput`, so KDE users who want ydotool can sideload that build and connect the plug manually: + +```bash +sudo snap install --dangerous ./vocalinux_0.18.2_amd64.snap +sudo snap connect vocalinux:audio-record +sudo snap connect vocalinux:raw-input +sudo snap connect vocalinux:hardware-observe +sudo snap connect vocalinux:uinput # only in the v0.18.x sideload; the recipe no longer ships it +``` + +`--dangerous` is required because this file is not a Store revision and will not refresh from the Store; once the Store carries a newer revision, `sudo snap install vocalinux --edge` (or `snap refresh`) replaces it. + +## Nix / NixOS + +The repo is a flake: GTK, PyGObject and the AppIndicator typelib come from nixpkgs, and `pywhispercpp` (absent from nixpkgs, same gap as Debian and Fedora) is built from its vendored PyPI sdist. The wrapped binary carries every injector and clipboard tool on PATH. + +```bash +nix run github:VocaHQ/vocalinux # try it +nix profile install github:VocaHQ/vocalinux +``` + +On NixOS, `vocalinux.nixosModules.vocalinux` provides `programs.vocalinux.enable`, which installs the package and enables `hardware.uinput`. Add each dictating user to `input` and `uinput` for Wayland shortcuts and ydotool typing. Home Manager users get `vocalinux.homeManagerModules.vocalinux` with the same `programs.vocalinux` options — device permissions stay on the system side. Usage snippets and scope notes: [packaging/nix/README.md](../packaging/nix/README.md). + +## From source + +```bash +git clone https://github.com/VocaHQ/vocalinux.git +cd vocalinux +./install.sh +``` + +## System requirements + +| Requirement | Details | +|-------------|---------| +| **OS** | Ubuntu 24.04+, Debian 12+, Fedora 42+, Arch, openSUSE Tumbleweed | +| **Python** | 3.11 or newer | +| **Display** | X11 or Wayland | +| **Hardware** | Microphone; GPU optional (Vulkan) | +| **Disk** | ~200MB with default whisper.cpp model | +| **RAM** | 4GB minimum; 8GB comfortable | + +The distro must ship Python 3.11+ because Vocalinux uses distro PyGObject (`python3-gi`). Ubuntu 22.04 (Python 3.10) and Debian 11 (3.9) are below that floor. Distro matrix: [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md). + +### GPU (optional) + +whisper.cpp uses Vulkan when available (AMD, Intel, NVIDIA). Check with `vulkaninfo --summary`. + +## Running Vocalinux + +```bash +vocalinux # if ~/.local/bin is on PATH +~/.local/share/vocalinux/venv/bin/vocalinux # direct +source venv/bin/activate && vocalinux # source checkout +``` + +Or launch from the application menu. + +### CLI + +```bash +vocalinux --help +vocalinux --version +vocalinux --debug +vocalinux --engine whisper_cpp +vocalinux --engine whisper +vocalinux --engine vosk +vocalinux --engine parakeet +vocalinux --engine remote_api +vocalinux --model tiny +vocalinux --model medium.en-q5_0 +vocalinux --model large-v3-turbo +vocalinux --wayland +vocalinux --start-minimized +``` + +## Autostart + +**Start on Login** writes an XDG autostart desktop entry (`~/.config/autostart/`). It does not install a systemd unit. Enable from the first-run dialog, tray menu, or Settings. + +## Data locations (XDG) + +| Directory | Purpose | +|-----------|---------| +| `~/.config/vocalinux/` | Configuration | +| `~/.local/share/vocalinux/` | Data and speech models | +| `~/.local/share/applications/` | Desktop entry | +| `~/.local/share/icons/hicolor/scalable/apps/` | Icons | + +## whisper.cpp (default engine) + +Default engine: C++ Whisper port with Vulkan GPU support and lower install cost than the PyTorch Whisper stack. + +| Size | Approx. | Use | +|------|---------|-----| +| tiny | ~74MB | Fast dictation (default) | +| base | ~141MB | Balance | +| small | ~465MB | Better accuracy | +| medium | ~1.5GB | High accuracy | +| large | ~3.0GB | Best accuracy | + +In Settings → Speech Model, simple setup steers language and speed/accuracy. Expand **Advanced** for engine, **Model Size**, and **Specialization** (multilingual, English-only, quantized, Turbo, legacy large). More detail: [USER_GUIDE.md](USER_GUIDE.md). + +Reuse an existing `pywhispercpp` build: + +```bash +./install.sh --auto # reuse if present +./install.sh --auto --rebuild-whispercpp # force rebuild +``` + +Switch engines in Settings → Speech Model (Advanced), or edit `~/.config/vocalinux/config.json` (`engine`: `whisper_cpp` | `whisper` | `vosk` | `parakeet` | `remote_api`). + +## Uninstall + +From a source checkout, `./uninstall.sh` removes the install and that checkout's `venv/`, `build/`, `dist/`, and Python bytecode. `--keep-config` and `--keep-data` leave `~/.config/vocalinux` and `~/.local/share/vocalinux` in place. + +```bash +./uninstall.sh +./uninstall.sh --keep-config +./uninstall.sh --keep-data +``` + +The website command downloads this same script and runs it: + +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/uninstall.sh -o /tmp/vul.sh +bash /tmp/vul.sh +``` + +That removes Vocalinux from your home directory (config, data, launchers, desktop entry, icons). It does not delete `venv/`, `build/`, `dist/`, or `.pyc` files in the directory where you ran it. + +From the source checkout, the equivalent by hand: + +```bash +rm -rf venv ~/.config/vocalinux ~/.local/share/vocalinux +rm -f activate-vocalinux.sh +rm -f ~/.local/share/applications/vocalinux.desktop +rm -f ~/.local/share/icons/hicolor/scalable/apps/vocalinux*.svg +gtk-update-icon-cache -f -t ~/.local/share/icons/hicolor +``` + +## Update + +See [UPDATE.md](UPDATE.md). + +```bash +curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh +bash /tmp/vl.sh +``` + +## More documentation + +| Doc | Contents | +|-----|----------| +| [INSTALL_MANUAL.md](INSTALL_MANUAL.md) | Manual install, PyPI/pipx, per-distro packages | +| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Common failures and fixes | +| [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md) | Support matrix | +| [USER_GUIDE.md](USER_GUIDE.md) | Day-to-day use | +| [SUPPORT.md](../SUPPORT.md) | Where to get help | +| [Documentation index](README.md) | Full list | diff --git a/docs/INSTALL_MANUAL.md b/docs/INSTALL_MANUAL.md new file mode 100644 index 0000000..1c09688 --- /dev/null +++ b/docs/INSTALL_MANUAL.md @@ -0,0 +1,194 @@ +# Manual installation and PyPI + +Use this when the [recommended installer](INSTALL.md) is not a fit, or you want an explicit package list. Prefer `./install.sh` for a normal desktop install. + +The commands below are human-readable baselines for manual installs. The +installer's authoritative package inventory, including build helpers and +fallbacks, is [`scripts/distro-package-map.yaml`](../scripts/distro-package-map.yaml). +Its generated shell module is committed so installations do not need a YAML +parser. + +Related: [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md), [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + +## System packages by distribution + +Install desktop/system dependencies **before** creating a venv or running `pip install vocalinux`. Pip cannot install GTK typelibs, AppIndicator, PortAudio, or text-injection tools. + +Debian 11 and earlier are unsupported; the Debian instructions start at Debian +12, which is the oldest release with the required Python 3.11. + +### Ubuntu + +```bash +sudo apt update +sudo apt install -y \ + python3-pip python3-venv python3-dev \ + python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 \ + libgirepository1.0-dev libcairo2-dev portaudio19-dev \ + build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \ + wget curl unzip + +# Tray: older Ubuntu → gir1.2-appindicator3-0.1 +# newer Ubuntu → gir1.2-ayatanaappindicator3-0.1 +sudo apt install -y gir1.2-ayatanaappindicator3-0.1 + +sudo apt install -y xdotool # X11 +sudo apt install -y wtype wl-clipboard xclip xsel # Wayland helpers +``` + +On Ubuntu 24.04+ or Pop!_OS, install `libgirepository-2.0-dev` if `libgirepository1.0-dev` is missing. + +### Debian 12 + +```bash +sudo apt update +sudo apt install -y \ + python3-pip python3-venv python3-dev \ + python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 \ + libgirepository1.0-dev libcairo2-dev portaudio19-dev \ + build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \ + wget curl unzip \ + gir1.2-ayatanaappindicator3-0.1 \ + xdotool wtype wl-clipboard xclip xsel +``` + +### Debian 13+ + +```bash +sudo apt update +sudo apt install -y \ + python3-pip python3-venv python3-dev \ + python3-gi python3-gi-cairo \ + gir1.2-gtk-3.0 gir1.2-ibus-1.0 \ + libgirepository-2.0-dev libcairo2-dev portaudio19-dev \ + build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \ + wget curl unzip \ + gir1.2-ayatanaappindicator3-0.1 \ + xdotool wtype wl-clipboard xclip xsel +``` + +Debian notes (OpenSSL build deps, ydotool, etc.): [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md). + +### Fedora + +```bash +sudo dnf install -y \ + python3-pip python3-devel python3-virtualenv \ + python3-gobject gtk3 ibus-devel libayatana-appindicator-gtk3 \ + gobject-introspection-devel portaudio-devel cairo-devel \ + gcc gcc-c++ make pkg-config cmake patchelf \ + wget curl unzip xdotool wtype wl-clipboard xclip xsel +``` + +### Arch Linux + +Prefer the AUR package (`yay -S vocalinux`). Manual packages: + +```bash +sudo pacman -S --needed \ + python-pip python-gobject gtk3 ibus \ + libayatana-appindicator gobject-introspection \ + python-cairo portaudio python-virtualenv \ + base-devel pkg-config cmake patchelf \ + wget curl unzip xdotool wtype wl-clipboard xclip xsel +``` + +### openSUSE Tumbleweed + +```bash +PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.version_info.minor}")') + +sudo zypper install -y \ + "${PYVER}-pip" "${PYVER}-gobject" "${PYVER}-gobject-cairo" \ + "${PYVER}-devel" "${PYVER}-virtualenv" \ + gtk3 ibus typelib-1_0-IBus-1_0 \ + typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \ + typelib-1_0-Notify-0_7 libnotify4 \ + gobject-introspection-devel portaudio-devel pkg-config cmake \ + gcc gcc-c++ make patchelf \ + wget curl unzip xdotool wtype wl-clipboard xclip xsel + +# Optional: whisper.cpp Vulkan builds +sudo zypper install -y vulkan-tools vulkan-devel shaderc +``` + +`-devel` packages are headers for compiling native Python deps, not "beta" packages. If `${PYVER}-virtualenv` is missing, try `${PYVER}-venv`. + +### Other distributions + +Gentoo, Alpine, Void, Solus, Mageia: [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md). + +## Install from a git checkout + +Use the **system** Python (`/usr/bin/python3`). Distro PyGObject (`gi`) is compiled for that interpreter only, so a venv from pyenv/conda/uv cannot import it even with `--system-site-packages`. + +```bash +cd vocalinux +/usr/bin/python3 -m venv venv --system-site-packages +source venv/bin/activate +pip install --upgrade pip setuptools wheel + +pip install . # standard +pip install ".[whisper]" # OpenAI Whisper extra +pip install ".[faster_whisper]" # Faster Whisper (CTranslate2) +pip install ".[parakeet]" # Parakeet (sherpa-onnx) +pip install ".[vad]" # neural VAD +pip install -e ".[dev,vad]" # development +``` + +### Desktop integration + +```bash +mkdir -p ~/.config/vocalinux \ + ~/.local/share/vocalinux/models \ + ~/.local/share/applications \ + ~/.local/share/icons/hicolor/scalable/apps + +cp vocalinux.desktop ~/.local/share/applications/ +VENV_PATH=$(realpath venv/bin/vocalinux) +sed -i "s|^Exec=vocalinux|Exec=$VENV_PATH|" ~/.local/share/applications/vocalinux.desktop + +cp resources/icons/scalable/*.svg ~/.local/share/icons/hicolor/scalable/apps/ +gtk-update-icon-cache -f -t ~/.local/share/icons/hicolor +``` + +## Install from PyPI + +Same system packages as above, then: + +```bash +python3 -m venv ~/.local/share/vocalinux-pypi/venv --system-site-packages +source ~/.local/share/vocalinux-pypi/venv/bin/activate +pip install --upgrade pip setuptools wheel +pip install vocalinux +# vosk engine: pip install "vocalinux[vosk]" +# faster-whisper: pip install "vocalinux[faster_whisper]" +# parakeet engine: pip install "vocalinux[parakeet]" +vocalinux +``` + +`--system-site-packages` lets the venv use distro GTK bindings (`python3-gi`). Building PyGObject from pip is often painful on Ubuntu/Debian. + +If the app starts but no speech model is present, open Settings and download a model, or use the official installer which downloads the default model during setup. + +### pipx + +```bash +# After system packages are installed: +pipx install vocalinux +vocalinux +``` + +pipx still cannot install system desktop libraries. Install those first with your package manager. + +## Why system packages are required + +Vocalinux uses GTK3, AppIndicator/Ayatana, PortAudio, and tools such as `xdotool`, `wtype`, and clipboard utilities. Those come from the distro. PyPI only supplies Python packages and wheels. + +## After install + +- Run: [INSTALL.md](INSTALL.md#running-vocalinux) +- Engines and models: [USER_GUIDE.md](USER_GUIDE.md) +- Failures: [TROUBLESHOOTING.md](TROUBLESHOOTING.md) diff --git a/docs/MODEL_SELECTION.md b/docs/MODEL_SELECTION.md new file mode 100644 index 0000000..944c21f --- /dev/null +++ b/docs/MODEL_SELECTION.md @@ -0,0 +1,61 @@ +# Choosing a speech model + +Vocalinux never downloads a speech model on its own. Changing the model pickers in +Settings → Speech Model only selects a model. If that model is not on disk yet, the +info card shows its download size and a **Download** button, and the transfer starts +only after you confirm it. Cancelling the download, or declining the confirmation, +puts the pickers back on the model you were already using. + +This page is a static guide to help you pick. The numbers are approximate starting +points, not measurements of your machine. If dictation feels slow, go one size down; +if it makes too many mistakes and speed is fine, go one size up. + +## whisper.cpp (default engine) + +| Your hardware | Suggested size | Notes | +|---|---|---| +| Under 8 GB RAM, CPU only | Tiny | Fastest. Accuracy is noticeably lower. | +| 8 to 16 GB RAM, CPU only | Tiny or Base | Base is a good default on a recent CPU. | +| 16 GB RAM or more, CPU only | Base, or Small if you can wait | Small on CPU can take a few seconds per sentence. | +| Vulkan GPU (AMD, Intel, NVIDIA) with under 8 GB RAM | Base | | +| Vulkan GPU with 8 GB RAM or more | Small | Older or low power GPUs may still prefer Base. | +| NVIDIA GPU (CUDA) with under 4 GB VRAM | Base | | +| NVIDIA GPU (CUDA) with 4 to 8 GB VRAM | Small | | +| NVIDIA GPU (CUDA) with 8 GB VRAM or more | Medium, or Large v3 Turbo | Large v3 Turbo is close to Large in accuracy at lower cost. | + +Settings → Speech Model may show a line such as "Suggested for this computer: Small". +That line is information only, based on the same rules as this table. It never changes +your model and never starts a download. + +## Model sizes and variants + +Each size comes in several variants. The quantized ones (`q5_0`, `q5_1`, `q8_0`) are +smaller files that use less memory, at a small cost in accuracy; `q8_0` is the +closest to the full model. The `.en` variants are English only and are a little +more accurate for English. + +| Size | Full download | Q8_0 | Q5 | Parameters | +|---|---|---|---|---| +| Tiny | 74 MB | 32 MB | 15 MB (q5_1) | 39M | +| Base | 141 MB | 82 MB | 60 MB (q5_1) | 74M | +| Small | 465 MB | 190 MB | 163 MB (q5_1) | 244M | +| Medium | 1.4 GB | 823 MB | 568 MB (q5_0) | 769M | +| Large v3 Turbo | 1.6 GB | 874 MB | 574 MB (q5_0) | 809M | +| Large (v3) | 2.9 GB | | 1.1 GB (q5_0) | 1550M | + +Sizes are the catalog values Vocalinux shows before a download; the file on disk can +differ slightly. + +If you already have a quantized model of the size you want (for example Small Q8_0), +there is no reason to download the full model of the same size. + +## Other engines + +- **Vosk**: the small models (about 40 MB) run on any CPU and are the lightest + option. +- **Faster Whisper** and **OpenAI Whisper**: the same size guidance as the table above. +- **Parakeet**: one model of about 640 MB. +- **Remote API**: transcription runs on your server, nothing is downloaded locally. + +Models are stored under `~/.local/share/vocalinux/models/` by default. Deleting a file there +removes the model; Vocalinux will show it as not downloaded again. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0062c05 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,35 @@ +# Documentation + +User and maintainer documentation for Vocalinux. + +| Document | Audience | Description | +|----------|----------|-------------| +| [INSTALL.md](INSTALL.md) | Users | Recommended install, AppImage, AUR, Flatpak, Snap, CLI | +| [INSTALL_MANUAL.md](INSTALL_MANUAL.md) | Users | Manual install, system packages, PyPI/pipx | +| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Users | Common install and runtime failures | +| [USER_GUIDE.md](USER_GUIDE.md) | Users | Dictation, engines, models, shortcuts, tips | +| [UPDATE.md](UPDATE.md) | Users | How to upgrade; release notes by version | +| [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md) | Users | Distro matrix, session caveats | +| [HTTP_REMOTE.md](HTTP_REMOTE.md) | Power users | Remote HTTP transcription | +| [FLATPAK_REMOTE.md](FLATPAK_REMOTE.md) | Users / maintainers | Self-hosted Flatpak remote and publishing | +| [GATEWAY_EMBED.md](GATEWAY_EMBED.md) | Users | Optional local VocaGateway integration | +| [CUSTOM_DICTIONARY_DESIGN.md](CUSTOM_DICTIONARY_DESIGN.md) | Contributors | Custom terms and correction design | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Contributors | Application modules, data flow, and threads | +| [Performance benchmarks](benchmarks/README.md) | Contributors | Before and after charts for the performance pass | +| [AUR.md](AUR.md) | Arch users / maintainers | AUR packages and publish secrets | +| [RELEASE_PROCESS.md](RELEASE_PROCESS.md) | Maintainers | Version bump, checklist, tagging | + +Project root: + +| Document | Description | +|----------|-------------| +| [README.md](../README.md) | Project overview and quick start | +| [CHANGELOG.md](../CHANGELOG.md) | Release history pointers | +| [SUPPORT.md](../SUPPORT.md) | Where to get help | +| [CONTRIBUTING.md](../CONTRIBUTING.md) | Development setup and pull requests | +| [SECURITY.md](../SECURITY.md) | Vulnerability reporting and supported versions | +| [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md) | Community standards | + +Website product and design notes live under [`web/`](../web/) (`PRODUCT.md`, `DESIGN.md`, `README.md`). Flatpak packaging: [`packaging/flatpak/README.md`](../packaging/flatpak/README.md). + +Website: [vocalinux.com](https://vocalinux.com) diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md new file mode 100644 index 0000000..1252a4a --- /dev/null +++ b/docs/RELEASE_PROCESS.md @@ -0,0 +1,551 @@ +# Vocalinux Release Process + +This document outlines the step-by-step process for creating a new release of Vocalinux. + +## Overview + +Vocalinux uses [Semantic Versioning](https://semver.org/) with pre-release suffixes: +- **Alpha**: Early testing phase (e.g., `0.4.1-alpha`) +- **Beta**: Feature-complete, testing phase (e.g., `0.5.0-beta`) +- **RC** (Release Candidate): Final testing before stable (e.g., `0.5.0-rc1`) +- **Stable**: Production-ready (e.g., `1.0.0`) + +## Release Infrastructure (one-time setup) + +These are configured once per repository. A tag pushed before they exist will fail +in the corresponding job. + +### PyPI trusted publishing (required) + +`publish-pypi` authenticates with a short-lived OIDC token instead of a stored API +token, so there is no `PYPI_API_TOKEN` secret to leak or rotate. It only works once +PyPI knows about this workflow. On https://pypi.org/manage/project/vocalinux/settings/publishing/ +add a GitHub publisher: + +| field | value | +|---|---| +| Owner | `VocaHQ` | +| Repository | `vocalinux` | +| Workflow name | `release.yml` | +| Environment | `pypi` | + +Until that publisher exists, `publish-pypi` fails with `invalid-publisher`. The old +`PYPI_API_TOKEN` secret can be deleted from the repository once the first trusted +publish succeeds. + +### AUR (optional) + +`publish-aur` skips itself when `AUR_SSH_PRIVATE_KEY` is unset. See docs/AUR.md. + +## Quick Release Checklist + +Use this checklist for every release: + +```markdown +## Version Bump Checklist for X.Y.Z-PHASE + +### Core Version Files +- [ ] `src/vocalinux/version.py` - Update `__version__` and `__version_info__` +- [ ] `pyproject.toml` - Confirm `Development Status` classifier and `requires-python` are correct for this release phase + +### Documentation +- [ ] `README.md` - Update short current-release blurb (not full notes) +- [ ] `CHANGELOG.md` - Point "Current stable" at the new tag +- [ ] `docs/INSTALL.md` - Verify install examples use `main/install.sh` (not version-pinned raw URLs) +- [ ] `docs/UPDATE.md` - Add "What's New" section for new version +- [ ] `SECURITY.md` - Update supported versions table + +### Website +- [ ] `web/src/app/page.tsx` - Update `softwareVersion` in schema +- [ ] `web/src/app/page.tsx` - Update version badge / live version surfaces if present +- [ ] `web/src/app/changelog/page.tsx` - Add new release entry to changelog +- [ ] `web/package.json` - Update version field +- [ ] `web/package-lock.json` - Keep top-level/version metadata aligned with `web/package.json` +- [ ] `web/PRODUCT.md` - Keep capabilities aligned with shipped features +- [ ] Confirm install docs/website mention **AppImage** when this release ships AppImages + +### Testing & Verification +Install `just` from https://just.systems or the distro package `just`. +- [ ] Run `just test` - All tests pass +- [ ] Run `just lint` - No linting errors +- [ ] Run `just typecheck` - No type errors +- [ ] Run `just build` - Package builds successfully +- [ ] Run `PYTHON=python3.11 just reproducible-wheel` - Independent wheels match +- [ ] Run `npm run build` in `web/` - Website builds successfully + +> Note: CI workflows also sync website version metadata from `src/vocalinux/version.py`, but source files should still be updated in-repo during release prep for review clarity. + +### Git Operations +- [ ] Create release branch: `git checkout -b release/vX.Y.Z-PHASE` +- [ ] Commit all version changes +- [ ] Push branch and create PR +- [ ] After PR merge, create tag: `git tag -a vX.Y.Z-PHASE -m "Release X.Y.Z-PHASE"` +- [ ] Push tag: `git push origin vX.Y.Z-PHASE` + +### After the release run finishes +- [ ] Run `just verify-release` - the published release verifies as published +- [ ] Re-run it after any hand edit to the release: attaching an asset, or rewriting the notes +``` + +`verify-release.yml` runs the same check when the release workflow finishes. +It cannot see a hand edit made afterwards, which is how every v0.16.2 defect +arrived, so run it yourself after touching a published release. The one +exception is `snap-backfill.yml`: that workflow attaches the snap, rewrites +`SHA256SUMS`, and then runs `scripts/verify_release.py` itself. + +### Pre-publication wheel reproducibility + +PR CI and the release workflow run `scripts/build_reproducible_wheel.py` before +publication. It checks lock/export drift, installs the hash-pinned `release-build` +toolchain into two fresh virtualenvs without system site-packages, and independently +builds an sdist and its wheel in each source tree with `--no-isolation`. +`SOURCE_DATE_EPOCH` comes from +the checked-out commit. Both builds use the same Python 3.11 interpreter and +platform; this checks repeatability under controlled inputs, not cross-platform +or cross-Python-version reproducibility. Only wheels are compared byte-for-byte. + +Build-only environments deliberately exclude host packages and setuptools plugins. +Unlike runtime/development virtualenvs, they do not need distro PyGObject: the +backend reads version metadata without importing GTK. + +The script runs `twine check --strict` with the locked tools and stages only the +canonical wheel and sdist in `dist/` after comparison succeeds. All release +consumers reuse those distributions. Snap Store publication also waits for this +gate. Verification builds never enter the release checksum manifest. + +Locally, `PYTHON=python3.11 just reproducible-wheel` includes tracked working-tree +edits and non-ignored new files, but excludes ignored build/egg-info/venv state. +Repeat runs automatically clean the gate's previous logs and build archives. +Staged files in `dist/` are removed only if their names and digests match the +retained canonical archives. Unrelated or modified files, unexpected diagnostic +files, and symlinks cause cleanup to stop before deleting anything; move those +files aside first. The rest of `build/` is never cleaned. Logs, hashes and both +builds are retained in `build/reproducible-wheel/`; CI uploads that directory on failure. +Refresh the toolchain through `just lock`, not by editing its export by hand. +Cleanup removes staged copies before their canonical originals, so an interrupted +cleanup can safely be retried. +The gate holds a checkout lock from before cleanup through final staging; a second +active run fails without changing the first run's outputs. Archives are copied to +an ignored temporary sibling of `dist/` and then atomically renamed into place, +so interrupted staging leaves only complete archives in `dist/` and can be retried. + +### AUR source publication gate + +After `publish-checksums`, `aur-source-test` downloads the published canonical +sdist and `SHA256SUMS`. `scripts/prepare_aur_source.py` checks the digest and +cryptographically verifies provenance against this repository's release workflow, +tag ref and source commit before preparing the checksum-pinned PKGBUILD. +The exact prepared recipe is built, checked with namcap, installed and smoked on +current Arch. `publish-aur` downloads only that verified PKGBUILD artifact and +pushes it without further version/checksum rewrites (`updpkgsums: false`). + +This gate runs even without AUR publication credentials. Failure prevents the +AUR push, but does not unpublish GitHub/PyPI artifacts already released. It is +separate from PR/local `--local-unattested` fixtures, which exercise the same +preparation/build path but are never used for publication. See [AUR.md](AUR.md) +for the trust model and the existing AUR-provider smoke-test limitations. +`vocalinux-bin` publication waits only for release creation and both AppImages; +source-gate failures or unrelated checksum producers do not block it. + +## Detailed Release Steps + +### Step 1: Determine Version Number + +Follow semantic versioning: + +``` +Given version MAJOR.MINOR.PATCH-PHASE: + +MAJOR - Breaking changes (for 1.0.0: public API stable) +MINOR - New features, backwards compatible +PATCH - Bug fixes, backwards compatible +PHASE - alpha, beta, rc1, rc2, etc. (omit for stable releases) +``` + +**Examples:** +- Alpha → Beta: `0.4.1-alpha` → `0.5.0-beta` (new features, phase change) +- Beta → RC: `0.5.0-beta` → `0.5.0-rc1` (same features, testing phase) +- RC → Stable: `0.5.0-rc1` → `1.0.0` (first stable release) +- Stable patch: `1.0.0` → `1.0.1` (bug fixes only) +- Stable minor: `1.0.0` → `1.1.0` (new features) + +### Step 2: Update Core Version Files + +#### 2.1 Update `src/vocalinux/version.py` + +```python +__version__ = "0.5.0-beta" +__version_info__ = (0, 5, 0, "beta") +``` + +#### 2.2 Update `pyproject.toml` + +Change the Development Status classifier: + +```toml +# For Alpha: +"Development Status :: 3 - Alpha", + +# For Beta: +"Development Status :: 4 - Beta", + +# For Stable: +"Development Status :: 5 - Production/Stable", +``` + +### Patch releases within a minor line (e.g. 0.14.x) + +When shipping bug fixes only (PATCH bump, same MINOR): + +- **README / `docs/UPDATE.md` "What's New"** keeps the **minor-series feature list** (everything introduced in that 0.Y line) and adds a short **Bug fixes in this patch** subsection for the delta only. Do not make the marketing highlight table look featureless because this tag only contains fixes. +- **Website changelog** (`web/src/app/changelog/page.tsx`) may list only the delta for the new entry; older entries stay historical. +- **AppStream** (`packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml`): add a short `` note for the patch delta only; leave the long product feature `