Files
voculinux/pyproject.toml
T
Jatin K MalikandGitHub fb9166ed9e feat(settings): run local VocaGateway from Advanced (podman-first) (#774)
* feat(settings): run local VocaGateway from Speech Engine (podman-first)

Add an optional Local VocaGateway panel next to Remote Server so Linux users
can start the pinned v0.1.0 compose project with podman (docker fallback),
watch Live/Pairable/Ready status, pair a phone without loopback QR URLs, and
preset remote_api to the gateway transcriptions endpoint. Stop preserves
volumes; Flatpak fails closed; tray offers Stop only when this session manages
the gateway. Default on-device engine stays whisper.cpp.

* fix(gateway-embed): drop nonexistent compose cpu profile

VocaGateway v0.1.0 exposes default service gateway plus native/cuda/vulkan
profiles, not cpu. Start and stop the default service without --profile, keep
the latest image pin rejection, and prefer save_config for the remote_api preset.

* fix(gateway-embed): keep compose --profile cpu per embed contract

v0.1.0 has no cpu profile entry, but Compose still starts the unprofiled
gateway service when --profile cpu is set, and keeps cuda/vulkan/native off.
That matches the frozen desktop embed contract and stays forward-compatible.

* fix(gateway-embed): harden embed path for settings and compose

Keep podman/docker probing off the GTK thread, reject link-local and
loopback public URLs for phone QR, sanitize compose .env image overrides,
and cap bootstrap token reads so a hostile file cannot blow memory.

* fix(gateway-embed): keep LAN toggle and phone QR in sync

When Allow LAN flips while compose is already managed, rewrite publish
host and PUBLIC_URL and recreate the gateway before Pairable comes back.
Also reject default docker/podman bridge addresses for phone QR.

* test(gateway-embed): pin bridge and LAN-gate pairing cases

Cover 172.17.0.2 / link-local not becoming Pairable, and the mid-run
LAN toggle path that must wait for compose republish before advertising
a phone URL.

* style(gateway-embed): black format embed modules

* fix(gateway-embed): reject short-form loopback and localhost.* for QR

Align Pairable/QR host checks with vocagateway desktop-embed policy so
127.1 / localhost.* never become phone-reachable display URLs.

* fix(gateway-embed): detach Settings status listener on destroy

Gateway poll callbacks were left registered after Settings closed, so
idle_add could touch destroyed GTK widgets while compose stayed up.

* fix(gateway-embed): pass host_env to bridge ip probe

_bridge_iface_ips shells out to host ip; without host_env the AST
gate fails and AppImage bundles can hand the wrong GLib to ip.

* fix(packaging): ship gateway_embed in the AppImage

The explicit setuptools packages list omitted the new package, so the
bundle booted from source trees but died with ModuleNotFoundError on
every distro boot test.

* style(packaging): black the gateway_embed package guard

* fix(settings): pack local VocaGateway into the Advanced island

The rebase onto main auto-merged textually onto the simple Speech Model
page. Keep the gateway card on advanced_box, next to Remote Server.

* fix(gateway-embed): treat missing Compose as unavailable

When podman or docker is present but Compose is not, stop advertising Run.
Return empty compose_args, show an install-Compose hint, and cover it with a unit test.

Also add module loggers on the new gateway_embed modules and annotate the
gateway Settings/tray callbacks Greptile flagged. just lock found no lockfile drift.

* fix(gateway-embed): keep Allow LAN bind matched to the switch

Snapshot the LAN flag for start/republish, record what compose actually
got, then republish again if the switch moved mid-flight so 0.0.0.0 never
lingers while Allow LAN reads off.

* fix(gateway-embed): detect leftover compose after Quit

Quit does not stop compose, so a LAN-published gateway can keep running.
Settings and the tray treated that as Stopped because managed_by_us is
only process memory. Probe compose after runtime detection, show Stop
when it is actually up, and let Stop work even if this session did not
start it.

* fix(gateway-embed): restore LAN switch from leftover compose

Quit leaves compose running, so the next launch now reads
VOCAGATEWAY_PUBLISH_HOST from the embed .env, adopts the project as
managed, and keeps Allow LAN honest so republish can close 0.0.0.0.

* fix(gateway-embed): restore .env if LAN republish fails

compose recreate can fail after write_env_file already pointed
VOCAGATEWAY_PUBLISH_HOST at loopback. Restore the previous .env so
orphan adopt still sees a live 0.0.0.0 bind, and snap Allow LAN back
to the running publish mode.

* style(gateway-embed): type helper args and black MagicMock

* refactor(types): move Readable protocol to common_types

AGENTS.md keeps Protocol interfaces in common_types. Pairing now imports
the shared Readable surface instead of a local _Readable helper.

* fix(gateway-embed): satisfy mypy on rebased embed code

* fix(audio): keep duck restore point when a set reports failure
2026-09-29 15:33:27 -07:00

230 lines
6.1 KiB
TOML

[build-system]
# >=77 for the SPDX license string below. No upper bound: Arch extra
# python-setuptools is 84, and the AUR PKGBUILD builds with --no-isolation.
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
name = "vocalinux"
dynamic = ["version"]
description = "A seamless voice dictation system for Linux"
readme = "README.md"
license = "AGPL-3.0-only"
license-files = ["LICENSE"]
authors = [
{name = "Jatin K Malik", email = "jatinkrmalik@gmail.com"}
]
maintainers = [
{name = "Jatin K Malik", email = "jatinkrmalik@gmail.com"}
]
requires-python = ">=3.11"
keywords = [
"voice", "dictation", "speech", "recognition",
"linux", "accessibility", "voice-typing", "speech-to-text"
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: X11 Applications :: GTK",
"Intended Audience :: End Users/Desktop",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Desktop Environment :: Gnome",
"Topic :: Multimedia :: Sound/Audio :: Speech",
"Topic :: Text Processing",
"Topic :: Utilities",
]
dependencies = [
"pywhispercpp>=1.5.0,<2",
"pynput>=1.7.6,<2",
"evdev>=1.6.0,<2; sys_platform == 'linux'",
"requests>=2.28.0",
"numpy>=1.22.0,<3",
"pyaudio>=0.2.13",
"PyGObject; sys_platform == 'linux'",
"psutil>=5.9.0,<8",
]
[project.optional-dependencies]
vosk = [
"vosk>=0.3.45,<0.4",
]
whisper = [
"openai-whisper>=20231117",
"torch>=2.13.0",
]
parakeet = [
"sherpa-onnx>=1.12.0,<2",
]
faster_whisper = [
"faster-whisper>=1.0.0,<2",
]
vad = [
"onnxruntime>=1.16.0",
]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-mock>=3.10.0",
"pytest-timeout>=2.1.0",
"pre-commit>=3.0.0",
"mypy>=1.0.0",
]
docs = [
"sphinx>=6.0.0",
"sphinx-rtd-theme>=1.2.0",
]
# Formatters and linters, deliberately outside the `dev` extra: `uv sync
# --only-group lint` installs them without the project, so the lint job needs
# no portaudio/girepository headers to build pyaudio and PyGObject.
[dependency-groups]
# install.sh installs this wheel-only toolchain before any source builds, then
# disables build isolation so pip cannot resolve another unpinned environment.
installer-build = [
"pip>=25.3,<27",
# Torch 2.13 supports setuptools 82+, so keep this at the patched version
# for setuptools' 2026 sdist normalization advisory.
"setuptools>=83",
"wheel",
"meson-python>=0.16",
"cmake==3.31.10",
"ninja==1.13.0",
"repairwheel",
"setuptools-scm>=8",
]
lint = [
"black>=23.0.0",
"isort>=5.12.0",
"flake8>=6.0.0",
]
[project.scripts]
vocalinux = "vocalinux.main:main"
[project.gui-scripts]
vocalinux-gui = "vocalinux.main:main"
[project.urls]
Homepage = "https://github.com/VocaHQ/vocalinux"
Documentation = "https://github.com/VocaHQ/vocalinux/tree/main/docs"
Repository = "https://github.com/VocaHQ/vocalinux"
Issues = "https://github.com/VocaHQ/vocalinux/issues"
Changelog = "https://github.com/VocaHQ/vocalinux/releases"
[tool.uv]
# Lock/exports are generated with uv; keep the minor pinned so lockfile
# upgrades are deliberate (see `just lock`).
required-version = ">=0.12,<0.13"
[tool.setuptools]
# Resource dirs carry no .py files, but setuptools still treats them as packages.
packages = [
"vocalinux",
"vocalinux.audio",
"vocalinux.gateway_embed",
"vocalinux.resources",
"vocalinux.resources.icons",
"vocalinux.resources.icons.scalable",
"vocalinux.resources.sounds",
"vocalinux.speech_recognition",
"vocalinux.speech_recognition.data",
"vocalinux.speech_recognition.engines",
"vocalinux.text_injection",
"vocalinux.ui",
"vocalinux.ui.keyboard_backends",
"vocalinux.utils",
]
package-dir = {"" = "src"}
[tool.setuptools.dynamic]
version = {attr = "vocalinux.version.__version__"}
[tool.setuptools.package-data]
"*" = ["*.svg", "*.wav", "*.onnx"]
"vocalinux" = ["resources/**/*"]
"vocalinux.speech_recognition" = ["data/*.onnx", "data/LICENSE"]
"vocalinux.utils" = ["model_checksums.txt"]
[tool.black]
line-length = 100
target-version = ['py311', 'py312', 'py313', 'py314']
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| venv
| _build
| buck-out
| build
| dist
| node_modules
)/
'''
[tool.isort]
profile = "black"
line_length = 100
skip_gitignore = true
skip = [".git", "venv", "node_modules", "build", "dist"]
[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --strict-markers --timeout=10 --timeout_method=thread"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
"audio: marks tests that require audio hardware",
]
filterwarnings = [
"ignore:The NumPy module was reloaded:UserWarning",
"ignore:There is no current event loop:DeprecationWarning",
]
[tool.coverage.run]
source = ["src"]
branch = true
omit = [
"*/tests/*",
"*/__pycache__/*",
# GTK dialog files are untestable without a real GTK environment
"*/settings_dialog.py",
"*/logging_dialog.py",
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
]
show_missing = true
[tool.mypy]
# Target the supported floor so typechecking covers what requires-python promises.
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true
exclude = ["venv", "build", "dist", "node_modules"]
# numpy 2.x ships stubs written in 3.12 syntax, which a 3.11 target cannot parse.
# We only pass arrays through, so skipping the stubs costs little compared to
# type-checking against the Python version we actually promise to support.
[[tool.mypy.overrides]]
module = ["numpy", "numpy.*"]
follow_imports = "skip"
follow_imports_for_stubs = true