-
Notifications
You must be signed in to change notification settings - Fork 4
Contributing
WM Keyboard has CI but no formal contribution process, issue templates or
CODEOWNERS yet. Mostly there's just a codebase to fit into cleanly. This page
covers how the repository is organized and styled, and where a first change is
easiest to land, so your pull request needs as little back-and-forth as
possible.
The repository is a single Gradle build of 20 Android library modules plus
:app: 17 under :core:* (config, common, language, input,
keyman, prediction, emoji, theme, icons, tools, content,
addons, voice, settings, feedback, plugins, intelligence) and 3
under :feature:* (tools, addons, ime). Two more sit outside that count:
the host-side :tools:dictc (see below), and :feature:llm, a dynamic
feature module settings.gradle.kts only includes when the Play channel flag
is on. A class's module is visible from its file path, not its package.
Everything still lives under com.wasimaster.wmkeyboard.*.
Dependencies only point downward, and that's a real Gradle project graph rather than a convention people remember to follow:
-
:core:configsits at the very bottom. It carries the build flags and API keys every other library reads. -
:core:commonbuilds on:core:configfor shared utilities every module needs. - The rest of
:core:*is one engine or store per feature area, each depending on only the lower modules it actually needs.:core:settingsdepends on eleven other:core:*modules because the settings screens surface controls for all of them, while:core:feedbackneeds just:core:settingsand:core:common. -
:feature:*sits above:core:settings, since the feature modules read the settings repository.:feature:ime(the keyboard runtime itself) depends on all 17:core:*modules plus:feature:tools. -
:appsits on top of everything: the settings activity, manifest, and assets, wired to all 20 library modules.
No module declares a dependency on anything above it in that list. There's no
dedicated boundary-checking task either, because the Gradle project graph is
the constraint. If a dependency direction ever looks surprising, read
:app/build.gradle.kts and the module's own build.gradle.kts.
One module breaks the pattern deliberately. :tools:dictc, the host-side
dictionary compiler (pure JVM, no Android), doesn't depend on
:core:prediction as a project. Its build.gradle.kts adds that module's
source directory as an extra Kotlin source directory and filters it to the
seven files it needs (Trie.kt, TrieWalker.kt, TrieCompleter.kt,
PackedTrie.kt, PackedTrieCodec.kt, RankFloorCache.kt,
DictionaryLoader.kt), alongside its own Main.kt. The compiler and the
reader share one literal implementation,
so they can never drift apart.
- Architecture The full module-layering diagram, plus the design decisions behind the IME, prediction, and persistence.
No autoformatter runs over this codebase. .editorconfig at the repo root
explains why: a mass reformat of a codebase this size would bury every real
finding in diff noise. So it documents the style the code already follows
instead of enforcing one. The values that matter most are a 4-space indent, a
140-character line length, Android Studio's ktlint code style, and unlimited
star-import thresholds (star imports are allowed). New code should look like
the code around it.
Static analysis runs in two layers, both configured under config/:
-
detekt (
config/detekt/detekt.yml), layered on detekt's own defaults.maxIssuesis0andexcludeCorrectableisfalse, but the rule weights are deliberately uneven. Complexity, comment and deprecation rules are turned down to zero weight, while correctness rules stay at full weight, because those are the ones that catch actual bugs. The input-dispatch and layout-resolution code is genuinely branchy, so style metrics like cyclomatic complexity aren't a useful signal there. -
Android Lint (
config/lint/lint.xml). The app module turns oncheckAllWarnings(roughly 200 checks that are off by default) andabortOnError, and escalates specific checks (NewApi,WrongThread,Recycle,StaticFieldLeak,HandlerLeak,MissingPermission, and others) toerror, each with an inline comment explaining why.
One task runs everything:
./gradlew staticAnalysisThat runs Android Lint against the app module's full flavor, detekt with full
type resolution against both the full and lite flavors of the app module plus
its unit tests, and per-module detekt across 19 of the 20 library modules.
(:core:config has no Kotlin sources to check, and :tools:dictc is skipped
deliberately. See below.) CI doesn't run this task, so run it yourself before
you ask for review.
- Dictionaries The two bundled seed word lists live in this repo; hundreds more live in the companion data repository.
- Emoji keyword packs 125 downloadable language packs, generated by scripts under tools/emoji/ and served from the data repository.
- Themes Built-in themes are Kotlin, not JSON: a shareable theme needs no code change at all.
A few things worth knowing before you start on any of these:
-
Dictionaries are mostly not in this repo.
app/dictionaries-src/bundles exactly two seed lists (en.txtandbn.txt, each a plainword countline per entry) compiled into.wmdictassets at build time. The much larger catalog of downloadable word lists is fetched at runtime from a separate repository, wasi-master/wmkeyboard-data, perDictionaryCatalog.kt. A new or improved word list for an existing language almost always means a pull request to that companion repository rather than this one. The dictionary pipeline page covers the format. -
Emoji keyword packs follow the same split. The 125 downloadable packs
(per
EmojiDictCatalog.kt) also come fromwmkeyboard-data. What lives in this repo is the generation tooling undertools/emoji/(generate_gemoji.py,export_keyword_pack.py,generate_catalog.py,add_names.py,generate_dict_catalog.py,generate_animated.py). -
Built-in themes are Kotlin, not data files. There are eleven built-in
theme families, 28 themes once the colour variants nested inside them are
counted. Most are
themeFromSeed(...).copy(...)calls in theBuiltInThemeslist incore/theme/.../ThemeSpec.kt. Six more sit incore/theme/.../PaletteThemes.kt. Five are ports of well-known editor palettes (Dracula, Nord, Solarized, Catppuccin, Tokyo Night) that carry their own attribution rules. The sixth is the original Cyberpunk palette, which has no upstream to attribute. Either way, contributing one means a Kotlin change to:core:theme. The.wmtheme.jsonformat you may have seen is the export format a user gets from sharing a theme they built in the app, and it requires no change to this codebase at all. See sharing themes and, for distributing one more widely, addon repositories. -
Docs are this site. Everything under
docs/src/content/docs/is an Astro/Starlight site with its own house rules, written down in the docs project's ownCONTENT_GUIDE.md: page anatomy, component usage, and the "verify every claim in code" ground rule. Read that first if you're adding or editing a page.
Commits in this repo follow Conventional Commits:
type(scope): summary, lowercase, present or imperative tense, for example
fix(addons): validate repo URL scheme or docs(tools): rewrite the search page. The common types are feat, fix, docs, perf, refactor,
test, build, chore, ci and style. Scopes generally track a module or
doc section name (addons, plugins, tools, icons, emoji, typing,
lint, about, and so on). Repo-wide changes drop the scope entirely (docs: ...,
feat: ...).
Before you open a pull request, run the checks a reviewer would otherwise have to run for you:
./gradlew staticAnalysis
./gradlew testFullDebugUnitTestSee Testing for what that second task covers and where its tests live.
Note
GitHub Actions runs the unit tests, a full and lite debug assemble, the
advisory evals and a docs link-check on every pull request
(.github/workflows/ci.yml). staticAnalysis is not in CI, and there's no
pull request template or CODEOWNERS file, so the static-analysis half of the
checklist above is still a recommendation rather than a gate.
Issues and pull requests belong on github.com/wasi-master/wmkeyboard, the repository the app's own About screen points at. Changes to word lists or emoji keyword packs belong on wasi-master/wmkeyboard-data instead, per the "Picking a first contribution" section above.
-
Lite-flavor detekt only runs where lite actually differs.
staticAnalysisruns a separate lite-flavor detekt pass for just:core:voice,:core:intelligenceand:feature:ime, the only modules whosesrc/litesource set replaces real implementations (ML Kit, LiteRT) with stubs. Every other module's full and lite sources are identical, so a second pass over them would repeat the first. -
:tools:dictcis excluded fromstaticAnalysison purpose. Its source set adds:core:prediction's source directory, so analyzing it would report those same seven files a second time under a different module name. -
A stricter local pass exists, but it's Kotlin-only.
:appand all 20:core:*/:feature:*library modules gateallWarningsAsErrorsin theirbuild.gradle.ktson awarningsAsErrorsGradle project property (./gradlew build -PwarningsAsErrors=true), off by default. The two modules outside that set,:tools:dictcand the Play-only:feature:llm, don't read it. Android Lint's ownwarningsAsErrorsflag is hardcoded tofalseinapp/build.gradle.ktsand doesn't read that property. The two are separate switches that happen to share a name. - Third-party licenses and attribution for bundled dependencies, data-pack sources, and online services the app calls are listed on the in-app About & licenses screen. Check there before adding a new third-party dependency or data source, since it needs an entry too.
-
One repo URL is cased differently in the app.
SOURCE_URLincore/common/.../Support.ktisgithub.com/wasi-master/WMKeyboard, while the docs site and the F-Droid recipe use the canonical lowercasegithub.com/wasi-master/wmkeyboard. Both resolve, but the lowercase form is the canonical one.
- Home
- Getting started
- Typing
- Languages
- Suggestions & correction
- Emoji & expression
-
Tools
- Clipboard manager
- Voice typing
- Offline voice (Whisper)
- Handwriting
- Scanner (OCR, QR, documents)
- Camera tool
- Translate
- Search, Wikipedia & dictionary
- Media controls
- AI chat
- AI tools
- Utility tools
- Snippets & text expansion
- Text editing & cursor tools
- Instruments
- Trackpad
- Calendar
- App launcher
- Learn from text
- Vocabulary
- Resize the keyboard
- The toolbar
- Themes & appearance
- Addons
- Plugins
- Privacy & security
- Accessibility
-
Reference
- Gesture cheat sheet
- Typing
- Hardware shortcuts
- Deep links & launcher shortcuts
- Key press
- Link builder
- Dictionaries & words
- File formats
- Languages
- Importing from other keyboards
- Appearance
- Importing from Espanso
- Keyboard themes
- Keyboard font
- Troubleshooting
- Glossary
- Icons
- Easter eggs
- Layout & size
- Key layouts
- Rows & bars
- Keyboard modes
- Emoji
- Phone number formats
- Tools
- Addons & plugins
- Reference - Accessibility
- Fingerprint lock
- Reference - Data saver
- Reference - Permissions
- Privacy
- Reference - Selection actions
- Servers
- Reference - Backup & restore
- About & diagnostics
- Statistics
- Settings A–Z
- Development