37cf549956eca98c5883a650ccee2e6873f6322f
40
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
180cd4d512 |
fix(spec16): close the review findings for the Void port (#34)
Model recommendation keyed to the installed build and free memory, fork-sourced Void templates, signed xbps release workflow on Gitea with the pinned repository key, niri injection guard and wtype-first order, tightened naming sweep, web copy fixes, and CI security and duplication fixes. |
||
|
|
e100af7bcd |
fix(injection): align fallback ladder to portal>wtype>ydotool>xdotool>manual
Ticket #31 review found startup and demote paths preferring ydotool over wtype, with a KDE ydotool-first exception, contradicting the decided order in spec #16 (portal first where a backend implements it, then direct keysym typing, then the virtual-input tool, then XWayland-only injection, then manual paste). - Auto-selection is now portal, wtype, ready ydotool, daemonless ydotool, xdotool on every desktop including KDE; the __init__ wtype probe validates wtype and demotes on compositor rejection. - Portal-deny demotion, IBus-fallback switch, XWayland recovery, and a new wtype runtime-failure retry all follow the same rung order with partial-type accounting preserved. - Ladder suite grows to 14 tests pinning KDE portal selection, wtype-before-ydotool selection, and each demotion trigger. - USER_GUIDE states the full rung order and portal-first on KDE. |
||
|
|
f660471757 | Merge feature/31 into integration/spec16 (ticket #31: injection ladder tests and first-dictation docs) | ||
|
|
e6f7938068 |
test(injection): pin fallback ladder per desktop class and niri first-run
Locks the ordered rungs without real compositors: portal first where a backend implements it (GNOME), direct typing first on niri with IBus excluded and no portal selected, deny demoting to direct typing with the full dictation kept, terminal probe choosing Ctrl+Shift+V, held modifiers delaying the XWayland paste, and clipboard manual paste as last resort. USER_GUIDE states the niri first run (no prompt, typing goes direct, never pin portal there) and the GNOME/KDE grant, persistence and deny fallback under the renamed backend setting and override names. Refs #31 |
||
|
|
c27d204a00 |
feat(niri): add niri keybind pack and Dank Material Shell notes
Docs-only slice of ticket #22: include file, integration page, index links. Tray-watcher observation stays open for hardware run (#32); no probe code changed. Refs #22 |
||
|
|
ea7be5f111 |
feat(identity): rename user-visible identity to voculinux with one-time legacy import
The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18 |
||
|
|
e66bd218d9 |
fix(audio): prevent dictation hangs on stalled microphones (#974)
* 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> |
||
|
|
aae1f4b474 |
fix(clipboard): keep paste-injection copies out of clipboard history 🤖🤖🤖 (#956)
* 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. |
||
|
|
2fce81e94d |
fix(shortcuts): preserve Ctrl events for mouse-wheel zoom (#967)
* fix(shortcuts): allow shortcut key passthrough Fixes #950 * fix(shortcuts): prevent Ctrl release race during passthrough * test(shortcuts): add type hints to passthrough regression tests * test(shortcuts): cover passthrough edge cases and integration --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
d59395b5c9 |
fix(hotkeys): use the X11 listener for Flatpak on native X11 sessions (#997)
* 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 |
||
|
|
65de3427fb |
perf: faster Settings, typing and dictation; mypy in CI, engine interface, architecture docs (#968)
* 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 |
||
|
|
64032fb999 |
fix(snap): drop uinput plug denied by the Snap Store (#960)
* 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> |
||
|
|
d11bd1bf0d |
feat(post-processing): pipe transcriptions through an optional postprocessing script (#479)
* feat(post-processing): pipe transcriptions through user-defined script Add a configurable post-processing step that passes each transcription result to an external executable via stdin and uses its stdout as the text to type. Enables custom transforms (formatting, LLM rewriting, etc.) without modifying Vocalinux. Falls back to original text on script failure or timeout. * test(post-processor): add unit tests and extract apply_post_processing Move config-reading logic from main.py closure into apply_post_processing() in post_processor.py so it can be tested independently. Add 11 unit tests covering PostProcessor.process() and apply_post_processing(). * docs(post-processing): document post-processing script feature * fix formatting of one file * fix(post-processing): run the user script with a stripped host environment Rebase compat: the host_process AST guard requires every subprocess spawned for a host binary to pass env=host_env() so an AppImage's LD_LIBRARY_PATH/GI_TYPELIB_PATH/PYTHONHOME do not leak in. Also give apply_post_processing() a typed ConfigManager parameter and keep text_callback_wrapper's local variable typed as str for mypy. * fix(post-processing): preserve paragraph breaks and tighten script handling Take script stdout verbatim instead of rstripping every trailing newline, so the "new paragraph" voice command's break survives a passthrough script. A single trailing newline is still dropped, but only when the transcription had none — it is almost always just the line terminator echo/print-style tools append. Also stop writing transformed output to the log (it can carry sensitive dictation into retained logs), catch only the expected process/OS/encoding failures instead of every Exception, and add the missing signature annotations. * fix(main): run the post-processing script off the recognition thread A user script may take seconds per segment, and text callbacks run on the recognition thread that drains the bounded audio-segment queue; stalling it drops queued dictation once the queue fills. Submit post-processing and injection to a dedicated single-worker executor so the backlog waits instead of losing speech, while keeping injection order. On timeout or failure the worker falls back to the original text, and an unexpected error is logged with a traceback and still injects the original rather than silently dropping the segment. Also pin the callback tests' get_str mock to no-script so they exercise the synchronous path deterministically, and add a callback test proving transformed text (including preserved paragraph breaks) reaches the injector. * docs(post-processing): annotate new callbacks and clarify stdout contract Add the missing type annotations on the new Settings page callbacks per the repository guide, and document that script stdout is used verbatim (a lone trailing newline added by line-oriented tools is dropped only when the transcription had none) and that scripts run on a dedicated worker. * test(main): annotate new post-processing callback tests * fix(main): bind queued dictation to its target app and drain worker on quit Route every transcription segment through the single post-processing worker (a pass-through when no script is configured) so clearing the script path mid-queue can no longer let a later segment overtake an earlier one still running. A segment that can sit in the backlog — a script is configured or a job is still running — is bound to the application focused when it was dictated; the worker drops it if focus has verifiably moved to another application, so delayed dictation cannot land in the wrong app. Wire the worker into the tray quit path via an on_quit hook: it clears the acceptance flag, cancels pending jobs, and waits — bounded by the script's own timeout — so nothing injects after the injector stops. * fix(main): keep voice actions ordered and probe focus off the recognition thread - Queue voice-command actions on the post-processing worker so an action can no longer overtake dictated text still waiting to be injected. - Move the submit-time focus probe onto a dedicated executor; the recognition thread only submits the probe, and the job joins the result at inject time, so compositor tools can no longer stall consumption of the bounded audio-segment queue. - Re-check the quit flag after the inject-time focus re-probe, then shut both workers down without waiting on a running script so quitting does not freeze on the script timeout. * fix(main): capture submit-time focus on dedicated threads and serialize quit - Replace the shared focus-probe executor with a fresh daemon thread per submission so a slow earlier probe cannot delay a later one past the focus change it is meant to capture; jobs read the result off a one-slot queue at inject time. - Give queued voice actions the same submit-time focus binding as text segments so a queued shortcut is dropped instead of fired into a newly focused application. - Hold injection_lock across the accept-check plus inject/action call so quit waits only for an in-flight injection instead of racing it. - Queue the IDLE reset of last_injected_text behind pending worker jobs so a queued 'delete that' still sees the text it targets. * fix(main): keep actions instant on a free worker and bound quit wait - An action now starts a submit-time focus probe only when it actually queues behind other jobs; with a free worker there is no delay window to bind against, so the probe's compositor calls are skipped and the action stays immediate even when a script is configured. - Probe and executor submission now happen inside pending_jobs_lock so queue order matches callback order — a job that saw an empty queue can no longer slip behind one that arrived while it was submitting. - Every inject-path subprocess call (xdotool window/key/type, wtype and ydotool type) is bounded by a timeout, and the quit hook's injection_lock wait is capped, so quitting can no longer freeze on a stalled injector subprocess. * fix(main): bind every action to submit-time focus without slowing it down - Every voice action now starts a submit-time focus probe: submission never guarantees immediate execution, so skipping the binding left a focus-change window in which a shortcut could fire into another app. The worker waits on the probe for at most one second — milliseconds on a healthy desktop — so immediate actions stay immediate while a genuinely stalled probe falls back to the injector's own targeting. - Quit waits for an in-flight injection without a cap again: every inject-path subprocess is individually bounded, so the wait always ends with the injection and a long transcription is never cut off. - The wtype/ydotool typing timeout is now derived from the configured per-key delay (a generous multiple), so a legitimately slow type can never be cut mid-text; a timed-out type no longer falls through to the clipboard fallback, which would duplicate the partial fragment. * fix(main): drop unverified actions and make quit-time injection abortable - An action whose submit-time focus probe cannot answer inside its one-second bound is now dropped instead of running unverified: a stalled probe can no longer leave a shortcut pointing at whatever application happens to be focused. - Quit asks the injector to abort in-flight injections, and injection paths honour it: the xdotool chunk loop stops between chunks, the wtype/ydotool type call runs as a polling Popen that can be killed, and paste/shortcut/backspace entry points exit early. The quit hook still waits for an in-flight injection, but the wait is bounded by at most one already-running subprocess call — never the injection's full length — and no subprocess is ever stopped mid-call by a timeout cap on the wait itself. - Annotate the remaining untyped test helpers. * fix(main): wait out slow focus probes; restore run() in wayland typing - An action no longer races its submit-time focus probe on a one-second bound: the job waits for the probe's own answer, whose compositor calls already carry per-call timeouts and short-circuit absent tools. A slow but living probe delays the command instead of discarding it, so a valid "undo" can no longer be lost to timing alone, and focus verification still decides every drop. - Wayland typing goes back to subprocess.run so tests keep intercepting the same entry point. Abortability is preserved by typing in 200-character chunks with the shutdown check between calls — a whole-string call could not be interrupted, but no single chunk holds the injector for more than its own scaled budget. - The abort flag gains a class-level default so injectors built without __init__ in tests still answer the checks; each real instance gets its own event in __init__. - Update call-shape assertions for the typed-call timeout and finish annotating the test methods touched by the callback refactor. * fix(inject): never retype text a failed chunked call already sent - _PartiallyTyped carries the confirmed-delivered prefix length out of the chunked type loops, so every downstream fallback continues from the untyped remainder: the Wayland-to-xdotool switch types only the tail, and the clipboard fallback copies only the tail, never the whole string a second time. - The xdotool retry loop gains the same accounting — a retry now resumes at the first untyped chunk instead of restarting at the top and duplicating everything the failed attempt already delivered. - Annotate the remaining test signature the repository guide requires. * fix(inject): report partial typing as failure even when clipboard lands A chunked type call that dies mid-text leaves only a prefix on screen, and the remainder-fallback keeps it that way: it copies the tail for a manual paste but now returns False, so the caller no longer records the full transcription as injected and a following "delete that" cannot erase text that was never typed. * fix(inject): keep deletion state aligned with delivered text inject_text now records how many characters a call confirmed on screen as last_typed_count: the whole length on success, the typed prefix after a partial failure, -1 when the delivered amount is unknowable. The transcription caller uses it to keep last_injected_text honest — a following "delete that" erases the typed prefix or nothing, never the previous segment or text that never arrived. * fix(inject): never replay input a timed-out portal job may have delivered - RemoteDesktopPortalError.delivered=None marks an unreported job's count as unknowable; all three portal fallbacks demote and skip instead of replaying, and text injection surfaces as TimeoutExpired so no full-text clipboard copy is offered - _PartiallyTyped counts raised after a portal demote are rebased onto the original text (typed_prefix) so the caller clips the real prefix - evdev: a consumed pure-modifier press is withheld and replayed ahead of the first non-target press, so holding RightAlt for AltGr composition still reaches the application - history: extend_latest gains an expected_latest CAS guard so a late segment from an older worker merges only into its own session's snippet, never into a newer session's entry - quit path aborts in-flight injections before waiting on the lock so quitting cannot freeze behind a long transcription's typing budget - tests: stop _ensure_ydotoold leaking a real Popen in Wayland tests * fix(shortcut): replay withheld modifiers across devices and after SYN_DROPPED - Replay withheld modifier presses from every device, not only the fd the character arrived on: split keyboards deliver AltGr and the character on separate devices, and a per-fd check forwarded a plain character. - Let the post-SYN_DROPPED resync run when only withheld modifiers exist for the fd, so a press whose release the dropped burst ate is pruned instead of replaying a stale modifier on the next ordinary key. * fix(history,pads): keep late segments pad-bound deliveries and pad deletions correct - Track the worker behind the ended session's snippet so late segments from an older worker cannot merge into a newer session's entry - Skip the focus-change drop for dictation-pad segments and pad-bound editing commands, which never target the focused application - Refuse 'delete that' when a manual pad edit blurred the segment boundaries instead of deleting the stale recorded length * style: drop unneeded nonlocal for mutated worker set * fix(shortcut): share one evdev device layer across shortcut listeners Each KeyboardShortcutManager built its own EvdevKeyboardBackend, and on evdev every backend opened and grabbed the same physical keyboards: the first grabber received all events and the rest received none, so per-language shortcuts could never fire on Wayland. All backends now bind to a process-wide EvdevDeviceHub that opens each keyboard once, owns the grabs and the paired uinput clones, and fans every event out to each registered engine. An event is re-emitted to applications only when no engine consumed it, so suppression semantics stay the same while per-engine matching state (combo progress, withheld modifiers, swallowed presses) remains independent. * fix(history): own late segments per worker and decide pad routing once - ended_worker_snippets maps each ended session's worker to its own (snippet, commit epoch): late deliveries merge into their session's entry however many sessions have committed since, so one session's stragglers never split across Recent Snippets entries. - The leftover-segment commit reads the live history epoch inside the session lock: a mid-session clear() no longer makes the stale ended epoch refuse valid post-clear dictation. - The pad-bound routing decision is captured once per job and passed to the injector/action handler, so a toggle flip between the focus check and delivery cannot reroute text or shortcuts into the focused app. - Dead recognition workers are pruned from the tracking set and map so long runs don't accumulate finished threads. - The custom-terms switch now suppresses GTK's own state-set on save failure so it shows the value that actually persisted. * fix(shortcut): serialize evdev device layer teardown against restart The last listener's unregister released the engine lock before joining the monitor thread and closing devices, so a new listener registering in that gap opened keyboards the stale teardown then closed — it stayed active but dead, and briefly grabbed keyboards stopped forwarding input. A lifecycle lock now covers the whole close/open transition in register, unregister, and the monitor's unexpected-exit teardown, so a cold start can only open devices after the previous generation finished closing (also making the new generation's grabs succeed). A generation stamp tells a monitor thread that outlived its join() its incarnation is over even when running has gone True again. * fix(injection): log focus-probe failures and narrow clipboard fallback catch A crashed focus probe turned into an unknown result with no logged error, indistinguishable from unavailable focus information; the partial-typing clipboard fallback caught every exception. Log the probe failure with context and narrow the clipboard catch to the exceptions the fallback path can actually raise. * fix(shortcut): join the evdev monitor outside the lifecycle lock If the monitor exited unexpectedly while the last listener unregistered, its cleanup queued on the lifecycle lock that unregister() held for the join — stalling shutdown for the join's full 2s timeout. The close now finishes inside the lock and the join waits after it is released, so the monitor's own cleanup can run and exit instead of queueing behind it. Closing before the join also dies the monitor's read/select on the closed fds right away. A _closed gate on _open_keyboard_device keeps a rescan from opening a device between detach and close that nothing would ever close. * fix(injection): narrow focus-probe catch to the raisable exception set get_focused_window() reports Optional and never raises; the catch now covers only what can realistically escape the queue write and logs it with context, keeping the waiting job released. * fix(shortcut): tag evdev device fds with the generation that opened them A monitor that outlived join() could still touch a newer generation's devices: the loop re-checked the generation only at the top, so mid-iteration it could dispatch from, forward to, or remove devices a restarted listener had just opened. Every fd is now tagged with the generation that opened it, and the tag is verified at each point a monitor could mutate shared state — fd lookup for dispatch, clone forwarding, forwarder release, device removal, and rescan opens — while dispatch re-checks the generation between events and the per-fd loop breaks on it. Cold starts bump the generation before opening so fresh devices are tagged for the new generation. Devices an older generation opened stay tagged stale, so its reader leaves them alone. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
dca937fc21 |
feat(dictionary): combined custom dictionary with corrections (#890)
* feat(dictionary): combined custom dictionary with corrections Rebased onto latest main (conflict-resolution squash for PR #818). * fix(dictionary): stabilize the terms and corrections file contracts - corrections_path() stays at the fixed config_dir() location promised by the file contract instead of following a custom or --dictionary-file terms path, which could strand or duplicate corrections; a leftover file beside the terms file is still read once and copied over. - _atomic_write() preserves the replaced file's permission mode instead of resetting it to the process umask. - build_initial_prompt() streams terms and stops at the configured cap instead of reading and processing the whole file for every segment. * fix(settings): restore the selected dictionary pane after a search Searching can auto-switch the Custom Dictionary management pane to reveal a match, but clearing the query only restored widget visibility. Record the pane selection in the search baseline snapshot and restore it alongside the previous page so users return to the pane they chose. * fix(dictionary): close the remaining custom dictionary file-contract gaps - A --dictionary-file session override no longer imports a corrections file found beside the override into the standard config directory; the override scopes to terms only. - Terms reads validate the whole file before yielding: corrupt UTF-8 mid-file now yields no terms at all, matching the invalid status Settings reports instead of leaking a partial dictionary to recognition. - The terms read is bounded by MAX_TERMS_FILE_BYTES (1 MiB); a larger file is invalid everywhere with a dedicated status instead of being read on every segment. * fix(dictionary): keep usable terms prefix on oversized files and legacy corrections under overrides * fix(dictionary): cap yielded terms and keep added terms visible on oversized files * fix(dictionary): keep added terms visible without dropping scanner-owned lines * fix(dictionary): keep oversized terms manageable and honor mid-search pane switches - terms list gains a Show all / Show first 500 toggle; adding a term beyond the cap auto-expands so the new row renders with a Remove button instead of being invisible until the file is hand-edited - a manual dictionary pane switch during an active search drops the saved pre-search pane so clearing the query no longer rolls back the user's newer selection - annotate the new settings tests and type model_info so the parakeet branch stops adding fresh mypy errors * fix(settings): do not auto-expand terms on add past display cap Rebuilding up to thousands of term rows on the UI thread stalls the dialog; the overflow button already shows the true count, so the added term stays one click away. * test(dictionary): stub segment_callbacks on bare manager for history dispatch * fix(dictionary): stream oversized terms files when building the prompt build_initial_prompt decoded the whole 1 MiB read window before every transcription segment even though it only needs the first max_words terms. _iter_terms_for_prompt now reads oversized files line by line and stops at the prompt limit, keeping the same contract: complete leading lines only, invalid UTF-8 inside the usable window yields nothing. * fix(history): only the ended session's worker may extend its snippet A late segment could extend the newest history entry regardless of which session produced it, so a worker finishing after the next dictation session leaked its text into that session's entry. Track the worker that owned the ended session and require late segments to arrive on the same thread before merging; anything else forms its own snippet. * fix(speech): transcribe queued segments the reload worker never drained The recognition worker can exit during stop_recognition's bounded audio-thread join, so the released key's final buffer lands in the queue after the worker is gone and was silently dropped when the reload teardown replaced the queue. Drain whatever remains before swapping it — except on capture failure or an explicit cancel, which still discard the session's leftovers. * fix(settings): build term rows in idle slices and pin hidden additions Two problems shared one cause. Rebuilding up to 4096 rows synchronously stalled the dialog, and a term appended past the 500-row cap reported success while staying invisible. Rows now build in bounded GLib idle slices guarded by a refresh token, and a saved term outside the visible window is pinned to the top of the capped list until it disappears or becomes visible on its own. Adding under "show all" scrolls to the new term instead. * test(engine): annotate new test method return types * fix(ci): match a candidate row that leads the latest track block snapcraft status normally prints candidate as a continuation line under the latest track (channel in column 1). If candidate ever leads the track block it rides the track line instead (channel in column 3), and the resolver missed it and refused to promote. Match both layouts in the resolve and the pre-release re-verify. * fix(dictionary): bound prompt reads and scroll to landed terms - _iter_terms_for_prompt now reads each line via readline() capped by the remaining byte window, so an overlong line can never pull more than MAX_TERMS_FILE_BYTES into memory - the prompt path keeps decoding every complete line inside the usable window after the term limit is collected, so invalid UTF-8 anywhere in the window still yields no terms and matches the reader's invalid status - under "show all" Settings scrolls to the index where an added term landed (prepended or appended), verified against the row's label, instead of always scrolling to the end * fix(history,reload): keep late-segment ownership and drain stamped tuples - Track session_workers_seen so a worker from an earlier session can never merge into a newer snippet while a session is open - Unpack (segment, language) queue tuples in _transcribe_queued_segments; passing the tuple as audio raised TypeError and lost final speech - Refuse 'delete that' when a manual pad edit blurred segment boundaries - Translate term-row coordinates into the scrolled card's space so the scroll actually reaches rows below the fold - Type _on_dictionary_pane_changed's varargs * style: drop unneeded nonlocal for mutated worker set --------- Co-authored-by: Graham <graham@thegeeky.ninja> |
||
|
|
8ebc4ce2ba |
feat(diarization): per-speaker transcripts via TinyDiarize + --transcribe-file (#884)
* feat(transcribe): add TinyDiarize file transcription surface Catalog small.en-tdrz (hosted at akashmjn/tinydiarize-whisper.cpp, pinned commit) while keeping it out of every dictation-model selection path: its [SPEAKER_TURN] markup would type noise into the focused window. The checksum generator resolves side-repo models at their own pinned revision, parakeet-style. Add speech_recognition.diarization (strict-WAV fast path with ffmpeg fallback, tdrz transcription, speaker-turn parsing, timestamped formatting) and surface it through a tray Transcribe Audio File flow with on-demand model download plus a headless --transcribe-file CLI. TranscriptDialog offers copy and save of the diarized transcript. Closes #756 * docs(user-guide): document --transcribe-file * fix(transcribe): pass tdrz_enable as a pywhispercpp kwarg pywhispercpp's Model takes decode params as keyword arguments; a params= dict crashes on the C-level whisper_full_params object. Also let --transcribe-file report unexpected failures cleanly instead of dumping a traceback. * fix(tray): dismiss the tdrz download dialog on response * test(diarization): pin real wave/numpy/tempfile around sibling mocks Several test modules replace sys.modules entries with MagicMocks at import time and never restore them, so diarization's wave/numpy/ tempfile bindings resolved to mocks when this file ran after them in CI. An autouse fixture reimports the real stdlib modules per test, the same remedy test_audio_feedback already applies for wave. * fix(transcribe): verify tdrz model, preload libs, gate formats on ffmpeg - Hash the on-disk TinyDiarize model against the pinned digest before native code maps it, matching the dictation load path - Extract the bundled-lib preloader to utils/pywhispercpp_loader.py and preload before the lazy pywhispercpp import so the headless --transcribe-file path works on source-built installs without RPATH - Offer only WAV in the file picker when ffmpeg is absent; name the dependency in the filter label - Type the new tray/diarization callbacks; document --transcribe-file and the new modules in AGENTS.md * style(audio): finish type annotations on capture helpers and download path _setup_alsa_error_handler now declares Optional[Any] and its ctypes callback gets concrete parameter types; the remaining capture helpers take audio: Any (PyAudio has no importable type) and test_audio_input's device_index becomes Optional[int], which also clears the preexisting implicit-Optional error. _download_whispercpp_model gains -> None and its HF url is narrowed to str so the concatenation and download call stop adding new mypy findings. * style(tests): wrap long monkeypatch line in preload test * refactor(main): narrow transcribe-file errors and add file context * style(ui): annotate TranscriptDialog constructor * fix(diarize): log worker tracebacks, isort test helpers, document --transcribe-file --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
f36c3f6ae5 |
feat(text_injection): add RemoteDesktop portal injection backend (#885)
* feat(text_injection): add RemoteDesktop portal injection backend Wayland offers exactly one sanctioned key-injection path: org.freedesktop.portal.RemoteDesktop's NotifyKeyboardKeysym. It reaches native clients on compositors with no input-method-v2 bridge, works from inside the Flatpak sandbox without /dev/uinput or a helper daemon, and delivers keysyms so non-US layouts survive -- unlike ydotool's evdev keycodes. RemoteDesktopPortal is a synchronous client over a dedicated worker thread holding a private bus connection and GLib context, which keeps Request::Response delivery deterministic. Sessions are created lazily on first use so startup probing only reads the interface version; v2 portal sessions persist a restore token (mode 0600 under data_dir) so the permission prompt appears once. Backend selection now prefers the portal on Wayland: it is probed at runtime ahead of ydotool/wtype/xdotool in autodetection, is pinnable via VOCALINUX_FORCE_BACKEND=portal or text_injection.backend, and demotes to the previous order on runtime failure. Flatpak still needs --device=all for evdev hotkeys, but uinput is only a fallback path now. Closes #750 * fix(text_injection): resolve Greptile findings on RemoteDesktop portal injection - Move persist_mode/restore_token to SelectDevices options (portal v2 sends them there, not CreateSession); a bad token is ignored rather than sinking the session, so drop the stale-token retry paths that could issue a second Start after a user cancel - Track delivered units on RemoteDesktopPortalError so Wayland fallbacks retry only the undelivered tail instead of retyping the prefix (text chars, backspace taps, shortcut steps) - Give queued portal jobs a single deadline budget across CreateSession/SelectDevices/Start waits; on submit timeout the job is cancelled via a Gio.Cancellable plus abort checkpoints so the worker cannot keep emitting keys after the caller gave up - Close sessions whose Start never succeeded - Release held shortcut modifiers on mid-step failure - Skip the portal probe when an installed wtype/ydotool backend is explicitly pinned - Type annotations on the new signatures and touched tests --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
a89966a744 |
feat(recognition): record while an idle model reloads (#851)
* feat(recognition): buffer audio during model reload * fix(recognition): recover buffered reload failures * test(recognition): cover buffered reload edge cases * fix(recognition): narrow buffered reload errors * fix(settings): wait for IDLE before restoring Test Dictation callbacks Buffered reload's stop_recognition() returns while PROCESSING. Test Dictation must keep its text callbacks until recognition reaches IDLE so the test utterance cannot inject into the focused app. * style: apply Black formatting to PR 851 files * fix(settings): cancel buffered reload before Test Dictation timeout restore On the ~3min idle-wait timeout, do not restore live injector callbacks while a buffered reload worker may still deliver text. Cancel via _cancel_reload_recording off the GTK thread, then restore; if cancel is unavailable or fails, leave test callbacks installed and show an error. Add return/param annotations and regression coverage. * fix(settings): keep Test Dictation waiting if buffered cancel fails After the idle-wait timeout, if cancel is unavailable or raises, keep polling with test callbacks and _test_active instead of marking the test done. Ending early left the test callback as the only text destination, so later normal dictation could land in the hidden test buffer. Restore live injectors only once recognition reaches IDLE. * fix(settings): reset Test Dictation timeout-cancel flag per session Clear _test_timeout_cancel_attempted when a test starts, when entering the buffered idle-wait, and when restoring UI so a later Test Dictation can still cancel after timeout. Narrow the cancel-worker except clause to AttributeError/OSError/RuntimeError/TypeError so unrelated defects are not treated as ordinary cancel failure. * fix(recognition): signal capture completion when buffered capture dies A dead microphone during a buffered model reload left should_record=false with no stop_recognition handoff, so the reload worker blocked forever on _capture_finished.wait(). Signal the event from the audio thread's failure teardown paths instead. Refs VocaHQ/vocalinux#851 --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
db07590761 |
feat(activation): D-Bus activation for compositor global shortcuts (#568)
Rebased onto latest main (conflict-resolution squash for PR #568). Co-authored-by: Alexander <ab@digital-benefits.de> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
eef2554f7c |
feat(audio): lower other audio while dictating (#861)
* feat(audio): lower other audio while dictating Opt-in setting lowers the default PipeWire or PulseAudio sink while the microphone is open, then restores that exact level. It stays off by default. * fix(test): keep pytest.approx working when numpy is mocked Other test modules replace sys.modules["numpy"] with a MagicMock at import and leave it there. pytest.approx then crashes on isinstance(..., numpy.bool_). * fix(audio): restore every channel and do not duck if it cannot be undone A failed restore-point write no longer lowers the sink. PulseAudio puts each channel back, and a dead microphone restores playback instead of waiting for an explicit stop. The recognition test patches the real audio module when an older test left a mock behind. * fix(test): put the audio-feedback mock back after the ducking test The recognition test has to patch the real module, but later tests still expect the mock that test_text_injector installs. Restore that mock when the test ends, and annotate the new helpers. * fix(test): annotate the playback-duck tests AGENTS.md requires a type hint on every new function. These tests were the ones still missing them. * fix(audio): skip arming playback duck after recording already failed * chore: retrigger review after rebase |
||
|
|
12e440ce1c |
feat(text-injection): let config.json pin the injection backend (#649)
* feat(text-injection): let config.json pin the injection backend (#476) Autodetection has to infer whether IBus commits actually reach the focused application, and it cannot verify that: commit_text() reports success even when the compositor drops the text. When the inference is wrong the failure is silent, and dictation appears to work in XWayland windows while doing nothing in native ones. VOCALINUX_FORCE_BACKEND already provides an escape hatch, but it is an environment variable: it covers a single run, is undocumented outside the test suite, and a user whose compositor is misdetected has to wrap the launcher in a shell script to make the choice stick. Add a persistent `text_injection.backend` setting alongside it, accepting the same ibus/wtype/ydotool/auto values. The environment variable still wins so a backend can be A/B-tested for one run without touching the saved configuration, and the startup log now names which of the two pinned the choice so a stale setting is diagnosable from --debug output. Reads the file directly rather than through ConfigManager, keeping this package independent of the UI layer, exactly as _should_copy_to_clipboard already does. An unreadable or corrupt config falls back to autodetection instead of blocking startup, and an unrecognised value is ignored with a warning so a typo cannot silently pin the wrong backend. Defaults to "auto", so existing installs behave exactly as before. * fix(text-injection): tell an explicit VOCALINUX_FORCE_BACKEND=auto from unset The variable is meant to override the saved text_injection.backend pin for a single run, which is what makes the two paths A/B-testable without editing config. Setting it to auto -- the value that means "autodetect this time" -- did not do that: the reader collapsed unset and an explicit auto into the same "auto" return, so the caller read both as "nothing was forced" and fell through to the saved pin. The pin won, and there was no way to ask for autodetection for one run short of editing the config file the variable exists to avoid. Read the variable as three states instead: unset, an explicit auto, or a backend name. Only the unset state consults config, so an explicit auto now short-circuits to autodetection. An unrecognised value is deliberately treated as unset rather than as auto. A typo means the user failed to override their preference, not that they asked to discard it, so falling through to a valid saved pin is the kinder reading. _forced_backend() keeps its signature and its collapsed return, so its existing callers and tests are untouched; the new three-state reading lives alongside it and is what the preference resolver consumes. * feat(text-injection): accept an xdotool pin and warn when ibus overrides the denylist Two gaps in the pinnable backend set. xdotool was not accepted, even though on X11 it is the tool that actually does the typing. Users were told to pin a *Wayland* backend to mean "not IBus", and get xdotool as a side effect. Naming it directly is what they were reaching for. On Wayland it needs no special handling: the value skips IBus like any other non-ibus pin and falls through to the existing XWayland fallback, so no branch is added to the selection chain. The accepted values now live in one tuple, because four places listed them -- both readers and both "expected ..." messages -- and a fifth value would have had to be added to all four. The two messages derive from the tuple, so they cannot fall behind it. Pinning ibus also silently bypasses the compositor denylist. That check exists because a denylisted compositor makes IBus report a commit as delivered while the text never arrives (#478, #485), so a pin is the one setting that can put a user back into a failure with no symptom other than nothing happening. The pin is still honoured -- an override that ignores you is worse than one that warns -- but it now says so at warning level, names the compositor, and gives the remedy. It stays quiet when the compositor genuinely bridges IBus, including a denylisted one running the ibus-wayland bridge, and on X11 where the denylist does not apply. The check is evaluated once. The guard chain short-circuits on the pin, so a pinned run never reaches the existing compositor branch, and this is its first and only call -- worth knowing because the check can shell out to pgrep or gdbus. A test asserts the call count so that stays true. Documents xdotool in the value table and rewrites the X11 paragraph, which described xdotool as the implicit result of pinning something else rather than as a value you can name. * fix(text-injection): say so when the pinned backend is not the one in use A pin could be quietly ignored. Pinning a tool that is not installed fell through to autodetection and typed with whatever it found, under a log line that had already announced the pin was overriding autodetection. Pinning xdotool on Wayland did nothing at all, since the selection chain reaches ydotool and wtype first. Pinning ibus where IBus support is unavailable skipped the whole IBus branch. In each case the user set a preference, saw no complaint, and got something else. These are one condition, not three: whatever ended up doing the typing is not what was pinned. The check compares the pin against the backend read back from final state, so a cause nobody has thought of yet is covered without another branch. It runs at the end of construction rather than inside _check_dependencies, which returns early on two paths and which the wtype probe can still override afterwards. The reason clause is added only where a generic check answers it -- the binary is absent, or IBus is unavailable -- and omitted otherwise rather than guessing. Reporting the source separately was letting the environment variable and the config setting disagree. _check_dependencies resolved the backend, then re-derived which source had won by asking again -- so the environment was parsed twice per construction, and a malformed value was reported to the user twice. Resolution now returns the backend and its source together, which fixes the duplicate warning and means the source named in a log line is the one that actually chose the backend. _backend_preference() keeps its signature and reads the first element. Two log lines also claimed VOCALINUX_FORCE_BACKEND regardless of where the pin came from, so a config-set backend was reported as an environment variable the user had never set. They now name the real source. The new warning is a startup diagnostic, not a live invariant: a tool switched later by _try_recover_from_fallback is not reported again. Noted in the docstring so the boundary is deliberate. * docs(user-guide): file the injection-backend section under Troubleshooting The section sat under Customization, directly beneath GPU Acceleration -- where the paragraph immediately above it reads "To check which backend is being used" about Vulkan and CUDA. The word means something else there, and two adjacent sections using it for different things is a bad place to explain either. It also is not a customization option. It exists because a compositor can make IBus report a commit as delivered while nothing arrives (#478, #485), and someone hitting that reads Troubleshooting, not Customization. The section already opens on the symptom, so it needs no rewriting to belong there. Pure reorder: no wording changes, and the heading text is unchanged so any existing link still resolves. * test(text-injection): cover the pin paths that skip the selection chain _check_dependencies had three uncovered branches, all reachable by pinning a backend and none of them exercised: the two early returns that hand over to IBus before a tool is chosen, and the daemonless-ydotool fallback a wtype pin lands on when wtype is absent. The early returns matter most, since the pin check runs at the end of construction precisely so those paths cannot skip it, and nothing proved that. Adds the ydotool counterpart to the missing-binary case, so a check keyed on one tool name cannot pass by coincidence; an X11 test for the guide's claim that any non-ibus pin turns the IBus path off, with a control asserting IBus is constructed without a pin so the assertion is the pin's doing; and an end-to-end explicit-auto case, which until now was only proven at the resolver rather than through selection. The pin log line said it was "overriding backend autodetection" before anything had been checked. An unavailable pin falls through to autodetection, so that line announced an outcome it could not know and then read as contradicting the "was not applied" warning. It now states the request only. _run_with_pin gains ydotoold_ready, defaulting to the previous behaviour so existing callers are unchanged. * fix(text-injection): report a config file the backend pin cannot be read from A corrupt config.json disappeared into logger.debug. The setting is hand-edited -- that is the workflow the user guide documents -- so a stray comma is a likely way to reach this, and staying quiet left the user with autodetection and the silent IBus miss the pin was set to avoid, with nothing to tell that apart from the pin not working. Unknown values already warned; an unusable file now does too, naming the file and the parse error. The bare handler was also catching AttributeError by accident. Valid JSON that is not an object -- a bare list, string, number or null -- reaches the lookup and raises there rather than at parse time, and so does a text_injection section that is not an object, where the top level is a mapping and the section is not. Both are now shape checks rather than exception handling, which is what lets the handler narrow to OSError and ValueError without those five shapes escaping it. Locating the file moves out of the try. It fixes the handler being unable to name the path it failed on, and it is a deliberate boundary: config_dir() resolving a path is not a failure this should paper over, so it is no longer guarded here at all. A missing config.json stays silent, which is the default install. * test(text-injection): stop the ibus-pin tests depending on an installed ydotoold The bridged-compositor and X11 cases asserted that a run logs no warnings at all. That is broader than what they are about, and _run_ibus_pin patches shutil.which to claim every tool exists without patching the probe that then shells out to one, so the real _ensure_ydotoold ran and warned whenever /usr/bin/ydotoold was absent. The tests passed on a machine that happens to have it installed and failed on the CI runners that do not. The assertions now name the warning they are about, and the helper patches _ensure_ydotoold the way its sibling _run_with_pin already did, so nothing in this class of test reads the host's installed packages. * fix(text-injection): report pending IBus initialization state * docs(text-injection): clarify backend pin behavior * fix(config): preserve a hand-edited backend across unrelated saves ConfigManager writes its full in-memory configuration on every save. A user who edits text_injection.backend in config.json while Vocalinux is running therefore loses that choice when an unrelated Settings or tray save writes the stale default back. Remember only the backend value observed at load or after a successful save. If the in-memory value is unchanged, preserve the current value from disk; if it changed through ConfigManager, treat that as the intentional write. Missing and explicit auto both reset to auto. The save aborts rather than overwriting a file whose JSON or text_injection shape cannot be read safely. This is deliberately per-key: no recursive configuration merge or injection-layer dependency is added. * style(text-injection): remove redundant typing suppression * fix(config): keep invalid UTF-8 config from blocking startup * fix(config): preserve malformed backend sections on unrelated saves * fix(config): retain text setting changes after malformed config * test(text-injection): annotate backend pin test helpers |
||
|
|
7bdbedce0b |
feat(language): follow the active keyboard layout while dictating (#837)
* feat(language): follow the active keyboard layout while dictating Adds a "Follow keyboard layout" switch to Settings -> Speech Model -> Advanced. With it on, the active keyboard layout is re-read at the start of every dictation and the matching language is used, so someone who switches layouts to work in two languages gets the right one without opening Settings. #796 already seeded the language once on first run, but from the *configured* primary layout, which never changes as the user switches between them: a "fr,us" setup reports fr forever. detect_active_keyboard_ layout() reads GNOME's input sources instead. It takes mru-sources rather than the current index, because that index goes stale -- observed still reading 0 after a switch to the second layout -- which is why the IBus injection path already trusts the MRU list (#497, #738). An active source that is an IBus engine rather than an xkb layout is a real answer that cannot be mapped, so the language is left to the engine's own auto-detect. Borrowing the configured xkb layout there would pin English for someone typing Japanese through mozc-jp. The mode is a switch over the language picker rather than an entry inside it, since it is a mode and not a language. While on, the picker is disabled and shows what the layout resolves to; turning it off pins that. A language change no longer re-initialises the engine when the loaded model takes the language per utterance, which is the whole whisper family, so following a layout costs a settings read rather than a model reload. VOSK loads one model per language, Parakeet ignores the picker, and an English-only model has no non-English weights, so Settings refuses the mode for all three rather than offering a switch that does nothing. Fixes #821 * fix(language): keep follow mode armed and map IBus engines Swap English-only whisper.cpp weights to the multilingual sibling while passing the layout sentinel into reconfigure, so follow mode stays armed. Map common GNOME IBus engines onto catalogue languages instead of leaving them unmapped. * fix(language): stop IBus hyphen false-maps and gate the sibling swap The last-token hyphen heuristic mapped mozc-us and foo-us to English. Restrict it to *-jp/*-kr and name Mozc's extra engines as Japanese. Only whisper.cpp may consult the whisper.cpp download catalog. * fix(language): keep multilingual weights while following layout The picker shows the active layout while follow is on, often en-us. Deriving whisper.cpp .en from that display made Settings refuse the mode as English-only, then the next auto-apply persisted a concrete language and disarmed follow. Use LANGUAGE_FOLLOWS_LAYOUT for .en vs multilingual derivation instead, including on the load path before the switch is synced. * fix(language): type follow helpers and log sibling-swap failures * fix(test): stub follow-layout model rec so mocked psutil does not blow up follow-mode English-only guard calls get_recommended_whispercpp_model, which reads RAM through psutil. Full CI leaves a MagicMock in sys.modules["psutil"], so ram_gb >= 16 raises TypeError. Patch the settings_dialog alias to return a multilingual tiny rec so the assertion still holds without real RAM. * fix(language): catch specific errors on layout lookup and sibling swap * fix(language): swallow more sibling-swap reconfigure failures ImportError, TypeError, AttributeError, and ChecksumError from the multilingual swap must not abort dictation or disarm follow mode. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
f48bdda525 |
docs: improve project documentation for a clearer public surface (#634)
* docs: polish project documentation for a clearer public surface Rewrite the README and supporting docs for a more professional first impression: less emoji and badge noise, install-first structure, and a real documentation index. Add a Contributor Covenant, tighten SECURITY and GitHub templates, and make distro/update guides read as product docs instead of internal status notes. * docs: split install guide and add changelog plus support pages Break INSTALL.md into a short primary guide, manual/PyPI package lists, and troubleshooting. Add root CHANGELOG.md and SUPPORT.md, wire them into the docs index and README, and drop the outdated screenshot lag note now that v0.15 assets are on main. * docs: humanize PR copy and remove em dashes Strip em/en dashes from the documentation rewritten in this PR, fix mid-sentence colon fallout from that pass, and smooth a few stiff phrases so the public docs read more natural. * docs: list Faster Whisper after rebase onto main Installer and CLI already accept faster_whisper. Document it next to the other engines, drop the outdated screenshot lag note, and keep the on-device wording honest for local vs remote. |
||
|
|
7664266823 |
feat(commands): localize punctuation voice commands (#640) (#642)
* feat(commands): localize punctuation voice commands by language Voice command phrases were English-only even when recognition language was set to Italian, French, etc. Merge locale punctuation/line-break aliases from the recognition language into CommandProcessor, keep English phrases available, and document the behavior. Fixes #640 Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * fix(commands): keep line breaks and French apostrophe aliases Attach-left punctuation now eats only horizontal whitespace, so a localized "new line" followed by a period or comma still inserts a newline. French aliases include ASCII, U+2019, and backtick apostrophes, and longest-match still wins so "point" cannot fire inside "point d'interrogation". --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> |
||
|
|
2c6acbc15e |
fix(inject): layout-aware clipboard paste for Neo and friends (#788)
* fix(inject): resolve paste chord for non-QWERTY layouts ydotool was sending physical KEY_V (47) for Ctrl+V. On German Neo that key types p, so paste opened Print instead. Prefer wtype keysyms when usable; otherwise resolve the evdev key for Latin v via xkbcommon, and read KDE kxkbrc so Plasma Wayland Neo users are covered. Fixes #787 * fix(inject): read Plasma active layout from real session sources kxkbrc has no CurrentLayout/LayoutIndex, so multi-layout Neo always fell back to us at index 0 and paste still hit KEY_V. Prefer D-Bus getLayout or layout_memory.xml for the active layout, keep kxkbrc for the list only, and prefer KDE over leftover GNOME on Plasma. |
||
|
|
008fd3138e |
fix(injection): use Ctrl+Shift+V when pasting into terminals (#734)
* fix(injection): use Ctrl+Shift+V when pasting into terminals Clipboard injection always sent Ctrl+V, which most terminal emulators ignore. Detect standalone terminal windows and send Ctrl+Shift+V, with a Settings override for nested IDE panels that window-class detection cannot see. * fix(injection): fail closed when terminal window probe is mocked Clipboard tests patch text_injector.subprocess.run, which replaces stdlib subprocess.run. Window detection now ignores non-string probe output so injection still uses Ctrl+V instead of raising TypeError. * docs: fix overclaimed terminal auto-detect in USER_GUIDE Auto-detect only runs on X11 and on Hyprland, Sway, and niri. GNOME and KDE Wayland stay on Ctrl+V unless you set Clipboard Paste Shortcut to Ctrl+Shift+V. |
||
|
|
279d99fea0 |
fix(whisper): honor bundled GPU libs and skip software Vulkan devices (#674)
* fix(whisper): use bundled GPU libs and skip software Vulkan devices AppImage 0.15.0 shipped a CPU-only pywhispercpp wheel, so the Settings GPU picker and host vulkaninfo never moved inference off the CPU. Rebuild pywhispercpp with Vulkan in the AppImage, log the libraries that actually load, skip llvmpipe in auto-select, and document how to verify. Fixes #604 * fix(appimage): pin gcc/g++ for Vulkan pywhispercpp rebuild Hosts that default cc/c++ to clang fail CMAKE's C++ test because clang does not search gcc's libstdc++ path. Default the rebuild to gcc/g++. |
||
|
|
ecd5839a13 |
feat(settings): delete unused downloaded speech models (#671)
* feat(settings): delete unused downloaded speech models Add a Remove Model dropdown on the Speech Model page so whisper.cpp, Whisper, and VOSK downloads can be deleted from Settings. The currently selected model is marked in use and cannot be removed until you switch away from it. Fixes #650 * fix(settings): move unused model deletion below the picker Unused downloads is its own group under the model info card. Only leftover files appear, each with a Delete button. The group hides when empty. * fix(settings): collapse unused downloads into an expander Keep leftover models behind one row so a long list does not stretch the Speech Model page. Expand to delete files one at a time; the list scrolls once it grows past a few entries. * fix(settings): keep leftover model list from clipping in the expander Wrap the unused-downloads list so the expander can show a few rows and scroll the rest, instead of painting only the first leftover. |
||
|
|
c337d8f56d |
feat(shortcuts): default to hold Right Alt (push-to-talk) for new installs (#648)
* feat(shortcuts): default to hold Right Alt push-to-talk on first install Double-tap Ctrl conflicts with many desktop and app shortcuts, so new installs now use hold Right Alt (Option) in push-to-talk mode. Existing config.json values are preserved unchanged. Updates README, user guide, AGENTS.md, first-run dialog, website copy, and tests to match the new defaults. * refactor(shortcuts): trim redundant default-change test and comments Drop the fresh-install assertion that duplicated DEFAULT_CONFIG coverage, remove the duplicated Right-Alt rationale from config_manager, and drop a vacuous source-string check from the settings shortcuts test. * fix(shortcuts): align install.sh seeds and preserve legacy toggle mode install.sh still wrote ctrl+ctrl without mode, so merges with the new push_to_talk default became hold-Ctrl. Seed right_alt + push_to_talk for new installs, and migrate existing shortcut-only configs to mode=toggle so prior behavior is unchanged. * style(config): fix black formatting after shortcuts migration * fix(shortcuts): preserve legacy defaults when shortcuts section is missing Existing config files without a shortcuts section previously inherited ctrl+ctrl toggle from DEFAULT_CONFIG. Pin those historical defaults on upgrade so the new first-install Right Alt push-to-talk defaults do not silently change behavior. * docs(agents): unwrap hard-broken dictation control paragraph Keep the AGENTS.md paragraph as a single line like the surrounding prose. |
||
|
|
67dde781f5 |
docs(user-guide): note CUDA device 0 limit for dual NVIDIA (#644)
Document that CUDA-backed pywhispercpp always uses device 0, and point to CUDA_VISIBLE_DEVICES or a Vulkan build as workarounds. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> |
||
|
|
fbb81238f5 |
chore(release): prepare v0.15.0 (#609)
* chore(release): prepare v0.15.0 Bump version across app, web, AUR, and Flatpak metainfo. Document GitHub release-notes rules, refresh README/UPDATE/INSTALL/USER_GUIDE for the 0.15 series (sidebar settings, AppImage, auto-pause/keepalive, Vulkan device selection), and align website product surfaces with shipped features. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * docs(release): fold latest main into v0.15.0 notes Include dictation capitalization/trailing-space, ibus-wayland injection, IBus teardown, and settings notice polish landed on main after the initial release prep rebase. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * docs(release): include Bluetooth mic crash fix in v0.15.0 notes Fold #599 (PortAudio Bluetooth SCO probe heap corruption) into README, UPDATE, changelog, AppStream, and release history after rebasing onto main. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * docs(release): include language catalog expansion in v0.15.0 notes Fold #616 (Hungarian and expanded speech languages, Fixes #565) into README, UPDATE, changelog, AppStream, PRODUCT.md, and release history after rebasing onto latest main. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * docs(release): fold settings footer and language follow-ups into v0.15.0 Include #618 sidebar dictation footer, #619 Custom Shortcut Record/Set restore, and #617 English (India) Whisper language mapping after rebasing onto latest main. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> |
||
|
|
e0ac5e409c |
feat(whisper): add guided whisper.cpp model variants (#465)
* feat(whisper): refine whisper.cpp model selection * refactor(whisper): exclude diarization model from picker * docs(whisper): document guided model variants * fix(ui): cap settings dialog height at 600px to prevent excessive height on high-res displays |
||
|
|
78f377fd0f |
fix(models): correct download size metadata for whisper.cpp and VOSK (#453)
* fix(models): correct download size metadata for whisper.cpp and VOSK The size_mb values in WHISPERCPP_MODEL_INFO were using parameter counts instead of actual download sizes, causing the UI to display incorrect sizes (e.g., showing 39 MB when actual download is 74 MB). Updated all whisper.cpp models with verified sizes from Hugging Face and adjusted VOSK medium/large sizes to better represent actual downloads. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) * docs: update model download sizes to match actual downloads Update whisper.cpp size references from parameter counts to verified download sizes (e.g., tiny: 39MB → 74MB, medium: 769MB → 1.5GB, large: 1.5GB → 3.0GB). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) * docs(web): update model sizes across website pages Sync whisper.cpp size references on gpu-acceleration, compare, whisper-model-guide, offline, and homepage with corrected download sizes. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) |
||
|
|
0469defae6 |
chore(release): prepare v0.8.0-beta (#280)
* chore(release): prepare v0.8.0-beta * docs(web): align release messaging with v0.8.0-beta features * docs(web): tighten homepage messaging for v0.8.0-beta |
||
|
|
9886e8cb9d |
feat(ui): add auto-start on boot support (#207)
* feat(ui): add auto-start on boot support - Add --start-minimized CLI flag for autostart entry - Create autostart_manager.py to handle XDG autostart desktop entry - Add autostart config option in config_manager.py - Add Start on Login toggle in Settings dialog - Add Start Minimized toggle in Settings dialog - Add Start on Login checkbox in system tray menu - Add first-run dialog to ask about autostart preference Closes #184 * fix(test): add first_run: False to mock config settings The tests were failing with StopIteration because main() now checks for first_run and shows a dialog if True. By setting first_run: False in the mock config, we skip the first-run dialog code path. * fix(ui): harden autostart behavior across Linux sessions Use XDG-compliant autostart paths and robust Exec command resolution, keep first-run prompt semantics correct, and fix GTK toggle handling so state changes are applied reliably. * docs(autostart): document XDG startup model and limits Clarify that Vocalinux uses desktop-session XDG autostart, not systemd services, and explain compatibility expectations for mainstream DEs versus minimal/custom WM sessions. * feat(ui): add first-run quick-start guidance text Expand the welcome dialog with concise first-time usage guidance and clear direction to the tray menu Settings entry while preserving the existing autostart decision flow. * fix(uninstall): remove XDG autostart desktop entry Ensure uninstall removes ~/.config autostart registration and reports leftover autostart entries during verification. * fix(install): ignore defunct processes in running-check Filter installer process detection to exclude defunct entries and installer/uninstaller shells so stale zombie rows do not block reinstall. * fix(uninstall): ignore defunct processes in running-check Use filtered process detection so zombie entries and installer shells do not trigger false running-process warnings during uninstall. |
||
|
|
03d55a989f |
chore(release): prepare v0.6.0-beta with whisper.cpp as default (#181)
* docs: update all documentation for whisper.cpp as default engine Comprehensive documentation update highlighting whisper.cpp as the new default speech recognition engine: README.md: - Added 'Major Change: whisper.cpp is Now Default!' section - Updated Features to highlight whisper.cpp and Vulkan GPU support - Updated Quick Install section with engine choices - Changed default engine from whisper to whisper_cpp in examples - Updated configuration examples - Updated roadmap to mark whisper.cpp as complete docs/INSTALL.md: - Added whisper.cpp section with performance benefits - Updated installation options (--engine flag) - Added GPU support matrix and Vulkan detection info - Added installation time comparison table - Updated system requirements (lower disk space for whisper.cpp) docs/USER_GUIDE.md: - Added comprehensive 'Choosing Your Engine' section - Added GPU acceleration information - Updated tips to mention whisper.cpp benefits - Added model size recommendations web/src/app/page.tsx: - Updated hero to mention whisper.cpp (C++ optimized, Vulkan support) - Changed installation time from ~5-10 min to ~1-2 min - Updated Features section to mention Vulkan GPU support - Added whisper.cpp as default engine card (3-column layout) - Updated installation commands (--engine flags) - Updated FAQ about switching engines This ensures all documentation reflects whisper.cpp as the new default and highlights its major benefits: 10x faster install, universal GPU support, and C++ optimized performance. * chore(release): prepare v0.6.0-beta This release brings whisper.cpp as the new default speech recognition engine: Version Updates: - Bump version from 0.5.0-beta to 0.6.0-beta - Update src/vocalinux/version.py - Update web/package.json Documentation Updates: - README.md: Update all v0.5.0-beta references to v0.6.0-beta - docs/INSTALL.md: Update version references and installation commands - docs/UPDATE.md: Add new 'What's New' section highlighting whisper.cpp - SECURITY.md: Update supported versions table (0.6.x now supported) Website Updates: - web/src/app/layout.tsx: Update softwareVersion to 0.6.0-beta Release Highlights: - whisper.cpp is now the default engine (10x faster install!) - Universal GPU support via Vulkan (AMD, Intel, NVIDIA) - C++ optimized inference with multi-threading - ~1-2 minute installation vs ~5-10 minutes before Testing: - All 472 tests passing - Black formatting clean - Flake8 linting clean - isort import ordering clean * ui(website): improve hero section layout * docs(readme): remove duplicate What's New section |
||
|
|
f8e5de5621 |
feat: Make Whisper tiny the default engine and download during install (#45)
* feat: Make Whisper tiny the default engine and download during install - Changed default engine from 'vosk' to 'whisper' in config_manager.py - Changed default model_size from 'small' to 'tiny' in config_manager.py - Added install_whisper_model() function to install.sh that downloads the Whisper tiny model (~75MB) during installation - Whisper model is downloaded when Whisper package is installed (in non-interactive mode or with --with-whisper flag) - Updated installation summary to show Whisper model status - Updated documentation (README.md, INSTALL.md, SECURITY.md, USER_GUIDE.md) to reflect Whisper tiny as the default engine This provides a better out-of-box experience: users can install and immediately use Vocalinux without any additional downloads or prompts. * test: Update test to reflect whisper as default engine |
||
|
|
a33c0d1b4c |
Simplify Keyboard Shortcuts to Exclusive Double-tap Ctrl Implementation (#9)
* Implement double-tap Ctrl keyboard shortcut feature * Simplify keyboard shortcuts to only use double-tap Ctrl * Update docs * Fix syntax * Fix CI build by adding portaudio19-dev dependency |
||
|
|
a28f55b72e |
Add pre-commit hooks to match GitHub CI pipeline (#7)
* Add pre-commit hooks to match CI pipeline linting rules * Add pre-commit hooks that match GitHub CI pipeline exactly * Ran lint check |
||
|
|
ee227252f2 | Refactor project name from 'Ubuntu Voice Typing' to 'Vocalinux'\n\nThis commit updates all references to the project name across the codebase, documentation, and metadata. The changes include:\n- Updating module docstrings, comments, and logging messages.\n- Renaming files and directories where necessary.\n- Updating references in README, INSTALL, and LICENSE files.\n- Adjusting paths and identifiers in the code to reflect the new name.\n\nThis refactor ensures consistency and prepares the project for its new branding as 'Vocalinux'.\n\nPlease review the changes carefully to ensure no functionality is affected. | ||
|
|
40072f2e61 | Add documentation and basic tests |