chore(release): prepare v0.17.0
Minor on the stable line. Faster Whisper and Parakeet extras, Speech Model simple setup, Snap uinput, Flatpak bundles on the tag, and QA fixes from the release branch. Fixes tracked in the PR. Post-release follow-ups: #834.
This commit is contained in:
+3
-5
@@ -12,13 +12,11 @@ Release history for Vocalinux.
|
||||
|
||||
## Current stable
|
||||
|
||||
**[v0.16.2](https://github.com/VocaHQ/vocalinux/releases/tag/v0.16.2)** (2026-09-05)
|
||||
**[v0.17.0](https://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0)** (2026-09-16)
|
||||
|
||||
Stability patch on the 0.16 series: KDE leftover IBus skip, Wayland/IBus shortcuts via wtype/ydotool, real BackSpace for "delete that", installer glslc on Fedora/Arch, nightly version stamp, release integrity pins, AUR PKGBUILD CI gate, distro CI/docs drift.
|
||||
Minor on the stable line: Faster Whisper and Parakeet local engines, Speech Model simple setup, first-run system language, Snap packaging with ydotool/`uinput`, Flatpak bundles attached by the release workflow, and injection/settings/audio fixes (HDA analog mics at native layout, native GTK OS dark/light, English-only models no longer trap other languages).
|
||||
|
||||
Series highlights (0.16.x): in-app update checker, Right Alt push-to-talk for new installs, searchable languages, unused-model cleanup, AGPL-3.0, family mic icons, tone picker, Justfile/uv installer pinning.
|
||||
|
||||
See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0162) for the highlight table and bug list, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.16.2).
|
||||
See [docs/UPDATE.md](docs/UPDATE.md#whats-new-in-v0170) for the highlight table, or the [GitHub Release](https://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0).
|
||||
|
||||
## Earlier versions
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Vocalinux turns speech into typed text in whatever app has focus. It is a free,
|
||||
|
||||
No Voca account is required. Models download once. After that, speech-to-text stays on your machine.
|
||||
|
||||
**Current release:** [v0.16.2](https://github.com/VocaHQ/vocalinux/releases/tag/v0.16.2). Stability patch on the 0.16 series (KDE leftover IBus, Wayland/IBus shortcuts, BackSpace for "delete that", installer glslc, release integrity pins). Series highlights include the in-app update checker, Right Alt push-to-talk for new installs, searchable languages, unused-model cleanup, and the family tone picker. Details: [docs/UPDATE.md](docs/UPDATE.md).
|
||||
**Current release:** [v0.17.0](https://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0). Minor: Faster Whisper and Parakeet engines, Speech Model simple setup, first-run system language, Snap with native Wayland typing, Flatpak bundles on the GitHub Release. Details: [docs/UPDATE.md](docs/UPDATE.md).
|
||||
|
||||
## Features
|
||||
|
||||
@@ -140,7 +140,7 @@ 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.16.2`.
|
||||
For a specific release tag, see [GitHub Releases](https://github.com/VocaHQ/vocalinux/releases) or `./install.sh --tag=v0.17.0`.
|
||||
|
||||
### Arch Linux (AUR)
|
||||
|
||||
@@ -184,7 +184,7 @@ sudo snap connect vocalinux:raw-input # global keyboard shortcuts (evdev)
|
||||
sudo snap connect vocalinux:uinput # native Wayland typing (ydotool)
|
||||
```
|
||||
|
||||
The **v0.16.2** store snap (`latest/edge` rev 7) has no `uinput` plug. `snap connect vocalinux:uinput` fails with `snap "vocalinux" has no plug named "uinput"`, and dictation only reaches XWayland apps. After `snap info vocalinux` shows a revision that lists `uinput` under plugs, `sudo snap refresh vocalinux` then connect `uinput` for native GNOME/GTK/Qt windows.
|
||||
This tag's snap ships ydotool and the `uinput` plug. After install: `sudo snap connect vocalinux:uinput` and restart Vocalinux for native GNOME/GTK/Qt windows. Older **v0.16.2** edge (rev 7) has no `uinput` plug; refresh first.
|
||||
|
||||
### From source
|
||||
|
||||
@@ -333,7 +333,7 @@ Vocalinux is part of [VocaHQ](https://vocahq.com). On-device speech-to-text firs
|
||||
|
||||
| Platform | Project | Website | GitHub | Status |
|
||||
|----------|---------|---------|--------|--------|
|
||||
| Linux | **VocaLinux** | [vocalinux.com](https://vocalinux.com) | [VocaHQ/vocalinux](https://github.com/VocaHQ/vocalinux) | Available now (`v0.16.2`) |
|
||||
| Linux | **VocaLinux** | [vocalinux.com](https://vocalinux.com) | [VocaHQ/vocalinux](https://github.com/VocaHQ/vocalinux) | Available now (`v0.17.0`) |
|
||||
| macOS | **VocaMac** | [vocamac.com](https://vocamac.com) | [VocaHQ/vocamac](https://github.com/VocaHQ/vocamac) | Beta (`v0.9.0`) |
|
||||
| Windows | **VocaWin** | [vocawin.com](https://vocawin.com) | [VocaHQ/vocawin](https://github.com/VocaHQ/vocawin) | Unsigned beta (`v0.1.0-beta.1`) |
|
||||
| Phone | **VocaPhone** | [vocaphone.vocahq.com](https://vocaphone.vocahq.com) | [VocaHQ/vocaphone](https://github.com/VocaHQ/vocaphone) | Android beta / iOS [TestFlight](https://testflight.apple.com/join/wd85wQ3W) |
|
||||
|
||||
+3
-2
@@ -6,9 +6,9 @@ Only the current stable minor line receives security fixes:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| 0.16.x | Yes |
|
||||
| 0.17.x | Yes |
|
||||
| 0.16.x | No |
|
||||
| 0.15.x | No |
|
||||
| 0.14.x | No |
|
||||
| older | No |
|
||||
|
||||
Upgrade to the latest release for security and reliability fixes. See [docs/UPDATE.md](docs/UPDATE.md).
|
||||
@@ -52,6 +52,7 @@ When the user enables **Remote API**, audio is uploaded to the configured server
|
||||
|--------|------------|--------|
|
||||
| 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 |
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ sudo snap connect vocalinux:raw-input # global keyboard shortcuts (evdev)
|
||||
sudo snap connect vocalinux:uinput # native Wayland typing (ydotool)
|
||||
```
|
||||
|
||||
**v0.16.2 edge (rev 7) has no `uinput` plug.** `sudo snap connect vocalinux:uinput` fails with `snap "vocalinux" has no plug named "uinput"`. That build only types into XWayland apps (Firefox/Chrome often work; native GNOME/GTK/Qt do not). A later store revision ships `ydotool` and the plug. After `snap info vocalinux` lists `uinput`, `sudo snap refresh vocalinux`, connect `uinput`, and restart Vocalinux.
|
||||
**v0.17.0** ships ydotool and the `uinput` plug. After `sudo snap install vocalinux --edge`, connect `uinput` and restart Vocalinux for native GNOME/GTK/Qt windows. **v0.16.2 edge (rev 7)** has no such plug and only types into XWayland apps; `sudo snap refresh vocalinux` first.
|
||||
|
||||
## From source
|
||||
|
||||
|
||||
@@ -448,6 +448,7 @@ git push origin v0.5.1-beta
|
||||
| 0.14.2 | 2026-07-17 | Stable | IBus engine launch + FocusIn gate; settings tabs scroll to fit monitor |
|
||||
| 0.15.0 | 2026-07-28 | Stable | Searchable settings + sidebar dictation footer, AppImage, expanded languages, dictation polish, auto-pause/keepalive, Vulkan device selection, ibus-wayland, Bluetooth mic + shortcut UI fixes |
|
||||
| 0.16.0 | 2026-08-23 | Stable | Update checker + tray notify, Right Alt PTT default (new installs), searchable language list, delete unused models, Voca tone picker (family preview WAVs), About page, AGPL-3.0, family mic icons, installer/just/uv pinning, Test Dictation missing-model message, IBus/audio/clipboard/GPU/AppImage reliability, vocalinux.com family workbench restyle |
|
||||
| 0.17.0 | 2026-09-16 | Stable | Faster Whisper + Parakeet engines, Speech Model simple setup, first-run system language, Snap with ydotool/uinput, Flatpak bundles on the GitHub Release, XWayland/layout paste and settings/audio fixes |
|
||||
| 0.16.2 | 2026-09-05 | Stable | Patch: KDE leftover IBus skip, Wayland/IBus shortcuts via wtype/ydotool, BackSpace delete that, installer glslc on Fedora/Arch, nightly version stamp, release integrity pins, AUR PKGBUILD CI gate, distro CI/docs drift, Gateway Beta on site |
|
||||
| 0.16.1 | 2026-08-30 | Stable | Patch: per-engine model at startup, leftover tray idle, terminal Ctrl+Shift+V paste, GNOME XWayland layout after IBus, Python 3.11 floor + verified downloads, AppImage glibc that boots on Debian 12, Settings/About polish |
|
||||
|
||||
|
||||
+48
-2
@@ -20,7 +20,7 @@ The installer detects a running instance, updates in place, preserves configurat
|
||||
```bash
|
||||
cd vocalinux
|
||||
git fetch origin
|
||||
git checkout v0.16.2
|
||||
git checkout v0.17.0
|
||||
./install.sh
|
||||
```
|
||||
|
||||
@@ -38,7 +38,7 @@ git pull origin main
|
||||
|--------|---------|
|
||||
| AUR | `yay -S vocalinux` (or your AUR helper) |
|
||||
| AppImage | Download the new file from [Releases](https://github.com/VocaHQ/vocalinux/releases) |
|
||||
| Snap | `sudo snap refresh vocalinux` (`--edge` until stable is promoted). **v0.16.2** has no `uinput` plug, so `snap connect vocalinux:uinput` fails and typing stays XWayland-only. After `snap info vocalinux` lists an `uinput` plug, refresh, then `sudo snap connect vocalinux:uinput` for native Wayland apps. |
|
||||
| Snap | `sudo snap refresh vocalinux` (`--edge` until stable is promoted). **v0.17.0** ships `uinput`; `sudo snap connect vocalinux:uinput` then restart for native Wayland apps. v0.16.2 rev 7 has no such plug. |
|
||||
| Flatpak (release bundle) | Install the new `.flatpak` from Releases; bundles do not auto-update |
|
||||
| PyPI | Reinstall in the same venv after system packages are current |
|
||||
|
||||
@@ -73,6 +73,52 @@ Missing system packages: see [INSTALL.md](INSTALL.md) or [DISTRO_COMPATIBILITY.m
|
||||
|
||||
---
|
||||
|
||||
## What's New in v0.17.0
|
||||
|
||||
0.17.0 is a **minor** on the stable line. Default engine is still whisper.cpp. This release adds two optional local engines (Faster Whisper and Parakeet), a simpler Speech Model page, Snap packaging with native Wayland typing, and Flatpak bundles built by the release workflow. It also fixes XWayland/layout paste, a KDE xdotool crash, push-to-talk tray redraw, clipped start/stop cues, HDA analog mics that abort when opened below native channel count, native GTK installs that ignored OS dark/light, and English-only models that hid every other language.
|
||||
|
||||
### 0.17 series highlights
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Faster Whisper** | Optional CTranslate2 / INT8 Whisper on CPU (`--engine=faster_whisper`) (#543) |
|
||||
| **Parakeet** | Optional Parakeet TDT 0.6B via sherpa-onnx; default v3-european bundle (`--engine=parakeet`) (#802) |
|
||||
| **Speech Model simple setup** | Language + speed/accuracy first; engine/size under Advanced (#801) |
|
||||
| **First-run language** | Seeds from keyboard layout / locale; saved choice is left alone (#796) |
|
||||
| **Searchable open picker** | Language list filters while open (#798) |
|
||||
| **Localized punctuation commands** | it/fr/de/es/pt/nl/pl/ru; English phrases still work (#642) |
|
||||
| **Bare F-keys** | F1–F24 are valid push-to-talk shortcuts (#815) |
|
||||
| **Snap** | Store listing, ydotool + `uinput` for native Wayland (#519, #823, #822) |
|
||||
| **Flatpak on the tag** | Workflow attaches `.flatpak` assets and checksums them (#786) |
|
||||
|
||||
### Also in v0.17.0
|
||||
|
||||
- Recommended model is a button that sets size and specialization together, and will not ignore a suitable file already on disk (#797)
|
||||
- XWayland xdotool fallback pastes via clipboard so missing-layout characters are not garbled (#680)
|
||||
- Clipboard paste uses the key that types **v** on the current layout (#788)
|
||||
- Timed-out ydotool paste releases Ctrl (#675)
|
||||
- Read `WM_CLASS` with `xprop` instead of crashing `xdotool getwindowclassname` on KDE Plasma Wayland (#807)
|
||||
- Applying an already-downloaded model no longer freezes Settings (#790)
|
||||
- Selected language picks the whisper.cpp variant (English → `.en`). Picking another language, or auto-detect, switches off `.en` / `.en-q*` instead of hiding the rest of the list (#795, #780, #826)
|
||||
- Speech Model labels are nouns (Language, Other languages, Speed vs accuracy). Unused downloads is a sibling expander of Advanced, not nested inside it (#826)
|
||||
- About family grid includes VocaWin (#826)
|
||||
- Push-to-talk tray icon turns red on every hold (#809)
|
||||
- Sink-wake no longer clips start/stop cues (#804)
|
||||
- Open 3-8 channel HDA analog mics at native layout instead of forcing 2ch/48kHz, so PortAudio no longer aborts after read() (`free(): corrupted unsorted chunks`); capture is downmixed to mono for engines (#829, fixes #813)
|
||||
- Native GTK installs (AUR / install.sh) follow OS dark/light via the appearance portal then gsettings; skipped when `GTK_THEME` is set so AppImage and user overrides stay in charge (#830, fixes #816)
|
||||
- Parakeet keeps recognizing after a decode error (#803)
|
||||
- Flatpak/AUR deps derived from `uv.lock` (#819)
|
||||
- Hash-pinned exports for VOSK, Parakeet, and Faster Whisper extras so `install.sh` no longer installs those unpinned; extras are version-capped (#828)
|
||||
- `just verify-release` checks the published GitHub Release (#791)
|
||||
- Distro matrix runs `install.sh` (#810)
|
||||
- `install.sh` Faster Whisper box is CPU CTranslate2 / INT8, not NVIDIA CUDA. The whisper.cpp GPU step does not install the CUDA toolkit; Vulkan is first, CUDA only if a toolkit is already on the machine (#826)
|
||||
|
||||
AppImage and Flatpak remain whisper.cpp only. Snap ships whisper.cpp plus VOSK. Faster Whisper and Parakeet are optional extras on `install.sh` / source installs.
|
||||
|
||||
See the [full changelog](https://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0).
|
||||
|
||||
---
|
||||
|
||||
## What's New in v0.16.2
|
||||
|
||||
0.16.2 is a **stability patch** on the 0.16 series. The feature set is the same as 0.16.x. This release fixes KDE leftover IBus so dictation types into real apps, Wayland and IBus keyboard shortcuts through wtype/ydotool, real BackSpace for "delete that", Fedora/Arch installer glslc packages, nightly version stamping, and release integrity pins. CI now gates AUR PKGBUILD builds and the distros the docs promise.
|
||||
|
||||
+30
-23
@@ -1074,22 +1074,30 @@ detect_whispercpp_backends() {
|
||||
VULKAN_COMPAT_REASON="NVIDIA GPU uses CUDA"
|
||||
fi
|
||||
|
||||
# Determine recommendation (Priority: CUDA > Vulkan > CPU)
|
||||
# IMPORTANT: The installer WILL install dev libraries (libvulkan-dev, glslc, CUDA) later,
|
||||
# so we recommend GPU if there's a compatible GPU regardless of current library status.
|
||||
# Determine recommendation (Priority: Vulkan > CUDA fallback > CPU).
|
||||
# install_whispercpp_with_gpu_support tries Vulkan first on every GPU,
|
||||
# including NVIDIA. CUDA is used only when Vulkan is unavailable or the
|
||||
# Vulkan build fails, and only if a complete toolkit is already present.
|
||||
# The installer never installs the CUDA toolkit.
|
||||
# IMPORTANT: Vulkan *dev* libraries (libvulkan-dev, glslc) are installed
|
||||
# later, so we recommend GPU when a compatible GPU is present even if
|
||||
# those packages are not installed yet.
|
||||
local RECOMMENDED_BACKEND="cpu"
|
||||
local RECOMMENDED_REASON=""
|
||||
local CAN_BUILD_GPU=false
|
||||
|
||||
# NVIDIA GPU - best option, uses CUDA
|
||||
if [[ "$HAS_NVIDIA_GPU" == "yes" ]]; then
|
||||
RECOMMENDED_BACKEND="cuda"
|
||||
if [[ "$HAS_CUDA_DEV" == "true" ]]; then
|
||||
CAN_BUILD_GPU=true
|
||||
if [[ "$HAS_VULKAN" == "yes" ]]; then
|
||||
RECOMMENDED_BACKEND="vulkan"
|
||||
RECOMMENDED_REASON="NVIDIA GPU detected (Vulkan)"
|
||||
elif [[ "$HAS_CUDA_DEV" == "true" ]]; then
|
||||
RECOMMENDED_BACKEND="cuda"
|
||||
RECOMMENDED_REASON="NVIDIA GPU with CUDA toolkit installed"
|
||||
else
|
||||
RECOMMENDED_REASON="NVIDIA GPU detected (CUDA toolkit will be installed)"
|
||||
RECOMMENDED_BACKEND="vulkan"
|
||||
RECOMMENDED_REASON="NVIDIA GPU detected"
|
||||
fi
|
||||
CAN_BUILD_GPU=true
|
||||
# Vulkan-compatible GPU (AMD, Intel Gen8+) - second choice
|
||||
elif [[ "$HAS_VULKAN" == "yes" && "$VULKAN_COMPATIBLE" == "compatible" ]]; then
|
||||
RECOMMENDED_BACKEND="vulkan"
|
||||
@@ -1251,22 +1259,21 @@ EOF
|
||||
echo " │ • Good for basic dictation needs │"
|
||||
echo " └─────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
echo " ┌───────────────────────────────────────────────────────────────┐"
|
||||
echo " ┌─────────────────────────────────────────────────────────────┐"
|
||||
echo " │ 4. FASTER-WHISPER │"
|
||||
echo " │ • Optional CTranslate2 Whisper backend │"
|
||||
echo " │ • Fast on CPU with INT8 quantization │"
|
||||
echo " │ • Best performance on NVIDIA GPUs (CUDA) │"
|
||||
echo " │ • Hugging Face models stay checksum-gated (not auto-dl) │"
|
||||
echo " └───────────────────────────────────────────────────────────────┘"
|
||||
echo " │ • Optional CTranslate2 Whisper backend │"
|
||||
echo " │ • Fast on CPU with INT8 quantization │"
|
||||
echo " │ • Checksum-verified Hugging Face models │"
|
||||
echo " └─────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
echo " ┌───────────────────────────────────────────────────────────────┐"
|
||||
echo " │ 5. REMOTE API (ADVANCED) │"
|
||||
echo " │ • Offload processing to a GPU server on your network │"
|
||||
echo " │ • Ideal for laptops without GPU │"
|
||||
echo " │ • Supports whisper.cpp server & OpenAI-compatible APIs │"
|
||||
echo " │ • Minimal local resources needed │"
|
||||
echo " │ • Requires a remote server to be running │"
|
||||
echo " └───────────────────────────────────────────────────────────────┘"
|
||||
echo " ┌─────────────────────────────────────────────────────────────┐"
|
||||
echo " │ 5. REMOTE API (ADVANCED) │"
|
||||
echo " │ • Offload processing to a GPU server on your network │"
|
||||
echo " │ • Ideal for laptops without GPU │"
|
||||
echo " │ • Supports whisper.cpp server & OpenAI-compatible APIs │"
|
||||
echo " │ • Minimal local resources needed │"
|
||||
echo " │ • Requires a remote server to be running │"
|
||||
echo " └─────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
# Show recommendation
|
||||
@@ -1358,7 +1365,7 @@ EOF
|
||||
echo " ┌─────────────────────────────────────────────────────────────┐"
|
||||
echo " │ 1. GPU (Vulkan/CUDA) * RECOMMENDED │"
|
||||
echo " │ • Fastest performance with GPU acceleration │"
|
||||
echo " │ • $RECOMMENDED_REASON │"
|
||||
printf " │ • %-*s│\n" 54 "$RECOMMENDED_REASON"
|
||||
echo " │ • Requires building from source (takes ~2-5 min) │"
|
||||
echo " └─────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
@@ -142,7 +142,7 @@ install-gate distro="debian:12":
|
||||
|
||||
# Check that a published release verifies as published: manifest, provenance,
|
||||
# notes and PyPI digests. Needs gh, downloads nothing.
|
||||
# Usage: `just verify-release` for the latest, or `just verify-release v0.16.2`
|
||||
# Usage: `just verify-release` for the latest, or `just verify-release v0.17.0`
|
||||
verify-release tag="":
|
||||
python3 scripts/verify_release.py {{tag}}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
pkgname=vocalinux
|
||||
# AUR pkgver cannot contain hyphens (v0.14.0-beta -> 0.14.0beta).
|
||||
pkgver=0.16.2
|
||||
_tag=0.16.2
|
||||
pkgver=0.17.0
|
||||
_tag=0.17.0
|
||||
pkgrel=1
|
||||
pkgdesc="Free, offline voice dictation for Linux"
|
||||
arch=('any')
|
||||
|
||||
@@ -105,6 +105,11 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release version="0.17.0" date="2026-09-16">
|
||||
<description>
|
||||
<p>Minor: Faster Whisper and Parakeet local engines, Speech Model simple setup, first-run system language, searchable open language picker, localized punctuation commands, bare function-key push-to-talk, Snap packaging with ydotool/uinput, Flatpak bundles attached by the release workflow, and XWayland/layout paste plus settings/audio fixes (HDA analog mics at native layout, native GTK OS dark/light, English-only models no longer trap other languages). This Flatpak still ships whisper.cpp only.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.16.2" date="2026-09-05">
|
||||
<description>
|
||||
<p>Stability patch: skip leftover IBus on KDE so dictation types into real apps, Wayland/IBus shortcuts via wtype and ydotool, real BackSpace for "delete that", Fedora/Arch installer glslc (not glslang), nightly version stamp before build, release integrity checksums and pinned builders, AUR PKGBUILD CI gate, distro CI plus docs drift fixes, and VocaGateway Beta on the site.</p>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Windows</title><path fill="currentColor" d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623"/></svg>
|
||||
|
After Width: | Height: | Size: 231 B |
+2
-2
@@ -12,7 +12,7 @@ name: vocalinux
|
||||
base: core24 # Ubuntu 24.04 / Python 3.12 (project requires-python >=3.11)
|
||||
# Fallback version; live version is set from src/vocalinux/version.py via
|
||||
# adopt-info + craftctl set in the vocalinux part (required for craftctl).
|
||||
version: "0.16.2"
|
||||
version: "0.17.0"
|
||||
adopt-info: vocalinux
|
||||
summary: Free offline voice dictation for Linux
|
||||
description: |
|
||||
@@ -31,7 +31,7 @@ description: |
|
||||
Sandbox notes:
|
||||
- Global hotkeys need: sudo snap connect vocalinux:raw-input
|
||||
- Native Wayland typing needs: sudo snap connect vocalinux:uinput
|
||||
(v0.16.2 store snaps have no uinput plug; refresh to a revision that lists it)
|
||||
(v0.17.0 ships the plug; v0.16.2 edge (rev 7) has no uinput plug; refresh first)
|
||||
- Microphone may need: sudo snap connect vocalinux:audio-record
|
||||
- Config and models live under ~/snap/vocalinux/
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Windows</title><path fill="currentColor" d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623"/></svg>
|
||||
|
After Width: | Height: | Size: 231 B |
@@ -16,6 +16,7 @@ from ..utils.vosk_model_info import SUPPORTED_LANGUAGES
|
||||
from ..utils.whispercpp_model_info import MODEL_SIZES as WHISPERCPP_MODEL_SIZES
|
||||
from ..utils.whispercpp_model_info import WHISPERCPP_MODEL_INFO, default_variant_for_size
|
||||
from ..utils.whispercpp_model_info import get_model_size as get_whispercpp_model_size
|
||||
from ..utils.whispercpp_model_info import is_english_only_model as is_english_only_whispercpp_model
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -166,16 +167,34 @@ DEFAULT_CONFIG = {
|
||||
}
|
||||
|
||||
|
||||
def _multilingual_sibling(model_name: str) -> str:
|
||||
"""Drop the ``.en`` infix so medium.en / medium.en-q5_0 become multilingual."""
|
||||
if ".en" not in model_name:
|
||||
return model_name
|
||||
stripped = model_name.replace(".en", "", 1)
|
||||
if stripped in WHISPERCPP_MODEL_INFO:
|
||||
return stripped
|
||||
size = get_whispercpp_model_size(model_name)
|
||||
derived = default_variant_for_size(size, language_is_english=False)
|
||||
return derived if derived in WHISPERCPP_MODEL_INFO else model_name
|
||||
|
||||
|
||||
def resolve_whispercpp_variant(saved_model: str, pinned_variant: str, language_id: str) -> str:
|
||||
"""Resolve the loadable whisper.cpp id for a saved size, pin, and language.
|
||||
|
||||
A pin outranks everything. When unpinned, a plain ``{size}.en`` English-only
|
||||
id is the language-derived default, not a legacy specialization, so a later
|
||||
language change can re-derive. True legacy specializations (quantized, turbo,
|
||||
versioned large, ``{size}.en-q*``) are still honoured.
|
||||
A pin outranks everything except an English-only id when the language is not
|
||||
English: those weights cannot transcribe Polish (or auto-detect), so the
|
||||
multilingual sibling of the same size is used instead. When unpinned, a
|
||||
plain ``{size}.en`` id is the language-derived default, not a leftover
|
||||
specialization. True leftover specializations (turbo, versioned large,
|
||||
quantized multilingual) are still honoured.
|
||||
"""
|
||||
language_is_english = SUPPORTED_LANGUAGES.get(language_id, {}).get("whisper") == "en"
|
||||
|
||||
pinned = pinned_variant.lower() if isinstance(pinned_variant, str) else ""
|
||||
if pinned in WHISPERCPP_MODEL_INFO:
|
||||
if not language_is_english and is_english_only_whispercpp_model(pinned):
|
||||
return _multilingual_sibling(pinned)
|
||||
return pinned
|
||||
|
||||
saved = saved_model.lower() if isinstance(saved_model, str) else ""
|
||||
@@ -189,9 +208,10 @@ def resolve_whispercpp_variant(saved_model: str, pinned_variant: str, language_i
|
||||
and saved not in WHISPERCPP_MODEL_SIZES
|
||||
and saved != f"{size}.en"
|
||||
):
|
||||
if not language_is_english and is_english_only_whispercpp_model(saved):
|
||||
return _multilingual_sibling(saved)
|
||||
return saved
|
||||
|
||||
language_is_english = SUPPORTED_LANGUAGES.get(language_id, {}).get("whisper") == "en"
|
||||
derived = default_variant_for_size(size, language_is_english)
|
||||
if derived in WHISPERCPP_MODEL_INFO:
|
||||
return derived
|
||||
|
||||
@@ -324,7 +324,7 @@ def _recommended_whispercpp_variant_for_language(
|
||||
english_variant = f"{recommended_size}.en"
|
||||
|
||||
if _language_is_english(language_id) and english_variant in WHISPERCPP_MODEL_INFO:
|
||||
return english_variant, f"{reason}; English language selected"
|
||||
return english_variant, reason
|
||||
|
||||
return recommended_model, reason
|
||||
|
||||
@@ -451,6 +451,7 @@ def _combo_with_suffix(
|
||||
VOCALINUX_SITE_URL = "https://vocalinux.com"
|
||||
VOCAHQ_SITE_URL = "https://vocahq.com"
|
||||
VOCAMAC_SITE_URL = "https://vocamac.com"
|
||||
VOCAWIN_SITE_URL = "https://vocawin.com"
|
||||
VOCAPHONE_SITE_URL = "https://vocaphone.vocahq.com"
|
||||
VOCAGATEWAY_SITE_URL = "https://vocagateway.vocahq.com"
|
||||
GITHUB_REPO_URL = __url__
|
||||
@@ -465,6 +466,7 @@ _ABOUT_OPEN_URLS = frozenset(
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAWIN_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
VOCAGATEWAY_SITE_URL,
|
||||
GITHUB_REPO_URL,
|
||||
@@ -567,6 +569,13 @@ _VOCAHQ_FAMILY_LINKS = (
|
||||
("platform-apple",),
|
||||
"Open vocamac.com",
|
||||
),
|
||||
(
|
||||
VOCAWIN_SITE_URL,
|
||||
"VocaWin",
|
||||
"Windows, unsigned beta",
|
||||
("platform-windows",),
|
||||
"Open vocawin.com",
|
||||
),
|
||||
(
|
||||
VOCAPHONE_SITE_URL,
|
||||
"VocaPhone",
|
||||
@@ -612,8 +621,8 @@ MODEL_SPECIALIZATION_TOOLTIP = (
|
||||
"lower-memory quantized models, Turbo speed, or a legacy large model."
|
||||
)
|
||||
LANGUAGE_TOOLTIP = (
|
||||
"Choose the language you dictate in. Type to search the list. English-only model "
|
||||
"specializations limit this list to English."
|
||||
"Choose the language you dictate in. Search the list. Picking a language "
|
||||
"other than English switches off an English-only model."
|
||||
)
|
||||
|
||||
|
||||
@@ -1043,12 +1052,26 @@ spinbutton {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Unused downloads: one collapsed row until expanded */
|
||||
.unused-downloads-expander {
|
||||
padding: 8px 12px;
|
||||
/* Sibling expander cards on Speech Model (Advanced, Unused downloads).
|
||||
Padding lives on the expander, not the title class: .preferences-group-title
|
||||
already has 16px inset, which stacked with expander margin and shoved the
|
||||
chevron off the unused-downloads title. */
|
||||
.expander-card expander {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.unused-downloads-expander list {
|
||||
.expander-card-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
color: @theme_unfocused_fg_color;
|
||||
}
|
||||
|
||||
.expander-card-subtitle {
|
||||
font-size: 0.85em;
|
||||
color: @theme_unfocused_fg_color;
|
||||
}
|
||||
|
||||
.expander-card list {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -1189,7 +1212,7 @@ class SearchablePicker(Gtk.Box):
|
||||
self.pack_start(self._button, True, True, 0)
|
||||
|
||||
self._search = Gtk.SearchEntry()
|
||||
self._search.set_placeholder_text("Type to search…")
|
||||
self._search.set_placeholder_text("Search…")
|
||||
self._search.connect("search-changed", self._on_search_changed)
|
||||
self._search.connect("activate", self._on_search_activate)
|
||||
|
||||
@@ -1642,6 +1665,30 @@ def _row_matches_query(query: str, title: str, subtitle: str = "", keywords=())
|
||||
return any(query in text.casefold() for text in haystacks)
|
||||
|
||||
|
||||
def _make_expander_card(
|
||||
title: str, subtitle: str
|
||||
) -> tuple[Gtk.Box, Gtk.Expander, Gtk.Box, Gtk.Label]:
|
||||
"""Card with a compact title+subtitle expander, used for Advanced and Unused."""
|
||||
island = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
island.get_style_context().add_class("preferences-group")
|
||||
island.get_style_context().add_class("expander-card")
|
||||
|
||||
expander = Gtk.Expander()
|
||||
header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
title_label = Gtk.Label(label=title, xalign=0)
|
||||
title_label.get_style_context().add_class("expander-card-title")
|
||||
subtitle_label = Gtk.Label(label=subtitle, xalign=0, wrap=True)
|
||||
subtitle_label.get_style_context().add_class("expander-card-subtitle")
|
||||
header.pack_start(title_label, False, False, 0)
|
||||
header.pack_start(subtitle_label, False, False, 0)
|
||||
expander.set_label_widget(header)
|
||||
|
||||
body = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
expander.add(body)
|
||||
island.pack_start(expander, False, False, 0)
|
||||
return island, expander, body, subtitle_label
|
||||
|
||||
|
||||
class PreferencesGroup(Gtk.Box):
|
||||
"""A card-style group of preferences, similar to libadwaita's AdwPreferencesGroup."""
|
||||
|
||||
@@ -1998,6 +2045,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
self.advanced_box = None
|
||||
self.advanced_island = None
|
||||
self.advanced_expander = None
|
||||
self.unused_island = None
|
||||
self.unused_expander = None
|
||||
self.simple_page = None
|
||||
self.simple_group = None
|
||||
self.engine_group = None
|
||||
@@ -3017,7 +3066,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
|
||||
def _build_simple_model_section(self):
|
||||
"""Build the simple questions and the Advanced reveal (#779)."""
|
||||
self.simple_group = PreferencesGroup(title="What you dictate")
|
||||
self.simple_group = PreferencesGroup(title="Language")
|
||||
|
||||
# Searchable, like the advanced row: over thirty languages is too many to
|
||||
# scroll, and a list you cannot type into is a step backwards.
|
||||
@@ -3037,7 +3086,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
)
|
||||
self.simple_language_row = PreferenceRow(
|
||||
title="Main language",
|
||||
subtitle="Type to search, or pick from the list",
|
||||
subtitle="Search or pick from the list",
|
||||
widget=self.simple_language_combo,
|
||||
keywords=("language", "speak"),
|
||||
)
|
||||
@@ -3048,8 +3097,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
self.simple_multi_switch = Gtk.Switch()
|
||||
self.simple_multi_switch.set_valign(Gtk.Align.CENTER)
|
||||
self.simple_multi_row = PreferenceRow(
|
||||
title="I also dictate whole texts in other languages",
|
||||
subtitle="Detects the language per utterance; can be wrong on short ones",
|
||||
title="Other languages",
|
||||
subtitle="Guesses the language each time. Short clips can be wrong.",
|
||||
widget=self.simple_multi_switch,
|
||||
keywords=("multilingual", "auto", "detect"),
|
||||
)
|
||||
@@ -3061,7 +3110,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
# First entry is the multilingual answer: any language, detected per
|
||||
# utterance. Naming one specific second language means the same thing
|
||||
# to the engine, but lets the user say which one they had in mind.
|
||||
self.simple_second_language_combo.append("auto", "Any language (auto-detect)")
|
||||
self.simple_second_language_combo.append("auto", "Any language")
|
||||
for language_id, info in SUPPORTED_LANGUAGES.items():
|
||||
if language_id != "auto":
|
||||
self.simple_second_language_combo.append(language_id, info["name"])
|
||||
@@ -3072,7 +3121,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
title="Other language",
|
||||
# Honest about what the engine does: whisper takes one language or
|
||||
# none, so any second language means detection per utterance.
|
||||
subtitle="Recognition detects the language of each utterance",
|
||||
subtitle="Or pick Any language to auto-detect",
|
||||
widget=self.simple_second_language_combo,
|
||||
keywords=("second", "language", "other"),
|
||||
)
|
||||
@@ -3085,8 +3134,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
for priority in PRIORITIES:
|
||||
self.simple_priority_combo.append(priority, PRIORITY_LABELS[priority])
|
||||
self.simple_priority_row = PreferenceRow(
|
||||
title="Priority",
|
||||
subtitle="Balanced follows what your hardware can run",
|
||||
title="Speed vs accuracy",
|
||||
subtitle="Balanced is the default for this computer",
|
||||
widget=self.simple_priority_combo,
|
||||
keywords=("speed", "accuracy", "priority"),
|
||||
)
|
||||
@@ -3104,33 +3153,17 @@ class SettingsDialog(Gtk.Dialog):
|
||||
# it could not be raised above the dialog at all. Both cards visible at
|
||||
# once also makes the expanded rows a readout of what the simple answers
|
||||
# resolved to.
|
||||
self.advanced_island = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
self.advanced_island.get_style_context().add_class("preferences-group")
|
||||
|
||||
self.advanced_expander = Gtk.Expander()
|
||||
header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
title = Gtk.Label(label="Advanced", xalign=0)
|
||||
title.get_style_context().add_class("preferences-group-title")
|
||||
subtitle = Gtk.Label(
|
||||
label="Engine, model size, specialization, downloads and the remote server",
|
||||
xalign=0,
|
||||
wrap=True,
|
||||
(
|
||||
self.advanced_island,
|
||||
self.advanced_expander,
|
||||
self.advanced_box,
|
||||
_,
|
||||
) = _make_expander_card(
|
||||
"Advanced",
|
||||
"Engine, model, and remote server",
|
||||
)
|
||||
subtitle.get_style_context().add_class("preference-row-subtitle")
|
||||
header.pack_start(title, False, False, 0)
|
||||
header.pack_start(subtitle, False, False, 0)
|
||||
self.advanced_expander.set_label_widget(header)
|
||||
self.advanced_expander.set_margin_top(12)
|
||||
self.advanced_expander.set_margin_bottom(12)
|
||||
self.advanced_expander.set_margin_start(16)
|
||||
self.advanced_expander.set_margin_end(16)
|
||||
|
||||
# Everything the detailed view holds is packed in here; the sections
|
||||
# built after this one append to it.
|
||||
self.advanced_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||
self.advanced_box.set_spacing(12)
|
||||
self.advanced_box.set_margin_top(8)
|
||||
self.advanced_expander.add(self.advanced_box)
|
||||
self.advanced_island.pack_start(self.advanced_expander, False, False, 0)
|
||||
self.content_box.pack_start(self.advanced_island, False, False, 0)
|
||||
|
||||
self.advanced_expander.connect("notify::expanded", self._on_advanced_expanded)
|
||||
@@ -3161,7 +3194,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
self.model_combo.set_tooltip_text(MODEL_SIZE_TOOLTIP)
|
||||
_prevent_scroll_on_hover(self.model_combo)
|
||||
self.model_row = PreferenceRow(
|
||||
title="Model Size",
|
||||
title="Model size",
|
||||
subtitle="Larger models are more accurate but slower",
|
||||
widget=self.model_combo,
|
||||
)
|
||||
@@ -3193,7 +3226,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
language_entry.connect("focus-out-event", self._on_language_entry_focus_out)
|
||||
self.language_row = PreferenceRow(
|
||||
title="Language",
|
||||
subtitle="Type to search, or pick from the list",
|
||||
subtitle="Search or pick from the list",
|
||||
widget=self.language_combo,
|
||||
)
|
||||
self.language_row.set_tooltip_text(LANGUAGE_TOOLTIP)
|
||||
@@ -3244,19 +3277,20 @@ class SettingsDialog(Gtk.Dialog):
|
||||
# priority or language change did anything, and what it will cost.
|
||||
self.simple_page.pack_start(self.model_info_card, False, False, 0)
|
||||
|
||||
(
|
||||
self.unused_island,
|
||||
self.unused_expander,
|
||||
unused_body,
|
||||
self.unused_expander_subtitle,
|
||||
) = _make_expander_card(
|
||||
"Unused downloads",
|
||||
"Downloaded, but not the one in use",
|
||||
)
|
||||
self.unused_models_group = PreferencesGroup(
|
||||
keywords=("delete", "remove", "unused", "disk", "storage", "downloaded"),
|
||||
)
|
||||
self.unused_models_group.title = "Unused downloads"
|
||||
self.unused_models_group.description = "On disk, but not the model currently selected"
|
||||
|
||||
self.unused_expander = Gtk.Expander(label="Unused downloads")
|
||||
self.unused_expander.set_expanded(False)
|
||||
self.unused_expander.set_use_underline(False)
|
||||
self.unused_expander.set_tooltip_text(
|
||||
"Leftover model files on disk. Expand to delete them one at a time."
|
||||
)
|
||||
self.unused_expander.get_style_context().add_class("unused-downloads-expander")
|
||||
# The island is the card; this group only holds rows for search/delete.
|
||||
self.unused_models_group.get_style_context().remove_class("preferences-group")
|
||||
|
||||
self.unused_models_scroll = Gtk.ScrolledWindow()
|
||||
self.unused_models_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
@@ -3273,15 +3307,16 @@ class SettingsDialog(Gtk.Dialog):
|
||||
self.unused_models_group.remove(self.unused_models_group.listbox)
|
||||
list_holder.pack_start(self.unused_models_group.listbox, False, False, 0)
|
||||
self.unused_models_scroll.add(list_holder)
|
||||
self.unused_expander.add(self.unused_models_scroll)
|
||||
# Backstop: the refresh measures while the expander is collapsed and
|
||||
# the dialog may not be mapped yet. Remeasure when the user expands,
|
||||
# so a short first measurement can never leave the list clipped.
|
||||
self.unused_models_group.pack_start(self.unused_models_scroll, False, False, 0)
|
||||
unused_body.pack_start(self.unused_models_group, False, False, 0)
|
||||
self.unused_expander.set_expanded(False)
|
||||
# Refresh may run while collapsed or before map. Remeasure on expand
|
||||
# and on map so a short first measurement cannot leave rows clipped.
|
||||
self.unused_expander.connect(
|
||||
"notify::expanded", lambda *_args: self._fit_unused_downloads_height()
|
||||
)
|
||||
self.unused_models_group.pack_start(self.unused_expander, False, False, 0)
|
||||
self.advanced_box.pack_start(self.unused_models_group, False, False, 0)
|
||||
self.unused_models_scroll.connect("map", lambda *_args: self._fit_unused_downloads_height())
|
||||
self.content_box.pack_start(self.unused_island, False, False, 0)
|
||||
|
||||
# Connect signals
|
||||
self.engine_combo.connect("changed", self._on_engine_changed)
|
||||
@@ -5241,7 +5276,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
|
||||
def _default_language_for_engine(self, engine: str) -> str:
|
||||
"""Return a safe default language for the selected engine and model."""
|
||||
if engine == "vosk" or self._is_selected_whispercpp_model_english_only():
|
||||
if engine == "vosk":
|
||||
return "en-us"
|
||||
return "auto"
|
||||
|
||||
@@ -5591,22 +5626,22 @@ class SettingsDialog(Gtk.Dialog):
|
||||
|
||||
def _refresh_unused_downloads(self):
|
||||
"""Rebuild the Unused downloads list, or hide it when empty."""
|
||||
if not hasattr(self, "unused_models_group"):
|
||||
if not hasattr(self, "unused_models_group") or self.unused_island is None:
|
||||
return
|
||||
|
||||
if self._get_selected_engine() == "remote_api":
|
||||
self.unused_models_group.hide()
|
||||
self.unused_island.hide()
|
||||
return
|
||||
|
||||
unused = self._list_unused_downloads()
|
||||
self.unused_models_group.clear_rows()
|
||||
if not unused:
|
||||
self.unused_models_group.hide()
|
||||
self.unused_island.hide()
|
||||
return
|
||||
|
||||
count = len(unused)
|
||||
leftover = "leftover model" if count == 1 else "leftover models"
|
||||
self.unused_expander.set_label(f"Unused downloads ({count} {leftover})")
|
||||
leftover = "unused model" if count == 1 else "unused models"
|
||||
self.unused_expander_subtitle.set_text(f"{count} {leftover} on disk")
|
||||
|
||||
was_expanded = self.unused_expander.get_expanded()
|
||||
for model_id, title, size_label in unused:
|
||||
@@ -5627,7 +5662,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
)
|
||||
self.unused_models_group.add_row(row)
|
||||
|
||||
self.unused_models_group.show_all()
|
||||
self.unused_island.show_all()
|
||||
self.unused_expander.set_expanded(was_expanded)
|
||||
self._fit_unused_downloads_height()
|
||||
|
||||
@@ -5639,6 +5674,10 @@ class SettingsDialog(Gtk.Dialog):
|
||||
was cut off below the edge of the viewport while the header still
|
||||
counted it (#683).
|
||||
"""
|
||||
if not hasattr(self, "unused_models_scroll"):
|
||||
return
|
||||
if self.unused_expander is not None and not self.unused_expander.get_expanded():
|
||||
return
|
||||
_, natural_height = self.unused_models_group.listbox.get_preferred_height()
|
||||
self.unused_models_scroll.set_min_content_height(
|
||||
_clamp_unused_downloads_height(natural_height)
|
||||
@@ -5881,7 +5920,6 @@ class SettingsDialog(Gtk.Dialog):
|
||||
return
|
||||
|
||||
engine = _engine_from_display(engine)
|
||||
english_only_whispercpp = self._is_selected_whispercpp_model_english_only()
|
||||
|
||||
for lang_code, lang_info in SUPPORTED_LANGUAGES.items():
|
||||
display_text = lang_info["name"]
|
||||
@@ -5893,9 +5931,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
is_downloaded = _is_vosk_model_downloaded("small", lang_code)
|
||||
display_text += " ✓" if is_downloaded else " ↓"
|
||||
elif engine in ["whisper", "whisper_cpp", "parakeet", "faster_whisper", "remote_api"]:
|
||||
if english_only_whispercpp and lang_info.get("whisper") != "en":
|
||||
continue
|
||||
# Both Whisper and whisper.cpp support auto-detect
|
||||
# An English-only model must not hide other languages: picking
|
||||
# Polish (or auto) retargets to the multilingual sibling.
|
||||
if lang_code == "auto":
|
||||
display_text += " ⚠"
|
||||
else:
|
||||
@@ -5915,8 +5952,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
|
||||
if self._is_selected_whispercpp_model_english_only():
|
||||
self.language_warning.set_markup(
|
||||
"<span foreground='#e5a50a'>⚠ English-only model selected. "
|
||||
"Language choices are limited to English.</span>"
|
||||
"<span foreground='#e5a50a'>⚠ This model only understands English.</span>"
|
||||
)
|
||||
self.language_warning.show()
|
||||
elif lang_info.get("warning"):
|
||||
@@ -5938,7 +5974,7 @@ class SettingsDialog(Gtk.Dialog):
|
||||
"""
|
||||
if self._processing_language_change:
|
||||
return
|
||||
if self._initializing or self._applying_settings:
|
||||
if self._initializing or self._applying_settings or self._simple_driving:
|
||||
return
|
||||
|
||||
lang_code = self.language_combo.get_active_id()
|
||||
@@ -6229,7 +6265,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
if is_remote:
|
||||
self.model_row.hide()
|
||||
self.model_variant_row.hide()
|
||||
self.unused_models_group.hide()
|
||||
if self.unused_island is not None:
|
||||
self.unused_island.hide()
|
||||
self.model_info_card.hide()
|
||||
self.remote_server_group.show_all()
|
||||
self.remote_status_label.show()
|
||||
@@ -6364,8 +6401,8 @@ class SettingsDialog(Gtk.Dialog):
|
||||
if already_have and already_have != model_name:
|
||||
target = already_have
|
||||
message = (
|
||||
f"You already have {_model_display_name(already_have)} on disk — "
|
||||
"using it needs no download"
|
||||
f"You already have {_model_display_name(already_have)} on disk. "
|
||||
"Using it needs no download."
|
||||
)
|
||||
|
||||
if target == model_name:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
Version information for Vocalinux.
|
||||
"""
|
||||
|
||||
__version__ = "0.16.2"
|
||||
__version_info__ = (0, 16, 2)
|
||||
__version__ = "0.17.0"
|
||||
__version_info__ = (0, 17, 0)
|
||||
__author__ = "Jatin K Malik"
|
||||
__email__ = "jatinkrmalik@gmail.com"
|
||||
__license__ = "AGPL-3.0-only"
|
||||
|
||||
@@ -38,6 +38,17 @@ class InstallerCudaDiagnosticsTests(unittest.TestCase):
|
||||
self.assertIn("CUDA_CMAKE_ARGS=$(get_cuda_cmake_args", source)
|
||||
self.assertIn("GGML_CUDA=1", source)
|
||||
|
||||
def test_backend_menu_does_not_promise_to_install_cuda_toolkit(self) -> None:
|
||||
"""NVIDIA boxes used to say the CUDA toolkit would be installed.
|
||||
|
||||
install.sh never installs it. Vulkan is tried first; CUDA is a fallback
|
||||
only when a complete toolkit is already on the machine.
|
||||
"""
|
||||
source = _installer_source()
|
||||
self.assertNotIn("CUDA toolkit will be installed", source)
|
||||
self.assertIn("NVIDIA GPU detected (Vulkan)", source)
|
||||
self.assertIn('printf " │ • %-*s│\\n" 54 "$RECOMMENDED_REASON"', source)
|
||||
|
||||
def test_gpu_build_failures_print_pip_log_tail_before_cpu_fallback(self) -> None:
|
||||
"""Backend failures should expose the real pip/CMake log."""
|
||||
source = _installer_source()
|
||||
|
||||
@@ -185,3 +185,23 @@ def test_agents_does_not_claim_requirements_are_consumed() -> None:
|
||||
assert "`uv.lock` is authoritative" in text
|
||||
assert "Do not edit `requirements/*.txt` by hand" in text
|
||||
assert "https://just.systems" in text
|
||||
|
||||
|
||||
def test_interactive_engine_boxes_share_one_width() -> None:
|
||||
"""Boxes 4/5 used a wider frame and unpadded bullets, so the right edge
|
||||
wandered. Keep every engine-menu echo at the same width as whisper.cpp."""
|
||||
source = _installer_source()
|
||||
start = source.index('echo " │ 1. WHISPER.CPP')
|
||||
end = source.index("Choose engine [1-5]", start)
|
||||
widths = []
|
||||
for line in source[start:end].splitlines():
|
||||
if 'echo " │' not in line and 'echo " ┌' not in line and 'echo " └' not in line:
|
||||
continue
|
||||
drawn = line.split("echo ", 1)[1].strip()
|
||||
assert drawn[0] == drawn[-1] == '"'
|
||||
widths.append(len(drawn[1:-1]))
|
||||
assert widths
|
||||
assert len(set(widths)) == 1, widths
|
||||
assert "Best performance on NVIDIA GPUs (CUDA)" not in source[start:end]
|
||||
assert "Fast on CPU with INT8 quantization" in source[start:end]
|
||||
assert "Checksum-verified Hugging Face models" in source[start:end]
|
||||
|
||||
@@ -54,6 +54,7 @@ def _dialog_stub(language: str = "en-us") -> Mock:
|
||||
# language-changed handler returns before updating dialog.language.
|
||||
dialog._initializing = False
|
||||
dialog._applying_settings = False
|
||||
dialog._simple_driving = False
|
||||
return dialog
|
||||
|
||||
|
||||
|
||||
@@ -658,7 +658,8 @@ class TestSettingsDialogHelperFunctions(unittest.TestCase):
|
||||
self.assertIn("largest model", MODEL_SIZE_TOOLTIP)
|
||||
self.assertIn("Standard multilingual", MODEL_SPECIALIZATION_TOOLTIP)
|
||||
self.assertIn("English-only", LANGUAGE_TOOLTIP)
|
||||
self.assertIn("Type to search", LANGUAGE_TOOLTIP)
|
||||
self.assertIn("Search the list", LANGUAGE_TOOLTIP)
|
||||
self.assertNotIn("limit this list to English", LANGUAGE_TOOLTIP)
|
||||
self.assertIn("only in English", _model_specialization_tooltip("medium.en"))
|
||||
self.assertIn("lower-memory systems", _model_specialization_tooltip("medium-q5_0"))
|
||||
self.assertIn("Turbo", _model_specialization_tooltip("large-v3-turbo"))
|
||||
@@ -705,7 +706,7 @@ class TestSettingsDialogHelperFunctions(unittest.TestCase):
|
||||
"mock hardware reason",
|
||||
"en-us",
|
||||
),
|
||||
("medium.en", "mock hardware reason; English language selected"),
|
||||
("medium.en", "mock hardware reason"),
|
||||
)
|
||||
self.assertEqual(_default_whispercpp_variant_for_size("medium", "en-us"), "medium.en")
|
||||
|
||||
@@ -730,6 +731,7 @@ class TestSettingsDialogHelperFunctions(unittest.TestCase):
|
||||
self.assertEqual(_whispercpp_variant_for_language("medium", "auto"), "medium")
|
||||
self.assertEqual(_whispercpp_variant_for_language("medium.en", "auto"), "medium")
|
||||
self.assertEqual(_whispercpp_variant_for_language("medium.en", "fr"), "medium")
|
||||
self.assertEqual(_whispercpp_variant_for_language("small.en", "pl"), "small")
|
||||
|
||||
# Quantized pairs mirror with language when a match exists.
|
||||
self.assertEqual(
|
||||
@@ -748,6 +750,27 @@ class TestSettingsDialogHelperFunctions(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(_whispercpp_variant_for_language("large", "en-us"), "large")
|
||||
|
||||
def test_english_only_model_does_not_hide_other_languages(self):
|
||||
"""Picking .en used to filter the language list, so Polish could not be chosen."""
|
||||
import os
|
||||
|
||||
source_path = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..",
|
||||
"src",
|
||||
"vocalinux",
|
||||
"ui",
|
||||
"settings_dialog.py",
|
||||
)
|
||||
with open(source_path, "r") as handle:
|
||||
source_code = handle.read()
|
||||
|
||||
populate = source_code.split("def _populate_language_options")[1].split("\n def ")[0]
|
||||
default = source_code.split("def _default_language_for_engine")[1].split("\n def ")[0]
|
||||
self.assertNotIn("english_only_whispercpp", populate)
|
||||
self.assertNotIn('lang_info.get("whisper") != "en"', populate)
|
||||
self.assertNotIn("_is_selected_whispercpp_model_english_only", default)
|
||||
|
||||
def test_whisper_delete_unknown_model(self):
|
||||
"""Test deleting an unknown Whisper model raises ValueError."""
|
||||
from vocalinux.ui.settings_dialog import _delete_whisper_model
|
||||
@@ -770,7 +793,8 @@ class TestSettingsDialogHelperFunctions(unittest.TestCase):
|
||||
with open(source_path, "r") as handle:
|
||||
source_code = handle.read()
|
||||
|
||||
self.assertIn('label="Unused downloads"', source_code)
|
||||
self.assertIn('"Unused downloads"', source_code)
|
||||
self.assertIn("self.content_box.pack_start(self.unused_island", source_code)
|
||||
self.assertIn("self.unused_expander", source_code)
|
||||
self.assertIn("self._on_unused_download_delete_clicked", source_code)
|
||||
self.assertNotIn('title="Remove Model"', source_code)
|
||||
@@ -831,7 +855,7 @@ class TestLanguageComboSearch(unittest.TestCase):
|
||||
self.assertIn("completion.set_text_column(0)", source_code)
|
||||
self.assertIn("Search languages…", source_code)
|
||||
self.assertIn("def _commit_or_restore_language_entry", source_code)
|
||||
self.assertIn("Type to search, or pick from the list", source_code)
|
||||
self.assertIn("Search or pick from the list", source_code)
|
||||
|
||||
def test_dictation_tone_picker_in_source(self):
|
||||
import os
|
||||
@@ -1081,6 +1105,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
self.assertIn("After a model is downloaded", self.about)
|
||||
self.assertIn("Private dictation for Linux, Mac, Windows, and phone.", self.about)
|
||||
self.assertIn("Android beta / iOS TestFlight", self.source)
|
||||
self.assertIn("Windows, unsigned beta", self.source)
|
||||
self.assertIn("Self-hosted, headless", self.source)
|
||||
self.assertNotIn("iOS source build", self.source)
|
||||
self.assertNotIn("SmartScreen", self.about)
|
||||
@@ -1096,6 +1121,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
"https://vocalinux.com",
|
||||
"https://vocahq.com",
|
||||
"https://vocamac.com",
|
||||
"https://vocawin.com",
|
||||
"https://vocaphone.vocahq.com",
|
||||
"https://vocagateway.vocahq.com",
|
||||
"https://github.com/VocaHQ/vocalinux/issues",
|
||||
@@ -1122,6 +1148,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
"Open vocahq.com",
|
||||
"Open vocalinux.com",
|
||||
"Open vocamac.com",
|
||||
"Open vocawin.com",
|
||||
"Open vocaphone.vocahq.com",
|
||||
"Open vocagateway.vocahq.com",
|
||||
"Open the Vocalinux GitHub repository",
|
||||
@@ -1160,6 +1187,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
self.assertIn("def _family_tile", self.source)
|
||||
self.assertIn("platform-linux", self.source)
|
||||
self.assertIn("platform-apple", self.source)
|
||||
self.assertIn("platform-windows", self.source)
|
||||
self.assertIn("platform-android", self.source)
|
||||
self.assertIn("platform-server", self.source)
|
||||
self.assertIn("platform-home", self.source)
|
||||
@@ -1181,6 +1209,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
VOCAWIN_SITE_URL,
|
||||
_can_open_url,
|
||||
)
|
||||
|
||||
@@ -1188,6 +1217,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAWIN_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
VOCAGATEWAY_SITE_URL,
|
||||
GITHUB_REPO_URL,
|
||||
@@ -1293,6 +1323,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
for name, title in (
|
||||
("platform-linux", "Linux"),
|
||||
("platform-apple", "Apple"),
|
||||
("platform-windows", "Windows"),
|
||||
("platform-android", "Android"),
|
||||
("platform-server", "Server"),
|
||||
("platform-home", "Home"),
|
||||
|
||||
@@ -343,10 +343,12 @@ def test_no_second_toplevel_is_created_for_advanced(settings_dialog, dialog_clas
|
||||
import inspect
|
||||
|
||||
source = inspect.getsource(dialog_class._build_simple_model_section)
|
||||
helper = inspect.getsource(settings_dialog._make_expander_card)
|
||||
|
||||
assert "Gtk.Window(" not in source
|
||||
assert "set_transient_for" not in source
|
||||
assert "Gtk.Expander" in source
|
||||
assert "_make_expander_card" in source
|
||||
assert "Gtk.Expander" in helper
|
||||
|
||||
|
||||
def test_opening_simple_mode_describes_the_current_model_instead_of_resetting_it(
|
||||
@@ -536,6 +538,7 @@ def test_changing_advanced_language_refreshes_the_simple_readout(settings_dialog
|
||||
dialog._processing_language_change = False
|
||||
dialog._initializing = False
|
||||
dialog._applying_settings = False
|
||||
dialog._simple_driving = False
|
||||
dialog.language_combo.get_active_id.return_value = "pl"
|
||||
dialog.engine_combo.get_active_text.return_value = "Local (whisper.cpp)"
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def test_snap_puts_gnome_platform_first_on_ld_library_path() -> None:
|
||||
|
||||
|
||||
def test_snap_docs_warn_that_0162_has_no_uinput_plug() -> None:
|
||||
"""v0.16.2 edge has no uinput plug; the connect command must not stand alone."""
|
||||
"""v0.16.2 edge (rev 7) has no uinput plug; the connect command must not stand alone."""
|
||||
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
||||
install = (REPO_ROOT / "docs" / "INSTALL.md").read_text(encoding="utf-8")
|
||||
update = (REPO_ROOT / "docs" / "UPDATE.md").read_text(encoding="utf-8")
|
||||
@@ -63,8 +63,8 @@ def test_snap_docs_warn_that_0162_has_no_uinput_plug() -> None:
|
||||
uinput_lines = "\n".join(line for line in text.splitlines() if "uinput" in line.lower())
|
||||
lowered = uinput_lines.lower()
|
||||
assert "uinput" in lowered
|
||||
assert "0.16.2" in uinput_lines or "rev 7" in lowered
|
||||
assert "plug" in lowered and "no" in lowered
|
||||
assert "0.17" not in uinput_lines
|
||||
|
||||
|
||||
def test_snap_strips_pygobject_and_uses_gnome_gi() -> None:
|
||||
|
||||
@@ -61,12 +61,31 @@ def test_scrollbar_stays_in_the_layout():
|
||||
assert "self.unused_models_scroll.set_overlay_scrolling(False)" in src
|
||||
|
||||
|
||||
def test_mapping_the_list_remeasures_it():
|
||||
"""Refresh may run before the dialog is mapped; map must remeasure so a
|
||||
short first measurement can never leave rows clipped."""
|
||||
src = inspect.getsource(settings_dialog)
|
||||
assert re.search(
|
||||
r"self\.unused_models_scroll\.connect\(\s*\"map\","
|
||||
r"\s*lambda \*_args: self\._fit_unused_downloads_height\(\)",
|
||||
src,
|
||||
)
|
||||
|
||||
|
||||
def test_expanding_the_list_remeasures_it():
|
||||
"""The refresh measures while collapsed and possibly unmapped; expanding
|
||||
must remeasure so a short first measurement can never leave rows clipped."""
|
||||
"""Measure while collapsed is worthless; expanding must remeasure."""
|
||||
src = inspect.getsource(settings_dialog)
|
||||
assert re.search(
|
||||
r"self\.unused_expander\.connect\(\s*\"notify::expanded\","
|
||||
r"\s*lambda \*_args: self\._fit_unused_downloads_height\(\)",
|
||||
src,
|
||||
)
|
||||
|
||||
|
||||
def test_unused_downloads_are_a_sibling_expander_not_nested_in_advanced():
|
||||
"""Same card chrome as Advanced, not an expander inside Advanced."""
|
||||
src = inspect.getsource(settings_dialog)
|
||||
assert "self.content_box.pack_start(self.unused_island" in src
|
||||
assert "self.advanced_box.pack_start(self.unused_models_group" not in src
|
||||
assert "self.unused_expander" in src
|
||||
assert "_make_expander_card" in src
|
||||
|
||||
@@ -225,15 +225,13 @@ def test_unpinned_plain_en_id_still_follows_english(
|
||||
assert dialog_class._resolve_saved_whispercpp_variant(dialog, "medium.en") == "medium.en"
|
||||
|
||||
|
||||
def test_unpinned_quantized_en_id_is_kept_as_legacy(
|
||||
def test_unpinned_quantized_en_id_follows_a_non_english_language(
|
||||
settings_dialog: Any, dialog_class: Any
|
||||
) -> None:
|
||||
"""``{size}.en-q*`` is a real leftover specialization, not a language default."""
|
||||
"""``{size}.en-q*`` cannot transcribe Polish; use the multilingual sibling."""
|
||||
dialog = _dialog_stub(settings_dialog, "pl")
|
||||
|
||||
assert (
|
||||
dialog_class._resolve_saved_whispercpp_variant(dialog, "medium.en-q5_0") == "medium.en-q5_0"
|
||||
)
|
||||
assert dialog_class._resolve_saved_whispercpp_variant(dialog, "medium.en-q5_0") == "medium-q5_0"
|
||||
|
||||
|
||||
def test_unpinned_save_of_derived_en_persists_bare_size_and_rederives() -> None:
|
||||
@@ -271,6 +269,20 @@ def test_leftover_en_in_model_size_does_not_block_language_switch() -> None:
|
||||
assert manager.get_model_size_for_engine("whisper_cpp") == "medium"
|
||||
|
||||
|
||||
def test_english_only_pin_does_not_block_a_non_english_language() -> None:
|
||||
"""A leftover English-only pin must not trap Polish on those weights."""
|
||||
manager = ConfigManager()
|
||||
manager.config["speech_recognition"]["whisper_cpp_model_size"] = "medium"
|
||||
manager.config["speech_recognition"]["whisper_cpp_model_variant"] = "medium.en"
|
||||
manager.config["speech_recognition"]["language"] = "pl"
|
||||
|
||||
assert manager.get_model_size_for_engine("whisper_cpp") == "medium"
|
||||
assert resolve_whispercpp_variant("medium.en", "medium.en", "pl") == "medium"
|
||||
assert resolve_whispercpp_variant("medium.en", "medium.en", "auto") == "medium"
|
||||
assert resolve_whispercpp_variant("medium.en-q5_0", "medium.en-q5_0", "fr") == "medium-q5_0"
|
||||
assert resolve_whispercpp_variant("medium.en", "medium.en", "en-us") == "medium.en"
|
||||
|
||||
|
||||
def test_pinned_save_persists_the_full_variant() -> None:
|
||||
"""A deliberate pin still stores the loadable id so the engine loads it."""
|
||||
manager = ConfigManager()
|
||||
|
||||
+4
-3
@@ -10,7 +10,7 @@ Linux desktop users who want voice dictation that stays on their machine: develo
|
||||
|
||||
## Product purpose
|
||||
|
||||
Vocalinux is free, open-source offline voice dictation for Linux. It turns speech into typed text in whatever app has focus, using local engines (whisper.cpp default, Whisper, VOSK) or an optional user-configured remote API. Success is: install once, dictate in any app, without cloud transcription or telemetry.
|
||||
Vocalinux is free, open-source offline voice dictation for Linux. It turns speech into typed text in whatever app has focus, using local engines (whisper.cpp default, Faster Whisper, Whisper, VOSK, Parakeet) or an optional user-configured remote API. Success is: install once, dictate in any app, without cloud transcription or telemetry.
|
||||
|
||||
## Positioning
|
||||
|
||||
@@ -18,7 +18,7 @@ Offline-first Linux voice typing with real desktop integration (system tray, X11
|
||||
|
||||
## Operating context
|
||||
|
||||
- Install via a one-line shell installer, AppImage from GitHub Releases, AUR, or local Flatpak build; then run from PATH, app menu, or the AppImage binary
|
||||
- Install via a one-line shell installer, AppImage from GitHub Releases, AUR, Snap (`--edge`), or Flatpak release bundle; then run from PATH, app menu, or the AppImage binary
|
||||
- System tray indicator and settings GUI (GTK) with searchable sidebar navigation and an always-visible sidebar footer for dictation status / Test Dictation / Close
|
||||
- Dictation into terminals, browsers, IDEs, office apps
|
||||
- Engines and models chosen for hardware (CPU, optional Vulkan GPU with discrete-device preference)
|
||||
@@ -26,7 +26,7 @@ Offline-first Linux voice typing with real desktop integration (system tray, X11
|
||||
|
||||
## Capabilities and constraints
|
||||
|
||||
- Engines: whisper.cpp (default), OpenAI Whisper, VOSK, optional Remote API
|
||||
- Engines: whisper.cpp (default), Faster Whisper, OpenAI Whisper, VOSK, Parakeet, optional Remote API
|
||||
- Speech languages: large selectable catalog (~33 + Auto-detect) shared by Settings/CLI; VOSK only lists languages with official Alphacephei models; remaining Whisper languages available via Auto-detect
|
||||
- Display servers: X11 and Wayland
|
||||
- Shortcut modes: push-to-talk default (hold Right Alt / Option); toggle available; left/right modifier distinction; configurable modifier+key combos
|
||||
@@ -39,6 +39,7 @@ Offline-first Linux voice typing with real desktop integration (system tray, X11
|
||||
- Optional disable of the missing-tray warning dialog
|
||||
- Settings → Audio family tone picker (Lift, Flick, Ember, Step, Voca, Soft, Chirp, Scale, Drop, Glass, Off) with Preview; new installs default to Voca; catalog files are the family preview WAVs
|
||||
- Vulkan discrete GPU auto-select with manual device override in Advanced settings
|
||||
- Native GTK installs follow the OS dark/light preference unless `GTK_THEME` is already set
|
||||
- Wayland: IBus when `ibus-wayland` is running, including on compositors previously treated as unbridged
|
||||
- Packaging: install.sh (distro python3-gi required; no pip sdist of PyGObject), AppImage (x86_64/aarch64), AUR, Flatpak (local/Flathub status as documented); uv.lock pins Python deps; Justfile replaces Makefile
|
||||
- No usage telemetry in the installed app
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vocalinux-website",
|
||||
"version": "0.16.2",
|
||||
"version": "0.17.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vocalinux-website",
|
||||
"version": "0.16.2",
|
||||
"version": "0.17.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^18.3.3",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vocalinux-website",
|
||||
"version": "0.16.2",
|
||||
"version": "0.17.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -14,6 +14,33 @@ import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const releases = [
|
||||
{
|
||||
version: "v0.17.0",
|
||||
date: "2026-09-16",
|
||||
type: "stable",
|
||||
highlights: [
|
||||
"Faster Whisper engine: CTranslate2 / INT8 Whisper on CPU, optional extra, installer --engine=faster_whisper (PR #543)",
|
||||
"Parakeet TDT 0.6B via sherpa-onnx: default v3-european bundle or v2-english, installer --engine=parakeet (PR #802)",
|
||||
"Speech Model simple setup: language + speed/accuracy first; engine/size under Advanced (PR #801)",
|
||||
"First run seeds recognition language from keyboard layout / locale (PR #796, #777)",
|
||||
"Language picker filters while the list is open (PR #798)",
|
||||
"Recommended model button sets size and specialization together and will not ignore a file already on disk (PR #797, #778)",
|
||||
"Localized punctuation voice commands for it/fr/de/es/pt/nl/pl/ru (PR #642, #640)",
|
||||
"Bare F1–F24 push-to-talk shortcuts (PR #815)",
|
||||
"Snap packaging with ydotool and uinput for native Wayland; Store --edge (PR #519, #823, #822)",
|
||||
"Release workflow attaches Vocalinux-<version>-{x86_64,aarch64}.flatpak (PR #786, #784)",
|
||||
"XWayland clipboard paste instead of layout-garbled xdotool type (PR #680, #657)",
|
||||
"Layout-aware Ctrl+V paste; ydotool releases Ctrl after a timed-out paste (PR #788, #675, #658)",
|
||||
"Read WM_CLASS with xprop so xdotool stops dumping core on KDE Plasma Wayland (PR #807)",
|
||||
"Settings freeze on already-downloaded model, English whisper.cpp .en variant, PTT tray redraw, sink-wake audio cues, Parakeet decode survival (PR #790, #795, #780, #809, #804, #803)",
|
||||
"English-only models no longer hide other languages; picking Polish or auto-detect switches to the multilingual sibling (PR #826)",
|
||||
"Speech Model copy and Unused downloads as a sibling expander of Advanced; VocaWin on the About family grid (PR #826)",
|
||||
"install.sh: Faster Whisper is CPU CTranslate2/INT8; GPU backend does not install the CUDA toolkit (PR #826)",
|
||||
"HDA analog mics (3-8 channel) open at native layout instead of forced 2ch/48kHz; capture is downmixed to mono (PR #829, fixes #813)",
|
||||
"Native GTK installs (AUR / install.sh) follow OS dark/light; skipped when GTK_THEME is set so AppImage and user overrides stay in charge (PR #830, fixes #816)",
|
||||
"Lock-derived Flatpak/AUR deps, hash-pinned exports for every engine extra, published-release verification, install.sh distro matrix (PR #819, #828, #791, #810)",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "v0.16.2",
|
||||
date: "2026-09-05",
|
||||
|
||||
@@ -34,6 +34,17 @@ const engineTable = [
|
||||
iconColor: "text-primary",
|
||||
iconBg: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
engine: "Faster Whisper",
|
||||
speed: "Fast CPU inference (CTranslate2 / INT8)",
|
||||
hardware: "CPU",
|
||||
accuracy: "High (Whisper-class)",
|
||||
footprint: "Optional extra; not in AppImage/Flatpak/Snap",
|
||||
bestFor: "CPU-only machines that want Whisper quality without PyTorch",
|
||||
icon: Sparkles,
|
||||
iconColor: "text-primary",
|
||||
iconBg: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
engine: "VOSK",
|
||||
speed: "Very fast realtime on low-end systems",
|
||||
@@ -45,6 +56,17 @@ const engineTable = [
|
||||
iconColor: "text-primary",
|
||||
iconBg: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
engine: "Parakeet",
|
||||
speed: "Fast CPU (sherpa-onnx TDT 0.6B)",
|
||||
hardware: "CPU",
|
||||
accuracy: "High on bundled English / European languages",
|
||||
footprint: "Large download (~639MB v3-european default)",
|
||||
bestFor: "English or European dictation without the Whisper catalog picker",
|
||||
icon: Cpu,
|
||||
iconColor: "text-primary",
|
||||
iconBg: "bg-muted",
|
||||
},
|
||||
{
|
||||
engine: "Remote API",
|
||||
speed: "Depends on server + network latency",
|
||||
@@ -211,14 +233,14 @@ export default function CompareEnginesPage() {
|
||||
Switching Between Engines
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You can switch between whisper.cpp, Whisper, VOSK, and Remote API from
|
||||
Settings. v0.10.1+ safely stops recognition before switching to
|
||||
prevent crashes. v0.12.0 adds Remote API configuration under Advanced
|
||||
settings for compatible transcription servers.
|
||||
You can switch between whisper.cpp, Faster Whisper, Whisper, VOSK,
|
||||
Parakeet, and Remote API from Settings. Speech Model simple setup
|
||||
(v0.17.0) puts language and speed/accuracy first; engine choice lives
|
||||
under Advanced. v0.10.1+ safely stops recognition before switching.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 grid gap-6 md:grid-cols-2 xl:grid-cols-4">
|
||||
<section className="mt-12 grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<article className="rounded-[12px] border border-border bg-background p-6">
|
||||
<h2 className="mb-3 text-2xl font-semibold">
|
||||
When to pick whisper.cpp
|
||||
@@ -239,6 +261,14 @@ export default function CompareEnginesPage() {
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="rounded-[12px] border border-border bg-background p-6">
|
||||
<h2 className="mb-3 text-2xl font-semibold">When to pick Faster Whisper</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Choose Faster Whisper for Whisper-class accuracy on CPU without the
|
||||
PyTorch stack. Optional extra; not bundled in AppImage, Flatpak, or Snap.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="rounded-[12px] border border-border bg-background p-6">
|
||||
<h2 className="mb-3 text-2xl font-semibold">When to pick VOSK</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -247,6 +277,16 @@ export default function CompareEnginesPage() {
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="rounded-[12px] border border-border bg-background p-6">
|
||||
<h2 className="mb-3 text-2xl font-semibold">When to pick Parakeet</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Choose Parakeet for English or European dictation on CPU via
|
||||
sherpa-onnx. It uses a bundled language set (v3-european default)
|
||||
instead of the Whisper catalog picker. Optional extra; not in
|
||||
AppImage, Flatpak, or Snap.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="rounded-[12px] border border-border bg-background p-6">
|
||||
<h2 className="mb-3 text-2xl font-semibold">
|
||||
When to pick Remote API
|
||||
|
||||
@@ -54,6 +54,11 @@ const reliabilityFeatures = [
|
||||
];
|
||||
|
||||
const releaseMap = [
|
||||
{
|
||||
version: "v0.17.0",
|
||||
highlights:
|
||||
"Faster Whisper and Parakeet engines, Speech Model simple setup, first-run system language, Snap ydotool/uinput, workflow Flatpak bundles, XWayland clipboard paste, xprop WM_CLASS instead of crashing xdotool, HDA analog mics at native layout, native GTK OS dark/light, English-only models no longer trap other languages, and PTT tray/audio cue fixes.",
|
||||
},
|
||||
{
|
||||
version: "v0.16.2",
|
||||
highlights:
|
||||
|
||||
@@ -4,8 +4,8 @@ import { CheckCircle2, ChevronRight, Globe, Languages, Mic } from "lucide-react"
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const WHISPER_ENGINES = ["whisper.cpp", "Whisper"] as const;
|
||||
const ALL_ENGINES = ["whisper.cpp", "Whisper", "VOSK"] as const;
|
||||
const WHISPER_ENGINES = ["whisper.cpp", "Whisper", "Faster Whisper"] as const;
|
||||
const ALL_ENGINES = ["whisper.cpp", "Whisper", "Faster Whisper", "VOSK"] as const;
|
||||
|
||||
const supportedLanguages = [
|
||||
{
|
||||
@@ -286,7 +286,7 @@ const features = [
|
||||
{
|
||||
title: "Engine-Aware Catalog",
|
||||
description:
|
||||
"Whisper engines cover the full language list. VOSK appears only where an official model exists.",
|
||||
"Whisper engines (whisper.cpp, Whisper, Faster Whisper) cover the full language list. VOSK appears only where an official model exists. Parakeet uses its own English / European bundles and ignores the catalog picker.",
|
||||
icon: Mic,
|
||||
},
|
||||
{
|
||||
|
||||
+11
-8
@@ -39,8 +39,8 @@ const homeJsonLd = [
|
||||
priceCurrency: "USD",
|
||||
},
|
||||
description:
|
||||
"Offline voice dictation and speech-to-text for Linux with whisper.cpp and VOSK.",
|
||||
softwareVersion: "0.16.2",
|
||||
"Offline voice dictation and speech-to-text for Linux with whisper.cpp, Faster Whisper, Whisper, VOSK, and Parakeet.",
|
||||
softwareVersion: "0.17.0",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
@@ -50,7 +50,7 @@ const homeJsonLd = [
|
||||
downloadUrl: GITHUB_REPO_URL,
|
||||
screenshot: "https://vocalinux.com/og-image.png",
|
||||
featureList: [
|
||||
"Local speech recognition with whisper.cpp, Whisper, and VOSK",
|
||||
"Local speech recognition with whisper.cpp, Faster Whisper, Whisper, VOSK, and Parakeet",
|
||||
"Remote API speech recognition for compatible self-hosted transcription servers",
|
||||
"Silero neural voice activity detection with amplitude fallback",
|
||||
"Works with X11 and Wayland",
|
||||
@@ -68,7 +68,7 @@ const homeJsonLd = [
|
||||
"Adaptive audio and IBus-aware text injection",
|
||||
"Clipboard fallback for unsupported Wayland compositors",
|
||||
"Sound effects toggle for audio feedback",
|
||||
"whisper.cpp, Whisper, VOSK, and Remote API support",
|
||||
"whisper.cpp, Faster Whisper, Whisper, VOSK, Parakeet, and Remote API support",
|
||||
"Advanced whisper.cpp anti-hallucination settings",
|
||||
"Auto-recover speech recognition after system suspend/resume",
|
||||
"Safe engine switching without segfaults",
|
||||
@@ -111,7 +111,7 @@ const homeJsonLd = [
|
||||
name: "How do I switch between speech engines?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Use the settings GUI or CLI flags for whisper.cpp, Whisper, VOSK, or Remote API. Open Settings from the tray and use the sidebar (search works). Remote API options live under Advanced.",
|
||||
text: "Use the settings GUI or CLI flags for whisper.cpp, Faster Whisper, Whisper, VOSK, Parakeet, or Remote API. Open Settings from the tray and use the sidebar (search works). Remote API options live under Advanced.",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -367,8 +367,10 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div className="workbench-meta">
|
||||
<span className="chip">whisper.cpp default</span>
|
||||
<span className="chip">Faster Whisper</span>
|
||||
<span className="chip">Whisper</span>
|
||||
<span className="chip">VOSK</span>
|
||||
<span className="chip">Parakeet</span>
|
||||
<span className="chip">Remote API</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -612,7 +614,7 @@ export default function HomePage() {
|
||||
/>
|
||||
<figcaption>
|
||||
<strong>Engine and model controls</strong>
|
||||
Pick whisper.cpp, Whisper, VOSK, or a Remote API you trust.
|
||||
Pick whisper.cpp, Faster Whisper, Whisper, VOSK, Parakeet, or a Remote API you trust.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure className="shot shot-frame">
|
||||
@@ -798,8 +800,9 @@ export default function HomePage() {
|
||||
question: "Does Vocalinux work offline?",
|
||||
answer: (
|
||||
<>
|
||||
Local engines (whisper.cpp, Whisper, and VOSK) process
|
||||
speech on your machine. Remote API is optional and only
|
||||
Local engines (whisper.cpp, Faster Whisper, Whisper, VOSK,
|
||||
and Parakeet) process speech on your machine. Remote API is
|
||||
optional and only
|
||||
talks to servers you configure.{" "}
|
||||
<Link href="/offline/">Offline details</Link>.
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user