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>
Represent every distribution format in the README header shields:
AppImage, .deb, .rpm, Flatpak, Snap (edge), AUR, PyPI, and Nix flake,
grouped on their own row above the social badges. Also documents the
flake install path in the Install section.
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* chore(release): v0.18.2
* chore(release): add Flatpak release notes and refresh changelog date
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
The evdev uinput clone copied EV_SW capabilities verbatim. "ThinkPad Extra Buttons" (thinkpad_acpi) declares SW_RFKILL_ALL, and a fresh uinput device reports every switch as 0. The kernel rfkill-input handler reads a 0 killswitch as radios-off, so every clone creation soft-blocked all radios at startup and again after every resume.
Strip SW_RFKILL_ALL from the clone capability list, dropping EV_SW entirely when it was the only switch. Other switches stay declared so real switch events still forward; a forwarded SW_RFKILL_ALL write is dropped by the kernel for the undeclared code.
Fixes#946
* fix(installer): keep piped script on stdin and prompt via /dev/tty
`curl | bash` left bash reading the installer itself from stdin, and the
detection block's `exec < /dev/tty` (from #315) closed that pipe about 300
lines into a 3000-line script. curl died with error 23 writing to the
closed pipe, and bash read the rest of the installer from the terminal as
commands instead of finishing the script.
stdin now stays on the pipe and a reachable terminal is recorded in
TERMINAL_PROMPTS. Every interactive prompt goes through read_prompt, which
reads /dev/tty explicitly only for that read, so user input still works in
a terminal while the script body keeps flowing through the pipe. The
interactive-mode gate now checks TERMINAL_PROMPTS rather than `-t 0`,
which also lets `--interactive` work under a pipe.
Regression coverage: the remote-install gate's second run now pipes the
bootstrap in under a `script` pty (the literal `cat | bash` shape a
curl user takes), and new tests run install.sh in a pty with the script on
a pipe to assert the mode and engine prompts still answer on the terminal.
Fixes#933
* fix(installer): drain the piped bootstrap before the handoff exec
A curl|bash bootstrap leaves its unread tail on stdin; the tagged installer
reads its commands from a file, so nothing drained the pipe and a still-
streaming curl reported error 23 when the installer exited. Drain stdin to
EOF before exec (Greptile P2 on #935).
Gate: drip-feed the piped run so the producer is mid-write at the handoff
like real curl, and run it under pipefail so a producer killed early fails
the run. Tests: pass --tag so a slow release lookup cannot eat the window,
wait for the engine prompt before Ctrl+C, and quote the installer path.
* test(installer): propagate a killed producer's status in the piped gate
A cat dying mid-drip (SIGPIPE when the consumer abandons the pipe) was
swallowed by the loop and the final rm, so drip returned success and
pipefail could not see the download failure the arm exists to catch.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(nix): add flake package, NixOS and Home Manager modules
Add a flake.nix plus packaging/nix derivations so Nix and NixOS users can
run and install Vocalinux declaratively, including via Home Manager.
- package.nix: buildPythonApplication with GTK3, libayatana-appindicator,
IBus and libnotify typelibs; wraps the entry point with every host tool
the injectors probe for (xdotool, wtype, ydotool, wl-clipboard, xclip,
xsel, xprop, setxkbmap, ibus, gdbus, notify-send, paplay, aplay)
- pywhispercpp.nix: builds pywhispercpp from its vendored PyPI sdist
(absent from nixpkgs, same gap as Debian/Fedora), static whisper.cpp,
GGML_NATIVE=OFF for a portable baseline
- module.nix: NixOS module enabling hardware.uinput and installing the
package; input/uinput group membership stays a per-user choice
- home-manager.nix: programs.vocalinux.enable installs into the profile
- docs/INSTALL.md gains a Nix / NixOS section; packaging/nix/README.md
carries the usage snippets
Refs #931
* fix(nix): address review findings on imports check and docs
- pywhispercpp: check pywhispercpp.model and _pywhispercpp too, so the
compiled binding is proven loadable, not just the package
- README Home Manager example: declare the nixpkgs/home-manager inputs
and pass pkgs, which homeManagerConfiguration requires
- package.nix: use top-level xprop/setxkbmap attrs (xorg.* is deprecated)
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
The pinned pycairo builds from source everywhere, and the sdist needs
cairo.pc. Fedora, mageia and the debian/ubuntu maps list the headers
explicitly; suse relied on gobject-introspection-devel pulling
pkgconfig(cairo) transitively, which the current Tumbleweed image no
longer does, so the opensuse/tumbleweed install gate now fails at
"Dependency cairo not found" during pycairo metadata generation.
void, solus and alpine have the same latent gap; each gets the same
package spelled in its own convention.
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(evdev): monitor remapper virtual keyboards ungrabbed
On Wayland the device hub grabs every discovered keyboard and clones it
via uinput so the dictation shortcut can be suppressed. With a remapper
like keyd running, the hub grabbed its virtual keyboard too, and the
remapper grabbed the clone in turn (keyd's default [ids] * matches it):
forwarded keys went to the remapper instead of the compositor, so users
could not type at all, and each forwarded event looped back through the
chain.
Detect virtual keyboards by their sysfs device path (uinput-spawned
devices have no physical parent and live under /devices/virtual/input),
falling back to known remapper names when sysfs is unreadable, and
monitor them ungrabbed. Their events still reach the listener, so
shortcuts bound to them keep working; only suppression is lost, which a
grab could never provide anyway since the remapper re-emits on a device
we cannot hold. keyd reports BUS_USB for its virtual devices, so the
bus type cannot discriminate; the sysfs path can.
Fixes#930
* test(evdev): annotate new #930 test mocks
Satisfies the repo rule that every function signature carries type
hints, including test fixtures.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
Prepares the v0.18.1 patch release: version bump, changelog and docs sync, native-package and web metadata, and release notes crediting every reporter and PR author.
Two small UI fixes found during the v0.18.1 smoke test.
* Settings > Advanced > Initial Prompt never accepted typed input: the wrapping GtkListBoxRow grabs keyboard focus on button press and steals it back from the GtkTextView. Mark the row non-focusable.
* Transcript and log save dialogs could return a doubled .txt.txt name; new paths.collapse_repeated_extension helper normalizes it, with a replace-confirmation when the collapsed path already exists.
2026-10-07 22:30:51 -07:00
dependabot[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jatin K MalikDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* chore(deps): bump the npm_and_yarn group across 1 directory with 3 updates
Bumps the npm_and_yarn group with 3 updates in the /web directory: [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser), [sharp](https://github.com/lovell/sharp) and [source-map-js](https://github.com/7rulnik/source-map-js).
Removes `postcss-selector-parser`
Updates `sharp` from 0.35.4 to 0.35.5
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.4...v0.35.5)
Updates `source-map-js` from 1.2.1 to 1.2.2
- [Release notes](https://github.com/7rulnik/source-map-js/releases)
- [Changelog](https://github.com/7rulnik/source-map-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/7rulnik/source-map-js/compare/v1.2.1...v1.2.2)
---
updated-dependencies:
- dependency-name: postcss-selector-parser
dependency-version:
dependency-type: indirect
dependency-group: npm_and_yarn
- dependency-name: sharp
dependency-version: 0.35.5
dependency-type: indirect
dependency-group: npm_and_yarn
- dependency-name: source-map-js
dependency-version: 1.2.2
dependency-type: indirect
dependency-group: npm_and_yarn
...
Signed-off-by: dependabot[bot] <support@github.com>
* chore(deps): keep tailwindcss on v3
The group update pulled tailwindcss ^4.3.3, whose PostCSS plugin moved
to @tailwindcss/postcss and whose Config type rejects darkMode:
["class"]. Pin back to ^3.4.3 so the Build Website and Netlify checks
pass; the sharp 0.35.5 and source-map-js 1.2.2 bumps are kept. A
Tailwind v4 migration needs its own PR.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[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>
* fix(whispercpp): reuse an on-disk same-size weight at startup
The Settings picker already lets a downloaded same-size model stand in
when the language-derived sibling is absent, but the startup path never
applied it: a headless config with language=en-us and model_size=medium
demanded ggml-medium.en.bin while ggml-medium.bin sat in the models
directory and loads fine.
Move the stand-in into whispercpp_model_info.on_disk_stand_in so both
paths share it, and give resolve_whispercpp_variant a prefer_on_disk
flag that only language-derived results use: pinned variants and stored
specializations still load exactly what they name, the .en preference
still wins when that file is present, and checksum verification is
untouched. get_model_size_for_engine opts in so startup and headless
launches match the Settings semantics.
Fixes#916
* fix(whispercpp): keep is_model_downloaded free of makedirs
get_model_path creates the model directory for callers that are about to
write a file, but is_model_downloaded is a read-only probe: on a fresh
profile its makedirs ran inside tests that stub os.makedirs for the
config directory, breaking them, and a bare existence check should not
mutate the filesystem anyway. Resolve the file path without creating
directories in the probe; get_model_path keeps creating for writers.
Refs #916
* test(whispercpp): annotate the stand-in fixture's return type
AGENTS.md requires type hints on every signature; _downloaded returns the
context manager from patch.object.
Refs #916
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(text-injection): end option parsing before typed text
A 20-char xdotool chunk, or a 200-char wtype/ydotool chunk, that starts
with '-' (a word hyphenated across the chunk boundary) is parsed as a
flag: 'xdotool type --clearmodifiers -stickation' exits 1 with
'type: unrecognized option' and the rest of the dictation is dropped.
Pass '--' before the text argument in the xdotool, wtype, and ydotool
type invocations so the remaining argument is always typed literally.
Fixes#921
* test(portal-fallback): expect the wtype option terminator
The partial-type fallback assertion predates the -- terminator and still
expected the bare ['wtype', 'llo'] argv.
Refs #921
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(dictation-pad): keep pad window out of the compositor ghost state
On GNOME Wayland an idle, fully covered toplevel gets shelved by the
compositor: it stays in the window list but renders nothing and takes
no input. Keep the pad floating above other windows and pinned to all
workspaces so it cannot be shelved, track iconify/obscure state events,
deiconify on show, and repaint when visibility returns.
Fixes#896
* test(dictation-pad): scope Window return_value patch in shelving test
* fix(dictation-pad): detect Wayland shelving and recreate dead surface
set_keep_above is a stub in GTK3's Wayland backend, so a covered idle pad
still gets shelved by the compositor on GNOME/Wayland: it stays in the
window list but draws nothing and takes no input, and present() cannot
revive it.
Add a frame-callback watchdog: each tick callback stamps the clock and a
GLib timeout flags a mapped window whose frames went cold as shelved. A
shelved pad is then recreated (hide + show_all) in show_pad, the same
path the user's manual Close + tray reopen takes.
Keep keep_above/stick for X11 where they are honored, plus the iconify
and visibility-event handling from the previous commit.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(evdev): require a real keyboard key in /proc keyboard discovery
Any nonzero B: KEY bit qualified a device as a keyboard, so power/sleep
buttons, the ACPI video bus, vendor hotkey blocks and pointer-button
interfaces were all grabbed and given (vocalinux) uinput clones, leaving
duplicate logical devices that confuse compositor input settings.
Require a modifier or KEY_A in the KEY bitmap — the same rule the evdev
fallback path already applies — and update the test fixtures to a real
keyboard bitmap so they exercise the intended gate.
Fixes#915
* fix(evdev): read /proc bitmaps at the kernel's word width
Capability bitmap words are unsigned longs — 32 bits on 32-bit kernels
(armv7l, i686), not 64. Parse the whole bitmap through _bitmap_to_int()
sized by platform.machine() so high key codes like KEY_RIGHTMETA land
correctly on both widths. Give pointer-test mocks real keyboard keys so
the pointer check — not the keyboard-key gate — drops them.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.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>
* fix(evdev): skip multitouch-only touchpads in keyboard discovery
Devices reporting only ABS_MT_POSITION_X/Y (no single-touch ABS_X/Y)
evaded the pointer-axis check, so multitouch touchpads like the Goodix
GXTP5100 were grabbed and cloned — breaking pointer input while running.
Fixes#914
* fix(evdev): read /proc bitmaps at the kernel's word width
Capability bitmap words are unsigned longs — 32 bits on 32-bit kernels
(armv7l, i686), not 64. Parse the whole bitmap through _bitmap_to_int()
sized by platform.machine() so MT axis bits and any future high bits
land correctly on both widths. Give the fallback MT-mock a real key so
the MT-axes check is the only thing that can drop it.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.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>
* fix(text-injection): detect KWin 6 VirtualKeyboard property
On KDE Plasma 6 the org.kde.kwin.VirtualKeyboard 'enabled' property no
longer exists; the setting is reported as 'available'. Query 'available'
first, keep 'enabled' as the Plasma 5 fallback, and accept both the real
(<true>,) gdbus output and the doubly wrapped (<<true>>,) form.
With the bridge check working, KDE sessions with Virtual Keyboard set to
"IBus Wayland" can safely use scoped IBus injection again: the
leftover-daemon trap of #752 is exactly what a confirmed VK bridge
disambiguates. KDE without a confirmed VK stays excluded.
KWin's RemoteDesktop portal scrambles letter case, so in auto mode on
KDE sessions an installed ydotool is preferred over the portal -- the
ordering used before the portal backend existed.
Fixes#911
* fix(text-injection): keep KDE portal fallback ahead of daemonless ydotool
On KDE, when ydotool is installed but cannot run (no daemon, no
/dev/uinput access) and wtype is absent, the daemonless-ydotool branch
fired before the portal fallback -- selecting a backend that fails at
injection time while a working portal sat unused. The portal fallback
now precedes the daemonless branch; working ydotool still outranks the
portal on KDE.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-10-07 17:54:56 -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(packaging): declare python3-pysocks in deb and rpm deps
#909 added PySocks as a runtime dependency for SOCKS5 proxy support but
only covered the AUR PKGBUILD — the nfpm deb and rpm packages never
declared python3-pysocks, so
test_every_project_dependency_is_covered_in_both_formats fails on main
and on every PR's merge check. Add it to both depends lists and the
test's DEB/RPM_PROVIDES maps.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(packaging): deb name is python3-socks
Debian/Ubuntu package PySocks as python3-socks (1.7.1+dfsg); the rpm
name stays python3-pysocks — Fedora's python3-socks is a different
library (python-socks). Verified in debian:12, ubuntu:24.04 and
fedora:42 containers.
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>
* fix(ci): attach snap with --repo, keep checksums on attach failure
The Attach Snap job had no checkout and no --repo flag, so
gh release upload failed with 'not a git repository' on v0.18.0.
Every other release upload passes --repo; do the same here.
Publish Snap to Store matched 'will need manual review', but current
snapcraft prints 'human review required', so the expected
allow-installation review hold reported as a hard failure.
A failed attach-snap also skipped Checksums and Provenance even though
it hashes build artifacts, not release state. Run it unless cancelled
and guard that all expected artifacts exist before manifesting.
* fix(ci): keep literal review phrase in snap publish grep
test_release_attaches_snap_then_tries_the_store asserts the literal
'will need manual review' stays in the publish-snap block. Keep it as
the first alternation so the existing test documents the matcher.
* fix(ci): require both native package sets in checksum guard
Merged main added build-native and build-native-arm64 (.deb/.rpm per
arch) to publish-checksums' needs and the SHA256SUMS glob. The artifact
guard now requires two .deb and two .rpm files so a failed native build
cannot publish an incomplete manifest.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(settings): stage Advanced model spec until Download is confirmed
Picker changes in Speech Model -> Advanced staged a spec but immediately triggered a model download for every intermediate selection. Gate the auto-apply download path so a missing model only stages: a Download action on the model info card now runs apply_settings() as the single explicit confirmation. Simple-mode questions keep the instant apply-and-download flow.
Fixes#894
* fix(settings): repaint model info card after a completed download
The post-download paths repopulated pickers and resynced the engine but never repainted the info card, so a finished download left the amber status and the Download action visible for a model already on disk. The cancel path already repainted via the idle resync; give success its own.
* fix(settings): keep staged spec through picker rebuilds and save non-model edits
* fix(settings): persist non-model edits without a model-reloading reconfigure
* fix(settings): live-apply non-model edits, keep whispercpp params saved-only while staged
* fix(settings): live-set changed VAD/timeout attrs without reconfigure while staged
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(settings): add proxy support for model downloads and update checks
Add a Proxy page to Settings (above About) with Off / System / Manual
modes. Manual mode supports SOCKS5 and HTTPS (HTTP CONNECT) proxies
with optional username/password auth; the password is stored in
config.json in plaintext like the other settings. System mode honors
the *_proxy environment variables and falls back to the GNOME
org.gnome.system.proxy manual configuration.
The resolved proxy is applied to every in-app outbound path that
fetches models or checks updates: the shared requests download path in
recognition_manager (_stream_model_download, used by the whisper.cpp,
whisper, Vosk, Parakeet and faster-whisper downloaders) and the
release checks in update_checker. A small Test Connection button on
the Proxy page verifies reachability through the configured proxy.
SOCKS5 needs PySocks, added as a runtime dependency; uv.lock, the
requirements/*.txt exports and the Flatpak dependency manifest are
regenerated by just lock (with a fix in sync_flatpak_deps.py for
packages like PySocks that publish more than one universal wheel).
Fixes#655
* fix(proxy): bracket IPv6 hosts, escape test errors, declare pysocks for AUR
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(history): add opt-in JSONL persistence for transcripts
Adds history.persist (default off) mirroring the bounded snippet list to
history.jsonl under the XDG data directory so transcripts survive
restarts. Entries carry timestamp, duration, language and model when
known. Clearing history truncates the file, turning persistence off
deletes it, and disabling history removes it so nothing is retained
anywhere. Settings gains a Keep on Disk switch and a Clear button in the
Transcription History group.
Fixes#758
* test(history): rebind real tempfile when siblings mock sys.modules
test_recognition_manager and test_speech_recognition replace
sys.modules["tempfile"] with a MagicMock at import time, so the new
persistence tests were sharing one mock-named store path in full-suite
runs. Guard the TemporaryDirectory binding the same way
test_whisper_model_info already does.
* fix(history): sweep stale temp files, survive damaged stores, and keep session fields on late commits
* fix(history): reject lone-surrogate records and survive encode errors on write
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* ci(install): gate the remote install path end to end
A curl|bash user takes a different path than the local install gate
covers: bootstrap outside a checkout, tag selection, clone/fetch of the
selected tag, then handoff to the tagged installer with the original
arguments. Add scripts/install-remote-test.sh, which publishes HEAD as a
tag in a local mirror, runs install.sh against it twice (fresh clone,
then fetch+reset of the existing clone), and fails closed when the
clone's HEAD is not the selected tag commit. install.sh gains a
VOCALINUX_REPO_URL override so the mirror can stand in for the public
remote. Wire a single-distro job into the distro matrix and a just
remote-install-gate recipe.
Refs #701
* ci(install): publish the gate tag as an orphan from HEAD's tree
CI checks out with fetch-depth 1, and git refuses to push or fetch a
shallow boundary commit into a ref. Build the tag on an orphan commit
created from HEAD's tree instead, and make tree equality the fail-closed
check that installer, modules and exports are this commit's files.
* test(install): force the re-run arm to prove fetch and reset
Review feedback on the remote-install gate: the second run reused a clone
already on the tag, so a broken fetch or reset would still pass. Move the
clone onto a descendant commit and delete its local tag ref before the
re-run, so only a real fetch + reset restores them. Also list the new
recipe in AGENTS.md's command guide.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(tray): remove Start on Login and About from the tray menu
Both entries already live in Settings: the autostart switch is in the
general page and About is a dedicated sidebar page, so the tray items
are redundant. View Logs stays.
Removes the Start on Login checkbox item along with its
_update_autostart_checkbox/_on_autostart_toggled wiring and the now
unused _add_menu_checkbox helper, plus the About item and
_on_about_clicked. _show_settings_page("about") stays for the Update
Available item. autostart_manager is untouched since Settings uses it.
Fixes#654
* fix(settings): sync autostart switch with the real desktop entry
The tray's removed _update_autostart_checkbox also reconciled
general.autostart with the actual login desktop entry at startup. Move
that check into the settings load so the Start on Login switch still
reflects reality when the entry changes outside the app.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
Reveal the transcription readout at the top of the sidebar footer so it
grows into the empty sidebar space above the Idle row instead of pushing
the Test Dictation button down when it appears.
Fixes#677
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(packaging): build thin .deb and .rpm with nfpm and attach them to releases
Adds packaging/native: an nfpm config that produces a .deb and an .rpm
from the release wheel. The packages stay thin - Python, GTK, PyGObject
and the typelibs are distro Depends; only pywhispercpp and pynput are
vendored because the first is unpackaged on both distros and the second
is missing on Fedora. The Debian 12 build image and the nfpm download
are pinned in tool_checksums.txt.
just native-packages builds both in the pinned container, and
native-smoke installs the built package per distro: debian:12 and
ubuntu:24.04 exercise the .deb, fedora:42 the .rpm, then each runs
vocalinux --version plus an import smoke of the app and the vendored
extension. unified-pipeline.yml gates on the same three-distro matrix,
and release.yml attaches both arches to the release and covers them in
SHA256SUMS and the provenance manifest.
Refs #600
* fix(packaging): preserve launcher args and document Wayland input group
The launcher joined $* into an unquoted exec, re-splitting file paths
that contain spaces before argparse saw them. Exec "$@" directly and
re-quote each argument for the inner shell of 'sg input -c'.
On Wayland the packages could not grant the 'input' group membership
evdev hotkeys need; name the usermod step and its caveat in INSTALL.md
and the release notes.
* test(packaging): add return types to the native packaging tests
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(appimage): reuse the Vulkan pywhispercpp build across CI runs
The x86_64 Build AppImage step spends most of its time recompiling
pywhispercpp with Vulkan. That compile depends on the base image,
tool_checksums.txt, and the pywhispercpp pin, not on the commit.
Cache the native tree under one key shared by CI, Nightly, and Release.
* fix(appimage): reject a partial Vulkan cache and key the build tools
A failed copy could leave a tree that still contained libggml-vulkan.so,
and the next run would reuse it. Check each copy, and keep the previous
cache until the new one is in place.
The cache id now also covers the pinned cmake and ninja export and the
Vulkan compile flags. The hit path is what the build calls, and a test
runs it with a stub compile command.
* chore(ci): rerun AppImage against the saved Vulkan cache
The review fix changed the native cache id, so run 37586979294
compiled pywhispercpp again and saved
appimage-native-X64-ebf478b87986e2f6b9d80c97c2f5869fe3120c2bcd48f5d8f5a65fe06ec03015.
This commit leaves the base image, tool checksums, pywhispercpp pin,
installer tool pins, and Vulkan compile flags alone.
* fix(evdev): skip pointer-motion devices in keyboard discovery
find_keyboard_devices() treated any device with a nonzero EV_KEY bitmap
as a keyboard, which includes Logitech mice (G502, MX Master 3) whose
button interfaces report key bits. The hub then grabbed them (EVIOCGRAB)
and re-emitted every event, including EV_REL pointer motion, through a
paired uinput clone — piping all cursor movement through the reader
thread and showing up as periodic cursor stutter.
Discovery now excludes devices that report pointer motion (REL_X+REL_Y
or ABS_X+ABS_Y) in both the /proc/bus/input/devices parser and the
evdev.list_devices() fallback. Wheel-only devices (REL_WHEEL) and real
keyboards are unaffected, and a mouse's separate keyboard interface
node keeps working for shortcuts.
Fixes#900
* test(evdev): annotate new device-discovery tests
* chore: drop accidentally committed pr_body.md
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>