- build-release.sh: POSIX-only checks (dash runs the release), a release form
is exactly X.Y.Z or X.Y.Z-alpha|beta|rcN, and the install smoke refuses a
package that has no smoke recipe.
- Tests: names and tag gate (exact match, committed template must agree), key
refusals before any container starts (fake podman records calls), the
render script pins only the build tree, podman wiring (only the sign phase
sees the key, and it has no network), the xbps ordering of pre-release
spellings, the workflow gates, tmpfs key staging, and the publish set
(sdist, packages, signatures, repodata, client key).
Model recommendation keyed to the installed build and free memory, fork-sourced Void templates, signed xbps release workflow on Gitea with the pinned repository key, niri injection guard and wtype-first order, tightened naming sweep, web copy fixes, and CI security and duplication fixes.
Replace the self-referential <repo-url-from-release-notes> placeholder
in the root README, the release-notes template, and the Void packaging
README with the concrete per-tag download prefix the build-xbps job
attaches the signed repo to. Static docs default TAG to the current
version; release.yml renders github.ref_name at publish time.
Fixes#29
Ticket #31 review found startup and demote paths preferring ydotool
over wtype, with a KDE ydotool-first exception, contradicting the
decided order in spec #16 (portal first where a backend implements
it, then direct keysym typing, then the virtual-input tool, then
XWayland-only injection, then manual paste).
- Auto-selection is now portal, wtype, ready ydotool, daemonless
ydotool, xdotool on every desktop including KDE; the __init__ wtype
probe validates wtype and demotes on compositor rejection.
- Portal-deny demotion, IBus-fallback switch, XWayland recovery, and
a new wtype runtime-failure retry all follow the same rung order
with partial-type accounting preserved.
- Ladder suite grows to 14 tests pinning KDE portal selection,
wtype-before-ydotool selection, and each demotion trigger.
- USER_GUIDE states the full rung order and portal-first on KDE.
Add build-xbps tag job to release.yml: verifies the tag against
src/vocalinux/version.py, fails closed when XBPS_SIGNING_KEY is unset,
builds and signs via podman-only repo.sh, and attaches packages,
signatures, repodata, and client key files to the release. Fold the
xbps-repo artifact into publish-checksums (manifest + SHA256SUMS).
Document the Void install in the root README (glibc x86_64 scope,
fingerprint check, third-party repo caveat) and the post-tag template
version/checksum bump as a pull request in docs/RELEASE_PROCESS.md.
Proof: local podman run with a throwaway RSA4096 key built all four
templates, signed packages + repodata, and verified voculinux installs
from the signed repo in a clean rootdir (fingerprint
eb:29:5d:9e:5c:94:c1:63:ae:35:cd:35:db:7a:28:da).
Both model-download call sites built the User-Agent string separately:
recognition_manager used a hardcoded literal while settings_dialog
interpolated APP_ID. Add app_identity.user_agent() and use it in both,
with a test pinning the shared definition.
Fixes#17
q8_0 threshold becomes 1644 MiB (1132+512) and q5_0 becomes 1358 MiB
(846+512) per the recorded baseline recommendation, replacing the
on-disk-size proxies. Tests pin the measured values and the 1644/1358
boundaries.
Build every template in smoke-gate order, index the full repository and
sign packages plus metadata with the RSA4096 key, in the pinned Void
container via podman. Fail-closed without a key: no key file, no repo.
Ticket #29 (needs #27, #28).
Ticket xavierk/voculinux#28, needs #27. One documented command
(packaging/void/smoke.sh, just void-smoke) lints, builds, installs
and smoke-tests every template under packaging/void/srcpkgs in
dependency order in the pinned Void container via podman, failing
fast naming the package with the harness echoing each stage.
The gate caught three latent breaks in the per-package harness and
the app template, fixed here:
- container-main.sh scoped the libggml scan to python3-pywhispercpp
and gave the leaf packages their own import smokes (pynput by
presence: no display in the container), failing loudly for
packages without a smoke recipe;
- voculinux template drops the deprecated wrksrc override (xbps-src
renames the single-top-dir archive itself; xlint rejects wrksrc)
and builds the PEP 517 wheel (the sdist ships no setup.py);
- post_install promotes the pre-rename sdist names (desktop entry
content, icons, /usr/bin launchers) to the renamed identity, and
the payload smoke rejects pre-rename leftovers in OS-visible paths.
Offline policy tests (tests/test_void_smoke_gate.py) pin the gate to
the template set, the app-last order, podman-only pinned inputs and
per-package failure identity; the #27 policy tests and the rename
sweep cover the harness and template contracts.
Ticket xavierk/voculinux#27, built on #18 (rename) plus the #25/#26
dependency templates. The template builds the app from the canonical
checksum-verified release sdist, depends on Void's interpreter, GTK,
desktop bindings and packaged Python libraries, and installs the
renamed desktop entry, icons and a packaged-install marker. No speech
models, no vendored wheels, no pip at build or install time.
Packaged installs keep background update checks as a signal, but every
update affordance presents the package-manager path
(src/vocalinux/utils/packaging.py). Also corrects the Void distro
inventory (Ayatana provider name; ydotool marked absent) and regens
the installer map.
Names, wordmarks, package and install references move to voculinux;
site domain to voculinux.com with renamed public assets. Layout keeps
Carbon spacing/type/motion discipline remapped to the house palette
(teal interactive, warm paper, system stacks, red reserved). Header
and footer gain Bongbetic attribution chrome linking hosted brand
assets; no brand binaries vendored. Engine copy states whisper.cpp as
guaranteed default, others as best-effort extras, remote API as
user-configured with audio leaving the machine. Nix page carries a
fork-status note (upstream flake installs Vocalinux).
Refs #24
Locks the ordered rungs without real compositors: portal first where a
backend implements it (GNOME), direct typing first on niri with IBus
excluded and no portal selected, deny demoting to direct typing with the
full dictation kept, terminal probe choosing Ctrl+Shift+V, held modifiers
delaying the XWayland paste, and clipboard manual paste as last resort.
USER_GUIDE states the niri first run (no prompt, typing goes direct,
never pin portal there) and the GNOME/KDE grant, persistence and deny
fallback under the renamed backend setting and override names.
Refs #31
Docs-only slice of ticket #22: include file, integration page, index
links. Tray-watcher observation stays open for hardware run (#32); no
probe code changed.
Refs #22
Alongside Toggle/Start/Stop, the renamed interface now publishes
dictation state (idle/listening/processing) as a StateChanged signal
and a readable State property, so status bars and scripts can follow
dictation without scraping the tray. Signals fire only on change, so
double start/stop emits no duplicates; command-line triggers behave
exactly as before.
Refs #21
Free memory picks the load-time rung (large-v3-turbo-q8_0 default,
q5_0 under 1086 MiB free, small/small.en floor) for unpinned default
configs; thresholds derive from catalog sizes plus the protocol's
512 MiB headroom. A manual pin is never overridden, and a failed load
steps one rung down and retries exactly once before surfacing.
CUDA ranks before Vulkan only with a complete architecture-matching
toolkit (nvidia-smi GPU, nvcc, compute capability >= 7.0); otherwise
Vulkan, then CPU. The driver-only CUDA recommendation now demotes to
CPU logic instead of naming an unreachable VRAM ladder.
Refs #20
The fork takes its own name everywhere a user can see it: binaries
(voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus
namespace (com.voculinux.Voculinux, no alias for the old name),
config/data/cache dirs, single-instance lock, IBus engine identity,
tray icons, resource prefixes, VOCULINUX_* environment names, and the
update-checker user agent. The internal Python module stays vocalinux
and the distribution metadata keeps the upstream URLs as provenance.
On first run a previous Vocalinux install's config.json, custom
dictionary, and portal restore token are copied once into the new
locations (new config always wins, no merge, no write-back); models
re-resolve through the pinned-checksum download path. An offline
naming-sweep test proves no old-name strings remain on user-visible
surfaces outside the allowlist.
Refs #18
Conflict in packaging/void/tool_checksums.txt (add/add with #25):
kept #26 rows (base-image digest + void-packages pin the kept
build.sh reads) with the #25 header reference folded in. #25's
base-image digest differed; flagged for re-pin on next bump.
BRAND_ACCENT single source with shade()-derived states; accent CSS
covers suggested-action buttons and the listening chip, installed at
USER priority from main().
Refs #23
Single app_identity module owns APP_NAME/APP_ID, D-Bus names,
desktop/icon/IBus names and related surfaces; 16 consumers import
from it with zero value changes. Deliberately excludes
settings_about and dictation_pad literals pinned by existing tests.
Refs #17
Spec #16 ticket #26: Void template for the whisper.cpp Python binding,
built from the pinned 1.5.0 sdist with the Vulkan backend (default-on
option), shared libraries, and portable CPU flags. Includes the podman
build/smoke harness, pinned base-image pins, and offline policy tests.
Refs: #26
Ticket #19 (spec #16): fixed three-metric protocol as a runnable script
(idle memory both keep-alive states, wake latency from debug logs,
real-time factor on the fixed 30 s sample per backend) plus per-rung
load footprints, portal/direct injection probe, and threshold
derivation for the model-ladder ticket (#20). Offline unit tests for
the parsing/statistics/report helpers. Evidence recorded on the ticket.
One debug-only marker (Toggle requested in state ...) makes wake
latency derivable from logs alone.
Ticket: #25
Two in-repo void-packages-layout templates under packaging/void/srcpkgs:
python3-pynput 1.8.2 (new; Void does not ship it) and python3-PyAudio
0.2.14 (updated local template; Void ships 0.2.12, below the project's
pyaudio>=0.2.13 floor). Versions and checksums track uv.lock and
requirements/runtime.txt; pynput post_patch strips setup_requires so the
build never fetches helpers over the network (setuptools-lint and twine
are not in void-packages). No noverifypydeps/nopyprovides anywhere.
Adds tests/test_void_small_deps.py (offline policy test): template
layout and metadata shape, lock/export/floor pins, checksum pins,
no verification-disabling options, linux dep coverage, verified-only
dependency names, and the digest-pinned base image in
packaging/void/tool_checksums.txt.
Verified: xlint clean on both templates; both packages build with
xbps-src in the pinned ghcr.io/void-linux/void-glibc-full container
(podman) and install in a fresh container with pyaudio 0.2.14
importing against PortAudio V19.6.0.
Intake at release-preparation sync points plus on-demand critical
fixes, freeze once the release branch opens to release-blockers only,
one upstream fix per fork pull request through the standard gates,
conflicts resolved by keeping upstream logic and re-applying fork
renames, and the qualification rule (fixes and user-instruction docs
port; features re-decided; dependency/CI churn only on a security
advisory). Names the retained internal vocalinux module as the reason
conflicts concentrate at renamed user-visible surfaces.
Tests: tests/test_upstream_intake_policy.py (7 tests)
* fix(overlay): detect Wayland by GType so the fallback overlay stays compact (#1030)
_is_wayland() checked isinstance(display, Gdk.WaylandDisplay), but GTK3's
Gdk typelib has no WaylandDisplay class, so it always returned False. On
Wayland without gtk-layer-shell, _layout_bottom_strip() therefore sized the
prewarmed fallback window to the full monitor width (the X11 strip) instead
of the compact centered window meant for Wayland, which on KDE showed up as
a wide empty "Vocalinux Dictation" window listed in the taskbar.
Drop _is_wayland() and use _display_is_wayland(), which checks the GType
name and already gates prewarm, so both decisions agree.
Also list gtk-layer-shell as an optdepend of the source AUR package: with
it KWin and wlroots compositors get a layer surface that is never listed
in the taskbar and never needs the prewarmed toplevel. The AppImage (and
so vocalinux-bin) already bundles it.
* docs(overlay): describe the compact Wayland fallback in the overlay docstrings
The class docstring still said the overlay is always a full-width strip, which no longer holds on Wayland without layer shell (#1030).
* fix(overlay): keep the full-width strip on GNOME Wayland
Mutter ignores CENTER_ALWAYS for a plain Wayland toplevel, so the compact
fallback window landed in the top-left corner on GNOME (tested on GNOME
Shell 46 with the Ubuntu Dock). The full-width strip at least keeps the
orb horizontally centered there. Use the compact window only on KDE,
where KWin listed the strip in the taskbar as a wide empty window (#1030).
* fix(overlay): quit Vocalinux when the dock closes the overlay window (#1028)
On GNOME Wayland without layer shell, the prewarmed dictation overlay is a
regular toplevel that the dock lists as the app's window. Since #1017 the
overlay refuses close requests so it is never destroyed (#1010), which made
"Quit" from the dock silently do nothing.
The overlay still keeps its window, but now forwards the close request to
an optional on_close_request callback, scheduled once from an idle source.
The tray passes its _quit(), so the dock's Quit behaves like the tray's Quit.
* fix(overlay): drop a queued dock close once the app is quitting
A dock close request is forwarded through an idle source. If the tray's
Quit or SIGTERM tears the overlay down before that idle runs, the
callback would run _quit() a second time. destroy() now drops the
callback, and the pending flag is cleared when the request runs so a
later close can retry if quitting failed.
Refs #1028
The Settings audio status label is set with Pango markup. A microphone
named with '&' or '<' (or an audio test error carrying them) made the
markup invalid, so GTK dropped the text. Escape both with
GLib.markup_escape_text.
Also correct the v0.17.0 notes in docs/UPDATE.md: the Snap never
bundled VOSK; it installs the project without extras.
* fix(snap): build and verify payload from locked python inputs
* docs(snap): document locked inputs and artifact verification
* test(clipboard): isolate restore workers between tests
* test(packaging): tighten isolation and clarify snap audit guarantees
* fix(ci): run snap recipe tests on snap-only changes
A change to snap/snapcraft.yaml or snap/local/smoke-test.sh only matched
the snap filter, so the Python tests that guard the recipe (including the
portable whisper.cpp flags from #957) and the smoke script were skipped.
Match all of snap/ in the python filter, as scripts/ already is.
* docs(snap): use a version placeholder in the smoke-test example
---------
Co-authored-by: Jatin <jatinkrmalik@gmail.com>
* feat(parakeet): add optional Underdog Bark 3.0 int8 model
Adds bark-3.0 to the Parakeet model picker: Underdog Bark 3.0, an
English fine-tune of Parakeet TDT 0.6B v3, as the int8 sherpa-onnx
conversion published at velddev/Underdog-Bark-3.0-sherpa-onnx. It runs
on the existing sherpa-onnx recognizer, pinned to a revision with every
file checked against model_checksums.txt, like Orukeet (#840).
The conversion writes the unknown-word token where a quote mark was, so
the Parakeet transcript drops <unk>; the other models never emit it.
* docs(parakeet): say when to pick Bark 3.0; loosen its catalog test
The README now says Bark suits English dictation of conversational
speech and v3-european stays the choice for read or multilingual
speech. The test checks that bark-3.0 is offered rather than that it is
the last catalog entry, so a later model does not break it.
* test(parakeet): add docstrings to the Bark tests
* feat(parakeet): add optional 4-bit Underdog Bark 3.0 build
Adds bark-3.0-int4: the same Bark 3.0 weights quantized to 4-bit with
onnxruntime's HQQ quantizer, for machines short on memory. It needs
about half the RAM of the int8 build (0.64 GB against 1.28 GB peak) and
a 379 MiB download, at two to four times the CPU time (about 2x on
Intel, 3.7x on AMD by the publisher's measurement). The files are
published in velddev/Underdog-Bark-3.0-sherpa-onnx (Hugging Face PR #1),
and both Bark entries now pin its revision 3ae3af1, which holds both
builds; the int8 files are unchanged there.
Catalog entries gain an optional "precision" key, so the loader opens
encoder.int4.onnx and friends instead of assuming int8; every existing
model keeps the int8 default. The README section now covers both
builds in a few lines.
* test(parakeet): type the Bark test signatures
* docs(parakeet): name the Bark sherpa-onnx source and its pins
The README now says where both Bark builds download from (a pinned revision of
velddev/Underdog-Bark-3.0-sherpa-onnx), that it is an unofficial conversion, and
that each file is checked against its bundled SHA-256 pin, as the Orukeet
section does.
---------
Co-authored-by: Jatin <jatinkrmalik@gmail.com>
The Input Device dropdown has a fixed width and cuts labels off at about
18 characters. Raw ALSA names such as "sof-hda-dsp: - (hw:0,6)" therefore
all look like "sof-hda-dsp: - (h…", and the tooltip always showed the same
generic text, so there was no way to tell which entry was selected.
The tooltip now shows the full label of the selected device. It updates
when the list is filled and every time the selection changes, and goes
back to the generic hint for System Default.
* fix(audio): bound capture reads and support cancellation
* fix(recognition): bound and serialize capture shutdown
* fix(recognition): cancel retries and report stalled microphones
* fix(audio): prefer pulse when the ALSA default is unavailable
* fix(recognition): guard late callbacks and finalize capture tests
* docs(audio): explain input selection and stall recovery
* fix(speech): enforce auto-pause across buffered reload timeouts
* fix(speech): recover auto-resume after reload timeout
* test(speech): fix auto-pause follow-up test regressions
* fix(speech): retain auto-resume after reconfigure timeout
* fix(tray): preserve auto-pause recovery across suspend
* test(capture-shutdown): give the bare tray the suspend path's shortcut managers
_on_system_suspend now releases the main and per-language hotkey
listeners before sleep (#1025); the fixture builds TrayIndicator without
__init__, so it needs the two attributes present.
* fix(tray): release hotkey listeners on suspend even while auto-paused
Merging #1025's suspend cleanup with this branch's auto-pause handoff
left an early return that skipped shortcut_manager.stop() whenever a
configured game was active, keeping EVIOCGRABs held through sleep. The
paused branch now only owns the model-lifecycle work; listener release
and stopping active recognition run for every suspend.
* fix(tray): hold the apply lock only around the download's verify and save
Wrapping the whole recommended-model download in _apply_settings_lock
blocked every GTK-thread settings apply (e.g. Test Dictation) for the
length of the transfer. The engine already serializes reconfigures, so
the lock only needs to cover verifying the download still owns the
receipt and saving it; an apply that lands mid-download now proceeds
instead of freezing the app.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(installer): add upgrade mode that keeps the existing configuration
Re-running install.sh on an existing install replayed first-time setup:
the mode menu offered only interactive or automatic, automatic mode
forced whisper.cpp regardless of the configured engine, and nothing said
config.json would be kept, even though docs/UPDATE.md promised the
installer updates in place and preserves configuration.
Detect an existing install early (venv activate + config.json) and offer
an upgrade path:
- The curl|bash mode menu gains 'Upgrade (keep current settings)' as the
default when an install is detected; the guided setup offers it first
too.
- A --upgrade flag runs the same path non-interactively for scripts, and
falls back to a normal automatic install when nothing is installed.
- Upgrade mode reads speech_recognition.engine from the existing
config.json instead of forcing whisper.cpp, reuses the venv, updates
that engine's dependencies, and rebuilds pywhispercpp only when the
installed version drifts from the pinned release or GPU support is
missing.
- config.json is never modified: every engine path that may rewrite it
(parakeet, faster_whisper, the whisper.cpp -> vosk fallback) goes
through a wrapper that is a logged no-op in upgrade mode, and
verify_configured_engine reports an unimportable configured engine
instead of repairing the file.
- The installer prints 'Existing configuration found at
~/.config/vocalinux/config.json - it will be kept.' when it detects
one.
Update docs/UPDATE.md with the upgrade behavior and add
tests/test_installer_upgrade.py covering detection, the mode menu
default, engine resolution from config, config immutability, the guided
offer, --upgrade parsing, and an end-to-end pty run of the piped
installer.
Fixes#1009
* test(installer): isolate the upgrade pty test from host changes
Skip system-dependency and model steps in the end-to-end installer run
so the only host-mutating calls are gated out entirely; the timed
interrupt is no longer the only thing between the marker and sudo.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(evdev): release EVIOCGRAB when forwarding cannot run (#1021)
With the internal evdev listener, Vocalinux holds EVIOCGRAB on physical
keyboards and re-emits through uinput clones. Two windows left the grab
held while nothing could forward:
- Suspend/resume: grabs persisted through sleep while the compositor's
input stack was rebuilt; the listener was torn down and rebuilt ~6s
after resume, and keystrokes typed at the GNOME lock screen in that
window went to a stale forwarding path.
- A monitor thread wedged mid-iteration (fd, lock, or a clone write that
never returns): grabs stayed held forever because the self-healing
path only fired on write exceptions, and the unexpected-exit cleanup
only ran when the thread died.
_on_system_suspend now tears the shortcut listeners down before the
machine sleeps, so no grab survives the suspend boundary; the existing
resume flow rebuilds them once /dev/input settles.
A forwarding watchdog now runs alongside the monitor. The monitor stamps
a heartbeat on every loop; if it goes stale while grab->clone pairs
exist, the watchdog runs the existing _release_failed_forwarder path on
each device so input reaches applications directly, and tracks them as
suspended. Once dispatching resumes, suspended devices are removed so
the rescan re-opens them through the standard open->clone->grab path.
Generation checks keep a recycled fd from ever being torn down under a
newer generation.
* fix(evdev): re-arm watchdog-released devices on the monitor thread
The watchdog closing a device fd races the monitor's in-flight select:
a dead descriptor in the select set trips the error handler, which
exits the loop and tears down every keyboard. Recovery now runs where
the close is safe — the monitor retires each suspended device at its
own loop top and the rescan re-opens it through the standard
open->clone->grab path. The watchdog only ungrabs (safe cross-thread:
that fd never sits in the select set) and marks the device suspended.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
On GTK's Wayland backend, a toplevel with the DIALOG type hint and no
transient parent becomes an orphan dialog: the next toplevel to receive
keyboard focus silently becomes its xdg_toplevel parent, and GNOME then
raises, minimizes, and workspace-moves the two windows as one.
Settings, the transcript viewer, and the log viewer are standalone
windows opened parentless, so the dictation pad (focused constantly)
adopted them, making Alt+Tab group them and minimize/workspace changes
apply to both. Force the NORMAL type hint on all three so they stay
independent.
Fixes#984
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(aur): gate source publication on attested sdists
Prepare and verify the canonical release source, build the exact PKGBUILD on Arch, and publish the gated artifact without further rewrites. Part of #701.
* fix(aur): isolate local gate and binary publication
Keep local gate distributions and diagnostics in a private workspace, preserving other build outputs and failed diagnostics. Publish vocalinux-bin after its own AppImage inputs without depending on the source gate or unrelated release jobs. Add regression coverage for both review findings.
* fix(clipboard): keep paste-injection copies out of clipboard history
The clipboard-paste injection path writes the dictated text to the
clipboard only to feed the paste chord, then restores the previous
content (#588, #646). Clipboard managers such as Klipper and cliphist
still record that transient copy, so every dictation lands in history.
Mark the injection copy with `wl-copy --sensitive`, which clipboard
managers honour by skipping the entry. The flag is probed once via
`wl-copy --help` (added in wl-clipboard 2.2) so older builds keep
working. It is only set when Copy to Clipboard is off: with that
setting on, the dictated text is meant to stay, so it remains an
ordinary copy. Restoring the user's previous content is never marked
sensitive. xclip/xsel have no equivalent and are unchanged.
* test(clipboard): address review on sensitive paste copies
- Move the unittest main guard below TestSensitivePasteCopy so running
the file directly also runs the new tests.
- Add -> None return hints to the new test methods.
- Cover _copy_to_clipboard forwarding the sensitive hint when a tool
declines and the next one is tried.
- Docs: history suppression needs wl-copy 2.2+ and a clipboard manager
that honours the hint; older wl-copy and the XWayland xclip/xsel path
still produce ordinary copies.
* fix(clipboard): type the wl-copy probe cache and adapt the X11 paste test
- Declare _wl_copy_sensitive_supported as a typed class default instead
of reading it through getattr(), which mypy (now in CI since #968)
flags as returning Any.
- test_plain_x11_clipboard_paste_uses_xdotool_and_x11_clipboard
asserted the exact _copy_to_clipboard kwargs. Check the text and the
X11 tools instead, so it does not depend on the sensitive hint (which
xclip ignores) or on the copy_to_clipboard setting.
* test(clipboard): wait for the restore thread instead of a fixed sleep
TestSensitivePasteCopy slept 0.5 s and hoped the background restore had
run. On a slow runner the patches could end first. Wait on an event set
by the restore copy, inside the patches, with a generous timeout.
* fix(recognition): never lose a stop that races start, and read capture outside the buffer lock
start_recognition() published LISTENING (and played the start cue) before setting should_record. A stop arriving in that window, e.g. a push-to-talk release handled on another thread, was undone by the later should_record = True, leaving the capture thread recording with no way to stop it. The session is now armed under _buffer_lock before LISTENING is visible.
The capture loop held _buffer_lock across source.read_chunk(). When a read blocks (the audio server reconfigures the device, a USB/Bluetooth mic stalls), stop_recognition() gives up its bounded join and then blocks forever on that lock, stuck in PROCESSING. The read now happens outside the lock and the session is re-checked before the chunk is appended.
* test(recognition): wait for the stalled read before stopping
Signal from inside the blocking read instead of sleeping after the first, non-blocking one, so the test always exercises the stalled-read path.
* test(recognition): run the stalled-read test against the real NumPy
Older test modules leave sys.modules["numpy"] as a MagicMock. Under that leak the capture loop's first chunk raised before the stalled read was reached, so the test failed in the full-suite order on CI. Restore the real module for these tests, as test_playback_ducker.py already does.
---------
Co-authored-by: AAG Projects Team <293887937+aagprojectsteam-max@users.noreply.github.com>
Use the maintained AyatanaAppIndicatorGlib 2.0 indicator when it is installed, mirroring the existing Gtk.Menu into a Gio.Menu whose actions keep stable, never-reused names and fire the original items. Fall back to the GTK 3 indicators otherwise; the AppImage stays on its bundled GTK 3 indicator, and the .deb recommends the glib typelib.
Fixes#1013
Inside the snap, list the sound server's capture sources with pactl and record from the chosen one by scoping PULSE_SOURCE to each PortAudio open (serialized behind one lock). Settings saves the stable source name, keeps each dropdown entry's server name so selection never queries the server from the GTK thread, and the snap reports whisper.cpp as CPU-only instead of probing host GPU tools.
Fixes#1011
Refuse close requests on the pre-mapped Wayland overlay so closing its GNOME dock entry no longer destroys it and brings back the first-recording focus loss, and unmap the overlay when it is turned off so the dock entry goes away immediately.
Fixes#1010