Moves play_stop_sound() to after audio_thread.join() but before the
final buffer processing. This closes the race where the audio thread's
in-flight chunk could capture the stop cue for short (≤ ~192 ms) recordings,
while still feeling immediate compared to the original placement after all
recognition cleanup.
* feat(settings): add Advanced tab with whisper.cpp anti-hallucination parameters
Add a new Advanced settings tab that exposes whisper.cpp decoding
parameters to help users reduce hallucinations and tune recognition
behavior.
Parameters exposed:
- No Timestamps (default: on)
- Suppress Non-Speech Tokens (default: on)
- No Context (default: on)
- Initial Prompt (default: empty)
- Temperature (default: 0.0)
- Temperature Increment (default: -1.0, disables fallback)
- Entropy Threshold (default: 2.4)
- Logprob Threshold (default: -1.0)
- No-Speech Threshold (default: 0.6)
All parameters are saved under the advanced config section and are
only active when the whisper.cpp engine is selected. The UI grays out
these settings and shows an explanatory note when another engine is
active.
Changes:
- config_manager.py: add whisper.cpp keys to DEFAULT_CONFIG[advanced]
- recognition_manager.py: read params from kwargs/config, pass to Model()
- main.py: pass advanced settings from config to SpeechRecognitionManager
- settings_dialog.py: new Advanced tab with switches, spin buttons, entry
- tests: update test_main.py assertions, add whispercpp_advanced_defaults
and whispercpp_advanced_persistence tests
Fixes#363
* fix(whispercpp): gracefully filter unsupported params for pywhispercpp 1.4.1
pywhispercpp 1.4.1 does not expose no_timestamps or suppress_non_speech_tokens
to Python even though they exist in the underlying whisper.cpp C struct.
This causes AttributeError on startup.
Add _load_model_with_compatible_params() which catches AttributeError,
identifies the unsupported param from the error message, removes it, and
retries until the model loads successfully or a non-AttributeError occurs.
Also refactor _build_whispercpp_model_kwargs() from _load_whispercpp_model()
for testability.
Fixes local startup crash reported during PR testing.
* fix(whispercpp): persist advanced settings correctly
* chore(config): add advanced power-user default
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* feat(settings): gate advanced controls behind power-user opt-in
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* fix(whispercpp): prefilter unsupported native params
* fix(settings): polish advanced whispercpp controls
The suse case in install_system_dependencies() previously installed all
packages blindly, causing the entire install to fail if a single package
was unavailable (e.g., glslang on some openSUSE repos).
Changes:
- Check each package individually with rpm -q before installing
- Add graceful fallback for glslang -> glslang-devel
- Add graceful fallback for libappindicator-gtk3 -> typelib-1_0-AppIndicator3-0_1 -> libayatana-appindicator3-1
- Continue installation with warning instead of hard failure
Fixes#407
* test(recognition): cover _perform_recognition and _enqueue_audio_segment
The previous commit demoted logger.info('DEBUG: ...') to logger.debug(...)
and removed a dead loop body in _perform_recognition. Codecov flagged 12
uncovered lines — all inside _perform_recognition and _enqueue_audio_segment,
which existing tests always mocked out.
Add direct (non-mocked) tests for both methods:
- _enqueue_audio_segment:
* empty buffer → early return
* normal buffer → enqueues correctly
* full queue → drops oldest and retries
- _perform_recognition:
* processes a segment then exits on None signal
* drains remaining items on None signal
* exits cleanly when idle with empty queue
* continues looping on queue timeout while recording
These tests exercise the actual threading/queue logic and cover the
logger.debug / logger.warning calls as a side effect.
* test(conftest): suppress real desktop notifications during test runs
_show_notification shells out to notify-send, which pops up real system
notifications when tests exercise the model-not-ready path. Add an autouse
fixture that patches _show_notification for all tests EXCEPT those whose
name contains 'show_notification' (so direct tests of the helper still
verify subprocess.Popen calls).
* chore(gitignore): ignore .sisyphus directory
* fix(ibus): wait for engine readiness before first injection
Add a startup readiness probe that pings the IBus engine after activation so first-use dictation no longer races do_enable(). Also switch NO_ENGINE retry behavior to exponential backoff and cover the new flow in tests.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* test(ibus): cover readiness probe edge cases
Add focused tests for the new startup readiness path, including direct PING success, retry exhaustion, unexpected responses, and engine re-activation before injection. This raises coverage on vocalinux.text_injection.ibus_engine from 68% to 73% in the targeted suite.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(text-injection): avoid blocking IBus and clipboard fallbacks
Warm IBus in the background so startup no longer stalls while readiness probing, and make clipboard helpers fail fast with per-session health caching so non-ASCII ydotool fallback does not lock dictation.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* test(ibus): update retry tests for merged NO_ENGINE recovery logic
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(ibus): retry and recover from transient socket failures
Harden IBus injection against connection-refused, timeout, and engine process races so dictation is more resilient in real-world desktop sessions.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* fix(text-injection): fallback to non-IBus backend on runtime IBus failures
Allow dictation to continue when IBus runtime injection fails by automatically switching to xdotool/wtype/ydotool fallbacks instead of returning an immediate failure.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* fix(ibus): preserve live socket injection during recovery
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* test(coverage): add runtime fallback and ibus retry coverage
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
* test(ibus): fix do_destroy super() test failing in CI without IBus installed
Patch builtins.super instead of relying on mock_ibus.Engine.do_destroy,
which fails with issubclass() TypeError when VocalinuxEngine is built
with object as its base (IBus unavailable in CI).
* test(ibus): fix MagicMock.__new__ TypeError on Python < 3.13
VocalinuxEngine inherits from MagicMock at import time (IBus is mocked
via sys.modules at test-file level). Calling VocalinuxEngine.__new__(VocalinuxEngine)
delegates to MagicMock.__new__ which runs issubclass(cls, AsyncMockMixin).
On Python < 3.13 that raises TypeError when the module-cached class is a
MagicMock spec object rather than a real type.
Fix: introduce _make_engine() helper that allocates via a thin concrete
subclass whose __new__ calls object.__new__ directly, bypassing the
MagicMock allocation path entirely. Verified across all three
TestVocalinuxEngineDestroy test methods.
* test(ibus): use unbound do_destroy call to avoid MagicMock interference
The previous fix (thin _ConcreteEngine subclass) still failed because
VocalinuxEngine inherits MagicMock.__getattribute__, which intercepts
.do_destroy on instances and returns a mock attribute rather than the
real method - regardless of Python version.
Call VocalinuxEngine.do_destroy(engine) directly with a plain object()
as the self argument instead. do_destroy only uses self for identity
comparison (is VocalinuxEngine._active_instance), so any object works.
This bypasses MagicMock's MRO entirely.
* test(ibus): reload module and instantiate real engine class for do_destroy tests
* refactor(ibus): extract destroy handler for stable cross-version tests
* test(ibus): cover destroy helper branches and do_destroy delegation
* test(ibus): stabilize do_destroy CI behavior across environments
Avoid brittle super() mocking in no-IBus environments and gate the IBus-only parent destroy path test behind runtime capability checks to prevent false CI failures on Python matrix runners.
* test(ibus): remove flaky do_destroy class-path assertions
Drop brittle class-method destroy tests that fail under mocked GI/IBus on older Python runners and keep deterministic helper-level coverage for destroy behavior.
.opentree/ contains git worktrees created by the opentree CLI tool
(https://github.com/axelgar/opentree). These are local developer
artifacts and should not be tracked.
opentree.toml is the per-repo opentree config - also local/personal,
not something contributors need to share.
Override do_destroy() to clear _active_instance when IBus destroys the
engine on keyboard layout switch (Super+Space). Add retry logic in
inject_text() — on NO_ENGINE, re-activates via switch_engine() and
retries once to recover cleanly.
Co-authored-by: Sbenazar <127324689+Sbenazar@users.noreply.github.com>
Replace hard-coded stop tail trim with configurable stop_sound_guard_ms
(default 200ms) so quick key release no longer clips the final spoken
word in push-to-talk mode.
Co-authored-by: Fakhareddine <fe.sabbari@claranet.com>
Fix false local repo detection when users run `uv init` before installation. Changed detection from naive file existence check to content validation:
- For pyproject.toml: check for `name = "vocalinux"`
- For setup.py: check for "vocalinux" string
- Falls through to remote clone mode if neither matches
Fixes#396
- Add Python 3.14 to classifiers
- Add lxml>=6.1.0 to dependencies to fix install on Python 3.14 (CachyOS, Arch)
- Update black target-version to include py314
Fixes#404Closes#405
- Bump version from 0.10.1-beta to 0.10.2-beta in version.py
- Update README.md What's New section with v0.10.2 highlights and bug fixes
- Update SECURITY.md supported versions table
- Update docs/UPDATE.md with v0.10.2 release notes
- Update docs/RELEASE_PROCESS.md version history table
- Update web/src/app/page.tsx softwareVersion and version badge
- Update web/src/app/changelog/page.tsx with v0.10.2 release entry
- Update web/package.json and web/package-lock.json to 0.10.2
Key changes in this release:
- Non-ASCII text injection via ydotool clipboard fallback (#376)
- IBus detection on Wayland without legacy env vars (#381)
- IBus engine process startup fix (#361)
- Missing deps for Pop!_OS and Ubuntu 24.04+ (#379)
- Systematic code quality refactor (#377)
- Redesigned website OG image (#392)
Replace the cartoon-style OG image with a clean, text-based design
inspired by VocaMac's approach:
- Left side: Logo, tagline, feature bullets, GitHub CTA, distro pills
- Right side: Feature cards with SVG icons and colored badges
- Proper SVG vector icons (shield, keyboard, lightning, microphone)
- Brand gradient accent lines top and bottom
- Added SVG source file for future edits
Image size reduced from 1.1MB to 113KB (PNG) and 96KB to 41KB (webp).
- Add Notify mock to global gi.repository mock in conftest.py
- This prevents real desktop notifications during test runs
- Clean up unnecessary comments in test_main_args_deps.py
* fix(text-injection): handle non-ASCII characters with ydotool via clipboard paste (fixes#362)
ydotool simulates evdev key events which only map to US ASCII keycodes.
Non-ASCII characters (accented letters like á, é, ú, ñ, ü, etc.) get
silently dropped during injection, so 'Esdrújula' becomes 'Esdrjula'.
When ydotool is the active Wayland injection tool and the text contains
non-ASCII characters, the new code:
1. Copies text to clipboard via wl-copy (or xclip/xsel fallback)
2. Simulates Ctrl+V via ydotool key to paste it
3. Falls back to regular ydotool type if clipboard tools unavailable
This preserves all Unicode characters perfectly. ASCII-only text still
uses ydotool type directly for lower latency.
* style: fix black formatting in text_injector.py
* test: add coverage for clipboard paste paths (xclip/xsel/wtype fallbacks)
Covers the uncovered paths flagged by Codecov:
- xclip fallback when wl-copy unavailable
- xsel fallback when wl-copy/xclip unavailable
- wtype Ctrl+V paste simulation
- Paste failure (Ctrl+V fails after clipboard copy)
- No clipboard tools available
* refactor(text-injection): eliminate duplication and dead code in clipboard paste path
- _inject_via_clipboard_paste now delegates to _copy_to_clipboard instead
of reimplementing wl-copy/xclip/xsel selection (removes ~30 lines of
duplication identified in code review)
- Remove unreachable wtype branch from _inject_via_clipboard_paste; wtype
handles Unicode natively via Wayland virtual-keyboard protocol so it
never calls this method
- Drop unnecessary time.sleep(0.05) after clipboard write; subprocess.run
for wl-copy is synchronous so the sleep was a no-op
- Add debug log noting the clipboard will be temporarily overwritten so
operators can observe the side-effect without diving into the code
test improvements:
- Fix fragile string-repr assertion in test_ydotool_ascii_uses_type_directly;
now compares actual list slices and verifies wl-copy is NOT called
- Replace test_clipboard_paste_with_wtype with test_wtype_injects_unicode_directly
which actively asserts wtype calls the tool directly without clipboard
- Add test_inject_text_ydotool_non_ascii_end_to_end covering the full
inject_text -> _inject_with_wayland_tool -> _inject_via_clipboard_paste
stack for accented text on Wayland+ydotool (fixes#362)
- Remove now-unnecessary @patch('time.sleep') decorators from three tests
Add a test for the for/else warning path in _setup_engine() introduced
in PR #361: when all 15 socket-readiness poll attempts exhaust without
SOCKET_PATH appearing, a WARNING is logged and activation is attempted
anyway. The new test mocks time.sleep to avoid real delays and asserts
the exact retry count (15) and log message.
Fixes Codecov patch-coverage gap reported on #361.
* fix(ibus): start engine process before checking registration
_setup_engine() gated on is_engine_registered() which queries
`ibus list-engine`. On systems where the IBus daemon doesn't scan
user-level component directories (~/.local/share/ibus/component/),
the engine never appeared in the list, blocking start_engine_process()
from ever being called — even though its register_component() D-Bus
call is the correct registration path for standalone-launched engines.
Reorder _setup_engine() to start the engine process first, relying on
register_component() for runtime registration. Remove the XML
install step from the setup path since install_ibus_component()
triggers `ibus restart` which kills the engine process.
Follow-up to PR #304 which added register_component() support.
* fix(ibus): remove dead code and add engine readiness check
Address review feedback:
- Remove install_ibus_component(), is_engine_registered(),
is_component_up_to_date(), _get_expected_component_xml()
and their tests — no longer called after setup order fix
- Add SOCKET_PATH poll after start_engine_process() to verify
engine is fully initialized before activation
- Rebase onto current main
* feat(web): add v0.10.1 updates and 5 new SEO pages
## New SEO Pages (5)
- /vs-nerd-dictation/ - Vocalinux vs Nerd Dictation comparison
- /whisper-model-guide/ - Whisper model sizes explained
- /voice-typing-vscode/ - How to dictate in VS Code on Linux
- /writers/ - Voice dictation for writers on Linux
- /gnome-kde/ - Voice typing on GNOME vs KDE Plasma
## Existing Page Updates
- homepage: Update JSON-LD featureList with v0.10.1 features, add new FAQs, link new pages in footer
- troubleshooting: Add suspend/resume recovery, tray resources, settings dialog fixes
- shortcuts: Add sound effects toggle, left/right modifier distinction, PTT improvements
- wayland: Add keyboard layout preservation, clipboard fallback note
- compare: Add engine switching safety notes
- faq: Add suspend/resume and keyboard layout FAQs
- alternatives: Link to new Nerd Dictation comparison
- seo-subpage-shell: Add new pages to navigation
- sitemap: Add 5 new URLs
Fixes#257 (SEO expansion)
* fix(web): add theme toggle to subpages, fix dark mode button contrast, simplify footer nav
- Add light/dark theme toggle to SeoSubpageShell header (all subpages)
- Fix duplicate useState/useEffect imports causing build error
- Fix unclosed div in header markup
- Add dark:text-black to CTA buttons across all 15 subpages for contrast on bright green bg
- Add dark:text-black to footer CTA button in SeoSubpageShell
- Simplify subpage footer to mainNavCategories (5 condensed categories) instead of full navCategories
* fix(web): fix list items stacking, add all new pages to nav, restructure footer
- Fix inline-flex → flex on all li elements across 13 pages so bullet points
always stack on separate lines instead of running together horizontally
- Add all 5 new SEO pages to subpage nav mainNavCategories (Whisper Models,
For Writers, VS Code, GNOME vs KDE under Features/Use Cases; new Compare
dropdown with vs Nerd Dictation, Alternatives, Offline, Open Source)
- Add /vs-nerd-dictation/ to homepage footer Learn column
- Restructure homepage footer from 2 crowded columns into 4 lean categorised
columns (Get Started, Learn, Use Cases, Support) — responsive 2-col on
mobile/tablet, 5-col on desktop
Fix two related IBus issues on KDE Plasma Wayland (and similar DEs):
1. IBus not detected without legacy environment variables:
On Wayland, IBus is configured via the DE's Virtual Keyboard setting
and IBus itself recommends unsetting GTK_IM_MODULE / QT_IM_MODULE.
The detection logic only checked these env vars, so it never found
IBus on a properly configured Wayland session.
Fix: When no IM env vars are set, check if ibus-daemon is running
and has an active engine. If another IM (e.g. Fcitx) is explicitly
configured via env vars, respect that and don't override.
2. IBus text injection silently fails on Wayland:
After switching to the Vocalinux IBus engine, the code called
setxkbmap to capture/restore the keyboard layout. On Wayland,
setxkbmap does not reflect the compositor's state and returns
incorrect results (e.g. 'us' when the user has 'de'). Calling
setxkbmap to restore this wrong layout interfered with IBus's
ability to commit text, causing silent injection failures.
Fix: Skip setxkbmap-based XKB layout management entirely on
Wayland sessions. The Wayland compositor manages keyboard layouts
natively, and setxkbmap can only cause harm there.
Fixes#380
Add cmake, libcairo2-dev, and dynamic libgirepository version detection
to fix installation failures on Pop!_OS (Cosmic DE) and newer Ubuntu.
Three issues addressed:
1. cmake: Required by pywhispercpp to build whisper.cpp from source.
Added to all distro package lists (apt, dnf, pacman, zypper, emerge,
apk, xbps, eopkg).
2. libcairo2-dev: Required to build pycairo. Was present in the Debian
package list but missing from the Ubuntu list, which Pop!_OS uses.
3. libgirepository version: Ubuntu 24.04+ and Pop!_OS Cosmic+ ship
libgirepository-2.0-dev instead of libgirepository1.0-dev. Added
runtime detection to select the correct package, mirroring the
existing version-aware handling already in place for Debian.
Reported-by: DavidHoenisch
Ref: https://github.com/jatinkrmalik/vocalinux/discussions/378
* fix(suspend): delay keyboard restart to 6s for USB re-enumeration
The 2s delay is enough for the speech engine, but USB devices
re-enumerate slowly after resume. At T+2s, stale /dev/input nodes
still exist and evdev opens them, but the kernel tears them down
at ~T+4s, causing another round of disconnections.
Split the reinit into two separate timers:
- 2s: speech engine reinit (unchanged)
- 6s: keyboard backend restart (after USB re-enumeration completes)
* fix(suspend): use inotify to detect device settle before keyboard restart
Replace hard-coded 6s keyboard restart delay with event-driven
Gio.File.monitor_directory() on /dev/input. After resume, a 2s settle
timer resets on each inotify event; keyboard shortcuts restart only once
devices stop changing. A 10s safety cap and a 6s fallback timer guard
against environments where inotify is unavailable.
After suspend/resume, evdev devices disconnect and the monitor thread
exits when all file descriptors close. The resume handler only
reinitialized the speech engine, leaving keyboard shortcuts dead.
Restart _setup_keyboard_shortcuts() in _reinit_after_resume() to
re-scan /dev/input and open fresh device file descriptors.
Copy-to-clipboard is now opt-in. Users can enable it in Settings under Text Injection. This avoids unexpected clipboard overwrites on fresh installs and reduces background wl-copy/xclip timeout warnings on Wayland where clipboard access from daemon threads can be unreliable.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(suspend): add D-Bus based suspend/resume handler
Monitors org.freedesktop.login1.Manager.PrepareForSleep via Gio.DBusProxy to detect system suspend/resume. On resume, invokes a callback to reinitialize the audio pipeline and speech model so dictation works immediately without manual intervention.
Fixes#367
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(suspend): wire suspend handler into tray indicator and recognition manager
TrayIndicator now creates a SuspendHandler on startup that listens for D-Bus PrepareForSleep signals. On suspend, active recognition is stopped. On resume, a 2-second delay allows audio hardware to stabilize before reinitializing the speech engine and resetting audio reconnection state.
Fixes#367
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(ptt): prevent premature transcription during push-to-talk silence
In push-to-talk mode, a brief silence while holding the hotkey would
trigger transcription mid-hold, injecting text before key release.
Add recognition_mode parameter to start_recognition() so the silence
timeout in _record_audio() defers enqueuing audio in PTT mode. Audio
accumulates in the buffer until key release calls stop_recognition(),
which already handles final buffer enqueue. Toggle mode is unchanged.
* fix(clipboard): run clipboard copy in background thread to prevent blocking
The _copy_to_clipboard calls (wl-copy, xclip, xsel) have 5s timeouts
each and run synchronously on the recognition thread. When they hang,
they block stop_recognition() from completing, delaying the state
transition to IDLE and preventing the next push-to-talk press.
Move clipboard copy to a daemon thread on the two success paths so it
never blocks transcription or state transitions. The failure-fallback
path stays synchronous since it needs the return value.
* fix(keyboard): preserve XKB layout when activating Vocalinux IBus engine
When Vocalinux starts, it may start or restart the IBus daemon, which
can override the user's system-level keyboard layout with IBus's
configured layout (often defaulting to US).
This fix captures the current XKB layout before activating the
Vocalinux IBus engine and restores it after activation, ensuring
users with non-US keyboard layouts don't lose their settings.
Fixes#292
* fix(keyboard): restore XKB layout after engine activation, not just on shutdown (#292)
The draft PR only restored the user's XKB layout when Vocalinux shut
down. The French AZERTY reporter confirmed the real problem: switching
to the Vocalinux IBus engine overrides the system keyboard layout
immediately, so typing 'a' produces 'q' (AZERTY mapped through US
QWERTY). The same applies to Spanish and other non-US layouts.
Changes:
- Restore XKB layout immediately after IBus engine activation in
_setup_engine(), not just in stop(). This preserves the user's
keyboard layout while Vocalinux is actively running.
- Remove the 'layout != us' guard in stop() -- always restore
whatever layout was captured, including US with custom options.
- Fix 4 broken tests: wrong expected options values, missing
TimeoutExpired argument, incorrect call count assertion.
- Add test cases for Spanish and French AZERTY layouts.
* test(keyboard): add coverage for XKB layout capture/restore in IBusTextInjector
Addresses Codecov report on PR #343 - adds tests covering:
- stop() restores captured XKB layout (Spanish with variant/options)
- stop() skips restore when no layout was captured
- _setup_engine() captures XKB layout and restores it after engine activation (French AZERTY)
* fix(test): mock start_engine_process in setup_engine XKB test
The test was missing a mock for start_engine_process, causing it to
fail on CI where the IBus engine PID file path doesn't exist.
Some window managers (on both X11 and Wayland) hide the title-bar close
button for Gtk.Dialog windows that have no action buttons. The previous
fix (PR #333) added set_decorated(True) which was not sufficient.
This adds an explicit Close action button to the dialog so users always
have a visible way to dismiss it, regardless of WM behavior. The
instant-apply pattern is preserved — settings still apply immediately
on change.
When switching from whisper.cpp to VOSK via the settings dialog, the
_auto_apply_settings path called reconfigure() without first stopping
active recognition. This allowed background audio/transcription threads
to continue using the old engine's native C resources (whisper.cpp model)
while they were being freed by Python's GC, causing a segmentation fault.
Changes:
- Add stop_recognition() guard in reconfigure() itself when the engine
or model changes and recognition is active — this makes the API safe
regardless of which caller triggers the reconfiguration
- Add defense-in-depth stop_recognition() call in _auto_apply_settings
to match the existing pattern in _apply_settings_internal
* fix: bundle resources in package to fix missing icons in system tray (fixes#349)
The resources directory (icons and sounds) was not included in pip-installed
packages because it lived outside the src/ package directory. This caused
the resource_manager to find an empty icon directory at runtime, resulting in
the ellipsis (three dots) icon in the GNOME system tray.
Changes:
- Copy resources into src/vocalinux/resources/ so setuptools bundles them
- Add 'vocalinux.resources/**/*' to package-data in pyproject.toml
- Add MANIFEST.in for sdist builds
- Update resource_manager to check package-bundled resources first
(module_dir.parent / 'resources') before checking external paths
- Add install_resources_to_venv() to install.sh for curl-based installs,
copying resources to /share/vocalinux/resources
This fixes both pip installs (resources bundled in the wheel) and curl-based
installs (resources copied to venv share directory).
* fix: ensure bundled resources are included in sdist and pip installs
- Add 'vocalinux.resources' to setuptools packages list so the
subpackage (and its __init__.py) is recognized by setuptools
- Add __init__.py to src/vocalinux/resources/ to make it a proper
Python subpackage for reliable discovery
- Add 'recursive-include src/vocalinux/resources *' to MANIFEST.in
so sdist archives include the bundled icons and sounds
Update release runbook steps to match current installer and workflow behavior, including main/install.sh usage, website lockfile/version checks, valid install sanity checks, and web build verification.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* chore(release): prepare v0.10.0-beta
Bump versions and release references across app, docs, installer, and website for the next beta release. Add v0.10.0-beta release notes entries covering reliability fixes and CI/test improvements since v0.9.0-beta.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* docs(release): align Python baseline references to 3.9+
Synchronize stale Python 3.8 references in agent guidance and distro dependency docs/scripts with the current 3.9+ minimum used by project metadata and release docs.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(keyboard): support AltGr (ISO_Level3_Shift) for Right Alt on int'l keyboards
On UK/International keyboards, Right Alt (AltGr) sends ISO_Level3_Shift
(keysym 0xfe03) instead of Alt_R (keysym 0xffea). The pynput backend
was only checking for keyboard.Key.alt_r, missing the alt_gr key that
pynput uses for ISO_Level3_Shift.
Changes:
- Add keyboard.Key.alt_gr to MODIFIER_NORMALIZE_MAP (normalizes to alt)
- Include alt_gr in the "alt" and "right_alt" key variants
- Update side-specific matching to check key variants instead of
exact key comparison
Fixes#322
* fix(keyboard): generalize pynput modifier alias matching
* test(keyboard): make variant tests independent of pynput install
When the kernel's evdev buffer overflows it sends SYN_DROPPED to signal
lost events. Without handling this, a missed key-release can leave
key_pressed_devices in a stale state, causing stuck push-to-talk.
Discard all events between SYN_DROPPED and the next SYN_REPORT, then
reset modifier key state for the affected device.
Refs: https://www.kernel.org/doc/html/latest/input/event-codes.html