* refactor(installer): generate distro package map * fix(installer): handle Debian derivative package maps * docs(installer): document generated package map * fix(installer): validate package map and refresh derivative probes * fix(installer): name openSUSE package roles
9.5 KiB
Contributing to Vocalinux
Thanks for your interest in contributing. This guide covers setup, style, testing, and pull requests.
Code of conduct
Participation is covered by our Code of Conduct. Be respectful and constructive.
Ways to contribute
- Report bugs - Open an issue
- Suggest features - Feature request or Discussions
- Improve documentation - Fixes and clarity are always useful
- Fix bugs or add features - Check open issues, especially
good first issue
Development setup
Automated (recommended)
git clone https://github.com/YOUR-USERNAME/vocalinux.git
cd vocalinux
./install.sh --dev
This installs system dependencies, creates a venv from the system Python, installs the package in editable mode with dev tools, and runs the test suite.
install.sh always builds venv/ from the system Python (/usr/bin/python3, or $SYSTEM_PYTHON), because distro PyGObject is only importable from that interpreter. It ignores an activated virtualenv, so you can run it from a shell that still has uv's .venv active. See The two environments.
Manual setup
-
Fork and clone the repository.
-
Install system dependencies (examples):
Ubuntu 24.04+ (
just depspip-builds PyGObject 3.56 from the lock):sudo apt update sudo apt install -y python3-pip python3-gi python3-gi-cairo \ gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libgirepository1.0-dev \ build-essential pkg-config libcairo2-dev \ python3-dev portaudio19-dev python3-venv xdotoolDebian 12 cannot pip-build PyGObject 3.56 (glib 2.74). Use
./install.sh --devfor tests and running from source, thenvenv/bin/pytest/venv/bin/python -m vocalinux.main --debug.sudo apt install -y python3-pip python3-gi python3-gi-cairo \ gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository1.0-dev libcairo2-dev \ build-essential pkg-config \ python3-dev portaudio19-dev python3-venv xdotoolDebian 13+:
sudo apt install -y python3-pip python3-gi python3-gi-cairo \ gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libcairo2-dev \ build-essential pkg-config \ python3-dev portaudio19-dev python3-venv xdotoolAppIndicator (system tray):
# Older Ubuntu: sudo apt install -y gir1.2-appindicator3-0.1 # Debian 12+ or newer Ubuntu: sudo apt install -y gir1.2-ayatanaappindicator3-0.1 -
Create the environment and install:
# Requires uv (https://docs.astral.sh/uv/); creates .venv/ with dev + vad extras just depsjust depscompiles PyGObject from the lock into.venv. That needslibgirepository-2.0-dev(Ubuntu 24.04+). Debian 12 cannot build it; use Option 1 and the installervenv/instead. -
Run:
just run-source-debug -
Optional pre-commit hooks:
uv run --no-sync pre-commit installHooks are optional. CI runs the same checks.
The two environments
The repository uses two virtual environments on purpose. Do not merge them:
| Directory | Created by | Python | Used for |
|---|---|---|---|
.venv/ |
just deps (uv) |
pinned in .python-version |
dev tooling: pytest, black, mypy, just recipes |
venv/ |
./install.sh |
the system Python | running the installed app, which needs distro PyGObject |
gi (PyGObject) is the reason. just deps / uv sync build it from source into .venv/, which works wherever glib 2.80+ and libgirepository-2.0-dev are present (Arch, Fedora, Ubuntu 24.04 with that package, CI). Debian 12 cannot build PyGObject 3.56; use ./install.sh --dev and venv/bin/pytest. install.sh never reuses .venv/: it builds venv/ from the system Python with --system-site-packages, and rebuilds it if another interpreter created it. Set SYSTEM_PYTHON=/usr/bin/python3.12 ./install.sh on systems that ship several system interpreters.
Making changes
Branch naming
git checkout main
git pull origin main
git checkout -b feature/your-feature-name
# or fix/, docs/, refactor/, test/
Never push directly to main. Open a pull request for every change.
Code style
| Tool | Role |
|---|---|
| Black | Formatting (line length 100) |
| isort | Import sorting (black profile) |
| flake8 | Linting |
| mypy | Type checking (just typecheck) |
just format # black + isort
just lint # flake8 + black --check + isort --check
just typecheck # mypy src/
Project structure
vocalinux/
├── src/vocalinux/ # Application
│ ├── main.py
│ ├── speech_recognition/ # Engines + command processor
│ ├── text_injection/ # X11 / Wayland injection
│ ├── ui/ # GTK tray, settings, config
│ └── utils/
├── tests/
├── resources/ # Icons and sounds
├── docs/
├── install.d/ # Installer modules, including generated package data
├── packaging/ # AppImage, AUR, Flatpak
├── scripts/ # Maintenance tools and distro package-map source
├── snap/ # Snap recipe
└── web/ # Marketing site (Next.js)
| Task | Start here |
|---|---|
| Voice command | src/vocalinux/speech_recognition/command_processor.py |
| UI | src/vocalinux/ui/ |
| Recognition engines | src/vocalinux/speech_recognition/recognition_manager.py |
| Text injection | src/vocalinux/text_injection/text_injector.py |
| Settings / config | src/vocalinux/ui/config_manager.py, settings_dialog.py |
| Installer package names | scripts/distro-package-map.yaml |
Installer package map
scripts/distro-package-map.yaml is the source of truth for package names,
alternatives, and conditional package probes used by install.sh. Do not edit
the generated install.d/package_map.sh directly. After changing the YAML, run:
just distro-packages
just distro-packages-check
Commit both the YAML and generated shell module. Package-manager control flow
and installation policy remain handwritten in
install.d/system_dependencies.sh; user systems never parse the YAML. The
remote bootstrap's pre-checkout git installation is the deliberate exception
because the generated module is not available yet.
Testing
just test
just test-cov
uv run --extra dev --extra vad --group lint pytest tests/test_command_processor.py
- Place tests in
tests/astest_*.py - Aim for solid coverage on new code (roughly 80%+)
- Use
pytest-mockvia themockerfixture
def test_command_processor_new_line(mocker):
"""Test that 'new line' command returns correct action."""
processor = CommandProcessor()
result = processor.process("new line")
assert result.action == "new_line"
Remote API test server
python scripts/test_remote_server.py
python scripts/test_remote_server.py --port 9000
python scripts/test_remote_server.py --delay 2
Endpoints:
- whisper.cpp style:
http://localhost:8080/inference - OpenAI-compatible:
http://localhost:8080/v1/audio/transcriptions
In Settings → Speech Model, select Remote API, set the server URL, choose the endpoint format, and use Test Connection.
Pull requests
Automated agents may append three robot emoji (🤖) to the PR title to opt into the agent fast-track merge path when that process is active.
Before you open a PR
- Style checks pass (
just lint,just typecheck) - Tests pass (
just test) - Docs updated when behavior or install steps change
- Commits use Conventional Commits
type(scope): short description
Longer description if needed.
Fixes #123
Types: feat, fix, docs, style, refactor, test, chore
Examples:
feat(commands): add "select all" voice command
fix(tray): resolve icon not updating on Wayland
docs(readme): update installation instructions
Submit
- Push your branch to your fork
- Open a PR against
mainand fill out the template - Link related issues
- Wait for CI; address review feedback
- Maintainers squash-merge when approved
Releases
Maintainers follow docs/RELEASE_PROCESS.md (version files, docs, website, tag, automated publish). Do not tag releases from feature branches.
Community
- SUPPORT.md - where to get help
- Discord - fastest place to talk with maintainers and other contributors
- GitHub Discussions
- GitHub Issues
- @vocahq on X
License
Contributions are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), matching the other VocaHQ distribution projects (VocaMac, VocaPhone, VocaGateway). By opening a pull request, you agree that your contribution may be distributed under that license.