Dasher is an information-efficient text-entry interface, driven by continuous pointing gestures. It lets you write using eye gaze, a mouse, a switch, a joystick, or touch — designed for accessibility and augmentative communication (AAC).
This is the GTK frontend, built on the shared DasherCore engine.
dasher.at — downloads, user docs, and live demo Feature status — what each platform supports All repos — engine, frontends, design guide
In development — early-stage GTK4 frontend aiming to replace Dasher 5. See the feature matrix for what's implemented.
Runtime controls live in the footer bar: the alphabet chooser, speed, a Learning toggle, colour and font choosers, and the Speech toggle. Keyboard mode sits on the top toolbar, and dwell-to-click is under Preferences → Input.
The live typing-rate readout (RFC 0012) lives under Preferences → Output,
showing characters per second and words per minute (e.g. 4.2 cps · 50 wpm) in
subtle text. It is computed engine-side over a short rolling window and refreshed
about twice a second, with a Reset button that restarts the measurement. See
RFC 0012.
Prebuilt Linux packages are attached to each Release:
- Flatpak — needs the GNOME 50 runtime
(
flatpak install flathub org.gnome.Platform//50), thenflatpak install --user Dasher.flatpakandflatpak run org.alternativeinterface.dasher. - AppImage —
chmod +x Dasher-x86_64.AppImage && ./Dasher-x86_64.AppImage(self-contained).
macOS and Windows aren't packaged yet — build from source (below). How the artifacts are produced is described under Packaging & releases.
Speech and Keyboard mode each depend on an external service:
- Speech (spoken feedback and read-aloud) needs a working text-to-speech
engine. A from-source Linux build enables the
systemfeature, which drives a runningspeech-dispatcher(sudo apt install speech-dispatcher). The Flatpak and AppImage shipcloud-only, so configure a cloud engine under Preferences → Speech. With no engine available the Speech toggle stays disabled. See TTS Support for how the feature set is chosen at build time. - Keyboard mode (types Dasher's output into other applications) uses
ydotool. Install it and run theydotoolddaemon (it needs access to/dev/uinput); on Debian/Ubuntu that issudo apt install ydotool, on Archsudo pacman -S ydotoolplussudo systemctl enable --now ydotool— the binary alone is not enough, Arch does not enable the service for you. Availability is checked by probing the daemon (a zero-length pointer move), and if injection fails mid-session the mode turns itself off with a message rather than dropping output silently. Clicking the Keyboard button (on the top toolbar) when ydotool isn't working opens a setup dialog with the install command for your distro and a Retry, instead of enabling the mode.
Dwell to click (hover in place to click, under Preferences → Input) needs no external dependency.
| Platform | Command |
|---|---|
| macOS | brew install gtk4 gtkmm4 pkg-config cmake |
| Linux (Debian/Ubuntu) | apt-get install build-essential libgtk-4-dev libgtkmm-4.0-dev git cmake pkg-config libspeechd-dev libclang-dev speech-dispatcher ydotool |
| Windows | Install GTK from GVSBuild to C:\gtk, add C:\gtk\bin to PATH. Requires CMake, Git, and MSVC or Clang. Use an optimized release build for binary compatibility. |
All platforms additionally require a Rust toolchain (cargo) to build the bundled
rust-tts-wrapper. Install it from rustup.rs. On Linux the
system TTS feature binds speech-dispatcher via bindgen, hence libspeechd-dev
(headers) and libclang-dev (for bindgen) above. The speech-dispatcher and
ydotool packages are runtime dependencies for the optional Speech and
Keyboard-mode features (see Optional runtime features);
the app still builds and runs without them (the Speech toggle greys out, and
Keyboard mode offers an in-app setup helper).
git clone --recursive https://github.com/dasher-project/Dasher-GTK.git
cd Dasher-GTK
mkdir build && cd build
cmake ..
cmake --build . --config Release --parallelThe runtime files are placed in build/Dasher/, and so is the binary, except
on Windows, where the default generator is Visual Studio. That one picks the
configuration at build time (hence --config above) and puts the executable in
a per-configuration subdirectory, so it lands in build/Dasher/Release/. The
data files are not per-configuration and stay in build/Dasher/, which is why
the two part company there.
run.py does the above and launches the result, on Linux, macOS and Windows
alike. It is a convenience wrapper, not a required part of the build:
python run.py # configure, build, then launch
python run.py --build-only # stop after the build
python run.py --tests # build, then run the ctest suite
python run.py --clean # start the build directory over--build-dir (default build), --build-type (default Release) and -j/--jobs
change where and how it builds; anything after a -- separator is forwarded to
Dasher itself.
It checks for the system dependencies listed above and names the package to
install when one is missing, initialises the submodules if you cloned without
--recursive, falls back to -DTTS_WRAPPER_FEATURES=cloud when the
speech-dispatcher headers are absent, and launches from the right working
directory (see Running for why that matters).
If a submodule is checked out at a commit other than the one this repository
records, the launcher says so and leaves it alone, since that is usually
somebody testing against a different engine revision. --sync-submodules
resets them to the recorded commits.
Dasher must be launched from the build/Dasher/ directory so it can find its
data files. Started from anywhere else it comes up looking fine, but every
letter box is the same size, because the language model found no training text.
The binary is lowercase dasher on Linux and Dasher on macOS:
cd build/Dasher
./dasher # './Dasher' on macOSOn Windows it is Dasher.exe, one directory deeper, and the working directory
still has to be build/Dasher for the data files:
cd build\Dasher
.\Release\Dasher.exeLightweight unit tests live in tests/ and build alongside the app; doctest is
fetched automatically at configure time, so no extra dependency is required.
After configuring and building, run the suite with ctest:
ctest --test-dir build --output-on-failureOn Windows add -C Release (or whichever configuration you built), for the
same reason cmake --build needs --config: a multi-config generator has no
single configuration for ctest to assume, and without it the suite reports no
tests found.
CI runs these tests on every push as part of the multi-platform workflow.
The rust-tts-wrapper submodule provides text-to-speech support. It is included
automatically when cloning with --recursive. CMake builds and links it if the
submodule is present.
- macOS: builds with
avsynth,cloudfeatures (no local speech-dispatcher needed) - Linux: builds with
system,cloudfeatures (uses speech-dispatcher + cloud engines); needslibspeechd-devandlibclang-dev(see Prerequisites) - Windows: builds with
sapi,cloudfeatures (uses the Windows SAPI engine plus cloud engines) - All platforms need a Rust toolchain (
cargo) onPATHto compile the wrapper
Override the default feature set with -DTTS_WRAPPER_FEATURES=... at configure
time — e.g. -DTTS_WRAPPER_FEATURES=cloud for a cloud-only build with no
speech-dispatcher dependency (this is what the Flatpak uses).
The feature set is decided at configure time and stored in the CMake cache, so
installing libspeechd-dev after a cloud-only build does not change anything
on its own: that build directory stays cloud-only until you reconfigure it from
scratch (run.py --clean, or delete build/).
The CMake build copies data files into build/Dasher/Data/. The directory layout
after building:
build/Dasher/
├── dasher # executable (lowercase on Linux; `Dasher` on macOS)
├── libdasher.so # Linux (libdasher.dylib on macOS, dasher.dll on Windows)
├── librust_tts_wrapper.so # TTS wrapper, unless built without it
├── UIStyle.css
├── Data/
│ ├── alphabet.*.xml # alphabet definitions
│ ├── color*.xml # colour schemes
│ └── training*.txt # language model training data (PPM)
├── Strings/
│ └── strings_*.json # UI translations
└── Resources/
└── License/
Dasher uses a PPM (Prediction by Partial Match) language model trained on text
files. Each alphabet definition specifies a trainingFilename
(e.g. training_english_GB.txt). Without training data, all letter boxes will be
the same size and prediction will not work. Training files are copied from
DasherCore/Data/training/ during the build; if letters appear uniformly sized
after launch, run from build/Dasher/ so the "Data" relative path resolves, and
rebuild if stale (cmake --build build).
- Keyboard mode and system Speech aren't available in the Flatpak and
AppImage builds. Both need host-level access the self-contained, sandboxed
formats can't provide:
/dev/uinput(viaydotoold) forydotoolkeyboard injection, andlibspeechdplus a runningspeech-dispatcherfor system TTS. The Flatpak iscloud-only for speech; Keyboard mode expects a from-source or native install. See Optional runtime features.
Linux is distributed as Flatpak and AppImage, both built by
.github/workflows/publish.yml.
Flatpak — manifest: packaging/flatpak/org.alternativeinterface.dasher.yaml
(GNOME 50 runtime + the rust-stable SDK extension; builds rust-tts-wrapper
with TTS_WRAPPER_FEATURES=cloud). The manifest bundles the working tree via a
type: dir source, so run flatpak-builder from outside the repo to avoid
copying its build directory into itself. --install-deps-from=flathub pulls the
runtime, SDK and rust-stable extension at the versions the manifest declares:
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
repo=$(pwd)
mkdir -p /tmp/dasher-fb && cd /tmp/dasher-fb
flatpak-builder --user --install --force-clean --install-deps-from=flathub build \
"$repo/packaging/flatpak/org.alternativeinterface.dasher.yaml"
flatpak run org.alternativeinterface.dasherAppImage — bash packaging/build-appimage.sh produces Dasher-x86_64.AppImage.
Cutting a release — a v* tag push makes the publish workflow build both
artifacts and attach them to a new GitHub Release. Two things must be true
before you tag, or CI refuses the release:
- The metainfo carries this release's entry — add a
<release version="…">block at the top of<releases>inpackaging/org.alternativeinterface.dasher.metainfo.xmland merge it. The validate workflow fails the tag push on a mismatch, and the publish workflow's release job hard-gates on the same check — a tag without its entry will not ship packages (that gate exists because v0.2.10 shipped with the check red). - The DasherCore submodule is pinned at an upstream tag, not a floating commit — tag DasherCore first and bump the pin.
The safe path is the helper, which enforces both plus the usual hygiene
(clean tree, on main, tag not taken) and refuses to tag otherwise:
Scripts/tag-release.sh --push v0.2.12 # verifies, tags, pushesFreshness is enforced by the publish workflow's tag-sanity job, which
runs within seconds of the tag push and gates both artifact builds — a tag
that doesn't point at origin/main's tip never starts a build. (The
validate workflow repeats the check as an early warning, but the
enforcement lives in publish, where failure actually blocks the release.)
If a bad tag already went out: add the missing metainfo entry, merge, then delete and re-push the tag — validate re-runs green and the publish workflow updates the existing release in place.
Dasher-GTK includes opt-in anonymous usage analytics and crash reporting to
help prioritise accessibility work. It is off by default — nothing is sent
until you turn it on under Preferences → Privacy. All Dasher frontends share
one self-hosted PostHog project; the complete event
schema is published in analytics-events.json.
- Collected only after opt-in: app launches, the input method / alphabet you
select, which settings tab you open, and crash reports. Each event carries a
random anonymous ID (resettable under Privacy) plus
platform,app_variant,app_version,os_version, and$os. - Never collected: the text you type, clipboard contents, canvas contents, your name / email / account, training text, or game-mode targets.
- Never located: every event, crash reports included, sets
$geoip_disable. PostHog Cloud otherwise derives city, postal code and approximate coordinates from the sending IP address even with project-level IP anonymisation enabled, and this per-event flag is the only control that suppresses it — so no location is ever derived from your connection (RFC 0001: no location data). - Crash reports capture the exception type and the last lines of the engine
log, with home-directory paths and email addresses scrubbed before anything
leaves your device. An uncaught C++ exception also carries its message and
the live log tail; a native signal (SIGSEGV/SIGABRT/SIGILL) is handled in an
async-signal-safe path that writes a minimal record with no stack trace, and
recovers the log tail from the mirrored
engine.logon the next launch. A crash is written locally and only transmitted (as a PostHog$exception) on the next launch if you have opted in; otherwise it is discarded after 7 days.
Design details are in the org RFCs 0001 (analytics) and 0009 (crash reporting).
This frontend consumes DasherCore through its C API (src/Engine/DasherBridge.cpp,
backed by dasher.h). DasherBridge owns the engine handle, feeds it GTK pointer
input, and receives draw commands that RenderingCanvas renders onto a GTK widget.
InputManager/DwellClickHandler translate raw input, and TtsService /
DirectModeService handle output and spoken feedback. The Analytics module
keeps a bounded engine-log ring buffer and installs crash handlers, powering the
opt-in analytics and crash reporting described under
Privacy & analytics.
flowchart LR
Input["GTK pointer / keys"] --> InputMgmt["InputManager<br/>DwellClickHandler"]
InputMgmt --> Bridge["DasherBridge"]
Bridge <-->|"C API (dasher.h)"| Core[("DasherCore<br/>engine")]
Core -.->|"draw commands"| Canvas["RenderingCanvas"]
Bridge --> Output["TtsService<br/>DirectModeService"]
See DasherCore's C API for the engine contract.
| Path | Purpose |
|---|---|
src/Engine/ |
DasherBridge + CommandRenderer: C API bridge to DasherCore |
src/Input/ |
InputManager, DwellClickHandler (pointer/switch input) |
src/Output/ |
TtsService, DirectModeService (speech + output modes) |
src/Preferences/ |
Settings UI (PreferencesWindow, SettingsSection) |
src/UIComponents/ |
Reusable GTK widgets (canvas, synced controls) |
src/Analytics/ |
Opt-in analytics + crash reporting (PostHog, RFC 0001/0009) |
tests/ |
doctest unit tests |
packaging/ |
Flatpak manifest + AppImage build script (Linux distribution) |
DasherCore/ |
DasherCore submodule (do not edit here — PR upstream) |
rust-tts-wrapper/ |
TTS wrapper submodule |
Thirdparty/SDL |
SDL submodule (joystick input) |
See CONTRIBUTING.md for build details, code style, and DCO sign-off. For project-wide conventions (code of conduct, RFCs, security), see the org contributing guide.
Please file bug reports in the issues of this repository. To join the development group, send a pull request or reach us via Slack (OpenAAC).
MIT — see LICENSE.