* fix(ui): AppImage smoke fixes for tray icons, labels, and identity
Replace placeholder mic tray SVGs with opaque branded icons, show
whisper.cpp instead of Whisper_cpp, set GLib/AppIndicator identity to
Vocalinux (not main.py), and give About dialog buttons enough padding
so labels are not clipped under Adwaita.
* fix(packaging): harden AppImage build for Wayland and tray deps
Pin the build interpreter, install onnxruntime into the bundle, set
desktop Exec=AppRun + StartupWMClass, prune typelibs to an allowlist
after linuxdeploy, and stop forcing GDK_BACKEND=x11 on Wayland
(VOCALINUX_FORCE_X11=1 remains as an escape hatch). CI AppImage jobs
also pull Ayatana AppIndicator and librsvg SVG loader packages.
* fix(packaging): bundle full stdlib from base_prefix in AppImage
When build.sh runs under a venv, sys.prefix only has site-packages, so
the AppImage was missing encodings and crashed on start. Copy the
interpreter and stdlib from sys.base_prefix, force pip
--ignore-installed into AppDir, set Gdk program class to Vocalinux, and
tone down the muted tray slash so small StatusNotifier icons stay slate.
* fix(tray): use absolute icon paths so stale hicolor icons lose
XFCE StatusNotifier resolves IconName through the system theme and can
ignore IconThemePath. A leftover ~/.local/share/icons placeholder
(vocalinux-microphone-off.svg filled #e74c3c) was winning over the
AppImage's bundled icons. Outside Flatpak, pass absolute SVG paths to
set_icon_full / new_with_path instead of bare theme names.
* feat(settings): replace tab navigation with searchable sidebar and topic pages
- Sidebar (icon + label) with Gtk.Stack replaces the 7-tab notebook
- Live settings search (Ctrl+F): filters rows in place, per-page match
counts, dims pages without hits, empty-results state
- Topic-based pages per GNOME HIG: Dictation, Speech Model, Audio,
Performance, Application, Advanced
- Persistent bottom status strip: recognition state, shared mic level
bar, and Test Dictation with inline transcription output
- GPU device selection moves out of the power-user gate onto the
Performance page; Reset to Defaults moves into the Advanced page
- Consolidated model info card (info + warning + legend in one surface)
- Contextual custom-shortcut row, in-list auto-pause empty state,
uniform control widths, aligned group descriptions
- Esc clears search, Ctrl+W closes
* test(settings): update source-inspection tests for sidebar redesign, add search coverage
* fix(settings): index unlocked advanced controls in search
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(settings): restore selected page after clearing search
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(injection): skip unbridged IBus on KDE Plasma Wayland
Do not auto-start XIM ibus-daemon on Wayland (circular viability check).
On KDE, require KWin VirtualKeyboard enabled before selecting IBus.
Fixes#574
* refactor(injection): tighten KDE VirtualKeyboard bridge check
Ponytail cleanup: shorter logs and fewer failure branches, same behavior.
* style: black-format ibus daemon Wayland test for CI
* fix(injection): parse KWin VirtualKeyboard gdbus variants strictly
Match <<true>>/<<false>> wrappers instead of bare substrings so ambiguous
gdbus output cannot flip the #574 IBus bridge decision.
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>
* feat(auto-pause): unload speech model while selected apps run
Add Settings toggle + process list so Vocalinux frees CPU/GPU/RAM while
configured games/apps are running, then reloads the model when they exit.
Closes the process-list half of #445 (window-focus pause is separate).
* test(auto-pause): isolate tray wiring to avoid stale GLib mocks
Move TrayIndicator auto-pause tests into test_suspend_handler so
test_auto_pause_monitor never imports tray_indicator under a collection-time
gi MagicMock. That import pinned GLib.SOURCE_REMOVE and broke resume tests
later in the suite (CI on #517).
* test(auto-pause): raise patch coverage for codecov target
Cover process snapshot I/O, GLib poll scheduling, config coercion,
unload edge cases, and tray fallback paths so codecov/patch clears 80%.
* feat(keepalive): idle model offload for GPU/battery saving
Add opt-in keep-alive that unloads the speech model after inactivity so
hybrid-GPU laptops can release VRAM/Vulkan. Next dictation lazy-reloads.
Shares unload_model with auto-pause (#445) using distinct reasons so
app-pause still blocks dictation while idle unload does not.
Fixes#591 (with #445 via shared offload infrastructure).
* test(keepalive): mock gi.repository.GLib via sys.modules
Match the auto-pause GLib test pattern so CI on Python 3.9/3.10
sees the late `from gi.repository import GLib` import.
* test(keepalive): drop post-fire source_remove assertion
After the GLib timeout callback runs, _timeout_id is already cleared,
so shutdown does not call source_remove.
* fix(settings): hide auto-pause empty-state label when apps are listed
The dialog-wide show_all() after _load_and_apply_settings() re-showed
the 'No apps listed yet' row that _refresh_auto_pause_list had hidden.
Mark the row no_show_all and manage visibility explicitly.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* feat(settings): move auto-pause and idle unload to dedicated Power tab
- New Power tab between Shortcuts and General so resource management
features are easy to find; General returns to app-behavior toggles only
- Rename 'Model Keep-Alive (Battery)' to 'Unload When Idle' — the old
name read as the opposite of what the feature does
- Clearer copy throughout: row titles, descriptions, tooltips,
'From running…' becomes 'Choose Running App…', empty-state hint
- Gray out app-list and idle-timeout controls while their feature
toggle is off
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(settings): show which notebook tab is selected
The tab CSS targeted :selected, but GTK3 marks the active notebook tab
with :checked, so no selected styling ever applied — all tabs rendered
identically. Use :checked and add an accent underline + bold label.
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>
* fix(vulkan): auto-select discrete GPU and add manual device selection
On hybrid GPU systems (Intel iGPU + NVIDIA dGPU), whisper.cpp's Vulkan
backend always selected device 0 (the integrated GPU), resulting in
significantly slower performance.
Changes:
- Add detect_vulkan_devices() to enumerate all Vulkan GPUs and identify
discrete vs integrated devices
- Set GGML_VULKAN_DEVICE env var before model init to select the correct
GPU (auto-selects discrete GPU when available)
- Add whispercpp_gpu_device config option for manual device selection
- Add GPU device dropdown in Settings > Advanced for users with multiple
Vulkan GPUs
Fixes#589
* test: add coverage for Vulkan GPU device selection
- Test detect_vulkan_devices() with single/multiple GPUs
- Test _prefer_discrete_vulkan_device() preference logic
- Test GPU device selection in recognition_manager
- Verify GGML_VULKAN_DEVICE env var is set correctly
* fix(speech_recognition): pass whispercpp GPU device to pywhispercpp Model context_params
The Vulkan device-selection logic from PR #590 set the GGML_VULKAN_DEVICE
environment variable, but pywhispercpp did not pick it up. Pass the selected
GPU device through the pywhispercpp Model context_params so whisper.cpp
actually initializes the requested Vulkan device.
Fixes#589
* fix(speech_recognition): fallback when pywhispercpp lacks context_params
Older pywhispercpp releases (< ~1.4.0) reject the context_params keyword
and raise TypeError. Detect that specific failure and retry without GPU
device selection so the engine still loads on those versions.
Fixes#589
* refactor(speech_recognition): inline context_params assignment
Simplify the GPU device selection path and remove the temporary dict.
* fix(vulkan): parse real vulkaninfo GPU0: headers
detect_vulkan_devices() only recognized fictional "GPU id = N" headers,
so real vulkaninfo --summary output (GPU0:/GPU1:) returned no devices and
hybrid NVIDIA systems fell through to CUDA. Also resolve preferred device
names by GPU id instead of list position.
Fixes#589
* refactor(whispercpp): drop ineffective GGML_VULKAN_DEVICE env var
pywhispercpp ignores GGML_VULKAN_DEVICE; device selection already goes
through context_params={"gpu_device": N}. Remove the misleading env write
so maintainers do not think the env var is the selection path.
* fix(uninstall): remove IBus runtime data directory on uninstall
Discussion #559 reported ~/.local/share/vocalinux-ibus surviving uninstall.
PR #569 fixed launcher wrappers but not this IBus IPC directory (socket,
PID file, stderr log). Stop the engine via engine.pid when present, remove
the directory regardless of --keep-data, and verify it in post-checks.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(scripts): stop Vocalinux by PID file, not pgrep -f
pgrep -f vocalinux matches any process whose argv mentions the repo
path (editors, shells, test runners) and can kill unrelated local work.
AGENTS.md already warns against this.
- Prefer PIDs from instance.lock and vocalinux-ibus/engine.pid
- Verify each candidate via is_vocalinux_process (python -m vocalinux.main,
ibus_engine.py under vocalinux, or argv0 basename vocalinux/vocalinux-gui)
- Narrow pgrep fallback only for those entrypoint patterns
- Drop the redundant stop_ibus_engine helper (engine covered via PID file)
- Still remove ~/.local/share/vocalinux-ibus on uninstall (#559)
- Align install.sh and correct the pkill tip in docs/UPDATE.md
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(scripts): detect setuptools console-script Vocalinux processes
Real installs run as `python …/bin/vocalinux`, so argv0 is python and the
previous argv0-basename check missed the live app (instance.lock PID was
skipped). Scan all cmdline args for …/bin/vocalinux or …/bin/vocalinux-gui
instead, which still avoids matching a bare repo directory path.
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>
* docs(mirror): add Codeberg mirror setup and optional sync workflow
Document one-time Codeberg migration (pull mirrors are disabled on
Codeberg.org) plus an optional GitHub Actions push for ongoing sync.
Links the expected mirror URL from README and CONTRIBUTING.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* docs(mirror): drop maintainer setup doc from PR
Keep only the README mirror table and optional sync workflow.
Setup notes are one-off and do not belong in the repo.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* chore(ci): remove obsolete mirror setup comment
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>
Anay GarodiaGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(cli): add version flag
* fix(cli): parse args before acquiring single-instance lock so --version works while app is running
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(web): deslop marketing site with workstation craft identity
Replace generic AI SaaS landing patterns with a deliberate Linux desk
utility look: cool paper palette, Bricolage + Source Sans, solid type,
editorial feature rows, and terminal install as first-class proof.
Adds PRODUCT.md, DESIGN.md, and WEB-DESLOP-NOTES.md. Drops unused geist
and react-intersection-observer. No product claim changes.
* chore(web): drop local deslop working notes from PR
WEB-DESLOP-NOTES.md was a firstmate/session artifact, not product docs.
* fix(web): stop mobile horizontal overflow and harden viewport layout
Long install URLs expanded the page past the phone width. Constrain
terminal pre with break-all, min-w-0 columns, and overflow-x clip.
Tighten mobile hero type and full-width CTAs. Add Playwright viewport
QA script and jest checks that assert no overflow at desktop/tablet/mobile.
* refactor(web): drop browser speech preview from marketing home
Remove the cloud SpeechRecognition demo section and nav Demo link.
Keep product truth: hero install, features, screenshots, engines.
Fold shortcut/engine/audio links into the features intro.
Delete unused LiveDemo component and its unit test.
* chore(web): remove orphaned demo overlay and double-press hook
Only LiveDemo used these; gone with the browser preview section.
* feat(web): borrow PR #546 craft language into deslop home
Merge closed redesign language onto feat/web-deslop-craft: pure white
and near-black iron surfaces, emerald-committed palette, asymmetric
hero with real app screenshots, full-bleed install strip, feature bento
with tray/settings shots, and install-to-dictation steps.
Keep Bricolage + Source Sans (not Geist), no browser speech demo, and
mobile overflow guards. Update screenshots homepage expectations.
* docs(design): align DESIGN.md tokens with PR #546 iron/emerald palette
* fix(web): drop settings screenshot from installer details
Uninstall sat too far below the list once the general-settings shot
was in that column. Keep checklist, requirements, and uninstall only.
* fix(web): raise light-mode primary and footer contrast
Darken light emerald so white button text hits ~5:1 (was 3.4).
Brighten install-strip and footer secondary text for legibility.
Verified light/dark × desktop/tablet/mobile with no overflow.
* fix(web): compact feature cards to kill empty vertical space
Right-column tiles were h-full + justify-between next to a tall
screenshot, so short copy left large voids. Use content-height cards
with denser padding and inline link rows instead.
* fix(web): pair settings shot with full feature rail
Replace three orphan cards beside a tall dialog with a six-row
feature panel that stretches with the screenshot frame. Crop the
settings image with object-cover/object-top so heights match and
no void sits next to the UI shot.
* feat(web): apply homepage craft language to SEO child pages
Rewrite SeoSubpageShell chrome (fixed nav, install pill, iron footer)
to match the deslop home. Add subpage utilities and retoken all
templated child routes: borders, radii, mono kickers, single emerald
accent, terminal panels, no rainbow icon tiles or gradient cards.
* fix(web): avoid invalid @apply hover:border-primary/40 in subpage-card
* fix(web): rewrite subpage CSS without invalid Tailwind @apply
* fix(web): color comparison X icons rose-red on alternatives
Use soft rose (#c45c5c / #e08b8b) for no/cons marks so they read as
distinct from emerald yes/checks without looking like form errors.
* docs(web): move product/design docs under web/ and drop impeccable markers
Keep PRODUCT.md and DESIGN.md next to the Next app (not repo root), add
web/AGENTS.md as the entry point, and point the root AGENTS website
section at those files. Strip tool-specific schema comments.
* fix(web): use icon-only copy control on install terminal
Replace the chunky Copy/Copied chip with a quiet clipboard icon that
turns into a check on success.
* fix(web): restyle system requirements as a spec rail
Replace the nested bullet card with mono-label / value pairs under the
installer section so it matches the craft language used elsewhere.
* fix(web): center engine rows and quiet the Default badge
Align name/badge with multi-line engine details on sm+; replace the
filled green pill with a small mono outline chip.
* fix(ui): use Gtk response signal for first-run dialog
Overriding do_response and calling Gtk.Dialog.do_response() breaks on
modern PyGObject with "Class GtkDialog doesn't implement response".
Connect to the response signal instead so dialog.run() still works.
Fixes#566
* test(ui): cover first-run dialog response handler
Exercise connect("response") wiring and _on_response mapping so
codecov/patch is green for the #566 Gtk.Dialog.do_response fix.
* test(ui): isolate first-run dialog response coverage
Load first_run_dialog under a unique module name with a real Gtk.Dialog
stand-in so _on_response and connect("response") run under full-suite
import pollution without clobbering other tests' gi mocks.
* test(ui): cover _on_response via SimpleNamespace
Call FirstRunDialog._on_response on a stand-in object instead of
constructing Gtk.Dialog; enough for codecov/patch on the handler body.
* test(ui): map first-run responses via SimpleNamespace
Exercise FirstRunDialog._on_response for YES/NO/LATER/unknown without
constructing Gtk.Dialog.
* test(ui): collapse first-run response cases into one table
One loop covers YES/NO/LATER/unknown instead of four copy-pasted tests.
* test(ui): restore isolated first-run dialog coverage load
SimpleNamespace calls against vocalinux.ui.first_run_dialog.FirstRunDialog
fail under the full suite when earlier tests leave the class as a MagicMock.
Reload the source under a unique module name with a real Gtk.Dialog stand-in
(as in 680c5ed) so _on_response and connect("response") stay green on CI.
Demote xclip, wl-clipboard, and wtype from hard depends to optdepends.
These are injection/clipboard fallbacks (alongside existing xdotool);
IBus and other primary paths may not need them on every install.
Responds to AUR feedback (Ataraxy, comment #1079450). Follow-up to #579
(virtual python-pywhispercpp); AUR picks this up on the next v* publish.
The PKGBUILD hard-depends on python-pywhispercpp-cpu by name while
optdepends tells GPU users to "replace" it with the -cuda variant.
That swap is impossible in a normal pacman transaction: the -cuda
package conflicts with -cpu but only provides the virtual
python-pywhispercpp name, so removing -cpu breaks vocalinux's
dependency and pacman aborts. The only workaround is pacman -Rdd,
which leaves a broken dependency record that re-breaks on every
vocalinux upgrade.
All three AUR backend packages (-cpu, -cuda, -rocm) declare
provides=('python-pywhispercpp'), so depending on the virtual name
lets any variant satisfy the dependency. AUR helpers prompt for a
provider on fresh installs, the standard Arch pattern for
swappable backends.
Claude-Session: https://claude.ai/code/session_01XAF9BkcSzwcMjTzkBxdx6k
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
On Ubuntu 24.04+ apt still lists libgirepository1.0-dev, so the installer
kept only 1.0 and skipped 2.0. Pip builds of PyGObject need the modern GI
headers from 2.0; install both packages when both exist.
Fixes#571
Tray troubleshooting previously only showed apt, which is wrong on Fedora
and Arch. Document AppIndicator install for Debian/Ubuntu, Fedora, and
Arch in INSTALL.md and the website troubleshooting page.
Fixes#570
* feat(packaging): add self-contained AppImage build
Bundles the wheel plus PyGObject/GTK3/AppIndicator/IBus typelibs into a
relocatable x86_64 AppImage via linuxdeploy + linuxdeploy-plugin-gtk, so
users get a zero-install, no-sandbox-negotiation alternative alongside
AUR/PyPI. Wired into nightly and tagged releases, plus a smoke-test job
in the PR pipeline since AppImage/ELF bundling can't be verified on a
macOS dev machine.
* feat(packaging): add aarch64 AppImage builds
Parameterizes build.sh on uname -m instead of hardcoding x86_64 (both
linuxdeploy and appimagetool publish aarch64 continuous builds, and
pywhispercpp ships manylinux_aarch64 wheels, so no from-source compile
needed). Adds an arch matrix to the PR smoke-test job, and a second
job in nightly.yml/release.yml that builds on GitHub's free
ubuntu-24.04-arm hosted runner and attaches the aarch64 AppImage to
the same release the x86_64 job already created.
uninstall.sh only removed ~/.local/bin/vocalinux when it was a symlink
([ -L ]), but a repo/dev install writes a real file there, and
~/.local/bin/vocalinux-gui was never removed — both were left behind.
- Remove both wrappers via safe_remove in remove_application_files (handles
files and symlinks).
- safe_remove also handles dangling symlinks (-e follows links and is false
for a dead link, so also test -L).
- verify_uninstallation checks both wrappers.
- Drop the redundant symlink-only block in remove_curl_install_files.
Co-authored-by: Alexander <ab@digital-benefits.de>
Fixes#550.
Root cause: VOSK_MODEL_INFO["medium"]["languages"] and
VOSK_MODEL_INFO["large"]["languages"] in vosk_model_info.py were missing
entries for "it" (Italian) and "en-in" (English-India), even though both
languages are present in the "small" table and VOSK actually publishes
bigger models for them (vosk-model-it-0.22, vosk-model-en-in-0.5 at
alphacephei.com/vosk/models).
When a user selected Italian + the medium (or large) VOSK model,
_init_vosk() built vosk_model_map["medium"] = None (via .get() on a
missing key), and _get_vosk_model_path() then called
os.path.join(MODELS_DIR, None), crashing with:
join() argument must be str, bytes, or os.PathLike object, not 'NoneType'
before any download was even attempted. This matches the exact debug log
in the issue and the settings-dialog download flow the reporter exercised.
Fix:
- Add the missing "it"/"en-in" entries to the medium/large tables so all
10 supported languages now resolve consistently across all three sizes.
- Add a guard in _get_vosk_model_path() that raises a clear ValueError
("No VOSK '<size>' model available for language '<lang>'...") instead of
crashing deep inside os.path.join, so any future data gap fails loudly
and helpfully rather than with a cryptic TypeError.
* docs(readme): pin CI badge to main and restyle social/coverage badges
Scope the Vocalinux CI status badge to the main branch so PR failures no
longer flip the README shield. Restyle the X follow badge to black/white X
aesthetics, and set Codecov coverage.range so 80%+ coverage shows green.
* docs(readme): use static X follow badge without broken count
Shields.io no longer returns Twitter/X follower counts (empty right-side
value after API restrictions). Switch to a static black badge that shows
Follow @jatinkrmalik with the X logo instead of an empty dynamic count.
* docs(readme): center-align header before hero image
Wrap icon, title, subtitle, and shields in a centered div so the top
of the README lines up cleanly above the hero image.
* docs(readme): tighten header badges and add social/install shields
Three clear rows: identity (Stable, release, license, PyPI, AUR),
quality (CI, Python, Linux, codecov), and community (stars, forks,
issues, contributions, X follow). Drop low-signal noise (watchers,
commit activity, last commit, Made with GTK). Slightly larger logo.
* docs(readme): add privacy, distro, and PRs-welcome shields
Expand the header into five scannable rows: identity, product values
(privacy/offline, data stays local, X11|Wayland, PRs welcome), official
distro logos linking to DISTRO_COMPATIBILITY, quality, and community.
Drop the generic Platform: Linux badge in favor of named distros.
* docs(readme): trim header to four badge rows
Drop Stable (release version is enough), merge privacy into one badge,
and remove stars/forks/issues (already on the GitHub repo page). Keep
X follow with CI/Python/codecov on the last row.
* docs(readme): order badge rows narrowest to widest
Step the header out toward the full-width hero: values → identity →
quality/social → distros as the base row.
* chore(release): prepare v0.14.2
IBus stability patch on the 0.14 series: engine process launch after XDG
import (#534) and FocusIn gate for first dictation on GNOME Wayland (#533).
Keep 0.14 series feature list in README/UPDATE for patch releases; changelog
and AppStream note only the delta. Document that rule in RELEASE_PROCESS.
* fix(release): address 0.14.2 review findings
- Dates: v0.14.2 uses 2026-07-17 (not before 0.14.1)
- AUR: sha256sums=SKIP until tag; document updpkgsums in RELEASE_PROCESS
- Single version source: package __init__ re-exports version.py; User-Agent uses __version__
- Docs: drop redundant series recaps; shrink AppStream/changelog delta
- README ecosystem status: Stable v0.14.2 (not Beta)
* docs(release): include settings scroll fixes in 0.14.2 notes
Cover #538 and #541 (scrollable settings tabs / wheel polish) in README,
UPDATE, website changelog, AppStream, and version history after rebasing
onto main.
* fix(ui): polish settings tab scrolling after oversized-dialog fix
Forward wheel events from unfocused combos/spins to the tab scroller,
drop the nested Advanced ScrolledWindow, and clear tab scroller shadows.
* refactor(ui): re-dispatch scroll to parent ScrolledWindow
Drop hand-rolled vadjustment math; forward the event and let GTK scroll.
The settings dialog opened taller than the screen (spilling onto a
second monitor) because none of the notebook tabs scrolled. A
Gtk.Notebook requests the minimum size of its tallest page, and the
Speech Engine (~950px) and Recognition (~1080px) tabs pushed the
window's minimum height to ~1350px at 700px width, overriding
set_default_size() entirely on 1080p displays.
Wrap each tab in a vertical-only Gtk.ScrolledWindow so the notebook's
minimum height collapses and the default size takes effect, and raise
the default height from min(600, 40% of screen) to min(800, 75%) since
tall content now scrolls instead of stretching the window.
Measured before/after on a 1920x1080 monitor: opens at 700x1351 before,
700x800 after (window minimum drops from 684x1161 to 684x243).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(ibus): wait for FocusIn before commit on scoped injection
Cold first activation of the vocalinux engine on GNOME Wayland could
commit before mutter bound a client context, so logs reported success
while the first dictation of a session was dropped.
Fixes#523
* refactor(ibus): clear focus event directly in do_destroy
Drop the clear_focus callback on the destroy helper; one local branch is enough.
* test(ibus): cover FocusIn gate helpers for patch coverage
Extract PING/focus-wait helpers and exercise them so codecov/patch
clears the 80% target on the #523 fix.
* fix(ibus): restore engine process launch after XDG path import
PR #484 switched ibus_engine to a relative import of xdg_data_home for
Flatpak-safe paths. start_engine_process and the IBus component exec still
run this file by path (python /path/to/ibus_engine.py), which has no
package context, so the process died immediately with ImportError and
Vocalinux fell back to ydotool (clipboard paste).
Accept absolute/inline import fallbacks when loaded as a script, log child
stderr from engine.stderr.log on startup failure, and add a path-based
--xml smoke test so this does not regress.
* test(ibus): cover start_engine_process failure diagnostics
Raise patch coverage for the engine.stderr.log failure paths so
codecov/patch meets the 80% target on #534.
* chore: re-trigger CI after rebase onto main
GitHub rejects job-level if expressions that reference matrix
(Unrecognized named-value: matrix). Resolve the include list in a
small setup job instead so PRs stay x86_64-only.
Using github.sha in the cache key forced a full rebuild on every commit.
Hash packaging/flatpak instead so .flatpak-builder can restore across
runs, and only rebuild modules that actually changed.
PRs build x86_64 only; main and workflow_dispatch still cover both arches.
Drop the extra upload-artifact step in favor of the action's own upload.
Patch release for Flatpak/AUR packaging and post-0.14.0 fixes:
installer wrappers, ydotool dual API and Flatpak socket probe, model
download timeouts, layout-aware hotkeys, and related docs/web bumps.
* feat(flatpak): add Flatpak packaging for universal distribution
Adds a Flathub-ready Flatpak for the whisper.cpp engine, plus the minimal
runtime adaptations Flatpak actually needs — deliberately scoped down from the
earlier attempts (#416, #428), which bundled unrelated whisper.cpp GPU, audio,
and clipboard changes.
Packaging (packaging/flatpak/):
- app manifest, AppStream metainfo, and desktop entry (com.vocalinux.Vocalinux)
- generated offline Python dependency modules (flatpak-pip-generator)
- bundled shared modules: PortAudio, libayatana-appindicator, xdotool, xsel
Runtime changes (Flatpak-only, behaviour unchanged outside a sandbox):
- utils/paths.py: honour XDG_CONFIG_HOME/XDG_DATA_HOME so config, models and
runtime data live under ~/.var/app/<app-id> in the sandbox
- autostart uses `flatpak run <app-id>` (the host launcher is always on PATH)
- text injection and keyboard backend detect a Wayland host reached via XWayland
(no Wayland socket granted) and route through xdotool / pynput-on-X11
- tray indicator references the exported `<app-id>-*` host icon theme names
Also: a CI workflow building x86_64 + aarch64 bundles, and README / packaging
README / distro-compatibility doc updates.
Verified with a full flatpak-builder build + bundle install on aarch64:
pywhispercpp loads its native libraries without LD_LIBRARY_PATH, XDG paths
redirect correctly, bundled tools are present, and the entrypoint runs.
Manifest and builddir lint are clean and AppStream metadata validates.
Closes#167
* chore(flatpak): sync metainfo version to 0.14.0-beta
Also ignore flatpak-build-dir/ so local builder output stays untracked.
* refactor(flatpak): trim docs and comments after ponytail review
Drop redundant packaging README command blocks, shrink path/tray/autostart
comments, and stop ignoring a non-canonical build-dir name.
* fix(flatpak): close readiness gaps for v0.14.0-beta
Route log files through XDG data dirs so Flatpak keeps them under
~/.var/app, refresh AppStream screenshots to vocalinux.com, and use
the SPDX GPL-3.0-only license id.
* fix(flatpak): enable global hotkeys via evdev and --device=input
pynput over XWayland only receives keys for the focused X client. Prefer
evdev inside Flatpak and grant /dev/input so shortcuts work app-wide.
* fix(flatpak): inject text with ydotool/uinput on Wayland
xdotool only reaches XWayland clients, so typing appeared to succeed but
never hit native apps. Ship ydotool, grant --device=all for uinput, start
ydotoold on demand, and type via uinput inside the Flatpak.
* fix(flatpak): paste via wl-copy + Ctrl+V instead of typing
ydotool type injects character-by-character. Ship wl-clipboard, grant
--socket=wayland, and prefer clipboard paste for instant insertion.
* test(text-injection): expect ydotool default key-delay of 2
Matches the shipped VOCALINUX_YDOTOOL_KEY_DELAY default used when
clipboard paste falls back to character typing.
* fix: sg not found on Ubuntu 26.04/Debian 13
The 'sg' command moved from the 'login' package to 'util-linux-extra'
in Debian 13 and Ubuntu 26.04, which is not installed by default.
This causes vocalinux and vocalinux-gui wrappers to fail with
'exec: sg: not found'.
Two changes:
1. Add util-linux-extra to APT dependency lists (Ubuntu + Debian 13+)
2. Add command -v sg safety check in both wrapper scripts so they
fall back to direct execution if sg isn't available
Fixes#522
* fix: attempt to install util-linux-extra if sg is missing
When the wrapper scripts detect that sg is not available, they now
attempt to install util-linux-extra via apt-get before falling back
to direct execution. This provides a better user experience on
Ubuntu 26.04/Debian 13 where sg moved to a separate package.
The installation runs silently and only triggers if:
1. User is in input group but session doesn't have it
2. sg command is not found
3. apt-get is available (Debian/Ubuntu systems)
If installation fails or sg still isn't available, the scripts
fall back to direct execution (app starts but keyboard shortcuts
won't work until next login).
* refactor: extract shared sg check, drop runtime install
- Extract sg fallback logic to shared SG_CHECK variable (eliminates duplication)
- Remove runtime apt-get install of util-linux-extra (dependency list handles it)
- Flatten to simple command -v sg check with direct exec fallback
net: -24 lines
---------
Co-authored-by: Alfred Pennyworth <alfred@openclaw.ai>
* docs(screenshots): refresh v0.14 settings/About shots and add website gallery
Replace the outdated About dialog screenshot and supersede the old single
settings dialog with the six v0.14.0-beta settings tabs. Keep unique product
shots that have no replacements. Add a dedicated /screenshots/ website page
with header/nav discoverability and a homepage link (no full gallery on home).
* docs(screenshots): drop obsolete features-overview shot
The annotated features overview was the pre-tabbed settings panel and no
longer matches the v0.14 UI. Remove it from README, website gallery, and assets.
* docs(screenshots): ship full-resolution web transcription shot
Restore the original PNG instead of the downscaled copy so the website
gallery matches the repo assets bit-for-bit.
* feat(web): add click-to-expand lightbox on screenshots gallery
Open a full-screen dialog when a shot is clicked, with prev/next controls,
keyboard arrows, Escape to close, and captioned counter for browsing the set.
* fix(web): avoid next/image HMR crash on screenshots gallery
Use plain img tags in the client gallery so dev-mode webpack chunk
reloads do not throw options.factory call errors while hydrating.
* feat(packaging): add AUR release package and CI publish
Ship packaging/aur/vocalinux for yay installs of tagged releases, wire
AUR publish into the release workflow (skipped until secrets are set),
and document one-time maintainer setup.
Fixes#116
* refactor(packaging): slim AUR PR to PKGBUILD + CI
Drop systemd unit, install script, website edits, and long docs.
Keep release PKGBUILD, minimal publish job, and short AUR setup notes.
* fix(hotkeys): resolve combo letters via active XKB layout
Settings capture stores character tokens from GDK keyvals (e.g. alt+a),
but the Wayland/evdev path mapped letters to fixed US KEY_* codes. On
AZERTY that made Alt+A listen for the key labeled Q. Resolve letter
tokens through libxkbcommon for the active layout so recording and
listening agree.
Fixes#513
* refactor(hotkeys): slim layout-aware key resolution
Drop unused resolve helper, dual knobs, setxkbmap/localectl probes, and
redundant tests. Keep gsettings + libxkbcommon map and the AZERTY combo path.
* test(hotkeys): drop redundant AZERTY resolve-only case
Keep fire/no-fire combo coverage and the XKB fr map check.
* test(hotkeys): cover layout map detection and error paths
Raise patch coverage for layout_key_map / evdev_code_for_key so codecov
patch target (80%) is met.
Bump brace-expansion, js-yaml, @babel/core, and related transitive
deps via npm audit fix. Override nested postcss so Next.js no longer
pulls vulnerable 8.4.31 (XSS in CSS stringify output).
* chore(release): prepare v0.14.0-beta
- Bump version 0.13.0-beta -> 0.14.0-beta (src/vocalinux/version.py)
- README: refresh What's New for v0.14.0-beta and bump comparison table
- docs/UPDATE.md: add v0.14.0-beta section and bump checkout example
- SECURITY.md: mark 0.14.x supported, 0.13.x unsupported
- docs/RELEASE_PROCESS.md: add v0.14.0-beta to version history
- web: bump package.json / package-lock.json / page.tsx schema + badge
- web changelog: add v0.14.0-beta entry
* ci(codecov): allow 0.5% project coverage threshold
Prevents version-bump/docs-only PRs from being blocked by minor
coverage fluctuations (e.g. -0.04% on a release-prep branch).
* docs(release): correct chronological order in version history table
* docs(release): add #509 shortcut UI exclusivity fix to v0.14.0-beta notes
Selecting a preset after recording a custom combo left the custom entry
filled, so both looked active. Sync the dropdown to "Custom Shortcut"
when a non-preset is set, and clear the custom field when a preset wins.
#502 only exempted KDE from the #478 real-engine gate. GNOME/Mutter has
the same scoped-activation path, so stock sessions with only an xkb:*
layout fell through to wtype, which Mutter rejects, then clipboard-only.
Allow any Wayland session without an explicit non-IBus IM to try IBus
when ibus-daemon is running; unbridged compositors still hit the
existing denylist.
Fixes#504
* feat(asr): add chat audio remote benchmarking
* style(asr): apply black formatting
* test(main): include remote API model kwarg
* test(asr): add coverage for chat-completions error handling and parsing
Add 10 new tests to cover uncovered lines in PR #468:
- ConnectionError handler in _try_chat_completions_audio_api (lines 1601-1602)
- _parse_chat_completion_transcription edge cases: None, list content,
empty/whitespace strings, JSON without text field, plain text, and
single-line language metadata (lines 1610, 1613-1621, 1625, 1629-1640)
Coverage for recognition_manager.py improved from 85% to 86%.
* feat(asr): support FunASR remote responses
* refactor(asr): scope remote FunASR support to the #469 path
Drop chat-completions/Qwen client, evaluation notes, and the remote ASR
benchmark harness. Keep model field, tolerant OpenAI response parsing,
SenseVoice label stripping, FunASR docs, and tests.