Skip to content

How it works

  • Wake word — OpenWakeWord detects "Hey Jarvis" instantly
  • Speech-to-text — faster-whisper transcribes commands locally
  • Text-to-speech — Piper provides voice feedback
  • Mouse control — GNOME Shell extension with Clutter virtual input
  • Browser scroll — JavaScript injection via qutebrowser IPC
  • Dictation — AT-SPI accessibility framework

All processing happens locally. No data leaves your machine.

File structure

easyspeak/
├── pyproject.toml
├── src
│   ├── extension.js           # GNOME Shell extension (bundled as package data)
│   ├── extension-helpers.js   # Pure JS helpers imported by extension.js
│   ├── metadata.json          # Extension metadata
│   ├── core
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   ├── about.py           # Standalone libadwaita "About" window
│   │   ├── cli.py             # CLI entry point: argument parsing + logging
│   │   ├── config.py          # Tuning constants + Whisper model factory
│   │   ├── gnome_extension.py # Installs/refreshes/enables the extension
│   │   ├── log.py             # Logging setup
│   │   ├── main.py            # EasySpeak class + main loop
│   │   ├── mediakeys.py       # Replays multimedia keys via Mutter
│   │   ├── speech.py          # Persistent piper -> player pipeline
│   │   └── tray.py            # Panel indicator + asleep lifecycle
│   └── plugins
│       ├── __init__.py
│       ├── 00_eyetrack.py     # Head tracking (experimental)
│       ├── 00_mousegrid.py    # Grid overlay mouse control
│       ├── apps.py            # Application launcher
│       ├── browser.py         # Qutebrowser control + auto-config
│       ├── dictation.py       # Voice-to-text + AT-SPI enablement
│       ├── files.py           # Folder navigation
│       ├── media.py           # Playback controls
│       ├── sleep.py           # Voice deactivate
│       ├── system.py          # Volume, brightness, DND
│       └── zz_base.py         # Help and exit
└── tests
    ├── acceptance             # Gherkin scenarios (pytest-bdd)
    ├── benchmarks             # pytest-benchmark suites for bencher.dev
    ├── integration            # tests against real binaries
    └── unit                   # tests for src/core/ and src/plugins/

GNOME Shell extension

On first start, core auto-installs the GNOME Shell extension to the user-local extensions directory (unless GNOME already sees it via a system-wide install). Files copied:

~/.local/share/gnome-shell/extensions/easyspeak@local/
├── extension.js
├── extension-helpers.js
└── metadata.json

The extension powers both the panel indicator and the mouse grid. On Wayland, GNOME Shell only scans for extensions at login, so a oneshot systemd user unit (installed by EasySpeak) re-copies the bundled files before the shell loads them at each login. See core.gnome_extension for the full lifecycle.