* feat(parakeet): add optional Underdog Bark 3.0 int8 model
Adds bark-3.0 to the Parakeet model picker: Underdog Bark 3.0, an
English fine-tune of Parakeet TDT 0.6B v3, as the int8 sherpa-onnx
conversion published at velddev/Underdog-Bark-3.0-sherpa-onnx. It runs
on the existing sherpa-onnx recognizer, pinned to a revision with every
file checked against model_checksums.txt, like Orukeet (#840).
The conversion writes the unknown-word token where a quote mark was, so
the Parakeet transcript drops <unk>; the other models never emit it.
* docs(parakeet): say when to pick Bark 3.0; loosen its catalog test
The README now says Bark suits English dictation of conversational
speech and v3-european stays the choice for read or multilingual
speech. The test checks that bark-3.0 is offered rather than that it is
the last catalog entry, so a later model does not break it.
* test(parakeet): add docstrings to the Bark tests
* feat(parakeet): add optional 4-bit Underdog Bark 3.0 build
Adds bark-3.0-int4: the same Bark 3.0 weights quantized to 4-bit with
onnxruntime's HQQ quantizer, for machines short on memory. It needs
about half the RAM of the int8 build (0.64 GB against 1.28 GB peak) and
a 379 MiB download, at two to four times the CPU time (about 2x on
Intel, 3.7x on AMD by the publisher's measurement). The files are
published in velddev/Underdog-Bark-3.0-sherpa-onnx (Hugging Face PR #1),
and both Bark entries now pin its revision 3ae3af1, which holds both
builds; the int8 files are unchanged there.
Catalog entries gain an optional "precision" key, so the loader opens
encoder.int4.onnx and friends instead of assuming int8; every existing
model keeps the int8 default. The README section now covers both
builds in a few lines.
* test(parakeet): type the Bark test signatures
* docs(parakeet): name the Bark sherpa-onnx source and its pins
The README now says where both Bark builds download from (a pinned revision of
velddev/Underdog-Bark-3.0-sherpa-onnx), that it is an unofficial conversion, and
that each file is checked against its bundled SHA-256 pin, as the Orukeet
section does.
---------
Co-authored-by: Jatin <jatinkrmalik@gmail.com>
The Input Device dropdown has a fixed width and cuts labels off at about
18 characters. Raw ALSA names such as "sof-hda-dsp: - (hw:0,6)" therefore
all look like "sof-hda-dsp: - (h…", and the tooltip always showed the same
generic text, so there was no way to tell which entry was selected.
The tooltip now shows the full label of the selected device. It updates
when the list is filled and every time the selection changes, and goes
back to the generic hint for System Default.
* feat(web): Linux-flavoured 404 page that sends visitors back
The static export served Next's default "404: This page could not be
found." with no way back into the site.
- "This page is not in your $PATH" heading, plus three true facts about
404 (chmod 404, exit 404 -> $? = 148, 0x194 / 0o624 / 0b110010100).
- "Did you mean ...?" suggests the closest page the site links to, by edit
distance after lowercasing. Catches typos and case slips, which matter
because GitHub Pages paths are case-sensitive.
- Back to home and Install buttons, plus quick links to the install
guides, FAQ, engine comparison, troubleshooting and changelog.
- Title "Page not found | Vocalinux", noindex.
* fix(web): keep the 404 page working on malformed URLs
decodeURIComponent throws on escapes like /%FF/, which replaced the 404
page with an error screen. Fall back to the raw path, and add mounted
tests for the suggestion and the malformed case.
* fix(web): SEO fixes from Search Console and Lighthouse audit
Technical:
- Emit BreadcrumbList JSON-LD from the same trail as the visual breadcrumbs
on every SeoSubpageShell page.
- absoluteUrl() no longer appends a trailing slash to file paths. The
Organization and publisher logo URLs pointed at /vocalinux.png/, a 404.
- Fetch the GitHub star count once at build time in next.config.js and
drop the client-side api.github.com fetch and preconnect (Lighthouse
"third-party code" on every page). Falls back to the "GitHub" label.
- Fix h1 -> h3 heading skips on 11 pages by promoting the leading card
headings to h2; new .subpage-card-title keeps the old h3 sizing.
- Header and footer logo get alt="" next to the visible "Vocalinux" text.
- Footer links every guide, distro and feature page, so the pages added in
#1038 and /vs-nerd-dictation/ (Discovered, not indexed) and /writers/
(no inbound links) are linked sitewide.
Titles and descriptions:
- Rewrite titles/descriptions for pages with high impressions and low CTR
in Search Console (home, FAQ, compare, languages, open-source, GPU,
alternatives, offline, install) to lead with the queries they rank for.
- Distro pages use "<Distro> Speech to Text: Offline Voice Typing Setup"
as the search title; the on-page h1 is unchanged.
- Remove brand repetition and shorten 75+ character titles.
- Bump sitemap lastmod for the rewritten pages.
* fix(web): keep home install card titles styled
The home page's install cards still use h3; only /install/ moved to h2.
Style both so the home cards keep their size and spacing.
* fix(web): stop doubled title suffix and correct stale page claims
- buildPageMetadata now emits titles that already end in the brand
(e.g. "... | Vocalinux") as absolute, so the layout template no longer
renders "| Vocalinux | Vocalinux" on 15 existing pages
- autostart: Start on Login moved out of the tray menu in v0.18.1; point
readers at Settings -> General and the first-run prompt
- wayland: align compositor support with docs/DISTRO_COMPATIBILITY.md
(sway good, Hyprland experimental, river untested)
* docs(web): add guides for v0.17-v0.18 features and more distros
New pages for features that only had changelog one-liners:
/parakeet/, /faster-whisper/, /dictation-pad/, /transcribe-audio-files/,
/custom-dictionary/, /multilingual-dictation/, /pipewire/,
/global-shortcuts/, /hyprland-sway/, /local-gateway/, /flatpak/,
/install/nixos/, plus /install/debian/, /install/opensuse/,
/install/linux-mint/ and /install/pop-os/ via the distro route.
Each page is checked against src/ and docs/ at v0.18.5, carries
TechArticle/HowTo/FAQPage JSON-LD built from the visible content, and
links to existing guides. The distro route now lists support levels from
the compatibility matrix instead of unsourced "tested on" claims, uses
the documented install commands, adds AUR and native package options,
and drops its duplicate breadcrumb.
Registration: sitemap entries, breadcrumb titles, a Features footer
group, links from the install hub, homepage engine chips and the
reliability page, and a test that every static route is in the sitemap.
The homepage VocaPhone card now says Android is on Google Play.
* chore(release): v0.18.5
- Update version from 0.18.4 to 0.18.5
- Add v0.18.5 sections to CHANGELOG, README, docs/UPDATE.md, metainfo.xml and the website changelog
- Bump website package metadata and softwareVersion schema
- Add the 0.18.5 row to the RELEASE_PROCESS version history
* docs(release): qualify the clipboard-history claim for wl-copy 2.2+
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(audio): bound capture reads and support cancellation
* fix(recognition): bound and serialize capture shutdown
* fix(recognition): cancel retries and report stalled microphones
* fix(audio): prefer pulse when the ALSA default is unavailable
* fix(recognition): guard late callbacks and finalize capture tests
* docs(audio): explain input selection and stall recovery
* fix(speech): enforce auto-pause across buffered reload timeouts
* fix(speech): recover auto-resume after reload timeout
* test(speech): fix auto-pause follow-up test regressions
* fix(speech): retain auto-resume after reconfigure timeout
* fix(tray): preserve auto-pause recovery across suspend
* test(capture-shutdown): give the bare tray the suspend path's shortcut managers
_on_system_suspend now releases the main and per-language hotkey
listeners before sleep (#1025); the fixture builds TrayIndicator without
__init__, so it needs the two attributes present.
* fix(tray): release hotkey listeners on suspend even while auto-paused
Merging #1025's suspend cleanup with this branch's auto-pause handoff
left an early return that skipped shortcut_manager.stop() whenever a
configured game was active, keeping EVIOCGRABs held through sleep. The
paused branch now only owns the model-lifecycle work; listener release
and stopping active recognition run for every suspend.
* fix(tray): hold the apply lock only around the download's verify and save
Wrapping the whole recommended-model download in _apply_settings_lock
blocked every GTK-thread settings apply (e.g. Test Dictation) for the
length of the transfer. The engine already serializes reconfigures, so
the lock only needs to cover verifying the download still owns the
receipt and saving it; an apply that lands mid-download now proceeds
instead of freezing the app.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(installer): add upgrade mode that keeps the existing configuration
Re-running install.sh on an existing install replayed first-time setup:
the mode menu offered only interactive or automatic, automatic mode
forced whisper.cpp regardless of the configured engine, and nothing said
config.json would be kept, even though docs/UPDATE.md promised the
installer updates in place and preserves configuration.
Detect an existing install early (venv activate + config.json) and offer
an upgrade path:
- The curl|bash mode menu gains 'Upgrade (keep current settings)' as the
default when an install is detected; the guided setup offers it first
too.
- A --upgrade flag runs the same path non-interactively for scripts, and
falls back to a normal automatic install when nothing is installed.
- Upgrade mode reads speech_recognition.engine from the existing
config.json instead of forcing whisper.cpp, reuses the venv, updates
that engine's dependencies, and rebuilds pywhispercpp only when the
installed version drifts from the pinned release or GPU support is
missing.
- config.json is never modified: every engine path that may rewrite it
(parakeet, faster_whisper, the whisper.cpp -> vosk fallback) goes
through a wrapper that is a logged no-op in upgrade mode, and
verify_configured_engine reports an unimportable configured engine
instead of repairing the file.
- The installer prints 'Existing configuration found at
~/.config/vocalinux/config.json - it will be kept.' when it detects
one.
Update docs/UPDATE.md with the upgrade behavior and add
tests/test_installer_upgrade.py covering detection, the mode menu
default, engine resolution from config, config immutability, the guided
offer, --upgrade parsing, and an end-to-end pty run of the piped
installer.
Fixes#1009
* test(installer): isolate the upgrade pty test from host changes
Skip system-dependency and model steps in the end-to-end installer run
so the only host-mutating calls are gated out entirely; the timed
interrupt is no longer the only thing between the marker and sudo.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(evdev): release EVIOCGRAB when forwarding cannot run (#1021)
With the internal evdev listener, Vocalinux holds EVIOCGRAB on physical
keyboards and re-emits through uinput clones. Two windows left the grab
held while nothing could forward:
- Suspend/resume: grabs persisted through sleep while the compositor's
input stack was rebuilt; the listener was torn down and rebuilt ~6s
after resume, and keystrokes typed at the GNOME lock screen in that
window went to a stale forwarding path.
- A monitor thread wedged mid-iteration (fd, lock, or a clone write that
never returns): grabs stayed held forever because the self-healing
path only fired on write exceptions, and the unexpected-exit cleanup
only ran when the thread died.
_on_system_suspend now tears the shortcut listeners down before the
machine sleeps, so no grab survives the suspend boundary; the existing
resume flow rebuilds them once /dev/input settles.
A forwarding watchdog now runs alongside the monitor. The monitor stamps
a heartbeat on every loop; if it goes stale while grab->clone pairs
exist, the watchdog runs the existing _release_failed_forwarder path on
each device so input reaches applications directly, and tracks them as
suspended. Once dispatching resumes, suspended devices are removed so
the rescan re-opens them through the standard open->clone->grab path.
Generation checks keep a recycled fd from ever being torn down under a
newer generation.
* fix(evdev): re-arm watchdog-released devices on the monitor thread
The watchdog closing a device fd races the monitor's in-flight select:
a dead descriptor in the select set trips the error handler, which
exits the loop and tears down every keyboard. Recovery now runs where
the close is safe — the monitor retires each suspended device at its
own loop top and the rescan re-opens it through the standard
open->clone->grab path. The watchdog only ungrabs (safe cross-thread:
that fd never sits in the select set) and marks the device suspended.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
On GTK's Wayland backend, a toplevel with the DIALOG type hint and no
transient parent becomes an orphan dialog: the next toplevel to receive
keyboard focus silently becomes its xdg_toplevel parent, and GNOME then
raises, minimizes, and workspace-moves the two windows as one.
Settings, the transcript viewer, and the log viewer are standalone
windows opened parentless, so the dictation pad (focused constantly)
adopted them, making Alt+Tab group them and minimize/workspace changes
apply to both. Force the NORMAL type hint on all three so they stay
independent.
Fixes#984
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(aur): gate source publication on attested sdists
Prepare and verify the canonical release source, build the exact PKGBUILD on Arch, and publish the gated artifact without further rewrites. Part of #701.
* fix(aur): isolate local gate and binary publication
Keep local gate distributions and diagnostics in a private workspace, preserving other build outputs and failed diagnostics. Publish vocalinux-bin after its own AppImage inputs without depending on the source gate or unrelated release jobs. Add regression coverage for both review findings.
* fix(clipboard): keep paste-injection copies out of clipboard history
The clipboard-paste injection path writes the dictated text to the
clipboard only to feed the paste chord, then restores the previous
content (#588, #646). Clipboard managers such as Klipper and cliphist
still record that transient copy, so every dictation lands in history.
Mark the injection copy with `wl-copy --sensitive`, which clipboard
managers honour by skipping the entry. The flag is probed once via
`wl-copy --help` (added in wl-clipboard 2.2) so older builds keep
working. It is only set when Copy to Clipboard is off: with that
setting on, the dictated text is meant to stay, so it remains an
ordinary copy. Restoring the user's previous content is never marked
sensitive. xclip/xsel have no equivalent and are unchanged.
* test(clipboard): address review on sensitive paste copies
- Move the unittest main guard below TestSensitivePasteCopy so running
the file directly also runs the new tests.
- Add -> None return hints to the new test methods.
- Cover _copy_to_clipboard forwarding the sensitive hint when a tool
declines and the next one is tried.
- Docs: history suppression needs wl-copy 2.2+ and a clipboard manager
that honours the hint; older wl-copy and the XWayland xclip/xsel path
still produce ordinary copies.
* fix(clipboard): type the wl-copy probe cache and adapt the X11 paste test
- Declare _wl_copy_sensitive_supported as a typed class default instead
of reading it through getattr(), which mypy (now in CI since #968)
flags as returning Any.
- test_plain_x11_clipboard_paste_uses_xdotool_and_x11_clipboard
asserted the exact _copy_to_clipboard kwargs. Check the text and the
X11 tools instead, so it does not depend on the sensitive hint (which
xclip ignores) or on the copy_to_clipboard setting.
* test(clipboard): wait for the restore thread instead of a fixed sleep
TestSensitivePasteCopy slept 0.5 s and hoped the background restore had
run. On a slow runner the patches could end first. Wait on an event set
by the restore copy, inside the patches, with a generous timeout.
* fix(recognition): never lose a stop that races start, and read capture outside the buffer lock
start_recognition() published LISTENING (and played the start cue) before setting should_record. A stop arriving in that window, e.g. a push-to-talk release handled on another thread, was undone by the later should_record = True, leaving the capture thread recording with no way to stop it. The session is now armed under _buffer_lock before LISTENING is visible.
The capture loop held _buffer_lock across source.read_chunk(). When a read blocks (the audio server reconfigures the device, a USB/Bluetooth mic stalls), stop_recognition() gives up its bounded join and then blocks forever on that lock, stuck in PROCESSING. The read now happens outside the lock and the session is re-checked before the chunk is appended.
* test(recognition): wait for the stalled read before stopping
Signal from inside the blocking read instead of sleeping after the first, non-blocking one, so the test always exercises the stalled-read path.
* test(recognition): run the stalled-read test against the real NumPy
Older test modules leave sys.modules["numpy"] as a MagicMock. Under that leak the capture loop's first chunk raised before the stalled read was reached, so the test failed in the full-suite order on CI. Restore the real module for these tests, as test_playback_ducker.py already does.
---------
Co-authored-by: AAG Projects Team <293887937+aagprojectsteam-max@users.noreply.github.com>
Use the maintained AyatanaAppIndicatorGlib 2.0 indicator when it is installed, mirroring the existing Gtk.Menu into a Gio.Menu whose actions keep stable, never-reused names and fire the original items. Fall back to the GTK 3 indicators otherwise; the AppImage stays on its bundled GTK 3 indicator, and the .deb recommends the glib typelib.
Fixes#1013
Inside the snap, list the sound server's capture sources with pactl and record from the chosen one by scoping PULSE_SOURCE to each PortAudio open (serialized behind one lock). Settings saves the stable source name, keeps each dropdown entry's server name so selection never queries the server from the GTK thread, and the snap reports whisper.cpp as CPU-only instead of probing host GPU tools.
Fixes#1011
Refuse close requests on the pre-mapped Wayland overlay so closing its GNOME dock entry no longer destroys it and brings back the first-recording focus loss, and unmap the overlay when it is turned off so the dock entry goes away immediately.
Fixes#1010
* chore(deps): allow uv versions below 0.14
* fix(ci): align uv version and generated exports
* fix(dev): check Fedora build headers and AppImage GI bundle
Fail early with distro-specific native dependency hints before dev sync. Verify the AppImage's PyGObject installation and Python path after linuxdeploy, and report isolated GI import failures with actionable path diagnostics. Guard the CI uv pin and cover both preflights with tests.
* feat(build): add locked reproducible wheel gate
Build independently in clean source trees and virtualenvs, compare wheel bytes, and stage only verified canonical distributions. Safely clean recognized prior outputs on repeat runs.
Part of #701
* ci(release): verify wheel reproducibility before publishing
Run the shared gate in PR and tag workflows, preserve failure diagnostics, and keep verification artifacts out of release checksums. Make Snap Store publication wait for the gate and reuse the canonical distributions for PyPI.
Part of #701
* docs(release): document reproducible wheel validation
Describe the locked toolchain, canonical publication boundary, same-platform scope, diagnostic outputs, and safe automatic cleanup.
* chore(scripts): update executable permissions
* fix(build): isolate release tooling and preserve cleanup retries
Exclude host site-packages from build-only virtualenvs so unlocked setuptools plugins cannot affect verified distributions. Delete staged artifacts before canonical ownership evidence to recover safely from interrupted cleanup.
Add regression tests and document the build-only environment exception.
* fix(release): make reproducible builds retryable and refresh tools
Regenerate release build exports from uv.lock. Hold a checkout lock through cleanup and staging, and atomically rename complete archives into dist. Cover interrupted staging, competing processes, and lock release with regression tests.
---------
Co-authored-by: Kanishk Pachauri <itskanishkp.py@gmail.com>
Co-authored-by: sesav <1636488+sesav@users.noreply.github.com>
- Update version from 0.18.3 to 0.18.4
- Add v0.18.4 sections to CHANGELOG, README, docs/UPDATE.md, metainfo.xml and the website changelog
- Bump website package metadata and softwareVersion schema
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(recognition): cancel in-progress transcription with hotkey
Adds per-session cancellation across the manager, tray, and Test Dictation.
Fixes#991
* fix(settings): keep Test Dictation cancellation responsive
Fixes#991
* fix(recognition): keep late workers cancellable (#991)
* test(tray): avoid early tray import in cancellation tests
* fix(recognition): keep a cancelled capture thread out of the next session
A cancel returns to IDLE without joining the capture thread, so a thread
still in its reconnection backoff could wake inside a newer recording,
reopen that session's device and write into its buffer. Tie the capture
loop and the reconnection to the session's cancel token: a cancelled or
replaced session stops reading, skips the reopen and exits without
raising an error.
* fix(dictation-pad): keep Test Dictation results and move pad routing to Settings
Keep late recognition callbacks on the test path.
Move Dictation Pad routing to Settings.
Fixes#983Fixes#986
* fix(recognition): ignore stop sentinels in pending transcription
Ignore stop signals when checking queued transcription work.
Add regression coverage for a real stop path leaving a sentinel behind.
Fixes#983
* fix(recognition): don't count orphaned queued audio as pending transcription
* fix(recognition): count superseded workers as pending transcription
* test(auto-pause): give the bare manager its worker-tracking state
* fix(settings): make model downloads explicit and cancel the whole intent
"Use it" on the recommendation card applied a different model than the one
shown, a cancelled or failed apply left the staged model behind so the next
Test Dictation downloaded it, and cancelling a download tore down and then
reloaded the previous model while the dialog waited.
- Recommendation is now passive text, compared by size tier, with a link to
docs/MODEL_SELECTION.md. The "Use it" button is gone.
- Every Settings download asks first and shows size and source host.
Declining, cancelling or failing drops the staged model spec and puts the
pickers back on the saved config.
- Test Dictation refuses a model that is not on disk instead of applying it.
- reconfigure() fetches a missing model before releasing the loaded one, so
a cancel restores state without reloading or rehashing the old model.
Cancellation state is reset only when a download claim is taken or ended.
- Follow-layout mode shows the resolved language in the simple Main language
control, read-only, and simple edits keep the "layout" setting.
Fixes#979Fixes#980Fixes#981Fixes#982Fixes#985Fixes#988
* fix(settings): restore follow-layout mode when a model change is declined
* fix(settings): keep follow-layout and dictation consistent during model changes
* style(tests): format follow-layout regression tests
* fix(recognition): skip resume reinit while a model download is running
* fix(recognition): keep a lazy reload pending when resume is skipped during a download
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(text-injection): route XWayland clients without IBus through xdotool
On KDE Plasma Wayland with the KWin IBus bridge enabled, Vocalinux injects through IBus. An X11 client under XWayland only reaches IBus when it loaded an IBus input method module from GTK_IM_MODULE, QT_IM_MODULE or XMODIFIERS. Without one, the engine commit lands in no input context and still reports OK, so the text was lost with no fallback.
Before each WAYLAND_IBUS injection, check XWayland's active window. A real client whose own environment does not select IBus gets that segment through the WAYLAND_XDOTOOL path, and the backend returns to IBus afterwards so native Wayland apps keep using it.
Fixes#972
* fix(text-injection): honour IBus pins and the client toolkit in the XWayland detour
Keep IBus when the user pinned it, judge a GTK or Qt client only by its own toolkit's IM variable (read from the libraries it maps), and have the clipboard restore after an XWayland paste read the X11 clipboard it wrote even after the backend switched back to IBus.
* fix(text-injection): keep a non-text X11 clipboard on the XWayland paste route
WAYLAND_XDOTOOL pasted through the X11 clipboard even when it held an image or file, which it cannot save and restore. Check the clipboard targets there as the X11 route already does, and type the text instead when they hold no text.
* fix(recognition): queue long dictation segments before the buffer cap
A held push-to-talk key (or a toggle session with no pause) keeps a single
segment growing until the 5000-chunk safety cap discards its oldest 25%.
The cap is a chunk count, not a duration, so the effective limit depends on
the capture format: at 16 kHz a chunk is 64 ms (5 min 20 s), but a device
that negotiates 44.1 kHz feeds 23 ms chunks and the same cap is 1 min 56 s.
A 3-minute hold therefore lost ~87 s of speech before transcription, with
only a log warning to show for it.
Flush the current segment onto the existing transcription queue once it
reaches _max_segment_seconds (90 s), or 75% of the chunk budget when chunks
are shorter, so the destructive trim can never fire on buffered speech.
Recording continues while queued segments are transcribed in order; reload
sessions keep the existing hold-until-reload behaviour.
Adds tests for the mid-hold flush, the buffer headroom, and the reload path.
* fix(recognition): hold push-to-talk text until the key is released
Text injection while PTT is held can release or modify the key.
* fix(recognition): hold PTT text without blocking the recognition worker
c7b9bd6 held transcribed text by waiting on the release gate inside
_process_audio_buffer, on the single recognition worker. Later segments
then sat in the 32-slot queue for the rest of the hold instead of being
transcribed: memory grows with every queued segment, and once 32 are
waiting (~48 min of 90 s segments) _enqueue_audio_segment starts dropping
the oldest speech.
Buffer the processed text instead. Every segment is transcribed as it
arrives; while the key is down its result is appended to a held list, and
the held results are delivered in capture order once the gate opens. The
final buffer carries the session gate too, so it cannot overtake text
that was transcribed before release. Held text is flushed when the worker
exits, at the end of stop_recognition, and after the reload drain. An
empty mid-hold result no longer flushes the held text early.
Tests assert that every queued segment is transcribed before release and
delivered in capture order afterwards.
* fix(recognition): transcribe held push-to-talk segments ahead of release
Keep transcription running during long push-to-talk holds while retaining each result until key release. Text injection during a hold can release or modify the PTT key.
* test(recognition): cover cross-session held text isolation
The per-session release gate must release only its own held text: a slow
worker from an earlier session finishing after a new push-to-talk session
starts must not deliver the new session's text while its key is down.
---------
Co-authored-by: Muhannad Abo Hajia <muhannadabohajia@gmail.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(flatpak): bundle onnxruntime for Silero VAD
Bundle the [vad] extra in the Flatpak and select the matching CPython wheel for each architecture.
Fixes#969
* docs(flatpak): keep the runtime.txt history in the sync script docstring
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(whispercpp): map GPU picker to ggml devices and size threads by effective backend
The GPU picker and auto selection used vulkaninfo indices, but whisper.cpp's
gpu_device is an ordinal over the GPU/IGPU devices ggml registered. ggml skips
Vulkan devices it cannot use (for example a Haswell iGPU), so the indices shift
and a selected NVIDIA card could fall back to CPU while logs claimed Vulkan.
Read ggml's device registry through the loaded pywhispercpp extension, select
and label devices by that ordinal, run on CPU explicitly when ggml has no GPU,
log the effective backend after load, and pick n_threads from the effective
backend instead of the presence of GPU libraries.
Fixes#987Fixes#990
* test(whispercpp): move new ggml probe tests into their test class
* fix(injection): give every RemoteDesktop portal request a unique handle_token
Subscribe to predicted Request paths before calling the portal and retain the RemoteDesktop portal in Flatpak when ydotoold is unavailable.
Fixes#993
* refactor(injection): share the portal close in _demote_portal_backend
* fix(injection): recreate the portal session and keep partial counts under Flatpak
sherpa-onnx 1.13.7 wheels require sherpa-onnx-core, which ships
libonnxruntime.so, but the armv7l wheels omit that metadata and uv.lock
recorded no dependency. install.sh installs the export with --no-deps, so
sherpa_onnx failed to import, Settings hid Parakeet and the installer
quietly switched config.json to whisper.cpp while still reporting Parakeet.
List sherpa-onnx-core in the parakeet extra, verify engine imports right
after installing them, and keep SELECTED_ENGINE in sync when the config
is repaired so the summary names the engine actually configured.
Fixes#973
* fix(hotkeys): keep Logitech HID++ keyboards that report pointer axes
The pointer-axis filter from #900 drops every device that reports
REL_X+REL_Y, assuming a keyboard never does. Logitech HID++ keyboards
(MX Keys, ERGO K860) expose a single node with the same capabilities
as the mice on their receiver, so they were skipped and the shortcut
no longer fired from them.
Capabilities cannot tell them apart, so a device whose name says
keyboard ("keyboard", "keys", "K<digits>") is exempted from the
pointer-axis rule, in both the /proc and the evdev discovery paths.
The MX Master 3 on the same receiver is still skipped.
* fix(evdev): monitor keyboard-pointer combos ungrabbed
* fix(evdev): keep parse_shortcut import and drop per-rescan discovery log
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(overlay): map the dictation overlay at startup on Wayland
Mutter activates newly mapped Wayland toplevels, which can take focus from the editor.
Map the fallback overlay transparently at startup so its first recording does not remap it.
Fixes#992
* fix(overlay): detect the Wayland display by GType name for the prewarm
Detect Wayland from the display GType because GTK3 omits GdkWaylandDisplay from its typelib.
* fix(hotkeys): use the X11 listener for Flatpak on native X11 sessions
Use evdev for Flatpak Wayland/XWayland while preserving the GNOME X11 fallback.
Fixes#971
* docs(flatpak): note that GNOME X11 still needs /dev/input for hotkeys
Slovenian was missing from SUPPORTED_LANGUAGES, so the Settings picker did not offer it and normalize_language_shortcuts dropped any shortcut bound to it. Add it as a Whisper-only entry (whisper code sl; alphacephei publishes no Slovenian VOSK model), map the Slovenian XKB layout si to it for follow-keyboard-layout, and list it on the website languages page.
Fixes#970
* fix(perf): reduce startup and inference overhead
* fix(perf): report effective whisper.cpp fallback settings
* fix(injection): add opt-in X11 paste for long dictations
* perf(vad): lower default silence timeout to 1.2s
* fix(types): resolve mypy errors across application source
* ci: run mypy in the lint job
* ci: include mypy in lint dependency group
* fix(types): narrow evdev timestamp comparison
* refactor(recognition): add SpeechEngine protocol and engine dispatch table
* refactor(settings): move About section and engine tables out of settings_dialog
* docs: add ARCHITECTURE.md and refresh docs index and AGENTS layout
* fix(settings): preserve GTK dialog compatibility after extraction
* style: format touched Python modules
* fix(types): keep local typecheck from syncing dependencies
* test(vad): align main startup expectation with new default
* style: sort engine registry and protocol test imports
* test(settings): follow extracted About builder attribute alias
* test(settings): isolate GTK dialog base in About test
* test(settings): isolate About color scheme build test
* test(settings): restore GTK base after About scheme test
* test(settings): isolate About scheme check from GTK mocks
* test(settings): support extracted dialog mixin methods
* docs: add before/after performance benchmark charts
* fix(injection): type instead of pasting when the clipboard holds non-text data
* fix(release): recreate refs/remotes dropped by git on the Flatpak remote publish
The first real remote publish (v0.18.3) failed at
'flatpak build-update-repo --generate-static-deltas' with
'opendir(refs/remotes): No such file or directory'. ostree init creates
refs/remotes empty, git does not track empty dirs, and the site/repo
checked back out of gh-pages therefore lacks it — while delta generation
opens that dir unconditionally. mkdir -p it (and refs/mirrors) after the
copy. Also name the Snap Store review-queue blockage in its publish
error, so the fix (reject the stale upload on the dashboard, re-run)
surfaces on the job page.
* test(release): pin the refs/remotes mkdir before build-update-repo
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* chore(release): v0.18.3
- Update version from 0.18.2 to 0.18.3
- Add v0.18.3 sections to CHANGELOG, docs/UPDATE.md, metainfo.xml and website changelog
- Bump website package metadata and softwareVersion schema
- Clarify that the uinput-carrying sideload snap is the v0.18.2 asset
* docs(release): include the transcribe modal-grab fix (#965) in v0.18.3 notes
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(ui): release modal grab after TinyDiarize download before transcript opens
The transcribe path's ModelDownloadDialog is modal. On success it stayed
open behind the diarized transcript dialog, holding its grab so every
button on the transcript was unclickable until the hidden dialog was
found and closed. Destroy it on success there and let the transcript be
the completion affordance; the failure path still keeps the dialog up to
show the error.
* style(tests): black-format new dialog-grab tests
* fix(ui): stop download dialog pulse timer on destroy; type test helper
* style(ui): type destroy-handler varargs
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(injection): send newlines as Return on the xdotool backend
xdotool type maps '\n' to the Linefeed keysym, which GTK targets
silently drop — "new line" produced nothing on X11 even though the
segment reached the injector. Split each chunk on '\n' and press
Return between typed segments so a newline lands as a real keypress
in every app.
Verified against a live GtkTextView on Xvfb: the same "A\nB" input
that previously produced 'AB' now produces 'A\nB'.
Fixes#955
* fix(commands): consume trailing punctuation after mid-utterance commands
"New paragraph." inside a longer utterance substituted correctly but
left the engine's sentence-final period typed after the break
("\\n\\n. Does this work now?"). Text-command substitution now
consumes a run of trailing , . ! ? ; : … when it precedes whitespace
or the end of the segment — the reporter's comma-phrased case
("…, new paragraph, …") stops leaking the stray comma the same way.
A mark glued to following text ("newline.io") is untouched.
Fixes#955
* fix(injection,commands): resume mid-chunk on retry, don't eat produced marks
Greptile review follow-ups on #962:
- A failed Return mid-chunk left 'typed' at the chunk start, so the
retry re-sent text xdotool had already delivered. 'typed' now
advances per typed segment and per Return keypress, so the retry
resumes exactly at the un-typed position — and the clipboard
fallback reports the same progress if retries run out.
- _TRAILING_PUNCTUATION could consume a mark produced by an earlier
command replacement ('hello new line exclamation mark' gave '\\n'
instead of '\\n!'). The eater now only consumes marks before
whitespace; the end-of-utterance case ('hello new line.') is a
pre-pass on the raw segment before any substitution runs.
* style: black-format comment indent and test condition
* fix(commands): strip segment before the end-of-utterance cleanup
Trailing whitespace ('hello new line. ') hid the final mark from the
pre-pass: the raw segment ended in a space, and the stripped segment
left the dot at end-of-string where the mid-utterance eater cannot
touch it. Strip first, then run the cleanup.
Also add the missing type hints on the new test helper.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
Fixes#954.
GNOME X11: mutter never delivers physical key events to the XRecord extension pynput relies on, so the listener started cleanly but shortcuts stayed dead. Prefer the evdev backend there (same path as Wayland), falling back to pynput when /dev/input is unusable.
Shutdown: `ibus engine <name>` rewrites the whole X server XKB map, and the unconditional restore collapsed GNOME's multi-layout map and broke layout switching. The daemon check now uses _query_active_engine() (no GNOME gsettings fallback) so a cleared engine reads as None; the restore write is skipped when a real engine is live or the target is an xkb:* source GNOME owns, and still runs for real IM engines (#558).
* fix(packaging): build pywhispercpp at the portable x86-64 baseline
Every packaged channel shipped a libggml-cpu compiled for AVX2+FMA:
the manylinux PyPI wheel itself carries -mavx2 -mfma -mbmi2 code, so
any package that bundles it dies with SIGILL when whisper.cpp loads a
model on a pre-Haswell CPU (the reporter's i5-3337U). GGML_NATIVE=OFF
alone is not the portable baseline either; while the native default
is on, ggml turns every GGML_* SIMD option on, so each is pinned off.
- Snap: force the sdist (PIP_NO_BINARY) with the flag floor via
CMAKE_ARGS in the part's build-environment.
- Flatpak: extend the module's CMAKE_ARGS with the same floor.
- Native .deb/.rpm: the build still vendors the wheels, then rebuilds
libggml-cpu alone from the same hash-pinned sdist and swaps it into
the vendored copies, asserting no AVX/FMA survives; the container
image gains cmake, g++, make and binutils.
- AppImage: extract the pinned-sdist reinstall into a shared helper
and route every CPU-only fallback of the Vulkan path through a new
baseline rebuild at the same floor; the Vulkan compile keeps its
own flags via the same helper. Keeping the wheel on those paths was
the bug's exact binary.
Fixes#957
* style(tests): apply black/isort to the new packaging guards
* fix(native): keep the wheel's libggml providers, alias its base/gomp sonames
Swapping the whole libggml set for the baseline build broke the import
smoke: the wheel's auditwheel-mangled core lib doubles as the
extension's __cxa_call_terminate provider, and its NEEDED entries are
the mangled names. Swap only libggml-cpu, and satisfy the baseline
library's own unmangled NEEDEDs (libggml-base.so.0, libgomp.so.1) with
aliases of the vendored copies, which carry no generated SIMD and are
the same upstream version. Fail closed when the vendored set has
restructured instead of silently shipping the AVX2 wheel.
* fix(packaging): fail builds that lose the speech engine, unblock the ISA assert
Greptile findings on the baseline work:
- A failed baseline rebuild in the AppImage returned success after
remove_appdir_pywhispercpp, so the bundle would ship with no speech
engine at all while logging that the wheel was kept. Propagate the
failure through every CPU-only fallback so the build stops instead.
- The native libggml-cpu assertion used grep -q under pipefail, where
grep's early exit SIGPIPEs objdump and the pipeline reports a miss,
letting an AVX2 library through. Read the full disassembly instead.
* fix(installer): build pywhispercpp from sdist on the CPU path
install_cpu_pywhispercpp let pip pick the manylinux wheel, whose
libggml-cpu is compiled for AVX2+FMA and SIGILLs on pre-Haswell CPUs
(#957). Pass --no-binary pywhispercpp like the Vulkan/CUDA paths so the
sdist compiles against this machine's own instruction set.
* fix(installer): install C++ toolchain on distros that ship without one
Forcing the pywhispercpp sdist on the CPU path needs a compiler, and the
fedora, mageia, alpine, void and solus package maps did not request one.
Add gcc-c++/make (rpm), build-base (alpine), base-devel (void) and
gcc/g++/make (solus) so source installs compile on minimal systems.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
The credential gate silently skipped the remote publish when
FLATPAK_GPG_PRIVATE_KEY / FLATPAK_REPO_TOKEN were unset, so the
documented auto-update remote could stay unpublished for releases at a
time (#951). Emit an Actions warning annotation and a run-summary entry
instead of a plain log line, and note in docs/FLATPAK_REMOTE.md that any
token with Contents write on the remote repo works.
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(commands): keep line breaks from standalone voice commands
Two stacked defects ate the newline "new line" / "new paragraph"
produce when spoken alone:
* _normalize_segment_text stripped all leading whitespace, so the
command's leading newline never reached the injector (a clean
"new line" segment was dropped entirely).
* whisper.cpp transcripts arrive punctuated ("New line."), which
missed the exact-match ladder and left a stray "." after the
substituted break, injected as ". ".
Normalize now strips only spaces/tabs, and the generic path
substitutes on a punctuation-stripped core when the whole segment
is command phrases, so engine edge punctuation cannot leak while
real text keeps it.
Fixes#955
* fix(injection): deliver newline-only segments to the focused app
inject_text treated whitespace-only text as empty and returned early,
so the "\n" a standalone "new line" now correctly produces was
discarded before any backend ran ("Empty text provided, skipping
injection"). Only a truly empty segment is skipped; whitespace-only
input is typed/pasted verbatim (xdotool types "\n" as Return).
Space/tab-only segments never reach this point: segment normalization
strips them upstream.
Fixes#955
* fix(injection): keep skipping space/tab-only segments
The previous commit let every whitespace-only string through, which
also delivered bare spaces and tabs a caller never wants typed.
Narrow the skip to text with no content outside space/tab so "\n"
still reaches the backend while " " stays a no-op (the pre-existing
test_empty_text_injection contract).
Fixes#955
* fix(injection): deliver newline segments over IBus too
IBusTextInjector.inject_text had the same whitespace guard and dropped
"\n" before it reached the engine socket; narrow it the same way so
a newline-only segment is sent while space/tab-only stays a no-op.
Also let capitalize_sentences uppercase the first letter after leading
whitespace, so Vosk + auto_capitalize keeps working on "\n hello".
Covers Greptile's findings: real-socket test for the IBus path, the
capitalize regression via the main() callback seam, and type hints on
the new injector tests.
Fixes#955
* test(injection): add type hints to new IBus socket test
AGENTS.md requires hints on every signature; the Greptile review
flagged the new test for missing them.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-10-15 16:54:05 -07:00
devin-ai-integration[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Jatin K MalikDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(snap): drop uinput plug denied by the Snap Store
The Snap Store rejected revisions 8-10 over the super-privileged uinput
plug (allow-installation), and reviewers denied the grant request on
forum.snapcraft.io/t/53528, pointing at the RemoteDesktop portal path
the app already prefers. Remove the plug and the uinput-only ydotool so
revisions pass automatic review again; Wayland typing continues through
the RemoteDesktop portal (already the default backend ahead of ydotool),
wtype, IBus Wayland on KDE, and clipboard paste.
KDE Plasma loses its correct-case ydotool path on the store snap (KWin's
portal mangles keysyms on some layouts, KDE bug 489021); the v0.18.x
GitHub .snap still declares uinput for KDE users who sideload it.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(text-injection): stop telling snap users to connect uinput
The Store snap no longer declares the plug, so the warning pointed at a
command that cannot work. The Snap hints now explain that only the
v0.18.x GitHub sideload declares uinput, and that native Wayland typing
uses the RemoteDesktop portal or IBus Wayland on KDE.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Jatin K Malik <jk.malik@yahoo.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>