Commit Graph
244 Commits
Author SHA1 Message Date
Graham JenkinsandGitHub b6fb2b838b fix(text-injection): use register_component for IBus engine activation (#304)
* fix(ibus): add register_component for standalone launch and --xml/--ibus flag handling (#303)

* refactor(ibus): consolidate engine metadata and add command_line/request_name checks (#303)
2026-03-17 18:11:31 -07:00
Jatin K MalikandGitHub f24674a4e8 docs: improve project description with narrative intro (#325)
* docs: improve project description with narrative intro

- Replace the one-liner README intro with a more compelling narrative
  that highlights key differentiators: offline, open source, multi-engine,
  GPU-accelerated, and privacy-first
- Update version.py __description__ to a more descriptive one-liner

* docs: replace all em-dashes with hyphens/commas/colons

Remove all em-dash (—) characters from README.md, replacing them
with contextually appropriate alternatives (hyphens, commas, colons).
2026-03-16 15:44:44 -07:00
Jatin K MalikandGitHub 44af1703c6 fix(docs): point all install curl URLs to main/install.sh (#314)
The install.sh on main now dynamically resolves the latest release tag
via GitHub API (merged in PR #313), so pinning to a specific release tag
in install commands is no longer needed — and was causing users to run
the old hardcoded installer.

Update all curl install command URLs across docs, code, and website from
versioned tags (v0.9.0-beta, v0.8.0-beta) to main/install.sh:
- README.md (4 URLs + note text)
- docs/INSTALL.md (2 URLs + note text)
- docs/UPDATE.md (2 URLs)
- docs/DISTRO_COMPATIBILITY.md (1 URL)
- tests/distro-testing-checklist.md (1 URL)
- src/vocalinux/main.py (1 URL)
- web/src/app/page.tsx (remove dynamic release fetch, hardcode main)
2026-03-14 14:16:38 -07:00
Jatin K MalikandGitHub baeee19f17 chore(release): prepare v0.9.0-beta (#310)
* chore(release): prepare v0.9.0-beta

- Bump version from 0.8.0-beta to 0.9.0-beta
- Update README What's New section with new features and bug fixes
- Update all install script URLs to v0.9.0-beta
- Add v0.9.0-beta What's New section to docs/UPDATE.md
- Update SECURITY.md supported versions (0.9.x supported, 0.8.x dropped)
- Update website schema softwareVersion and nav badge to 0.9.0
- Update website featureList with new sound effects and clipboard features
- Add v0.9.0-beta entry to changelog page
- Update web/package.json version to 0.9.0
- Add v0.9.0-beta row to RELEASE_PROCESS.md version history

* docs(readme): add contributors avatar grid via contrib.rocks

* feat(about): add Changelog link to About dialog

* Revert "feat(about): add Changelog link to About dialog"

This reverts commit 15e0984f516caccb14a0609e96d5fb6870ea247e.
2026-03-14 13:53:22 -07:00
Jatin K MalikandGitHub e2fdf5ddf2 fix(clipboard): copy to clipboard after IBus text injection (#311)
The IBus injection path had an early return that skipped the clipboard
copy logic. Store the result, copy to clipboard if enabled, then return.

Also add xclip and wl-clipboard to install.sh for all supported distros
so clipboard tools are available out of the box.
2026-03-14 13:37:04 -07:00
18e9e0a966 feat(audio): add simple sound effects toggle (#309)
Add a single toggle switch to enable/disable sound effects in the Audio
settings. This provides a simpler alternative to full sound customization
while covering the most common use case.

- Add sound_effects.enabled config option (default: true)
- Add toggle switch in Audio settings tab
- Add helper methods to ConfigManager
- Update play_*_sound() to check enabled state
- Add tests for the new functionality

Closes #283

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-14 13:21:09 -07:00
Jatin K MalikandGitHub 8a4dbad4ee fix: improve Debian/pipx installation experience (#289)
* fix: improve Debian/pipx installation experience

- Enhanced error messages in check_dependencies() to provide distro-specific
  installation commands (Ubuntu/Debian, Fedora, Arch Linux)
- Added explicit pipx guidance: install system packages BEFORE pipx install
- Updated error message to use gir1.2-ayatanaappindicator3-0.1 for Debian
- Added comprehensive pipx installation section to DISTRO_COMPATIBILITY.md
  with step-by-step instructions for all major distributions

* fix: add display availability check to prevent GTK crash on Debian

The original PR only added documentation and improved error messages.
This commit adds the actual fix for issue #288.

Root cause: On some systems (especially Debian), GTK widgets can be
created before the display connection is properly established, causing
the crash: gtk_widget_get_scale_factor: assertion 'GTK_IS_WIDGET (widget)' failed

Fix: Add check_display_available() function that verifies Gdk.Display
is available before any GTK widgets are created. This provides a clear
error message instead of a cryptic GTK crash.

Fixes #288
2026-03-14 13:02:45 -07:00
Jatin K MalikandGitHub 73082fa393 fix: add clipboard fallback for Wayland compositors without virtual keyboard support (#299)
* fix: add clipboard fallback for Wayland compositors without virtual keyboard support

Fixes #297

On KDE Plasma Wayland, the compositor doesn't support the virtual keyboard
protocol that wtype requires. When both wtype and xdotool fallback fail,
text injection was completely broken.

Changes:
- Add _fallback_to_clipboard() method that tries wl-copy, xclip, and xsel
- When all injection methods fail, copy text to clipboard so user can paste
- Play success sound when clipboard fallback succeeds (text is recoverable)
- Improve error messages with KDE-specific guidance and ydotool instructions

For KDE Plasma Wayland users, the recommended solution is:
1. Install ydotool: sudo apt install ydotool
2. Enable daemon: sudo systemctl enable --now ydotoold

Alternatively, clipboard fallback will copy text for manual pasting.

* style: fix black formatting

* fix: wrap clipboard fallback in try/except to handle test mocks

* feat(clipboard): add configurable always-copy-to-clipboard feature

Enhanced the clipboard functionality beyond just a fallback mechanism:

- Renamed _fallback_to_clipboard to _copy_to_clipboard (more general)
- Added _should_copy_to_clipboard() to read user preference from config
- After successful text injection, copies to clipboard if setting enabled
- Still falls back to clipboard on injection failure regardless of setting
- Added 'copy_to_clipboard' setting to config (default: true)
- Added toggle switch in Settings > General for user control

This provides better UX by:
1. Allowing users to reference pasted text later via clipboard history
2. Providing a backup when injection partially works
3. Enabling easy pasting into apps where injection might not work

Fixes #297

* feat(clipboard): show notification when text copied as fallback

When text injection fails but clipboard succeeds, show a desktop
notification telling the user to paste with Ctrl+V. This ensures
users know their text was saved and can be recovered.
2026-03-14 12:53:16 -07:00
Jatin K MalikandGitHub e35bdb2f62 fix(dictation): remove unwanted leading space when starting new transcription (#307)
Reset last_injected_text when recognition transitions to LISTENING state,
ensuring the first dictation segment in each session is not prefixed with
a space. Also simplify the space-prepending logic: always add a separator
space between consecutive segments (not just after punctuation), and strip
incoming text as a safety net against whisper tokenizer artifacts.
2026-03-14 12:40:58 -07:00
Jatin K MalikandGitHub 80108c3456 feat(shortcuts): add left/right modifier key distinction for keyboard shortcuts (#308)
Allow users to bind voice commands to specific modifier key sides (e.g.,
Right Ctrl only), preventing conflicts with common left-side shortcuts
like Alt+Tab or Ctrl+C.

- Expand SUPPORTED_SHORTCUTS from 3 to 9 entries (either/left/right)
- Add SHORTCUT_GROUPS for grouped display in settings UI
- Implement side-aware key matching in pynput and evdev backends
- Update settings dialog with grouped shortcut selector
- Add comprehensive tests for side-specific shortcuts

Closes #306
2026-03-14 12:37:51 -07:00
Jatin K MalikandGitHub c7e56e4c82 fix(shortcuts): pass configured mode to KeyboardShortcutManager on startup (#305)
On app restart, the shortcut mode (toggle vs push-to-talk) was not being
passed to KeyboardShortcutManager during initialization in TrayIndicator.
The backend always defaulted to 'toggle' mode regardless of user config,
causing push-to-talk shortcuts to not bind correctly on startup.

Fixes #296
2026-03-14 12:19:00 -07:00
Jatin K MalikandGitHub 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
2026-02-28 20:58:55 -08:00
Jatin K MalikandGitHub 86ca22ff5c feat(shortcuts): add push-to-talk mode and improve shortcut reliability (#278)
* feat(shortcuts): add push-to-talk mode as alternative to toggle

This commit implements push-to-talk functionality (Issue #252) as an
alternative to the existing double-tap toggle mode:

- Add keyboard shortcut mode setting (toggle vs push_to_talk)
- In push-to-talk mode: hold key to speak, release to stop
- Update both pynput and evdev backends to support press/release callbacks
- Add UI controls in settings dialog for mode selection
- Maintain backward compatibility with existing toggle mode as default

Closes #252
Related: Closes #185 (superseded by push-to-talk approach)

* fix(shortcuts): remove duplicate code blocks that broke settings dialog

The previous push-to-talk implementation had duplicate code blocks
in tray_indicator.py and settings_dialog.py that caused the settings
dialog to fail silently. This commit cleans up the duplicate code
while preserving the push-to-talk functionality.

Fixes issue where settings dialog would not open.

* fix(ui): add set_subtitle method to PreferenceRow class

The PreferenceRow class was missing a set_subtitle method, which is called by
_update_shortcut_ui_for_mode when switching between toggle and push-to-talk
modes. This caused an AttributeError that silently crashed the settings dialog
during initialization.

Also stores subtitle_label as instance variable for later updates.

* fix(keyboard): remove duplicate code and fix mode handling

Critical fixes for keyboard backends:
- evdev_backend.py: Removed duplicate __init__ and _handle_key_event methods
- pynput_backend.py: Removed duplicate __init__, _on_press, _on_release methods
- Both backends now properly handle toggle and push-to-talk modes

Fixed first_run_dialog.py:
- Added do_response() override to properly close dialog
- Added explicit destroy() call in show_first_run_dialog()

Fixed tray_indicator.py:
- Added _unregister_keyboard_callbacks() method
- Updated _setup_keyboard_shortcuts() to properly clean up old callbacks
- Updated update_shortcut() to properly handle mode switching

Fixed settings_dialog.py:
- Added set_subtitle() method to PreferenceRow class
- Updated UI text for push-to-talk mode
- Fixed _on_shortcut_changed to properly pass mode to callback

All 547 tests pass locally.

* feat(shortcuts): improve mode switching and add mode-aware display names

Key improvements:
- Add SHORTCUT_MODE_DISPLAY_NAMES for mode-specific shortcut names
- Add get_shortcut_display_name() function for mode-aware display
- Fix restart_with_shortcut() to properly save and restore all callbacks
- Fix update_shortcut() to properly handle mode changes
- Fix _setup_keyboard_shortcuts() to clear old callbacks before registering new ones
- Ensure only one type of callback is active at a time

This fixes issues where:
- Old callbacks remained registered after mode switch
- Single-tap triggered action after switching from push-to-talk to toggle
- Display names weren't appropriate for the selected mode

* fix(shortcuts): stabilize live mode switching callback lifecycle

* test(shortcuts): cover callback reset on mode transitions

* fix(shortcuts): remove super preset and migrate legacy config

* test(shortcuts): update coverage for super preset removal
2026-02-28 20:40:32 -08:00
Jatin K MalikandGitHub bcd6d94eea fix(text-injection): detect if IBus is the active input method before using it (#277)
* fix(text-injection): detect if IBus is the active input method before using it

This commit fixes issue #249 where Vocalinux incorrectly tries to use
IBus for text injection when IBus is installed but not configured as
the active input method (e.g., user uses ydotool on Arch + KDE Plasma 6).

Changes:
- Add is_ibus_active_input_method() function that checks GTK_IM_MODULE,
  QT_IM_MODULE, and XMODIFIERS environment variables to determine if
  IBus is the active input method
- Update _check_dependencies() to skip IBus when it's not the active
  input method, falling back to ydotool/wtype/xdotool instead
- Add comprehensive unit tests for the new functionality

The fix ensures Vocalinux will use the appropriate text injection
method based on the user's actual input method configuration,
rather than just checking if IBus is installed.

Fixes #249

* test: fix existing tests to mock is_ibus_active_input_method

* test: add comprehensive test coverage for is_ibus_active_input_method

Add additional tests to ensure complete coverage of the new functionality:

- Edge cases for empty string env vars
- Case-insensitive detection for all three env vars
- Priority ordering (GTK > Qt > XMODIFIERS)
- X11 fallback when IBus is not active input method
- Exception handling during IBus setup falls back correctly

Total tests: 62 (was 54), all passing.
2026-02-28 17:08:47 -08:00
Jatin K MalikandGitHub 0b044cff6b fix(audio): detect and use device-supported channel count (#275)
* fix(audio): detect and use device-supported channel count

This fixes the "Invalid number of channels" (PyAudio error -9998) that
occurs with certain audio devices (e.g., HD-Audio Generic interfaces) that
require stereo (2 channel) input instead of mono (1 channel).

Changes:
- Add _get_supported_channels() helper function to detect supported channel
  configuration by testing mono first, then falling back to stereo
- Update test_audio_input() to detect channels before opening stream
- Update _record_audio() to detect channels and convert stereo to mono
- Update _attempt_audio_reconnection() to use detected channel count
- Add stereo-to-mono conversion in audio processing loop to ensure speech
  recognition engines receive mono audio as expected

Fixes issues introduced by voice commands feature where audio settings test
would fail with "no speech detected" because the audio stream couldn't be
opened due to channel mismatch.

Closes audio device compatibility issues

* fix(settings): delay callback restoration to prevent race condition in test

The recognition test was restoring text callbacks immediately after
stop_recognition(), but the recognition thread has a 1-second timeout.
If whisper.cpp transcription took longer than 1 second, the callbacks
would be restored before transcription completed, causing the text to
be sent to the wrong callback (the main app callback instead of the
test callback).

Changes:
- Add 500ms delay before restoring callbacks to allow transcription to complete
- Add new _restore_callbacks_and_check_result() helper method
- Increase result check delay from 200ms to 300ms for more reliable detection

This fixes the "No speech detected during test" issue where audio was
being recorded and transcribed, but the result wasn't being captured by
the test callback.

* debug: add diagnostic logging to trace recognition test flow

* fix(recognition): prevent race condition between audio enqueue and recognition thread

- Increase audio thread join timeout from 0.1s to 2.0s to ensure audio thread fully exits
- Remove redundant second audio thread join
- Add comprehensive debug logging to trace recognition thread lifecycle
- Add logging for final buffer enqueue to diagnose race conditions

The race condition occurred because:
1. should_record=False was set
2. Recognition thread could check queue.empty() before audio was enqueued
3. Recognition thread would exit before processing the final audio segment

This fix ensures the audio thread completes before we process the final buffer.

* fix(recognition): use poison pill pattern to prevent premature thread exit

The recognition thread was exiting immediately when should_record=False
and the queue was empty, even if audio was about to be enqueued.

Changes:
- Recognition loop now runs until it receives None signal (poison pill)
- When queue times out and should_record=False, waits 0.5s for final items
- When None is received, drains all remaining queue items before exiting
- Increased recognition thread join timeout from 1s to 5s for transcription

This ensures the recognition thread stays alive long enough to process
the final audio buffer that is enqueued after should_record is set to False.
2026-02-28 16:19:08 -08:00
Jatin K MalikandGitHub 6fc1401ae0 feat(commands): make voice commands optional with auto-enable for VOSK (#274)
* feat(commands): make voice commands optional with auto-enable for VOSK

This implements a new voice commands feature that:
- Adds 'voice_commands_enabled' config option (None=auto, True, False)
- Auto-enables voice commands for VOSK engine (which needs them for punctuation)
- Auto-disables for Whisper engines (which handle punctuation natively)
- Adds a toggle in Settings > Recognition tab to override the default

The toggle shows the current auto-detected state and allows users to
enable/disable voice commands regardless of engine choice.

This addresses issue #147 discussion about voice commands utility with
modern Whisper models that handle punctuation well.

Closes #270 (by providing an alternative solution - disabling commands)

* fix(tests): add voice_commands_enabled parameter to test expectations

Update test assertions to include the new voice_commands_enabled
parameter that was added to SpeechRecognitionManager.

* style(tests): fix black formatting in test_speech_recognition.py

* fix(commands): keep voice command mode in sync on reconfigure
2026-02-28 13:06:05 -08:00
Jatin K MalikandGitHub 18c099b5bb fix: prevent GTK widget crash on Fedora startup dialog (#268)
The first-run dialog was crashing on Fedora/Wayland when clicking any
button with the error: gtk_widget_get_scale_factor: assertion
'GTK_IS_WIDGET (widget)' failed.

Root cause: Direct destroy() calls from button click handlers caused
race conditions where GTK tried to access widget properties after the
widget was marked for destruction but before event processing completed.

Fix: Use GTK's proper dialog response signal pattern instead of direct
destroy() calls. Buttons are now added with response IDs, and the
response signal handler sets the result. This lets GTK manage the
dialog lifecycle properly and prevents the race condition.

Fixes #267
2026-02-28 00:40:10 -08:00
Jatin K MalikandGitHub cb9b66adf3 fix: resolve text injection issues from issue #34 (#261)
* fix(text_injection): re-check for better tools after xdotool fallback

When wtype fails due to compositor not supporting virtual keyboard protocol,
the code switches to xdotool permanently. This prevents using ydotool
even if its daemon is started later.

This fix adds _try_recover_from_fallback() method that checks for better
tools (ydotool with running daemon, or wtype with compositor support)
on each text injection attempt when in fallback mode.

Fixes issue #34 comment 3 (logs show xdotool but ydotool works)

* feat(main): auto-start IBus daemon on Vocalinux launch

This resolves the IBus autostart issue mentioned in issue #34.

When Vocalinux starts, it now automatically starts the IBus daemon if:
- IBus is installed on the system
- IBus daemon is not already running

This removes the need for users to manually configure IBus autostart
on desktop environments where it doesn't start automatically (e.g.,
some KDE Plasma installations or minimal window managers).

Changes:
- Add start_ibus_daemon() function in ibus_engine.py
- Call start_ibus_daemon() in main() after logging is initialized
- Export start_ibus_daemon in text_injection/__init__.py
2026-02-25 11:56:50 -08:00
Jatin K MalikandGitHub a525f3c4a4 fix: prevent state flicker in recognition thread (#259)
- only transition to LISTening if still recording and currently in processing state
- Cleaner state management simplifies thread safety logic
- Fixes #258: high CPU behavior during prolonged sessions
2026-02-25 00:37:36 -08:00
Jatin K MalikandGitHub 60989b3ee1 fix: auto-detect audio sample rate for device compatibility (#262) (#263)
* fix: auto-detect audio sample rate for device compatibility (#262)

- Add _get_supported_sample_rate() helper function
- Tests device default rate first, then common rates (48k, 44.1k, 32k, 22k, 16k, 8k)
- Fixes 'Invalid sample rate' error for devices like Vocaster One (48kHz only)
- Update test_audio_input(), _record_audio(), and _attempt_audio_reconnection()
- Return detected sample_rate in test_audio_input() result dict

* fix: import pyaudio inside _get_supported_sample_rate

* fix: resample audio to 16kHz for Vosk/Whisper compatibility

- Store detected sample rate in self._capture_sample_rate
- Resample audio chunks to 16kHz before appending to buffer (linear interpolation)
- Initialize _capture_sample_rate to 16000 in __init__
- Update both _record_audio() and _attempt_audio_reconnection()

This ensures devices like Vocaster One (48kHz only) can capture audio
and speech recognition engines (Vosk/Whisper) receive 16kHz as expected.

* style: fix black formatting

* feat(ui): show sample rate info in audio test results

- Display detected sample rate in test results (e.g., '48kHz → 16kHz auto')
- Remove redundant numpy import in _record_audio function
- Helps users understand that non-16kHz devices are supported
2026-02-25 00:01:44 -08:00
Jatin K MalikandGitHub 6e02e928ae chore(release): prepare v0.7.0-beta (#255)
* chore(release): prepare v0.7.0-beta

## Version Bump (v0.6.3-beta → v0.7.0-beta)

This is a major beta release with significant new features including autostart support, tabbed settings, and improved GPU detection.

### Core Version Files
- Updated src/vocalinux/version.py to 0.7.0-beta
- Updated pyproject.toml classifiers (already correct - Beta)

### Documentation
- Updated README.md with new features, bug fixes, and version references
- Updated docs/INSTALL.md with v0.7.0-beta install commands
- Updated docs/UPDATE.md with comprehensive 'What's New' section
- Updated SECURITY.md supported versions table (0.7.x now supported)

### Website
- Updated web/src/app/page.tsx version badge (v0.7.0 Beta)
- Updated web/src/app/page.tsx softwareVersion schema
- Updated web/package.json version to 0.7.0
- Updated web/src/app/changelog/page.tsx with v0.7.0-beta release entry

### Other Updates
- Updated install.sh default tag to v0.7.0-beta
- Updated src/vocalinux/main.py error message install command
- Updated AGENTS.md branch naming example

## Changes Since v0.6.3-beta

### New Features (13 commits)
- Autostart on login support via XDG autostart
- Tabbed settings dialog with 5 organized tabs
- Intel GPU compatibility detection with automatic CPU fallback
- Single instance prevention
- Evdev device management to prevent CPU spin
- Improved GPU detection avoiding false positives
- IBus fallback when daemon not running
- Fedora dnf check-update fix
- Web SEO - 7 new optimized pages

### Contributors
- Jatin K Malik (maintainer)
- Antoine Monnet (contributor)
- jatinkrmalik (contributor)

### Bug Fixes
- #254: Improved GPU detection to avoid false positives
- #251: Skip IBus setup when daemon not running
- #243: Prevent dnf check-update from exiting script on Fedora
- #240: Raise exception on IBus setup failure
- #234: Removed leading space from first speech transcription
- #242/#253: Removed disconnected evdev devices to prevent CPU spin

## Verification
- [x] Lint passed
- [x] Black formatting passed
- [x] isort check passed
- [x] Version imports correctly as 0.7.0-beta

* docs: update version history in RELEASE_PROCESS.md

Add entries for 0.6.0-beta through 0.7.0-beta to the version history table.
2026-02-22 21:30:05 -08:00
Jatin K MalikandGitHub 58b635b8b9 feat: detect incompatible Intel GPUs and fallback to CPU (#239)
* feat: detect incompatible Intel GPUs and fallback to CPU

Implements two-layer protection for users with older Intel GPUs that lack
VK_KHR_16bit_storage support required by whisper.cpp Vulkan backend.

Affected GPUs: Intel Gen7 and older (Ivy Bridge, Haswell, Sandy Bridge)
- Intel HD Graphics 2500/4000 (Ivy Bridge)
- Intel HD Graphics 4400/4600 (Haswell)

Layer 1: Install-Time Detection (Prevention)
- New check_vulkan_gpu_compatibility() function in install.sh
- Parses GPU name from vulkaninfo output
- Checks against known incompatible patterns
- Shows clear warning and recommends CPU backend
- Updates detect_whispercpp_backends() to skip Vulkan for incompatible GPUs

Layer 2: Runtime Fallback (Safety Net)
- Added error handling in recognition_manager.py _init_whispercpp()
- Catches RuntimeError with "16-bit storage" or "Unsupported device"
- Automatically disables Vulkan/CUDA and retries with CPU
- Shows desktop notification explaining the fallback

Fixes #238

* fix(gpu): improve compatibility detection and add tests
2026-02-22 20:29:06 -08:00
7656447995 fix(evdev): remove disconnected devices to prevent CPU spin (#242) (#253)
When an input device is disconnected, select() reports the fd as
readable but read() fails with ENODEV. Previously, the code caught
the error but kept the dead fd in the list, causing an infinite
busy loop that consumed 100% CPU.

Now disconnected devices are removed from both self.devices and
self.device_fds when read() fails, preventing the busy loop.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Antoine Monnet <39551484+AntoineMontane@users.noreply.github.com>
Co-authored-by: AntoineMontane <monnet.antoine@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-22 20:18:12 -08:00
068240e7be fix(evdev): remove disconnected devices to prevent CPU spin (#242)
When an input device is disconnected, select() reports the fd as
readable but read() fails with ENODEV. Previously, the code caught
the error but kept the dead fd in the list, causing an infinite
busy loop that consumed 100% CPU.

Now disconnected devices are removed from both self.devices and
self.device_fds when read() fails, preventing the busy loop.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: AntoineMontane <monnet.antoine@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-22 19:59:49 -08:00
0c5e641060 fix(ibus): skip IBus setup when daemon not running (#251)
On desktop environments like KDE Plasma, the IBus daemon is not started
by default. Previously, Vocalinux would attempt to register its IBus
engine anyway, which would fail and (before #240) not properly fall back
to alternatives.

This change:
- Adds is_ibus_daemon_running() to check if ibus-daemon is running
- Skips IBus setup entirely when daemon isn't running
- Logs an informative message explaining why IBus is skipped
- Falls back to ydotool/xdotool immediately (no wasted time)

Fixes #250

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: AntoineMontane <monnet.antoine@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-22 19:54:32 -08:00
Jatin K MalikandGitHub c8142151eb refactor(ui): convert settings dialog to tabbed layout (#246)
* refactor(ui): convert settings dialog to tabbed layout

Convert the settings dialog from a long scrolling list to a tab-based UI
using Gtk.Notebook. This follows GNOME HIG best practices for settings
panels with 3-5 sections.

Changes:
- Add notebook CSS styling for modern tab appearance
- Create 4 tab content boxes (Recognition, Audio, Shortcuts, General)
- Replace ScrolledWindow with Gtk.Notebook
- Reorganize settings sections into appropriate tabs:
  - Recognition: Engine, Model, Language, VAD, Silence, Test
  - Audio: Device selection, Audio level test
  - Shortcuts: Keyboard shortcuts
  - General: Autostart, Start minimized
- Widen dialog to 700px to accommodate tab content

This eliminates vertical scrolling and provides a cleaner UX with
logical grouping of related settings.

* refactor(settings): split recognition tab into Speech Engine and Recognition tabs

- Reordered tabs by importance: Speech Engine, Recognition, Audio, Shortcuts, General
- Split the Recognition tab into two separate tabs:
  - Speech Engine tab: Engine, Model Size, Language, Model Info
  - Recognition tab: VAD Sensitivity, Silence Timeout, Test Recognition, Recognition Status
- This reduces vertical height of each tab and improves usability

* fix(ui): reduce settings dialog height to 50% of screen

Previously set to 75% which made the dialog unnecessarily tall.

* fix(ui): remove redundant close button from settings dialog

Settings auto-apply, so the bottom close button is unnecessary.
Users can close using the title bar X button.

* test(settings): update test for no-action-buttons pattern

The settings dialog now uses title bar close instead of a bottom
Close button, so update the test accordingly.
2026-02-22 19:52:16 -08:00
Jatin K MalikandGitHub b0565b36ad fix: prevent multiple instances from running simultaneously (#245)
* fix: prevent multiple instances from running simultaneously

Add single-instance enforcement using file locking to prevent duplicate
instances when auto-start is enabled on boot (issue #244).

Changes:
- Add single_instance.py module with fcntl-based file locking
- Check for existing instance early in main() before any initialization
- Register atexit handler to release lock on normal exit
- Lock file located at ~/.local/share/vocalinux/instance.lock

If another instance is already running, the app exits gracefully with
a helpful error message telling the user how to resolve the situation.

* fix: add GTK notification when duplicate instance detected

Show a system notification to the user when another instance is already
running, in addition to the existing stderr/log output.

Changes:
- Import Notify from gi.repository when duplicate instance detected
- Show notification with error icon and message
- Give notification time to display before exiting (0.5s sleep)
- Fallback to silent exit if notification fails (e.g., headless)
2026-02-21 15:35:52 -08:00
Jatin K MalikandGitHub 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.
2026-02-21 15:06:20 -08:00
59455098d3 fix(ibus): raise exception on setup failure to enable fallback (#240)
* fix(ibus): raise exception on setup failure to enable fallback

- Add IBusSetupError exception class for detailed error context
- Modify _setup_engine() to raise exception instead of silent return
- Ensures text_injector.py fallback mechanism works when IBus fails
- Add unit tests for IBusSetupError and setup failure scenarios

Fixes silent IBus text injection failures on Wayland/Fedora reported in #34

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(text-injector): add tests for IBus setup failure fallback

- Test that TextInjector falls back to xdotool when IBus fails
- Test fallback on both X11 and Wayland environments
- Test that text injection still works after IBus failure

Related to #34

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-21 00:39:19 -08:00
Jatin K MalikandGitHub d7d91a7db2 chore(release): prepare v0.6.3-beta (#235)
* chore(release): prepare v0.6.3-beta

- Fix #220: Installer default tag now points to correct version
- Update version from 0.6.2-beta to 0.6.3-beta
- Update documentation version references
- Update website schema and badges
- Add v0.6.3-beta changelog entry

The v0.6.2-beta installer was defaulting to v0.6.1-beta which is
missing the psutil dependency, causing fresh installs to fail.

* docs(update): simplify update guide, remove duplicate version history
2026-02-18 23:07:00 -08:00
Jatin K MalikandGitHub 38bacabee0 fix(command-processor): remove leading space from first speech (#234)
The command processor was adding a leading space when processing action
commands with text after them (e.g., 'delete that text' -> ' text').

This was causing the first speech to always have an unwanted leading space.

Changes:
- Modified command_processor.py to only add space when there's text BEFORE
the action command, not at the beginning of the transcription
- Updated test cases to reflect the correct behavior without leading spaces

Fixes leading space issue on first speech.
2026-02-18 22:48:12 -08:00
Jatin K MalikandGitHub 9292431452 fix(whisper.cpp): properly suppress [BLANK_AUDIO] tokens (#229)
The previous fix used `suppress_non_speech_tokens` which doesn't exist
in pywhispercpp's C++ bindings (whisper.cpp uses `suppress_nst` but it's
not exposed). This caused initialization error:
"_pywhispercpp.whisper_full_params object has no attribute suppress_non_speech_tokens"

Changes:
- Remove invalid `suppress_non_speech_tokens` parameter
- Add `no_speech_thold=0.6` and `entropy_thold=2.4` for better filtering
- Add `_filter_non_speech()` post-processing function that filters:
  - [BLANK_AUDIO] and other bracketed tokens
  - Music notes (♪♫♬♩♭♮♯)
  - Pure punctuation sequences
  - Low speech-content text (<30% alphanumeric)

Fixes #219
2026-02-17 23:49:00 -08:00
Jatin K MalikandGitHub 79c3ca7f88 feat(text-injection): extend IBus support to X11 for non-US keyboard layouts (#227)
IBus sends Unicode directly via commit_text(), bypassing keyboard layout
issues entirely. This provides the same reliable text injection on X11
that we already have on Wayland.

Changes:
- Add X11_IBUS desktop environment enum
- Prefer IBus on both X11 and Wayland (before falling back to xdotool)
- Add tests for X11 IBus preference and fallback behavior

This supersedes the layout-switching approach in PR #206, which only
worked on X11 and had overhead from layout switching.

Closes #199
2026-02-17 22:40:20 -08:00
Jatin K MalikandGitHub 13991e16e6 fix(recognition): add thread safety for model access to prevent crash (#216)
Fixes #215

The crash (segfault at ffffffff in libstdc++.so.6) was caused by a race
condition between the transcription thread and model reconfiguration:

1. When recording finishes, `_transcribe_with_whispercpp()` accesses
   `self.model.transcribe()` which is a C++ object via pywhispercpp
2. If settings change simultaneously, `reconfigure()` sets `self.model = None`
3. The transcription thread then accesses null/freed memory → segfault

This fix adds a `_model_lock` to protect concurrent access to:
- `self.model` (Whisper and whisper.cpp engines)
- `self.recognizer` (VOSK engine)

The lock is now used in:
- `_transcribe_with_whisper()` - protects model.transcribe()
- `_transcribe_with_whispercpp()` - protects model.transcribe()
- `_process_final_buffer()` - protects recognizer for VOSK
- `reconfigure()` - protects model reinitialization

Each transcription method also checks if model/recognizer is None before
proceeding, returning an empty result gracefully if reconfiguration is in
progress.
2026-02-17 22:30:30 -08:00
a1725f7cfc feat(text-injection): add IBus engine for Wayland text injection (#211)
* feat(text-injection): add IBus engine for Wayland text injection

Add a custom IBus input method engine for reliable text injection on
Wayland, solving keyboard layout issues with xdotool/wtype.

Key features:
- IBus proxy engine that passes through normal keyboard input
- Text injection via Unix socket IPC
- User-level component installation (no sudo required)
- Auto-reactivation when user switches keyboard layouts
- Graceful fallback when IBus is unavailable

Changes:
- New ibus_engine.py with VocalinuxEngine and IBusTextInjector
- TextInjector now prefers IBus on Wayland for best compatibility
- Add IBus GObject bindings to system dependencies in install.sh
- Add comprehensive tests for IBus engine

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* security: secure IBus socket with permissions and peer verification

- Set directory to 0o700 (owner-only access)
- Set socket to 0o600 (owner-only read/write)
- Add SO_PEERCRED verification to reject connections from other users

This provides defense-in-depth: even if directory permissions are
bypassed, the socket server verifies the connecting process UID.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address PR review feedback for IBus text injection

- Fix infinite loop in socket server by adding _server_running flag
- Use sys.executable instead of hardcoded python3 for venv compatibility
  - Also inherit environment (env=os.environ.copy()) for venv deps
  - Fix component XML to use sys.executable in <exec> tag
- Replace unsafe pgrep with PID file tracking and /proc verification
- Integrate TextInjector.stop() in TrayIndicator._quit() for cleanup
- Remove unused is_engine_registered import
- Fix redundant hasattr check to use proper None comparison
- Auto-detect and update stale IBus component XML on startup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: AntoineMontane <monnet.antoine@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-17 22:15:06 -08:00
Jatin K MalikandGitHub 2acf6899bd chore(release): prepare v0.6.2-beta (#224)
* chore(release): prepare v0.6.2-beta

- Update version from 0.6.1-beta to 0.6.2-beta
- Update documentation version references
- Update website schema and badges

Changes since v0.6.1-beta:
- Fix #221: Add missing psutil dependency
- Fix #219: Suppress [BLANK_AUDIO] tokens in whisper.cpp output
- Fix #204: Resolve PyAudio paInt16 attribute error
- Fix #205: Ensure whisper module installed with --auto
- Improve Vulkan shader package detection for multiple distros
- CI: Fix nightly build tag cleanup

* docs(agents): update AGENTS.md with current project structure

- Update speech recognition engines list (whisper.cpp is now default)
- Add key dependencies section
- Update project structure with whispercpp_model_info.py and vosk_model_info.py
- Add release process quick summary

* docs(agents): add important rules about PR workflow

- Never push directly to main
- All changes require a PR
- Wait for CI before merging
- Squash merge PRs
2026-02-17 21:48:24 -08:00
Jatin K MalikandGitHub d3f67642c2 Fix #219: Suppress [BLANK_AUDIO] tokens in whisper.cpp output (#222)
- Add suppress_blank=True and suppress_non_speech_tokens=True parameters
  to whisper.cpp Model initialization to prevent blank audio tokens
  from being output when transcribing silent audio
- This implements Option 2 from the investigation: suppress at the
  whisper.cpp level rather than post-processing filtering
- Root cause: whisper.cpp outputs a special [BLANK_AUDIO] token
  when transcribing silent audio, which was being inserted into the
  text field when users paused with the mic on
2026-02-17 17:20:36 -08:00
Jatin K MalikandGitHub 8b4a1b2f50 fix(audio): resolve PyAudio paInt16 attribute error on reconnection (#204)
The _attempt_audio_reconnection method was incorrectly accessing
paInt16 as an attribute of the PyAudio instance instead of the
pyaudio module. Added local import and fixed the reference.

Fixes #203
2026-02-12 17:46:51 -08:00
Jatin K MalikandGitHub 1e0a2a6cee chore(release): prepare v0.6.1-beta (#201)
Bump version from 0.6.0-beta to 0.6.1-beta with installation improvements:

Version Updates:
- src/vocalinux/version.py: 0.6.0-beta → 0.6.1-beta
- install.sh: Default tag and help text updated
- web/package.json: 0.6.0 → 0.6.1
- web/src/app/layout.tsx: schema.org softwareVersion updated
- web/src/app/page.tsx: version badge updated

Documentation Updates:
- README.md: New 'What's New' section for v0.6.1-beta
- docs/UPDATE.md: Added v0.6.1-beta release notes with new features
- docs/INSTALL.md: Version references updated
- docs/RELEASE_PROCESS.md: Example commands updated
- tests/distro-testing-checklist.md: Version references updated
- src/vocalinux/main.py: Error message updated

New Features in v0.6.1-beta:
- Interactive backend selection for whisper.cpp (GPU vs CPU)
- Enhanced welcome message with testing instructions
- Simplified install commands (tag-based URLs)
- Better GPU support with proper library detection

Bug Fixes:
- Fixed bash arithmetic with set -e
- Fixed TTY detection for interactive mode
- Auto-install git if missing
- Fixed GPU installation commands
2026-02-12 13:12:21 -08:00
Jatin K MalikandGitHub 86ee79cc0d chore: update install commands to use tag-based URLs (#200)
Changed install commands to download install.sh directly from the release
tag instead of from main branch with --tag parameter. This ensures:

1. The install script version matches the release being installed
2. No version mismatch between script and code
3. Simpler, cleaner install commands

Changes:
- install.sh: Updated error message to use v0.6.0-beta URL
- README.md: All install commands now use v0.6.0-beta/install.sh
- docs/INSTALL.md: Updated quick install command
- docs/UPDATE.md: Updated update command examples
- docs/RELEASE_PROCESS.md: Updated example command
- src/vocalinux/main.py: Updated error message install command
- tests/distro-testing-checklist.md: Updated download URL
- web/src/app/page.tsx: Now uses dynamic tag in URL (not main)
- .github/workflows/release.yml: Release notes use tag-based URL

Before:
  curl .../main/install.sh | bash -s -- --tag=v0.6.0-beta

After:
  curl .../v0.6.0-beta/install.sh | bash

Fixes version consistency between install script and installed code.
2026-02-12 13:02:51 -08:00
Jatin K MalikandGitHub 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
2026-02-11 20:33:28 -08:00
Jatin K MalikandGitHub 57a0ddd0b8 feat(speech): add whisper.cpp with Vulkan support for GPU acceleration (#179)
* feat(speech): add whisper.cpp with Vulkan support for GPU acceleration

Add whisper.cpp as a new speech recognition engine with the following features:
- Vulkan GPU acceleration support (AMD, Intel, NVIDIA)
- Fallback to CUDA for NVIDIA GPUs
- CPU-only mode with automatic thread optimization
- Support for all model sizes: tiny, base, small, medium, large
- Hardware detection to recommend best model/backend
- Models downloaded from Hugging Face (ggml format)
- Updated UI to show compute backend info in settings
- pywhispercpp as the Python binding library
- whisper_cpp as the new default engine

Changes:
- New whispercpp_model_info.py with model metadata and hardware detection
- Updated recognition_manager.py with whisper.cpp init and transcription
- Updated settings_dialog.py with whisper.cpp UI support
- Updated config_manager.py with whisper_cpp_model_size config
- Updated main.py with whisper_cpp CLI option
- Updated pyproject.toml with pywhispercpp dependency
- Updated install.sh with vulkan-tools for GPU detection

Closes #104

* style: fix black and isort formatting issues

* test: update config manager test for whisper_cpp default engine

* feat(install): interactive mode with 3 engine choices and whisper.cpp as default

Major changes to install.sh:
- Interactive mode is now the DEFAULT (not --auto)
- Added --auto flag for non-interactive automatic installation
- New --engine flag to specify engine directly
- Interactive mode shows 3 engine options with visual boxes:
  1. whisper.cpp (recommended) - Vulkan/CUDA/CPU support
  2. whisper (OpenAI) - PyTorch-based, NVIDIA-only
  3. VOSK - lightweight option
- Hardware detection with recommendations:
  - Detects Vulkan support (AMD/Intel/NVIDIA)
  - Detects NVIDIA CUDA
  - Checks available RAM
  - Recommends best engine based on hardware
- Updated help text with new options and examples
- Removed old --with-whisper, --whisper-cpu, --no-whisper flags
- whisper.cpp is now the default engine for automatic mode

* feat(uninstall): kill running vocalinux processes before uninstall

* fix(whisper.cpp): auto-download model, use all CPU cores, hide unavailable engines

Three fixes for whisper.cpp implementation:

1. Auto-download model during install
   - Added install_whispercpp_model() function to install.sh
   - Downloads ggml-tiny.bin (~39MB) from Hugging Face
   - Runs automatically when whisper.cpp engine is selected

2. Use all CPU cores for transcription
   - Added n_threads parameter to Model initialization
   - Uses multiprocessing.cpu_count() to get all cores
   - Significantly improves transcription speed

3. Hide unavailable engines from dropdown
   - Added get_available_engines() function to detect installed engines
   - Settings dialog now only shows engines that are actually installed
   - Falls back to first available engine if current not available

Fixes user-reported issues with whisper.cpp integration.

* ui(settings): display whisper.cpp correctly and increase dialog height

- Changed engine display name from 'whisper_cpp' to 'whisper.cpp'
- Added format_engine_display_name() helper function
- Dialog now uses 75% of screen height for better visibility
- More options visible by default without scrolling

* revert(settings): remove display name change, keep dialog height increase

- Reverted engine display name change (was breaking engine selection)
- Kept dialog height at 75% of screen for better visibility

* style: fix black and isort formatting for whisper.cpp logging

* style: fix black formatting in settings_dialog.py

* ci: fix Fedora 39 container package installation

Add dnf cache update and fallback with --nogpgcheck for Fedora containers
that may have stale or missing package metadata.
2026-02-11 19:37:58 -08:00
Jatin K MalikandGitHub 4a72c96b5f fix(text-injection): check ydotoold daemon before selecting ydotool (#176) 2026-02-11 17:26:21 -08:00
Jatin K MalikandGitHub 2b498f0888 fix: Phase 1 - Critical cross-distribution compatibility fixes (#158)
* fix: Phase 1 - Critical cross-distribution compatibility fixes

This commit implements the first phase of cross-distribution compatibility
improvements for Vocalinux, addressing the most critical issues that prevent
the application from working on non-Ubuntu distributions.

## Changes

### install.sh
- Add detect_typelib_path() function that dynamically discovers GI_TYPELIB_PATH
  using pkg-config with fallback to distribution-specific paths
- Update wrapper scripts (vocalinux, vocalinux-gui) to use detected path
- Support for: x86_64, arm64, armhf, riscv64, powerpc64le, s390x architectures
- Specific paths for: Ubuntu multiarch, Fedora/RHEL (lib64), Arch (lib)

### src/vocalinux/speech_recognition/recognition_manager.py
- Add _get_system_model_paths() function for dynamic model path discovery
- Enhance ALSA library detection with multiple library name fallbacks:
  - libasound.so.2, libasound.so, libasound.so.0, asound
- Support XDG_DATA_DIRS environment variable for model paths
- Distribution-specific paths for Fedora/RHEL/CentOS/Rocky/AlmaLinux
- Arch-specific handling (excludes /usr/local)

### src/vocalinux/utils/resource_manager.py
- Enhanced _find_resources_dir() with more candidate paths
- Support for XDG_DATA_DIRS environment variable
- Add lib64 and lib paths for Fedora/RHEL distributions
- Add Flatpak-specific path (/app/share/vocalinux/resources)

## Testing

All 462 existing tests pass.

## Related Issues

Fixes #157 (partial - Phase 1 of 5)

* chore: Remove analysis docs from PR, add to .gitignore

* style: Apply black formatting to pass lint checks
2026-02-07 16:26:49 -08:00
Jatin K MalikandGitHub d004a401af chore(release): prepare v0.5.0-beta (#148)
This commit prepares the repository for the v0.5.0-beta release,
transitioning from alpha to beta status.

Changes made:
- Update version from 0.4.1-alpha to 0.5.0-beta in version.py
- Update Development Status classifier from Alpha to Beta in pyproject.toml and setup.py
- Update status badges from Alpha (orange) to Beta (blue) in README.md
- Update all installation command examples to use v0.5.0-beta tag
- Update release announcement from Alpha to Beta in README.md
- Add RELEASE_PROCESS.md documentation for future releases
- Update docs/INSTALL.md with new version references
- Add What's New section for v0.5.0-beta in docs/UPDATE.md
- Update SECURITY.md supported versions table
- Update website schema softwareVersion in layout.tsx
- Update website version badges in page.tsx
- Update website package.json version to 0.5.0

Testing:
- All 463 tests passing (100%)
- Version consistency verified across all files

Release process documentation added to docs/RELEASE_PROCESS.md
2026-02-05 19:00:17 -08:00
Jatin K MalikandGitHub fdf1de5180 fix(transcription): add space after punctuation when transcribing again (#151)
When transcribing text continuously (stop and start again), the new
text was being injected immediately after the last character without a
space. This caused issues like "Hello world.This is a test" instead of
"Hello world. This is a test".

Fix:
- Check if the last injected text ends with punctuation or other characters
  that typically need a space after them (., ,, !, ?, ;, :, quotes, etc.)
- If yes, add a space before injecting the new text
- Log when space is added for debugging

Characters requiring space:
- Punctuation: . , ! ? ; : ) ] } - _
- Quotes: " '

Closes #151
2026-02-05 18:47:38 -08:00
Jatin K MalikandGitHub 81d1973b7d fix(recognition): prevent stop sound from being transcribed (#150)
When stopping voice recognition, the stop sound was being captured by the
microphone and transcribed as "thank you" or similar text. This happened
because the sound was played BEFORE stopping recording.

Fix:
- Stop recording FIRST by setting should_record = False
- Wait briefly for audio thread to stop
- Discard the last ~1 second of audio buffer (15 chunks) which would
  contain the feedback sound
- THEN play the stop sound after recording has stopped
- Process remaining valid audio

This ensures that audio captured during the stop sound is discarded and
won't be transcribed.

Technical details:
- Audio recorded in 1024-sample chunks at 16000 Hz = ~64ms per chunk
- Discarding 15 chunks = ~960ms of audio
- Uses existing _buffer_lock for thread safety

Closes #150
2026-02-05 18:31:14 -08:00
Jatin K MalikandGitHub 1593e9f962 fix(text-injection): remove unnecessary shell escaping for subprocess calls (#149)
Removes the _escape_text method and its usage since text is passed to
subprocess.run() via list arguments, not shell strings. When using
subprocess with a list (without shell=True), arguments are passed directly
to the command without shell interpretation, making shell escaping
unnecessary and actually harmful.

This fixes the bug where apostrophes and other special characters were
typed with backslash escapes (e.g., "Let\'s" instead of "Let's").

Changes:
- Remove _escape_text() method from TextInjector class
- Remove escaped_text variable from inject_text() method
- Pass text directly to _inject_with_xdotool() and _inject_with_wayland_tool()
- Update docstrings to remove references to escaped text
- Update tests to verify text is NOT escaped when passed to subprocess

Closes #119
2026-02-05 18:21:14 -08:00
Jatin K MalikandGitHub 1e97f36d6c fix(ui): prevent scroll events from modifying settings widgets on hover (#143)
Add _prevent_scroll_on_hover() helper function that blocks scroll events
on ComboBoxes and SpinButtons unless they have explicit focus. This fixes
the UX issue where scrolling through the settings dialog accidentally
changes dropdown/spinbutton values when the mouse cursor happens to be
over them.

Changes apply to:
- Audio device combo
- Engine selection combo
- Model size combo
- Language selection combo
- VAD sensitivity spin button
- Silence timeout spin button
- Keyboard shortcut combo

Users must now click/focus a widget first before scroll events will
modify its value, making scrolling through settings much more predictable.
2026-02-05 17:08:34 -08:00
Jatin K MalikandGitHub 7a1c42aae6 feat(shortcuts): add customizable keyboard shortcuts via GUI (#137)
* feat(shortcuts): add customizable keyboard shortcuts via GUI

Implement configurable keyboard shortcuts for toggling voice recognition,
allowing users to choose between Ctrl+Ctrl, Alt+Alt, Shift+Shift, or
Super+Super (Windows key) double-tap shortcuts.

Changes:
- Add shortcuts configuration section to settings dialog with dropdown
- Update keyboard backends (pynput and evdev) to support configurable
  modifier keys for double-tap detection
- Add SUPPORTED_SHORTCUTS, SHORTCUT_DISPLAY_NAMES constants in base module
- Update KeyboardShortcutManager to accept and pass shortcut configuration
- TrayIndicator now reads shortcut from config on startup
- Add comprehensive tests for shortcut configuration and parsing

The default shortcut remains Ctrl+Ctrl for backward compatibility.
Changes take effect after app restart (noted in UI).

Closes #16

* feat(shortcuts): enable live shortcut switching without app restart

Add restart_with_shortcut() method to KeyboardShortcutManager that stops
the listener, updates the shortcut configuration, and restarts with the
new shortcut. The settings dialog now applies shortcut changes immediately
with visual feedback indicating success.

- Add restart_with_shortcut() to KeyboardShortcutManager
- Add update_shortcut() to TrayIndicator for exposing live updates
- Update SettingsDialog to use callback for immediate shortcut changes
- Show green success message when live switching succeeds
- Add 7 new tests for restart_with_shortcut functionality

* fix: use Python 3.8 compatible context manager syntax

Convert parenthesized context manager (Python 3.10+) to nested with
statements for Python 3.8 compatibility in test_keyboard_shortcuts.py
2026-02-05 16:47:29 -08:00