# WM Keyboard — complete documentation > A modern, privacy-first Android keyboard — offline intelligence, 843 languages, themes, tools and an addon ecosystem. Source: https://wmkeyboard.pages.dev/ · Licence: MIT · Generated from the site's own pages. Every page of the documentation follows, in sidebar order, each under its own URL. Cite the URL of the section you use rather than this file. --- # WM Keyboard URL: https://wmkeyboard.pages.dev/ Summary: A modern, privacy-first Android keyboard: offline intelligence, 1,555 languages, themes, tools and an addon ecosystem.

The short version

## Why WM Keyboard Dictionaries, prediction, autocorrect, transliteration and emoji search all run on device. No telemetry, no cloud round-trips. The network is opt-in, one tool at a time. You start in the languages your phone is already set to. Avro-style Bangla phonetic typing, CJK input and downloadable dictionaries for 330+ languages are all here, plus musical notation, braille chording and Morse. Clipboard manager, translator, OCR & QR scanner, voice typing (online and offline Whisper), handwriting, GIFs & stickers, calculator, media controls, even an on-device LLM. Material You themes with a full editor, icon packs, addon repositories you can self-host, and sandboxed Python plugins that add new tools to the keyboard. glide Trace a path over the letters and lift your finger. The glide engine runs on the same dictionaries as prediction, so it works in every language, Bangla included. Cursor swipes, long-press popups and spacebar gestures ride along. tools/whisper-catalog — The Whisper model catalog inside WM Keyboard settings Dictate with Whisper running entirely on device. Pick from 29 models, tiny ones for older phones up to big multilingual ones, and nothing you say leaves the device. There is a lighter online engine too, if you want it. This is the Bangla National (Jatiya) layout, rendered live from the same key data the app ships. Hover any key to peek at its long-press alternates, or tap shift for the second layer. Avro phonetic typing, custom layouts and per-field language detection are all part of the same system. privacy/on-device-learning-toggles — The on-device learning toggles in settings Prediction, autocorrect and learning all happen offline. Every tool that touches the network says so up front and stays off until you switch it on. Learned words can be inspected, exported or cleared at any time.

Pick a door

## Jump in Install & set up — Get the APK, enable the keyboard, and pick your languages. — /start/installation/ A two-minute tour — The strip, the toolbar and the panels, in the order you'll meet them. — /start/tour/ Languages & layouts — Add languages, switch between them, and edit or build your own layout. — /languages/overview/ The tool belt — Clipboard, translate, voice, OCR, handwriting and the rest of the panels. — /tools/overview/ Suggestions & autocorrect — Tune prediction, autocorrect confidence, smart chips and your personal dictionary. — /smart/suggestions/ Themes & appearance — Material You themes, the theme editor, fonts, icon packs and backgrounds. — /themes/overview/ Privacy at a glance — What stays on device, what the network is used for, and how to turn it off. — /privacy/overview/ Every setting, explained — The reference section documents every screen, toggle and slider. — /reference/settings/

Free and open source

Get WM Keyboard

No ads, no tracking, MIT licensed. Get it from Google Play, F-Droid or GitHub and make it yours.

Install guide Google Play Latest release

Writing about WM Keyboard? Logos, screenshots and copy are in the media kit.

--- # Which build you have URL: https://wmkeyboard.pages.dev/start/editions/ Section: Getting started Summary: The standard, offline and F-Droid builds, what the F-Droid build leaves out, and how to tell which one you have. WM Keyboard is one app built from one source tree. Up to 0.6.2 it came in two editions, Full and Lite. From 0.6.3 there is no Lite: every build has the same features, and what differs is where the larger parts come from. - **Standard**, from GitHub and Google Play. Feature libraries such as the handwriting recogniser or the offline voice engines are not in the APK. Each one downloads the first time its feature is used, from Play on a Play install and from the project's own data repository otherwise. - **Offline**, from GitHub. The same app with no internet permission at all. Like the standard build it leaves the feature libraries and the other interface languages out, and since it can't fetch them, you [import](https://wmkeyboard.pages.dev/privacy/offline/#the-offline-build) the ones you want from files fetched elsewhere. An all-languages version, for Android 7 to 10, has the 48 languages built in. - **F-Droid.** The standard build without Google's SDKs (ML Kit, LiteRT and LiteRT-LM), which F-Droid does not accept. Its feature libraries download only after you agree to it under Privacy, since that skips F-Droid's own checks. start/editions-toolbox-full — The Tools panel on a standard build: Handwriting, Text scan, QR & barcode, Document scanner and Grammar check all appear. ## What the F-Droid build leaves out The F-Droid build is compiled with one switch on, `wmkb.enableFdroid`, which leaves Google's SDKs out and puts small stand-ins where the code called them. No tool goes missing from the toolbox: every tool that runs on a Google library elsewhere has an open-source one in its place there, and two of them read less. Grammar check is in every build, and so is the Harper spell checker under Android's system spell-checker settings, because F-Droid compiles Harper from source. The document scanner is in every build. Where Google Play services can run Google's scanner it opens that one; on the F-Droid build, and on a phone without Play services, it opens the keyboard's own [built-in scanner](https://wmkeyboard.pages.dev/tools/scanner/#scanning-a-document) instead. Text scan is in every build too. ML Kit's text recognizer, Tesseract's library and the LiteRT engine are all left out of the F-Droid build, so it reads with [PaddleOCR](https://wmkeyboard.pages.dev/tools/scanner/#which-languages-it-reads) alone, run by MNN, the open-source engine the sticker generator also uses. That covers Chinese, Japanese and Latin-script text such as English or French, after one download of 21 MB. Other scripts read there only with the **Online model** reader you set up yourself. A standard build without Play services keeps ML Kit's Latin reader and Tesseract, because they come with the app. The QR & barcode scanner is in every build too. ML Kit's barcode reader is a Google library, so the F-Droid build reads codes with [zxing-cpp](https://wmkeyboard.pages.dev/tools/scanner/#which-reader-decodes-codes) instead, an open-source reader that knows the same formats and a few more. So does the Google Play copy on a phone without Play services, where ML Kit's reader has no model. The result card, the Insert, Copy and Open buttons and pairing with WMK Desktop work the same with either reader. Handwriting is in every build, but the F-Droid one reads fewer languages, and less accurately. ML Kit's recogniser, which covers 126 languages, is a Google library; in its place the F-Droid build has zinnia, an open-source recogniser with models for Japanese, Simplified Chinese and Traditional Chinese, and [PaddleOCR's text recognisers](https://wmkeyboard.pages.dev/tools/handwriting/#paddleocr-for-other-scripts) for Latin, Cyrillic, Arabic, Devanagari, Tamil, Telugu, Thai, Greek and Korean writing. For a language in any other script, Bangla among them, the panel says that this version cannot read it. The "Handwrite" option under **What a swipe does** follows the same rule. See [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/), [Scanner](https://wmkeyboard.pages.dev/tools/scanner/), and [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/) for what each tool does in detail. ## Other parts the F-Droid build lacks A few more features sit inside tools that every build has: - **Offline voice engines.** Every build offers [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) with its whisper.cpp models, [offline Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), English dictation that writes the words while you speak, [offline Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) for 25 European languages and [offline Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/), a small quick English model. The F-Droid build lacks Whisper's older LiteRT models and [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/), whose library F-Droid can't build from source. The system recognizer stays the default everywhere until you switch it. - **LiteRT-LM models for on-device AI.** The F-Droid build has the [AI writing tools](https://wmkeyboard.pages.dev/tools/ai/) "On your device" provider, but it runs only the [GGUF models](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp) on llama.cpp, which F-Droid builds from source. The LiteRT-LM list (Gemma 4, Gemma 3, Qwen and SmolLM2 as `.litertlm` or `.task` files) is left out, and importing one of those files says it needs the Google Play or GitHub build. - **Smaller pieces.** The sticker editor's one-tap background remover, ML Kit's on-device translator (Bergamot, the open-source one, is in every build), Google Home in the smart home tool, the NPU as a compute choice, and search by meaning. ## Why the download is small None of the builds that can reach the internet carry their feature libraries in the APK. On Play each arrives as an on-demand part the first time its feature is used: the LiteRT-LM runtime, ML Kit's translator, the LiteRT interpreter behind offline Whisper, the sticker editor's own background remover and the scanner's PaddleOCR reader, ML Kit's handwriting recogniser, the whisper.cpp, ExecuTorch and Whistle libraries behind the other offline voice engines, the Tesseract library behind the scanner's offline reader, zxing-cpp behind the QR scanner where ML Kit's reader is not used, and the Harper library behind the grammar tool and the spell checker. GitHub's standard build fetches the same libraries itself, checked against a hash the APK carries, and so does the F-Droid build once you allow it. Most of what that saves is runtime *library code*, not language data. The biggest models these features use (handwriting recognition per language, Whisper's transcription graphs, LLM weights) download on demand into app storage in every build, so none of those sit in the APK either. The offline build leaves out the same libraries, and takes each one as an import of the file the standard build would have downloaded, checked against the same hash. Up to 0.6.3 it carried every library and language instead, at about ten times the size. ## Privacy doesn't change between builds The F-Droid build is a build with fewer Google parts, not a more private one. Every feature it lacks or reads differently already runs on-device and says so in its own settings caption. Handwriting recognition "runs fully on your device, with Google ML Kit or, for Chinese and Japanese, zinnia." Text scan says "no photo and no text leaves your device, and it works offline." Document scanning says "the processing runs on your device." Whisper "runs fully on your device across many languages and sends no audio anywhere." The on-device AI caption says the text you run an action on "stays on your device." The privacy policy the About screen opens does differ, because ML Kit reports usage to Google and the F-Droid build has no ML Kit to do so. ## Telling which build you have About / App / Version start/editions-about-version — Settings → About → Version names the build, build type, store and version code. The Version row shows "Standard build (release) · code …", or "Offline build" for the offline one, with " · F-Droid" or " · Play Store" after the build type when the app came from a store. The toolbox doesn't tell them apart: every tool is in both. ## Switching builds There's no in-app switch. Which build you have depends on which APK you installed. They share a package name and the release signing key (F-Droid publishes its build with the project's own signature, from 0.5.10 on), so installing another build over your current one is a normal app update. Your settings, themes, learned words and the rest of the app's data stay in place. A GitHub install from 0.6.2 or earlier that was Lite moves onto the standard build at its next update. ## Details & edge cases - In the F-Droid build, Text scan's **Text recognition** setting offers only Automatic and Online model, since ML Kit and Tesseract aren't there. A choice of either restored from another build's backup reads as Automatic. - Build release APKs on different machines without sharing a signing keystore and the packages carry different signatures. Installing one over the other then fails the way any mismatched-signature update does (see [Installation → Details & edge cases](https://wmkeyboard.pages.dev/start/installation/#details--edge-cases)). - Exported debug logs stamp which language build (`intl` or `en`) produced them, which the bug-report bot needs to read the crash trace. --- # FAQ URL: https://wmkeyboard.pages.dev/start/faq/ Section: Getting started Summary: Quick answers to the questions everyone asks first. Quick answers to the questions people ask before they've read anything else. Each one links to a fuller page if you want the details. ## Is anything sent to the cloud? No, not by default, and not for typing itself. WM Keyboard's prediction, autocorrect, gesture typing, and dictionaries all run entirely on your device, and there's no analytics SDK anywhere in the app. The one exception worth knowing is Google's ML Kit, which handwriting, OCR and QR/document scanning are built on: it reports its own diagnostics to Google while you use those features, never anything you typed. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/). What does need the internet is a handful of optional toolbar tools that are online services by nature: weather (Open‑Meteo), the dictionary look‑up, translate (every build can also translate on the device, with no connection, once you download the languages), currency conversion, Wikipedia, GIF/sticker search (KLIPY, GIPHY), and web/image search (Brave, Tavily), plus any AI tool you set up with your own API key. Almost all of them make a request only when you open and use the tool. Link previews are the one opt‑in exception. Turn that on and copying a URL anywhere on the device, or scanning a QR code, fetches the page title in the background without any WM Keyboard tool being open. It's off by default, and Power saving can drop it along with the other background network fetches. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full tool‑by‑tool breakdown of what gets sent where. There is no release without the internet permission, but you can build one: [Building](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-internet-permission) takes one extra property, and everything that runs on the device keeps working in it. start/faq-privacy-summary — The Privacy screen's on-device summary, with the data-clearing controls below it. Privacy This screen also controls learning, incognito, and Android's own backup. See [Where are my learned words stored?](#where-are-my-learned-words-stored-and-how-do-i-wipe-them) below, and [Your data on device](https://wmkeyboard.pages.dev/privacy/data/) for the complete inventory of what's stored and where. ## Why does Android warn me when I enable the keyboard? Because that warning is standard Android behavior for *any* third‑party keyboard. WM Keyboard doesn't add it and can't turn it off. Every input method has to see everything you type to work at all, Gboard and SwiftKey included, so the system shows a generic "this input method may be able to collect the text you type" notice the first time you turn on a keyboard that didn't come with the phone. None of that dialog's wording is WM Keyboard's. It belongs to Android's own Settings app. Tapping "Enable keyboard" during setup opens Android's input method settings directly, which is where that warning lives. See [Enable the keyboard](https://wmkeyboard.pages.dev/start/setup/) for the full setup flow. What happens to your typing once the keyboard *is* enabled is the previous question's answer. Your typing never leaves the device, and the rest is limited to the optional online tools and the opt-in link-preview fetch already covered above. ## Why doesn't glide typing work in my language? Because the language has no word list installed. A swipe is decoded against the active language's dictionary, and English and Bangla are the only ones with a list built in. French, German, Spanish and every other language get theirs from a download under Languages & layouts, on the language's own page. Until then a swipe has nothing to match and lands as the keys it crossed. With glide typing on, the keyboard tells you: the first time you type in such a language, a chip on the suggestion strip reads "Glide typing needs a French word list", and tapping it opens the download. It asks once per language and then never again, and it only asks where a download would help: English and Bangla never raise it, because their list is in the app. If a swipe does nothing on one of those two, the list is switched off rather than absent: check **Word list** on the language's page. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/#turning-it-on) for the other things a swipe needs, and [Dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for the lists. ## Why is Ask AI or Look up in Chrome's selection menu but not in other apps'? Because the app you are in decides who is in that menu, not WM Keyboard. Since Android 11 an app sees only the other apps it has asked to see in its own manifest, and the selection menu is built inside that app. Chrome is allowed to see every app, so **WM Keyboard**, **Ask AI** and **Look up** always show up there. Other apps see the keyboard only if their own list happens to cover it, which differs from app to app, and an app that can't see it leaves its entries out, often along with Google Translate's and ChatGPT's. No setting or reinstall on the keyboard's side changes that. Before deciding they are missing, open the menu's three dots and scroll to the end of that list, where the entries often sit last. Where the entries are missing, use the keyboard's own [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/#turning-it-on): it reads the selection in any box you are typing into and offers Translate, AI and the rest, in every app. See [When the entry is missing](https://wmkeyboard.pages.dev/smart/selection-actions/#when-the-entry-is-missing) for text you can't type into. ## Does it use much battery or memory? Ordinarily very little, and there's a dedicated mode if you want it to use even less. Nothing runs in the background out of the box. The keyboard is a normal Android input method process, alive while you're typing and free for the system to pause or reclaim like any other app. Three service components ship in the app (two in the Play Store build, which has no accessibility service), and every one of them is inert until you switch it on: a notification listener (feeds the media-control tool and the one-time-code chip, and needs the notification-access grant), an accessibility service that keeps the keyboard's own gestures working while a screen reader runs (it reads nothing, and needs its own grant), and a scheduled job for the automatic backup, which is off by default. Turn none of them on and the keyboard exists only while a text field has focus. One thing keeps it alive after you switch to another keyboard: WM Keyboard is also a spell checker, and while Android's **Spell checker** setting (under System › Languages & input) names it, the system keeps the app running to check other apps' text, so a force stop in the app settings has it straight back. Pick another spell checker there, or turn it off, and the process goes once you switch keyboards. The two heaviest things it can hold in memory are an offline Whisper voice model and a local LLM. Both are released the moment Android signals memory pressure, and both reload the next time you use them. Word lists use compact, mostly on-disk structures rather than big in-memory maps, so even large downloaded dictionaries barely touch the Java heap. If you're on a storage- or RAM-constrained device, two things help further: - **Leave heavy features alone.** Their libraries are not in the APK until a feature asks for one, so a tool you never open costs no storage. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/). - **Power saving** is a dedicated mode that trims the costliest per-keystroke work: vibration, key sounds, animations, the glide trail, Words on the keys, background network look-ups, screenshot watching, and the on-device voice/handwriting models, either by hand or automatically. Tools / Power saving By default, power saving never switches on by itself: only the toolbar tile or the settings switch turns it on, so the keyboard never changes how it feels without you asking. If you want it automatic, **Turn on by itself** can follow Android's own battery saver, a low battery ("The battery is low", 20% out of the box), or either, and those automatic triggers are ignored while the charger is plugged in. Gesture typing and the key‑press popup stay on even under power saving, for opposite reasons. The popup is cheap to draw. Gesture typing is the most CPU-expensive thing the keyboard does, and it stays on anyway, because dropping it would be the most noticeable loss for the people who installed the keyboard for it. Every item here is its own switch, so keep what matters to you and drop the rest. ## Where are my learned words stored, and how do I wipe them? In a plain JSON file in the app's private storage, and nowhere else. It's `user_lexicon.json`, alongside a dozen more files that quietly track things like emoji usage, undone corrections, CJK picks and language‑mix confidence, listed in full below. Before the phone's first unlock there's no file to write to at all. Anything typed in that window stays in memory for the session and is thrown away once the device unlocks, never queued up to save later. Two different screens manage it, depending on whether you want to edit individual words or wipe everything: start/faq-personal-dictionary — Personal dictionary: every learned word, with per-word delete and an Add word button. Typing / Suggestions / Personal dictionary Lists every word the keyboard has learned, with a per‑word delete and a button to add your own. Words you add here read "You added this word." Words picked up from typing read "Seen 4 times" instead. Changes take effect immediately, even if the keyboard is already open. Privacy / Your data / Learned words The bulk wipe. **Delete the learned words** takes out all 13 learning files in a single pass: - the learned words, and the new words still waiting to be counted - your hand and tap-position models - your per-app and overall language‑mix confidence - the two glide-typing learning stores - your emoji usage - the corrections you undid, and the separate autocorrect-fix memory - your CJK picks, and the dictionary's word ranking Nothing personal is left behind for the keyboard to keep guessing from. Words you added by hand go with them, because the Personal dictionary screen above writes into that same learned-words file rather than a list of its own. Snippets and imported word lists are separate files and stay put. A running keyboard follows along rather than needing to be restarted. The settings app bumps a version number the keyboard service watches, and the service drops its in‑memory copies the moment it sees the change, so the words it was holding can't be written straight back over the emptied files. Learning itself can be switched off entirely, or paused for a while, from the same Privacy screen: - **Learn from typing.** On by default. The master switch. Turning it off erases nothing already learned, it only stops anything new being added. - **Incognito mode.** Off by default. Pauses learning and clipboard capture until you turn it back off. Existing learned words are left alone either way. - **Follow private browsing.** On by default. Turns incognito on by itself in apps and fields that mark themselves private, like Chrome's incognito tabs and most password managers, for as long as that field has focus. See [Your data on device](https://wmkeyboard.pages.dev/privacy/data/) and [Incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) for the complete inventory and behavior of each. ## Where can I find or share themes? Most people find them on the [add-on store](https://wmkeyboard.pages.dev/addons/), which lists every theme, layout, font and sound in the repositories the app knows, with a live preview, and installs one with **Open in app**. To share your own, export the theme from the theme editor and send the file: anyone who opens it with WM Keyboard gets asked whether to import it. For something that stays listed, publish it in a repository of your own; the store's [builders](https://wmkeyboard.pages.dev/addons/new/) make the files and the repository for you, and anyone can add it by its address. The [Telegram group](https://t.me/wmkeyboard) has a topic just for themes and layouts, too. ## Can I read these docs offline, or see what changed? Yes to both. [`/wm-keyboard-docs.zip`](https://wmkeyboard.pages.dev/wm-keyboard-docs.zip) is every page of this site as a Markdown file, in folders by section, built with the site so it is never older than what you are reading; any notes or Markdown app opens it. Each page also ends with the date it last changed and a **See what changed** link, which opens its history on GitHub: one entry per change, each showing the lines that changed. ## How do I report a bug or request a feature? Export a diagnostic report from the app first. Then use one of the two **Feedback** rows on the About screen. **Report a bug** opens the project's GitHub issue tracker. **Email the developer** opens a pre-filled draft to `arianmollik323@gmail.com`, for anything you'd rather not post publicly. An issue is the better first stop, because it's searchable and can be tracked to a fix. About / Diagnostics This screen shows what the keyboard has recorded since it last started, plus any crash it didn't survive. That includes the size of the navigation bar the keyboard was handed and any time its window was shorter than the keys, so a report of the keyboard sitting on the navigation bar or cut off at the bottom is worth sending with it. The page states plainly that nothing you type is ever recorded here. **Share diagnostics** hands the report to Android's share sheet as a file, so no app that only accepts short text can truncate it. **Copy to clipboard** gives you the same report as plain text. An **Include the system log** toggle, off by default, adds the process's full Android log for a hard-to-reproduce bug. Read that log before you send it anywhere: every library in the app writes to it, not only code the keyboard controls. Holding the crash text on that screen copies it on its own, with the `version:` and `android:` lines placed in front of it. Those two lines are worth keeping whatever else you trim, since they say which build and which Android version the crash came from. Paste that report into the issue whole, including the header lines at the top. A crash in a release build reads `at q3.a(SourceFile:15)`, because the code is compiled with its names shortened, so the trace names nothing. The header says which build the crash came from, and a bot on the repository uses it to fetch that build's name table and post a readable version of the trace back on your issue, usually within a couple of minutes. Trimming the report to "just the crash part" is what stops that working. **About → Source code** opens the project's GitHub repository, where you can open an issue or start a discussion the same way you would for any open-source project. If what went wrong is something the [AI tool](https://wmkeyboard.pages.dev/tools/ai/#reporting-a-result) generated rather than the keyboard misbehaving, use the **Report** button on the AI panel instead. It builds a draft that quotes the generation itself, which a bug report wouldn't. ## Details & edge cases - The Android "collect the text you type" warning and the request for `BIND_INPUT_METHOD` have nothing to do with any specific permission WM Keyboard asks for. Both come from registering as an input method, which every keyboard app must do. - "No telemetry" means no analytics or crash-reporting SDK is built into the app. It doesn't mean no network code exists. The optional tools above are real HTTP clients, and the opt-in link-preview fetch is a real background request, gated by its own switch rather than by opening a tool. - Power saving's automatic trigger ignores Android's per-percentage battery broadcast, which fires on every single point drop. It watches coarser signals instead: battery‑low, battery‑okay, charger in and out, and the system saver being toggled. So it reacts no more often than it needs to. - Clearing learned words does not touch your typing *settings*: autocorrect, suggestions, and every other toggle stay exactly as you left them. It only removes the words and history the keyboard has picked up from use. - Words the keyboard learns from your typing can optionally mirror into Android's own system‑wide personal dictionary, shared with other apps and keyboards. That's a separate, off‑by‑default toggle on the Privacy screen (**Add words to the system dictionary**), because it writes outside the app. Words you add by hand from Personal dictionary's Add word button stay local to WM Keyboard. Only words picked up from typing get mirrored. --- # Installation URL: https://wmkeyboard.pages.dev/start/installation/ Section: Getting started Summary: Install from Google Play or F-Droid, download the standard or offline APK from GitHub, or build from source, and install it on your device. Install it from [Google Play](https://play.google.com/store/apps/details?id=com.wasimaster.wmkeyboard) or [F-Droid](https://f-droid.org/packages/com.wasimaster.wmkeyboard/), or grab a signed APK from the releases page and install that like any other app. Building it yourself works too, and takes a few minutes if you already have the Android SDK. ## Installing from Google Play Open the [WM Keyboard listing](https://play.google.com/store/apps/details?id=com.wasimaster.wmkeyboard) and tap **Install**. There is nothing to pick: Play carries the standard build and serves your phone the right CPU architecture on its own. The Play copy differs from the GitHub APKs in a few ways: - **Heavier parts arrive when you first need them.** The on-device AI runtime, the on-device translation engine, handwriting recognition and the interpreter behind offline voice typing and the sticker editor's background remover are not in the first install. The first time you use one, Play downloads it, and the feature carries on by itself once it lands. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/). - **ML Kit comes from Google Play services** rather than from inside the app, so OCR and QR scanning use the copy Play services already keeps. Without Play services the QR scanner reads with zxing-cpp instead. - **Updates come from Play.** The app has no updater of its own there. When a new version is out, the settings app can offer it through Play's in-app update prompt. - **Two device-side extras are left out:** the accessibility service that keeps keyboard gestures working under TalkBack, and the photos permission that lets new screenshots turn up in the clipboard. Install from GitHub or F-Droid if you need either. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). - **Play signs it with its own key.** Android only updates an app with a build signed by the same key, so you cannot install a GitHub or F-Droid APK over the Play copy, or the other way round. To switch, export a backup, uninstall, install from the other source, and import the backup. See [Updating and what carries over](#updating-and-what-carries-over). Its privacy policy is [Privacy policy (Google Play edition)](https://wmkeyboard.pages.dev/privacy/policy/). ## Downloading a release APK Each release on the [GitHub releases page](https://github.com/wasi-master/wmkeyboard/releases) has three builds, **standard**, **all languages** and **offline**. Most people want standard. The release page puts it first. 1. Open the latest release on GitHub. 2. Take the **standard** build, unless you want a keyboard that can never reach the network. **Standard** carries the keyboard and English, and fetches the rest when you use it. Features that need their own code (offline voice typing, grammar checking, the scanner, handwriting, on-device translation and on-device AI) download it the first time you use them. [Every feature library](https://wmkeyboard.pages.dev/privacy/offline/#every-feature-library) is listed with what it adds. On Android 11 and later, the app's own screens download another language when you pick it, about 2 MB each. On Android 7 to 10, the standard build's screens stay in English. Every download is checked against a checksum built into the app. **All languages** is the standard build with all 48 interface languages built in, so nothing has to be downloaded to see the app in yours. Take it on Android 7 to 10. It installs over the standard build and back, and its updater keeps you on it. **Offline** has the internet permission removed, and is otherwise the standard build: English, with no feature libraries inside. It can't download anything, including its own update check, so you fetch the interface languages and feature libraries you want on another device and [import them](https://wmkeyboard.pages.dev/privacy/offline/#the-offline-build), along with any language packs, models and GIF packs. Interface languages need Android 11 or later; on Android 7 to 10, take **offline, all languages** instead, which has all 48 built in. See [A build with no internet permission](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-internet-permission). 3. Pick your CPU architecture. Almost every phone made since 2017 is `arm64-v8a`. Older or budget devices are `armeabi-v7a`. Emulators and a few tablets are `x86_64`. The `universal` APK holds all three and works anywhere, at several times the download size. The offline build comes in `arm64-v8a` and `universal`, the offline all-languages build in `universal` only. 4. Download the APK to your device. 5. Optional: verify the download. The release page shows a SHA-256 next to each file. Run `sha256sum ` on a computer, or use a checksum app on the phone, and compare the two. 6. Open the APK to install it. The release page carries the real size of every file next to its link, so check there rather than here. Up to 0.6.2, each release also had a Lite edition. If you installed it from GitHub, the app's updater moves you to the full build with the same languages: your settings stay. 0.6.3 had no real all-languages build (its `full-intl` files were copies of the standard one), so an all-languages install that updated to 0.6.3 is on the standard build; from 0.6.4 it can go back with About, then App language, or by installing the all-languages APK. From 0.6.3 there is no Lite edition anywhere; F-Droid builds the standard app without Google's SDKs. ## Building the APK yourself 1. Install a JDK 17 or newer, and the Android SDK. The dictionary-compiler build tool pins its own JDK 17 toolchain. Foojay provisions that toolchain for you if you do not already have one. 2. Clone the repository. From the repository root, run one of these commands: ```sh ./gradlew assembleIntlDebug # every language ./gradlew assembleEnDebug # English only ``` The task name is the language build, then the build type. `Intl` is all 48 languages and `En` is English alone. Add `-Pwmkb.enableFdroid=true` to build what F-Droid builds, without Google's SDKs. For a release build, replace `Debug` with `Release`. A release build needs your own signing keystore. Without one the APK comes out unsigned, and Android refuses to install it. The debug builds above are signed with the local debug key and install fine on your own device. 3. Find the APK under `app/build/outputs/apk/intl/debug/`, or the directory matching whichever task you ran. 4. Copy the APK to your device and install it. The build produces one APK per CPU architecture only when `wmkb.splitApks` is on, which is how the release APKs above are made. With the flag off, a release build packs all three architectures into one APK, and a debug build carries `arm64-v8a` alone. See [Building from source](https://wmkeyboard.pages.dev/development/building/) for the full set of build flags. start/first-launch — First launch after install, before the setup wizard starts. ## Choosing a build The GitHub builds and the Google Play copy have every feature. F-Droid's is the fdroid, which leaves out the parts that come from Google: ML Kit's handwriting and scanners, the LiteRT runtimes behind the original Whisper models and the local AI model, and the Whistle voice engine. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) for the complete comparison and how to tell which build you're running. ## Interface languages This is about the words the app shows you, not the ones you type. Every layout, every dictionary and every prediction model is in every build. The app's own text exists in 48 languages besides English. F-Droid, the Google Play copy and the offline all-languages build carry or fetch them on their own. The standard GitHub build carries English and, on Android 11 and later, downloads your phone's language by itself a few seconds after it first starts (about 2 MB). Pick another under About, then App language. The offline build takes the same language files, but [imported](https://wmkeyboard.pages.dev/privacy/offline/#interface-languages) from the release page. You can also set it under Settings, then Apps, then WM Keyboard, then Language, on Android 13 and up. The translations are machine translations from Google Play, and nobody has read them line by line, so expect the occasional clumsy sentence. A string with no translation yet falls back to English. See [Languages overview](https://wmkeyboard.pages.dev/languages/overview/) for the list and the caveats. ## Installing from the APK Android blocks installs from outside the Play Store by default. The first time you open the APK, Android shows an "install unknown apps" prompt. Grant the permission to whichever app you opened the file with (Files, a browser download, and so on), then continue. This is standard Android behavior, not anything WM Keyboard adds. Opening the app for the first time drops you straight into a short setup wizard rather than a home screen. See [Enable the keyboard](https://wmkeyboard.pages.dev/start/setup/) for what each step does. Once you finish the wizard, it won't run again on later launches. ## Updating and what carries over Installing a new build over an existing install (same package, same signing key) works like any Android app update. Your settings, themes, learned words, snippets and everything else stay exactly as they were, automatically. F-Droid builds and the GitHub APKs share a key from 0.5.10 on, so you can move between them the same way. The copy from Google Play is signed with Play's own key, so it can't be swapped for either of them in place. Move across with a backup, the same way as below. F-Droid still compiles its copy from source, and it publishes one only after checking it is identical to the `-fdroid.apk` on the same GitHub release. From 0.6.2, F-Droid builds one APK per CPU architecture and installs the one that fits your phone, instead of one APK carrying all three. Each release's version code now ends in a digit for the architecture (1 for `armeabi-v7a`, 2 for `arm64-v8a`, 3 for `x86_64`, 0 for `universal`), and the GitHub APKs carry the same digits, so the `arm64-v8a` APK from GitHub and the one from F-Droid still install over each other. A `universal` APK of the same version can't go over an F-Droid install, because its version code is lower; the next release installs as usual. The exception is an install from F-Droid of 0.5.9 or older. Those were signed with F-Droid's own key, and Android will not update an app to a build signed with a different one, so F-Droid can't update it to 0.5.10. To move over once: export a backup (see below), uninstall, install WM Keyboard from F-Droid again, and import the backup. Every update after that is ordinary. Reinstalling from scratch or moving to a new phone is different. Nothing carries over on its own unless you set one of these up beforehand. Data / Backup & restore Export your configuration to a file, keep the file or move it to another device, then import it after reinstalling. You choose what each export includes. The defaults are: | Included by default | Off by default | |---|---| | Settings, themes, snippets, custom word lists, addon repository list, emoji history, typing statistics | API keys inside settings, stickers, icons, learned dictionary, clipboard history | Import always shows a confirmation before it writes anything, so restoring isn't something you discover after the fact. Privacy / Android backup / Back up with Android This separate toggle is off by default. It opts the keyboard into Android's own backup and phone-to-phone transfer system, which is independent of the manual export above. Leave it off and Android carries no WM Keyboard data across a device switch on its own. Turn it on and Android's platform-level backup includes this app the next time it runs. ## System requirements - **Android 7.0 (API 24) or newer** is the floor the app is built against. It targets Android 16 (API 36). - **Supported CPU architectures:** `arm64-v8a`, `armeabi-v7a`, and `x86_64`. Generic 32-bit `x86` is not supported. Only the 64-bit `x86_64` is. - Camera, microphone and sensor access are all optional. Every hardware-backed tool (camera scanner, voice dictation, compass) declares the hardware feature it needs as non-required in the manifest. Missing hardware disables that one tool instead of blocking the install. ## Details & edge cases - The release APKs, and a build you make yourself with the default flags, both carry a GitHub-releases updater. It checks this project's releases in the background and shows an "Update available" card on the Home screen. It downloads the APK itself and asks you to confirm before installing. With it comes a small `.dm` file, the keyboard's baseline profile, so Android compiles the keyboard while it installs the update and typing is at full speed straight away, rather than only after the phone's overnight maintenance has run. Only the Google Play build skips the updater, because Play handles its updates, and Play applies the same profile itself. - Installing over a release APK works only if both were signed with the same key. A release APK replaces an earlier release APK cleanly. It will not install over a build you made yourself, and vice versa. Uninstall first, and export a backup before you do. - Build release APKs on more than one machine without sharing a signing keystore and they carry different signatures. Android then refuses to install one over the other. You have to uninstall first, which loses your data unless you exported a backup. - The Backup & restore export has a separate "include API keys" switch, off by default. It covers the translate, GIF, search and AI tool keys you may have entered. An export made with it on is worth treating like a password, because anyone holding the file gets those keys. - Some data is cheap to fetch again: downloaded (not custom) dictionaries, emoji keyword packs, Whisper and LLM model weights, and cached camera captures. Both the manual backup and Android's own backup leave all of it out, because re-downloading beats restoring. --- # Migrating from another keyboard URL: https://wmkeyboard.pages.dev/start/migrating/ Section: Getting started Summary: Coming from Gboard, SwiftKey or FUTO? Feature equivalents and what to set up first. Switching keyboards is mostly muscle memory, and WM Keyboard was built with that in mind. Dozens of its behaviors deliberately follow Gboard and SwiftKey conventions, right down to code comments that say so. This page maps the features you already rely on to their WM Keyboard equivalent, and calls out the handful of places where migration isn't a straight swap. start/migrating-suggestion-center — Suggestions default to the top pick in the center slot, the same layout Gboard uses. ## Finding the feature you know If a term below doesn't match what your old keyboard called it, this is where to look for it in WM Keyboard. | You know it as | WM Keyboard equivalent | Default | |---|---|---| | Glide typing / Flow | Gesture typing: drag through letters instead of tapping | On | | Swipe-to-handwrite | A gesture-typing sub-option that recognizes handwritten letters instead of a glide path | Off (glide path) | | Clipboard manager | The Clipboard tool: history with pinning, and a paste chip for your last copy | History on, entries expire after 24 hours or once you pass 100 items | | Theme store / custom themes | A full theme editor: 29 built-in themes (including ports of well-known editor palettes like Dracula, its light half Alucard, Nord and Solarized), custom colors, background images (including a landscape-specific one), scheduled light/dark, and `.wmtheme.json` import/export | Follows system light/dark, Material You colors on | | Voice typing / dictation | The Voice tool: a full-screen panel by default, or a compact bar over the keys | Full panel; continuous listening and spoken punctuation both on | | Autocorrect strength | A confidence multiplier you can tune, rather than a plain on/off switch | On, ×4 (adjustable ×1.5–×10) | | Emoji suggestions replacing the typed word | Emoji insert mode set to replace the word, with a long press on the suggestion keeping the word instead | On | | A keyboard made in Keys Cafe (Samsung) | Open the `.kcf` file, or pick it under Layout & size → Key layouts → Import from another keyboard. The grid comes across as you laid it out, bottom row and all; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#samsung-keyboard-keys-cafe-layouts) | Imported layouts stay off until you turn them on | | A layout or theme from Multiling O | Copy the layout text (DIY, `OK_Layout_Begin`, or a whole settings export) into a text file and pick it under Layout & size → Key layouts → Import from another keyboard; a theme goes through the **Multiling O** button in Keyboard themes. Symbols and AltGr pages and swipe keys come across; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#multiling-o-keyboard-layouts) | Imported layouts stay off until you turn them on | | A theme from Kinetica | Export a backup from Backup in Kinetica's settings and pick it with the **Kinetica** button in Keyboard themes, or open it from a file manager. Only the theme is read; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#kinetica-themes) | The theme is saved, not switched on. Kinetica has no custom layouts to bring | | Themes, layouts, fonts, sounds, actions or a language from Clink (iOS) | Pick a `.clinktheme` with the **Clink** button in Keyboard themes, or open a `.clinktheme`, `.clinklayout` or `.clinkext` from a file manager. A Clink repository (`owner/repository`) can be added under Addons, the same address Clink asks for; see [Clink repositories](https://wmkeyboard.pages.dev/addons/installing/#clink-repositories) | Themes are saved, not switched on. Actions install as plugins and need plugins turned on. Clink's panels and profiles are Clink's own and do not come across | | A layout made for a Windows, Mac or Linux computer | Open the `.klc` or `.keylayout` file, or pick it (or a Linux XKB symbols file) under Layout & size → Key layouts → Import from another keyboard. The letter keys keep their places, and the AltGr and dead-key letters move to press and hold; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#layouts-made-for-a-computer-windows-macos-linux) | Imported layouts stay off until you turn them on | | A layout from Unexpected Keyboard or CleverKeys | Save the layout XML as a file and pick it under Layout & size → Key layouts → Import from another keyboard. The swipes to each key's corners come across as flick arms; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#unexpected-keyboard-and-cleverkeys-layouts) | Imported layouts stay off until you turn them on | | An input method from gcin, OpenVanilla, Rime or m17n (ibus) | Pick the `.cin`, the `.schema.yaml` with its `.dict.yaml` files, or the `.mim` under Layout & size → Key layouts → Import an input method. It becomes a layout that types through the method's own table or rules; see [Input methods](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#input-methods-cin-rime-and-m17n) | Imported layouts stay off unless you pick Import and turn on | | A whole backup from HeliBoard, LeanType, FUTO, FlorisBoard, CleverKeys or AnySoftKeyboard | Data / Backup & restore / Import from another keyboard, or **Restore a backup** in the setup wizard. You get a list of every layout, theme, language, word list and setting in the file, and pick what comes; see [Whole backups](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#whole-backups) | Everything starts ticked; nothing is written until you tap Import | | "Virtual key" haptic feel | A Haptic style picker. **Tap** asks the platform for its keyboard-tap effect, the one an OEM tunes for its own keyboard, but setup skips past it. **Key**, the generic virtual-key buzz, is what setup actually picks on most phones, falling back to **Heavy** where the platform effects aren't supported | On, style Key or Heavy, picked by setup | Voice typing, glide typing, and several tools are covered in their own pages: [Voice typing](https://wmkeyboard.pages.dev/tools/voice/), [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/), [Clipboard](https://wmkeyboard.pages.dev/tools/clipboard/), and [Themes](https://wmkeyboard.pages.dev/themes/overview/). Autocorrect and gesture settings live under [Typing settings](https://wmkeyboard.pages.dev/reference/settings/typing/). Tune the confidence multiplier there if the default feels too eager or too quiet. Did your old keyboard cover handwriting, OCR text scanning, QR scanning, document scanning, offline dictation, or an on-device AI model? The fdroid has all of them, but reads some with open-source engines in place of Google's SDKs: handwriting only in Chinese and Japanese, text scan only in Chinese, Japanese and Latin script, and on-device AI only with GGUF models. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) for the complete comparison. There's no built-in comparison against FUTO Keyboard here, because the two projects share no code and no settings. If offline-first, no-telemetry behavior is what drew you to it, read [What WM Keyboard does differently](#what-wm-keyboard-does-differently) below. It covers what this keyboard does and doesn't send anywhere, checked against its own source rather than restated from memory. ## Bringing your dictionary across Most keyboards keep their learned words in a format no other app can read. The exceptions are the AOSP family (HeliBoard, LeanType) and FUTO Keyboard: their backup files can be imported here, learned words and word pairs included, and FUTO's carries the words you added by hand too. See [Whole backups](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#whole-backups). Here's what happens otherwise, and your real options. Words you've typed build up in an on-device dictionary automatically, the same way any predictive keyboard learns your vocabulary. There's nothing to migrate beyond typing normally with [Learn from typing](#options) switched on. You can also add words one at a time from the personal dictionary screen. Anything added that way reads "You added this word." Words the keyboard picked up from typing read "Seen 4 times" instead. start/migrating-personal-dictionary — The personal dictionary screen: words you add show 'Added by you'; words picked up from typing show a use count. There's no bulk-import button here. For seeding a large vocabulary at once (brand names, jargon, a whole word list you already have), use **Custom dictionaries** instead. It's a different feature (word lists layered per language, mainly meant for languages with no bundled dictionary), but it's the closest thing to a bulk import: - Import a plain text file, or pull one from an `http://` or `https://` URL. - Format: one word per line, optionally followed by a space and a frequency number; lines starting with `#` are ignored. - A `.combined` word list or a compiled `.dict` dictionary from an AOSP-derived keyboard (HeliBoard, LeanType, FUTO, OpenBoard, or one pulled from a GApps package) imports as it is, with nothing to convert first. Its word pairs and shortcuts can come too: a dialog asks which parts you want. - A HeliBoard or LeanType backup `.zip` imports its dictionaries, including the user history: the words that keyboard learned from you and the word pairs it saw you type. - Words imported this way are never autocorrected away. - Files are capped at 32 MiB per import. SwiftKey is one that can. With its sync on, your OneDrive holds a `vocabulary.txt` of the words it learned, in the Apps folder. Download it and import it under **Custom dictionaries**, and it becomes a list called "SwiftKey learned words" in the language you choose. The `dynamic.lm` beside it, the model behind SwiftKey's predictions, can't be read. So if your old keyboard can export a word list, import it here. See [Dictionaries & words](https://wmkeyboard.pages.dev/reference/settings/dictionaries/) for the full screen reference, and [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#dictionaries) for what each part of a dictionary becomes. Android's own system dictionary (the one some keyboards share words through) isn't a bridge here either. WM Keyboard can optionally *write* learned words into it for other apps to see, but it never *reads* plain words back out of it to seed its own suggestions; only dictionary-shortcut expansions (like "omw" → "on my way") round-trip that way. ## Settings worth changing on day one The first-run wizard covers most of this already (see [Enable the keyboard](https://wmkeyboard.pages.dev/start/setup/) for the full walkthrough). If you skipped or rushed past its pages, these four are worth doing by hand: start/migrating-tools-recommended — Settings → Tools: every tool starts on. Recommended switches to the curated 17-tool set the setup wizard offers. - **Add every language you actually type in.** A fresh install enables only what your phone's own language list told it to, plus English. Add the rest from Settings → Languages, where a **Suggested for you** group offers the likely ones first. - **Trim the toolbox.** Every tool but Learn from text ships switched on by default, 80 of them outside the F-Droid build. Setup narrows that for you, and the persona quiz's "how much do you want" answer is what picks the size: "Keep it simple" lands 8 tools, "A good middle" 22, "Show me everything" 37. Answering applies the set on the spot, before the Tools page shows up, and that page's **Recommended** button puts the same set back if you've been toggling. On the Settings → Tools screen there's no bulk switch at all, so flip off the ones you don't want one at a time. To get the buttons back, replay the wizard from **About / Run setup again**. - **Check your spacebar swipe directions**, if cursor-on-a-quick-swipe is what you're used to. Untouched, a quick swipe switches language and a hold-and-swipe moves the cursor. The wizard's gesture page offers four pairings and puts a "Recommended" badge on whichever one matches your persona answer: say you type several languages and it marks Language on both slots, say you type one and it marks Cursor on both. That answer has already written the pairing by the time you reach the page, so the badge is a description rather than an offer. Set it to what your thumbs expect. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/). - **Revisit autocorrect strength** if your old keyboard's felt looser or tighter. WM Keyboard's confidence multiplier defaults to ×4 out of a ×1.5–×10 range. That sits nearer the eager end than the conservative one, but well short of the most aggressive setting. ## What WM Keyboard does differently WM Keyboard is MIT-licensed, ships no analytics or crash-reporting SDK, and its core (dictionaries, prediction, transliteration, emoji search) runs fully on-device with no network connection required at all. That's the part that doesn't change no matter what you toggle. What does reach the network is a long list of individually optional tools. Each one calls out only when you open it, or when you configure it with your own API key: weather, dictionary lookups, translation, GIF and sticker search, web and image search, currency conversion, the AI writing tools, downloadable dictionaries and emoji-keyword packs, addon repositories, and clipboard link previews (off by default). None of it runs in the background, and none of it runs while you're just typing. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) and the full [tool-by-tool network table](https://wmkeyboard.pages.dev/privacy/network/) for exactly what each one sends and when. Two more things migrating users tend to look for: - **Incognito mode** pauses learning and clipboard capture on demand. A Follow private browsing toggle engages it by itself for fields the app flags as private, like Chrome incognito tabs. It can't detect apps that never send that flag. - **Backup is opt-in and local by default.** Android backup is switched off out of the box. Turn it on and settings, themes, layouts, learned words and saved API keys travel through Android's own encrypted backup. Voice and AI models, downloaded dictionaries and emoji keyword packs are left out, because they're too large. ## Options Typing / Suggestions / Personal dictionary Typing / Suggestions / Custom dictionaries - Personal dictionary lists every word the keyboard has learned, with a button to add one manually and a way to remove any entry. - Custom dictionaries import word lists per language, from a file or a URL, as described above. Tools - Every tool this build ships, Learn from text aside, is switched on before setup runs. Flip each one off individually here. The **Recommended** and **Everything** buttons appear only on the setup wizard's Tools page, not on this screen. Privacy - **Learn from typing** (on by default). Personalizes suggestions from what you type, entirely on-device. Turn it off and a rebuilt dictionary stops growing. - **Add words to the system dictionary** (off by default). Mirrors learned words into Android's own dictionary for other apps to see. - **Follow private browsing** (on by default). Engages incognito mode by itself for fields the app flags as private. - **Back up with Android** (off by default). Opts into Android's encrypted backup for settings, themes, and learned words. See the [Privacy settings reference](https://wmkeyboard.pages.dev/reference/settings/privacy/) for the rest of that screen. ## Details & edge cases - The setup wizard can be replayed any time from **About / Run setup again**, and a replay never rewrites settings on its own. Reopening the app otherwise just goes to the Home screen. Reinstalling over an existing install (same signing key) keeps the `onboardingDone` flag, so only a clean reinstall, or a fresh phone with no settings backup, shows the wizard unasked. - **Backup & restore** can move your learned-word dictionary between two phones that both already run WM Keyboard. It's a WM-Keyboard-to-WM-Keyboard path only, never an import from another app, and an export leaves the dictionary section out unless you turn it on yourself. - Custom dictionaries and the personal dictionary are two separate features that both feed suggestions. The personal dictionary holds words you've typed or added one at a time. Custom dictionaries hold bulk lists, layered on top of a language's bundled dictionary or standing in for it. - On a fdroid build every tool is there, but two options inside them are not: Whisper's LiteRT models in voice typing, and LiteRT-LM models for the on-device AI. Handwriting, text scan, QR and document scanning read with open-source engines instead of Google's. --- # Enable the keyboard URL: https://wmkeyboard.pages.dev/start/setup/ Section: Getting started Summary: Turn WM Keyboard on in system settings and make it your active input method. Installing WM Keyboard doesn't make it your keyboard. Android keeps every installed input method switched off until you enable it and then pick it as the active one. The app's Home screen has a card that walks you through both steps, and it disappears the moment you're done. start/setup-card-not-enabled — The setup card before WM Keyboard is enabled: 'Enable keyboard' opens system settings, 'Switch keyboard' opens the picker. ## Using the setup card The card sits at the top of the app's Home screen until both steps are done, and it isn't a first-run-only banner: leave setup half finished and it's still there on the next launch. While it's on screen it checks Android's input-method state about once a second. There's no callback for "the user switched keyboards" while your app sits behind a system dialog, so polling is the only way to notice. Both steps are always drawn, stacked, with only the one still to do live as a button: 1. **Not enabled.** The card reads "Turn on WM Keyboard in the system settings. Then select it as your keyboard." **Turn on keyboard** is the live button. **Switch keyboard** sits under it as a grayed-out outline, because there is nothing to switch to yet. 2. **Enabled, not selected.** The card reads "WM Keyboard is turned on. Press the button below to switch to it." **Turn on keyboard** now carries a checkmark and goes quiet. **Switch keyboard** becomes the live button. 3. **Enabled and selected.** The card goes away entirely. A green checkmark and "Your active keyboard" appear under the app name at the top of the Home screen instead. Nothing left to do. Tap **Turn on keyboard** to open the system screen. Turn WM Keyboard's toggle on there. Tap **Switch keyboard** to open Android's "Change keyboard" sheet, which lists every enabled input method on the device. Tap WM Keyboard in that sheet to make it active. Both screens are also reachable through Android's own Settings app, if you'd rather not use the card at all. The setup wizard's first page embeds the same card. There it stays put once you're done, as a short "WM Keyboard is your active keyboard" row with a checkmark in your app accent color. ## What the system permission warning means here Enable any third-party keyboard and Android shows its own warning about keyboards being able to see everything you type. That text is the stock OS wording. WM Keyboard doesn't write it and can't customize it, and it reads a little differently on each Android version. The warning exists because *some* keyboards phone data home, which makes it worth knowing what this one does. Core typing runs entirely on-device: the spelling dictionaries, transliteration, emoji search, prediction, everything you actually type with. All of it works with no network connection at all. A handful of optional toolbox features do reach the network, and only when you open them yourself. Weather, the word-definition lookup, translate, GIF and sticker search, web and image search, Wikipedia, and the AI writing tools pointed at a cloud provider each call their own service, and several want your own API key before they work at all. None of that runs in the background, and none of it runs while you're just typing. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for the full breakdown. ## Running the setup wizard The first time you open the app, it drops you into a short setup wizard instead of the Home screen. Every choice writes straight to settings as you make it. Going back, skipping ahead, or tapping **Skip** in the top corner therefore loses nothing, and you can change any of it again later. There is no page for the app's own language. The app starts in your phone's language; to show it in a different one, use [About, App language](https://wmkeyboard.pages.dev/reference/settings/about/#changing-the-apps-language). The wizard tailors itself to a short quiz on its **Make it yours** page, so not everyone sees every step. "Keep it simple", or no answer at all, stops after the quiz (and Your languages, if you type in more than one). The full list: 1. **Welcome.** The setup card from above, embedded in the wizard. Enable and select WM Keyboard here and the wizard advances on its own. You do not have to tap Next after you come back from system settings. Under the card, **Restore a backup** takes a backup instead of the questions: see [Restoring a backup instead](#restoring-a-backup-instead). 2. **Make it yours.** Two quick questions, both optional: how many languages you type in, and whether you want a simple or a fully loaded keyboard. Each answer's subtitle spells out what picking it changes. Answers apply their defaults on the spot, and the rest of the wizard trims itself to match. Leave both alone and you get the short version. [What the quiz changes](#what-the-quiz-changes) below lists every setting each answer writes. 3. **Your languages** *(hidden if you answered "just one" language and only one is enabled)*. Your currently enabled languages come first, each with its layouts individually switchable, then a short **Suggested for you** list, then a search box for the other 840-odd. The suggestions come from your phone's own language settings and region. See [Suggested languages](https://wmkeyboard.pages.dev/languages/overview/#suggested-languages) for exactly what's read. A language that ships one layout is added on the spot. A language that ships several (Bangla ships Avro, Probhat and National) opens a picker first, with its first layout already checked and a miniature of each key grid. Check as many as you want, but at least one. 4. **Look & feel** *(only for "show me everything" answers)*. Two controls only: theme mode (Auto/Light/Dark/AMOLED), then a grid of keyboard themes that match the mode you picked. Choose Auto and the grid splits into a Light tab and a Dark tab, so you pick one theme for each half. 5. **Typing feedback** *(skipped for "keep it simple" answers)*. Haptics, key sounds, and the key popup bubble. 6. **Spacebar & keys** *(skipped for "keep it simple" answers)*. One choice sets both spacebar-swipe directions, short and long, and a "Recommended" badge follows your quiz answer. There's also a toggle to replace the 🌐 key with an emoji key, and a number row toggle. 7. **Worth knowing about** *(only for "show me everything" answers)*. A tour of the features most keyboards don't have: smart chips, [per-app keyboards](https://wmkeyboard.pages.dev/typing/modes/#keyboard-modes), tool hotwords, the toolbox, photo backgrounds, clipboard history and more. Most cards carry a switch right on them. The rest say where the feature lives. The per-app keyboards card sits second, and its switch is the master switch for [modes](https://wmkeyboard.pages.dev/reference/settings/modes/). Modes are off until you turn them on: they're the one feature here that changes the keyboard without being asked, so setup offers the switch rather than springing it on you in the next app. 8. **Your tools** *(only for "show me everything" answers)*. Every toolbar tool, each with its own switch, plus quick "Recommended" and "Everything" buttons. Neither button, and no answer above, turns on Learn from text: that one stays off until you switch it on yourself. 9. **Tool setup** *(only for "show me everything" answers with Calendar, Weather, or Compass enabled)*. First-run choices for whichever of those you kept: alternate calendars and which days count as the weekend, a saved place (searched, typed, or read once from the phone) and a temperature unit, or a qibla toggle. 10. **Try your keyboard** *(skipped for "keep it simple" answers)*. A live text field, so the keyboard appears in the theme you picked. Up to three "try this" prompts come with it, drawn from what you enabled. Type `12*8=` for a smart chip, type `translate` for its hotword chip, or open the toolbox. start/wizard-gestures-page — The Spacebar & keys page, where one choice sets both swipe directions at once. ## Restoring a backup instead Moving from another phone, or from another keyboard? **Restore a backup** on the Welcome page opens a file picker. It takes: - a backup from WM Keyboard, `.wmconfig.json`, an encrypted `.wmconfig.enc` (it asks for the passphrase), or an older `.wmsettings.json`; - a whole backup from HeliBoard, LeanType, FUTO Keyboard, FlorisBoard, CleverKeys or AnySoftKeyboard, where you pick what comes across first. See [Whole backups](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#whole-backups). If the file can't be opened, the dialog offers **Choose another file** instead of leaving you stuck. A backup can be up to 256 MiB here and on the Backup screen. Once the backup is restored, the rest of the wizard is skipped: the quiz, the languages and every page after them. Your backup already answered all of it, and nothing the quiz would preset is written over it, the tool set included. Only the Welcome page is left, because no backup can turn a keyboard on for you. If WM Keyboard is already enabled and selected, the wizard closes by itself; if not, the page says so, and **Finish** stays open so you can do it later. The languages in the backup get their word lists and prediction data downloaded in the background, the same way a language added on the wizard's own page does. A file that is not a backup, such as a single theme or layout, imports as usual, and the wizard carries on. ## What the quiz changes Each answer does two things: it writes a few settings there and then, and it decides which of the later pages you get. Here's the whole mapping, so you can see what you agreed to. **How many languages do you type in?** | Answer | What it writes | What it does to the wizard | |---|---|---| | Just one | Both spacebar swipes (short and long) move the cursor, and the 🌐 key stays an emoji key, which is what a fresh install ships | Your languages is skipped, unless you already have two or more enabled | | Two or more | Both spacebar swipes change the language, and the emoji key next to the spacebar turns back into the 🌐 language key | Your languages appears | Neither is a one-way door: the Spacebar & keys page later offers the same choice again, with a **Recommended** badge on whichever one matches your answer. **What kind of keyboard do you want?** | Answer | Toolbar tools it turns on | What it does to the wizard | |---|---|---| | Keep it simple | Eight: emoji, GIFs, stickers, clipboard, voice typing, AI writing tools, grammar check, and the settings shortcut | The short path. The wizard ends after the quiz | | A good middle | 22: those eight plus themes, dictionary, fancy text, translate, autocorrect, snippets, one-handed, split, text editing, trackpad, handwriting, text scan, undo and redo | Adds Typing feedback, Spacebar & keys and Try your keyboard | | Show me everything | 37: those 22 plus Wikipedia, vocabulary, power saving, keyboard modes, numpad, special symbols, calculator, unit converter, currency, password generator, calendar, weather, media controls, apps and hide keyboard | Also adds Look & feel, Worth knowing about and Your tools, and Tool setup when calendar, weather or compass is among them | The same answer sets how much of the settings app shows until you pick for yourself: Simple, Standard or Everything. See [Choosing how many settings you see](https://wmkeyboard.pages.dev/reference/settings/#choosing-how-many-settings-you-see). A set that can't run in full on your phone gets topped up rather than left short. Part of it may be unavailable on your phone. Wikipedia and power saving then come in behind whatever's missing, so a "recommended" set never quietly arrives two tools smaller. Leave the quiz alone entirely and you get the short page list, the same one "keep it simple" gets, plus the middle tool set when the wizard closes. ## Switching keyboards later Once WM Keyboard is set up, there are a few ways to change which keyboard is active, or which of WM Keyboard's own languages is: - **The Home screen's Switch keyboard button** comes back whenever WM Keyboard stops being the active keyboard, because the whole setup card returns with it. While WM Keyboard *is* active the card is gone, and the Home screen says "Your active keyboard" instead. Reach the picker from Android's own settings in that case. - **The 🌐 key and spacebar swipes** cycle between WM Keyboard's *own* enabled languages. They don't open the system keyboard picker, so they can't switch you to a different keyboard app. A fresh install draws the emoji key in the 🌐 key's slot, so spacebar swipes are the language switch until you put the globe back. The option below lists WM Keyboard's languages individually in Android's own switcher sheet, rather than as one generic "WM Keyboard" entry. It's off by default. Turn it on if you want to jump straight to one language from the system sheet. ## Options Languages & layouts / System language switcher - **List languages in the Android switcher** (off by default). Registers each of your enabled layouts as an Android input-method subtype, so the system's "Choose input method" sheet lists them individually and can jump straight to one. Left off, the keyboard registers only one subtype, named after the keyboard and carrying the language you're typing in, so the sheet lists it under its own name while Android's spell checker still knows which language to check. - **Show app name first** (off by default, and only shown while the option above is on). Prefixes each subtype's label with "WM Keyboard ·" instead of just the language name. - **Select languages in the system settings** (also only shown while the first option is on). Opens Android's own subtype list. On Android 14 and newer the keyboard turns them on for you, so this is a repair tool. On Android 13 and older you have to turn them on here yourself, or the sheet shows only one. Privacy - **Learn from typing** (on by default). Personalizes suggestions from what you type, entirely on-device. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) and the [Privacy settings reference](https://wmkeyboard.pages.dev/reference/settings/privacy/) for the rest of that screen. ## Running it again The wizard isn't a one-shot. About / Run setup again replays it whenever you like. A replay presets nothing and seeds nothing, so walking through with Next writes no settings at all. It skips the Welcome page outright when the keyboard is already enabled and selected. Finishing a replay drops you back on the About screen. ## Details & edge cases - Whether the wizard appears has nothing to do with whether the keyboard is enabled. A "have you finished onboarding" flag drives it, not a check of Android's input-method state. Enable and select WM Keyboard entirely from system Settings, without ever opening the app, and the wizard still appears on first launch. Its Welcome page just already shows the "done" state and lets you move straight past it. - A restore from the Welcome page is the only way to skip the wizard that keeps the everything-enabled tool default when the backup has it. Skip and Finish otherwise replace it, as the next point says. - Skipping the wizard early still lands a sane tool set. Finishing it, or skipping over the untouched everything-enabled default, replaces that default with the recommended set. Answer "keep it simple" first and you get the smaller essential set instead. - On the F-Droid build the Tools page offers every tool. Handwriting, text scan and QR scanning read with open-source engines there, and the document scanner is the built-in one. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/). - Installing over an existing WM Keyboard install (same signing key) keeps the wizard from running again. `onboardingDone` is a regular setting, so it carries over like any other. A clean reinstall, or a new phone with no settings backup, shows the wizard again. See [Installation](https://wmkeyboard.pages.dev/start/installation/) for what does and doesn't carry over. --- # Supporters URL: https://wmkeyboard.pages.dev/start/supporters/ Section: Getting started Summary: The people who sponsor WM Keyboard on SupportKori, one-off or monthly, or tipped through Google Play, credited by name. WM Keyboard is free, with no ads and no paid tier. The people below sponsor it through [SupportKori](https://www.supportkori.com/wasimaster), the yellow button in the corner of this site, or with a tip in the Google Play version of the app. Thank you. Every SupportKori sponsor is credited by name. You're listed on this page with whether you made a one-off payment or joined the monthly membership, how much it came to and the date, and your name also appears in the app under **About › Supporters**. The credit stays up for as long as the list does; it isn't cleared when a new version comes out. Sponsors can also request features: email [arianmollik323@gmail.com](mailto:arianmollik323@gmail.com) with the name you sponsored under and what you'd like to see. The same names are in the app under **About › Supporters**. The app shows names only; what each person gave and their message are on this page. ## How the list works A name appears here when someone sponsors on SupportKori, either with a one-off payment or by joining the monthly membership. Each entry shows the name you typed on SupportKori, whether it was one-off or a membership, how much it came to, and the date. SupportKori doesn't pass on the message you write there, so only messages left with a Google Play tip appear here. If you left the name blank or chose to stay anonymous, nothing is listed. ## Tipping from the Google Play version Google Play doesn't allow apps to link to another payment site, so the Play version of the app can't open SupportKori. It has a tip jar of its own instead, under **About › Supporters**. You pick a tier, type the name you want listed and, if you like, a message, and pay through Google Play like any other purchase. Turn on **Stay anonymous** to tip without being listed at all. The tip unlocks nothing. The rest of the app is the same either way. A Play tip is listed with its tier's price in US dollars, such as $4.99. Google Play charges a local price in each country, so what you paid in your own currency can differ a little from the figure shown. After Google Play confirms the payment, the app sends the name and message you typed and the purchase receipt to this site. The site checks the receipt with Google before it adds the name, so no one can add a name without a real tip, and it never sees your card or Google account. Stay anonymous, or leave the name empty, and nothing is sent at all. If the app is offline at that moment, it keeps the name and tries again the next time you open Supporters. The page shows the list as it was when the site was last built. **Check for new supporters** asks for anyone added since then; nothing is fetched until you press it. The app works the same way: it ships with a copy of the list and only goes online when you press its button. Want your name or message taken off? Email [arianmollik323@gmail.com](mailto:arianmollik323@gmail.com) and it will be removed. --- # A two-minute tour URL: https://wmkeyboard.pages.dev/start/tour/ Section: Getting started Summary: The suggestion strip, toolbar, emoji, tools and settings: where everything lives. WM Keyboard looks like a normal keyboard until you need more. Then a toolbar, a panel, or a long-press gets you there. This page walks the layout so nothing feels hidden. ## The anatomy tour/anatomy-default — Fresh install: the top bar shows pinned tools, not an empty suggestion strip, until you type something. The keyboard is built from a small set of stacked bars, plus the key area itself: - **The top bar** does double duty as either the suggestion strip or the toolbar, never both at once. Before you type anything, and whenever there's nothing to suggest, it shows the toolbar. Once there are candidates, it shows those instead. - **The key area** fills the rest of the height with the current layout's keys. Opening almost any tool swaps this area out for that tool's panel. See [Panels replace the keys](#panels-replace-the-keys) below. - **A symbol row** (a strip of quick symbols like currency or arrows, with a set picker) is available but off by default. Turn it on from Rows & bars / Symbol row, and see [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/) for the full set list. - **A dedicated emoji row** above the keys is also available but off by default. Switch it on from Emoji / Emoji row: **Button** adds a toolbar toggle that flips the top bar over to show emoji in place of suggestions, or **Own row** keeps it permanently visible. See [Emoji settings](https://wmkeyboard.pages.dev/reference/settings/emoji/) for both modes. Don't confuse this opt-in symbol *row* with the `?123` key, which cycles the whole key grid to a symbols layer, or with the Symbols *tool*, which opens a full-screen browser of the entire Unicode symbol catalog from the toolbox. All three are covered on their own pages: [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/) for the row, [Tools overview](https://wmkeyboard.pages.dev/tools/overview/) for the toolbox entry. ## Using the toolbar tour/toolbar-pinned — The toolbar: back-to-keyboard chevron, the Toolbox launcher, then your pinned tools. Reading left to right, the toolbar has: 1. A back arrow, shown only while a panel is open, that returns you to the keys. 2. A **Toolbox** button (the grid icon) that opens a grid of every tool you have enabled. 3. Your **pinned tools**: the small set of tools that live directly on the bar. Out of the box a phone pins three tools: **Emoji**, **Clipboard**, and **Settings**. A tablet starts wider, with five on a small tablet (adding GIF and Voice) and seven on a large one (adding Translate and Text edit). See [Size & position](https://wmkeyboard.pages.dev/typing/size-position/#on-a-tablet) for the tablet defaults. Everything else (GIFs, translation, the calculator, sensors, scanners, and dozens more) lives one tap away in the Toolbox grid, four columns wide and ordered roughly by how often people reach for each tool. The Toolbox's first-run hint explains the gesture. Hold a tool in the grid and drag it onto the toolbar to pin it. Drag a tool inside the grid to reorder it. Drag a pinned tool back down into the grid to unpin it. tour/toolbox-grid — The Toolbox grid: every enabled tool, four to a row, drag to pin or reorder. During onboarding you can widen this from a "Recommended" starter set to everything. That only changes which tools are *available* in the Toolbox. It adds nothing to the pinned bar. To keep more tools within one tap, drag them up yourself. See [Tools overview](https://wmkeyboard.pages.dev/tools/overview/) for what each one does, and Tools to manage the enabled set later. ### Panels replace the keys Tap a tool and the key area almost always becomes that tool's panel. The keyboard doesn't get taller. It reuses the space the keys were sitting in. Most panels take the toolbar row as well, so the tool gets the keyboard's full height: Emoji, GIFs, stickers, Clipboard, Translate, Themes, the calculator and the rest. Others, the Toolbox and Text editing among them, leave the toolbar in place above them, so you can hop to a different tool without backing out first. Emoji, the media panels and Clipboard each have their own switch for this, all three on by default, so you can hand any of them back to the toolbar. Either way, a back chevron returns you to typing. It sits in the toolbar on panels that keep the toolbar, and in the panel's own header when the toolbar has stepped aside. ## Long-press for more tour/long-press-alternates — Long-pressing a key with alternates pops up the accented and symbol variants. Long-press is the keyboard's "there's more here" gesture in a few different places: - **Keys with alternates** (most letters, and some punctuation) pop up a row of accented or related characters on long-press. A small corner mark hints which keys have alternates. Turn that hint off from [Key press settings](https://wmkeyboard.pages.dev/reference/settings/keypress/). - **The globe key** cycles to your next enabled layout on a tap. Long-press it instead to jump straight to the full language picker. A fresh install draws an emoji key in that slot rather than the globe. Turn **Emoji key instead of 🌐** off under Layout & size / Bottom row keys to get the globe back. - **Toolbar tools** respond to three gestures. A tap runs the tool. A hold that doesn't move opens that tool's own settings page. A hold that then drags picks the tool up to reorder, pin, or unpin it. - **Emoji** open a popup of skin-tone and gender variants on long-press, along with the emoji's name and a favorite toggle. Two more long-press behaviors have switches of their own, both under Key press / Press and hold shortcuts. Holding ?123 opens the number pad over any field instead of behaving like a tap, and that one ships on. Letter keys also carry shortcuts for select-all, copy, paste, cut, undo and redo, one switch each, and all six ship on. They sit at the end of the press-and-hold popup of A, C, V, X, Z and Y, and a row below the switches moves them onto keys your own layout actually draws. ## Getting to settings tour/settings-entry — The Settings tool opens the full settings app to its home screen. There are three ways to reach the settings app from the keyboard itself: - **Tap the Settings tool** (pinned by default) to open the settings app at its home screen. - **Long-press any tool**, pinned or in the Toolbox, to jump straight to that tool's own settings page instead of the home screen. - **Use a panel's own shortcut** to land directly on that section. Snippets and AI writing tools put a gear in the panel header. Themes has an "Edit themes" button, Weather offers one when it needs a location, and the searches offer one when they need a key. ## The settings app map The settings app is a flat list of screens, one per section, grouped into six clusters on the home screen: - **Typing:** Languages & layouts, Typing, Key press - **Keyboard:** Appearance, Layout & size, Rows & bars - **Features:** Emoji, Voice typing, Clipboard, Text expansion, Tools - **Accessibility:** Accessibility - **Data:** Privacy, Backup & restore, Advanced - **About:** About Most screens are just a title and a back button, with one exception. **Advanced** bundles five screens few people open day to day: Keyboard modes, Add-ons, Data saver, Notifications and Selection actions. Key layouts lives inside Layout & size instead of its own home-screen row. Every section here has a matching reference page under [Reference → Settings](https://wmkeyboard.pages.dev/reference/settings/) with the exact options and defaults. This tour only covers where things live, not every switch. ### While a screen opens A settings screen composes its rows as it opens, so the ones below the fold arrive a frame or two after the ones at the top. While they are on their way the screen holds their place with blank rows the size of the real ones, which is why a long screen opens at its full height and fills in rather than growing under you. Settings search does the same. Its index is built the first time you open the search screen, so if you type immediately you get placeholder rows for a moment. Search only tells you that nothing matched once it has something to match against. ### On a tablet On a screen wider than about 840 dp (a tablet either way up, a foldable opened out, or a large free-form window), the settings app splits in two. The home list stays down the left side and the section you open fills the rest of the window, so you can move between sections without going back first. The row you opened is marked in the list, and Back closes the section and leaves the list where it is. Until you open something, the right side shows the app icon and a line telling you to pick a section. Below that width the app stays one screen at a time, and on a screen wider than two phones the content stops stretching and sits in a column in the middle. A settings row is a name on one side and a switch on the other, and a very wide row puts too much space between the two. ### Lists that keep going A list that is too long for its window gets a rail down the right edge. The rail shows how much of the list you can see and where in it you are. Drag the rail to move through a long list quickly. A list with groups gets one piece of rail for each group, and the group you are reading is highlighted. Press and hold the rail on such a list and the group name follows your finger. The bottom edge of the list fades while there is more below it. The same rail is in the keyboard, on menus like the language list from the spacebar and the translation language picker. A list that scrolls also moves down a little and comes back when it opens, so that you can see it scrolls. It stays still if your device has its animations turned off. Long-press the WM Keyboard launcher icon on your home screen for shortcuts straight into Tools, Languages, AI chat and Themes. Handy if you tweak settings often. The F-Droid build reads with open-source engines where the others use Google's: handwriting there reads Chinese and Japanese only, and text scan Chinese, Japanese and Latin script. Everything else on this tour is in every build all, including all three default pinned tools, the toolbox, and every settings section. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) for the full comparison. --- # Keys & typing basics URL: https://wmkeyboard.pages.dev/typing/basics/ Section: Typing Summary: Key presses, long-press popups, shift behavior, the number row and key previews. Every key does more than the letter printed on it. A tap commits that letter. A hold opens accents, digits, symbols, or an editing command on six letters you can arm yourself. This page covers what one key does under your finger, before the gestures that travel across several. ## Tapping and long-pressing a key Key press / Key popup typing/long-press-popup — Holding a letter opens its accent and symbol alternates. While you hold a key, its character floats in a bubble above your finger. That bubble is the **key popup**, and it lets you confirm what you hit before you let go. It's on by default, and by default it grows upward out of the pressed key instead of hovering above it with a gap (the **Popup on key** toggle). Even a fast tap leaves it up for at least 140 ms after you release, so a quick press still flashes something readable. On number, phone and date/time keypads it stays hidden by default, a numeric PIN field included. Echoing each digit large enough to read over your shoulder is not much of a favor. The **Popup on number pads** toggle brings it back on those keypads if you want it there. A key with a [flick](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) or a [swipe](https://wmkeyboard.pages.dev/typing/gestures/#swipes-on-keys) on it shows the bubble too, until you swipe or hold long enough to bring up its cross of directions. long-press — Hold a key past the long-press delay to open its alternates. Hold past the **long-press delay** (300 ms by default) and the key's alternates pop up, if it has any. `Q`–`P` lead with the digits 1–0. `A`–`L` and `Z`–`M` lead with a symbol (`@`, `#`, `$`, `&`…). Some letters carry accented forms after that first alternate: `e`, `u`, `i`, `o` after their digit (`e` → è é ê ë, `u` → ù ú û ü, and so on), and `a`, `s`, `z`, `c`, `n` after their symbol. The first alternate also prints as a small corner hint on the key itself, so you can see what a hold will lead with before you commit to it. That's the **Press and hold hints** toggle, on by default. Every other layout laid out as QWERTY gets the same digits and symbols at the front of its holds, ahead of its own accents, unless it already puts digits on its keys; a popup you wrote yourself in the layout editor is left as you wrote it, an empty one included. A layout you made or imported borrows none of them: its keys hold what you gave them and nothing more. The symbol pages hold more than they print. On the second page, `•` holds `◦ ‣` and the music note and card suits `♪ ♥ ♡ ♠ ♣ ♦`, `=` holds `≠ ≈ ≤ ≥ ∆`, `^` holds the arrows, and `✓` holds `✔ ✗ ☆ ★`. With Shift on or caps lock locked, the popup and the corner hint show capitals: holding `e` offers È É Ê Ë, and the one you pick is typed as a capital. Digits and symbols stay as they are. On a right-to-left language (Arabic, Persian, Urdu, Hebrew), the corner hint and the popup draw a bracket the way it will look in your text. Right-to-left text flips `(` into `)`, `<` into `>`, and the same for `[ ]`, `{ }` and `« »`, so a key that types `(` shows `)`, which is what lands on the screen. Only the drawing changes; the key types the same character it always did. A popup with more alternates than fit across the screen wraps onto a second row and a third, so nothing ever sits off the edge where you can't reach it. That's the case you meet with "All accents on press and hold" turned on, or with a raised popup text size, or on a key you gave a long list of your own. Keep your finger down and you can pick one without a second tap. The popup opens with its first alternate right over the key you're holding and highlighted, and the rest spread out on both sides of it: the second just to its right, the third just to its left, and so on outward, so the next alternate is one short slide away in either direction. Sliding the finger moves the highlight, and letting go types whatever is lit. The slide counts from where your finger started, so moving one alternate's width moves the highlight one alternate along, wherever on the key you pressed. Up and down work the same way: the highlight stays a row above your finger, so sliding up one row's height reaches the row above without your thumb climbing onto it and hiding it. On a key near either edge, where the popup has no room on one side, the alternates run away from the edge in reading order instead, so the first one still sits over the key. Turn off **Centre on the key** to have every popup run in reading order, all to one side of the first alternate. The alternate you're aiming at grows as your finger nears it and shrinks as you move on, and the highlight gives a little under your finger as it moves, then steps to the next alternate almost instantly once your finger crosses into it. If you'd rather nothing moved, turn on **Classic press-and-hold popup** under Accessibility. Hold and release without moving and you get that first alternate, which is what a hold does on any other keyboard. To back out, slide well away from the popup before you lift. If you'd rather have the older two-step popup, which stays open after you lift so you can tap the one you want, turn **Choose without lifting** off. Nine settings under **Alternate keys** shape that popup. **Alternate key size** and **Alternate key spacing** make the characters bigger and give each one more room, and the size slider reaches ×3.20, well past what the preview bubble allows. **Alternate key columns** is Auto by default, fitting as many per row as the screen takes. Set it to a number from 3 to 11 instead and every row gets that many equal columns, so a long list reads as a block and each character stays put between one hold and the next. When that many don't fit across the screen, the spacing between characters shrinks first, then the number of columns. **Even rows** makes the number a most instead: with 4 columns, 5 alternates show as 3 and 2 rather than 4 and a lone 1, and 6 as 3 and 3. **Closest to the key first** flips which end of a wrapped popup the list starts at: off, it fills top-down like lines of text, which puts the first alternate furthest from your thumb; on, it fills from the key outward the way stock keyboards do, so the first alternate is the nearest one. **Order of the alternates** is a list you drag: a letter's popup holds up to three groups, the alternates the layout itself puts under the key, the accents that **All accents** and **Native letters first** add, and the capital or shifted form that **Shifted letter on press and hold** adds, and this decides which comes first. Inside a group the order stays as it was. The default is the layout's own, then the accents, then the capital, which is how the popup was always built; put the capital first if a hold is how you type capitals. **Remember the punctuation key's pick** makes the comma and full stop keep what you last chose from them: pick `?` from the comma and it types `?` until you pick something else. The popup isn't only for characters. A key in a [custom layout](https://wmkeyboard.pages.dev/languages/custom-layouts/) can carry action alternates too, so a hold on Enter can offer Tab, and a hold on any key can open voice typing or the editing pad. Every key can have alternates except the four that hold for something else already: ⌫, ⌦ and Space hold to repeat, and a braille dot holds as part of its chord. A few more long-press behaviors live under the same **Press and hold shortcuts** settings group: Key press / Press and hold shortcuts - **All accents on press and hold** (off by default) adds each Latin letter's complete accent set to its long-press popup. Nothing is taken away to make room: the digit or symbol stays, with every accent beside it. - **No accents on press and hold** (off by default) does the opposite: it takes every accented letter out of the letter popups, the layout's own included, on every layout at once. The digits, symbols and shortcuts stay. It wins when both are on. - **Top row press and hold** (Auto by default) decides what the holds on `Q`–`P` type, whatever the number row is doing. **Auto** keeps the digits without the number row and drops them with it. **Digits** keeps them either way. **Symbols** puts `! @ # $ % ^ & * ( )` in their place. **None** drops them. - **Your hold symbols** lets you pick the symbol each letter holds, on every layout, without editing one: type a symbol under a letter and a hold on that letter offers it first, in place of the digit or symbol the layout put there, with the corner hint to match. A box left empty keeps the layout's own. **SwiftKey** fills in the set SwiftKey uses (`@` on A, `#` on S, `&` on D, and so on). **Gboard** fills in Gboard's (digits on the top row, `@` on A, `$` on D, `!` on N), and **Samsung** fills in Samsung Keyboard's, which keeps digits off the letters: `+` on Q, `=` on R, `!` on A, `@` on S. Under them, **Under a number row** is the top row again, for when a row of digits sits above the letters: the number row, or one the layout draws itself. Gboard fills it with what it trades its digits for then (`%` on Q, `=` on R, `{` on O), and a box left empty keeps the letter's symbol above; SwiftKey and Samsung leave it empty, since SwiftKey keeps its digits and Samsung has none there. Last comes **Number row**, one box per digit, which replaces that digit's whole popup on the number row and on a layout's own row of digits; Gboard and Samsung fill it with their fractions and superscripts (`¹ ½ ⅓ ¼` on 1). Any box takes several symbols split by spaces, and they fill the popup in that order. **Clear all** goes back to every layout's own. - **Your language's letters first** (on by default) puts the letters of the language you're typing ahead of the number or symbol that opens a key's popup. On German, holding u types ü instead of 7. English has no accented letters, so its keys stay as they were. A key whose popup you edited yourself keeps the order you typed. - **Shifted letter on press and hold** (off by default) puts each key's shifted character at the end of its popup: the capital in Latin scripts, the key's second letter in Arabic, Devanagari and other scripts without capitals. Handy on its own, and the way to type a capital out of the [drag off ?123 or ABC](https://wmkeyboard.pages.dev/typing/gestures/#dragging-from-123-or-abc), which types on a layer the Shift key isn't on. - **Press and hold ?123 for the number pad** (on by default) makes a long press on the symbols key open a full numeric keypad panel over the current field. A quick tap still switches to the symbols layer as usual. - **Press and hold enter for emoji** (off by default) puts an emoji entry at the front of the Enter key's popup, so a hold and release opens the emoji panel. Where Enter shows Send or Search, the line break stays in the popup next to it. - **Currency keys** lets you reorder or replace the glyphs the $ key's popup offers (৳ € £ ¥ ₹ ₿ by default). Type them space-separated, with the one you use most first. On the ?123 page of a language with a currency of its own, that key shows the language's currency instead of $, the way Gboard does: ₹ on Hindi and the other Indian languages, ৳ on Bangla, € on German or French, ₽ on Russian, and so on. $ moves to the front of its popup, and your **Currency keys** follow it. A layout that draws its own symbols page keeps whatever it put there. English, Spanish and Portuguese are written across several currencies, so they go by where your phone is: its SIM, then its network, its time zone and its languages. English shows £ in Britain, € in Ireland and ₹ in India, and stays on $ anywhere the keyboard has no answer for. Portuguese shows R$ in Brazil and € everywhere else. Spanish shows the currency of the Spanish-speaking country you're in, and € outside them. The letter that holds $, d on QWERTY, leads with the same currency, and $ sits right behind it. To put a different symbol there, set it under **Your hold symbols**. A popup you wrote yourself in the layout editor keeps its order. ## Shift, caps lock, and auto-capitalization Key press / Timing ![Shift off, one-shot Shift, and Caps Lock: the three states the key cycles through.](@assets/screens/typing/shift-states.webp) Shift cycles through three states. From off, one tap arms a one-shot **Shift** that capitalizes only the next letter and then reverts on its own. A second tap inside the **caps-lock double-tap** window (350 ms by default, adjustable 150–600 ms) locks **Caps Lock** instead. From either state, a single tap that *isn't* a fast double-tap turns it back off. Getting out of caps lock takes one plain tap, not another double-tap. The key shows which state it's in: its icon turns the theme's accent color for Shift, and sits on a patch of that color for Caps Lock. With text selected, Shift changes the selection's case instead, the way SwiftKey's does: each press steps it through lower case, Title Case and UPPER CASE, and the selection stays put so you can keep pressing (see [Text editing](https://wmkeyboard.pages.dev/tools/text-editing/)). You can also hold Shift and type with another finger, the way a physical Shift works: every letter tapped while it's down comes out a capital, and lifting it puts the keyboard back where it was, without arming Shift for the letter after. Hold it past the long-press time without typing anything and it locks **Caps Lock**, the way a long press of Shift does on most keyboards. That is also the way to caps lock with the cursor on a word while **Shift changes the case of the word at the cursor** is on, where a tap of Shift re-cases the word instead. Under caps lock a held Shift does nothing extra, since the letters are capitals already. Tap Enter while holding Shift and it's Shift+Enter, the same as on Gboard. Auto-capitalization arms Shift for you at the start of a sentence, but only when three things line up: **Automatic capital letters** is on (the default), the field is a plain text field rather than a structured keypad, and the active language's script has letter case at all. Bangla, for instance, doesn't, so auto-capitalize never fires while you type it. It re-checks after every sentence-ending mark (`.`, `!`, `?`, `।`) and whenever you move the cursor or switch fields. A mark inside a quotation or a bracket that opened in the middle of a sentence ends only the quoted words, not the sentence: `An "exclamation!" is` carries on in lower case. When the quotation is the whole sentence, as in `"Stop!" Then`, the next word gets its capital. Caps Lock is off limits to it, so an all-caps line you locked in stays locked in. Switching language re-checks it too whenever either side has no letter case. On Arabic, Bangla and the like, Shift opens a second set of letters rather than capitals, so a Shift armed or locked on English is dropped when you switch to one of them, and the keyboard opens on its plain letters. Switching back to English at the start of a sentence arms Shift again. Between two languages that both have case, English and Russian say, Shift stays as it was. Typing / Automatic corrections That toggle lives on the Typing screen rather than here. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) for the rest of the automatic-corrections group it sits in. ## Hold a letter for a quick edit Key press / Press and hold shortcuts Six letters carry an editing command in their press-and-hold popup: A selects all, C copies, X cuts, V pastes, Z undoes, Y redoes. All six are on by default. They sit at the end of the popup, after the accents the key already has, so a hold on `C` offers `'çć` and then copy, and you slide onto the copy icon to run it. The icon lights up in the same highlight colour as a character does. Copy and cut select everything first if nothing is already selected, so one hold in an untouched field copies the whole field. The same six also run from a slide off the 🌐 key, if you turn on **Slide from 🌐 for shortcuts**: press 🌐, slide onto C, lift, and the text is copied. See [Switching languages](https://wmkeyboard.pages.dev/languages/switching/#sliding-from--onto-a-shortcut). Those six keys are `a c v x z y` only because that is the QWERTY answer. The **Shortcut keys** row in the same group is where you move them onto keys your own layout actually draws. A Bengali or Russian grid has no `a` to hold, so without that row the switches would do nothing at all. ![Copy at the end of C's press-and-hold popup, after the accents.](@assets/screens/typing/hold-letter-clipboard.webp) This is a different mechanism from Ctrl+letter on a physical keyboard, which has its own toggle for whether it sends the same clipboard action or a raw key event (some apps, like terminals, need the raw event). See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/). ## The Enter key Typing / Enter key typing/enter-action-icons — Enter drawing a field's own action icon. The key's icon and behavior follow whatever the focused field asks for: Search, Send, Go, Next, Previous or Done, each with its own icon, or a plain newline when the field doesn't specify. When an app supplies its own label instead of a standard action ("Reply", "Post"), the key draws that text directly rather than an icon. A message box that asks for a line break gets one, even when the app also watches for the Enter key press itself. Discord's is the example: it declares Done and then asks the keyboard not to use it, so the key draws the newline symbol, and now it types the break straight into the box rather than pressing a key the app would have read as Send. Some apps change the action as you type, and the key follows. The Pixel Launcher's app search is one: the key shows Search while your words are a web search and an arrow once they match an app, and pressing it runs that search or opens the app instead of putting a line break in the box. **Shift and Enter type a new line** (on by default) lets you press Shift then Enter, or hold Shift on a physical keyboard, to force a line break in a field that would otherwise send or search. The key itself changes to the newline symbol while Shift is armed, so it never promises a Send it isn't about to do. Only a Shift you pressed yourself counts. The one-shot Shift that auto-capitalize arms at the start of a message is ignored, so the first line of a chat still sends normally, and Caps Lock is ignored too. Holding Shift down while you tap Enter counts as well. In a field with no Send or Search of its own, such as a document in Google Docs or Word, the keyboard sends a real Shift+Enter instead of a plain one. Those editors read Shift+Enter as a line break inside the paragraph and plain Enter as a new paragraph, so you get both. This works with the setting off too, since there's no action for it to override; an ordinary text box types the same new line either way. While that setting is on, the key carries both things at once: whichever one it's about to do fills the key, and the other sits small in the top right corner, the way a letter's alternate does. So a Send field shows the paper plane with a newline symbol in the corner, and arming Shift swaps the two over: the newline fills the key and the paper plane moves to the corner. Send never leaves the keyboard; it just changes places. The hold swaps with them: press and hold Enter while Shift is armed and the popup offers Send instead of a second line break. Sending that way also puts Shift down, so the next Enter in the emptied box sends again. There's a second way in that doesn't need the setting at all: in any field where Enter has taken on the app's own action, press and hold the key. The popup that opens carries a newline entry, the same as Gboard's. Tap it and you get a line break, whatever the app declared. This one is always available and it never sends. ## Number row and numerals Layout & size / Number row typing/number-row — The number row above the letters. **Number row** (on by default) gives the digits 1–0 a dedicated row above the letters, so they don't need a long-press. While it's there, the digit that would otherwise be the top row's first long-press alternate is dropped, since the row makes it redundant. To keep those digits, or to hold the row's symbols on `Q`–`P` instead, change **Top row press and hold** under Key press / Press and hold shortcuts. A few sub-settings sit under it. **Number row height** runs 32–100 dp. **Symbols on shift** (off by default) swaps the row to `= \ < > [ ] { } | ~` while Shift is held, instead of digits. Only a finger on Shift does it: a tap of Shift for one capital, or the capital at the start of a sentence, leaves the digits in place. **Number row in symbols** (on by default) keeps the digit row up on the ?123 layer too. The symbols layer's own second page carries its arrow and comparison symbols either way. **Symbol row on number pads** (on by default) keeps the row over the keypad a number, phone, date or time field puts up, where it carries symbols rather than a second set of digits: `+ * # , ; ( ) - / .` on a phone field, `+ - * / = ( ) % : .` on the others. Turn it off for the bare keypad; the digits over the letters stay either way. A [handwriting layout](https://wmkeyboard.pages.dev/tools/handwriting/#handwriting-as-a-layout) never has the row over its writing area. To change the row's keys once for every layout and language, open **Number row keys** in the same group; it also has the row as JSON. You can change the keys of the number row on each layer of one layout, and the row that takes the place of the digits on the symbols layer, in the [layout editor](https://wmkeyboard.pages.dev/reference/settings/keymaps/#the-rows-around-the-grid). A layout's own row wins over the keyboard-wide one. If you'd rather move the cursor with keys than with a drag on the spacebar, **Arrow keys row** under Layout & size adds a row of ← ↑ ↓ → under the spacebar, in the order you choose. Turn on **Arrow keys on top** to have it above the keys instead. See [Layout & size](https://wmkeyboard.pages.dev/reference/settings/layout/#arrow-keys-row). Layout & size / Numerals Separately, **numeral system** decides which *glyphs* the number row and keypad show, per language: Arabic can display ٠-٩ while English next to it stays on 0-9. Every language starts on **Auto**, which follows that language's own convention (Arabic and Sorani → Arabic-Indic, Persian/Urdu → Persian, Bangla → Bengali, Devanagari-script languages → Devanagari, everything else → Latin). Override it per language on that language's own screen rather than here (see [Languages](https://wmkeyboard.pages.dev/languages/overview/)). This screen's **Type native digits in** setting answers a different question: whether those native glyphs are what actually gets *committed* when you type them. **Text fields**, the default, types the native glyphs everywhere except number, phone and date/time fields, which keep plain 0-9. **Everywhere** commits the native glyphs unconditionally. **Display only** always types plain ASCII digits, whatever the keys show. For a language with digits of its own, the other set is a press and hold away on the number row. Hold a digit and the first entry in its popup is the same digit in the other system, so a plain hold and release types it: `1` on an Arabic board that shows `١`, and `١` on one you've switched to `0-9`. Under **Display only** the hold offers the glyph the key shows, since a tap already types ASCII. Languages that only use `0-9` have nothing to offer, and their popups are unchanged. ## Autopilot Typing / Suggestions / Autopilot **Autopilot** (on by default) quietly widens a key's touch target toward the letter that continues the words the suggestion strip is offering, so the keys follow the same prediction the strip does, secondary languages and context included. A tap that lands just inside a neighboring key can still commit the letter you meant, but only near the shared edge. A deliberate press in the middle of a key is never second-guessed. It applies to the letters layer only, and it needs [Suggestions](https://wmkeyboard.pages.dev/smart/suggestions/) turned on to have anything to predict from. On Pinyin the strip's Hanzi share no letters with what you type, so it reads the reading behind each candidate instead: after `nih`, 你好 makes the `a` key the one that grows. Among the layouts that convert a reading, only Pinyin (with Jianpin or Double Pinyin too) does this; on the others autopilot stays off. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/#reducing-typos-before-they-happen) for how it sits alongside autocorrect as an earlier line of defense against typos. **Strength** (1–10, 5 by default) sets how far an expected letter reaches over its neighbor. Two optional views show you what that reach looks like: **Show the effect** redraws the three likeliest letters at the size their touch areas have grown to, and **Outline the touch areas** traces the same edge. Both are off by default and are meant for tuning the slider, not for everyday typing. Neither shows during a glide, and neither comes back when you move the caret into an old word. **Size of the effect** (×1.0 to ×3.0) exaggerates both for readability, multiplying the growth alone; it changes the drawing and never the touch areas themselves. ## Multi-touch and key repeat Every finger on the keyboard is tracked on its own, so pressing a second key before you release the first works correctly. Both register, whether that's rollover typing across the letters or two fast taps on the very same key. On the default layout, ⌫ and Space are the two keys that auto-repeat on a hold. Repeating has its own wait, **Time before a key repeats** (300 ms by default, adjustable 150–800 ms), separate from the long-press delay above. The two used to be one number, which meant lengthening the wait before accent popups also slowed backspace down. Past that wait each key fires again on its own cadence, **Delete repeat interval** and **Space repeat interval** (50 ms and 100 ms by default, both adjustable 20–200 ms), because a runaway row of spaces costs more to undo than a fast delete does. A held backspace stops on its own once there's nothing left to delete. A press with the cursor already at the very start of a text box is passed to the app as a plain backspace key, so apps that split a page into blocks, such as Notion, join the empty line to the one above it the way they do with any other keyboard. In a text box on a web page, every backspace goes out as that key press, so code editors on a page such as Violentmonkey's script editor delete too; **Backspace as a key press on web pages** under Key press turns that off. **Holding delete clears words** (off by default, on the same Timing group) turns that repeat into one whole word per tick, at its own slower **Word delete interval** (140 ms, adjustable 60–500 ms); a single press still deletes one character either way. A forward-delete key (⌦) repeats the same way if you add one to a [custom layout](https://wmkeyboard.pages.dev/languages/custom-layouts/), and clears words the same way too. No built-in layout places one, and a physical keyboard's Delete-forward key goes straight to the system rather than through this engine. Every other key long-presses into its popup or shortcut instead of repeating. Accessibility / Touch **Ignore repeated presses** (off by default) filters out the accidental double-taps a hand tremor causes. Auto-repeat deliberately bypasses that filter: the software repeat is already deterministic, so filtering it would only cap how fast delete or space can go. Both delete keys also have a drag-to-delete-whole-words gesture, on by default, separate from this hold-and-repeat: leftward on ⌫, rightward on ⌦. [Gestures](https://wmkeyboard.pages.dev/typing/gestures/) covers them. ### What one press deletes A press of ⌫ takes one character as it appears on screen, which is not always one thing in the text underneath: - **A multi-part emoji goes whole.** A family, a flag, a thumbs-up with a skin tone or a keycap like 1️⃣ is several code points, and one press removes all of them. - **Invisible parts go with what they belong to.** A variation selector (which picks the text or emoji style of a symbol, or a variant form of a CJK character), the second half of a Windows line break, and a Korean syllable spelled out as separate jamo would each leave a press that changed nothing on screen, so they are taken together with the character before them. - **A mark you type with its own key comes off on its own.** An Arabic harakah, a Hebrew vowel point, a combining accent or a Persian zero-width non-joiner is deleted alone and leaves its letter in place, so fixing a wrong mark costs one press rather than a retyped letter. - **Indic conjuncts** follow a per-language setting: whole, or one part at a time. [Bengali](https://wmkeyboard.pages.dev/languages/bengali/#editing-conjuncts-and-clusters) describes it. ⌦ has nothing typed ahead of the cursor to peel back, so it always takes the whole character after it, marks and all. What counts as one character comes from Android's own Unicode rules, with the keyboard's emoji rules on top for older Android versions that split emoji apart. On Android 15 and later those rules also treat a whole Indic conjunct as one character, so ⌦ takes क्ष in one press there. ## Options Key press | Setting | Default | | --- | --- | | Key popup | On | | Popup on key | On | | Popup on number pads | Off | | Minimum / maximum popup duration | 140 ms / 750 ms | | Popup font size | ×1.00 | | Popup height | 110 dp | | Alternate key size | ×1.00 (×0.70–×3.20) | | Alternate key spacing | 10 dp (0–32 dp) | | Alternate key columns | Auto (or 3–11) | | Even rows | Off | | Closest to the key first | Off | | Remember the punctuation key's pick | Off | | Order of the alternates | Layout's own, accents, capital | | Choose without lifting | On | | Popup distance above key | 10 dp (0–96 dp) | | Popup sideways shift | 0 dp (−64–64 dp) | | Popup color / Popup text color | Automatic | | Press and hold delay | 300 ms (100–800 ms) | | Time before a key repeats | 300 ms (150–800 ms) | | Delete repeat interval | 50 ms (20–200 ms) | | Space repeat interval | 100 ms (20–200 ms) | | Caps lock double press | 350 ms (150–600 ms) | | Press and hold hints | On | | All accents on press and hold | Off | | No accents on press and hold | Off | | Top row press and hold | Auto | | Your hold symbols | None (each layout's own) | | Your language's letters first | On | | Shifted letter on press and hold | Off | | Press and hold ?123 for the number pad | On | | Press and hold enter for emoji | Off | | Currency keys | ৳ € £ ¥ ₹ ₿ | | Press and hold A/C/X/V/Z/Y for select all/copy/cut/paste/undo/redo | On (each) | Open the **Key press** tile on the settings home grid, or search "Key press" in settings search. This table covers the Key popup, Alternate keys, Timing and Press and hold shortcuts groups. The same screen's Haptic feedback and Key press sound groups are covered on [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/). For the field-by-field breakdown see [Key press settings](https://wmkeyboard.pages.dev/reference/settings/keypress/). A handful of related settings live elsewhere: - **Automatic capital letters** is on by default, under Typing / Automatic corrections. - **Autopilot** (Typing / Suggestions / Autopilot) and **Shift and Enter type a new line** (Typing / Enter key) are both on by default. - **Swipe to delete** is on by default, under Key press / Delete keys, and starts out deleting a word per step of the swipe. - **Number row** (Layout & size / Number row) is on, and the numeral commit scope (Layout & size / Numerals) starts on "Text fields". Full breakdown at [Layout & size settings](https://wmkeyboard.pages.dev/reference/settings/layout/). - **Numeral system per language** starts on Auto, under Languages & layouts / (a language) / Numerals. See [Languages](https://wmkeyboard.pages.dev/languages/overview/). - **Ignore repeated presses** is off by default, under Accessibility / Touch. See also [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/). ## Details & edge cases - **A hold-shortcut on A/C/X/V/Z/Y is appended to the end of that letter's accent popup, not swapped in for it.** With the Copy hold shortcut on, `c` still offers `'çć` first, then Copy. Use **Action position** to move the shortcut in among the accents, one slide from the key, or to the front, where a plain hold runs it. - **Exiting Caps Lock only takes one tap.** The double-tap window matters only on the way *into* Caps Lock. Any single tap while Shift or Caps Lock is already active turns it off. - **The Enter key never lies about what it's about to do.** With Shift + Enter's newline override active, the key visibly redraws to the newline symbol first. It doesn't swap behavior behind an unchanged icon. The action it stepped off doesn't vanish either. It moves to the key's corner, so you can see both what Enter does now and what letting the Shift go gives you back. - **A line break from the popup is a real one, even in apps that read Enter themselves.** Some chat apps watch for the Enter key press and send on it regardless of what the keyboard meant. The popup entry types the character straight into the field instead of pressing a key, so there's nothing for the app to catch. Shift + Enter works the same way, and so does plain Enter in a message box that asked for a line break. - **The long-press delay has one home.** It's the Key press → Timing slider (100–800 ms). Accessibility → Touch shows the current value and links to it. - **Number row height is independent of key height.** Shrinking the digit row doesn't touch the letter keys below it, so you can get quick number access without paying for a full extra row of keyboard height. - **Ignore repeated presses only filters repeats of the *same* key.** Two different keys typed quickly are never blocked, so the setting can be raised without slowing normal typing down. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/) for spacebar swipes, cursor control and the backspace word-delete drag. [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) covers how physical Ctrl and Shift shortcuts differ from the on-screen ones here, and [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/) covers the rest of the Key press screen. --- # Sound & vibration URL: https://wmkeyboard.pages.dev/typing/feedback/ Section: Typing Summary: Key sounds, haptics, and how to tune or silence them. Every key press can confirm itself two ways: a short vibration and a click sound. Both are tunable on their own, down to which events buzz and which don't. Haptics are on by default. The sound is off. typing/keypress-settings-overview — The Key press screen: haptic style chips above the key press sound group. ## Haptic feedback Key press / Haptic feedback **Key press haptics** vibrates on every key press, including the small steps as you swipe-drag a cursor across the Space bar. It's on by default. Turn it off and the keyboard goes quiet for touch, though the sound still plays if you've turned that on. With haptics on, **Haptic style** picks how the buzz is produced: | Style | What it does | Requires | | --- | --- | --- | | Tap | Platform's own "keyboard tap" haptic, the one vendors tune for keyboards (default) | Any device | | Key | Platform's own "virtual key" haptic, the general button press | Any device | | Click | Hardware-tuned click effect | Android 10+ | | Heavy | Hardware-tuned heavy-click effect | Android 10+ | | Sharp | Hardware click primitive, strength follows the intensity slider | Android 11+ | | Custom | Drives the vibration motor directly by duration and intensity | Android 8+ | typing/haptic-style-custom-sliders — Haptic style chips, with the strength and intensity sliders shown for Custom. Tap and Key are the two to reach for first. They hand the buzz to the same platform call stock keyboards use, so a phone with vendor-tuned haptics (Samsung, Pixel) gives you that tuned click, and it follows the system's own haptic-intensity slider on its own. Tap is the default, and on most phones it's the one you want. Vendors usually tune a separate waveform for keyboards, and Tap is what their own keyboard plays, so it tends to feel crisper than Key even though the two are only one constant apart. On a Galaxy S25 Ultra, for instance, Tap and Key resolve to different Samsung effects, and Tap is the one Samsung Keyboard uses. Switch to Key if Tap feels wrong on your device. That happens on phones whose vendor never bothered to tune a keyboard-specific effect. Click, Heavy, Sharp and Custom bypass the platform call and drive the motor more directly, which is useful if you want a feel the platform styles don't offer. When a style isn't available on a device, it falls back to a hardware Click and then to Custom, so every style produces some feedback everywhere. Custom exposes two sliders: **Haptic strength** (5–60 ms, vibration length) and **Haptic intensity** (1–255, how hard the motor is driven). Sharp exposes intensity too, to control the punch of its click primitive, but not strength, since the hardware fixes the primitive's length. The system-wide touch-feedback vibration setting still scales the final strength on top of whatever you set here. Two more toggles cover the edges of a long press. **Long-press haptics** (on by default) buzzes the moment a long press registers, whether that's the alternate-character popup opening or a long-press action firing, so your finger knows it can let go. **Long-press release haptics** (off by default) adds a second buzz when you lift your finger afterward. It's off because a third vibration in one gesture reads as excessive to a lot of people. ### What the setup wizard picks Tap is the app's own default, but it may well not be the style you find selected after a first run, and that isn't a bug. The setup wizard asks the vibration motor once, at the top of a fresh install, and writes whichever style the device can actually play. That's **Key** on a phone whose motor supports the predefined click effects, and **Heavy** where the motor reports them unsupported, or where the phone is older than Android 11 and support can't be read at all. It only ever happens on a genuine first run, never on a replay of the wizard, so it can't overwrite a choice you've already made. If you wanted Tap, pick it from the chips. Nothing will move it back afterwards. ### Per-event vibration Six more toggles, visible only while Key press haptics is on, let you keep the buzz everywhere except one event, rather than trading it all for silence: - **Vibrate on space** (on) is the Space bar's own press buzz. Turning it off doesn't silence the space key outright, since the key press sound still plays if it's on. - **Vibrate on delete swipe** (on) buzzes on each word a ⌫ swipe removes. Off turns clearing a sentence into one smooth pull instead of a buzz-saw. A plain backspace tap is unaffected either way. - **Vibrate on key repeat** (on) buzzes on every auto-repeat while ⌫ or Space is held. Off keeps only the first press buzzing. - **Click on key repeat** (on) is the sound half of the same thing: every auto-repeat plays the key sound too. Off leaves only the first press audible, which is worth having if you hold backspace with the sound on. It does nothing unless the key sound is on. - **Follow the system touch feedback setting** (off) is the odd one out, since it isn't per event. By default the keyboard keeps buzzing even when Android's own touch-feedback switch is off, because the keyboard has its own switch for that. Turn this on and the system switch silences the keyboard too. - **Vibrate past the system touch feedback switch** (on, Android 13 and newer) is what keeps that true for every style. The System styles get past Android's switch on their own. Custom, Sharp, Click and Heavy click drive the vibrator directly, and before this setting Android silenced them along with every other short buzz. With it on they go out as hardware feedback, which the switch leaves alone; Android's vibration-strength slider still scales them. Turn it off if the buzz feels wrong on your phone. The row hides while "Follow the system touch feedback setting" is on. With Android's switch off, the haptics group shows a note saying whether your style still vibrates; on Android 12 and older, only the System styles do. - **Mute haptics in Do Not Disturb** (off) is covered in [Silent and Do Not Disturb](#silent-and-do-not-disturb) below. ## Key press sound Key press / Key press sound **Key sound** plays a click on every key press. Unlike haptics, it's off by default. Turning it on immediately previews whatever style is currently selected, so you hear what you're about to turn on. typing/keysound-style-chips — Sound style chips with the volume slider below. **Sound style** offers seven options as chips: | Style | Source | | --- | --- | | Click | Device's system sound pack: the classic key tick, matching the stock keyboard | | Standard | Device's system sound pack: the softer AOSP key press | | Pop | Built into WM Keyboard: a soft bubble pop, identical on every device | | Thock | Built into WM Keyboard: a deep mechanical bottom-out, identical on every device | | Chime | Built into WM Keyboard: a small bell ring, identical on every device | | Custom | A single sound file you've installed (see [Installing more key sounds](#installing-more-key-sounds)) | | Sound pack | An installed pack of many recordings, one picked per key press and, on some packs, another when you lift (see [Sound packs](#sound-packs)) | Click and Standard come from the device's own system sound pack, so they vary phone to phone the way the stock keyboard's clicks do. Pop, Thock and Chime are synthesized in-app and sound the same everywhere. All seven chips are on the settings screen. On the keyboard's own Sound & haptics panel, Custom and Sound pack appear only once one of them is already selected, since neither names a fixed waveform and there's no file picker on the keyboard. Pick them from the settings screen the first time. **Sound volume** (5%–100%) is relative to the system media volume rather than an absolute level. ### Installing more key sounds Pick the Custom chip to reveal your installed sound library and an **Import a sound file (.mp3)** button that also accepts `.ogg` and `.wav`. A sound you haven't chosen falls back to the system click rather than staying silent, so a key press never goes unheard because a file went missing. You can grow that library two ways: import a file directly here, or install a key sound addon from an addon repository. Both land in the same list. See [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general install flow. A key sound addon lands under **Key press → Sound style** and, like themes and layouts, offers to switch to it right after install. ## Sound packs Key press / Key press sound / Sound pack A **sound pack** is a key sound with more than one recording in it. Pick the Sound pack chip to reveal your installed packs and an **Import a sound pack (.wmsoundpack or LeanType .zip)** button. Why it exists: a single file can't sound like a real keyboard, because a real keyboard never makes the same noise twice. A pack holds up to 32 recordings per key, and the keyboard picks one for each key press, so a run of the same letter doesn't come out as a loop. The row under each pack's name tells you how many recordings it holds. The same recording never plays twice in a row, which is a small but audible difference from how [monkeytype](https://monkeytype.com) does it. Monkeytype picks uniformly across the whole list, so on a three-recording set roughly every third keystroke repeats the previous one, which is exactly what the recordings are there to avoid. ### Key release sound A mechanical keyboard makes two noises per key: the switch going down, and the stem coming back up when your finger leaves. Packs recorded from real switches carry both, and the keyboard plays each when it happens. Hold a key and you hold the second sound too, instead of it firing on a timer. Packs that have it say so on their row ("10 recordings • keys sound on the way up"), and a **Key release sound** switch appears under the list to turn it off. The switch is hidden for packs without key-up recordings, because there'd be nothing for it to do. Every other sound style is one sound per press and is unaffected either way. Tap a pack in the list and it plays a whole keystroke (press, a short hold, release) rather than just the way down, since half a pack is a different pack. ### Separate sounds per key A pack can also carry different recordings for different kinds of key: the Space bar, Enter, ⌫, and the modifier row (Shift, ?123, the globe and so on). On a real board those do sound different, mostly because of the stabilizers under the longer keys. Most packs don't fill those slots and don't need to. When a pack does, its row says so: "10 recordings • separate sounds for space, enter". A slot a pack leaves empty falls back to the pack's main recordings, so nothing ever goes silent. This is independent of the key-up recordings above. A spacebar recorded on its own still uses the pack's ordinary key-up sound unless it brought one. ### A sound for one key A pack can go further and name individual keys, so a and b make different noises. That is what a voice pack is: a syllable per letter instead of a click for the whole board, in the spirit of [Animalese typing](https://github.com/joshxviii/animalese-typing-desktop). Packs that do it say so on their row ("26 recordings • its own sound for 26 keys"). Keys the pack didn't name are unaffected and keep using its main recordings, so a pack can cover five letters and leave the rest of the board alone. A pack addresses a key by **what it types**, not by where it sits, so one recording of `a` covers that letter on every layout that has it. Uppercase and lowercase share the one recording. Keys that type nothing (Shift, the layer switches, the globe) can't be named individually; they're covered by the modifier slot above. Publishing one is [Per-key sounds](https://wmkeyboard.pages.dev/development/addon-repos/sound-packs/#per-key-sounds). ### A different pack per layout Key press / Key press sound / Sound pack / A pack per layout With two or more layouts enabled and at least one pack installed, a list of your layouts appears under the packs. Tap one to give it its own pack: a voice pack on the layout you chat in, switch recordings on the one you write code in. Layouts you leave alone say "Same as the pack above" and follow the pack selected in the list, so this is a list of exceptions rather than a second place to keep in step. **Clear every layout's pack** drops the lot. A layout's pack is decoded when you switch to it, so the first key of the new layout already sounds like it. ### Where packs come from Same two routes as single sounds: import a `.wmsoundpack` file here, or install one from an addon repository. A sound pack `.zip` made for [LeanType](https://github.com/LeanBitLab/LeanType-SoundPacks) imports too, and the official addon repository carries 22 of them, converted, under the names you would see in LeanType. The [Monkeytype Key Sounds](https://github.com/wasi-master/wmkeyboard-monkeytype-sounds) repository ships pre-added and carries 26 packs built from monkeytype's own sound files, including recordings of Cherry MX, Kailh, Akko, Tealios and Razer switches. Twelve of them (the mechanical switch sets, the typewriter and the two boards) have their key-up sounds cut out of the recordings, so they behave like the keyboards they came from. Before installing, an addon's page has a **Preview** that plays each recording in the pack separately, key-downs and key-ups on their own rows, so you can hear how much it actually varies rather than guessing from one sample. If a pack goes missing while it's selected, the key press falls back to the system click rather than going quiet, exactly as a missing single sound does. ## Silent and Do Not Disturb The ringer switch and Do Not Disturb are handled differently: - **Key press sound follows the ringer switch automatically, with no setting for it.** It plays through the same system sound-effect stream the rest of Android uses, so silent and vibrate mode mute it the way they'd mute any other system sound. The toolbar "Sound & haptics" panel (see below) shows a live warning whenever the sound is on but the ringer would swallow it: "Your device is on vibrate. The key press sounds do not play.", or the same line with "silent" in place of "on vibrate". - **Haptics do not follow Do Not Disturb, by default.** DND targets notifications rather than touch feedback, so the keyboard keeps buzzing under DND unless you turn on **Mute haptics in Do Not Disturb**. With that on, every keyboard vibration stays silent for as long as DND is active, down to the long-press and per-event buzzes. Ringer mode and DND are independent Android states. Sound only checks the ringer, haptics only check DND, and neither one looks at the other. ## A toolbar shortcut Sound and haptics settings are reachable straight from the keyboard, without opening the Settings app. Add the **Sound & haptics** tool to your toolbar and its panel gives you the master toggles in a compact view: haptic style with its intensity and strength sliders, sound style and volume. The per-event toggles and long-press options stay on the full settings screen. Every change here writes to the same settings either way. As on the settings screen, a slider on this panel stores its value when you lift your finger rather than on every step of the drag; see [Settings overview](https://wmkeyboard.pages.dev/reference/settings/#details--edge-cases). typing/sound-haptics-toolbar-panel — The Sound & haptics toolbar panel, with the ringer warning banner shown. No onboarding answer switches it on, not even "Show me everything", so if you finished the setup wizard you'll need to enable it from Tools first. Either way, drag it from the toolbox up onto the toolbar to pin it. See [The toolbar](https://wmkeyboard.pages.dev/tools/overview/) for how pinning works. ## Options Key press | Setting | Default | | --- | --- | | Key press haptics | On | | Haptic style | Tap (but see [what the wizard writes](#what-the-setup-wizard-picks)) | | Haptic strength (Custom only) | 15 ms | | Haptic intensity (Custom, Sharp) | 255 | | Long-press haptics | On | | Long-press release haptics | Off | | Vibrate on space | On | | Vibrate on delete swipe | On | | Vibrate on key repeat | On | | Click on key repeat | On | | Follow the system touch feedback setting | Off | | Vibrate past the system touch feedback switch | On | | Mute haptics in Do Not Disturb | Off | | Key sound | Off | | Sound style | Click | | Sound volume | 50% | | Key release sound (packs that have it) | On | | A pack per layout | None: every layout follows the selected pack | Open the **Key press** tile on the settings home grid, or search "Key press" in settings search. For the field-by-field breakdown, including the key popup and the long-press editing shortcuts that share this screen, see [Key press settings](https://wmkeyboard.pages.dev/reference/settings/keypress/). ## Details & edge cases - **You'll see a slimmed-down version of this during setup.** The onboarding wizard's "Typing feedback" page is three plain on/off switches: Key press haptics, Key press sound and Key popup. Turning either of the first two on plays what you just chose. Everything finer (the style chips, strength, per-event toggles, DND) lives only on the full settings screen. - **Fast typing won't feel like a smear.** Rapid presses coalesce with a short minimum gap between vibrations, so rollover typing still reads as distinct taps rather than one continuous buzz. The key sound skips that coalescing deliberately, since two clicks overlapping is not the problem two vibrations overlapping is. - **Power saving turns both off out of the box.** While [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/) is active, its **Key vibration** and **Key sounds** switches override whatever you've set here, and your choices come back the moment it deactivates. Both of those switches ship on, and each can be turned off on its own under Tools / Power saving if you would rather keep the buzz or the click while the battery is low. - **Nothing on this page is build-gated.** Haptics, key sounds and sound addons all work identically in every build. See [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) for how touch feedback interacts with screen-reader use, and [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general addon install flow that key sound packs go through. --- # The keyboard adapts to the field URL: https://wmkeyboard.pages.dev/typing/field-adaptation/ Section: Typing Summary: Email fields get @, URL fields get / on the comma's long press, and password fields get privacy automatically. WM Keyboard reads what kind of field you've tapped into, email or phone number or password or five others, straight from the app's own field description. Then it quietly changes the keys on offer to match. None of this needs a setting. It's the same keyboard reacting to what's under your thumb. typing/field-adaptation-email — An email field: the bottom-row comma has become @, and the period key's long-press offers .com, .net, .org, .edu, .co. ## How the keyboard reads a field Every field an app gives you carries an `EditorInfo` with a type, the same information Android's own keyboards read. WM Keyboard turns that into one of eight `FieldKind` values: `TEXT`, `EMAIL`, `URI`, `NUMBER`, `PHONE`, `DATE`, `TIME`, `DATETIME`. Five of them (`NUMBER`, `PHONE`, `DATE`, `TIME`, `DATETIME`) are numeric-pad kinds and get a dedicated keypad instead of the letter layouts. The other three (`TEXT`, `EMAIL`, `URI`) keep the letters and adjust individual keys, covered below. This detection is automatic and isn't a setting you can turn off. It runs on every field focus, for every app, in every build. A password field is a separate signal entirely. It doesn't get its own `FieldKind`: a masked text field reports `FieldKind.TEXT` identically to an ordinary prose box. That's deliberate, because the privacy behavior a password needs (suggestions off, nothing learned, clipboard cleaned up) applies just as much to a numeric PIN field as to a text one. Instead there's a second, independent flag, `secureField`, computed alongside `FieldKind` from the field's password variation, or from its PIN variation on a numeric field. A 4-digit PIN box shows both firing together: it's `FieldKind.NUMBER`, so it gets the numeric keypad, and `secureField = true`, so it gets every privacy rule below. ## Typing in email and web address fields typing/field-adaptation-uri — A URL field: / ? # & = on the comma key's long-press. Email and URL fields keep the ordinary letter layout, since you're typing words either way, but two keys on the bottom row only change to match: - **Email fields** (`FieldKind.EMAIL`): the comma key becomes @, and the period key's long-press gains domain endings ahead of its usual alternates: `.com`, `.net`, `.org`, `.edu`, `.co`. - **URL fields** (`FieldKind.URI`): the comma key stays a comma, since a browser's address bar doubles as its search box, but its long-press becomes `/`, `?`, `#`, `&`, `=`, and the period key's long-press gains `.com`, `.org`, `.net`, `www.`, `https://`, `/`. The swap only touches the bottom row on purpose. A Dvorak layout has real `.` and `,` letter keys elsewhere on the board, and those stay untouched. It's scoped to the letters layer as well, so switching to the symbols page shows the normal punctuation. Like field detection itself, this has no toggle in settings. It's on whenever the field kind matches. ## Typing in number, phone, date, and time fields typing/field-adaptation-numpad — A phone-number field: a 4-by-4 dial pad in phone order, not calculator order. The five numeric-pad field kinds each get their own 4-column keypad. Digits are arranged phone-pad style, 1-2-3 on top rather than 7-8-9 like a calculator, with 0 sitting under the 8 and enter anchoring the bottom-right. Delete takes the top-right corner on four of the five. The Number pad swaps it down a row and puts space on top, since deleting is the frequent correction while typing a figure. A keypad's space key is marked ␣, the open box keycaps use for a space, since a key a quarter of the pad wide has no room for the language name the spacebar shows. The remaining keys are tailored to what that field type needs: | Field kind | Right column | Bottom-left | Bottom-right | Notes | | --- | --- | --- | --- | --- | | Number | `-` (long-press `+ % * /`), space | `.` (long-press `:`) | `,` | Minus and decimal always shown, even if the field didn't ask for them | | Phone | `-` (long-press `( ) / .`), space | `*` (long-press `,`) | `#` (long-press `;`) | Zero key's long-press adds `+` | | Date | `/` (long-press `:`), `-` | `.` | `,` | Covers `20/07/2026`, `2026-07-20`, `20.7.26` styles | | Time | `:` (long-press `.`), space | `.` | `-` | Colon-led for `17:45` | | Date & time | `/` (long-press `.`), `:` | `-` | space | Date and time separators together | If you moved backspace or enter on the **Numpad** tool's own layout, these keypads follow you: each of the two keys trades places with whatever key of the pad sits where you put it on the tool, and everything else on the pad stays put. A Numpad you never edited changes nothing, and neither does a keypad a custom layout defines for itself. Snippets work on these keypads too. A trigger ends with space or Enter as anywhere else, and one led by a symbol or a digit, such as `#1` or `@@`, works here and on the **Numpad** tool. In these fields a snippet expands only when its text is made of what the field takes: digits and the marks on that keypad. A snippet with letters in it is left as you typed it, rather than have the app keep some of its characters and drop the rest. Suggestions, autocorrect and learning stay off in them. The Number pad also does sums. An operator typed after a number starts one, which the keyboard holds on the strip because the field can't, and Enter or a tap on the chip puts the answer in place of the number. See [Sums in number fields](https://wmkeyboard.pages.dev/smart/chips/#sums-in-number-fields). | | Text | Email, web address | Number, phone, date, time | Password | | --- | --- | --- | --- | --- | | Suggestions, autocorrect, learning | Yes | No | No | No | | Snippets on space or Enter | Yes | Yes | Only text the field takes | Only if [turned on](https://wmkeyboard.pages.dev/tools/snippets/) | | Snippet chips | Yes | Web address only | Every snippet whose text the field takes, offered outright since the keypad can't type a trigger | No | These five keypads are shared across every built-in layout. Only the default QWERTY layout defines them and every other language layout inherits the same set, but a custom `.wmlayout.json` can override any of the five with its own key rows. The keypad shows up whenever the field kind calls for it, with one exception. If focus has been rerouted to something that needs letters, such as an in-panel search box (emoji, dictionary, clipboard, media), a plugin's own text field, or the typing-test tool, the letter layout stays up instead. A digits-only pad would leave those impossible to type in. Key press / Key popup / Popup on number pads The floating character bubble that normally appears above a held key is off by default on these five keypads. Turn on **Popup on number pads**, shown only once **Key popup** itself is on, to bring it back. It's off by default because, over a PIN, echoing each digit large enough to read is closer to a shoulder-surfing risk than a help. Don't confuse this with the standalone **Numpad** tool in the toolbox, which is a separate keypad panel you open on purpose from any field. Its own phone-vs-calculator digit order is a toggle under that tool's own settings and has no effect on the field-adapted keypads described here. Those are always phone-pad ordered. ## Typing in a password field typing/field-adaptation-password — A password field: the suggestion strip stays hidden, and pasting into it wipes the clip afterward. `secureField` drives a wider set of behavior than just hiding the suggestion strip: - **Suggestions never show.** Password variations force the suggestion strip off unconditionally. Typing / Suggestions / Suggestions in every field can't override it, since that setting exists specifically to make hidden strips show anyway on ordinary fields. Only one other thing is beyond that setting's reach: a field whose class isn't text at all, whose keypad has no words to offer. - **Autocorrect and gesture typing turn off too, and not only in password fields.** These, along with on-device learning, are gated on a broader check than `secureField` alone. It also excludes every structured field kind (`EMAIL`, `URI`, `NUMBER`, `PHONE`, `DATE`, `TIME`, `DATETIME`). A keypad has no words to autocorrect, and an email address shouldn't be second-guessed either. Only a plain `TEXT` field that also isn't secure gets the full typing-intelligence treatment. - **Phonetic (Avro) composing is the one exception: it keeps running everywhere, password fields included.** Avro turns roman key presses into Bangla as you type, and that step isn't gated on the same check at all. Turning it off in a secure or structured field would mean the roman keys commit untransliterated rather than no Bangla being produced. Only the suggestion-feeding side of composing, plain English composing, which exists purely to feed autocorrect and suggestions, is gated alongside autocorrect and gesture typing. - **Nothing is learned.** The same gate feeds Privacy / Learning on this device / Learn from typing. Typing in a password field, or any structured field, never reaches the on-device learning dictionary. - **Voice typing refuses to start.** Both the voice panel and the toolbar strip's dictation toggle decline while a secure field is focused. The toolbar toggle shows a toast reading "You cannot use voice typing in a password field", the panel shows its own inline notice ("Voice typing does not work in password fields."), and the voice bar reads "Not available in password fields". - **Clipboard history stops recording** while a secure field has focus, so new copies (and screenshot-to-clipboard captures) are skipped for as long as it's focused. - **Pasting into a password field can wipe the clip afterward.** See [Clipboard: passwords are handled deliberately](https://wmkeyboard.pages.dev/tools/clipboard/#passwords-are-handled-deliberately) for the full paste-time purge behavior and its setting. Typing / Suggestions / Password manager suggestions shows your system autofill service's saved-login chips inline in the suggestion strip. It isn't gated by `secureField`, since it's tied to a login field rather than a secure one, and your password manager draws the chips itself: the keyboard is only told their size and never sees the username or password inside them. It needs Android 11+ and a password manager set as your system autofill service, and it turns itself off automatically while incognito mode is on. The system's [smart replies](https://wmkeyboard.pages.dev/smart/autofill/#smart-replies) come down the same channel but have their own toggle, and aren't tied to field type at all. ## Typing in a terminal A terminal emulator like Termux isn't an ordinary text box. It reports its field with no type at all, and its connection to the keyboard carries only *finished* text: whatever the keyboard is still working on stays invisible to the terminal until something finishes it. That matters because plain English composing works by holding the word you're typing as an unfinished draft, so autocorrect and the suggestion strip have something to read, and only finishing it at the end of the word. In a terminal that draft went nowhere visible. Typing `echo` showed an empty prompt until the space after it, which then dumped the whole word in at once. So the keyboard doesn't compose in a field like that. Every key you press is finished text immediately, and the terminal shows it as you type. Glide typing still works, since a swiped word arrives finished in one piece. The rest of the prose rules stand down in a terminal for the same reason a password field doesn't get them: autocorrect, on-device learning, the double-space period and the space typed after punctuation are all rules about writing sentences, and a shell prompt isn't one. A space the keyboard adds after a comma is a different command. Phonetic (Avro) composing is the exception here as it is everywhere else, and it keeps the old behavior: Bangla typed at a terminal prompt still only appears once the word ends. Deleting works the other way round: a terminal never tells the keyboard what's on the command line, it only replies that the line is empty. Holding backspace used to stop on that reply after a single character, so a long command had to be erased one tap at a time. Backspace and forward delete now keep repeating in a terminal for as long as you hold them, and the terminal decides when there's nothing left. Two things that need to read the text can't work there, so they fall back rather than doing nothing: holding delete clears one character a tick instead of a whole word, and a sideways swipe on delete erases as it goes instead of selecting what it's about to take. Paste keys paste into a terminal as typed text, since the terminal ignores the paste command other apps follow. Copy keys can't work there: what you select in a terminal stays in the terminal's own view, out of the keyboard's reach, so the key says so and you copy with the app's own menu. The same field description, a field with no type, is also what a window with no text box at all reports, which is where a keyboard you've kept open spends most of its time. It's treated identically. ## When an app gets its own field wrong There's no manual override for field detection. No setting forces a field to be treated as a password or an email box. Detection reads only the field's real `inputType`, every time it's focused, and there's nothing else to configure. Two related things exist, but neither is a correction for a misreporting app: - **`FORCE_ASCII`**: if a field sets this flag and your current layout isn't Latin-script, the keyboard switches to your first enabled Latin layout for that field. That's the app declaring a hard constraint, not a workaround for bad detection. If none of your enabled layouts is Latin-script, the field stays on your current layout: the keyboard never brings back a layout you removed, English included. - **`hintLocales`**: an app can hint which language a field expects. It's honored only if you already have that language enabled, and it's advisory. It never overrides field-kind detection. [Keyboard modes](https://wmkeyboard.pages.dev/typing/modes/) look like an override at first glance, since you can manually pin the built-in **Passwords** mode over any field. But a mode only replaces settings: the theme, the toolbar tools, the symbol row, the emoji row, the layout, and the autocorrect, automatic-capitals and suggestions switches. It does not touch `secureField`, so it can't turn on learning-off or clipboard purging for a field an app has mislabeled. The shipped Passwords mode doesn't set the three typing switches either, so pinning it changes nothing about what the keyboard does to your text. If a login field is reported as plain text, pinning Passwords dresses the keyboard for it without restoring the actual privacy protections. Those come only from the field's real, correctly-reported type. ## Options | Setting | Default | Path | | --- | --- | --- | | Suggestions in every field | On | Typing / Suggestions / Suggestions in every field | | Contact emails in email fields too | On | Typing / Suggestions / Contact emails in email fields too | | Password manager suggestions | On (Android 11+) | Typing / Suggestions / Password manager suggestions | | Popup on number pads | Off | Key press / Key popup / Popup on number pads | | Forget after you paste a password | Off | Clipboard / Passwords and codes / Forget after you paste a password | | Learn from typing | On | Privacy / Learning on this device / Learn from typing | | Add words to the system dictionary | Off | Privacy / Learning on this device / Add words to the system dictionary | **Contact emails in email fields too** only shows up once **Suggest contact emails** itself is on. When it is, a contact-email completion still appears in an email field even if the app asked for a hidden strip there, while other suggestions in that field stay hidden. ## Details and edge cases - **A password field's `FieldKind` is `TEXT`, not a dedicated kind.** If you're reading behavior off the field kind alone, remember that password status is the separate `secureField` flag layered on top. A numeric PIN field carries both `FieldKind.NUMBER` and `secureField = true` at once. - **The typing-intelligence cutoff is broader than "passwords only."** Autocorrect and learning turn off for every field kind except plain, non-secure `TEXT`, which takes in email and URL fields as well as passwords and numeric pads. Two things reach one step further, into URL fields: glide typing, and word completion on the strip while **Suggestions in every field** is on. Neither autocorrects or learns there. Phonetic (Avro) composing is the one true exception. It keeps running in every field, secure or structured, since it exists to transliterate roman keys into Bangla rather than to feed suggestions. - **A field with no type at all is a third cutoff**, alongside secure fields and structured kinds. A terminal emulator and a window with no text box both report one, and neither can show an unfinished word, so nothing composes there and none of the prose rules run. See [Typing in a terminal](#typing-in-a-terminal). - **The suggestion strip is force-emptied a second time**, independent of the strip-hiding check above, as a backstop specifically for secure fields. - **The email/URL key swap has no toggle.** It's derived purely from field kind and layout mode (letters layer only), so there's nothing to turn off short of the field itself not being an email or URL field. - **A custom layout can override any of the five numeric keypads** by defining its own `NUMBER`/`PHONE`/`DATE`/`TIME`/`DATETIME` layer. Anything it doesn't define falls back to the shared ones described above. - **No build differences.** Field-kind detection, the key swaps, the five keypads and every secure-field behavior on this page work identically in every build. For symbol-page cycling and how a keyboard mode can bind itself to a field kind, see [Modes & the symbol row](https://wmkeyboard.pages.dev/typing/modes/). For the paste-time clipboard purge in full, see [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/). --- # Gestures URL: https://wmkeyboard.pages.dev/typing/gestures/ Section: Typing Summary: Spacebar cursor and numpad swipes, swipe-down-to-hide, the hint flick, backspace word-delete, and volume-key cursor control. Past tapping and long-pressing single keys, WM Keyboard has a few drag gestures that move across or along a key. The spacebar, backspace and the volume rocker each do something different when you swipe instead of tap. This page covers what those drags do once they're moving. Where a gesture belongs to another page, it links there rather than repeating it. ## Which swipe fires: quick vs hold Typing / Gestures The spacebar has two independent swipe slots: **Quick swipe on the spacebar** (default **Language**) and **Press and hold, then swipe** (default **Cursor**). Which one a drag resolves to depends on *when* it starts relative to the long-press delay, not on how far it travels. If a quick swipe sometimes counts as a hold, raise **Hold delay** on the same page: it gives the spacebar a delay of its own, from 50 to 1500 ms. The full timing engine, the language-switch behavior and the hold-drag picker list all live on [Switching languages](https://wmkeyboard.pages.dev/languages/switching/). This page covers the other two things a slot can be set to: **Cursor** and **Numpad**. space-swipe — A spacebar swipe resolves to whichever action its slot is set to. Each slot's options are **Nothing**, **Language**, **Cursor**, **Numpad** or **Keyboards**. Keyboards opens Android's own "Choose input method" list, for changing to a different keyboard app. On **Press and hold, then swipe** it needs no swipe: holding the spacebar past the hold delay opens the list. On the quick swipe it opens as soon as the drag starts. A tap with no movement always types a space, whatever either slot is set to. **Space swipe distance** sets how far your finger has to move sideways before a press on the spacebar counts as a swipe: 12 dp by default, from 6 to 60 dp. Raise it if your thumb slides a little on its way to a space and switches the language or moves the cursor. Setting **Press and hold, then swipe** to Cursor or Numpad costs one thing: holding the spacebar no longer opens the [hold-drag language picker](https://wmkeyboard.pages.dev/languages/switching/#holding-to-drag-through-the-list). A hold can only mean one thing, and that slot is where you said what it means. The picker is still on the 🌐 key's long press and on a quick swipe set to Language. ## Moving the cursor with a spacebar swipe typing/spacebar-cursor-swipe — Dragging the spacebar moves the text cursor a character at a time. Set a slot to **Cursor** and the drag moves the text cursor instead of typing. The caret steps one character for every 16 dp of horizontal travel, in whichever direction you drag. That distance is a slider of its own, **Cursor speed**, which sits under the two swipe pickers and runs 4–32 dp. Shorter moves the caret faster. Any composing text in progress (a word mid-transliteration, say) commits first, without autocorrect, so the cursor lands where you can see it rather than jumping past a word that was still being corrected. Typing / Gestures / Speed up long cursor swipes **Speed up long cursor swipes** (off by default) makes a long drag cover more text. The first 48 dp of travel move at your Cursor speed, so a short nudge of a few characters stays exact. After that, each character costs less travel, and by 240 dp the cursor is moving at **Top cursor speed**, 4× by default (2× to 16×). It keeps that pace for as long as you keep dragging the same way, the way a mouse's middle-button scroll does. Turn back and the cursor drops to Cursor speed again and builds up from there, so fixing an overshoot is as exact as a short nudge. A small wobble the other way doesn't count as turning back. With the 2-D cursor touchpad on, up and down movement speeds up the same way, on its own, one line at a time. **Move the cursor directly** (off by default) changes what a swipe sends. Normally each step is a left or right arrow key, which is what a hardware keyboard would send and what every editor understands. A few fields read an arrow as something else: a search box with a dropdown steps through its suggestions, and some file managers put the cursor back at the start of a name being renamed. With this on, the swipe places the cursor by moving the selection instead, and the app sees no key at all. It needs the app to have told the keyboard where the cursor is; when one hasn't, the arrow keys are used as before. With the 2-D cursor touchpad on, the same goes for up and down in a one-line field like a search box or a file name: up puts the cursor at the start of the text and down at the end, where an up or down arrow would have stepped into the box's suggestions or out of the field. In a field with several lines, up and down stay arrow keys so the cursor follows the app's own line wrapping. Selection mode and a held shift stretch the selection the same direct way, so a search box sees no Shift+arrow either. **Keep moving while held** (off by default) is for the line that outruns the spacebar. The cursor keeps going by itself, about eight characters a second, in two cases: at once when your finger is past either end of the spacebar, and after a pause of about half a second when you hold your finger still at least a fifth of the spacebar's width away from where the swipe began. Move your finger again, or lift it, and it stops. A short swipe, or settling the finger near where you started, never sets it off. **Whole keyboard as touchpad** (off by default) hands the drag the whole key area once a swipe has turned into a cursor drag, the way SwiftKey's does. The keys dim, and a second finger landing on them types nothing until you lift. Your finger could always wander off the spacebar mid-drag; what changes is that the edges Keep moving while held uses are the sides of the keyboard instead of the ends of the spacebar, and its hold-still reach is a fifth of the keyboard's width. When the drag also moves up and down (the 2-D cursor touchpad, or Swipe up for the touchpad), the keyboard's top and bottom edges keep the cursor moving between lines the same way. Typing / Gestures / 2-D cursor touchpad **2-D cursor touchpad** (off by default) turns the same drag into a touchpad. A vertical component now steps the cursor up and down through the text too, on the same Cursor speed distance. Both axes send real arrow-key events rather than computing a raw text offset, so vertical movement follows whatever line-wrapping the app is doing instead of guessing at it. Move the cursor directly changes that in one-line fields, where up and down go to the start and end of the text. Only movement that is mostly up or down counts toward a line step: the small wobble of a sideways drag drains away instead of adding up into a jump to another line. The toggle only appears once one of the two swipe slots above is set to Cursor, or Swipe up for the touchpad is on, and Cursor speed appears beside it. Typing / Gestures / Swipe up for the touchpad **Swipe up for the touchpad** (off by default) opens the 2-D touchpad from a swipe up, with no hold first. Swipe up from the spacebar, keep your finger down, and the cursor follows it left, right, up and down until you lift. The finger has to travel 80 dp up, and more up than sideways, before the touchpad opens, so a tap that rolls a little still types a space and there is room left below to move the cursor down. You feel a tick when it opens. For the length of your long-press delay after that, the cursor stays put, so the swipe that opened the touchpad doesn't move it. After that, the rest of the upward swipe still doesn't move the cursor a line up: pause for a moment or move down first, and from then on up and down move the cursor too. Sideways swipes are untouched: they still run the two slots above, so the default quick swipe keeps switching the language. This works whatever the slots are set to and whether the 2-D cursor touchpad switch is on, and every cursor setting on this page applies to the drag. With **Keep moving while held** and **Whole keyboard as touchpad** both on, the touchpad scrolls by itself at every edge of the keyboard: the sides keep the cursor moving along the line, and the top and bottom 24 dp keep it moving up or down, about five lines a second, until you move back or lift. The band the drag started in doesn't count, so starting on a spacebar near the bottom edge never sets it off. Typing / Gestures / Swipe up for the number pad **Swipe up for the number pad** (off by default) opens the number pad from a swipe up the spacebar, at any speed, the way SwiftKey does. Like the touchpad it waits for a short way up, so a tap that slides still types a space, and sideways swipes keep their own actions. With both swipe-up switches on, the touchpad wins. Typing / Gestures / Cursor magnifier **Cursor magnifier** (on by default) shows the line of text around the cursor, enlarged, in a bubble over the text while the drag moves the cursor. It comes up once the drag has resolved to Cursor and goes when you lift your finger. With **Press and hold, then swipe** set to Cursor it comes up sooner, the moment the hold is long enough, with a short vibration, so you know the next swipe moves the cursor before you start it. With the magnifier off, the vibration alone says so. The bubble is the keyboard's own drawing of the text the app hands it, not a zoomed picture of the screen, so it uses the keyboard's colours and font. It sits above the cursor, or below it when the cursor is too close to the top of the screen, for apps that tell the keyboard where their cursor is, which most do. For an app that doesn't, or when the cursor is scrolled out of sight or hidden behind the keyboard, it sits just above the keyboard instead. In a password field it shows dots, never the password. It never takes a touch and it goes the moment you lift your finger. It appears beside Cursor speed, and the [trackpad](https://wmkeyboard.pages.dev/tools/trackpad/) has a separate switch for the same bubble. The same drag selects text instead of moving through it while selection mode is on, on either axis. Turn the mode on from the toolbar's [Selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) tool, or hold that tool down and scrub the spacebar with your other thumb. A Shift you pressed yourself does it too, lock included, exactly as [Shift+Arrow](https://wmkeyboard.pages.dev/tools/text-editing/#hold-shift-to-select) would: lock Shift, then scrub. A Shift the keyboard armed by itself at a sentence start doesn't count, so an ordinary drag never selects behind your back. ## Swiping in from the edge to go back Typing / Gestures / Swipe in from the edge to go back Android's own back gesture, a swipe in from the side of the screen, works over the keyboard on most phones. Some phones keep it off the keyboard, so it has to start above it, which is half a screen up. With this setting on, a swipe that starts within the outer 16 dp of the keyboard's left or right edge and moves at least 40 dp inward, more across than up or down, does what Back does: it closes the open panel (the voice panel with its recording, a search, the emoji panel), or the keyboard when nothing is open. It acts when you lift your finger, and the key it started on types nothing. It's off by default. A glide that starts right at the edge and heads inward looks exactly the same, so with this on, start glides on the edge keys a little way in. A second finger on the keys while the swipe starts means typing, and the swipe is left alone. ## Swiping down to hide the keyboard Typing / Gestures / Swipe down to hide the keyboard A straight downward drag on the spacebar dismisses the keyboard, once it clears a short threshold and is steeper than it is wide. It's off by default, so a stray vertical wobble mid-drag never closes the keyboard on you. Turning it on changes nothing about holding the spacebar: with both swipe slots set to Nothing, a hold still repeats the space and never opens the language picker. The 2-D cursor touchpad, when it's on, claims the vertical axis ahead of this only once a drag has actually resolved to the Cursor action. Hold time decides that resolution, same as any other spacebar swipe. With the defaults (quick swipe = Language, press and hold then swipe = Cursor), a downward flick fast enough to clear the hide threshold before the long-press delay elapses still belongs to the quick-swipe slot, so it dismisses the keyboard. Only a drag held past that delay hands the vertical axis to the touchpad. There's a second, separate **Swipe down to hide** toggle for the toolbar strip, under Appearance → Toolbar. Two words shorter in the settings list, and a different element it acts on: a downward swipe on the toolbar, not on the spacebar. The two are independent switches, and both ship off. ## Swiping down on a key for its hint Typing / Gestures / Swipe down to type the hint Every key with press-and-hold characters shows the first of them in its corner: the 1 on q, the accents on a vowel, the currency signs on $. With this on, a short, quick swipe straight down on the key types that corner character outright, with no hold to wait out. It's the same character the popup would put first, on any key that has one, on every layer. Off by default. The swipe has to be all five things at once: down (within about 30° of straight down), short (between half a key's height and two of them), straight, quick (under a quarter of a second from touch to lift), and, with glide typing on, not lifted on another letter. A swipe that ends in the middle of another letter key is a word like "ok", so glide typing gets it; the narrow seams between keys don't count, so a flick that overshoots onto the row below still types the hint. A drag that misses any of those is whatever it always was, so a slow pull down is still a glide and a tap that drifts is still a tap. The keys with a drag of their own keep it: the spacebar's swipes, backspace's delete swipe, the modifier and ?123 drags, and a [flick key's](#flick-keys) arms. The one real cost is with [glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/). A very fast glide of a short word that lifts on the seam between two keys rather than on the second letter can still read as a flick and type the hint instead. Ending on the letter, the way a glide usually does, keeps the word. On a layout whose rows line up without the usual half-key stagger, the key straight below is a whole key, so a flick there has to stop short of the next row. ## Swiping up on a letter for its capital Typing / Gestures / Swipe up for a capital The same flick the other way. With this on, a short, quick swipe straight up on a letter types its capital, so a single capital in the middle of a word or a name needs no trip to Shift. With [two-thumb typing](https://wmkeyboard.pages.dev/typing/glide-typing/#typing-with-both-thumbs) on, the capital joins the word your thumbs are building instead of standing alone. On a key that has a shifted character of its own, such as a number-row key with a symbol above it, the swipe types that character instead. Keys whose Shift changes nothing (a digit with no shifted twin, a script without capitals) don't take the swipe. It doesn't touch Shift either: a Shift you armed is still armed for the next letter. Off by default. The rules are the ones the hint flick uses, pointed up: within about 30° of straight up, between half a key's height and two of them, straight, and under a quarter of a second. When [Words on the keys](https://wmkeyboard.pages.dev/typing/words-on-the-keys/) shows a word on the key, a swipe up still takes that word, as it always has. The glide rule is the mirror image too: with glide typing on, a swipe that lifts in the middle of another letter, "de" from d to e for instance, is the word, and only one that ends between keys can type a capital instead. With glide typing off there is no word to make, so a flick up off a middle-row letter types its capital even when it ends on the key above. ## Flick keys Typing / Gestures / Show flick arms on keys Some keys carry more than one character. A tap types the one in the middle; a short swipe from the key in one of eight directions types the one at that edge or corner instead. The [Japanese Flick](https://wmkeyboard.pages.dev/languages/cjk/#japanese-input) pad has always worked this way, with four arms, and the shipped **English flick (3×3)** layout takes it further in the MessagEase manner: all twenty-six letters on nine big keys, with `o` in the middle carrying q, u, p, c, b, g, d and j around it. Enable it under Languages like any other layout. Bangla has the same board twice, typing through Avro or Khipro: **Avro flick (3×3)** and **Khipro flick (3×3)**. Any key of any layout can be given arms of its own in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key), and an arm can run an action, such as moving the cursor, as readily as it types. Each key draws its arms small, in the hint colour, at the edge or corner you swipe towards, so you can see what a swipe will type before you make it. **Show flick arms on keys** turns that drawing off, which leaves the face to the centre character the way the kana pad looked before. A layer can differ: in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), **Show flick arms here** on a layer with flick keys (or on the Numpad's panel editor) shows or hides them for that layer alone, so the number pad can keep its arms while the kana pad goes without. Once your finger moves towards an arm, or rests on the key for a quarter of a second, a **Flick popup** over the key shows the centre and every arm in a 3×3 cross, with the one your finger is heading for lit. A quick tap types the centre without it popping up; turn it off if the arms on the face are enough and you'd rather nothing covered the keys around it. The arm lights up with a short buzz the moment it is chosen. **Flick distance** is how far the finger has to travel from where it landed before the press reads as a flick rather than a tap, 22 dp by default. Past that, the swipe's direction picks the nearest arm the key actually has, so a key with only four edge arms still has the generous quarter-circle each of them always had, a key with all eight gets a 45° sector for each, and a swipe towards an edge or corner that carries nothing types the centre character, as a drifting tap always did. Two more things a flick key does, both borrowed from FlickBoard and Thumb-Key. Swipe out to a letter and back again to where you started, and the swipe types that letter's capital: `q` from a swipe up-left on `o`, `Q` from the same swipe brought more than halfway back. That only applies to an arm that has a capital of its own (or a **with Shift** form set in the editor); on a kana arm, an action arm or an arm whose shift changes nothing, coming back to the centre cancels to a plain tap, exactly as before. And a held flick key still opens its press-and-hold alternates, since the hold only starts once the finger has stayed inside the flick distance. A flick key can also be all swipes, with nothing in the middle: turn on **Swipes only** for it in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key), or import an Unexpected Keyboard layout that has keys like that. A tap on a swipe-only key types nothing. **Tap a swipe-only key to see its swipes** makes that tap bring up the key's flick popup for a moment instead, so you can check what each swipe types; it works even with **Flick popup** off. It's off by default. With glide typing on, a letter with flick arms shares each swipe with the glide. That matters most on a layout with characters on every corner, such as one brought over from Unexpected Keyboard. Nothing is taken from the key while the swipe could still be a flick. It becomes a glided word once it can't be one: it turns, it reaches the middle of another letter in a direction no arm points, or it travels past **Flick reach**, 141% of the key's diagonal by default. A swipe that lifts before any of that types its arm, even when it overshot into the next key, which a corner flick on a phone-sized key nearly always does. So "er" from an `e` whose arms are all on its corners glides as soon as your finger reaches `r`, "the" glides once it turns at `h`, and a flick up-right off `d` types its é even if it ends inside `r`. **A flick onto a letter glides a word** settles the one case that is left: a short straight swipe from a letter onto its neighbour, along the way an arm points. Off, the default, it types the arm, so you tap those two-letter words. On, lifting inside the neighbour types the word, and a flick has to stop short of the next key. Until the keyboard has decided, a glide that starts on one of these keys can show the flick popup and buzz for the arm for a moment. ### Flicks off the edges and off special keys Flicks belong to keys, not to the edges of the keyboard. A swipe down off the bottom row, up off the top row or outward off the first or last key in a row is that key's arm in that direction, so it does something only where you have given the key an arm in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key). Nothing is reserved for an edge on its own. The special keys take arms too: Enter, Shift, ?123, ABC, the globe, the emoji key, the space bar and the rest, keys set to repeat while held included. An arm on one of them types text or runs an action, and the tap is still the key's own. Enter with `?` on its flick up types one `?`, without sending and without a new line. ⌫ takes arms and keeps its swipe and its repeat: forward delete on its flick up is the usual one, and it repeats while your finger stays out on the arm. A direction with an arm belongs to the arm, the delete swipe keeps the others, and a press held still until the repeat starts is a hold, not a flick. The arrow keys work the same way: word left and right on the side flicks of ← and →, page up and down on ↑ and ↓, and a still hold still moves the cursor over and over. That goes for the keys of the arrow keys row too, and for any key you set to repeat while held. The space bar takes arms on the same terms: put [No space](https://wmkeyboard.pages.dev/typing/glide-typing/#the-space-after-a-glided-word) on its flick up and it sits under your thumb, while the directions without an arm keep the spacebar's swipes. With an arm on one side, a swipe the other way still moves the cursor or switches language, and the hold is unchanged. A down arm takes the place of **Swipe down to hide**, and an up arm the place of **Swipe up for the touchpad**. A swipe waits for the flick distance before it starts, so it never runs off with a flick. Changing a key's action in the layout editor clears its text arms, so a letter turned into Shift doesn't keep typing the letter's arms. Flicks off the edges stay a key's own. Where two gestures could claim the same swipe, the rule is fixed rather than guessed, and the layout editor says so under the flick pad of a key where an arm takes something over: - **An arm on Shift, Ctrl, Alt or Meta replaces [dragging from that key](#dragging-from-a-modifier-key).** On a key with arms the swipe is the arm; a modifier key without arms keeps the drag. - **An arm on ?123 or ABC replaces the drag that peeks at the other layer.** Holding ?123 still and tapping a key, the [symbol chord](#chords-with-123), is a hold and not a swipe, so it works either way. So do the other still holds: ?123 held for the number pad and the globe held for the language picker. - **A key with text arms gives up [Swipe down to type the hint](#swiping-down-on-a-key-for-its-hint).** Its own down arm, if it has one, is what a swipe down types. - **A capital stays out-and-back.** On a top-row letter with an up arm, a flick up types the arm, and swiping out to it and back again types the arm's capital, the same as any other arm. - **Glide typing never starts on a special key**, so arms there cannot cost you a word. On a letter, the arm and the glide share the swipe by the reach rules above. ## Swiping to add 's Typing / Gestures / Swipe to add 's The apostrophe is rarely a key of its own, so a possessive usually costs a long press and a tap. Pick a key here, comma, full stop or a real apostrophe key if the layout has one, and a short straight swipe from that key to s adds `'s` to the word before the cursor: "developer" becomes "developer's". Off by default. The same swipe to another letter adds a contraction instead: | Swipe to | Adds | Example | |---|---|---| | s | `'s` | what → what's | | t | `'t` | don → don't | | d | `'d` | she → she'd | | m | `'m` | I → I'm | | l | `'ll` | we → we'll | | r | `'re` | they → they're | | v | `'ve` | you → you've | `'t` only goes on a word ending in n, so "what" never becomes "what't". A word that already has an apostrophe isn't extended again, and a word in capitals gets its suffix in capitals: "WHAT'S". While you swipe, a line runs from the key to your finger, the same line a [modifier drag](#dragging-from-a-modifier-key) draws, so you can see the swipe is being read. It's a gesture in its own right, not a glide-typing feature. It works after a tapped word, after a glided one, and on the word you're still typing, with glide typing on or off. A space after the word moves behind the `'s`, so you get "developer's " and never "developer 's". The chosen key still types its own character when you press it. Holding it still opens its long-press popup as usual, but once your finger starts to move the swipe owns the stroke, so a slow swipe no longer opens the popup and types `!`. A stroke that isn't the swipe types the key if you lift back on it, and nothing if you lift anywhere else. A short flick down still types the hint when [Swipe down to type the hint](#swiping-down-on-a-key-for-its-hint) is on. The shape is what separates it from a glide: short and straight, its path no more than 1.6× the straight line between the two keys. A real word glided from the same key to one of those letters wanders further than that, so it decodes as a word. The spacebar can't start this swipe, since a stroke off the spacebar is always a spacebar swipe. It only extends English words: any word on an English layout, and on another language's layout with English among its secondary languages (Banglish on Avro, for one), a word spelled in Latin letters. A word still being typed on Avro counts when its preview shows it in English. Glide typing and handwriting never start from the chosen key, so the swipe works even on a layout that puts that key right beside a letter. ## Opening the numpad with a swipe Set a slot to **Numpad** and the [numeric keypad panel](https://wmkeyboard.pages.dev/tools/text-editing/#the-numeric-keypad) opens the moment your drag crosses the initial touch slop. There's no further distance to cover. The swipe triggers the panel and then goes inert until you release, and the release types nothing. It's the same panel that ?123's long-press and the toolbar's Numpad tool open. See [Text editing](https://wmkeyboard.pages.dev/tools/text-editing/) for what's on it. ## Your own keys on the spacebar long press Typing / Gestures / Spacebar long press keys Type the characters you want into this field, with a space between each one, and holding the spacebar opens them in the same popup a letter key's accents use. Emoji, punctuation, a signature character, anything you can type into the box. Leave the field blank (the default) and nothing changes. The field is deliberately blunt about what it costs: keys you put here take the spacebar's long press over completely. The [hold-drag language picker](https://wmkeyboard.pages.dev/languages/switching/#holding-to-drag-through-the-list) no longer opens, and neither does the space repeat. Both of those are still reachable elsewhere. The 🌐 key opens the picker on a hold and cycles on a tap, a spacebar swipe still switches language, and a second tap still types a second space. Clear the field to get the hold back. Swipes are untouched. The popup only opens on a hold that stays still, so a quick swipe, a hold-then-swipe and the swipe down to hide all behave exactly as they did. Once the spacebar carries keys, it draws the first one small in its top right corner, the same corner hint every other key uses. That hint is the one thing on the keyboard that says the long press changed, so leave **Press and hold hints** on if you want it. It lives on the [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/) screen and ships on. A layout can carry its own set instead. Open a layout in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), tap the spacebar, and the alternates fields are there like on any other key. The setting comes first in the popup, and the layout's own keys follow it. ## Deleting more than a character with backspace Key press / Delete keys typing/backspace-word-swipe — Dragging backspace to the left deletes text a word at a time instead of a character at a time. **Swipe to delete** (on by default) lets a leftward drag on ⌫ take out more than the one character a press does. Backspace treats tap, hold-to-repeat and this swipe as one gesture. A press that stays still starts the normal hold-to-repeat once **Time before a key repeats** has elapsed, exactly as described on [Keys & typing basics](https://wmkeyboard.pages.dev/typing/basics/#multi-touch-and-key-repeat). Moving left past a small slop cancels that repeat and switches to the swipe. Backspace owns the whole stroke: glide typing, handwriting and the floating-word flick never start from it, even though the key sits right beside the bottom letter row, so neither a swipe nor a held, repeating backspace that drifts onto the letters types a word on top of what it deletes. That holds with **Swipe to delete** off too, and the same goes for ⌦. **Swipe deletes** picks what one step of that swipe takes off: - **Words** (the default) clears a sentence in one pull. - **Characters** walks back a letter at a time, which is the finer tool when the fix is inside a word rather than at the end of it. A whole multi-part emoji, or a whole conjunct in a script that asks for it, still counts as one character, the same as a backspace press does. Each step costs a certain amount of leftward travel. In Words mode the cost shrinks as you go, so clearing a whole sentence is one long pull rather than a series of flicks. The first word costs the most: **Word delete distance**, 72 dp by default and adjustable 32–120 dp in the same Delete keys group. Every word after it costs less, on a fixed curve derived from that one number, down to a floor past which extra words get no cheaper. In Characters mode there is no curve at all: every character costs the same **Character delete distance**, 20 dp by default and adjustable 8–48 dp, so the count stays easy to follow. ### Seeing what the swipe will delete **Show what you will delete** (on by default) makes the swipe select the text instead of deleting it as the finger passes over it. The highlight grows a word or a character at a time, so you can see exactly how far the gesture has got, and the delete happens when you lift your finger. While the highlight is up the swipe is still undoable: dragging back to the right shrinks it a step at a time, and dragging all the way back to where the swipe started leaves the text alone entirely. The gesture retraces its own steps, so a word that cost a short pull on the shrinking curve gives itself back for the same short pull. Turn the setting off to get the older behaviour, where each word or character goes the moment the swipe passes it. Deleted text cannot be given back, so dragging right only re-anchors the gesture, and the next word costs the full first-word distance again instead of picking up where the shrinking curve left off. A few editors cannot hold a selection while the keyboard drives it, and a search box drawn by the keyboard itself (emoji search, for one) has no cursor to select with. The swipe falls back to deleting as it goes in both cases, with no setting to change. It does the same over a word that is still being converted, such as pinyin or Vietnamese Telex, so what is left of the word is still read as one. ### Swiping up to clear everything **Swipe up to clear all** (off by default) empties the whole text box from a swipe straight up on ⌫. Once your finger has gone as far up as the first word of a sideways swipe costs (the **Word delete distance**, 72 dp by default), every bit of text in the box is selected, and lifting deletes it. Bring your finger back down below halfway before you lift and the selection goes away with nothing deleted. The text is always selected first, whatever **Show what you will delete** is set to, because a whole message is a lot to lose to a stray flick. The swipe has to be steeper than it is wide, so a leftward word swipe that drifts upward stays a word swipe. It works in the keyboard's own text boxes too. A text box that shows the keyboard only part of a long text can't be selected whole, and there the swipe does nothing rather than leave the rest behind. A backspace key you gave a flick up in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) keeps its flick. ### Swiping up or down to delete lines **Swipe up or down to delete lines** (off by default) turns the two vertical swipes on ⌫ into line deletes. A swipe up deletes back to the start of the line the cursor is on. With the cursor already at the start of a line, it deletes the whole line above, so each swipe up takes one more line. A swipe down deletes to the end of the line, then the line below. Keep your finger out after the swipe and it goes on a line at a time, the way holding delete repeats. With **Swipe up to clear all** on as well, the swipe up still clears everything and only the swipe down deletes lines. A delete key your layout already gave an up or down swipe keeps its own. The same two steps are **Delete line** and **Delete line forward** in the layout editor's actions, for any key or swipe. ### Holding delete to clear words Key press / Timing **Holding delete clears words** (off by default) changes what the hold does, not what the swipe does. With it on, a delete key that stays still under your finger starts taking one whole word per repeat once **Time before a key repeats** has elapsed, instead of one letter. A single press is untouched and still deletes one character, so nothing you do quickly changes. Word clearing gets its own speed, **Word delete interval** (140 ms by default, adjustable 60–500 ms), which only appears while the setting is on. It is deliberately slower than **Delete repeat interval**: at the letter speed of 50 ms a held key would empty a paragraph before you could lift your finger. The words come off exactly as a swipe step takes them, so the whitespace next to the cursor goes with the word beyond it, and a run of punctuation counts as a word of its own. Holding still stops on its own once there is nothing left to delete. ## Deleting forward with ⌦ Key press / Delete keys ⌦ is not on any built-in typing layout. Add one to a row, to a key's press-and-hold alternates, or to the [text-editing pad](https://wmkeyboard.pages.dev/tools/text-editing/) in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), and it gets the same three gestures backspace has, each pointed the other way: a press deletes the character *after* the cursor, a hold repeats, and **Swipe on forward delete** (on by default) lets a rightward drag take whole words or characters ahead of the cursor. The finger travels the way the deletion travels, which is the only difference in the gesture. Everything that shapes it is shared with backspace rather than doubled: **Swipe deletes**, **Show what you will delete**, **Word delete distance** and **Character delete distance** all govern both keys, so tuning the pull on one tunes it on the other. **Holding delete clears words** covers ⌦ too, clearing the words after the cursor. Where you put a delete key does not change what it is. The text-editing pad's own ⌫ and ⌦ are these keys, not a weaker copy of them: the swipe, the preview, the word-clearing hold and the delete repeat interval all apply there exactly as they do on the letters row. One step deletes any trailing whitespace plus the run of same-kind characters behind the cursor, where letters and digits count as one kind and punctuation as another. That's what stops a step from stalling on a run like `...` or a string of dashes. It also means a word glued to punctuation (a contraction, a trailing comma) can take more than one step to clear, since the letters and the punctuation are different kinds and each gets its own step. A few things a word-delete step does specially, rather than falling back to plain character deletion: - If there's an active text selection, one step clears the whole selection instead of doing word math. - If a word is still mid-composition (a transliteration buffer, for a phonetic layout), one step clears the entire composing word in one go. - Inside a panel's own search box (emoji, clipboard or dictionary search, a plugin's text field, the typing-test box) or mid-stroke on a handwriting panel, word deletion steps down to one character at a time. Word-deleting the field hidden behind the overlay would be invisible and wrong, so it doesn't try. See [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/) for the **Vibrate on delete swipe** haptic that ticks on each word this gesture removes. ## Dragging from a modifier key Drag from Shift onto a letter and lift, and you get the capital: one movement instead of a shift tap and then the letter. A band stretches from the key you started on to your fingertip while you drag, drawn in the [glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) trail's colour and width. It stays a straight line rather than tracing the path your finger took, because the chord is the two keys at its ends and the route between them means nothing. While you drag from Shift the letters turn to capitals, and the key under your finger lights up the way a pressed key does, so you can see which capital the lift will type. The same light follows your finger from Ctrl, Alt and Meta, on any key that has a combination to send. Ctrl, Alt and Meta keys work the same way, and send the combination to the app: Ctrl to C copies, Ctrl to Z undoes, Ctrl to Enter sends the message in the apps that bind it. Shift does that too on any key that isn't a letter, so Shift to Enter is a real Shift+Enter and Shift to Tab tabs backwards. A capital dragged out of Shift is typed, not sent as a key press, so the word still gets its suggestions, its autocorrect and its spot in the learned words. Shift's own tap, double-tap for caps lock, and press-and-hold all still work exactly as they did. Every layout has a Shift key. None of the shipped ones have a Ctrl, Alt or Meta key, so add one from the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key), or import a layout that already carries a modifier row. A few rules keep the drag from firing when you didn't mean it: - Lift outside the key grid and nothing happens. That's the slide-away cancel every key has. - Wander off the modifier and back onto it, and it acts like a plain tap: Shift arms, Ctrl arms, and a second tap locks either. - Keys that only change what the keyboard shows have no combination to send, so landing on ?123, the emoji key or the globe does nothing. Nor does Shift onto Ctrl, or either onto itself. (Dragging *from* ?123 is a gesture of its own, below.) - A letter your device's keyboard map can't name (Bengali letters, currency signs, combining marks) sends nothing under Ctrl rather than typing itself. Shift is unaffected by this, since it types the character rather than sending a key press. ## Dragging from ?123 or ABC Drag off ?123 and the symbols come up under your finger. Lift on one and it types, and the letters are back before you've moved your hand. It's the quick way to drop in a single symbol or a piece of punctuation without two trips through the layer key, and it's the gesture the stock Android keyboards have had for years. ABC works the same way in the other direction. Drag off it and the letters show for as long as you hold, so a layer of your own can type one letter without leaving. Starting from the ?123 key of the first symbols page reaches the second one, where the arrows and the maths signs live, exactly as a tap on it would. The drag works from a panel too. Put ?123 or ABC on the text editing pad, the Numpad or any other [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts), drag off it, and that layer shows over the panel for as long as you hold. Lift and the panel is back, with the emoji or clipboard list still where you left it. A panel isn't one of the keyboard's layers, so ABC on a panel always shows the letters. While you drag, the key under your finger lights up the way a pressed key does, and a straight band stretches from the key you started on to your fingertip, the same one the [modifier drag](#dragging-from-a-modifier-key) draws. Hold still over a key and its press-and-hold popup opens. Slide onto the character you want, lift, and that's what you get. This popup always follows your finger, whatever **Choose without lifting** is set to, because lifting is what ends the gesture: a popup waiting for a separate tap could never be answered here. Letters arrive lowercase, since the Shift key is back on the layer you came from. **Shifted letter on press and hold** (Key press → Press and hold shortcuts, off by default) puts each letter's capital at the end of its popup, which is how you get one out of this gesture. Nothing about the keyboard's own layer changes while you hold. A tap on ?123 still switches layer and stays there, and a press that never travels is a tap. A few rules keep the drag from firing when you didn't mean it: - Lift outside the key grid and nothing happens, the same slide-away cancel every key has. - Lift on a key that only moves the keyboard around, such as the ABC key now sitting where you started, Shift, the emoji key or the globe, and nothing is typed. Sliding back to where you began is how you cancel. - An ABC key on the letters layer has no other layer to show, so a drag off it does nothing at all. - If you held the key long enough to open its own popup first (a ?123 key with alternates of its own), that popup keeps your finger and the layer stays where it is. ## Swiping with several fingers Typing / Gestures / Multi-finger swipes Put two, three or four fingers on the keys and swipe them the same way together, and the keyboard runs an action instead of typing. Each finger count has its own four directions, so there are twelve swipes to bind. Out of the box only two-finger swipes do something: | Swipe | Does | | --- | --- | | Two fingers up | Selects all the text | | Two fingers left | Undo | | Two fingers right | Redo | | Two fingers down | Paste | Tap any of the twelve rows to change it. The list is the same one the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key) offers for a key, so a swipe can do anything a key can: type a piece of text, open a tool or a panel, run a text-editing step, switch to a layer or another keyboard app, send Tab or an arrow, or fire a broadcast for an automation app. **No action** empties a slot, default or not, and **Reset swipe actions** at the end of the list puts all twelve back. A line follows each finger while the swipe is on, in the [glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) trail's colour, width and length, and fades once you lift. The swipes also work on the keys under a tool that types into a box of its own, such as [Translate](https://wmkeyboard.pages.dev/tools/translate/), a search box or a note. There select all, copy, cut, paste and undo act on that box's text, the way its own selection bar does, and not on the app behind the keyboard. Redo has nothing to undo back in those boxes, so it does nothing there. Fast typing is never mistaken for a swipe. Nothing is decided until every finger on the keys has travelled about 28 dp, all of them the same way. A typist rolling from one key to the next lifts long before that, and the keys type as they always did. Lifting any finger before the swipe is recognised hands the whole touch back to the keys. ## Chords with ?123 Typing / Gestures / Chords with ?123 Hold ?123 still with one finger and tap a key with another, and the keyboard runs the action you gave that key instead of typing it. Let go of ?123 afterwards and you're still on the letters. Out of the box there's one chord: ?123 and L switches to your next language. Hold ?123 a moment and every key with a chord shows it in a small label at the top of its face, so you can see what's bound before you tap. **Add a chord** asks for the key, as the one character it types, then offers the same list of actions as a [multi-finger swipe](#swiping-with-several-fingers): a piece of text, a tool or panel, a text-editing step, a layer, another keyboard app, Tab or an arrow, or a broadcast for an automation app. Tap a chord's row to change it, or pick **No action** to remove it. **Reset chords** puts the list back to the default. A few things keep chords out of your way: - A key with no chord types as it always did, even with ?123 held. - The chord needs ?123 to be down first and held for a moment. Tapping ?123 and rolling straight onto the next key is too quick to count, so the layer switch works as before. - [Dragging off ?123](#dragging-from-123-or-abc) still shows the symbols. Only a still ?123 is a chord. - Once a second finger lands, ?123's own press and hold (its popup, or the number pad) stays shut for that press. Held on its own, it opens exactly as before. A ?123 with nothing to open on a hold now switches layer when you let go, rather than partway through the hold. - Chords are off during a [typing test](https://wmkeyboard.pages.dev/tools/utilities/#testing-your-typing-speed). Chords suit [two-thumb typing](https://wmkeyboard.pages.dev/typing/glide-typing/#typing-with-both-thumbs) in particular. There, flick characters on a letter stop a swipe starting on it, and a hold on a letter fires when a thumb rests there while the other types. A key held still is never part of a swipe, so one thumb on ?123 and a tap from the other is a shortcut that never collides with typing. ## Swipes on keys Typing / Gestures / Swipes on keys [Flick keys](#flick-keys) are built into a layout, one layout at a time, in the layout editor. Swipes on keys are the same short swipes set up once here and laid over whichever layout is on screen. Put `1` up and to the right of Q and it is there on QWERTY, AZERTY, Colemak and every other layout with a Q, the way Unexpected Keyboard keeps its numbers on the top row's corners. **Add a key** asks for the key, as the one character it types, then shows its eight directions around it. Tap a direction to pick what a short swipe that way does: a piece of text, or any action a [multi-finger swipe](#swiping-with-several-fingers) can run, such as copy, undo, a tool or an arrow. **No action** clears it. Each key with swipes gets a row of its own that lists them, and tapping the row brings the directions back. **Add a preset** merges a ready-made set into yours: | Preset | Puts | |---|---| | Numbers | 1 to 0 up and to the right on the top row | | Symbols | `! @ # $ % ^ & * ( )` up and to the left on the top row | | Editing shortcuts | Select all, copy, paste, cut, undo and redo down and to the left on A C V X Z Y | A preset replaces only the directions it fills, so your other swipes on those keys stay. **Remove all key swipes** takes every one off, and turning **Swipes on keys** off keeps them stored but off the keys. Once a key has swipes it behaves like any other flick key. Its swipes are drawn small on its face (unless **Show flick arms on keys** is off), the flick popup and **Flick distance** apply, and glide typing shares the stroke by the same rules: a swipe that turns, reaches the middle of another letter where no swipe points, or passes **Flick reach** glides a word. Three things to know: - The layout comes first. If a layout already has a swipe in a direction, that swipe stays and yours for the same direction is ignored there, so the English 3×3 board and the Japanese flick pad are not rewritten by a preset meant for QWERTY. - Only the letters page gets them. The symbols pages keep their keys as they are. - A key with swipes gives up [Swipe down to type the hint](#swiping-down-on-a-key-for-its-hint) and the slide to select, since the flick owns that key's stroke. A key whose layout makes its hold repeat or run an action is left without swipes, so it keeps that hold. ## Volume keys as a cursor control Typing / Volume keys **Volume keys move the cursor** (off by default) repurposes the hardware volume rocker while the keyboard is on screen: volume down moves the cursor one character left, volume up moves it one character right. Holding a volume key repeats for free, riding Android's own hardware key-repeat rather than a separate timer. Both the press and the release are swallowed, so the app underneath never sees a stray volume keypress. It applies only while the keyboard is actually shown. Once it closes, the volume keys go back to controlling volume. With [Selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) on, or with a Shift you pressed yourself still up, each press selects a character instead of stepping past it, so the rocker drags a selection out. typing/volume-cursor-control — With Volume cursor control on, the hardware volume keys move the text cursor instead of the volume. **Release the keys while audio plays** (on by default) hands the volume keys back to ordinary volume control the instant something is playing: music, a video, a podcast. Typing a reply never costs you the ability to turn the sound down. The keys return to the cursor the moment playback stops. This is re-checked on every key press rather than latched once, so starting or stopping playback mid-typing takes effect immediately. Turn it off if you'd rather the volume keys always moved the cursor while the keyboard is open, playback or not. ## Gestures that live on other pages A few more drags belong to features big enough to have their own page, so they're only linked here: - **Language switching** (the spacebar's Language swipe, the hold-drag picker list, and the globe key's tap-vs-long-press) is fully covered on [Switching languages](https://wmkeyboard.pages.dev/languages/switching/). - **Swiping across the letters** to write a whole word, or to draw handwriting instead, is one setting with two outcomes. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) for the default word-swipe mode and [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/#handwriting-without-opening-the-panel) for drawing ink over the keys. A printable table of every gesture on one page lives at [Gesture cheat sheet](https://wmkeyboard.pages.dev/reference/gestures/). ## Options Typing / Gestures | Setting | Default | | --- | --- | | Quick swipe on the spacebar | Language | | Press and hold, then swipe | Cursor | | Space swipe distance | 12 dp (6–60 dp) | | 2-D cursor touchpad | Off | | Cursor speed | 16 dp (4–32 dp) | | Speed up long cursor swipes | Off | | Move the cursor directly | Off | | Keep moving while held | Off | | Whole keyboard as touchpad | Off | | Swipe up for the touchpad | Off | | Swipe up for the number pad | Off | | Top cursor speed | 4× (2–16×) | | Cursor magnifier | On | | Swipe in from the edge to go back | Off | | Swipe down to hide the keyboard | Off | | Swipe down to type the hint | Off | | Swipe up for a capital | Off | | Show flick arms on keys | On | | Flick popup | On | | Tap a swipe-only key to see its swipes | Off | | Flick distance | 22 dp (8–48 dp) | | Flick reach | 141% (80–200%) | | A flick onto a letter glides a word | Off | | Swipe to add 's | Off | | Arrows on the spacebar | On | | The spacebar shows | Language | | Spacebar text | Blank (live language name) | | Typing speed on the spacebar | Off | | Swipe to delete (backspace) | On | | Swipe deletes | Words | | Show what you will delete | On | | Word delete distance | 72 dp (32–120 dp) | | Character delete distance | 20 dp (8–48 dp) | | Swipe on forward delete | On | | Holding delete clears words | Off | | Word delete interval | 140 ms (60–500 ms) | | Volume keys move the cursor | Off | | Release the keys while audio plays | On | | Multi-finger swipes | On | | Two fingers up / left / right / down | Select all / Undo / Redo / Paste | | Three and four fingers, any direction | Nothing | | Chords with ?123 | On | | ?123 + L | Next language | | Swipes on keys | On, with no keys set | Open the **Typing** tile in settings, or search "Gestures", "Delete keys" or "Volume keys". The last two rows live under Typing / Key press rather than Delete keys, and Word delete interval only appears while Holding delete clears words is on. Cursor speed and Cursor magnifier only appear while a swipe slot is set to Cursor. Swipe deletes, Show what you will delete and the two step distances only appear while Swipe to delete is on, and the two distances are one setting each: Words mode shows Word delete distance, Characters mode shows Character delete distance. The Gestures group's language-switching settings (Arrows on the spacebar, The spacebar shows, Spacebar text, Typing speed on the spacebar) are covered on [Switching languages](https://wmkeyboard.pages.dev/languages/switching/#swiping-the-spacebar) instead of here. For the field-by-field breakdown see [Typing settings](https://wmkeyboard.pages.dev/reference/settings/typing/). ## Details & edge cases - **Numpad is a discrete action, not a distance-based one.** Cursor keeps stepping for as long as you keep dragging. A swipe assigned to Numpad only needs to clear the initial slop once: the panel opens immediately and the rest of the drag does nothing. - **A flick key keeps its stroke until the stroke can't be a flick.** Handwriting, the floating-word flick and the hint and capital flicks leave a key with flick arms alone, whichever way the finger goes. Glide typing watches instead of taking over at the edge of the key: a swipe that turns, reaches another letter where no arm points, or passes the flick reach is a glided word, and anything shorter and straighter is the arm, overshoot included. This holds with two-thumb typing too. A key whose arm moves the cursor or deletes keeps the whole stroke, since a held arm has already done its work by the time the finger wanders off. On the English 3×3 board glide typing never starts at all, which is by design: the letters are on the arms, not under a glide path. - **A hint flick is judged when the finger lifts, never while it's moving.** Nothing is claimed early, so a downward stroke that turns out to be longer, slower or bendier than a flick is decoded as the glide or read as the tap it always was. The one stroke it does take from glide typing is a fast, straight, short one that lifts off any other letter, so a short glided word that ends on its last letter never qualifies. - **The 's swipe is judged at the lift too, and never claims a stroke a glide could want.** Glide typing doesn't start strokes on punctuation keys, so a stroke off the comma or full stop is never a word in the first place, whether glide typing is on or off. Only a swipe that actually changed the field keeps the lift; one with nothing behind the cursor to extend lands as the key's own tap. - **Two settings hide the keyboard on a downward swipe, two words apart.** "Swipe down to hide the keyboard" lives here under Typing → Gestures and applies to the spacebar. "Swipe down to hide" under Appearance → Toolbar applies to the toolbar strip. They don't affect each other, and both are off out of the box. - **The 2-D cursor touchpad only takes precedence over swipe-down-to-hide once a drag has resolved to Cursor.** Hold time decides which slot governs a drag. With the defaults (quick swipe = Language, press and hold then swipe = Cursor), a fast downward flick inside the long-press delay belongs to the quick-swipe slot and dismisses the keyboard instead of moving the cursor down through lines. - **A held delete key and a swiped one can disagree about the unit, and that's on purpose.** Holding delete clears words is a Key press setting about the repeat; Swipe deletes is a Delete keys setting about the drag. One can be Words while the other is Characters. - **Swiping the wrong way on a delete key cancels the repeat and does nothing else.** Either key claims a drag past the slop in either direction, so the hold stops as soon as your finger travels. Only travel in the key's own direction takes text: leftward on ⌫, rightward on ⌦. - **Backspace's word-delete acceleration resets on a reversal, not on release.** Dragging right mid-swipe doesn't bring back a deleted word. It only means the *next* leftward word costs full travel again instead of the shrunk amount. - **A multi-finger swipe takes the whole touch once it is recognised.** A word you had started gliding with the first finger is dropped, a key press under any of the fingers types nothing, and a chord or layer drag fires nothing at the lift. The swipe is counted by the most fingers that were down at once, so a third finger that lands a moment late still makes it a three-finger swipe. - **Not inside a typing test, and not over a panel without typing keys.** The test counts every keystroke in order, so it keeps the keys to itself; a panel such as emoji or the numpad has no letters to swipe on. - **Two-finger swipes stand aside while [two-thumb typing](https://wmkeyboard.pages.dev/typing/glide-typing/) is on.** There two thumbs moving together are two halves of a word, so only three and four finger swipes run. The two-finger rows say so. - **A swipe that leans more than about 35° off straight up, down, left or right is no swipe at all**, and neither is one where the fingers head different ways, such as a pinch. Those fall through to the keys. - **Volume-key cursor control re-checks "is audio playing" on every key press**, not once at the start of the drag, so control passes back and forth live as playback starts and stops. - **A drag off ?123 or ABC is never read as a word either**, for the same reason as a modifier drag: ?123 sits against the bottom row beside the spacebar, and a touch that starts there belongs to the layer drag. - **A drag off a modifier or Shift key is never read as a word.** Glide typing and handwriting both measure where your finger landed, and a touch that started on one of those keys belongs to the chord drag. Shift is the one you may notice: it sits against the bottom letter row, so a glide that used to be startable from its inner edge now capitalizes instead. Start the word one key further in. - **A stroke from the spacebar is never read as a word either.** Glide typing, handwriting and the floating-word flick all leave it alone, so a language or cursor swipe is always that swipe. Glide typing used to decide by distance to the nearest letter, measured in key widths, and on a phone turned sideways the keys are twice as wide as a row is tall, so the whole spacebar counted as "near a letter" and a language swipe glided a word instead. - **Caps lock is not a drag source.** It's a state you leave switched on, so holding it for one key means nothing, and Shift is one key away for anyone who wants that. - **Ctrl to A, C, V or X goes through the same route as the toolbar's copy and paste.** Those four reach text fields that a raw key event doesn't, such as web views and Compose fields. **Ctrl shortcuts as raw key events**, under Typing / Key press, opts back out of that for terminal apps. Ctrl+V sends the real key anyway when the clipboard holds a picture or anything else that isn't text, so a web page with its own paste handler can take it. Whether the page accepts it is up to the page. - **Word deletion falls back to character-by-character inside overlay search boxes** (emoji, clipboard or dictionary search, a plugin text field, the typing-test box, or an in-progress handwriting stroke), since word-deleting the real field hidden behind the overlay would be invisible and wrong. See [Keys & typing basics](https://wmkeyboard.pages.dev/typing/basics/) for tap and long-press behavior on single keys, [Switching languages](https://wmkeyboard.pages.dev/languages/switching/) for the spacebar's Language swipe and the hold-drag picker, [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) for swiping across the letters, and [Text editing](https://wmkeyboard.pages.dev/tools/text-editing/) for the numpad panel and the other cursor tools. --- # Glide typing URL: https://wmkeyboard.pages.dev/typing/glide-typing/ Section: Typing Summary: Swipe across the keys to write whole words, with live preview and alternates. Glide typing lets you write a word by sliding one finger from letter to letter without lifting it. WM Keyboard watches the shape of the stroke rather than which keys it happens to cross, so a loose, fast swipe still lands the right word. glide — Slide from letter to letter; lift to commit the word. ## Turning it on Typing / Gestures / Glide typing Glide typing is on by default, and it isn't tied to one language. What it needs is a layout that types the letters you press rather than converting them, plus a word list covering enough of that layout's alphabet. English gets it on every layout registered under English, QWERTY or otherwise: AZERTY, Dvorak, Colemak, Workman and Halmak all glide, because the word list comes from the language and every one of those grids can spell it. Other languages glide as soon as their word list is installed and covers the keys. French, German, Spanish and the rest ship no built-in list, so until you download one under [Dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) a swipe on them has nothing to decode against and lands as taps. The keyboard says so rather than leaving you guessing: the first time you type in such a language with glide typing on, a chip on the suggestion strip reads "Glide typing needs a French word list". Tap it to open the language's page, where the list downloads, or ✕ to dismiss it. It shows once per language, for good rather than once each time the keyboard opens, and goes away as soon as you type. Only languages that can actually gain a list from a download are ever asked. English and Bangla carry theirs inside the app, so they never raise the chip, and neither does a language you have set to [your own imported lists alone](https://wmkeyboard.pages.dev/languages/dictionaries/#options), because a download would not be read while that is on. If glide typing does nothing on English or Bangla, the word list is switched off rather than missing: see [Dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/). A swipe only turns into a glide when several things line up at once: - You're on the letters layer. Glide typing doesn't apply to the symbols or number pages. - The swipe has somewhere to land. With no panel open that's your field. With a panel open it's that panel's own text box, if it has one: emoji and clipboard search, the dictionary lookup, a media panel's search bar (GIF, sticker, web, image, Wikipedia, Translate, App launcher, Vocabulary, QR), the AI prompt, Find and replace, the Learn-from-text speller, a plugin's text field, and the word card's spelling bar all take a swiped word at their own caret. See [Panels and captured keys](https://wmkeyboard.pages.dev/tools/overview/#panels-and-captured-keys). The keypad boxes (the calculator's expression and the converters' amount) take none, since a word list means nothing on a keypad. - The layout types letters rather than converting them. Hangul, Vietnamese and the CJK input methods all turn keystrokes into something other than the keys pressed, so a stroke over them spells a reading, not a word. The phonetic layouts are the exception: Avro for Bangla and [Hindi phonetic](https://wmkeyboard.pages.dev/languages/hindi/). They convert too, but the grid is Latin and the output is another script, so a stroke is decoded against a romanized vocabulary rather than the native one: the language's known-spellings map, joined by its downloaded romanized word list (Banglish, Hinglish) if you have one. Without that spelling map there's nothing to read the stroke against, and the layout quietly stops gliding rather than guessing. Khipro is the other exception: its strokes are read against the Khipro spelling of every word in your Bangla lists, which the keyboard works out on the phone the first time and keeps. Khipro Dark glides on those same Classic spellings, so a stroke never has to cross the `/` key to join consonants. The fixed Bengali layouts, Probhat and Jatiya, shape clusters instead of converting, so a stroke over them is decoded against the Bengali word list on the ordinary path. Full [Pinyin](https://wmkeyboard.pages.dev/languages/cjk/#swiping) and Japanese [Kana JIS](https://wmkeyboard.pages.dev/languages/cjk/#swiping-on-kana-jis) glide too, in their own way: a stroke spells a reading, which joins the text being composed for the candidate strip to convert. - The active word list covers enough of the layout's letters. Edit a layout in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and drop letters the language needs, and glide typing quietly turns itself off for that layout rather than guessing at a key that isn't there. It's worth checking first if swiping stops working after an edit. A finished dictionary download re-asks the same question, so a language can start gliding a minute after you add it. Only the language's own list is asked: a language you've added under **Also suggest from** doesn't count against the layout, so English with Polish mixed in still glides on QWERTY, and Polish words that need letters the grid lacks just aren't offered from a swipe. The same goes for lists you import under [Custom dictionaries](https://wmkeyboard.pages.dev/reference/settings/dictionaries/#custom-dictionaries). While the language has a bundled or downloaded list, that list alone answers, so an English list kept under Arabic can't switch Arabic glide off, and a few imported Latin words can't switch it on for a grid that can't spell Arabic either. Imported words the grid can draw are swiped like any others. Your imports only get the say when they're all the language has: no list downloaded, or the language set to read its imported lists alone. - You're not in the middle of a [typing-test tool](https://wmkeyboard.pages.dev/tools/overview/) run, unless the test's own glide switch is on. - The field allows it at all. Secure fields (password boxes) and structured keypads (email, number, phone, date/time) skip glide typing the same way they skip autocorrect: there's no dictionary word to guess on a PIN pad. A URL bar is the exception: it gets a swipe like a text field does, since a browser's address bar is typed as often as it's searched, even though autocorrect and learning still skip it. The word list behind a glide is whatever dictionary is active for the language you're typing. That's the bundled one, or a bigger downloaded list if you've installed one under [Dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/), since a larger downloaded list always takes over from the bundled copy. Words you've typed and had WM Keyboard learn are folded in too, and weighted heavily, so a word you've typed even once becomes an easy swipe target next time. A letter-area swipe can do something other than glide a word: outside the F-Droid build google you can set it to **handwrite** instead, drawing letters over the keys for on-device recognition. That's a separate feature with its own page: see [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/#handwriting-without-opening-the-panel). Glide typing itself, the "Type words" mode, ships in every build. ## The trail While you swipe, a comet-style trail follows your finger: thick and opaque at the head, thinning and fading toward the tail, so the stroke dissolves behind you instead of leaving the whole path drawn on screen. It takes its color from your [theme](https://wmkeyboard.pages.dev/themes/overview/)'s accent color by default, or from a color the theme sets specifically for it. Lifting your finger doesn't clear it instantly. The trail stays where it is and finishes fading on its own. Three sliders under the same Gestures group tune it (trail width, how long it lingers, and its opacity). See [Options](#options) for their ranges and defaults. Power Saving mode drops the trail's opacity to zero to save battery while leaving glide typing itself working: see [Details & edge cases](#details--edge-cases). ## Live preview while you swipe You don't have to wait until you lift your finger to see what WM Keyboard thinks you're writing. Partway through a swipe, the suggestion strip already fills in with the current best guesses, and it updates as you keep moving. Those guesses are decoded against the same word list used for the final commit, so the word you see mid-swipe is never a preview of something different from what lands when you lift. Watching the candidates reshuffle under your finger isn't for everyone. **One word on the strip** (Typing / Gestures, off by default) holds the strip to a single word for as long as the stroke is down: the same word the floating preview shows, and the same word a lift commits. The emoji, punctuation and rewrite chips left over from the word before the stroke stand down with the extra candidates, so the row carries one thing. Lift your finger and the alternates fill the strip as they always have, and the [ambiguity picker](#when-a-swipe-is-a-genuine-toss-up) still offers them mid-stroke if you hold still. If you also use [words on the keys](https://wmkeyboard.pages.dev/typing/words-on-the-keys/), that feature has its own answer for what a stroke does to the keys, under **While gliding**. It ships set to **Nothing**, so the keys stay bare for as long as your finger is down and the words come back when you lift. The other two choices put the decoder's alternatives on the keys so you can steer into one, or put the *next* word on the keys while you're still drawing this one, ready to flick the moment the swipe ends. See [During a glide](https://wmkeyboard.pages.dev/typing/words-on-the-keys/#during-a-glide). ## Finishing a long word early A swipe is normally read from end to end: the first letter goes where you started and the last where you stopped, so "dictionary" can't be offered until you have glided all ten letters. **Finish long words early** (Typing / Gestures, off by default) lets the keyboard offer a word your stroke has only started, the way tapping four letters of it would. The guess shows in the floating preview and on the strip like any other reading, and lifting your finger types it. It's colored rather than only bolded when the keyboard is sure of it, under **Color the word a lift will type**. Two rules keep it from running ahead of you: - **A word you have drawn in full keeps its place.** Gliding "thing" all the way to the g reads "thing", not "things", and "that" stays "that" even when "that's" is a word you use a lot. A longer word only takes over when it's clearly the likelier one, letter for letter, over what you have actually spelled. "When sure" asks for a wide lead; "Eager" offers the longer word as soon as it's ahead at all. - **Under "Prefer my words" and "Only my words", a guess is always one of your own words.** The dictionary still reads a stroke you finish, but it never finishes one for you, so a rare dictionary word can't jump in ahead of a word you have written a hundred times. Guessing the rest of a word is about twice as accurate out of one person's vocabulary as out of a dictionary, which is why the two settings belong together. Up to 0.5.11 the first rule stopped applying under "Prefer my words": a guess at one of your own words pushed the whole dictionary reading aside before the lead was measured, so "exam" came back "example" and "When sure" behaved exactly like "Eager". Your own words are still preferred, but only a word your stroke really spells can set that comparison now. Lifting early on a guessed word is a choice, much like tapping a suggestion, so the strip treats it as one. Instead of the stroke's other readings (words that end where your finger stopped, which is nowhere useful), it fills with the word's neighbours: the plural, the possessive, the verb form, the shorter word the guess ran past. Lift on "dictionary" and "dictionary's" and "dictionaries" sit beside it; lift on "functionality" and "functional" is one tap away. Not available on phonetic layouts such as Avro and Hindi phonetic, where a stroke spells a romanization rather than the word itself. A word you finish early teaches [your swipe style](#what-a-swipe-teaches) only what you actually drew. Lift on "dictionary" after "d-i-c-t" and the keyboard learns where your finger landed on those four keys, not on the six it never reached. No shape is kept for the word either: the shapes are of whole words, and yours was only the start of one. The word itself is still learned like any other. ## Alternates, and fixing a wrong guess The moment you lift your finger, the best-scoring word commits to the field. The decoder hands back as many candidates as the suggestion strip has slots for, and the strip fills them from that list. The word that just committed is bolded in the center slot by default, following the same "best suggestion in the middle" layout that autocorrect and normal typing use, with its next-best rival to the left and a third option to the right. Tap any of them to swap it in for the word that just landed. Widen the strip (Typing / Suggestions, **Number of suggestions**, three by default and up to six) and every extra slot gets a candidate too. From the third slot on, the strip also keeps up to two words one key off the word that went in, the same slips a typed word keeps: glide "is" and "us" is offered too. A swipe is read from the key where your finger went down to the key where it lifted, so a start or finish one key over is the miss the decoder ranks lowest, and on a confident stroke its own readings used to fill every slot first. These words are only ever offered. A lift never types one, and they stay out of the [ambiguity picker](#when-a-swipe-is-a-genuine-toss-up)'s decision of whether a stroke is a close call. They aren't added under **Only my words**, after a word finished early, or on phonetic layouts. A swipe down or up on the strip right after a glide now pages through the stroke's own readings; up to 0.6.3 it showed the openers or next words left over from before the swipe. Tap-to-replace only works while your cursor is still sitting exactly where the glide left it. Move the caret away, or type something else first, and the alternates for that word retire. The same guard governs a plain backspace: press it right after a glide and it deletes the whole swiped word in one go rather than a character at a time, since a wrong swipe shouldn't cost a letter-by-letter cleanup. That takes the trailing space with it, so one press puts the cursor back where the swipe found it. If your swipes tend to come out one or two letters off, turn off **Backspace removes a glided word** (see [Options](#options)) and backspace after a glide deletes one character at a time, the same as after a word typed on the keys. ## When a swipe is a genuine toss-up Some strokes have no single right answer. On a fixed Bangla layout ক and খ share a key, so a word and its aspirated twin are drawn in exactly the same shape, and on any layout a hurried stroke can fit two words equally well. Rather than commit the likelier one and leave you to notice, **Ask when a glide is unclear** (on by default) offers the choice while your finger is still down. When a stroke's best candidate barely leads its runner-up, keep your finger down and hold it still. After **Time to hold still** (350 ms by default) the closest words appear just above your fingertip, in their own window, so a stroke that ends on the top row floats them over the suggestion strip rather than hiding them under the keys. The keyboard's own first choice wears a thin outline, so you can see what a lift anywhere else will type. Slide onto a word and lift to type it. Lift without choosing and the keyboard commits its best guess exactly as it always did, and the same words are on the suggestion strip either way, so ignoring the picker costs nothing. The moment the words appear, the stroke is finished. Sliding to a word does not redraw it, so the word you land on is the word that lands in the field. Change your mind about the whole swipe and slide down, more than a key below where you stopped: the words fade, a small "Lift to cancel" note appears, and lifting there types nothing at all. Slide back up and the words come back. Four settings under the toggle tune it: - **When to ask** decides how close a call a stroke has to be. *Near ties* asks only when the two words fit almost equally well. *Close calls*, the default, asks when the best word barely beats the next one. *Any doubt* asks whenever another word comes close. *Every pause* asks each time you hold still, whatever the keyboard thinks. - **Time to hold still** is the pause before the words appear, from 150 ms to 1 s. Small movements do not count, so a finger resting on the glass still counts as still. - **Hold longer to always ask** (on by default) lets a stroke the keyboard is sure of ask anyway: keep holding for twice the time and the words appear. Turn it off and a confident stroke never asks, however long you hold. - **Words to choose from** sets how many words appear, from two to five. Up to three share a row above your finger; four or five add a second row above the first. The picker appears only in the "Type words" swipe mode, since a handwritten stroke resolves through a different path. It works on a chained stroke too: pause on the last word of a swipe that crossed the spacebar and the choice is for that word. A word that is a snippet trigger or a personal dictionary shortcut shows what it expands to, like `omw = on my way`, since that is what lifting on it types. ## The space after a glided word Lifting your finger commits the word *and* a space, so you can glide the next word or tap it out without the two running together. The space belongs to the keyboard rather than to you, which is what makes the next three things work: - **Punctuation takes it back.** Type `.` `,` `?` `!` `;` `:` straight after a glide, or a closing `)` `]` `}` `"`, and the space steps aside, so you get "hello." and not "hello .". Opening brackets keep it, because "hello (world)" is what you meant. - **A space press is spent on it.** Reaching for the spacebar out of habit doesn't leave a double gap. The press confirms the space that's already there. Press space *again* and you get the usual double-space full stop, exactly as if you'd typed the word out. - **Backspace still undoes the whole thing.** The word and its space come out together. A word glided back into the middle of a sentence, where a space already follows, doesn't get a second one, except in web pages, where the space is always typed ([why](https://wmkeyboard.pages.dev/smart/autocorrect/#a-space-that-is-already-there)). A word swiped over a highlighted one is the exception to that exception: nothing on the page is a suggestion while a word is highlighted, so the space already after it is kept and no second one is added. The space is skipped in structured fields too: passwords, email addresses, web addresses and number pads, the same set that turns off every other typing courtesy. Glide a word in front of a mark that closes up onto it, such as a full stop, a comma, a question mark or a closing bracket or quote, and the space waits. Put the cursor after "done" in "Is it done?" and glide `yet`, and you get "Is it done yet?", not "Is it done yet ?". The first letter you type there brings the space with it, and a mark, a space or a cursor move drops it. A browser's address bar spaces a glided word when what you've typed reads as a search: a line that already has a space, or plain letters with no dot, slash or digit in them. Type `weather` and glide `today` and you get "weather today". After `example.` or `github.com/` the word still joins on, as an address needs. For one word at a time, put **No space** somewhere you can reach it: a key, a flick or a long-press alternate in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), a key of the [text editing panel](https://wmkeyboard.pages.dev/tools/text-editing/), a [multi-finger swipe](https://wmkeyboard.pages.dev/typing/gestures/) or a `?123` chord. Like Nintype's spaceless key, it takes back the space the keyboard just put after a glided or picked word, and the next word you glide joins straight on: glide `foot`, **No space**, glide `ball`, and you have "football". Tapped while a word is still being typed, two-thumb words included, it leaves the word open and the key lit: you can keep typing or swiping it, the strip still works, and when the keyboard ends it, with the all-thumbs-up pause or a pick from the strip, no space follows and autocorrect stays out of it. Press Space yourself and you get the space anyway. Tap again to call it off. A double tap ends the word on the spot, exactly as written. Right after a word and a space you typed yourself, it takes that space back too: type `foo`, Space, **No space**, glide `ball`, and you have "fooball". A space after a punctuation mark stays, and a word ending in a vowel point or other combining mark counts as a word. With nothing to take back, a tap still joins the next glide: after `foot`, **No space**, glide `ball`, and you have "football". Moving the cursor, Space, Enter or a backspace calls the joining off. **No space** also works like Shift, for the one key after it. Double-tap it, or hold it, and the key lights up: the next key you type gets no space from the keyboard, before or after it. That is the `.` in `github.com` typed in an ordinary text field, where **Space after punctuation** would otherwise make it `github. com`. A space already in front of that key stays, so `best` then the armed `.` gives `best .tld` rather than `best. tld`. Holding the key only arms it: the word you are typing stays open and its suggestions stay on the strip. A word picked from the strip counts as the next key, so with the key lit, picking `specific` leaves no space after it and you can type on to `specifically`. Type the `.`, and the key goes dark again, so the next full stop is spaced as usual. Space, Enter, a new field or another tap of the key call it off, and a glided word counts as the next key, joining on with no space in front. On a flick, the arm does both: flick to **No space** and let go for the tap, or flick and hold your finger out on the arm until the key lights to arm it, so one flick up on the space bar carries the two. Flicking it twice quickly arms it as well. The arm on its own is also an action, **No space after the next key**, offered only for a gesture, where there is no hold; a key or flick arm picks **No space** and holds it. A key set to the arm-only action by an older version keeps working. Turn this off with **Space after a glided word** (see [Options](#options)) and a glided word commits bare. Words chained in one stroke are still spaced from each other either way. The setting only governs the space after the last one. ## Gliding a whole phrase You don't have to lift your finger between words. Start a glide, cross the spacebar without pausing, and keep gliding straight into the next word. Each crossing ends one word and starts the next, and the whole phrase lands together when you lift. A crossing gives a small haptic tick if haptics are on, and the floating preview shows the last few words of the phrase so far. An earlier word in it can change as you draw the ones after it. If you have the context model, it has its say on the lift, so a word can change once more there. A crossing has to reach the middle of the spacebar. Dip past its midline and come back up into the next word. A stroke that only clips the top of the bar on its way to a bottom-row letter like n or m stays part of the word it was drawing, so a long word that hugs the bottom row isn't cut in two. Once you have crossed, the finger is on its way across until it climbs back to the middle of the bottom row: sliding off the end of the bar, running past its lower edge, or jittering along its top doesn't start a word or cross again. The words either side of a crossing are read knowing where they end. A word drawn alone ends on its last letter, and the decoder leans on that. A word that runs on into the spacebar ends at the bar's edge instead, after a straight walk from its last letter, and the next word begins with the walk back up. The walk can be diagonal: the bar spans only the middle of the board, so a word ending on p heads for its near end. Those walks are read as walks, not as letters: the decoder may put the last letter anywhere on the way down and the first anywhere on the way up, so "I want to by a car" no longer comes out with "by" read off the key the finger passed on its way to the bar, and "the" followed by a walk no longer vanishes because no word ended there. This holds in the keyboard's own text boxes and the typing test too, and when another swipe engine is reading the stroke, the walks are cut off for it. Measured on 30,999 real swipes joined into the sentences they were drawn for, with walks that curve toward the bar's real extent, a word in a chain is read right 78% of the time, against 85% drawn alone. A word in a chain teaches the keyboard the word, but not where your finger goes for its letters: its ends are the bar, not the keys. The stroke is read as a phrase, so each word is judged by the words after it as well as those before. "by a car" loses to "buy a car" once you have drawn "a car", and the first word takes the two already in the field into account. The evidence is what you have typed yourself, then the [word-pair list](https://wmkeyboard.pages.dev/smart/suggestions/) for your language if you have downloaded it, then the context model if you have that too. Measured on sentences the strokes were not fitted on, with the English word-pair list, words read right go from 77% to 83%, two-word phrases from 65% to 73%, and four-word phrases from 41% to 53%. With the context model as well they reach 85%, 76% and 58%. Reading the same list one word at a time, as a chain used to, gets 81%, 71% and 50%. With neither download, only the words you have typed and a small built-in list of pairs can change a reading. A word of one letter works too. Glide up to a or i from the bar and straight back down: a stroke that is nothing but its way there and its way back is read as that letter, where the language's word list has it. On real strokes it reads right 80% of the time before the words around it have their say, and about one longer word in a thousand reads as a single letter by mistake. Only the final word of a multi-word glide gets alternates in the suggestion strip. Backspace takes the words back whole, last first. This behavior has its own toggle, **Glide across spacebar**: turn it off and a swipe that crosses the spacebar is read as a single word instead (see [Options](#options)). ## Typing with both thumbs **Two-thumb typing** (Typing / Gestures, off by default) lets both thumbs work on the same word at once, the way Nintype did. Each thumb can tap or swipe, at the same time as the other, and every tap and swipe adds to one word instead of each swipe being a word of its own. Say you want "something". Tap s with your left thumb, swipe from o to m with your right, tap e with your left, then swipe t h i n g with your right. The thumbs can overlap: the next one can start before the last one has lifted. The keyboard keeps each thumb's letters in the order that thumb made them, works out how the two thumbs' letters fit together from when each one happened, and picks the word from the dictionary that fits best. A tap that lands while the other thumb is still swiping goes into the word at that point of the swipe. Swipe over h e l o and tap l with the other thumb just after the swipe passes the l, and you get "hello". One tap can also stand for a doubled letter, so k e p tapped can give "keep". A quick tap that rolls a little before you lift still types its key, as it does with one finger. **Delay after typing** works per thumb: right after a thumb taps, that thumb has to travel further before its stroke counts as a swipe, but the other thumb's taps never make it harder to start one. The keyboard tells the thumbs apart by the half of the board each one lands on. A thumb can wait on a key while the other one types. If it stays put and the other thumb taps or swipes meanwhile, it's resting: holding it opens no popup and runs no hold action, and lifting it after 0.4 seconds or more types nothing. A quick overlap, one thumb landing just before the other lifts, still types both keys, and a long press while the other thumb is off the screen opens the popup as it always does. A resting thumb can still start a swipe from where it is. With **All thumbs up**, the pause before the word ends starts once only resting thumbs are left on the keys, so you don't have to lift them. A third finger that lands while one thumb is swiping is ignored. A letter you gave flick arms in the layout editor keeps them. A tap types the letter, and a stroke counts as a flick for as long as it still could be one; once it travels past the flick reach or heads onto another letter where no arm points, it is a swipe, the same as with one finger. Keys whose arms repeat, such as a caret arm, keep the whole stroke for themselves. [Swipe down to type the hint](https://wmkeyboard.pages.dev/typing/gestures/#swiping-down-on-a-key-for-its-hint) and [Swipe up for a capital](https://wmkeyboard.pages.dev/typing/gestures/#swiping-up-on-a-letter-for-its-capital) work for each thumb, by the same rules as with one finger. A short flick down on a letter types its corner hint and adds nothing to the word. A short flick up adds that letter as a capital to the word you're building, as a tap under a held Shift would, so you can start "Hello" with a flick and carry on with both thumbs. The word is read again after every tap and swipe, so the word in the text field and the alternates on the suggestion strip change as you go. A word you are still building often isn't a word yet: the s and o-m of "something" spell nothing on their own. Then the field shows the start of a word they fit, here "som", or the letters under your gestures if nothing in the dictionary starts that way. Nothing you did is dropped. The next tap or swipe reads the whole word again, and tapping space types whatever the field shows, after reading the last gesture if it was still being read. A space, a mark or Enter pressed while that reading is still running waits for it, along with anything you type after it, so the word, then its space, then your next letters land in that order. Up to 0.6.1 the keyboard read the whole word again on the spot instead, which could hold up the key press for a long word. Backspace takes back your last tap or swipe, not the last letter, and the word is read again without it. When no swipe is left, what remains is the letters you tapped, as typed. That holds just after the word is typed too: swipe `car` and `pet`, let "carpet " go in, and the first Backspace takes out "pet" and the space, leaving "car" open for the next swipe. A word of a single swipe still comes out whole, with its space. And when a letter or swipe takes back the space **All thumbs up** typed, the word opens again as the taps and swipes it was, so a Backspace after that takes back a swipe rather than a letter. **Finish a two-thumb word** decides what types the word: - **Space** (the default). The word stays open while you tap and swipe. Tap space or punctuation to type it. Ordinary one-thumb glides work the same way in this mode: a swiped word waits for the space instead of getting one automatically. - **All thumbs up.** The word is typed, with its space, once no thumb has touched the keyboard for a short pause, like ordinary glide typing with a moment's grace. A thumb that lands before the pause is over carries on the same word, so your thumbs can take turns on "movement" or put the second e of "heed" on the other thumb without having to overlap. Words you only tap end the same way: tap T, lift, tap o, and "To" is typed with its space once you stop, corrected the way a space would correct it. A word you have just backspaced into waits for your next letter instead. A finger on the suggestion strip or the toolbar counts as a thumb on the keys, so you can reach up for another reading without the first one being typed under your finger: the word stays open until you lift, the reading you tap replaces it, and a touch you drag off without picking starts the pause again. Up to 0.6.1 only the keys held the pause. Four sliders and a switch, shown with All thumbs up, tune that pause: - **Pause after a swiped word** is the pause for a word with a swipe in it, from 100 to 1500 ms, 350 ms by default. Raise it if words end before you have finished them, and lower it if you find yourself waiting for the space. - **Pause after a tapped word** is the same pause for a word you only tapped. Tapping leaves longer gaps inside a word than swiping does, and stopping to think halfway through a tapped word is normal, so you may want more time here. It starts at the swiped-word pause. - **Pause after a one-letter word** is the least a single letter waits, whichever pause it would otherwise get. A lone letter is far more often the start of a word than a word. It starts at the shorter of the two pauses above, so it changes nothing until you raise it. - **Take the space back** handles a space that came too early. A letter you tap, or a swipe you start, within this time after the space takes it back and carries the word on, as long as the two still lead to a word in the dictionary. Type car, let the space land, then type pet, and you get "carpet". Type car, let the space land, then type dog, and it stays "car dog". If the letters stop leading anywhere partway, the space goes back where it was: car, then d, o, g reads "card" for a moment and splits back into "car dog". A letter typed after this time always starts a new word, and so does a capital. The time is counted to when your thumb lands, so a swipe you began in time still joins. It starts at twice the swiped-word pause, 700 ms by default, and goes up to 3 seconds. At the left end it is off. Twice the pause comes from the measurements published with [Kinetica](https://github.com/EZ-eta/kinetica), another two-thumb keyboard: about one change of word in eight came that soon after a space, and the dictionary check kept 70% of those apart. A full stop or comma after the space still takes the space and sits against the word ("car."), as before. - **Show the pause on the strip** (on by default) draws a thin line along the bottom of the suggestion strip while your thumbs are up. It shrinks towards the middle over the pause, and the word is typed with its space when it is gone. A thumb that lands before then takes the line away and carries on the word. With reduced motion on, the line stays whole until the pause is over. Up to 0.6.1 there was one pause for every word, nothing took the space back, and nothing showed the wait. These settings start where that one pause was, so the timing only changes once you move a slider. The tapped-word, one-letter and take-back rows follow the swiped-word pause until you move them; **Reset** on one of them makes it follow again. When the next word's swipe joins a word because it began before the space, "car" and "pet" coming out "carpet", the strip offers the two apart: **car pet**, beside the joined word but never in its place, so Space still types "carpet". Tap it and "car" goes in with its space, and "pet" is the start of the next word, open for more taps and swipes. The chip stays on offer after the all-thumbs-up pause has typed the joined word, and picking it then takes that word and what it taught back out first. Undo the last swipe with Backspace and the strip offers it back as **↪ pet**, the start of the next word; anything else you type, swipe or move takes that chip away. While a word is still being built, the floating word over the keys follows the stroke you are drawing, including the start of a word ("som") when the gestures so far finish none. A tap from one thumb while the other is still swiping joins the preview of that swipe instead of replacing it, and when the field shows only the start of a word, the suggestion strip offers the words it could become, as it does for typed letters. Some glide features read one stroke as a whole word, so they are off while two-thumb typing is on: [gliding across the spacebar](#gliding-a-whole-phrase), [glide over Shift to capitalize](#capitalizing-without-leaving-the-stroke), [the picker for a toss-up](#when-a-swipe-is-a-genuine-toss-up), Hold to ask included. The settings screen's note on Two-thumb typing lists them too, and while two-thumb typing is on each of their rows says it is off. Their values stay as you set them for when you turn two-thumb typing off. [Pause, loop and wiggle marks](#marking-a-letter-as-you-glide) all count in a word that is one swipe and nothing else. In a swipe that is part of a bigger word only the loop counts: tap m, then swipe e t with a small circle on the e, and "meet" moves up the suggestions instead of trailing "net". Two-thumb typing works in the app you are typing in, and in the keyboard's own text boxes that have the keys: the searches, the AI prompt, a plugin's text field. There a swipe that lands right after letters joins them into one word, read again whole, and Space ends it; **All thumbs up** doesn't end a word in those boxes. The typing test and the word card's spelling bar keep one-finger glide. Phonetic layouts such as Avro, and keypads that guess, read every swipe as a word of its own. A two-thumb word teaches the keyboard the way a glided word does. Each swipe in it teaches [your swipe style](#what-a-swipe-teaches) where your finger landed on the letters it drew, and a word that is one swipe and nothing else also files its shape. Backspacing the word straight after it is typed takes those lessons back. Fixing a two-thumb word later teaches only that you wanted the other word, never a typing mistake, since the decoder chose the letters and not your fingers. Letters the keyboard falls back to when your gestures finish no word, like "qy" for a swipe from q to y, are typed as shown but never counted towards learning a new word. A word you only tap is learned like any other typed word. Up to 0.6.1 two-thumb words were learned as if you had typed every letter. [Statistics](https://wmkeyboard.pages.dev/reference/settings/statistics/) counts a two-thumb word once, as a glided word, when it ends, and a word with more than one tap or swipe in it also goes towards the **Two-thumb typing** achievement. The keystrokes it saved are its letters less one for each tap and swipe. ### Finishing typed letters with a swipe You can also do this with one thumb. **Swipe finishes typed letters** (Typing / Gestures, off by default) makes a swipe right after typed letters finish them as one word: type t o, then swipe g e t h e r, and "together" is typed with a space, as a glided word is. With the setting off, the typed letters are a word of their own and the swipe types the next one. Two-thumb typing always works this way, so the setting is only shown while two-thumb typing is off. A word you only put the caret back on doesn't count as typed letters. Tap at the end of "Hello" and swipe, and the swipe is a new word with its own space, with two-thumb typing on or off. Type or delete a letter in that word first and the swipe finishes it as usual. Up to 0.6.1 the swipe joined the word the caret landed on and came out glued to it. ## How it picks a word The decoder scores every dictionary word that could plausibly match your stroke and returns the best one, weighing three things: - **Shape.** How closely the drawn path matches the ideal path through that word's letters. Both are resampled to the same number of points, so a fast, loose swipe compares fairly against a careful one. - **Start and end anchoring.** How close your stroke actually started and ended to that word's first and last letter keys. A word is thrown out early if the stroke doesn't begin and end near its endpoints at all. - **Word frequency**, as a small tiebreaker. It's what makes "the" win out over similarly-shaped rivals like "they" or "them" when the early part of the stroke looks the same. How much that tiebreaker counts depends on the language, not on you, so the keyboard sets it per language rather than offering a slider. With another language's words mixed in through **Also suggest from**, the shipped English weights apply instead, since the language's own were measured on its list alone. The decoder's weights were fitted on English swipes, where the words people swipe are also the common ones in the list. German is different: its compounds are rare in any word list, so a frequency weight fitted on English lets a common short word outvote a well-drawn long one. Since 0.6.4 a German layout decodes with frequency at half its English weight and a stiffer charge for a detour between two keys. On 2,588 real German swipes that read 2.9 more in every hundred right, and 10.6 more on a corpus of rare words; the same weights would cost English about one in a hundred, which is why they apply to German alone. Everything you set under [Advanced tuning](#advanced-tuning) stays yours on every language; the per-language weights are the two no setting reaches. Other languages that build long compounds (Finnish, Dutch, Swedish, Norwegian) are the next candidates once their swipes are measured the same way. A word list can't hold every compound a language builds as it goes, and German builds them constantly. On real German swipes, 36 in every 100 words of fifteen letters or more were missing from a 150,000-word list, and a bigger list wouldn't help: generating compounds into a list adds far more non-words than words. Since 0.6.4 a German layout reads a compound the list lacks from its parts. When a stroke has passed through a listed word of four letters or more and keeps going, the decoder may read what follows as a second listed word, or a third, on the same stroke, as if the list held the joined spelling. "Haushalt" is read from "Haus" and "Halt" when only the parts are listed. A word list says which words exist, not which pairs of them make a compound, so the joined spelling also has to appear in the full German frequency list, which the keyboard fetches once (5.5 MB, when **Download data automatically** is on and the connection isn't metered). Until it has, nothing is joined. That is what keeps "fragenroot" off the strip when you swipe "Garagentor". Every part needs four letters, so a short common word can't end a compound: "hast" and "alt" never make "hastalt". The joined reading ranks by its rarest part and pays a charge for each join, and a part the reading joins past pays more the commoner it is, so a word the list holds whole still wins over the same letters joined from two common ones. Without that last charge, two common parts outbid real words often enough to lose about 3.5 swipes in a hundred; with it, the same 2,588 German swipes read half a point better than with no joining at all. Linking letters between parts (the s in "Arbeitszeit") aren't read yet, so a compound built with one still needs to be in the list, or learned once you've typed it. How forgiving those gates are is yours to set. **Start error radius**, **End error radius** and **Path error radius** live in the **Advanced tuning** fold (Typing / Gestures) and are all measured in key widths. The first two are how far the ends of your stroke may sit from a word's first and last letter, and the third is how close the stroke has to pass a key on the way for that letter to still be available. They ship at 1.6×, 2.0× and 1.9×. Raise them when your swipes run wide or cut corners and the word you wanted keeps missing, and lower them to hold the keyboard to the keys you really crossed, which prunes the search a little as well. Going much under 1.3× is where words start dropping out, so move down in small steps and swipe a few sentences before you decide. They're also the quickest way to compare how a layout behaves under your thumb, since a fixed layout with crowded keys wants a different tolerance than a roomy one. Past 1.6×, the two end radii also change how a word scores. Whatever you add above 1.6× is distance the decoder forgives between where your finger went down (or lifted) and the word's first (or last) key, so a word that starts 2.5 keys from your touch-down competes on shape and frequency with one that starts right under it. That is the setting for thumbs that habitually land low or lift early. The end radius ships at 2.0× for that reason: on thousands of real recorded swipes, lifts stopped short of the last letter often enough that forgiving the last 0.4 of a key read more words right. At 1.6× or below, nothing is forgiven and the radii only decide which letters a word may start and end on. A language can keep its own radii. One value rarely suits every layout: Kana JIS packs about fifty keys into the grid, so a radius that forgives a sloppy English stroke on QWERTY lets a Japanese stroke reach keys it never went near. Open the language in Languages, and its **Glide typing** group (shown at the Advanced depth, while glide typing is on) has the same three sliders. Each one starts at the global value. Move it and that language decodes with its own value from then on, whichever layout of it is on screen, while every other language keeps the global one. A radius you leave alone keeps following the global slider, and the row's reset hands it back. **Error radius by language**, under the three global sliders, lists the languages that have their own with the values they use, and opens each language's page. The marks for a doubled letter below aren't per language, because they describe your hand, which is the same in every language. None of those three looks at the word that came *before*. The stroke decoder is deliberately context-free: it reads the path and the candidate's own frequency, nothing else. Context arrives one step later. Where the language has a [context model](https://wmkeyboard.pages.dev/smart/suggestions/) downloaded (English and romanized Bangla do), the model first nudges each word on the shortlist up or down by how much likelier it finds that word than the decoder's first choice after the words already in the field, within a fixed limit, so a close call between two strokes goes to the word that fits the sentence. Since 0.6.4 that counts for more on a swipe than on taps, because a stroke says less about its letters than taps do: on 2,294 German swipes with a word before them, measured with a simple word-pair model standing in for a real one, it took the decoder's first choice from 71 to 81 right in a hundred. The shortlist is then reordered by the same n-gram reranker the suggestion strip uses, and it's the top word *after* that reorder that commits. That step is **Context-aware suggestions** (Typing / Automatic corrections, on by default and gated on autocorrect being on). Turn it off and the decoder's own order stands, so a glide commits purely on shape, anchoring and frequency. A word the model holds only in pieces, such as a long compound, gets a say too, and German weighs the sentence on its own scale. Long words are read the same way as short ones, all the way to the 32-letter limit. The limit was 24 up to 0.6.4, which left a 26-letter compound like "Qualitätsmanagementsystem" unreadable however well it was drawn. A big word list holds thousands of short words that fit the first few keys of any stroke, so the search judges each partial word on how well it could still explain the stroke left ahead of it, not only on how well its own letters fit. Up to 0.6.0 it judged only the letters so far, and on a large list it ran out of room before reaching a long word at all: a German compound like "Computertomographie" or "Qualitätsmanagement" came back as a short word that fit a third of the stroke. On the 205,000-word German list, strokes of 15 to 17 letters went from about 1 in 7 read right to nearly all of them. A stroke that crosses the keyboard many times over is also read in finer steps than a short one, so the corners of a twenty-letter word don't blur together: drawn the way a finger really draws it, "internationalization" went from about 1 in 3 read right to 5 in 6. Since 0.6.4 the steps keep getting finer the longer the stroke runs, so a 30-letter word is read at about the same detail per letter as a 15-letter one rather than at half of it. Shorter words are read exactly as before. A couple of shape quirks are worth knowing. Double letters only need one pass over the key, so "good" traces g-o-d, because drawing a letter twice in a row adds nothing a shape comparison can see. A small circle on the key says you meant the letter twice, and it has a section of its own below, with the two marks that say a letter is in the word at all. And a word needing a character with no key on your current layout, an accented letter your layout doesn't have, say, is quietly left out of the running rather than causing an error. Every letter a key carries counts as that key for a swipe, not only the one it types: the letter on its Shift, the one in its corner and the ones behind its hold all sit where the key does, which is how a Bengali aspirate on Shift or a German `ü` behind `u` can be drawn. Since 0.6.4 that reaches keys that type no letter themselves. A digit on a custom number row with a letter behind its hold, or a punctuation key whose Shift is a letter, puts that letter on the grid at its own spot. It does so only for a letter no letter key already carries, so a letter key keeps its own letters and a digit holding the same letter adds nothing. A stroke that strays above the letters is read as the top row it was reaching for. Going for `i`, a finger overshoots onto the `8` above it as easily as it dips into the spacebar below `n`, and nothing above the top row spells a word. Since 0.6.4 every point of a stroke above the top row is moved straight down onto that row's upper edge before the stroke is read, so drawing m, a, 8, n gives "main". The ends gained the most. A stroke that starts on the digit above its first letter used to start further from that letter than the [start error radius](#how-it-picks-a-word) allows, and gave nothing at all: twenty common words started that way on test strokes, a row and a fifth up, were read none of the time before and every time now. When a number-row key carries a letter of its own (above), the number row is part of the grid and the stroke is only moved down from above it. There is no setting for this, because a swipe can't type a digit: no stroke through the number row ever meant one. Punctuation inside a word is the exception to that last rule. Hyphens, dots, at signs, underscores, ampersands, plus signs and slashes aren't on the keyboard's letter grid, so you can't draw them, and words are spelled with them all the time. Swipe the letters and leave the punctuation out: `fdroid` for "f-droid", `coop` for "co-op", `wasime` for "wasi.me". The word comes back punctuated the way your dictionary spells it. Where both spellings exist, the unpunctuated one goes first on the strip and the punctuated one sits right beside it, so "email" wins over "e-mail" on a tie and loses to it when your dictionary says "e-mail" is much the commoner of the two. Some entries in a downloaded or imported word list are never offered for a swipe. The big English download is built from film subtitles, which spell out speech as people said it. That gives you cut-off words ("that-", "can-"), words with the full stop still on them ("no."), and stutters ("n-no", "c-c-can"). Each one draws the same as the real word, so all they could ever do was take the strip's other slots. A word list entry that ends on punctuation, or whose part before the hyphen starts the part after it again, is skipped. That rule also catches a handful of real words such as "D-Day" and "t-test". Words you added yourself or the keyboard learned from you are never skipped, however they're spelled. The apostrophe is the one piece of punctuation left out of this, because contractions are exactly where guessing goes wrong. "its" against "it's", "were" against "we're" and "shed" against "she'd" are all one stroke each, and picking between them on word frequency alone would be a coin toss. It has its own answer instead: set **Apostrophe in a glide** to a key and swipe through that key. Without that key, the drawn word goes in and its contraction sits right behind it on the strip, with one exception: where the word before says a clause is starting, a pronoun's contraction goes in instead. Draw `hell` at the start of a sentence, or after words like *and*, *but*, *so*, *if*, *when*, *because*, *said* or *think*, and you get "he'll", with "hell" right behind it on the strip. The same goes for "she'll", "she'd", "we'd" and "I'd". The contractions that are *not* ambiguous are swiped without it. Draw `doesnt`, `isnt`, `wasnt` or `shouldnt` and the keyboard reads the stroke as the contraction and writes "doesn't", the same repair **Fix missing apostrophes** makes when you type. This holds for every contraction in that repair's table, whether or not the word list has the bare spelling, and it goes away when that setting is off. Where the bare spelling is a rarer word of its own, such as `ill`, `cant` or `wont`, the stroke is weighed as the contraction it is far more often drawn for, so drawing `ill` gives "I'll". Words you saved yourself with an apostrophe in them work the same way: save "Jenny's" and drawing `jennys` gives "Jenny's", no apostrophe key needed. That holds only where the spelling without the apostrophe is not a word in its own right. A spelling a word list holds only because people leave the apostrophe off doesn't count as a word of its own: German lists count `gehts` far more often than "geht's", and since 0.6.6 a "geht's" you've typed in place of a wrong swipe is learned for the stroke like any other word, so the next `gehts` you draw gives "geht's". Before, the word and the shape of your stroke were kept under the spelling with the apostrophe, which the swipe never asked for, so correcting it taught nothing. ## The FUTO Swipe engine Everything above describes the keyboard's own decoder, which reads every language and layout. For English on a QWERTY layout there is a second one to choose from. **Glide engine** (Typing / Gestures, **Built-in** by default) can be set to **FUTO Swipe**, which reads a swipe with two small neural networks published by FUTO: an encoder that reads the stroke and an English QWERTY decoder that turns it into letters, trained on about a million swipes people drew on real phones. The keyboard then spells words out of your own word lists from those letters, the same lists, added words and learned words the built-in decoder uses, and the result goes through the same context reranking, ambiguity picker and alternates on the strip. On 5,000 swipes from FUTO's public test set (real people's English swipes, not drawn by a program), against the keyboard's bundled English list: | | Right word first | Right word in the top four | |---|---|---| | Built-in decoder | 80% | 94% | | FUTO Swipe | 96% | 99% | Against the 160,000-word downloadable English list the built-in decoder scored 82% and 92%, FUTO Swipe 95% and 99%. Both figures count only swipes whose word is in the list, since neither engine can type a word it doesn't know. The built-in decoder stays the default until the same comparison has been made on phones. Picking it asks you to accept the FUTO Model Weights License, then downloads the two models, 3.9 MB together, from FUTO's Hugging Face page, pinned to one version and checked against their SHA-256 checksums. On a build that leaves the ExecuTorch runtime out of the APK (the Play Store, GitHub and F-Droid builds fetch it on first use, the same runtime offline Asr4All, Parakeet and Moonshine run on), that comes down too. The models run on the phone, so your swipes never leave it. The licence asks that the keyboard say it is powered by FUTO Swipe, which the setting does under its row and the About screen does in its list of components. FUTO Swipe only reads English on a QWERTY layout. Another language, another layout (AZERTY, QWERTZ, Dvorak, Colemak and the rest all move some letter to another row), a phonetic layout such as Avro, and any swipe before the models are on the phone all go to the built-in decoder, with nothing to switch. Two-thumb words are always read by the built-in decoder. Three of the built-in decoder's features carry over to FUTO Swipe, each measured on FUTO's own test swipes before it was kept: - **[Finishing long words early](#finishing-a-long-word-early).** Lift part way through a long word and the strip can offer the word you were spelling, the commonest one that starts with the letters you drew. On swipes cut at 70% of their length the right word came first 12 times in 100 instead of 7, and on fully drawn swipes a guess took the place of the word you drew 17 times in 38,000. FUTO Swipe scores words on a narrower scale than the built-in decoder, so **When sure** almost never offers a guess with it; use **Eager**. - **A loop for a doubled letter.** FUTO Swipe reads "god" and "good" off the same keys, and a small circle on the o settles it for "good". The loop only decides between spellings that differ by doubling that key, so a curl drawn at a corner moves no other word. Without a loop the more common spelling leads, rather than the single one. - **[Your swipe style](#what-a-swipe-teaches)'s shapes.** A word you have swiped before gets a little credit when you draw it the same way again. On words a person had already swiped earlier in their session, the right word came first 98.4 times in 100 instead of 98.1. A pause or a wiggle on a key does not mark a letter with FUTO Swipe. The model reads the timing of your stroke for itself, and counting pauses on top of that made it less accurate on real swipes. ## Google's gesture library The third choice under **Glide engine** is **Google's gesture library (imported)**. This is the closed-source glide engine from Google's own keyboard, the same file HeliBoard and other open keyboards based on AOSP can load. WM Keyboard doesn't include it, host it or download it, because it belongs to Google and isn't licensed for anyone else to hand out. You bring the file yourself. The file is called `libjni_latinime.so`. GApps packages (the bundles of Google apps that custom Android builds install separately) carry it as `libjni_latinimegoogle.so`, usually inside a folder or zip called "swypelibs"; rename it or not, the keyboard accepts either name. There is one build for each processor type, and the keyboard takes only the exact builds HeliBoard accepts, checked by their SHA-256: | Processor (ABI) | SHA-256 | |---|---| | arm64-v8a | `b1049983e6ac5cfc6d1c66e38959751044fad213dff0637a6cf1d2a2703e754f` | | armeabi-v7a | `442a2a8bfcb25489564bc9433a916fa4dc0dba9000fe6f6f03f5939b985091e6` | | x86_64 | `c882e12e6d48dd946e0b644c66868a720bd11ac3fecf152000e21a3d5abd59c9` | | x86 | `bd946d126c957b5a6dea3bafa07fa36a27950b30e2b684dffc60746d0a1c7ad8` | Almost every phone sold in the last several years is arm64-v8a, and the **Gesture library** row names the one your phone needs. Any other file is refused with a reason: a build for another processor says which one it is, and anything else is "not a build this keyboard knows". The check is strict on purpose. The library is machine code that runs inside the keyboard, written against one exact set of entry points, and a different build would crash it or behave in ways nobody has looked at. Turning off **Verify downloaded files** in Privacy doesn't relax it. Import a file only from a source you trust. To set it up, pick **Google's gesture library (imported)** under **Glide engine**, tap **Import** on the **Gesture library** row and choose the file in the system file picker. The Storage screen's import (About / Storage / Import downloaded files) also recognises it, by its name or by its checksum if it was renamed on the way; see [Importing instead of downloading](https://wmkeyboard.pages.dev/privacy/offline/#importing-instead-of-downloading). So does opening the file from a file manager or a chat app: WM Keyboard asks **Import the gesture library?** and runs the same import. The file is copied into the keyboard's private storage, read-only, and it is never backed up. It loads the next time you swipe. Android can't unload a library, so replacing one that is already running takes effect after the keyboard restarts. **Remove gesture library** deletes the file and the dictionaries built for it, and glide typing goes back to the built-in decoder straight away. A library that was already loaded stays in memory until the keyboard restarts, because Android can't unload it, and the **Gesture library** row says so (**Removed**) instead of **Loaded**. The library needs a dictionary in its own format. The keyboard builds one on the phone from the word lists of the language you're typing in, its commonest 120,000 words, and has the library write it so the format is always the library's own. That takes a few seconds the first time and is kept for later; a new or changed word list triggers a rebuild. Words you have added or the keyboard learned from you go into it as well. Until the dictionary is ready, and in a language with no word list, swipes are read by the built-in decoder as usual. The words it returns then go through the keyboard's own blocked-words list, context reranking, capitals and ambiguity picker, the same as with the other engines. It works in any language whose layout has one letter per key; HeliBoard notes that keys with several letters or the same letter twice confuse it. [Finishing long words early](#finishing-a-long-word-early), the marks in the next section and the shapes [your swipe style](#what-a-swipe-teaches) keeps don't apply while it reads a swipe, and two-thumb words stay with the built-in decoder. If the keyboard closes while the library is loading, building a dictionary or reading its first swipe, it is switched off and the row says so. **Try again** turns it back on. Nobody has yet measured how well it reads swipes on this keyboard, so the built-in decoder stays the default. The Google Play version can't load it at all. Play doesn't allow an app to run code that didn't come from Play, so that build leaves the import out and the choice doesn't appear. ## Marking a letter as you glide Some words are the same stroke. "good" and "god" are: the o is one key and your finger crosses it one time, so the shape of the path has nothing to say about which word you meant. So are "write", "wire" and "wrote", which all draw the same straight line along the top row of a QWERTY keyboard. Without a mark, the commoner word wins on frequency alone. There are two kinds of mark you can make on a key, and they mean different things: - **Stay on the key: "this letter is in the word".** Slow to a stop on the i and carry on, and every word with no i there loses, so "wrote" drops away and "write" and "wire" are left. The stop counts for the key you stop on, not the keys next to it. The keyboard measures the pause against your own speed in that same stroke rather than in seconds, so it works the same whether you glide quickly or carefully. This one can't be switched off. - **Rub back and forth on the key: the same thing, without stopping.** A wiggle is the moving way to make a pause, for when stopping feels slow. It's off out of the box, because a slow turn drawn with an unsteady finger looks much the same to the keyboard, and reading it marks keys you only went past. Turn it on if it's the mark you naturally make. - **Draw a small circle on the key: "this letter, twice".** Swype's mark, and the only one that doubles a letter. Go around on the o once and carry on for "good". The circle costs the word nothing: the loop is taken back out of the distance your finger travelled, so "god" is no worse off for having been drawn with one. The circle belongs to the one key nearest its middle, so the keys its edge passes over stay out of the word, and a circle on the f of "off" can't turn into "odd". A circle on the last letter, with nothing after it but the lift, also says the word ends there, so with **Finish long words early** on a circle closing "see" stays "see" and isn't read as the start of a longer word. A pause or a rub never doubles a letter. Without a circle, the two spellings tie on shape and the more common word leads: a plain "al" stroke reads as "all", "ben" as "been", and "ill" or "I'll" come up for a stroke from i to l. Only when the two are about as common does the single spelling keep a slight edge, and a circle on the key is how you settle that one. The circle also no longer draws in neighbouring letters, so a looped "need" stays "need" rather than turning into "needed", and "bee" doesn't become "bede". If you always pick the same word from the strip for a stroke, the keyboard learns that too. Each has a row in the **Advanced tuning** fold (Typing / Gestures). **Pause to mark a letter** sets how long a stop has to be. **Loop to double a letter** turns the circle on and off, and under it **Loop sensitivity** sets how much of a circle a curl has to be, **Biggest loop that counts** keeps a wide sweep through a ring of keys (the way "murderer" is drawn) from reading as a mark, and **Loop centring** sets how near the letter the middle of the circle has to sit. **Wiggle to mark a letter** does the same for the rub, with a strength and a size of its own. These are worth moving because they're about your hand, not your layout: a careful glide pauses everywhere and a quick one barely pauses at all, and the same curl is a deliberate circle from one person and a shaky corner from another. If words lose letters you only went past, raise the pause; if your stops don't seem to count, lower it. If doubled letters keep appearing on their own, raise the loop sensitivity; if your circles don't count, lower it. ## Autocorrect and learning ### Going back to a misread swipe Putting the cursor back on a swiped word puts that stroke's other readings on the strip, ahead of the strip's own answer. This matters more than it sounds. The strip normally reads the letters standing in the field, so a stroke misread as "form" offers "forms" and "fort": respellings of the wrong word, never the word you drew. The readings the stroke actually produced *do* contain it, so those go first, and taking one is both the fix and a lesson: it counts as a correction of that stroke, the same as re-picking straight after the swipe. The readings are kept for the last 500 swiped words in the field and go when the field does. Each set is tied to the spot its word sits in, so the same word typed somewhere else in the text never picks up that stroke's readings. Sometimes the readings don't contain it either. Under **Only my words** a swipe is answered out of the words the keyboard has learned from you, so a word it has never seen you write cannot come back however you draw it. That is the trade the setting makes. The stroke itself is still there, though, and it is the only record of what you meant: the letters left in the text are the keyboard's guess, not your finger's path. So hold any word on the suggestion strip while the cursor sits in the wrong word and pick **Search all words**. The stroke is read again against every word list you have, past the sandbox and past the vocabulary limit, and the strip fills with what the full dictionary makes of it. Take the right word and it both fixes the text and teaches the stroke: the hand model, the shape for that word and the pair, exactly as though you had picked it the moment you swiped. This is the proofreading pass a swipe otherwise doesn't get; tap typing has it for free, one letter at a time. The search is asked for rather than automatic, because it is the slow, noisy decode that the vocabulary limit and **Only my words** exist to avoid, and running it on every word you read back would undo the setting. If you'd rather not go through the menu, **Offer a full search while proofreading** puts a small search button on the strip whenever the cursor lands in a swiped word whose stroke is still kept. Tap it to run the search; hold it to send it away. It is an icon rather than a sentence on purpose: the words beside it are the ones the search is offering to improve on, and a chip that spelled the offer out took half a phone's strip with it. It's still off until you turn it on. Paths are much heavier than readings, so by default only the newest 24 swiped words in the field keep theirs. Past that the readings remain and the full search is no longer offered. **Swiped words a full search can reach** (Typing / Gestures, beside **Offer a full search while proofreading**) changes that number, from 0 to 500. Each path is a few kilobytes, so 500 can take a few megabytes while you write a long text; 0 keeps none, and the full search is never offered. Autocorrect never runs a second pass over a glided word. The decoder only ever returns real dictionary words in the first place, so there's no typo to fix. What glide typing does share with normal typing is learning: every word a glide commits goes through the same learning path a tapped-out word does. It's reinforced in your personal dictionary and recorded as following whatever word came before, which is what powers next-word prediction later. It works the other direction too. A word you've recently typed or had autocorrected becomes swipeable right away, since learning a word immediately refreshes the list glide typing decodes against. Learning from a glide follows the same rules as any other typing. It respects the "Learn from typing" toggle, pauses under incognito, and skips entirely in fields that don't allow typing intelligence. There's no glide-specific exception. ### What a swipe teaches A kept swipe teaches three more things besides the word, all under **Learn my swipe style**. The first is where your finger actually lands. Nobody swipes through the exact middle of every key: a thumb cuts the far keys short and lands low on the near ones, and every swipe used to pay for that. Each swipe you keep records where your finger really passed each letter, and later swipes are read against keys moved to match, so a consistent miss stops costing anything, and so does a hand that draws every word a little smaller than the keyboard and never quite reaches the far keys. The second is how you draw each word. The decoder compares a stroke against the textbook path through a word's keys, and nobody draws that either: you cut the same corners the same way every time. So a few of your own shapes are kept per word (three, unless you change **Shapes kept per word** under [Advanced tuning](#advanced-tuning)), and a word you have swiped before is read against them as well as the textbook path. The shape only lands once the word has settled as a learned word, never at the moment you swipe it, so a swipe you take back or replace teaches nothing, and one you replace marks the shape that read it wrongly. The third is what you do about a wrong reading. Take a different word off the strip, lift on a picker word that wasn't the leader, or backspace a swiped word straight away, and the keyboard remembers it against that pair of words. It is a nudge, not a rule: three consistent corrections are enough to flip a close call, and the same stroke then reads your word first without asking, but no habit overturns a reading the stroke makes clearly, and a correction the other way cancels the first. Backspacing a swiped word takes its lessons back. If you then swipe or type the word you meant within ten seconds, the stroke teaches that word instead, but only when it started and ended within about two keys of that word's first and last letters. Delete a swiped "the" and type "a" and nothing is learned, since that is an edit of the sentence rather than another go at the swipe. Up to 0.6.2 any word typed next took the stroke. Only the shapes are filed under words, and only under words already in your personal dictionary. The finger positions are numbers on a grid, and the corrected readings are scrambled fingerprints of the words, so those files say nothing readable about what you wrote. All three travel in a backup under **Swipe style**, which is off until you turn it on, for the same reason the dictionary is: the shapes are filed under the words you drew them for. **Forget my swipe style** deletes all three and starts over, and leaves your learned words alone; the word card a held suggestion opens can also forget the shapes of one word. Since 0.6.6 the shapes are stored in a compact binary file (`learning/glide_shapes.bin`) rather than JSON, about half the size, and quicker to read when the keyboard starts. Your existing shapes move over the first time the keyboard saves after the update. The file carries a SHA-256 checksum, so a file damaged on disk is noticed rather than misread: the keyboard puts it aside, reads the version before it, which it keeps beside the live one, and starts without learned shapes only if both are unreadable. A backup still carries the shapes as JSON, so it restores on older versions too. Going back to 0.6.5 or older by installing it over this one doesn't carry them, because those versions can't read the binary file: make a backup with **Swipe style** in it first, and restore it after the downgrade. Your hand is allowed to change its mind. A word you start drawing from a different place, or hold at a different angle, is a new shape: keep drawing it the new way and the new shape settles in and the old one fades out, rather than the first way you ever drew it holding the slot for good. It also no longer matters how you make the correction. Taking the right word off the strip, backspacing the wrong word and picking off the strip that comes back, and backspacing it and drawing again all teach the keyboard the same three things. Backspacing first used to teach nothing at all: the stroke was thrown away the moment the word came off, so there was nothing left to attach the right answer to, and the same correction could be made over and over without the reading ever moving. Now the stroke waits a few seconds for whatever you put in its place. One more shift-related detail: only the first word of a multi-word glide honors a held Shift, capitalizing it or, under caps lock, the whole word. Words chained afterward in the same stroke always land lowercase. ### Capitalizing without leaving the stroke Tapping Shift and *then* swiping is two gestures for one word, and it breaks the flow that makes gliding worth using. That's why most people stop capitalizing swiped names. So the stroke can say it itself: draw through the Shift key on your way and the word comes out capitalized. Cross it twice in one stroke and the whole word is in capitals, the same ladder tapping the key walks up. The detour spells nothing. The walk out to the Shift key and the walk back are cut from the path before the word is read, not only the key itself, so going out to the key and back never adds a letter: a stroke drawn t→Shift→h→e→r→e reads as "there", not as whatever lies between `t` and the key. Nothing happens unless the key is actually crossed, so ordinary swipes near it are unaffected, and the switch (**Glide over Shift to capitalize**) turns it off entirely. The preview keeps up: from the moment the stroke crosses the key, the word floating above your finger and the candidates on the strip show the capital the lift will type. **What a pass over Shift capitalizes** chooses what a crossing means. *Whole word* is the ladder above. *One letter* reads each crossing as a capital for the letter your finger just left, so a stroke can write `HeLLo` or `LeanType`: glide to the letter, out to Shift, and on to the next letter, as many times as the word needs. Under that reading a whole-word shout is one trip rather than two: end the stroke on the Shift key and every letter is a capital. A capital the keyboard armed itself at a sentence start still stands, so a drawn capital and an automatic one can be different letters of the same word. This is also the one way a swipe can teach a capital. A capital the *keyboard* chose is never evidence of how you spell a word (see [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#capitals)); a capital you drew is. ## The glide check-up Two of the numbers behind a swipe depend on what you write, not on the keyboard: how much of the dictionary a swipe may answer with (**Words a glide can produce**) and how much a word's commonness counts against the shape you drew. The second has no slider. It is set per language, because a word list's frequencies mean different things in different languages, and the right value also moves with the person: someone who writes rare words wants shape to count for more than someone whose words are the common ones. The check-up (Typing / Gestures / Glide typing / Glide check-up) finds both from your own swipes instead of asking you to guess. It is off until you open it and turn **Measure my swipes** on. While it measures, each word you swipe is read again in the background under a few candidate values, and only the words that come out are kept, never the stroke. If you later fix that word, by taking another reading off the strip, running a full search, or backspacing it and drawing again, the record is updated with the word you meant. After 30 swipes the screen proposes one change at a time, and only one that would have read more of your own swipes right. It says so in those terms: "On your last 60 swipes this would have read 56 right instead of 52: 5 more, 1 fewer." The proposal is judged on swipes the fit never saw, so a value that happens to suit half your words and not the other half is not offered. The vocabulary limit is tried first, then the frequency weight; the radii and the other geometry are not on the ladder, because on thousands of real swipes they were already at their best and a per-person fit had nothing to win. **Apply** sets the value, **Not now** puts that candidate aside until you start over, and **Undo** puts the old value back. A proposal measured against a value you have since changed by hand is refused rather than applied over it, and measuring starts again from the new value. After you apply a change the check-up watches the next 40 swipes and compares how often you corrected a swiped word before and after. It then says which way it went and offers **Keep** or **Undo**; nothing is ever undone for you. The screen also shows what it learned about your hand, in key widths: how far inside a corner you cut, where your finger sits relative to the letters, how much of a word's span a stroke covers, and how far from the first and last letters it lands and lifts. A fitted frequency weight is listed at the bottom of the screen with a reset back to the language's own; **Start over** forgets everything measured. ## Options Typing / Gestures typing/glide-settings-gestures — The Gestures group on the Typing settings screen. | Setting | Default | Notes | |---|---|---| | Glide typing | On | Master switch for glide typing. | | Glide across the spacebar | On | Only shown while glide typing (not handwriting) is the active swipe mode. A crossing has to reach the bar's midline; clipping its top edge doesn't count. Turn off to treat a spacebar-crossing swipe as one word. | | Two-thumb typing | Off | Only shown while glide typing is the active swipe mode. Both thumbs can tap and swipe at once, and every tap and swipe adds to one word. See [Typing with both thumbs](#typing-with-both-thumbs). | | Finish a two-thumb word | Space | Only shown while Two-thumb typing is on. Space: the word stays open until space or punctuation. All thumbs up: the word is typed with its space once no thumb has touched the keyboard for the pause below, tapped words included. | | Pause after a swiped word | 350 ms | Only shown with All thumbs up. How long both thumbs have to stay up before a word with a swipe in it is typed, from 100 to 1500 ms. A thumb that lands sooner carries on the same word. | | Pause after a tapped word | Same as the swiped-word pause | Only shown with All thumbs up. The same pause for a word you only tapped, from 100 to 1500 ms. | | Pause after a one-letter word | Same as the swiped-word pause | Only shown with All thumbs up. The least a single letter waits before it is typed as a word, from 100 to 1500 ms. | | Take the space back | Twice the swiped-word pause (700 ms) | Only shown with All thumbs up. How soon after the space a letter can still take it back and carry the word on, when the two lead to a dictionary word. 0 to 3000 ms, off at 0. See [Typing with both thumbs](#typing-with-both-thumbs). | | Show the pause on the strip | On | Only shown with All thumbs up. A line along the bottom of the suggestion strip shrinks over the pause. | | Swipe finishes typed letters | Off | Only shown while Two-thumb typing is off. A swipe right after typed letters finishes them as one word. See [Finishing typed letters with a swipe](#finishing-typed-letters-with-a-swipe). | | Glide over Shift to capitalize | On | Only shown while glide typing is the active swipe mode. Draw through the Shift key mid-swipe for a capital, twice for all capitals. See [Capitalizing without leaving the stroke](#capitalizing-without-leaving-the-stroke). | | What a pass over Shift capitalizes | Whole word | Only shown while Glide over Shift to capitalize is on. Whole word: one pass for a capital, two for all capitals. One letter: each pass capitalizes the letter the finger came from, and ending the stroke on Shift makes the whole word capitals. | | Ask when a glide is unclear | On | Only shown while glide typing is the active swipe mode. When a stroke's best word barely beats its runner-up, hold still without lifting to pick between them. See [When a swipe is a genuine toss-up](#when-a-swipe-is-a-genuine-toss-up). | | When to ask | Close calls | Only shown while Ask when a glide is unclear is on. How close a call a stroke has to be: Near ties, Close calls, Any doubt or Every pause. | | Time to hold still | 350 ms | Only shown while Ask when a glide is unclear is on. How long the finger rests before the words appear. Range 150 ms to 1000 ms. | | Hold longer to always ask | On | Only shown while Ask when a glide is unclear is on, and hidden under Every pause. A stroke the keyboard is sure of still asks after twice the hold time. | | Words to choose from | 3 | Only shown while Ask when a glide is unclear is on. How many words the picker offers, 2 to 5. | | Learn my swipe style | On | The keyboard learns where your finger really lands on each key, how you draw each word, and which words you take over a swipe's reading, and reads later swipes by it. See [What a swipe teaches](#what-a-swipe-teaches). Off freezes what has been learned. | | Forget my swipe style | | Deletes what the switch above learned, after a confirm. Learned words are not affected. | | Glide check-up | | Opens the screen that measures your own swipes and proposes one change at a time. See [The glide check-up](#the-glide-check-up). | | Space after a glided word | On | Only shown while glide typing is the active swipe mode. A glided word is followed by a space; punctuation typed next takes it back. Turn off to commit the word bare. | | Backspace removes a glided word | On | Only shown while glide typing is the active swipe mode. One backspace right after a glide deletes the whole word and its space. Turn off to delete one character at a time instead. | | Swipe start distance | 2.0× | How far a touch has to travel, as a multiple of the system's touch slop, before it's read as the start of a glide rather than a tap. Range 0.5×–4×; lower is more sensitive but can mistake a stationary tap for a glide. A stroke that starts a glide but never gets more than about two-thirds of a key from where it landed still types the key you pressed, so a tap that rolls a little during fast typing isn't lost. | | Delay after typing | 160 ms | A brief window right after a tap where a glide needs to travel further before it takes over, so fast tapping doesn't misfire into an accidental swipe. Range 0–500 ms; 0 turns it off. Two more guards against a finger skidding on the glass come with it. In the middle of a word you are tapping out, within about 0.6 seconds of the last letter, a glide has to cover a key and a half before it starts, and a shorter skid types the key you pressed. And during a burst of typing, a stroke that is over in under 0.15 seconds and never gets more than about a key and a quarter from where it landed types its key instead of being read as a word, so a quick glide of two neighbouring letters only works away from typing. Swipe finishes typed letters, and a key with a hint, capital or word flick, keep the mid-word guard off. 0 turns all three off. | | Trail width | 10 dp | Thickness of the trail at the head (the tail tapers to about 30% of this). Range 2–24 dp. | | Trail length | 350 ms | How long a point on the trail lingers before fading out. Range 100–1200 ms. | | Trail opacity | 55% | Peak opacity of the trail. Range 0%–100%; set it to 0 to glide with no trail at all. Power saving drives the same value to 0 on its own, see [Details & edge cases](#details--edge-cases). | | Word preview | On | Only shown while glide typing is the active swipe mode. The word decoded so far floats above your finger and updates as you draw. It rides in its own window, so near the top row it rises over the suggestion strip instead of sitting on the keys. Off leaves it to the suggestion strip. | | Preview distance above finger | 56 dp | How far above the fingertip the floating word sits. Range 0–160 dp. Raise it when your hand covers the word. | | Preview sideways shift | 0 dp | Moves the floating word left (negative) or right (positive) of the fingertip. Range −80–80 dp. | | Preview text size | 18 sp | Size of the floating word. Range 12–32 sp. | | Preview color / Preview text color | Automatic | Background and text of the floating word, and of the words offered when a stroke stops on a key. Automatic follows the keyboard theme's popup colour. Whichever it is, a fill too close to the colour of the keys gets a thin outline so it stands out from them. | | One word on the strip | Off | Only shown while glide typing is the active swipe mode. While the stroke is down, the suggestion strip carries only the word the keyboard reads at that moment instead of the whole candidate list. The alternates come back when you lift. | | Whose words a glide can produce | Every word | Every word, Prefer my words, Only my words or Automatic. Only my words answers a swipe out of the words the keyboard has learned from you and nothing else, so downloaded and imported word lists sit out. Automatic starts at Every word and asks on the suggestion strip before moving up (tap the question to say yes, tap its cross to say never, or hold it to open this setting first); once you say yes it works like that option, and the line under the setting says which. Picking any other option puts Automatic back at the start. Backspacing a glided word always searches the whole dictionary, and so does **Search all words** on a word you come back to. | | Offer a full search while proofreading | Off | Puts a small search button on the suggestion strip when the cursor lands inside a swiped word whose stroke is still kept, for searching that stroke against every word list. Tap it to search, hold it to dismiss. The same search is always available from a held word on the strip; this only saves the hold. See [Going back to a misread swipe](#going-back-to-a-misread-swipe). | | Swiped words a full search can reach | 24 | How many of the newest swiped words in the field keep their stroke for the full search, from 0 to 500. Each stroke is a few kilobytes of memory. | | Finish long words early | Off | Off, When sure or Eager. Lets a swipe be read as a word it has only started, and lifting types it. A word you have drawn in full is never overtaken by a longer one unless the longer one is clearly likelier; under Prefer my words and Only my words the guess is always one of your own words. See [Finishing a long word early](#finishing-a-long-word-early). | | Color the word a lift will type | When sure | Never, When sure, Any early word or Always. Which word on the strip and in the preview gets the strip's own colour, meaning "this is what a lift types", not just bold. | | Glide engine | Built-in | Built-in, FUTO Swipe (English, QWERTY) or Google's gesture library (imported). FUTO Swipe reads English swipes on a QWERTY layout with FUTO's neural models, downloaded under their own licence. Google's gesture library reads every language once you import the file yourself. Everything else stays on the built-in decoder. See [The FUTO Swipe engine](#the-futo-swipe-engine) and [Google's gesture library](#googles-gesture-library). | | Gesture library | | Imports Google's `libjni_latinime.so` through the file picker, if it is one of the pinned builds for your phone, and shows whether it is loaded. Appears while Google's gesture library is the engine or a file is imported. Not in the Google Play version. See [Google's gesture library](#googles-gesture-library). | | Remove gesture library | | Deletes the imported library and the dictionaries built for it. Glides use the built-in decoder at once; a loaded library leaves memory when the keyboard restarts. | ### Advanced tuning A fold further down the same screen, closed until you open it, holding the weights the decoder reads a stroke with. The first three decide which words a stroke may be read as at all; the next eight decide how a stroke marks a letter, as in the word or as written twice, and the last is how many of your own shapes are kept for a word. Every row has its own reset, so you can move one, glide a few sentences, and put it back if it was worse. The whole group is hidden while glide typing is off or the swipe mode is Handwrite. | Setting | Default | Notes | |---|---|---| | Start error radius | 1.6× | How far from a word's first letter your finger may go down and still get that word, in key widths. Raise it if your swipes start off the key, lower it to be stricter about where a word begins. Above 1.6× the extra distance is also forgiven in the score, so a word starting that far off can still win. Range 0.5×–4×. | | End error radius | 2.0× | The same for where you lift. It's separate because the end of a swipe is only where a movement stopped, so it tends to be looser than the start. Range 0.5×–4×. | | Path error radius | 1.9× | How close the stroke has to pass to a key for that letter to still be available to a word, in key widths. Raise it to cut corners freely, lower it to keep the keyboard to the keys you actually crossed. Range 0.5×–4×. | | Error radius by language | None | The languages that glide with radii of their own in place of the three above, each with the values it uses. Tap one to open its page in Languages, where the radii are set. See [How it picks a word](#how-it-picks-a-word). | | Pause to mark a letter | 2.0× | How long a stop on a key has to be to say the letter is in the word. Words without that letter lose; the stop never writes a letter twice. A multiple of your own speed in that same stroke, not a number of seconds, so it reads a quick glide and a careful one alike. Lower it if your stops don't seem to count, raise it if a careful glide marks keys it only went past. Range 1×–8×. | | Loop to double a letter | On | Reads a small circle drawn on a key as that letter twice, Swype's mark, and the only mark that doubles a letter. The circle costs the word nothing, and it belongs to the one key nearest its middle, so the keys around it stay out of the word. Turn it off if your swipes curl on the way and you get doubled letters you didn't ask for. | | Loop sensitivity | 1.0× | How much drawn path a curl needs, in key widths, before it counts as a circle. Low catches a small curl and also the shake of a slow turn; high asks for a circle drawn on purpose. Range 0.5×–2×. | | Biggest loop that counts | 0.8× | How wide a circle may be and still be a mark on one key, in key widths. A word like "murderer" goes around a ring of neighbouring keys and draws a wide circle meaning nothing of the sort, so this stays under one key's width. Range 0.4×–1.2×. | | Loop centring | 0.60× | How near the letter's key the middle of the circle has to sit, in key widths. A circle drawn through a key sits about a third of a key to one side, so very low finds no letter for the mark. The circle only ever goes to the one nearest key, so high only accepts circles drawn further off centre. Range 0.35×–1×. | | Wiggle to mark a letter | Off | Reads a rub back and forth over a key the way it reads a pause: the letter is in the word. It never writes the letter twice. Off because a slow turn drawn with an unsteady finger looks much the same, and the cost is keys marked that you only went past. | | Wiggle strength | 50% | What a rub is worth against a full stop on the key. 100% makes the two equal; lower leaves the rub as a hint a much commoner word can still win against. Only shown while the switch above is on. Range 10%–100%. | | Biggest wiggle that counts | 0.50× | How far the finger may travel in a rub, in key widths. Stays under one key's width, or a word drawn back and forth between two neighbouring keys reads as a rub on one. Only shown while the switch above is on. Range 0.2×–0.9×. | | Shapes kept per word | 3 | How many of your own ways of drawing one word [your swipe style](#what-a-swipe-teaches) keeps apart. Raise it if you swipe some words several different ways and one of them keeps being forgotten; the shapes travel in a backup under **Swipe style**, so you can see how many your words really use. Lowering it takes the extras out of use at once, and they're only deleted when that word next learns a new shape, so moving it back up before then loses nothing. Only shown while **Learn my swipe style** is on. Range 1–20. | | Seed candidates from learned shapes | Off | Offers a word whose saved shape matches your stroke even when the stroke starts or ends off that word's keys, at the same rank as the decoder's best reading. Swipe "can" a few times starting near V and pick it, and the same stroke offers "can" alongside "van". Only shown while **Learn my swipe style** is on. | The three marks have a section of their own: [Marking a letter as you glide](#marking-a-letter-as-you-glide). **What a swipe does** lives in this same settings group and decides what a letter-area swipe means in the first place. **Type words** is glide typing. **Handwrite** draws ink instead: see [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/#handwriting-without-opening-the-panel). **Slide to a key** types no words at all. Put a finger down, slide it to the key you meant, and only that key is typed when you lift. The key under your finger lights up with its preview bubble while you slide, and the keys you pass over type nothing. It's for a finger that keeps landing one key off: slide to the right key instead of typing the wrong one and deleting it. ## Details & edge cases **Power Saving mode splits glide typing into two independent switches.** By default, power saving drops only the trail: it forces trail opacity to zero, so nothing is drawn while you swipe, and decoding and committing carry on as normal. Turning glide typing off altogether under power saving is a separate option, and it's off by default. Decoding a swipe really is the most expensive thing the keyboard does, but it's also why many people installed a gesture keyboard, so the cheap fix ships on and the expensive one is opt-in. See [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/). **A layout that can't spell the language turns glide typing off rather than breaking it.** The decoder throws out any word holding a character the grid can't produce, so a layout covering only a fraction of a language would never fail loudly. It would decode against that fraction and commit confident nonsense. Instead, WM Keyboard takes the language's most common words, counts how many the layout can draw end to end, and wants at least 90% of them before it will glide. The threshold sits short of 100% on purpose: real word lists carry a few loanwords and proper nouns in other scripts, and demanding every one of them would fail layouts that handle everything people actually type. Words spelled with an apostrophe or a hyphen the layout has no key for, such as French `c'est`, `d'un` and `lui-même`, aren't counted either way, since no layout can draw them. Up to 0.6.0 they counted as misses, and in French they are about one common word in eleven, so a layout carrying é, è, ê, à, ù and ç on its keys but not â, î and ô (an imported Unexpected Keyboard AZERTY, say) scored just under 90% and lost glide typing entirely. The common words come from the language's own bundled or downloaded list. Up to 0.5.10 an imported list was counted alongside it with an equal share, so one import the grid couldn't spell (an English list under Arabic, or a compiled `.dict` that 0.5.9 had copied in without unpacking it) halved the score and every swipe landed as taps, with nothing on screen to say why. Imports no longer count while the language has a list of its own, and those old `.dict` copies are [repaired automatically](https://wmkeyboard.pages.dev/reference/settings/dictionaries/#custom-dictionaries). **Only the final word of a multi-word glide has alternates on the strip.** Backspace takes the final word back whole, then the one before it, and so on back through the stroke, each leaving its own other readings on the strip. Typing or moving the caret ends that walk, as it does after a single swipe. **The bigram context updates correctly when you swap an alternate.** Tap an alternate to replace a glide's word and the "what word came before" tracking re-links to the word before the one you just replaced, rather than to the discarded guess. Next-word prediction stays accurate after a correction. See also the [gesture cheat sheet](https://wmkeyboard.pages.dev/reference/gestures/) for every gesture on one page, and [Reference → Settings → Typing](https://wmkeyboard.pages.dev/reference/settings/typing/) for the full Typing screen. --- # Hardware keyboards URL: https://wmkeyboard.pages.dev/typing/hardware-keyboard/ Section: Typing Summary: Physical keyboards route through the same engine: autocorrect, suggestions and tools included. Plug in a Bluetooth or USB keyboard and WM Keyboard keeps running underneath rather than getting out of the way. Autocorrect, suggestions and transliteration all work on physical keystrokes exactly as they do on the soft keys. A leader key opens any tool without your hand leaving the keys. typing/hardware-typing-live — Typing on a physical keyboard: a word underlined mid-correction in the field. ## Typing on a physical keyboard Typing / Physical keyboard / Process the physical keys With **Process the physical keys** on (the default), a key you press on a physical keyboard runs through the same code path as a tap on the screen. Phonetic layouts transliterate as you go, words compose for suggestions, autocorrect fires on Space, and ⌫ undoes a swipe or a correction instead of just deleting a character. **Wrap the selection with brackets** carries over too, and it's on by default: select some text, type a bracket (either half) or a quote on the physical keyboard, and it wraps the selection the same way a tap would. Turn the toggle off and physical keys type their raw characters straight into the field. The keyboard's own layout owns input entirely from there, and correction is the system's business rather than WM Keyboard's. Two things stay true no matter how this toggle is set: - **Shortcuts, navigation and function keys always go straight to the app.** Ctrl+C, the arrow keys, Tab, Esc and forward-delete are never intercepted for typing. Only actual character keys are. AltGr combinations look like a shortcut and aren't one: a German keyboard's AltGr+Q for "@" arrives as Ctrl and Alt together, and the keyboard reads that as a character, decoding it the way the system does. - **Password fields and structured fields never get autocorrect help.** Email, URL, number, phone, date, time and date-time fields, and anything marked as a password, always take raw physical typing. There's nothing to transliterate or correct in a field that isn't ordinary prose. Android composes a physical dead key (an accent key that combines with the next letter) before the keystroke reaches the keyboard, so WM Keyboard doesn't run its own accent logic on top. You get the accented character the same way any other app would see it. One soft-keyboard-only behavior is worth knowing: a bare press of the physical Shift key is only a modifier, so it doesn't cycle a selection's capitalization the way tapping the on-screen Shift key does. ## Opening tools with the leader key Typing / Physical keyboard / Tool shortcuts list typing/hardware-tool-picker — The tool-picker overlay after double-tapping Ctrl, showing the bound letters. **Tool shortcuts** (on by default) let you open any tool without touching the screen at all. Double-tap Ctrl, a combination nothing else uses, and every button on the keyboard sprouts a small badge under its icon showing the key that operates it. Press that key and the button fires. Press nothing and the badges disappear on their own. The badges cover everything on screen: | Where | Key | | --- | --- | | Toolbar icons | 1 onward, counting from the toolbox button at the start | | Toolbox tools | the tool's own letter, or Shift+a spare key for the ones with no letter (all 26 letters first, then the digits) | | Symbol row | Shift+a letter | | Emoji row | Shift+1 onward, counting from the left like the toolbar | | Suggestions | 1 to 9, left to right (see below) | The badges spell their modifier out, `Ctrl+2` and `Shift+Q`, because those are the names printed on a PC keyboard. Typing / Physical keyboard / Write the modifier names swaps them for the shorter `⌃` and `⇧` glyphs if you'd rather have the space back. Typing / Physical keyboard / Time to press a key sets how long they stay up: eight seconds by default, anywhere from one to thirty. Keep it long while you're learning them and short once you aren't. The keyboard does nothing else until the time runs out or you press a key. T still opens the full toolbox from anywhere, and ? opens the cheat sheet: a complete legend of every key, including the tools that aren't on screen to wear a badge. While the badges are up, a small `? for all keys` pill in the bottom-right corner points at it. The legend has a close button of its own, so you're never stuck in it without a keyboard. ### Ctrl and a number, with no leader Typing / Physical keyboard / Ctrl + number opens a toolbar tool The toolbar's numbers also work as a direct chord, so the common tools are one keystroke away rather than three. Ctrl+1 is the toolbox button, Ctrl+2 the first tool after it, and so on to Ctrl+0 for the tenth button. Anything past the tenth stays reachable through the toolbox. That's the one setting here worth a second thought before you leave it on. Browsers and editors use Ctrl+number to switch tabs, and while you're writing in a text field the keyboard takes the key first. Turn it off and the same numbers still work after the leader key. ### The tool letters typing/hardware-shortcuts-list — The Tool shortcuts list screen: every tool with the letter that opens it. The default letter for each tool is close to its own initial, wherever that letter was still free: | Key | Tool | Key | Tool | | --- | --- | --- | --- | | A | AI writing tools | O | Settings shortcut | | B | Power saving | P | Password generator | | C | Clipboard | Q | QR code generator | | D | Dictionary | R | Translate | | E | Emoji | S | Snippets | | F | Fancy text | U | Unit converter | | G | GIFs | V | Voice typing | | H | Hide keyboard | W | Web search | | I | Image search (web search's Images tab) | X | Text editing | | J | Apps | Y | Special symbols | | K | Stickers | Z | Undo | | L | Calculator | | | | M | Media controls | | | | N | Numpad | | | Open **Tool shortcuts list** to change any of it. (The screen is reachable only while Tool shortcuts is on.) Tap the shortcut key row to pick a different double-tapped modifier, Ctrl, Alt or Shift, or to capture an arbitrary chord live from an attached keyboard. Tap any tool's row to type a new single letter or digit for it. **Reset to defaults** puts every letter and the shortcut key itself back the way they started. T and ? are reserved and can never be bound to a tool: they always open the toolbox and the cheat sheet, respectively. The full binding table and cheat sheet also live in [Hardware shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/). There are dozens of tools and only 25 bindable letters on the bare tier (T is spoken for), so "give everything its own key" is arithmetically impossible on one tier. The ones with no letter of their own get a Shift+key assigned automatically while the toolbox is open, drawing on the letters first and then the digits. The Shift tier uses all 26 letters, Shift+T included, because only the bare T is reserved. That's what the badges in the grid are showing. They follow the grid's order, so they stay put unless you rearrange the toolbox itself. ## Symbols and emoji from the keyboard While the toolbox is closed, the same Shift tier goes to the symbol row and the emoji row. Double-tap Ctrl, then press Shift with the key shown under the cell you want, and it's inserted. The symbol row takes the letters and the emoji row takes the digits: Shift+1 for the first emoji, counting from the left exactly the way the toolbar's numbers do. Letters against digits means the two pools can never collide, so adding a character to a symbol set never renumbers the emoji beside it. An open toolbox takes the whole tier for itself and the rows drop their badges while it's up. That's deliberate: sharing would move the rows' keys around every time a panel opened, and a key that moves is a key nobody learns. ## macOS shortcuts Typing / Physical keyboard / macOS shortcuts Off by default, and worth turning on for a keyboard built for a Mac. With it on, Command (the key beside the space bar, which Android sees as Meta) and Option behave the way they do on macOS: | Chord | Does | | --- | --- | | ⌘+C / V / X / A | Copy, paste, cut, select all | | ⌘+Z / ⌘+Shift+Z | Undo, redo | | ⌘+← / → | Start, end of the line | | ⌘+↑ / ↓ | Start, end of the text | | ⌥+← / → | One word left, right | | ⌥+⌫ | Delete the word before the cursor | | ⌘+⌫ | Delete to the start of the line | Hold Shift with any of the moves to select instead. Ctrl keeps its usual meaning throughout. On a Mac it's a separate modifier with its own habits, and an app that already handles Ctrl+C should keep getting it. Every other Command chord (⌘+F, ⌘+S) goes straight to the app, and some devices let the system claim a few Command chords, which never reach the keyboard to be remapped at all. One deliberate difference from the on-screen long-press shortcuts: ⌘+C with nothing selected does nothing, rather than selecting the whole field first. That's what every other keyboard on a Mac does, and quietly copying a half-written message is a poor surprise. ## Switching language Typing / Physical keyboard / Ctrl+Space changes the language A keyboard with its own language key (a globe key, or the 한/영 key on a Korean keyboard) can always switch. Press it and the keyboard moves to the next enabled language, flashing a small list so you can see where you landed. With [🌐 goes back to the last language](https://wmkeyboard.pages.dev/languages/switching/#going-back-to-the-last-language) on, it goes back to the language you used last instead, and quick presses go further back. That needs no setting and can't collide with anything, since apps have no use for the key. A few keyboards wire 한/영 as a second right Alt instead, which the keyboard can't tell apart from the real one. On those, use Ctrl+Space below. For every other keyboard there's **Ctrl+Space changes the language**, on by default. Ctrl+Space works the way Alt+Tab does for windows. Keep Ctrl held and each press of Space steps through the list on screen. Add Shift to step backward. Release Ctrl and you switch to whatever is highlighted. A quick tap-and-release is a one-step switch. Esc cancels, and the rows in the list can be tapped too. One thing to know: some apps want Ctrl+Space for themselves, code editors especially, where it shows completions. While you're in a text field the keyboard takes it first. If that bites you, this is the toggle to turn off, and the dedicated language key and the spacebar keep working either way. ⌘+Space is untouched regardless, since it belongs to the system's own keyboard switcher. Whether you see the list depends on **Show the keyboard for shortcuts** below. With that off the switch still happens, and you find out from the spacebar label the next time the keys are up. Some phones never hand Ctrl+Space to the keyboard at all. Huawei's tablets keep it for their own keyboard, so on a MatePad it does nothing here. For those there's **Ctrl+Shift changes the language**, off by default. Press Ctrl and Shift together, with no other key, and let go: the keyboard moves to the next language, as on Windows, and flashes the list. A shortcut such as Ctrl+Shift+Z is left alone, because another key came between. ## Driving a tool with the keyboard Typing / Physical keyboard / Arrow keys move a highlight typing/hardware-focus-ring — The focus ring highlighting a result inside an open panel after an arrow-key press. Once a tool is open, **Arrow keys move a highlight** (on by default) lets you drive it without touching the screen. The arrow keys move a highlighted outline over the panel's results, chips or search box. Enter activates whatever's highlighted. Tab jumps between the panel's sections (search, category chips, then results) once the highlight exists. Home and End jump to a section's first and last item, and PageUp/PageDown move four rows at a time through a long grid. The highlight is a filled, outlined ring in your theme's accent color, and it only ever appears after you press a key. It never shows up while you're using the keyboard by touch. This covers nearly every tool, searchable or not: the AI panel's actions and Replace/Insert buttons, translate's language picker, media controls' transport, the password generator's options, a running plugin's buttons and text boxes, the calendar's month arrows and day grid, the sound & haptics switches and style chips, the weather refresh, the voice tool's mic. The camera and scanner tools join in once they're showing a captured photo or a scanned result. The live viewfinder itself stays touch-only. The grammar strip is navigable too, with one difference. You're usually mid-sentence while it's open, so the arrows keep moving your cursor. The highlight appears only when you open the tool from the shortcut key, and Enter on an issue applies its top suggestion. A few tools work by typing rather than highlighting. The calculator and the unit and currency converters take digits and operators straight from the physical keys (type `2*3` and the display shows `2×3`), and in the calculator Enter is the = key. The text-editing and numpad panels stay touch-only on purpose, since a physical keyboard already does everything they offer. The compass, level and moon phase have no buttons at all, and the handwriting canvas is for fingers and pens. Typing / Physical keyboard / Esc closes the tool **Esc closes the tool** (on by default) makes Esc shut whatever panel or picker the keyboard has open. It only consumes the key when the keyboard actually has something open. With nothing open, Esc goes straight to the app, so it still stops a page loading or backs out of insert mode in an editor. ## Picking a suggestion from the keyboard Typing / Physical keyboard / Number keys select suggestions The suggestion strip can't be tapped while you're typing on a physical keyboard, so **Number keys select suggestions** gives you a keystroke instead. It has three modes: - **Off.** Number keys always type digits. - **Alt + number**, the default. Alt+1 through Alt+9 commit directly, with no leader key first. Browsers and chat apps sometimes claim Alt+number for tab switching, so this is the one to change if that bites. - **After the shortcut key.** Double-tap the shortcut key, then press 1 through 9. Nothing else uses it, at the cost of one extra keystroke. In this mode the bare numbers belong to the strip, so the toolbar's tools rely on the Ctrl+number chord above. In practice the strip is what sets the ceiling, not the digits. It draws between two and six chips (Typing / Suggestions, **Number of suggestions**, three out of the box), so the numbers only ever run as far as the words you can actually see. Typing / Physical keyboard / Show the suggestion numbers In **Alt + number** mode the strip is the one surface that wears its badges all the time: a small `Alt+1`, `Alt+2`, `Alt+3` under the words, for as long as a physical keyboard is attached. That's on by default, and you can turn it off if you'd rather have the clean strip. **After the shortcut key** has no equivalent, because a bare 1 does nothing until the leader is pressed, so its numbers only appear once it is. The numbers count slots, left to right, not the engine's ranking. That matters if you use Typing / Suggestions / Best suggestion in the middle, which draws the runner-up first: the strip still reads 1, 2, 3 across, and Alt+1 commits the leftmost word you can see rather than the one the engine ranked first. ### Pick mode Typing / Physical keyboard / Pick mode key Pick mode drops the modifier altogether. Choose a **Pick mode key**, press it once, and the bare number row picks suggestions: 1 takes the leftmost word, 2 the next, and 0 the tenth. To type a number while pick mode is on, hold Shift with it. Shift+Alt with a number types the symbol printed on that key, the one plain Shift would normally give you, read from your keyboard's own layout. Press the pick mode key again to turn the mode off. A short message says which way it went, and the strip numbers its words with bare digits for as long as the mode is on. The key can be any chord with Ctrl, Alt, Shift or Meta, a double-tapped modifier, or F1 to F12 or Insert on its own. It can't be the shortcut key that opens the tools. There's no pick mode key until you set one. Since a physical keyboard normally hides the on-screen keyboard, the suggestion strip comes up on its own while pick mode is on, without the keys. A number whose slot is empty types itself, so a PIN or phone-number field, which has no suggestions at all, still takes digits. Holding a number down picks once rather than once per repeat. Pick mode stays on as you move between text fields and switches off when the keyboard restarts. Typing / Physical keyboard / F-keys press the toolbar **F-keys press the toolbar** gives the function row the job of the Ctrl+number chord: F1 opens all tools and F2 to F10 press the toolbar buttons from the left. On **In pick mode**, the default, the F-keys stay with the app until pick mode is on. **Always** keeps them all the time, which gets in the way of terminals and code editors. **Off** never takes them. While the F-keys work, the toolbar badges read `F1`, `F2` and so on. ### Chinese and Japanese candidates Typing Chinese or Japanese, the candidate strip comes up on its own while a reading is waiting to convert, with the on-screen keys still hidden, and the keys work it the way a desktop input method does. The arrow keys move a highlight along the candidates, Space or Enter types the highlighted one, and once one is highlighted 1 to 9 pick by position and Esc goes back to the reading. To have the space bar step through the candidates rather than type the first one, turn on **Space steps through candidates** for that language. See [picking with space, the arrows and Enter](https://wmkeyboard.pages.dev/languages/cjk/#picking-with-space-the-arrows-and-enter). ## Options Typing / Physical keyboard | Setting | Default | | --- | --- | | Process the physical keys | On | | Tool shortcuts | On | | Shortcut key | Double-tap Ctrl | | Ctrl + number opens a toolbar tool | On | | Write the modifier names | On | | Time to press a key | 8.0s | | Arrow keys move a highlight | On | | Esc closes the tool | On | | Number keys select suggestions | Alt + number | | Show the suggestion numbers | On | | Pick mode key | Off | | F-keys press the toolbar | In pick mode | | macOS shortcuts | Off | | Ctrl+Space changes the language | On | | Ctrl+Shift changes the language | Off | | Show the keyboard for shortcuts | On | **Show the keyboard for shortcuts** covers the case where a physical keyboard is attached and there's no reason for the on-screen keyboard to be up. Press a shortcut that opens a tool and the keyboard comes up anyway, because the tool needs somewhere to draw. It hides again once you close the tool. A related setting lives elsewhere. Appearance / Toolbar / Only the toolbar with a hardware keyboard hides the key grid entirely, leaving just the suggestion strip and toolbar, whenever a physical keyboard is attached. You're typing on the physical keys anyway. It's off by default. ### Show the keys again With only the toolbar showing, a keyboard button sits at the far end of the toolbar. Tap it and the on-screen keys come back, for when the physical keyboard is folded away or out of reach. Tap it again (it now shows a hide-keyboard icon) and they go away. On the physical keyboard, the shortcut key and then Space does the same thing. You don't have to put the keys away yourself. The next key you type on the physical keyboard hides them again, the way Gboard does. Shortcuts, arrow keys and modifiers don't count, only keys that type. If you'd rather keep the keys up until you tap the button, turn off Appearance / Toolbar / Hide the keys when I type on the hardware keyboard. The keyboard remembers whether the keys were up, so the next text box opens the way you left the last one. The button lives on the toolbar, so while suggestions fill the strip, tap the arrow to bring the toolbar back first. ## Details and edge cases - **The tool picker waits eight seconds for a key** before it disarms itself. Adjustable from one to thirty under **Time to press a key**. - **The badges never resize the keyboard.** They're drawn over the buttons rather than added beside them, so double-tapping Ctrl can't shove the app's text out from under your hands. - **Toolbar badges only show when the toolbar does.** With suggestions occupying the strip instead, Ctrl+number still opens the same tools, with nothing on screen to label them. The cheat sheet lists them either way. - **Reserved chords still work as a shortcut key, with a warning.** Capture a chord like Ctrl+C, Ctrl+V, Ctrl+S or Ctrl+Tab as your shortcut key and the picker warns you it's "usually the app's own shortcut", then lets you use it anyway. It's a nudge, not a block. - **Process the physical keys only governs typed characters.** Turning it off stops physical keys from running through autocorrect and suggestions. Tool shortcuts, panel navigation and Esc keep working regardless, since none of them touch typed text. - **No build differences.** Everything on this page works the same in every build. - **Presence detection is automatic.** The keyboard notices a physical keyboard attaching and detaching through the system's own configuration, which is also what drives the separate "Only the toolbar with a hardware keyboard" setting above. For the complete shortcut and cheat-sheet reference in one table, see [Hardware shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/). For the toolbar setting that hides the key grid when a keyboard is attached, see [Appearance settings](https://wmkeyboard.pages.dev/reference/settings/appearance/). --- # Modes & the symbol row URL: https://wmkeyboard.pages.dev/typing/modes/ Section: Typing Summary: Letters, symbols, numbers: how modes work and how the symbol row is arranged. "Mode" means two different things on this page: the letters/symbols layer under your thumb, and the keyboard-wide profile (theme, tools, symbol row) that switches itself on for a given app or field. Both are covered here, along with the symbol row that sits between them. typing/symbols-page-open — The symbol page open over a message field, with the second symbol page one tap away. ## Switching between letters and symbols Tapping ?123 steps through the symbol pages. ABC always jumps straight back to letters, from any page. The cycle is short and doesn't grow a third stop: - **Letters → Symbols.** ?123 on the letters layout opens the first symbol page (digits, `@`, `#`, currency, brackets, punctuation). - **Symbols → Symbols 2.** The key relabels to {'=\\<'} at 1.5× width. Tapping it opens the second page (math and typographic symbols, a dead-key accent key, more punctuation). - **Symbols 2 → Symbols.** The same corner now reads ?123 again and goes back one page. There's no page 3, so it bounces between the two. - **Letters key wins from anywhere**, including from the Fn layer. Pressing ABC while Fn is active leaves Fn entirely and drops any Fn lock, rather than returning to whatever Fn was layered over. Pressing ?123 to leave letters also drops any in-progress handwriting ink on the writing surface, since letters is the only layer that hosts it. The keyboard's height doesn't jump when you cycle: its row count is fixed to the tallest reachable grid (letters, symbols, symbols-2, Fn and the field's own keypad), so a shorter page grows its keys to fill the same height rather than resizing the keyboard. The bottom row (space, enter) keeps its height on every page, so it stays under your thumb; the rows above it share the extra room, the way Gboard's symbol pages do. A layout that sets its own row heights keeps them and pads at the top instead. Key press / Press and hold shortcuts / Press and hold ?123 for the number pad **Press and hold ?123 for the number pad** turns a long press on ?123 into a shortcut straight to the number pad panel, skipping the symbol pages. It's on by default. A quick tap always cycles the layers as described above, whatever this setting says; only the long press changes. If your layout gives ?123 press-and-hold characters of its own, an imported one for example, the hold opens those instead, and the number pad is an entry in that popup. For a key you can see, turn on **1234 key on the symbol pages** under Layout & size / Bottom row keys. It puts a 1234 key after ABC on both symbol pages that opens the same number pad. It's off by default. ### Coming back automatically Layout & size / Symbols layer / Go back to the letters Most trips to ?123 are one character long: a full stop, a question mark, a comma. **Go back to the letters** notices that and returns you to the letter keys as soon as you type one of them, so you don't press ABC yourself every time. It's off by default, and the characters that trigger it are yours to edit (they start as `!` `?` `.` `,` `;` `:`) in **Characters that go back** just below the toggle. **Go back after a space**, in the same group, does the same for the spacebar: type "5", press space, and you're on the letters for "pm". It works with or without the character list, and it's off by default so a phone number typed with spaces stays on ?123. Digits stay off the list on purpose, because typing "12" is exactly when you want the symbols to stay. Only single-character keys count either way, so a `.com` key won't send you back, and both symbol pages behave the same. Full detail is in the [Layout & size reference](https://wmkeyboard.pages.dev/reference/settings/layout/#symbols-layer). The emoji panel has had the same setting for a while, under Emoji → Return to the keyboard after you insert. ## The symbol row Rows & bars / Symbol row typing/symbol-row-picker — The symbol row above the key grid, with its set picker open. The **symbol row** is a strip of one-tap characters and snippets that sits above the key grid, independent of the ?123 pages below it. It's for the character you reach for constantly without wanting to leave the letters layout. It's off by default. Five built-in sets ship with it, and every one is enabled by default: **Email** (`@`, common domain endings), **Web** (`https://`, `www.`, path characters), **Coding** (brackets, operators, `->`, `=>`, a tab key), **Math** (±, ×, ÷, fractions, Greek letters) and **Punctuation** (the set the row opens on by default). A chip at the row's left edge opens a dropdown of whichever sets you've enabled, but only when more than one is enabled. With just one, there's nothing to switch between. Rows & bars / Symbol row / Symbol sets Enable or disable sets with the checkboxes in **Symbol sets**. At least one has to stay checked. Tap a set's edit icon to rename it or rewrite its characters, entering them space-separated so a snippet like `->` counts as one entry the same way a single character does. Editing a built-in set doesn't touch the shipped version: your edit is stored as an override under the same ID, so anything that references that set (a keyboard mode's `symbolSetIds` included) keeps working unchanged. Delete your override and the original characters come back, which is why the icon reads Reset rather than Delete on a built-in. **New symbol set** creates one from scratch. ### Press and hold Any entry of a set can carry a popup of its own. Open the set's editor, and under **Press and hold** tap the entry's chip, then type the characters and snippets it should offer, space-separated the same way. A tap on the entry still types it; holding it opens the popup, and the popup behaves exactly like a key's alternates: the same column count, font size and padding from Key press settings, and the same hold-to-select, so you can slide onto an entry and let go. The first entry in the box is what a plain hold and release types. An entry with a popup shows its first alternate in the corner, the way a key does, under the same **Key hints** switch. ### Holding an entry Hold an entry that has no popup of its own and a menu opens, the same way holding a word on the suggestion strip does. What it offers depends on the set and on the mode you're in: - **Remove "https://"** (or whichever entry you held) takes that entry out of its set. On a built-in set the change is saved as your edit of that set, so **Reset set** in the set's editor brings the entry back. A set's last entry stays. - **Hide the row in Browser** (or whichever mode is on) shows while a keyboard mode turns the row on or picks its own sets. It sets that mode's **Symbol row** to Off, so the row stays away whenever the mode is active. The global row and every other mode are untouched, and the mode keeps its set list for when you turn the row back on. - **Delete the "*name*" set** shows on a set you made yourself. It asks first. A mode that offered only that set shows your global sets instead. The five built-in sets can't be deleted: remove entries from them, hide the row, or uncheck the set on the Symbol row page. - **Symbol row settings** opens the settings behind what the row is showing. When a mode is why the row is there (the row is off globally, or the mode picks its own sets), that's the mode's editor, scrolled to its symbol row settings. Otherwise it's the Symbol row page. An entry that has a popup keeps its popup on a hold. Hold another entry of the same set to reach the menu. Advanced / Keyboard modes / Browser / Custom symbol sets ### More than one line Rows & bars / Lines of symbols **Lines of symbols** stacks the row up to four lines high, each one as tall as **Symbol row height**, so more of a set is on screen without scrolling. Entries are dealt down the lines in turn: the first entry on the top line, the second under it, and so on. With two or more lines a **Scrolling** choice appears. **Together** moves the lines as one grid, so a scroll on any line moves all of them. **Each line alone** lets every line scroll on its own. The two look the same until a line is actually scrolled, and the picker chip stays at the left edge whatever the count. The row hides itself automatically whenever the special-characters panel or another full-bleed panel is open. A second row of symbols on top of a symbols panel would just be noise. ## Row order Rows & bars / Row order Six rows can stack above the key grid: the **emoji row**, the **tools row**, the **toolbar**, the **symbol row**, the **dictionary bar** and the **fancy text styles** row. By default they run top to bottom as emoji, tools row, toolbar, symbol row, dictionary bar, fancy text styles, with emoji on top because it's reached for most often and the style strip at the bottom, next to the keys whose letters it changes. Drag the rows into any order in **Row order**. A hidden row (symbol row off, emoji row set to off or button-only, fancy text styles while the fancy keyboard isn't the one typing, the tools row while the tools share the strip) keeps its slot empty rather than collapsing the stack, and turning it back on doesn't change where it lands. The tools row is the one **Where the tools go** adds under its **A button** and **Always** choices. Where you put it decides which way the strip's arrow points: up for a row above the strip, down for one below. The **Fancy text** toggle on the same screen turns the fancy keyboard on and off, the same as the [Fancy text tool](https://wmkeyboard.pages.dev/languages/fancy-text/#the-fancy-text-tool) on the toolbar, which is when the styles row appears. This is a global setting. A keyboard mode (below) can turn the symbol row on or off and change which sets it offers, but it can't reorder the rows. Row order is the same everywhere. When a mode draws the emoji row or the symbol row differently from its global setting, the row's status line in **Row order** says so ("Changed by 4 modes"), and the row's own setting lists the modes under it: under **Symbol row** on Rows & bars and on the Symbol row page, and under **Emoji row** on the Emoji screen. Each mode is a chip with the value it sets, like "Email: On" or "Passwords: Off". Tap one to open that mode's editor. So turning the symbol row off globally tells you straight away that Email, Browser, Writing and Coding still turn it on in their fields. A mode that sets the row to the same value as the global setting isn't listed, and nothing is listed while **Use modes** is off or for a mode switched off by itself. ## Keyboard modes typing/modes-tool-panel — The Modes tool: None and Automatic at the top, then one row per mode with its auto-activation summary. Beyond the global settings above, WM Keyboard can carry a different profile into different apps and fields, with its own theme, its own pinned toolbar tools and its own symbol row setup, switching automatically as you move between them. Advanced / Keyboard modes Each mode has an off switch of its own, at the end of its row on the Keyboard modes screen and at the top of its editor as **Use this mode**. Off, the mode never activates and the Modes tool doesn't list it, but nothing inside it is lost: it's the way to silence the Passwords mode on every password box, say, without deleting it. The whole feature has a switch too. **Use modes**, at the top of the Keyboard modes screen, is off by default. While it's off the keyboard keeps one setup everywhere, the Modes tool stays off the toolbar and the toolbox, and nothing switches itself as you move between apps. Your modes are kept and the list stays editable, so turning it on restores all of them. It's also offered as a card on every path through [setup](https://wmkeyboard.pages.dev/start/setup/), because a keyboard that quietly looks different in the next app reads as a keyboard that lost your settings if you didn't know it was going to. A mode can override the emoji row's presentation, whether the symbol row shows and which sets it offers, the theme, the toolbar's pinned tools and toolbox order, the **layout** it types on, and six typing switches: **autocorrect**, **automatic capitals**, **suggestions**, **automatic spaces**, **glide typing** and **two-thumb typing**. Anything left unset inherits the global value. **Automatic spaces** is one switch over every space the keyboard types without you pressing for it: after punctuation, after a suggestion you pick from the strip, and before and after a word you glide. Turn it off in a mode and every space in those fields is one you typed, so two glided words in a row run together. That's the point in a file manager renaming an extension, in a terminal, or anywhere a stray space is a mistake rather than a courtesy. Turn it on and all three are on, including the space after punctuation, which is off by default everywhere else. **Glide typing** and **two-thumb typing** decide whether swipes type anything while the mode is on. With glide off, a tap that drifts across the keys stays a tap instead of starting a swipe. With two-thumb typing off, a pause no longer ends a tapped word with a space you didn't press. Pick a mode that turns either off by hand in the middle of a two-thumb word and that word is typed as it stands first. No built-in mode does this, but a **peck mode** takes one minute to set up: a new mode with glide typing, autocorrect, suggestions and automatic spaces all off. Every tap then types exactly its key, which suits codes, handles, slang and mixed-language text. Bind it to the apps or field hints where you want it, or leave it unbound and pick it from the Modes tool when you need it. A mode's layout is a hard override in the same sense its theme is. The switch ring is untouched, and your own layout comes back when the mode ends. Switch that layout off later and the mode falls back to inheriting whichever layout you're on. Six modes ship by default: | Mode | Changes | Activates on | | --- | --- | --- | | Passwords | Emoji off, symbol row off, toolbar replaced with password generator, clipboard, settings | Password fields | | Email | Symbol row on (Email, Punctuation sets) | Email fields | | Browser | Symbol row on (Web set) | URL fields | | Chat | Emoji row always on, emoji/GIF/sticker/voice appended to the toolbar | A text field in any of 15 messaging and social apps (WhatsApp, Messenger, Instagram, Telegram, Signal, Discord, Facebook, Facebook Lite, TikTok, Snapchat, Viber, LINE, KakaoTalk, WeChat, Beeper), plus any notification reply box | | Writing | Toolbar replaced with AI, text editing, voice, clipboard, emoji; symbol row on (Punctuation) | Keep, Docs, Samsung Notes, Word, OneNote, Obsidian, Notion, Evernote, Joplin (plus mail app bodies), and any text field | | Coding | Symbol row on (Coding, Math sets) | Termux, Acode, RK Editor, Spck | Passwords and Writing replace the toolbar outright instead of adding to it. Nothing that opens a network-facing panel belongs next to a password box, and Writing's own pins want the bar to themselves, so the global Settings shortcut doesn't ride along into a writing session. Chat appends its tools to your existing pins instead. A tool a mode pins is switched on for as long as that mode is active, even if you'd turned it off globally, because a mode that pinned nothing you could see would be pinning nothing at all. That reach stops at the mode's own list: a tool you've switched off in **Tools** stays off under every mode that doesn't name it. Switching a tool off leaves it in its toolbar slot so re-enabling puts it back where it was, and a mode inheriting your toolbar inherits that slot without reading it as a pin of its own. If an existing install already had its own mode list saved before Chat and Writing shipped, both are appended automatically on upgrade. A mode you've since deleted stays deleted and isn't re-added. A later round topped up the Chat mode the same way, adding the nine apps it didn't have yet (Facebook, Facebook Lite, TikTok, Snapchat, Viber, LINE, KakaoTalk, WeChat, Beeper) and the notification-reply field type to a stored copy. An app you'd already bound to another mode is left where it is. ### Editing a mode Tap a mode in **Keyboard modes** to open its editor, which is headed with that mode's own name and glyph, both carried up from the row you tapped. It holds a name and an icon, then **What this mode changes**: Emoji row (Inherit/Off/Button/Row), Symbol row (Inherit/On/Off), a theme picker with a Clear button, a **Layout** picker offering the layouts you've switched on, and the **Autocorrect**, **Automatic capitals**, **Suggestions**, **Automatic spaces**, **Glide typing** and **Two-thumb typing** switches, each inheriting until you set it. Further toggles give the mode its own pinned toolbar tools, toolbox order and symbol sets, each with its own reorderable chip list. Below that, **Turn on automatically for** holds the field-type chips, the field-hint list and the app picker that decide when the mode kicks in. Edits save immediately as you make them, so there's no save button, though a brand-new mode isn't written to disk until its first edit. Only the six built-in modes have anything to reset to. A mode you create from scratch has no default to fall back on. A built-in mode you deleted comes back from **Restore built-in modes**, a row that shows at the end of the list only while one of the six is missing. If the saved mode list can't be read back, for instance after installing an older version over a newer one, the keyboard drops only the modes it can't read and keeps the rest. If none of them can be read, it shows the six built-in modes instead of an empty list. ## How a mode gets picked typing/modes-manual-picker — The Modes tool open, showing the manual override list. WM Keyboard re-checks which mode should be active every time you focus a field. A field can match more than one kind at once, since a password box always adds **Password** on top of whatever else it reports. The built-in field-type categories used here (Password, Email, URL, Number, Phone, Text, Notification replies, Multi-line text, Single-line text, No suggestions) are deliberately narrower than the field-kind detection covered in [The keyboard adapts to the field](https://wmkeyboard.pages.dev/typing/field-adaptation/). **Text** in particular means plain free text only. A password field also reports as text-capable at the OS level, but it's explicitly excluded from the Text category, so a mode bound to Text (a chat composer, a document body) can never take over a login form. **Multi-line text**, **Single-line text** and **No suggestions** describe the *shape* of a text box rather than what goes in it, and a field reports one of them on top of its kind. **Multi-line text** is a box that takes line breaks (an editor, a note body); **Single-line text** is every one-line box (a rename dialog, a title, a search field); **No suggestions** is a field that asked the keyboard to keep its word suggestions to itself, which is what most code editors do. They exist for apps whose fields all look alike to the categories above. A file manager's rename box, text editor and code editor are all plain text as far as Android says, but only the editors are multi-line, and only the code editor turns suggestions off. Password boxes never report a shape, for the same reason they're excluded from Text. **Send key**, **Search key**, **Go key** and **Done key** match on what the field says its Enter key does, which tells a chat box from a search box when both are plain text. Apps fill this in unevenly, so check it on the app you have in mind. **Field hints** is a list of words the field's placeholder (the grey text an empty box shows) must contain, case ignored. It's the one thing an app writes on a field that says what the field is *for*: a rename dialog might say "Enter name" on one tab and "Extension" on another while its field type stays the same, so a mode bound to the hint "extension" with autocorrect and automatic spaces off catches exactly that box. The catch is that hints are the app's own wording in the app's own language, so a hint you add while the app is in English stays silent once the app runs in French. A mode's **apps** list, **field-type** chips and **field hints** combine as an AND when more than one is set: a mode bound to an app, a field type and a hint needs all three to match. Set only one and that alone is enough. Set none and the mode never activates on its own. It shows as "Manual only, from the Modes tool" in its settings-list subtitle and is only reachable by picking it yourself. **Notification replies** is the exception to that AND. The inline reply box in the notification shade belongs to the system UI, so the package the keyboard sees there is the shell's rather than the app you're answering, and an app binding could never match it. A mode carrying that chip matches the reply box on its own, apps ignored (a hint binding still has to hold). That's why the shipped Chat mode carries it alongside Text. **Not in these apps** keeps a mode off in the apps you name, whatever else matches, so a mode can cover every text box but one app's. When more than one mode's bindings match, the highest **Priority** wins. Among equals, the one that names a field hint wins over one that names a field type, which wins over one that only names apps. So a password box inside a coding app still gets the Passwords mode rather than Coding, and the "Extension" box of a rename dialog gets the mode written for it rather than the one for every single-line field. Beyond that, the first match in your mode list wins. **Any browser**, **Any terminal**, **Any messenger or social app** and **Any mail app** count as apps in the list, so a mode can follow a kind of app. The keyboard has a list of each and also asks Android, which finds a browser or a social app installed later. Android has no category for terminals or mail apps, so those two only know the apps on the list. **Screen direction**, **Hardware keyboard** and **Time of day** are further conditions that all have to hold. Each can also make a mode on its own, with no app or field named: a mode set to 22:00 to 07:00 that silences the keys and dims the theme needs nothing else. A time across midnight works. The direction and the keyboard apply as soon as they change, and the time is checked each time you tap a text box, so a mode doesn't switch in the middle of a field. **Screen size**, **One-handed mode** and **Bluetooth audio** work the same way. A foldable counts as a tablet while it is open and as a phone while it is shut. **Bluetooth device names** narrows that to one device, so a mode can follow the car and not the earbuds. It only sees audio devices, by the name Android reports for them. Android doesn't tell a keyboard whether the app is in split screen or a floating window, so there's no condition for those. **Field names** matches what an app calls the field, or the field's id number, and **Field languages** matches the languages it asks for. Few apps fill these in, so check on the app you have in mind. **Text in the field** and **Text starts with** read the field as you type, so a mode can switch on at the `/` of a chat command and off again when you delete it. These two look up to 64 characters back from the caret. **Incognito** turns incognito on for as long as the mode is active, for a work app or a browser on a shared phone. It can't turn off incognito you switched on yourself. **Key sound** and **Key vibration** set those two for the mode. **Enter types a line break** makes Enter a line break even where the app labels it Done or Send, with the app's own action left on the key's hold. **Number row**, **Key height**, **Key preview**, **Long-press delay**, **Copied text chip** and **Key swipes** each set that one thing for the mode. A screen shape with a size of its own keeps it, whatever **Key height** says. **Languages** narrows your enabled languages by code and can't add one, and **Voice typing languages** sets what dictation listens for. **Use one-handed mode** picks a side or turns it off. **Snippet triggers** set to off stops every kind of trigger expanding in the mode, for a terminal where a trigger is a real word; the Snippets tool still inserts them. You can also override the automatic pick from the keyboard itself. Open the **Modes** tool, from the toolbar or the toolbox, for a list starting with **None**, then **Automatic** and its "Follow the app and field bindings of each mode" subtitle, then one row per mode. **None** switches the current mode off, including one an app or field binding turned on, so the keyboard runs on your global settings alone. Tapping a mode pins it by hand. How long that pin (or **None**) lasts is **A mode you pick by hand**, under Keyboard modes: **Until you change app**, the default, or **Until you change it**, which keeps the mode on until you pick another or turn it off. A mode can also be one press away. In the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), set a key, a flick or a long-press alternate to **Toggle a keyboard mode** and pick the mode. The key shows the mode's name, and keeps showing it as you rename the mode, unless you type a label of your own; a key whose mode you delete shows ⊘ and does nothing until you pick another. Pressing it picks that mode exactly as the Modes tool would, and pressing it again goes back to **Automatic**. A peck mode on a flick of the `?123` key, say, is there for one URL and gone after it. A mode that is on because its apps or fields chose it has no "off" for the key to go back to, so the key leaves it on, and the next field still follows its own rules. ## Rearranging tools inside a mode Advanced / Keyboard modes / Rearrange tools When a mode prescribes its own pinned toolbar tools or toolbox order, a tool you drag on the keyboard while that mode is active needs somewhere to go. **Drags edit the active mode**, on by default, sends that drag into the mode itself. The rearrangement then sticks for apps that resolve to that mode, and every other app keeps its own arrangement. The first time this happens you'll see a one-off toast naming the mode. For the Chat mode it reads "The keyboard saved the tool order only for the Chat mode. Other apps keep their own order." Turn the toggle off and drags always edit the global tool order instead. If the active mode still prescribes its own list, though, that list keeps winning on screen whatever you just dragged, because the mode's fixed list is applied on every recomposition. Turning this off doesn't make a mode's tool order editable from the keyboard. It only stops on-keyboard drags from targeting modes at all. A mode with no pinned tools or toolbox order of its own is unaffected either way. There's nothing for it to own, so drags always go to the global order. ## Options These settings live on four separate screens. Key press | Setting | Default | | --- | --- | | Press and hold ?123 for the number pad | On | Layout & size | Setting | Default | | --- | --- | | Go back to the letters | Off | | Characters that go back | `!?.,;:` | | Go back after a space | Off | Rows & bars | Setting | Default | | --- | --- | | Symbol row | Off | | Fancy text | Off | | Enabled symbol sets | All 5 built-ins | | Dictionary bar | Off | | Row order (emoji / tools / toolbar / symbol / dictionary bar / fancy text styles) | Emoji, Tools row, Toolbar, Symbol, Dictionary bar, Fancy text styles | Advanced / Keyboard modes | Setting | Default | | --- | --- | | Use modes | On | | A mode you pick by hand (until you change app, until you change it, or remembered for each app) | Until you change app | | Keyboard modes list | 6 seeded modes | | Drags edit the active mode | On | ## Details and edge cases - **A custom or hand-edited layout can't get stuck on a symbol page.** If a symbols/symbols-2/Fn layer in a layout file has no key that leads back to letters or symbols, WM Keyboard silently appends an ABC key to it so the layer always has a way out. - **Picking a set from the row's chip usually becomes the new default.** It's written back to the same setting the row opens on next time, so there's no separate settings entry for it beyond the enabled-sets list. The one exception: if the active keyboard mode prescribes its own symbol sets, a pick from the chip is session-only. It never overwrites the mode's own list, and it lasts only until the next field, since focusing a field resets the session pick either way. - **A mode's symbol-row override only sets on/off and which sets.** It can't reorder the emoji/toolbar/symbol stack, which stays global no matter which mode is active. - **The manual mode pick is never written to disk.** It lives only in the running keyboard service. On the default **Until you change app** it also clears the instant the focused app's package changes, so closing and reopening the keyboard in the same app keeps it while switching apps doesn't. Set to **Until you change it**, the pick survives an app switch and waits for you to choose another mode. - **No build differences.** Symbol pages, the symbol row and keyboard modes all work the same in every build. For how a field's type is detected in the first place, see [The keyboard adapts to the field](https://wmkeyboard.pages.dev/typing/field-adaptation/). For the full settings screens referenced above, see [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/), [Keyboard modes](https://wmkeyboard.pages.dev/reference/settings/modes/) and [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/). --- # Power saving URL: https://wmkeyboard.pages.dev/typing/power-saving/ Section: Typing Summary: A lighter keyboard when the battery is low: what turns off and when. Power saving drops the parts of WM Keyboard that cost the most battery: vibration, key sounds, animations, the glide trail, background network fetches. It goes on with one tap. It never turns on by itself unless you ask it to follow Android's battery saver or a low battery. It's a view laid over your settings rather than a rewrite of them. Turn it off and everything comes back exactly as you had it, because nothing was ever changed. typing/power-saving-toolbar-toggle — The Power saving tile on the toolbar, lit after a tap. ## Turning it on The quickest way is the **Power saving** toolbar tile. One tap flips it on or off, and a toast confirms which ("Power saving is on", "Power saving is off"). Nothing ever pins it to the toolbar for you, so drag it up from the toolbox yourself, or reach it from **Tools** in settings the way you'd reach any other tool. On a physical keyboard, double-tap Ctrl and press B to do the same without touching the screen. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) for the rest of the leader-key shortcuts. However you flip it, the manual switch is sticky. It survives the keyboard being torn down and rebuilt between fields, and neither a full battery nor the charger going in turns it back off. Once you've turned it on by hand, it stays on until you turn it off by hand. ## Switching on by itself Tools / Power saving typing/power-saving-settings-triggers — The Power saving group: the switch, the automatic trigger picker, and the battery slider. Beyond the manual switch, **Turn on by itself** decides whether power saving also arms automatically: - **Only when I turn it on**, the default. The manual switch is the only way in, so the keyboard never changes how it feels on its own. - **Android's battery saver is on.** Power saving follows the system's own Battery Saver toggle in lockstep. - **The battery is low.** It arms once the battery drops to or below **Low battery is**, a slider from 5% to 50% (default 20%). The slider only appears on this trigger and the next. - **Battery saver or a low battery.** Either condition arms it. Two things temper the automatic triggers. **Off while charging** (on by default) suppresses them entirely while the charger is in, since there's nothing to save with power flowing in, and a phone sitting at 100% on the charger counts as charging too. An unread battery level, before the keyboard has taken a reading, is always treated as "not low", so it can never falsely trigger on startup. Neither of these touches the manual switch: turning it on by hand means it, charger or no charger. This is a one-way follow. Turning on the keyboard's own power saving never touches Android's system Battery Saver setting: only the reverse. The keyboard watches a handful of coarse system broadcasts (battery low, battery okay, charger connected and disconnected, battery-saver mode changed) rather than the noisy per-percent battery broadcast. Toggling the charger or the system's battery saver mid-sentence therefore takes effect immediately. The exact battery percentage is a different matter: it's re-read only each time the keyboard comes back on screen. On **The battery is low** trigger, crossing your threshold while the keyboard is already open doesn't switch power saving on until you switch fields or reopen it. ## What gets dropped typing/power-saving-what-to-drop — The What to drop group: fifteen independent switches. Power saving is fifteen independent switches plus one drop with no switch of its own, and a **What to drop** row above them that sets the fifteen as a bundle: **Light** (vibration, sounds, animations, moving themes, the glide trail, glass blur and the background watchers), **Balanced** (Light plus background fetches and the on-device models; the shipped default) or **Aggressive** (Balanced plus glide typing, emoji suggestions, smart chips, the floating button and loaded models). Change any switch by hand and the row reads **Custom**. Each switch defaults to whatever gives up the least for the most battery back. | Switch | Default | What it drops | |---|---|---| | Key vibration | On | The vibration motor on every keystroke (the single biggest per-key power draw). | | Key sounds | On | The key click sound. See [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/). | | Animations | On | Every transition and motion effect (the same flag [Reduce motion](https://wmkeyboard.pages.dev/accessibility/overview/#reducing-motion) uses), so it can end up on even if you never touched that toggle. | | Glide trail | On | Only the comet trail drawn behind a swipe. The swipe itself keeps decoding: see [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/). | | Glass blur | On | A [glass theme](https://wmkeyboard.pages.dev/themes/glass/)'s blur of the app behind the keyboard and under its popups, which the system redraws every frame. The glass keeps its rim, grain and wallpaper or image backdrop, and the board stands on that backdrop instead. | | Moving themes | On | The parts of a theme that move: an animated background image shows as a still picture, a [moving background](https://wmkeyboard.pages.dev/themes/editor/#moving-background) holds a single frame, and key lighting, the glow under your finger and press particles go. Colours, pictures and key finishes stay. Separate from Animations, so panels can keep their transitions while the theme stands still. | | Glide typing | Off | Swipe-to-type entirely. Left off by default: decoding a swipe is the most expensive thing the keyboard does, but it's also why many people installed it, so only the trail is dropped unless you opt into this too. | | Emoji suggestions | Off | Scanning what you type for emoji to suggest. Off by default: it's a dictionary look-up on a word you've already typed, and a strip that stops offering emoji reads as broken rather than as saving power. | | Smart chips | Off | The calc, currency, unit and keyword chip family: see [Smart chips](https://wmkeyboard.pages.dev/smart/chips/). Off by default for the same reason as emoji suggestions: a few pattern matches on the current word aren't where a battery goes. Clipboard's own entity detection (pulling codes, numbers and links out of what you copy) isn't affected either way. | | Background network | On | Unrequested fetches only: link previews for copied URLs and scanned QR codes, and the dictionary's look-up-on-selection. Tools you open by hand (translate, search, GIFs) still work. | | Screenshot watching | On | Watching the screenshot folder for new images to offer in the clipboard. | | On-device models | On | Whisper dictation falls back to the system recognizer, and the letter-swipe action falls back to gesture typing if it was set to handwriting. See [Offline voice](https://wmkeyboard.pages.dev/tools/whisper/) and [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/). | | Media tool pin | On | Putting [Media controls](https://wmkeyboard.pages.dev/tools/media/) on the toolbar by itself while music plays. The pin itself costs nothing to draw, but keeping it right means watching the media session for as long as the keyboard is up. The tool stays reachable from the toolbox. | | Floating button | Off | Hides the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/), a small window Android draws over every app. It comes back where you left it when saving ends. Off by default because some people open the keyboard with it; with this off the button stays, and Animations stops its quick menu from animating. | | Loaded models | Off | Waiting 2 minutes after the keyboard hides before it unloads its models (the language model, voice typing, the translator, text scanning and the rest). With this on, it unloads them after 15 seconds instead. Off by default because a loaded model costs memory more than battery, and loading it again for the next field is what uses battery. [Resources](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-keyboard-holds-in-memory) shows what is loaded and unloads any of it by hand. | Power saving also always turns off [Words on the keys](https://wmkeyboard.pages.dev/typing/words-on-the-keys/), the predicted words that float over the keys. Unlike the switches above, this one has no settings row of its own. Whenever power saving is in force, the floating words are off, full stop. A few things are deliberately left alone. The compass and the bubble level read their sensors only while their panel is open, so they stop the moment you close them. The clipboard listener is what fills your clipboard history, so stopping it would lose what you copy rather than save anything. To see where the keyboard's memory and processor time actually go, open [Resources](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-keyboard-holds-in-memory) in About. Power saving only does anything *useful* if at least one of these is on. Turn every switch off and the tile's toast admits it plainly: "Power saving is on. No feature is set to stop yet." If what you're protecting is a data allowance rather than a battery, [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is the same idea pointed at the network, with a per-feature ask-first answer power saving has no use for. Both can be on at once, and either one holding a fetch back is enough. ## Options Tools / Power saving The **Power saving** group holds the controls above: | Setting | Default | |---|---| | Power saving now | Off | | What to drop | Balanced | | Turn on by itself | Only when I turn it on | | Low battery is | 20% (5%–50% range) | | Off while charging | On | Below it the fifteen switches sit in three cards, shown at the Everything settings depth: **Effects to drop** (Key vibration, Key sounds, Animations, Glide trail, Glass blur, Moving themes), **Typing helpers to drop** (Glide typing, Emoji suggestions, Smart chips) and **Background work to drop** (Background network, Screenshot watching, On-device models, Media tool pin, Floating button, Loaded models). The key popup and the typing statistics are no longer droppable: the popup is cheap to draw and the counters are a few integer bumps per keystroke, so neither saved anything you could measure. ## Details & edge cases - **Nothing is ever overwritten.** The reduced settings are computed on the way out, the moment power saving applies. The stored values never change, so turning it off, the charger going in, or the battery recovering all bring every feature back exactly as you set it. - **Turning off the manual switch doesn't always turn saving off.** If an automatic trigger, low battery say, is still holding it on, the toast reads "Power saving stays on. The battery trigger is on." instead of "Power saving is off". - **The toolbar tile lights up while power saving is actually in force**, the way the Incognito tile does. It follows the fully resolved state rather than just the manual switch, so it's lit while an automatic trigger is holding things on too. - **Never pinned for you.** Power saving is in none of the shipped toolbars, phone or tablet, so it's always yours to pin. Whether it arrives *switched on* depends on setup: the wizard's "Show me everything" answer turns it on, "A good middle" and "Keep it simple" don't, and it's also topped up into a shorter set when something else in that set can't run on your device (the F-Droid build's missing handwriting and text scanning, or a phone with no Google services). - **Works before you unlock your device once after a restart**, and behaves identically in every build. Related: [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/) for haptics and key-click tuning on their own, [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) for how the trail and the decoder split apart, [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) for Reduce motion outside of power saving, and [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the fuller picture of what WM Keyboard can and can't send. --- # Size, position & one-handed use URL: https://wmkeyboard.pages.dev/typing/size-position/ Section: Typing Summary: Resize, float, and reposition the keyboard, per orientation and fold state. Every height, width and gap on WM Keyboard is a slider rather than a fixed number. Shrink the keyboard toward one thumb, split it down the middle for a wide screen, or detach it into a floating panel. Sizing you set for portrait carries to the unfolded shapes by default, landscape starts from its own shorter board, and all three can override any of it, right down to scaling the whole keyboard at once. typing/size-position-settings-overview — The Size & position group: key height, key spacing, width and position sliders. ## Sizing the keys Layout & size / Size and position / Resize by dragging **Resize by dragging**, the first row of the group, skips the sliders altogether. Its **Resize** button opens the test field with the keyboard under it already in [resize mode](https://wmkeyboard.pages.dev/tools/resize/): drag the top edge for height, the middle to raise it off the bottom, the sides for margins, and press **Done**. It needs the keyboard test button, so the row is hidden while that button is switched off. Layout & size / Size and position / Key height **Key height** sets the height of every key row and, with it, the overall input height. It's a slider from 32dp to 100dp, default 48dp. The emoji, clipboard and snippet panels scale with this value too, so a taller keyboard means taller panels. **Bottom row height** gives the spacebar and Enter row its own height, independent of the letter rows: a slider from 0dp to 96dp. At 0, the default, it shows "Follow keys" and matches the letter rows. Raise it for an easier-to-hit spacebar without growing the whole keyboard. A custom layout that sets its own row heights ignores this slider. Layout & size / Size and position / Toolbar height **Toolbar height** sizes the row above the keys, the toolbar and the suggestion strip it shares, the way the sliders above size the key rows: 24dp to 80dp, default 44dp. Lower it to give the app more of the screen, raise it for tools that are easier to hit. A theme that sets its own toolbar height pins it, and the slider says so. On a phone turned sideways the keyboard uses 70% of this number unless landscape has a toolbar height of its own (see [what landscape starts at](#what-landscape-starts-at)). **Key spacing** adjusts the gap around every key, from 0% to 200%, default 100%. Higher spreads the keys apart, and makes the keyboard a little taller, since the gap is part of each row. Lower packs them tighter. Layout & size / Size and position / Extend edge keys into the gaps **Extend edge keys into the gaps** is on by default. A row shorter than the keyboard, like QWERTY's A to L row, leaves blank space at both ends. With this on, a tap in that space types the key at that end of the row: left of **A** types A, right of **L** types L. The bottom row reaches down the same way, over the padding under it, so a tap just below the spacebar types a space, and one below **Enter** or **?123** presses that key. It stops where the navigation bar starts, so a swipe up from the screen's edge types nothing on its way to the home screen. It follows whatever bottom row the layer has, custom layouts included, unless you have moved a bar row below the keys. The keys stay the size and in the place they are drawn, only the area you can tap grows. Turn it off to make taps in the gap do nothing. While Layout & size / Number row / Number row is on (its own group at the top of this same screen, above Size & position), it has its own **Number row height** slider too: 32dp to 100dp, default 42dp, independent of the letter keys. The size of the labels printed on the keys is a separate slider, **Key label font size**, under Appearance / Key label font size: 0.7× to 2.0×, default 1.0×. It isn't part of this settings group, but the [per-screen sizing](#sizing-each-screen-shape) below can still override it per screen shape. ## Narrowing and repositioning it Layout & size / Size and position / Keyboard width **Keyboard width** shrinks the keyboard horizontally instead of spanning the full screen, from 50% to 100%, default 100%. Below 100%, a **Keyboard position** choice appears (Left, Centre, or Right, with Centre the default) to pick which edge the narrowed keyboard hugs. This is a permanent dock. The slider's own info text calls [one-handed mode](#one-handed-mode) "a quick preset" that "replaces this value while it is on", and it leaves the stored width and position alone. **Left padding** and **Right padding** narrow the keys toward the middle for thumb reach, without docking to one side the way one-handed mode does. Each is a slider from 0% to 30%, default 0%, and both stack on top of keyboard width: the keyboard ends up at width × (1 − left − right) of the screen. Two sliders rather than one so the margins don't have to match. Set them equal for an even margin; set only the left one and the keys slide toward your right thumb without the keyboard docking to that edge. Layout & size / Size and position / Full-width toolbar **Full-width toolbar** splits the two apart, the way Samsung Keyboard does on a tablet: the toolbar, the suggestion strip and any open panel keep the whole width of the screen, and only the key rows take the narrower width and padding. It appears once the keys are narrower than the screen, through **Keyboard width** or either padding, and is off by default, so the whole keyboard narrows together. A tablet held sideways is where it usually earns its place, so you'd typically turn it on for **Landscape, unfolded** alone under [per-screen sizing](#sizing-each-screen-shape). It doesn't apply in one-handed mode, to the floating keyboard, or to the television card, which size the whole board themselves. Each padding is claimed at its own edge before **Keyboard position** gets a say, so a margin you asked for is there whichever edge the keyboard hugs. Position then distributes whatever space the width slider left over on top of it. On Centre, equal paddings give exactly the even margin they always did. **Bottom padding** raises the whole keyboard away from the bottom edge and the gesture navigation bar, from 0dp to 160dp. Until you move it, the keyboard picks the amount itself: 32dp on Android 15 and up with gesture navigation, where the keyboard reaches down to the thin gesture handle, and 8dp with three-button navigation or on older versions, where the bar below is already tall enough to keep your thumb off the edge. The padding is counted from the top of the navigation bar, which the keyboard always keeps clear of on its own, so 0dp puts the space bar right on the bar, never under it or a row above it. Raise it if the bottom row feels cramped against the screen edge, or if you keep triggering system navigation by mistake. The slider runs so high because the inline **Resize** tool moves the same value, and that can push the keyboard a long way up the screen. **Hide the system keyboard bar** removes the bar Android draws under the keyboard with gesture navigation, the one with a chevron that hides the keyboard and a button that switches to another keyboard. The keys then move down into the space the bar took, and automatic **Bottom padding** drops to the small 8 dp. If they still sit higher than you like, lower **Bottom padding**. The keyboard already has both buttons' jobs covered: Back hides it, and the 🌐 key switches keyboards. It needs Android 16 or later, the first version that lets a keyboard ask for this; on older versions the switch is greyed out. It also needs gesture navigation: with three buttons, or on a phone whose system draws the bar into its own navigation bar, no keyboard can remove it, and the switch is greyed out there too. Some phones, such as a Moto G86 Power on Android 16, report the bar gone when the keyboard asks and keep drawing it anyway; since the bar sits in the keyboard's own window, the keyboard then takes it away itself. It is off by default. **Round top corners** and **Round bottom corners** cut rounded corners out of the docked keyboard, from square (the default) up to 40dp, and the app shows through the cut. The top and bottom are set apart, and **Which corners**, shown once either one is round, picks which of the four corners they apply to. All four by default, so rounding only the top two means leaving the bottom at Square. Round bottom corners suit gesture navigation and a phone whose screen corners are round too. With the older three-button navigation bar, the bar sits below the keyboard and the bottom corners open onto the app above it. The floating keyboard and the television card have round corners of their own and ignore these. ## Sizing each screen shape Layout & size / Size and position / Per screen typing/size-position-per-screen — Per-screen sizing: Landscape expanded, showing the Keyboard scale slider. Portrait is the base size: everything in the two sections above. Three other screen shapes can each override any of it, namely **Landscape**, **Portrait, unfolded** and **Landscape, unfolded**. Whatever you leave untouched on an override follows portrait, so tuning one shape never means setting up the rest. Landscape is the one exception, and it starts shorter rather than following portrait. A phone turned sideways keeps its width but has less than half the height, so a portrait-sized board would take the whole screen. See [what landscape starts at](#what-landscape-starts-at) below. "Unfolded" isn't read from a fold sensor. It's decided by the screen's smallest dimension being at least 600dp, checked live as the configuration changes. That deliberately reads a phone turned sideways as still "not unfolded", since it's wide but still a phone. A large screen counts as "unfolded" however it's held, whether that's an opened foldable's inner display or a tablet that never folded at all. There's no separate "folded" state in the code. Portrait's values are what a folded or ordinary phone always used, which is why the settings screen's own caption calls portrait your "folded" size. Tap a shape's row to expand its editor: - **Keyboard scale**, a whole-keyboard multiplier from 50% to 150%. It exists only on these three override shapes; portrait has no base scale of its own. It multiplies key height and number row height together, so a foldable's roomy inner display can run a smaller keyboard than its cover screen, or the reverse, without re-dialing each height by hand. - **Key height**, **Number row height** (if the number row is on), **Bottom padding**, **Keyboard width**, **Toolbar height**, **Font size**, **Key spacing**, **Left padding**, **Right padding**, **Bottom row height**, and, when width is under 100%, **Keyboard position**. Same ranges as their portrait counterparts above. - **Full-width toolbar**, shown once this shape's keys are narrower than the screen. Turn it on here and not for portrait to get the tablet arrangement only on the shape that has the room for it. - **Number row**, the one override that isn't a number at all. Landscape is what it's there for: it has the least room for a sixth row and the most need for the keys underneath it, which is why sideways starts without it unless you've set the digit row by hand. - **One-handed mode on this screen** and **Split keyboard on this screen**, which say whether this shape shows those modes at all. They default to the shared **Portrait only** and **Split only on wide screens** rules, and setting either one here wins over that rule for this shape alone. - **Follow portrait again**, which appears only once a shape has at least one override set. It clears every override for that shape in one tap. On landscape that returns it to the landscape defaults below, not to your portrait numbers. Everything in [one-handed, split and floating](#one-handed-mode) below is set apart from these shapes. One-handed tuning and floating go by orientation only, portrait or landscape, and split is one switch for every shape. ## What landscape starts at A phone turned sideways has roughly 360dp of height where portrait has 750dp. Left to follow portrait, the board came out taller than the window it had to fit in, and the bottom rows (the shift row and the spacebar) were drawn past the bottom of the screen and cut off. So landscape starts from its own numbers instead, measured against what Gboard does on the same screen: | | Portrait | Landscape | | --- | --- | --- | | Key height | 48dp | 33dp | | Number row height | 42dp | 29dp | | Number row | On | Off, unless you set it yourself | | Toolbar height and tool width | 44dp / 38dp | 70% of whatever you've set, unless landscape has a toolbar height of its own | That lands the whole board at a little over half the screen, leaving the app you're typing into the rest. The digits aren't lost with the row switched off: they stay as the small hint labels on the top letter row, and a long press still types them. These are defaults, not limits. Anything you set for landscape in the per-screen editor above wins over all of them, and a theme that authors its own key height wins over them too. Turning the number row on by hand anywhere keeps it on sideways as well. The toolbar is scaled rather than replaced, so a height and tool width you picked survive the rotation in proportion. Without it the tool circles stayed at their portrait size and ended up drawn larger than the keys underneath them. A **Toolbar height** set for landscape in the per-screen editor replaces the scaled height outright. A tablet in landscape isn't affected by any of this. "Unfolded" is decided by the screen's smallest dimension, so a tablet on its side is still at least 600dp tall and has the height to spare. The tools can also leave the top of the keyboard while it's sideways, a tablet's included. **Tools in landscape** under [Toolbar](https://wmkeyboard.pages.dev/reference/settings/appearance/#toolbar) stands them in a column down the left or right edge, beside the suggestions and the keys. Tools that had a row of their own give that row's height back to the keys. ## On a tablet A tablet gets a wider keyboard automatically, with nothing to set up. The screen's smallest dimension decides which one you get, the same measurement per-screen sizing uses: - under 600dp is a phone, and nothing below applies; - 600dp to 767dp is a small tablet, which is also where an unfolded foldable lands; - 768dp and up is a large tablet. On either tablet size the letter rows gain a column on each side. That fills the width with keys instead of stretching the letters across it, and leaves the alphabet sitting in the middle where two hands reach it: - Tab and \\ flank the top letter row; - Caps and Enter flank the home row, so Enter is under your right little finger rather than at the bottom corner; - the number row gains a ` and takes over Backspace, which frees the shift row's right end for a second Shift; - the comma moves up beside the letters, and a real emoji key appears next to ?123; - ← and → join the bottom row. A large tablet goes further. The arrows sit right next to the spacebar, a second ?123 lands at the right end so you don't have to reach back across ten inches for it, and the period joins the comma up on the letter rows. A small tablet keeps the period on the bottom row beside the spacebar, with the arrows after it, because a 7-inch tablet is still held and still thumb-typed. Two settings also start from different defaults on a tablet, and only until you touch them: - key height starts at 44dp (large) or 46dp (small) instead of 48dp, and number row height at 38dp or 40dp instead of 42dp, so six rows on a tablet take a smaller share of the screen than five do on a phone; - the toolbar starts with seven pinned tools (large) or five (small) instead of three. Change either of them and your value sticks on every device, tablet included. To get Samsung Keyboard's tablet look, with the toolbar and suggestions running the whole width over a narrower block of keys, narrow the keys under **Landscape, unfolded** (or **Portrait, unfolded**) in [per-screen sizing](#sizing-each-screen-shape) and turn on **Full-width toolbar** there. The [Resize tool](https://wmkeyboard.pages.dev/tools/resize/) does the same in place: drag the side handles in, then tap its **Full-width toolbar** chip. The Layout & size / Number row / Number row isn't one of them any more. It's on by default everywhere now, phones included, so there's nothing left for a tablet to override. The extra keys are added to whatever layout you're using, so every shipped language gets them, and so does anything you build in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/). Layouts that aren't ordinary alphabetic grids are left alone: the Japanese flick pads, braille, morse, the Chinese shape and phonetic pads, and a handful of scripts whose grids have no shift key. If you'd rather a particular layout was left alone too, the editor has an **Add tablet keys automatically** switch for it. ## One-handed mode Layout & size / One-handed, split and floating / One-handed mode **One-handed mode** shrinks the keyboard toward one edge for reach with a single thumb: Off (the default), Left, or Right. Unlike the per-shape sizing above, it's tuned separately per orientation, Portrait and Landscape, rather than per fold state. A one-handed keyboard cares about which way you're holding the phone, not whether it's unfolded. Each orientation gets its own three controls: - **Width** is the keyboard's share of the screen while one-handed is active, 40% to 85%. The default is 78% in portrait and 55% in landscape. Landscape starts narrower because a landscape keyboard is already very wide, so 78% there would barely help. - **Height** scales the key height down for reach, 60% to 100%, default 100% in both orientations. - **Side** is which edge it docks to, Left or Right, default Right in both orientations. **Portrait only**, off by default and shown while the mode is on, holds one-handed mode back when the phone is sideways. The mode is one setting for both orientations, so a keyboard made small for a thumb in portrait stayed small after a turn, where the width is usually wanted for a [split keyboard](#splitting-the-keyboard-in-half) instead. With it on, the keyboard is one-handed upright and whole in landscape, and comes back as soon as you turn the phone back. The One-handed tool on the toolbar says so if you press it sideways. typing/size-position-one-handed — One-handed mode docked to the right edge, with the flip and exit rail. On the keyboard itself, a narrow rail fills the space one-handed mode leaves behind. It carries two buttons: an arrow that flips the keyboard to the other edge (live, and remembered as that orientation's new default side) and a fullscreen icon that exits one-handed mode entirely. Pin the **One-handed** tool to your toolbar and tapping it toggles one-handed on using that orientation's preferred side, or off if it's already on. It has no default letter binding on a physical keyboard, but the leader key still reaches it. Bind it a letter in **Tool shortcuts list**, or press T for the toolbox if you've pinned it there. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/). ## Splitting the keyboard in half Layout & size / One-handed, split and floating / Split keyboard **Split keyboard** divides every row down the middle with a gap between the halves, so your thumbs travel less on a wide screen. It's most useful on tablets, foldables, and phones turned to landscape. It's off by default, and turning it on reveals two more controls. **Split gap** runs from 5% to 60% of the keyboard's width, default 12%; a bigger gap pushes the halves further toward the edges but narrows each key. By default the spacebar splits between the two halves along with everything else. Turn off **Split the spacebar** to keep it as one key that runs across the gap, so both thumbs land on it where they meet; the rest of the bottom row stays in its halves. Like one-handed mode, split is global rather than tied to a screen shape. **Split only on wide screens** is the second one, off by default. Split is a single global switch, so turning it on in landscape leaves the keyboard split after you rotate back, where half a phone-width keyboard is close to unusable. Turn this on and the split only happens in landscape or on an unfolded screen. Portrait on an ordinary phone stays whole. Both of these are shared rules. For a finer answer, each shape under [per-screen sizing](#sizing-each-screen-shape) has its own **One-handed mode on this screen** and **Split keyboard on this screen** toggles. On a foldable, turning split off for **Portrait, unfolded** means the opened screen held upright gets one-handed mode alone instead of a one-handed split keyboard, while both landscape shapes stay split. typing/size-position-split — Split keyboard: each row divided into left and right halves with a center gap. ## Floating the keyboard Layout & size / One-handed, split and floating / Floating keyboard typing/size-position-floating — The floating panel's handle bar: dock button, drag pill, and resize grip. **Floating keyboard** detaches the keyboard from the bottom of the screen into a small, movable panel that hovers over whatever app is open. It's off by default. Portrait and landscape each remember their own answer, the way Gboard does. **Floating keyboard** is the portrait switch and **Floating in landscape** the sideways one, so you can float the keyboard sideways and keep it docked upright: turn the phone and it changes on its own. The **Floating** tool on the toolbar and the panel's dock button change only the orientation the phone is held in. Until you change either one, landscape follows the portrait switch, so a keyboard that already floated before this setting existed still floats both ways. Sideways on a phone, Android normally swaps the app for a large text box with a **Done** button while you type, since the keyboard leaves the app so little room. Turn off **Full-screen typing in landscape** to keep the app in view above the keyboard instead, the way it is in portrait. It's on by default, which is what Android does on its own. Some apps turn that text box off themselves, and tablets and unfolded phones never get it. With either one on, two sliders appear: **Floating keyboard width**, 240dp to 500dp, default 320dp, and **Floating keyboard height**, 60% to 160% of the keyboard's normal height, default 100%. The resize grip on the panel drives the same two values, and shares the 240dp floor and the 60–160% height range. It doesn't share the width ceiling: see the edge case below. A **Reset the floating panel** row appears below the sliders once you've moved or resized the panel, and puts its size and position back. **Handle bar position** puts the handle bar **Under the keys**, the default, where your thumb already is, or **Above the keys**. The handle bar carries three controls, left to right: - **Dock button**, a fullscreen icon that returns the keyboard to its normal, docked position. - **Drag pill**. Dragging anywhere across the middle of the handle bar moves the panel, not just the small pill drawn there. Its position is saved as a fraction of the free space around it rather than as a pixel coordinate, so it survives rotation. By default that's centered horizontally and pinned to the bottom of the available space. The panel stays where you let go of it. - **Resize grip**, drawn as two short diagonal lines rather than a diagonal-arrows icon, deliberately, so it doesn't read as another fullscreen button. Dragging it resizes width and height together, and width tracks your finger directly. With the bar under the keys, drag down to make the panel taller and up to shrink it. With the bar above the keys it runs the other way: drag up to grow the panel and down to shrink it. The panel's top-left corner stays put while you resize, so the panel grows and shrinks from there instead of wandering across the screen. Making the panel narrower than its default 320dp, or shorter than 100%, shrinks everything in it, not only the keys: the letters and symbols on them, their corner hints and flick hints, key icons such as the arrows, the toolbar's tools and the toolbox. It follows whichever of the two you squeezed more, down to 60%. A panel wider or taller than the default keeps them at their normal size. Floating mode replaces the normal layout logic rather than combining with it. While it's on: - The one-handed rail and edge-docking don't render. Your one-handed setting is untouched but has nothing to apply to. - Keyboard width, keyboard position and bottom padding from the sections above don't apply either. The panel's size and position come entirely from its own width slider, drag position and resize grip. - **Split keyboard still works while floating.** The two features aren't mutually exclusive, since split rendering happens inside the same keyboard body the floating panel wraps. Touches outside the panel pass straight through to the app underneath, and the keyboard forces full-screen (extract) mode off for as long as it's floating. The panel also keeps clear of the navigation bar, so a handle bar parked at the bottom of the screen never sits on the gesture strip. Pin the **Floating** tool to your toolbar and tapping it toggles floating on or off for the orientation you're in. ## Keeping the keyboard on screen Layout & size / One-handed, split and floating / Keep the keyboard on screen **Keep the keyboard on screen** stops the keyboard from closing when the text field goes away. Off by default. With it on, the keyboard stays up after a field loses focus, over screens that have no text field at all, and across app switches, until you close it yourself. It works the same docked or floating: the switch decides whether the keyboard stays, not where it sits. The point of it is apps that read keys without a text field. A notes app that asks you to press a hotkey combination, a terminal, a game with keyboard controls: normally the keyboard vanishes the moment you leave the text box, so there's no way to send Ctrl+Shift+P from the screen. With this on, the keyboard stays and every key, modifier chords included, reaches the app as a key event. Letters and backspace arrive as key presses too, so plain typing into a fieldless screen works. Closing it is deliberate rather than automatic. The **Hide keyboard** tool, a swipe down on the toolbar, and the Back button all put it away, and it stays away until you next tap a text field, at which point it comes up and sticks again. The keyboard never follows you onto the lock screen. An app that forces the keyboard closed every time it appears gets a few tries and then wins, so a badly behaved app can't leave the keyboard flickering. If the keyboard is put back more than six times in five seconds across any mix of fields, it stops coming back until you next open it yourself, which breaks the loops some web pages and spelling popups set off. Back hides the keyboard before it reaches the app, as it does with this setting off, so a browser's address bar takes a second Back to leave. Before version 0.6.2, Android 14 and newer sent Back past this rule, and the keyboard came straight back up instead of going away. Pin the **Keep on screen** tool to your toolbar to toggle this directly. That tool and this row write the same setting. ## Opening the keyboard without a text field Advanced / Floating button / Show the floating button Keeping the keyboard on screen only helps once it's open. To open it on a screen that has no text field to tap (a game, a terminal, a remote-desktop app, your home screen), turn on the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/). Off by default. A small round button floats over every app. With its tap left at **Show keyboard**, a tap opens the keyboard right there, field or not, and it stays open on that app's screens until you close it. Keys reach the app as key presses, the same as with **Keep the keyboard on screen**. Switch to another app and the keyboard goes back to opening for text fields only. While the keyboard is open the button stays, moved up to sit just above the keys, and its glyph changes to a keyboard with a down arrow. Tap it then and the keyboard closes. Back, the **Hide keyboard** tool and a swipe down on the toolbar close it too, and it stays closed until you tap the button or a text field. When the keyboard closes, the button drops back to where you left it. Kept or summoned open over WM Keyboard's own settings, the keyboard brings up the settings' [test field](https://wmkeyboard.pages.dev/reference/settings/#trying-a-change), so you can type straight away and every row stays reachable above the keys. It does this once each time you come to the app: close the field and it stays closed until you leave the app and come back. Opening the settings from the keyboard itself, such as holding a tool, doesn't do this unless the keyboard is kept on screen. The same button can do more than open the keyboard. Its tap, double tap and press and hold can each start voice typing, ask the AI about the screen, open the clipboard or any other tool, and pressing and holding it opens a quick menu of more actions by default. Drag it to move it, and drag it onto the cross at the bottom of the screen to hide it for a while. All of that is on its own page, [Floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/). The button needs Android's **Display over other apps** permission, and it reads nothing from the screen. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). The **Keyboard controls** notification (Settings › Notifications) does the same job from the notification shade, without the permission: tap it, or its **Show** button, and the keyboard opens over the app you were in once the shade closes. On Android 14 and later, where a swipe can clear even a permanent notification, the controls come straight back; turn them off in Settings › Notifications instead. The floating button is the more dependable of the two, because pulling the shade down briefly takes the screen's focus away from the app, and a few apps answer that by closing the keyboard again. Two Quick Settings tiles cover the same ground with no notification and no permission. Add them from the edit (pencil) view of your Quick Settings panel. **Show keyboard** opens the keyboard over the app you were in, field or not, exactly like the shade's **Show** button: the panel closes and the keyboard comes up behind it. On the lock screen it asks you to unlock first, and while another keyboard is the one in use it shows as unavailable. **Keep keyboard on screen** switches [Keep the keyboard on screen](#keeping-the-keyboard-on-screen) on or off, and lights up while it's on. ## Options Layout & size | Setting | Default | | --- | --- | | Key height | 48 dp | | Bottom row height | Follow keys (0) | | Toolbar height | 44 dp | | Left padding | 0% | | Right padding | 0% | | Key spacing | 100% | | Extend edge keys into the gaps | On | | Bottom padding | 32 dp (Android 15+ with gesture navigation) / 8 dp (three-button navigation, or older) | | Round top corners | Square (0 dp) | | Round bottom corners | Square (0 dp) | | Which corners | All four | | Keyboard width | 100% | | Keyboard position | Centre | | Full-width toolbar | Off | | Keyboard scale (per screen shape) | 100% | | Key height (landscape) | 33 dp | | Number row height (landscape) | 29 dp | | Number row (landscape) | Off | | One-handed mode | Off | | One-handed width | 78% portrait / 55% landscape | | One-handed height | 100% | | One-handed side | Right | | Split keyboard | Off | | Split only on wide screens | Off | | Split gap | 12% | | Split the spacebar | On | | Floating keyboard | Off | | Floating keyboard width | 320 dp | | Floating keyboard height | 100% | | Keep the keyboard on screen | Off | Related but not part of this settings screen: **Key label font size** lives under [Appearance settings](https://wmkeyboard.pages.dev/reference/settings/appearance/). The **Number row** toggle itself, as opposed to its height slider above, lives in its own group at the top of this same Layout & size screen. Accessibility settings has its own **Key size and spacing** shortcut that opens this exact screen. See [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) if you got here looking for bigger, easier-to-hit keys rather than a specific size. ## Details & edge cases - **Keyboard scale has no portrait equivalent.** It exists only on the three override screen shapes. Portrait is sized entirely by its own key-height and number-row-height sliders, with no multiplier on top. - **Landscape's defaults ignore your portrait key height, deliberately.** A landscape phone is short whatever you like upright, so carrying a 64dp portrait key across would put the board back through the bottom of the window. The digit row is the one that does carry: it's a row you asked for rather than a size the screen dictates. - **The key preview bubble gives way to the keys.** The empty band the keyboard holds above itself for a top-row preview is taken out of the screen's leftover space, never out of the board. On a screen too short for both, the board wins and the bubble gets whatever is left. - **A camera cutout at the side is board, not a gap.** Turned sideways, a punch-hole camera sits beside the keys. The keyboard's background runs to the screen's edge under it, and only the keys move in, clear of the camera's strip. - **No full-screen editor on a tablet or unfolded foldable.** Android's default gives a landscape keyboard a full-screen text box above it whenever the app doesn't opt out, which on a big screen covers the app for nothing. WM Keyboard keeps that box to phones in landscape, like Gboard does; a floating keyboard never gets it at all. - **"Unfolded" is a screen-size check, not a fold-sensor read.** Any device with a smallest dimension of 600dp or more counts, a tablet that never folds included. The label describes what the screen currently looks like, not the hardware. - **One-handed mode's width setting is a live override, not a rewrite.** Turning it on temporarily overrides the keyboard-width and position settings while active. Turning it off returns to whatever those were set to. - **A very wide screen can let the floating panel's resize grip briefly exceed 500dp mid-drag.** It snaps back to the 500dp maximum once you release it. - **No build differences.** Every setting on this page (sizing, one-handed, split and floating) works identically in every build. For haptics and sound on each keystroke, see [Sound & vibration](https://wmkeyboard.pages.dev/typing/feedback/). For the physical-keyboard leader key that can toggle one-handed, split and floating without touching the screen, see [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/). --- # Android TV and Google TV URL: https://wmkeyboard.pages.dev/typing/television/ Section: Typing Summary: Typing with a remote: the D-pad ring over the keys, and what a television changes on its own. WM Keyboard runs on Android TV and Google TV, and it is driven entirely with the remote. The arrow keys move a ring from key to key, and the centre button types the key the ring is on. Nothing else about the keyboard changes: the same layouts, languages, suggestions and tools are there, on the same engine as on a phone. ## Setting it up 1. Install the app on the TV. It appears in the TV's own launcher, under its own tile, and opens straight into the settings app. 2. Turn the keyboard on in **Settings → System → Keyboard** (the wording differs between TV makers) and select **WM Keyboard** as the current keyboard. 3. Open any text field. A search box is easiest. The board comes up as it does anywhere else; press an arrow key and the ring appears on the spacebar. If you are sideloading rather than installing from a store, the same APK works: there is no separate television build. ## Typing with the remote | Press | What happens | | --- | --- | | Arrows | Move the ring one key | | Left or right at the end of a row | Wrap around to the other end of the same row | | Up from the top row | Reach the suggestions, then the toolbar, then leave the keyboard for the app behind it | | Centre, or Enter | Type the key the ring is on | | Centre, held | Open that key's long-press alternates (or repeat it, for the keys that repeat) | | Centre, in an open popup | Type the highlighted alternate | | Back, in an open popup | Close it without typing | | Back | Close whatever the keyboard has open, then hide the keyboard | Holding an arrow repeats it, so crossing the board is one long press rather than ten short ones. The ring reaches the toolbar and the suggestion strip as well as the keys: ring up off the top row of keys and it lands on the words, then on the toolbar buttons above them. Taking a suggestion is one button press instead of spelling the rest of the word. Everything a key does when it is tapped, it does when the ring types it: ?123 switches layer, the globe switches language, the emoji key opens the emoji picker. Inside a tool the ring becomes the tool's own highlight (the same one a physical keyboard's arrow keys drive), so the emoji grid, the clipboard and the rest are navigable with the remote too. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) for what that highlight can do. ## The board becomes a grid On a television the keyboard you are already on is drawn as a rectangle: every row exactly ten columns, every key one column wide, each key squarely above and below its neighbours. It is the shape every TV keyboard converges on, and the reason is the remote: one press right always travels the same distance, so nothing about the board is a surprise. What it does with your layout: - **The letters keep their rows and their order.** That order is the thing you already know. A row too long for the grid (a Cyrillic board's eleven) is split into equal parts (six and five) rather than spilling one key onto a line of its own. - **Shift and backspace flank the last letter row**, where a phone keyboard already draws them, one column each. - **A short row is filled** with the punctuation your layout was carrying on its bottom row: on QWERTY the full stop finishes the home row and the comma sits beside `m`, exactly as a TV board draws them. If a layout has none to spare, the grid fills from `. , ? ' - /`. - **The bottom row carries caret arrows** (`◀ ▶`) beside the layer switch, the spacebar and Enter. They are not decoration: the remote's own arrows move the ring, so without these there is no way to put the cursor back into the middle of a word. Because it is a transform rather than a layout, you keep your own alphabet: type Russian, Greek, Hindi or Bangla on a set-top box and you get your letters, in your order, on the same grid. Two kinds of board are left exactly as authored: the ambiguous ones (T9 and Compact QWERTY), which are already big-key grids meant to be aimed at, and the pads whose geometry is the feature: any board with [flick keys](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) (the kana pad, the English 3×3 board), braille, morse, the Chinese shape pads. ## What a television changes by itself A TV has no touchscreen, so the features that need a finger are switched off there and stay off however they are set on your phone: - Glide typing, and handwriting on the keys - The split and floating boards, and the one-handed rail - The press-and-hold preview bubble - Key-press vibration (a TV box has nothing to vibrate) Three more are *defaults* rather than overrides, so a number you choose on the TV is kept: - The number row is on, because reaching a digit through a layer switch costs a button press - The keys are slightly shorter than a phone's (40 dp), so six rows do not fill the screen - The board fills half the width and sits in the middle, which keeps it clear of the edges older sets crop, and keeps the keys close enough together that the ring does not have far to travel - The board is drawn as the grid described above Change any of them under Layout & size and your choice sticks. ## The board floats over the app On a television the keyboard is a card rather than a strip across the bottom of the screen: rounded top corners, a soft shadow, your theme painted inside it, and the app still visible on both sides. Make the board wider or narrower under Layout & size and the card follows; at full width it spans the screen the way it does on a phone. The card sits where your alignment setting puts it (the middle, by default), unless the text field asks for a side. Apps built for Google TV can ask for the keyboard to sit on the left or the right, so that a form down one side of the screen stays visible beside it. WM Keyboard follows that request for as long as that field is focused, then goes back to your own setting. If you build TV apps: it is the same option Gboard reads. Set `android:privateImeOptions="horizontalAlignment=right"` (or `left`, or `center`) on the `EditText`. Other entries in the same comma-separated string are left alone. ## Options Typing / Physical keyboard / Arrow keys move over the keys The ring itself. On by default on a television, off by default everywhere else. On a phone or a laptop the arrow keys belong to the text field you are typing into, and a keyboard that took them would break every cursor movement. Turn it on anywhere if you want to drive the on-screen keys from a physical keyboard's arrow keys. Typing / Physical keyboard / Arrow keys move a highlight The highlight *inside* a tool, which is a separate switch and on everywhere. With the ring on and a tool open, this one is what is moving. ## Details & edge cases - **The ring never appears until you press an arrow key.** Until then the board looks exactly as it does on a phone. On a keyboard with an Esc key, Escape puts it away again, which is useful if you switched the ring on beside a physical keyboard and want Enter back. - **Up and down can leave the keyboard; left and right cannot.** Leaving upward, past the suggestions and the toolbar, is how the remote gets back to the app's own controls. Left and right wrap inside the row instead, so spelling a word can never quietly move the app's selection. - **A layer switch keeps the ring where your eye is.** Press ?123 and the ring lands on whichever symbol key is nearest to where it was standing. - **Accents come from a held centre button.** It opens the same popup a long press opens on a phone; the arrows walk the entries and the centre button types the highlighted one. Pressing down out of the bottom row closes the popup without typing, which is the remote's version of sliding your finger back to the key. Keys that repeat while held (backspace, the arrow keys) repeat instead of opening anything, so holding backspace still deletes. - **Sliders in the settings app take left and right only.** Up and down move on to the next setting, the way they do on every other row. Material's own slider answers all four arrows, which on a remote means the first slider you reach keeps the highlight until you press Back; ours hands the vertical pair back to the page. - **Voice typing works**, and is usually the fastest way to enter a long string on a TV: ring up to the toolbar, land on the microphone and press the centre button. A remote with a microphone button of its own does not route it to the keyboard; the system handles that itself. --- # Words on the keys URL: https://wmkeyboard.pages.dev/typing/words-on-the-keys/ Section: Typing Summary: A predicted word floats over the key that would reach it; flick up to write the whole word. WM Keyboard can write a small word above the key that would carry on what you are typing. With `hel` typed, `hello` sits over the L and `help` over the P. Flick up on that key and the whole word is written. The idea is the BlackBerry Z10's, which called it in-letter prediction; the Octopus keyboard later brought it to other phones. Nothing else on WM Keyboard's board tells you *where to go next*. The suggestion strip lists words, but you have to look away from the keys to read it. typing/octopus-keys — Words floating over the keys that reach them: the typed part is faded, the part still to come is in the accent colour. ## Turning it on Typing / Suggestions / Words on the keys It is off by default. The suggestion strip keeps working exactly as before, so this adds to it rather than replacing it. ## What appears, and where A word always sits above the key you would press next to reach it. That one rule covers three cases: - **Completions** carry on the word you are typing. - **Corrections** fix it, and sit over the key where the word stops matching what you typed. They are drawn entirely in the accent colour, so a fix never looks like a continuation. Only the strongest few corrections appear: further down the list the keyboard is guessing at what you meant rather than reading what you wrote. - **Next words** appear after a space, above their first letters, before you have typed anything at all. Any of the three can be switched off on their own. The typed part of a word is faded and the part still to come is in the accent colour, so you can see at a glance how much of it you have already written. ## How many The **How many words** slider runs from 3 to 26, and starts at 6. The words come from the same ranked list the suggestion strip draws from, in the same order, so what the strip offers is what the keys offer: one flick away instead of one look away. The keys see further down the list, because they have room for more words than the strip does. Two words that carry on with the same letter cannot both appear, since a key normally says one word. `the`, `they` and `there` all continue with an `e`, and the list's order decides which one gets it. **Words per key**, below, is the way around that. The number is a cap, not a quota. If only three words are worth showing, three appear and the rest of the board stays bare. Nothing is invented to fill a free key, because the words that would fill it are the ones the ranking already turned down. A word too wide for its key is left out rather than shortened, so a busy board never becomes a row of `…`. ## Words per key The **Words per key** stepper runs from 1 to 3, and starts at 1. One word per key is what both of the keyboards this copies drew, and it is all a phone-sized key has room for. On a tablet the keys are tall enough for more, so a key may carry a stack: the likeliest word sits nearest the key, the next one band further away, and the third beyond that. Floating and lane placements stack upward; inside the key, the stack grows downward so it never covers the key's own letter. `the`, `they` and `there` can then all hang off the `e`. A flick up takes the word nearest the key, the one a single-word board would have shown. A tap takes the word you touch, wherever it sits in the stack. **How many words** still caps the whole board, so a stack of three on one key is three of that total. Where a stack would reach into a word on the row above, the better-ranked word keeps its place and the other is dropped, the same rule as for two words leaning into each other on one row. ## Taking a word Two gestures, each of which can be switched off: - **Flick up** from the key. What counts is the direction, not the speed: up, and fairly straight. - **Tap** the small word itself. One press of ⌫ takes the whole thing back, word and trailing space together. ### With glide typing on A glide also begins by dragging off a key, so the keyboard has to tell the two apart. It does that on direction alone: a stroke that goes up and goes there fairly straight takes the word, and one that leans or curves becomes a glide. A flick that misses types the letter. The consequence: with words on the keys turned on, a glide that begins straight upward off a key that is carrying one is harder to start. That is deliberate. A board with words on its keys is a board whose owner asked for them. **Flick sensitivity** moves the line. Each setting is a cone around straight up plus how straight the stroke has to be. Choose **Strict** if you glide a lot and find your swipes being taken as picks, and **Relaxed** if the words matter more to you than the swipes. ### During a glide A stroke and a still finger want different things from the keys, so **While gliding** answers for the stroke and **What may appear** is left to answer for a word you type by hand. Three choices: - **Nothing** (the default) keeps the board bare until you lift. Mid-swipe a completion cannot be taken and a correction cannot be judged, so the words are movement under a moving finger and little else. This is what most glide typists want, and it is why the setting exists: silencing the stroke used to mean switching **Completions** off, which emptied the keys while tap typing too. - **Other readings** floats the alternatives the decoder is still holding, each above the key that would reach it. Mid-swipe you can see that carrying on to the P gives `help` where lifting now gives `hello`, and steer. Nothing else on the keyboard can show you the *way* to an alternative; the strip and the picker can only list them. - **The next word** offers what would follow the word you are drawing, above the key it starts with. The offer is already there when the swipe ends, so you can flick it without the look and the reach that taking a next word off the strip costs, which is what makes next-word prediction usable at glide speed. The prediction follows the stroke's leading reading, so it changes as that reading changes, and at the lift it stops being a guess. **What may appear** still applies on top: other readings carry on the word being drawn, so they are completions, and a board that forbids completions stays bare under the finger. The same goes for next words. Whichever you choose, the board at the *lift* is unchanged. A finished glide puts the next-word offer on the keys, the same as a word finished by hand. ## Where the words sit Three choices under **Where the words sit**: - **Above the key** (the default) puts each word in the gap over its key, half on and half above it. The keyboard stays the same height. Words on the top row sit a little lower than the rest, wholly on the keyboard, so a tap on one never lands on the suggestion strip instead. - **Own lane** gives every row a strip of its own, so nothing ever overlaps anything. The keyboard grows a little taller. - **Inside the key** writes the word over the letter. Nothing can collide, but the word and the letter share a very small box. ## Word size **Word size** scales how large the floating words are drawn, from 0.6× to 1.6× the corner hint's own size, default 1.0×. A larger word is easier to read but crowds its neighbours sooner. ## The corner hints On most boards the word sits clear of the corner hint and the two do not collide, so **Hide the corner hint** is off by default. Turn it on if your key size or word size brings them close enough to read as one thing; a key showing a word then hides its hint for as long as the word is there, and keys with nothing to say keep theirs. On a phonetic layout such as Avro, the corner hint is the reading of what you have typed rather than a long-press alternate, and it is never hidden. ## What it does not do yet - **Most conversion layouts.** On Pinyin a Han word floats over the key that continues its reading, so 你好 sits over `a` once `nih` is typed. On Jianpin that is the next initial, on Double Pinyin the next key of your scheme, and on the T9 keypad the digit. After a word goes in, what the strip predicts floats over the key that starts its reading, so 世界 sits over `s` after 你好; a pick goes in with no space. With Fuzzy Pinyin on, a word found through a loose spelling floats too: 是 and its longer words count `si` as `shi`. Japanese and the other conversion layouts don't float yet. With English under **Also suggest from**, the strip's English words float on all of those that read the letter keys, behind any Han word on the same key. - **Phonetic layouts.** On Avro and the other transliterating layouts the keyboard's suggestions are readings of what you have already typed rather than words longer than it, so there is no next key to hang anything on. This needs a roman-side word list before it can work, and it is not built yet. - **Long-press characters.** By default a word only appears when a plain press of the key would write its next character. **Long-press characters too** lifts that, at the cost of some keys offering a word a press alone will not finish. On an English QWERTY board this changes nothing you'll see: the characters behind a hold there are digits and accented vowels, and no English word needs one. It's for a word like *café* or *für* on a board that keeps its accents behind a hold. ## With a screen reader Hovering a key speaks its letter and then what it is offering: "L, suggests hello". An **Insert hello** action on the same key writes the word. Activating the key itself still types the letter, as it always does. ## Power saving Power saving turns this off. It predicts and redraws across the whole board on every keystroke, which is the kind of work a low battery wants back, and the suggestion strip is still saying the same thing while it is gone. Unlike power saving's other switches, this one has no toggle of its own: whenever power saving is in force, the floating words stay off. See [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/#what-gets-dropped). --- # Languages overview URL: https://wmkeyboard.pages.dev/languages/overview/ Section: Languages Summary: 1,555 languages, each enabled independently with its own layouts, numerals, and dictionary. WM Keyboard's language catalogue holds 1,555 entries. Alongside the languages you'd expect are InScript variants, minority Cyrillic alphabets, and a dozen constructed languages. Every one you enable becomes a full input mode with its own layout, dictionary, and settings, and you move between them without leaving the keyboard. languages/list-overview — The Languages screen: your enabled languages, plus Add language to search the full catalogue. ## Suggested languages The Add language screen opens with a short **Suggested for you** group, so the language you came for is usually already on screen. Each row says why it's there: "One of the languages on your device", or "Many people type this language in Bangladesh". Tapping one adds it exactly like tapping a search result does. Two things on the device feed that list, in this order: - **Your phone's language list** (Settings → System → Languages), in your own preference order. Anything you've told Android you read is a strong signal that you type it too. - **Your region**, which maps to the languages widely typed there. A Bangladeshi SIM suggests Bangla and Sylheti, an Indian one Hindi, Bangla, Tamil, Telugu, Marathi and so on, and a Bosnian one Bosnian, Croatian and Serbian. Where a language has a [Romanized variant](#adding-a-language), it sits directly under its own language: Banglish under Bangla, Hinglish under Hindi. Wanting one is a good reason to be shown the other. That holds even when the language itself is already on, because somebody typing Bangla is exactly the person who'd want Banglish offered. Four things can say what your region is: the SIM's country, the network's country, your phone's time zone, and last the region attached to your locale. Only the first of them with anything to offer is used. They disagree more often than you'd think. A phone set to English (United Kingdom) sitting in Dhaka carries both BD and GB, and reading both would bury Bangla under Welsh, Irish and Scottish Gaelic. The SIM and the time zone say where the phone actually is, while a locale's region is very often just the ROM it shipped with. Nothing else is read. The keyboard doesn't look at what you've typed, your contacts, your accounts, or which apps you have installed. None of this needs a permission, and no part of it leaves the device. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/) for the full accounting. Suggestions stay suggestions: nothing is enabled until you tap it. A brand-new install is the one exception, since it starts out in the languages your phone is already set to. See [What ships](#what-ships). ## Adding a language languages/add-language-search — Add language: search matches endonym, English name, id, and locale. Tap **Add language** to search the whole registry. The screen that opens leads with the suggestions above the search results. The search box matches on endonym, English name, id, locale tag, and the names and ids of the layouts the language ships, so "german", "Deutsch", "de", "de-DE" and "QWERTZ" all find the same entry. Tap an unadded language and it's enabled immediately on its first layout, then that language's own screen opens so you can pick a different layout or turn on suggestions from another enabled language. If the language has data to download (a word list, emoji keywords, an n-gram pack of word pairs, or any mix of the three) you're asked once, right there, with the rough size for the lot on the dialog. **Download** starts all of them in the background. **Not now** adds the language without them. **Cancel** backs out of the whole thing, for the tap that landed on the wrong row. Nothing is enabled until you answer, so Cancel really does leave your language list as it was. You can start any of them by hand later from the language's own screen. Emoji keywords also have an automatic download of their own, covered in [Emoji search in your language](https://wmkeyboard.pages.dev/emoji/customization/#emoji-search-in-your-language), but the prompt is the one chance to get the word list at the same time. Removing a language asks the mirror question when it has files on disk: delete its downloaded word list and keywords to get the storage back, or keep them for next time. The language goes either way, because the dialog is only about the files. Deleting the keywords also tells the automatic download to leave that language alone, so they don't quietly come back. Language names in the list are shown as `endonym · English`, for example "বাংলা · Bangla" or "Русский · Russian". The native name comes first and the English name after it, so you can find a language whether or not you can already read its script. English itself is just "English", since repeating it would say nothing. Fifteen languages (including Bangla, Hindi, Arabic, and Russian) have a separate Romanized entry in the catalogue: Banglish, Hinglish, Arabizi, Translit, and so on. These type on a plain Latin QWERTY layout and draw suggestions from a transliterated word list. Romi Konkani is one of them but its list is not transliterated: Romi is the Latin spelling Konkani has been written in since the 16th century, so its words come from text people wrote that way. Each one is its own selectable language rather than a mode switch on the native-script one. Phonetic typing is different again: Bangla's Avro, Hindi phonetic and Urdu phonetic take Latin letters and commit native script, and so does Bangla's Khipro, which composes the script from lowercase keys by fixed rules instead of by sound. They're covered on [Bangla](https://wmkeyboard.pages.dev/languages/bengali/), [Hindi](https://wmkeyboard.pages.dev/languages/hindi/) and [Urdu](https://wmkeyboard.pages.dev/languages/urdu/). Marathi, Gujarati, Punjabi, Odia, Assamese, Tamil, Telugu, Kannada, Malayalam, Persian and Arabic have phonetic layouts too, covered on [Phonetic typing in more languages](https://wmkeyboard.pages.dev/languages/phonetic/). Vietnamese Telex and VNI work the same way inside one script, building the accented letters and tones out of plain keys. See [Vietnamese](https://wmkeyboard.pages.dev/languages/vietnamese/). Burmese types Unicode and can also write Zawgyi, the old font encoding much Burmese text is still in, for the apps and friends that use it. See [Burmese](https://wmkeyboard.pages.dev/languages/burmese/). ## Each language has its own screen Tap any enabled language to reach its screen. **Layouts** and **Dictionaries** are always there. The rest of the groups appear only where they mean something, so most languages show four or five in all: languages/language-detail-screen — A language's own screen: a row of layout cards, numeral system, and other enabled languages to suggest from. - **Layouts**: a row of cards, one per layout the language ships. Each card is the keyboard itself drawn small, with your theme, fonts, key height, number row and toolbar, so you see what typing on that layout looks like before you pick it. The first time a card comes into view it shows a grey outline of keys while its keyboard is drawn, one card at a time, and only once the row stops moving, so scrolling stays smooth. After that the drawing is kept, even across restarts, so the row opens straight onto the finished cards. Change your theme, fonts or key size and the cards keep their old picture until the new one is ready. A long layout name wraps onto a second line under the card rather than being cut off. A tick and a tinted card mark the layouts that are on. Tap a card to turn its layout on or off, and scroll the row sideways for the rest. Where a language has more layouts from the Keyman project, the row ends on a **More layouts** card that opens them, as the same kind of cards, with a search box and the download for each one's typing rules. See [Keyman layouts](https://wmkeyboard.pages.dev/languages/keyman/). A card shows its layout as it looks once it is on, so the spacebar already names it if another layout of the same language is on too. At least one layout has to stay enabled somewhere across your whole set, so tapping the last one says so and leaves it on. - **Use a layout from another language**: under the cards. It lists the layouts of every other language that uses the same letters, with a search box that takes a layout name or a language name, so QWERTZ, AZERTY or Colemak can be the layout you type English on. Pick one and it's copied to a layout of your own in this language, named with the language it came from ("QWERTZ (German)"), and turned on. The copy uses this language's dictionary, autocorrect and spelling rules, sits on the Layouts shelf with the rest, and is yours to edit. Picking the same layout again turns the existing copy back on rather than making a second one. Keyman layouts and phonetic boards aren't offered, because what their keys type depends on their own language. - **Autocorrect**: **Autocorrect this language**, on by default. Turn it off and every word you type in this language stays as typed, while your other languages are still corrected: English fixed, romanized Bangla left alone. The suggestion strip still offers its words to tap. The row is hidden while the main **Autocorrect** switch in Typing is off, because that switch turns it off for every language. It keeps its answer for when that switch comes back on. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/#one-language-at-a-time). - **Spacing**: **Space after a suggestion**, on by default, except for Bangla, where it starts off so a kar picked inside a word doesn't split it. Turn it off and a word you pick from the suggestion strip while typing this language goes in without a space after it, so an ending can follow straight on: pick a Bangla word and add its case marker or suffix without deleting a space first. Your other languages still get the space. The row is hidden while the main **Space after a suggestion** switch in Typing is off, because that switch turns the space off for every language. See [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). - **Glide typing**: **Start error radius**, **End error radius** and **Path error radius** for this language alone, shown at the Advanced depth while glide typing is on and swipes type words. Each starts at the value set in Glide typing's Advanced tuning and follows it until you move it. Once moved, glides in this language use the new value and your other languages keep theirs, so Japanese on Kana JIS can be strict while English stays forgiving. The row's reset makes it follow the global value again. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/#how-it-picks-a-word). - **Numerals**: which digits the number row and keypad show while this language is active. Two options: the language's own digits (the default) or plain `0-9`. This is set per language, so Arabic can show `٠-٩` while English next to it stays on `0-9`. Languages that already use `0-9` don't get the group at all, and no language offers another language's digits, since nobody writes Bangla with Persian numerals. - **Also suggest from**: appears once you have more than one language enabled. Toggle other enabled languages on to blend their words into suggestions while you type this one. Words pulled in this way are never autocorrected away. - **Dictionaries**: the language's word list, emoji keywords and word pairs, one row each. A row on the device has a checkbox and a delete button, a row that is not has a Download button, and a **Download all** button sits in the heading while anything is missing. [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) covers it in full. - For Chinese, Japanese, and Cantonese, a conversion-dictionary group with a larger conversion dictionary plus script and input-scheme options. [Chinese, Japanese & Korean](https://wmkeyboard.pages.dev/languages/cjk/) covers it. Three more groups turn up on the languages that have them: **Clusters** on a conjunct script (see the note below), **Spellings** on Bangla, whose "Use known spellings" switch turns "tmr" into তোমার and "table" into টেবিল, and **Style** on the Fancy Text entry, which is where its 32 styles are listed. A **Remove** button sits at the bottom once you have more than one language enabled. Removing a language turns off every layout that belongs to it. If anything was downloaded for it, a dialog asks whether that goes too, and names the size: its word list and phonetic index, emoji keywords, word pairs, spell checker, Chinese or Japanese dictionaries and Bangla's Avro dictionary. The neural suggestions model is shared, so it's offered only when no language left on still uses it. **Keep** removes the language and leaves the files. ## Switching between enabled languages Once more than one layout is enabled, cycle between them with the 🌐 key, a spacebar swipe, or by holding the spacebar and dragging to the one you want. [Switching languages](https://wmkeyboard.pages.dev/languages/switching/) has the full detail, including the hold-drag picker and how the cycle order is set. That order is its own setting, **Switch order** on the Languages screen, and it reorders every enabled *layout* rather than every language. AZERTY and QWERTY keep separate slots in the cycle even though they're both "English." ## Where languages show up outside the keyboard Two settings on the Languages screen connect your language choices to the rest of Android. Both sit on the language list itself rather than on any per-language screen. **Remember language per app**, under Per-app language, is off by default. Turn it on and the keyboard remembers which layout you last explicitly switched to in each app. Reopening that app's text field restores it, while apps you haven't picked a language in follow your last-used layout globally. A field that requires Latin input (a password box) or that advertises its own language to the keyboard still overrides the memory either way. **List languages in the Android switcher**, under System language switcher, is off by default. Turn it on and every enabled layout is registered as an Android input-method subtype, so the system's "Choose input method" sheet lists each one and can switch directly to it. It starts off because that sheet draws a subtype's label in the same slot where it draws the name of a keyboard with no subtypes, so a single language name sitting there just reads as the keyboard's name. Left off, switching stays entirely inside the keyboard. It still tells Android the language you're typing in, under the keyboard's own name, so a system spell checker set to "use system language" checks that language rather than the phone's. Turn it on and a second toggle appears: **Show app name first**, off by default, which puts "WM Keyboard" ahead of the language in the switcher's label instead of after it. languages/list-per-app-and-switcher — Further down the Languages screen: per-app language memory and the Android system switcher toggle. ## The language the app itself speaks Everything above is about the languages you *type*. The words the app *shows you*, on its settings screens, its panels and its keys, are a separate thing, and they now come in 48 languages besides English. The app follows your phone's language on its own. Set the phone to German and the settings screens are in German the next time you open them. To give the app a language of its own, without changing the phone, open Settings, then Apps, then WM Keyboard, then Language, and pick one there. That entry is an Android 13 feature and does not appear on older releases, which follow the phone. A right-to-left phone language (Arabic, Hebrew, Persian, Urdu) translates the text but does not mirror the keyboard. The keys stay where the layout puts them, so the Arabic board has ض at the left end of its top row and backspace on the right whatever the phone is set to. The suggestion strip still runs right-to-left while you type a right-to-left script. On a right-to-left board the bracket keys draw the glyph your text will show. Right-to-left text flips paired brackets, so the opening bracket reads as `)`, and the key that types it is drawn as `)` too. The same goes for `[ ]`, `{ }`, `< >` and `« »`, in the corner hints and the hold popups as well. The characters typed are the standard ones, so an opening bracket stays an opening bracket when the text is copied anywhere else. The 48 are Arabic, Bengali, Bulgarian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, Estonian, Finnish, French, German, Greek, Gujarati, Hebrew, Hindi, Hungarian, Icelandic, Indonesian, Italian, Japanese, Kannada, Korean, Latvian, Lithuanian, Malayalam, Marathi, Norwegian, Persian, Polish, Portuguese, Punjabi, Romanian, Russian, Slovak, Slovenian, Spanish, Swahili, Swedish, Tamil, Telugu, Thai, Turkish, Ukrainian, Urdu, Vietnamese and Zulu. Google Play translated them, and nobody has read them line by line. Expect a clumsy sentence, and expect a term that does not match the one your phone uses for the same idea. A string that has no translation yet falls back to English, so a screen can show both at once. Corrections are welcome: report the string and the language, and see [Contributing](https://wmkeyboard.pages.dev/development/contributing/) for how translations get regenerated. Bengali (ঃ) and Devanagari (ः) both write a visarga that's drawn as a colon and does a different job. The symbol layers are one grid shared by every language, so there's no key to give it. It rides the long-press of the colon key instead, on every layout in those two scripts. A language on a script that forms conjunct clusters (Bengali, Devanagari, Tamil, Gurmukhi, and several others) gets a **Clusters** group on its own screen. Its **Conjunct-aware backspace** toggle is off by default, and turning it on makes one backspace remove a whole cluster like ক্ষ or क्ष instead of one code point at a time. It's per language, so you can have whole clusters go in one press in Bangla and code points in Hindi. Persian writes می‌رود and کتاب‌ها with a zero-width non-joiner between the parts: the letters stay unjoined but no space is typed. The Persian board has a key for it next to the space bar, drawn as ◌│◌. Its grid follows the standard Iranian arrangement, so چ sits at the end of the third row and ژ is the long-press on ز. A second Persian layout, **فارسی (Gboard)**, lays the keys out the way Gboard does: ژ gets a key of its own beside ز, there is no Shift, and the vowel marks, ؟ and ! are the hold on the full stop. Add it from the language's layout list. You don't have to press it for a word the Persian word list knows. Type or glide میکنم or سگها and the suggestion is می‌کنم or سگ‌ها, and autocorrect puts the half-space in when you press space, the same way it puts back a missing accent in Polish. If yours still suggests میکنم, it was downloaded before the half-spaces were added to the list: download it again from the language's Dictionary screen. Neither script has capitals, so Shift opens a second set of keys instead. On both boards its top row holds the vowel marks: fatha, damma, kasra, the three tanwin, sukun and shadda, then the superscript alef. Persian adds the hamza above and the madda at the end of that row and follows the Iranian standard layout (ISIRI 9147) for the rest of the shifted set, so ؤ ئ أ إ آ ة sit on the home row and « » are on ن and م. ## What ships WM Keyboard ships 3,388 layouts in total, in six groups. On a television every one of them is drawn as an even grid for the remote (see [Android TV and Google TV](https://wmkeyboard.pages.dev/typing/television/)). 36 are built into the app: QWERTY, AZERTY, Dvorak, Colemak, Workman, Halmak, Avro, Khipro Classic, Khipro Dark, Probhat, Jatiya, BSTI, dedicated layouts for French, German, Spanish, Korean, Russian, Arabic, Greek, Hebrew, and Hindi, and the phonetic grids for Hindi, Urdu, Marathi, Gujarati, Punjabi, Odia, Assamese, Tamil, Telugu, Kannada, Malayalam, Persian and Arabic. Two more boards put more than one letter on a key: T9 and Compact QWERTY. See [Ambiguous layouts](https://wmkeyboard.pages.dev/languages/ambiguous/) for how those two work. T9 also ships as a layout file for 315 more languages. Every letter layout carries the same alternates as QWERTY: a digit on each key of the top letter row, 1 to 0, and a symbol on the keys of the two rows below, shown in the key's corner and typed by a press and hold. On Russian, Ukrainian, Greek, Hebrew, Arabic, Korean, Hindi and Bangla that puts the digit ahead of the letters already on the hold, so ё sits on е behind the 5. AZERTY's lower rows differ in a few places for French: c holds `ç` first, d holds `€` before `$`, and French's symbols page has `€` on the currency key. Layouts whose author placed digits somewhere else are left as they were, and so are the [Keyman layouts](https://wmkeyboard.pages.dev/languages/keyman/), the Chinese and Japanese layouts and Vietnamese VNI, where a digit means something else to the keyboard. Layouts you make yourself get only the alternates you give them. 829 more are bundled as individual layout files, one per language or variant that isn't among those 21. English has QWERTZ among them, the German grid with Y and Z swapped, for anyone whose hands learned German keyboards but who types in English. Bosnian and Croatian open on QWERTZ too, the grid keyboards are sold with there, with the plain QWERTY version still on the shelf. Those files include every layout HeliBoard, FlorisBoard and FUTO Keyboard ship that had no match here, so moving over from one of them doesn't mean learning a new grid. Some highlights: - **Hebrew SI-1452**, the 2018 Israeli standard. ץ and ן move to the top left, ת takes ן's old place, and the bottom row ends at צ. The niqqud are on long-press of the keys the standard puts them on. - **Gboard+ for English and Hebrew**, each in two versions. Both carry tool shortcuts and extra characters on long-press. The plain one also puts punctuation on a flick down from each bottom-row letter (z gives `*`, m gives `?`); the swipe-safe one leaves those flicks off, so a glided word can never set one off. The Hebrew pair has no Shift key: each letter's shifted form and its second long-press entry are the English letter on the same key of an SI-1452 desktop keyboard, and on the plain version a flick up from a bottom-row letter types it too, so you can type an English word without switching layouts. - **Russian Student, ЯЖЕРТЫ and ЯВЕРТЫ**, the phonetic grids, plus Bulgarian Dvorak. - **German, Spanish and Swedish Dvorak**, Bone, Colemak-DH (ANSI), ClearFlow and KASROZ. The last two use tall shift, delete and space keys that cover two rows, and keep a 🌐 key in the bottom row, between ?123 and the comma, so you can switch away from them. - **Korean phonetic and Danmoeum** (단모음, where tapping ㅂ ㅈ ㄷ ㄱ ㅅ twice quickly types ㅃ ㅉ ㄸ ㄲ ㅆ, and ㅏ ㅐ ㅓ twice types ㅑ ㅒ ㅕ), Lulua for Arabic, Arabic as Gboard draws it (every letter on a key, no shift key, harakat on long-press of the top row), Akkhor and Unijoy for Bangla, and alphabet-order boards for Kannada, Marathi, Gurmukhi, Gujarati, Malayalam and Tamil. - **"Letters row" and "letters on keys" versions** for languages whose usual grid here keeps some letters behind a long-press: Bashkir, Kazakh, Tatar, Chuvash, Yakut, Tajik, Northern and Inari Sami, Esperanto, Ewe, Hausa, Hungarian, Lithuanian, Bosnian, Croatian, Romanian and more. The original layout is still there. The new one sits next to it. 22 languages came with them, mostly minority languages of Russia and the Nordic countries that these keyboards type and WM Keyboard didn't: Kaitag, Abaza, Aghul, Rutul, Tabasaran, Tsakhur, Forest and Tundra Enets, Nganasan, Nenets, Ingrian, Karelian, Ludian, Votic, Lakota, Pite, Ume, Southern and Lule Sami, and Latin-script Kazakh, Kashmiri and Tamazight. 640 more come from comparing every keyboard in Gboard against this list. A board Gboard has and WM Keyboard didn't is here under its language's name with "(Gboard)" after it: Hindi Flick A, Urdu in alphabet order, Baybayin, a desktop-shaped English board with its number row. Where the two had the same keys, the letters Gboard keeps on press and hold were added to the WM Keyboard layout after what each key already offered, so nothing moved. They brought 316 languages, listed under English names. Boards Gboard fills in after you type a consonant, as it does for Amharic, are fixed here: the forms it would show are on press and hold. 77 are [phonetic layouts](https://wmkeyboard.pages.dev/languages/phonetic/#languages-with-rules-only) for languages that had none, from Maithili and Pashto to Santali in Ol Chiki. Nine language and script pairs came with them. 663 more come from the same comparison against Samsung Keyboard. Samsung's alphabetical and column boards for the Indic languages keep the vowels on one page and the vowel signs and conjuncts on the next, and the key in the corner turns the page. Its QWERTY boards are here for each Latin-script language it offers, as are its versions with ɛ and ɔ on keys, with ñ on a key, and with an apostrophe key. QWERTZ and AZERTY stay only where they are the language's usual board, such as Breton, Occitan and Low German. T9 keypads with Samsung's own letter groups sit alongside the ETSI ones. The rest are its national boards where ours differs, named "(Samsung)". Where a Samsung board matched one of ours, the letters it holds for that language joined the end of our key's long-press, so nothing moved. They bring 286 more languages, nearly all minority languages written in Latin script, and none of them has a word list yet. Its Korean [Moakey, Vega and Cheonjiin Plus](https://wmkeyboard.pages.dev/languages/cjk/) and its 12-key Zhuyin and stroke pads came with typing engines of their own. The remaining 862 are key grids converted from the [Keyman keyboards repository](https://github.com/keymanapp/keyboards), which is where most of the long tail comes from. They're what takes the language count past 800. See [Keyman layouts](https://wmkeyboard.pages.dev/languages/keyman/) for what they are and what they can and can't do yet. Which of them start enabled depends on your phone. A fresh install reads your phone's language list and turns on one layout per language it recognises, up to four, always including English. English is there because it's what fields that force plain ASCII fall back to: passwords, URLs, email addresses. A phone set to Bangla starts with Avro and QWERTY. A phone set only to English starts with QWERTY alone. Your region is *not* used here. Being somewhere is a reason to offer a language, not to switch your keyboard to it. Every other language sits in the catalogue until you add it, loaded on demand, so a language you haven't picked yet costs nothing. ## The long tail Past the languages most keyboards cover, the catalogue reaches into territory most don't: - **Indic InScript** layouts for Marathi, Nepali, Sanskrit, Tamil (Tamil99), Sinhala (Wijesekara), Telugu, Kannada, Malayalam, Gujarati, Punjabi, Odia, Konkani, Maithili, Bodo, Dogri, and Bhojpuri. On Marathi, every key's shifted letter is also its first press-and-hold alternate, so hold क for ख without touching shift. Conjuncts ride along on the hold too: क्ष on क, ज्ञ on ज, त्र on त, श्र on स. - **Minority Cyrillic** alphabets: Tatar, Bashkir, Chuvash, Chechen, Ossetian, Yakut, Udmurt, Komi, Kalmyk, Tuvan, Buryat, Erzya, Mari, Adyghe, Kabardian, Abkhaz, Avar, Lezgian, and more. - **Ethiopic** script for Amharic, Tigrinya, and Tigre; **Syriac**, **Polytonic Greek**, and classical **Sanskrit** and **Latin** for liturgical and historical typing; **Thaana** (Dhivehi), **Tifinagh** (Tamazight, Tachelhit), and **N'Ko**. Tachelhit also has a Latin entry of its own, since most of what is written in it online uses the Latin alphabet. - **Syllabaries**: Cherokee has its own script, and Inuktitut types in Canadian Aboriginal Syllabics. - **Constructed languages** (twelve of them): the auxiliary-language family Esperanto, Interlingua, Ido, Interlingue, Novial, Lingua Franca Nova, Kotava, and Volapük, alongside Lojban, Klingon, Tolkien's Quenya, and Toki Pona. Klingon and Lojban have extras of their own, below. ### Klingon and Lojban In Klingon a capital is a different letter: `q` and `Q` are two consonants, and `D`, `H`, `I` and `S` are only ever written as capitals. Lojban is written in lower case, and a capital there marks stress. So for both languages the keyboard never changes case on its own. Auto-capitalize leaves the start of a sentence alone, a capital is not carried over from the language you switched from, and a suggestion is never re-cased to follow what you typed. - **Layouts.** Klingon's QWERTY types `D`, `H`, `I`, `S` and `Q` without Shift, and puts `ch`, `gh`, `ng` and `tlh` on keys of their own where English has letters Klingon never uses. Hold `q` for `Q` and `t` for `tlh`. Klingon also has a **pIqaD** layout in its own alphabet, with its digits on the top row's holds. pIqaD lives in Unicode's Private Use Area, which no phone font covers, so the first time the layout is shown the keyboard downloads a font for it (Klingon pIqaD HaSta, about 100 KB, SIL Open Font License) and draws the keys in it from then on. pIqaD has its own row at Appearance / Keyboard font / Klingon pIqaD font, apart from English, so you can draw it in any pIqaD font you import without changing the font of your Latin keys. The app you type into needs a pIqaD font of its own to show what you write. - **Word list.** Klingon's downloadable word list keeps every word's capitals, and the strip keeps words apart that differ only in case: typing `qa` offers `qaH` (sir), typing `Qa` offers `QaH` (help). A word typed with the right letters in the wrong case, such as `qapla'`, is fixed to `Qapla'` when you press space. A Klingon list downloaded before this update was all lower case, so download it again to get its capitals. - **Endings.** Once what you have typed is a whole Klingon word, the strip also offers its likeliest endings, so `jatlh` offers `jatlhpu'` and `jatlhbe'`. Whether a word takes noun or verb endings is read from the forms the word list already has. - **Numbers.** Type digits and the strip offers the number in Klingon words: `42` becomes `loSmaH cha'`. - **Glide.** The apostrophe is a letter in both languages, so a swipe draws it through the `'` key without the **Apostrophe in a glide** setting having to name one, and a swipe through the `ch` key writes the `c` of `ch`. The registry also carries a handful of entries that aren't spoken languages at all but behave like one in every setting on this page: IPA, musical notation, chorded Braille, and Morse code, plus a Unicode "fancy text" keyboard with 31 switchable styles. These get their own pages, [Music, braille & Morse](https://wmkeyboard.pages.dev/languages/notation/) and [Fancy text](https://wmkeyboard.pages.dev/languages/fancy-text/), because typing on them works differently enough to need separate walkthroughs. ## Options Languages & layouts Everything on this page is reachable from the app's **Languages** screen, and the exhaustive setting-by-setting reference lives on [Languages](https://wmkeyboard.pages.dev/reference/settings/languages/) in the Settings reference. A few settings that touch languages live elsewhere: - **Emoji keyword downloads** sit under Emoji / Emoji keywords and are covered on [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/). - **Word lists, emoji keywords, word pairs and custom dictionaries** sit under Languages & layouts / (a language) / Dictionaries and are covered on [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/). - **Your own layouts**, including edits of a shipped layout, live under Layout & size / Key layouts and are covered on [Custom layouts & the layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/). ## Details & edge cases - **A language you haven't added yet isn't silently active.** Only enabled layouts are ever offered on the 🌐 cycle or spacebar swipe. Everything else sits in the catalogue, unloaded, until you search for it and tap it. - **Dictionaries aren't tied to whether a language is "complete."** Only English and Bangla bundle a built-in word list. Nearly every other language in the catalogue starts with zero suggestions beyond what you type, until you download or import one. See [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for exactly which languages have which. - **"Also suggest from" only adds words.** It never demotes the language you're actually typing. Suggestions from a secondary language are blended in below your main language's, even when the secondary has a large downloaded dictionary and the main language only its built-in one, and they're exempt from autocorrect, so a word you meant to borrow doesn't get "corrected" back. - **A letter of the alphabet gets a key; an accent gets a hold.** Swedish å, ä and ö, Danish and Greenlandic æ and ø, Norwegian ø and æ, Faroese ð, Estonian õ and ü, Icelandic þ and æ, Turkish ı and ğ, Maltese ġ and ħ: each has its own seat in its alphabet, so each gets its own key, exactly where that language's national keyboard puts it. French é, Spanish á, Polish ą, Czech č and German ä sort with the letter they're built on, so they stay on its long-press, which is also where every phone keyboard keeps them. If a layout is missing a key its national keyboard has, that's a bug worth reporting. - **A sentence mark replaces the full stop on the letters page only.** Japanese `。`, Bengali and Hindi `।`, Armenian `։`, Khmer `។`, Burmese `။` and the rest take the key beside the spacebar, with ASCII `.` moved to the front of its long-press. Every other layer leaves the key as a plain `.` and puts the mark on its long-press instead. That covers the symbols pages, an Fn or Number layer and a secondary grid, because those are where decimals, file names and addresses are typed. In a layout of your own, set a key's **Field adaptation** to "exactly as laid out" to keep a literal full stop anywhere at all. - **A question mark is always one hold away.** On the built-in Latin grids it's the hold on m. AZERTY keeps it on the apostrophe key instead, with / on the m. A layout that has no key like that gets it at the front of the full stop key's long-press instead, so holding the key beside the spacebar and letting go types it. Languages written in Arabic script (Persian, Urdu, Arabic, Pashto, Sorani, Sindhi) and Dhivehi get their own `؟` there rather than `?`, and on those languages every key that would type `?`, the one on the symbols page included, types `؟` instead, with `?` first on its long-press for addresses and code. - **Sorani's four-row and five-row standard layouts and New Qwerty have no Shift.** Letters like ڵ, ڕ and ڤ sit on their own keys or on the hold of their base letter, and the Shift position on the five-row layout types the Tatweel ـ. - **The Arabic vowel marks are on the full stop's hold.** On every Arabic-script layout, the end of the full stop key's long-press carries fatha, damma, kasra, sukun, shadda, the three tanwin, the dagger alef, maddah, hamza above and below, and the subscript alef. Each one is drawn on a dotted circle so you can see it, and typing it puts it over the letter before the cursor. Sorani is the exception: Kurdish isn't written with them, so its full stop key leaves them off. - **Numerals change the glyphs, not the script.** Switching Hindi from `०-९` to `0-9` leaves its dictionary, its layout, and the script its letters use alone. All it changes is which glyphs `0`-`9` show as and, depending on the field, which glyphs get committed. - **Romanized languages widen the catalogue's reach without doubling its content.** Banglish, Hinglish, Arabizi, and the other twelve Romanized entries are dictionary-only variants: same underlying language, plain Latin QWERTY layout, suggestions from a Latin-script list. - **Per-app language memory and the system switcher are independent of each other.** You can have Android list every layout as a subtype while per-app memory stays off, or the reverse. Turning one off doesn't touch the other. ## Every language All {languages.length} registered languages. Filter by name, id or script; click a heading to sort. [l.id, l.name, l.english, l.script])} placeholder="Filter 1,555 languages…" caption="Generated from the app's language registry." /> --- # T9 & Compact QWERTY URL: https://wmkeyboard.pages.dev/languages/ambiguous/ Section: Languages Summary: Layouts that put more than one letter on a key and let the prediction engine work out which one you meant: T9 in 446 languages and Compact QWERTY, with glide typing on both. Most keyboards give every letter its own key. Twenty-six keys across a phone screen means each one is about a fingertip wide, and that's the size everything else has to fit around. Two of the shipped layouts don't do that. **T9** puts the alphabet on eight keys, the way phone keypads did. **Compact QWERTY** keeps QWERTY's exact letter order but pairs the keys up, five to a row instead of ten. Both give you keys two to four times the usual size, and both work out what you meant afterwards rather than asking you to be precise. Neither one starts enabled. Turn them on under Languages & layouts / English, where they sit with English's other layouts alongside Dvorak and Colemak. T9 also comes in 315 more languages, each listed under its own language: see [T9 in other languages](#t9-in-other-languages). languages/ambiguous-t9-field — The T9 keypad: eight letter keys, each carrying three or four letters. ## Why other keyboards can't do this This is the part worth explaining, because it's not a matter of nobody having thought of it. Almost every Android keyboard descends from AOSP's, and its prediction engine assumes one keystroke is one letter. Type `h`, `e`, `l`, `l`, `o` and it looks up "hello". That assumption is baked deep enough that a key carrying three letters has nothing sensible to hand it, which is why layouts like these vanished when phones got touchscreens. The keyboards that kept them, like Smart Keyboard Pro and Multiling O, wrote their own decoders. WM Keyboard's decoder walks the dictionary itself rather than the alphabet, so a keystroke doesn't have to be a single letter. It can be a *set* of them, and every word the dictionary can spell from that run of sets comes back as a candidate. Press the keys for `4` `6` `6` `3` and you get "good", "gone", "home", "hood" and the rest, ranked by which one you're most likely to have meant. ## T9 The grid is the keypad you already know. 2 is `abc`, 3 is `def`, and so on up to 9 for `wxyz`. The digit sits in each key's corner, and holding a key types it. 1 is the punctuation key, same as it was on a phone. It types a full stop, and its long-press holds the comma, question mark, exclamation mark, apostrophe, hyphen, colon and semicolon. Delete, shift and enter take a column of their own down the right, so the letter keys keep the full quarter of the screen each. The bottom row is ?123, the globe, and a double-width spacebar. One key per letter group means you tap a word's length and nothing more. "keyboard" is eight taps, the same eight taps as on any keyboard, but on keys four times the size. ### Reading the word back The text field shows the best reading as you type, not the raw keys. Tap the four keys of "home" and "home" is what appears (or "good", if that's the commoner word for those keys, in which case "home" is one tap away on the suggestion strip). Space commits whatever is showing. There's no confidence threshold here the way there is for autocorrect on a normal keyboard, because there's nothing to be conservative about: the keys on their own aren't a word, so committing "the reading" is the only answer that makes sense. ### Words the dictionary doesn't know Names, passwords, made-up words. Hold the key and its letters are in the popup, after the digit. Pick one and it goes in as itself: that position stops being a guess, and the rest of the word carries on being decoded normally. So you can spell the tricky part of a word by hand and let the keyboard handle the rest. Anything you spell out this way gets learned like any other word, so you only have to do it a couple of times. ## T9 in other languages The same keypad ships for 444 more languages, so switching language on the globe key keeps you on the keypad instead of dropping you back onto a full keyboard. Each one is under its own language in Languages & layouts, called **T9** with the language's name after it, and like the English one it starts switched off. The grid never changes: punctuation on 1, letters on 2 to 9, the same column down the right. Which letters each key carries, and in what order, comes from ETSI ES 202 130, the European standard phone makers followed for the 12-key keypad. It's the layout you'll remember if you texted on a Nokia or a Sony Ericsson in that language: - **Latin-script languages** keep `abc` on 2 through `wxyz` on 9 and add their own letters to the key of the letter they're built from: German puts `ä` on 2 and `ß` on 7, Polish puts `ł` on 5, Czech puts `ř` and `š` on 7. - **Russian** runs `абвг` `деёжз` `ийкл` `мноп` `рсту` `фхцч` `шщъы` `ьэюя`, and Ukrainian, Belarusian and Bulgarian follow the same pattern with their own letters. - **Serbian Cyrillic** matches its keys to Serbian Latin, so `ц` and `ч` are on 2 with `а б`, the way `c` and `č` sit with `a b`. - **Greek** runs `αβγ` to `χψω`, with the accented vowels and `ς` on their letter's key. - **Hebrew** starts `דהו` on 2 and `אבג` on 3, with the final forms beside their letter. - **Arabic, Persian and Urdu** each have their own table: Arabic starts `ب ت ة ث` on 2 and puts every form of alef and hamza on 3. - **Armenian** fills 2 to 8 and keeps 9 for Armenian punctuation, and **Georgian** runs its alphabet in order across all eight. Where the standard assigns a language another's table, that's the one you get: Catalan and Galician use the Spanish keypad, Faroese and Greenlandic the Danish one, Rusyn the Ukrainian one, Occitan, Breton and Walloon the French one. Languages the standard doesn't cover start from the table of the language their phones were sold in (Bavarian and Swiss German from German, Kurmanji Kurdish from Turkish, Talysh from Azerbaijani, Kazakh, Tatar and the other Cyrillic-script languages of Russia and Central Asia from Russian, Pashto and Sorani from Persian, Sindhi and Saraiki from Urdu), or from the plain keypad for languages whose phones were sold with the English one, such as Indonesian, Swahili, Zulu and the romanized Hindi, Bangla and Urdu. Their own letters then go on the key the standard's language-independent table gives them, or on the key of the letter they're read as: Hausa's `ɓ` on 2, Ewe's `ɛ` on 3, Kazakh's `қ` on 4. Tifinagh has no keypad standard, so Tamazight and Tachelhit run the alphabet in order across the keys. Scripts with no capital letters (Georgian, Hebrew, Tifinagh and the Arabic-script languages) have a comma in the shift key's place in the right-hand column. The Arabic-script keypads use `،` `؟` `؛` on 1, and Urdu, Sindhi, Saraiki and Punjabi add `۔`. ### Hindi, Bangla, Tamil and the other scripts of India If you typed Hindi or Bangla on a keypad phone sold in India, this is that layout. The standard records it for Hindi, Bangla and Tamil, taken from the phones on the Indian market, and every other Indic keypad here follows the same plan: | Key | Carries | Hindi | |---|---|---| | 1 | the signs: candrabindu, anusvara, visarga, virama | `ँ ं ः ्` | | 2 | the a, i, u and ri vowels, with their vowel signs | `अ आ इ ई उ ऊ ऋ` and `ा ि ी ु ू ृ` | | 3 | the e and o vowels, with their vowel signs | `ए ऐ ओ औ` and `े ै ो ौ` | | 4 to 8 | one consonant row each | `क`-row, `च`-row, `ट`-row, `त`-row, `प`-row | | 9 | the rest of the consonants | `य र ल व श ष स ह` | A vowel and its sign share a key, so you press the same key for `आ` at the start of a word and `ा` after a consonant, and the dictionary knows which one the word needs. A conjunct is the virama on 1 between its consonants, as it was on the phone. Nukta letters such as `क़` and `ड़` sit with their consonant and take one press. Because 1 holds the signs, the sentence's punctuation moves to the key where the shift key would be: `।` on the Devanagari, Bangla, Gurmukhi and Odia keypads, `။` for Burmese, `។` for Khmer and `.` for the others, with the rest behind a hold. That covers Hindi, Marathi, Nepali, Sanskrit and the other Devanagari languages, Bangla and Assamese, Punjabi, Gujarati, Odia, Tamil, Telugu, Kannada, Malayalam and Sinhala. Santali's Ol Chiki and Sylheti's Syloti Nagri follow the same plan, and so do Burmese and Khmer, with the signs on 1, the vowels on 2 and 3, and the consonant rows after them. ### Thai, Lao and Vietnamese Thai uses the Thai phone keypad: consonants in alphabet order across 2 to 7, vowels and tone marks written above or below the line on 8, and vowels written before or after it on 9. Lao is laid out the same way. Vietnamese uses the standard's table (`ă` and `â` on 2 with `a`, `ơ` and `ô` on 6) and puts every toned vowel on its vowel's key. Press the keys for the plain letters and the dictionary supplies the tones, the way predictive Vietnamese worked on a phone. ### Tone marks and other combining marks Some languages write marks that have no single-character form: Yoruba's `ẹ́`, Guarani's `g̃`, Dinka's `ɛ̈`, Tyap's `a̱`. Each mark is a keypress of its own, after its letter, on a fixed key: acute on 2, grave on 3, hook on 4, tilde on 5, dot below on 6 (Vietnamese's tone keys in Traditional T9), and diaeresis on 8. Arabic-script keypads put the harakat (tanwin, shadda and the short vowels) on 3 with the alef forms. Yiddish uses the standard's table, where a pointed letter such as `אַ` is one press. Cherokee's syllabary runs across the eight keys a consonant row or two at a time. Every letter a key carries is in its long-press popup too, so the rule from [Words the dictionary doesn't know](#words-the-dictionary-doesnt-know) still holds: hold the key, pick the letter, and that position is spelled exactly. The decoding runs on the language's word list, so the keypad needs that list on the device: adding a language offers to download it, and [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) covers getting it later. ## Compact QWERTY Same idea, gentler. The rows are QWERTY's own, folded two letters to a key: | Row | Keys | |---|---| | Top | `qw` `er` `ty` `ui` `op` | | Home | `as` `df` `gh` `jk` `l` | | Bottom | `zx` `cv` `bn` `m` | `l` and `m` end their rows on their own, because nine and seven are odd numbers. Every letter is still where your thumb expects it, so there's nothing to relearn. Two letters per key is much less ambiguity than three or four, which means the decode is closer to certain and the suggestion strip has less work to do. Shift and delete flank the bottom row at half width, which on this grid is the width of an ordinary QWERTY key. The digits keep their QWERTY positions in the long-press alternates, so `1` and `2` are both behind the `qw` key and they still read left to right across the top row. ## Glide typing Both layouts glide, and this is the part that surprises people, because it's rare enough that plenty of long-time T9 users have never seen it work. It falls out of how glide already worked here. The decoder has always matched a stroke against *keys* rather than letters, because plenty of layouts put more than one character on a key already. Probhat, the Bengali layout, has ক and খ on the same key, and refusing the second one would make most aspirated Bengali words impossible to swipe. So a T9 key carrying `abc` is the same situation with more letters, and the existing decoder handles it without changes. Draw through the keys of a word and the candidates come back, ordered by the language model the same way tapped words are. On T9 a stroke is ambiguous (the same path spells "good", "gone" and "home"), so the strip earns its keep. The [glide ambiguity picker](https://wmkeyboard.pages.dev/typing/glide-typing/) helps here too: when two readings are close, it holds the word until you lift so you can choose. Double letters have the old keypad problem, since a finger can't visit a key twice without leaving it. The decoder allows for it, the way it does on a normal layout. ## What to expect Straight talk about the trade: - **Common words come easily.** "the", "and", "have", "there" come out of the top slot without a thought. - **The strip matters more.** On QWERTY you can ignore it for whole sentences. Here it's part of typing, especially on T9, where words like "home" and "good" share every key. - **Rare words take a moment.** Anything the dictionary hasn't got needs the long-press spelling, once. - **Typo correction still runs, quietly.** Hitting the key next door is still caught, but it's held to a much higher bar than usual: a reading that uses your keystrokes exactly as you pressed them beats one that assumes you slipped, unless the evidence is overwhelming. The [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) does most of the heavy lifting over time. The more the keyboard knows about how you write, the less ambiguous your keys become. ## Building your own This isn't limited to the two shipped layouts. Any layout can put a set of letters on a key, in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/): tap the key, and **Letters this key stands for** is under Output. Type `qw` and that key stands for both. Two things happen when you do, and they're the two things that are easy to miss if you build one of these by hand: - **Output becomes the first letter of the set, and stops being editable.** That isn't fussiness. A tap has to commit *something*, and the decoder tracks what you pressed as one character per keystroke with the key's set carried alongside; an output from outside the set would make those two describe different typing. The editor writes it for you so it can't drift. - **The letters are offered to the long-press popup**, with a button that puts them there. Take it. Without the letters in the popup there is no way at all to spell a word the dictionary doesn't know, and that's not something you notice while testing "the" and "and". Keys with one letter need nothing: leave the field blank. A mixed grid is normal, and it's what Compact QWERTY is, with `L` and `M` alone on their keys. Everything else follows on its own. The decoder reads the key sets, glide picks up the letters at that key's centre, and typo proximity works out neighbours between keys instead of between letters. The field is `letters` in the [raw JSON](https://wmkeyboard.pages.dev/languages/custom-layouts/#reaching-the-raw-json) if you'd rather write a whole board at once: ```json { "label": "ABC", "output": "a", "letters": "abc", "longPress": ["2", "a", "b", "c"] } ``` A file that gets the anchor wrong is repaired on import rather than accepted quietly, and the confirm dialog says what changed. Four keys, three keys, letters grouped however you like. --- # Bangla URL: https://wmkeyboard.pages.dev/languages/bengali/ Section: Languages Summary: First-class Bangla: Avro-style phonetic typing, Khipro Classic and Dark, eight fixed layouts, and the spellings phonetic rules can't reach. Bangla is one of only two languages that ship a dictionary and emoji keywords inside the app itself. English is the other, and Bangla is the only one of the two with a purpose-built phonetic engine on top. It ships eleven layouts: the phonetic Avro, the compositional Khipro Classic and Khipro Dark, and eight fixed ones, Probhat, Jatiya, BSTI, Baishakhi, Bornona, InScript, Mayabi, and Samsung. Adding Bangla turns on Avro, and the rest are a tap away on its own screen. A phone whose Android language list already includes Bangla gets it enabled from the first run. Type romanized Bangla and the keyboard converts it to script as you go, or pick one of the fixed keyboards if you'd rather type the script directly. languages/avro-lenient-spelling — Avro phonetic typing: "ami valo asi" commits as আমি ভালো আছি. ## Typing phonetically with Avro **Avro phonetic** is a fixed QWERTY grid underneath (every key is exactly where it is on the English layout) with a rule-based transliteration engine layered on top. Type a romanized syllable and it renders in Bengali script directly in the composing text, no separate conversion step: "ami" becomes আমি, "kolokata" becomes কলকাতা, "dhorrmo" becomes ধর্ম. The engine handles conjuncts, reph, khanda-ta, and the vowel/consonant context that decides whether a vowel is a standalone letter or a kar sign. All of that is automatic, so you type the sounds and leave the script mechanics alone. Where it goes further than a plain transliterator is lenient spelling. Bangla speakers write the same word several different ways in Latin letters (আছি gets typed "achi", "achhi" or "asi" depending on the person), and Avro takes them all. Two layers do that, in this order. First a list of about 14,000 romanized spellings, described under [Spellings the rules can't reach](#spellings-the-rules-cant-reach) below. It covers most of the everyday vocabulary, and a spelling it names is settled there and nowhere else, because the list outranks both the dictionary and the literal transliteration. Both `achi` and `asi` are on it, and they're listed separately: `achi` commits আছি, `asi` commits আসি. Anything the list has never heard of falls to a phonetic fold. Both your romanized input and every word in the Bangla dictionary are reduced to a lenient key where confusable sounds collapse (স/শ/ষ/ছ/চ all fold to the same sound, aspiration like "kh" or "bh" drops to "k"/"b", and so on), so an unlisted spelling still finds the dictionary word it sounds like. The dictionary lists the verb forms of chat Bangla too (বানাইছি, করতিছে, নিছিলাম, আসবিনি, দেখেনাই), so `ashbini` finds আসবিনি and `khujtese` finds খুঁজতেছে. The `s` people type for the ছ sound still gives ছ. Words sharing a key are phonetic siblings, and the most frequent sibling leads. Two things you wrote down are weighed before frequency, because the fold throws them away: a sibling that contradicts an "h" you actually typed is pushed down, and so is one that ends in ও when you typed no final o. The "ch" of চ and the "ph" of ফ are exempt, since those are ordinary English-influenced spellings rather than claims about aspiration. A sibling only takes the commit from the literal transliteration when it is at least twice as frequent. A near-tie leaves what the composing text already showed you, because a silent swap between two words of roughly equal currency reads as a bug rather than a correction. The exception is a literal that isn't a dictionary word at all: with nothing to protect, the siblings lead, and the composing text changes to the word that will go in as soon as the strip has ranked it, so what you see underlined is what Space writes. The literal transliteration is always in the suggestion strip as a fallback either way, so you can pick it if the substitution guesses wrong. To stop the substitution altogether, turn off **Correct to dictionary words** (see [Options](#options)). A word made of a known stem and a common ending is recognised as that word even when the list has never seen the whole. The ending comes off, the stem is read on its own, and the ending goes back onto that reading, so `chelegulo` is ছেলেগুলো from the stem ছেলে and `bondhuder` is বন্ধুদের. The endings are গুলো, গুলা, গুলি, গুলোর, গুলোকে, দের, দেরকে, খানা and খানি. The stem goes through the same fold and known spellings as any other word, and the whole then counts as a dictionary word for ranking and autocorrect rather than a bare letter-for-letter reading. It works on Avro and Borno alike. A mistyped spelling gets one more chance, and this one is autocorrect. `vako` has no reading of its own, but `valo` is one key away, and `valo` is ভালো. The keys you pressed are checked against the romanized spellings people write (the list above, plus the romanized word list if you've downloaded it), the way an English typo is checked against English words, and the nearby spellings are weighed by how often they're written and by the one or two words before them. The closest word goes on the strip, and a space writes it when the slip is small (the key next door, two letters swapped, one letter too many) and nothing else comes close. The composing text keeps showing what you typed until then, so words don't jump around as you spell them. Backspace right after the space puts the reading back. A word of under four letters, one that's already a known spelling, an English word and a word you've taught the keyboard are all left alone. Neither layer is part of autocorrect, and neither is controlled by the autocorrect toggle. Avro's phonetic commit runs regardless of whether [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) is on or off. The fixed-spelling list has its own switch (see **Spellings** under [Options](#options) below). The phonetic fold has none. A few more of the rules worth knowing: | You type | You get | Why | |---|---|---| | `valo` or `bhalo` | ভাল | "bh" and "v" both fold to the "b" sound | | `valO` | ভালো | lowercase "o" is the inherent vowel and writes nothing; only capital "O" is ও/ো | | `kolokata` | কলকাতা | the explicit "o" between l and k breaks the conjunct that `kolkata` (no vowel between them) would form | | `korbo`, `bolchi` | করব, বলছি | two consonants only join where Bangla writes them as one letter, the same list desktop Avro uses; `kt` is ক্ত, but `rb` and `lch` stay apart | | `dhorrmo` | ধর্ম | reph is a double r, as on desktop Avro; the common words people spell with one r, like `dhormo`, are on the known-spellings list | | `onko` / `oncol` | অঙ্ক / অঞ্চল | "n" before k or c takes that letter's own nasal | | `amio` / `khaoa` | আমিও / খাওয়া | "o" straight after a written vowel is ও | | `bangla` / `songe` / `baNgali` | বাংলা / সঙ্গে / বাঙালি | ং joins nothing; before a vowel it is ঙ্গ, as on desktop Avro, and capital `Ng` is the bare ঙ | | `swasthyo` | স্বাস্থ্য | "w" after a consonant is a ব-ফলা join | | `wasi` | ওয়াসি | "w" anywhere else is ও | | `panjabi` | পাঞ্জাবি | "n" before "j" is ঞ | | `hoThaTH`, `hoThat` + a trailing backquote, or `hoThat,,` | হঠাৎ | `TH`, a backquote or a trailing hasant all spell khanda-ta (ৎ) | | `shyam` | শ্যাম | "y" after a consonant is a য-ফলা join | | `meye` | মেয়ে | "y" elsewhere is য়, not a join | | `yes` | ইয়েস | "y" at the start of a word is ইয় | | `kee` | কী | "ee" is ঈ, or ী after a consonant | | `aZp` | অ্যাপ | `aZ` is the অ্যা of English loanwords | | `rZab` | র‍্যাব | after a plain র, য-ফলা keeps the র whole; `karryo` still gives the reph of কার্য | | `ggan` | জ্ঞান | `gg` is the জ্ঞ conjunct | | `Bangla` | বাংলা | a capital that spells nothing of its own reads as its lowercase self | | `$` / `123` | ৳ / ১২৩ | currency and digits convert too | | `.` / `..` / `...` | । / `.` / `...` | a single dot is দাঁড়ি; two dots is a literal period, and three stay an ellipsis | Both of Avro's spellings for the marks work, and so do the ones Ridmik uses: ঁ is `^` or `qq`/`cb`, ঃ is `:` or `HH`, ৎ is `t` plus a backquote or `TH`, and the hasant is `,,` or `hs`. The `hs` form only fires after a consonant, where a hasant can actually go, so it never eats the h and s of a name like `ahsan`. ### Avro key map Every key the engine reads, in one place. The same tables are in the app, under **Key map** on Bangla's own screen (see [Options](#options)). **Vowels.** The first form is the letter on its own (at the start of a word, or after another vowel). The second is the sign it becomes after a consonant. | Key | Letter | After a consonant | |---|---|---| | `o` | অ | nothing: the inherent vowel | | `a`, `A` | আ | া | | `i` | ই | ি | | `I`, `ee` | ঈ | ী | | `u`, `oo` | উ | ু | | `U` | ঊ | ূ | | `rri` | ঋ | ৃ | | `e`, `E` | এ | ে | | `OI` | ঐ | ৈ | | `O` | ও | ো | | `OU` | ঔ | ৌ | An `o` straight after another written vowel is ও, so `amio` is আমিও. An `a` straight after another written vowel glides with য়, so `kiamot` is কিয়ামত. A capital `A` is always a plain আ. **Consonants.** Two consonants in a row join into a conjunct with no extra key, but only where Bangla writes that pair as one letter: `kt` is ক্ত, `ndh` is ন্ধ, `st` is স্ত, while `rb`, `lch` and `kd` stay apart, so `korbo` is করব and `bolchi` is বলছি. It is the same list of joins desktop Avro uses. An `r`, `y` or `w` after a consonant always joins as a ফলা, and "n" before k, kh, c, ch or j becomes ঙ or ঞ. Type `o` between two consonants to keep them apart whatever the pair. | | | | | | |---|---|---|---|---| | `k`, `q` ক | `kh` খ | `g` গ | `gh` ঘ | `Ng` ঙ | | `c` চ | `ch` ছ | `j` জ | `jh` ঝ | `NG` ঞ | | `T` ট | `Th` ঠ | `D` ড | `Dh` ঢ | `N` ণ | | `t` ত | `th` থ | `d` দ | `dh` ধ | `n` ন | | `p` প | `ph`, `f` ফ | `b` ব | `bh`, `v` ভ | `m` ম | | `z` য | `r` র | `l` ল | `sh`, `S` শ | `Sh` ষ | | `s` স | `h` হ | `R` ড় | `Rh` ঢ় | `Y` য় | | `J` জ় | `kkh`, `kx` ক্ষ | `gg` জ্ঞ | `nj` ঞ্জ | | **Joins and signs.** | Key | Gives | Example | |---|---|---| | `y` after a consonant | ্য (য-ফলা) | `shyam` শ্যাম | | `y` at the start of a word | ইয় | `yes` ইয়েস | | `y` after a vowel | য় | `meye` মেয়ে | | `Z` | ্য, wherever it is | | | `w` after a consonant | ্ব (ব-ফলা) | `swasthyo` স্বাস্থ্য | | `w` anywhere else | ও, or ওয় before a vowel | `wasi` ওয়াসি, `we` ওয়ে | | `rr` before a consonant | র্ (reph) | `dhorrmo` ধর্ম | | `x` at the start of a word | এক্স | `xre` এক্সরে | | `x` anywhere else | ক্স | `bax` বাক্স | | `aZ`, `AZ` | অ্যা | `aZp` অ্যাপ | | `ng` | ং, or ঙ্গ before a vowel | `bangla` বাংলা, `songe` সঙ্গে; `Ng` for a bare ঙ, `baNgali` বাঙালি | | `^`, `qq`, `cb` | ঁ (চন্দ্রবিন্দু) | `ca^d` চাঁদ | | `:`, `HH` | ঃ (বিসর্গ) | `du:kho` দুঃখ | | `,,`, `hs` | ্ (hasant) | | | `TH`, or `t` then a backquote | ৎ | `hoThaTH` হঠাৎ | | a backquote between consonants | keeps them apart | `` k`s `` কস, where `ks` is ক্স | | `.` / `..` / `...` | । / `.` / `...` | | | `$`, `0` to `9` | ৳, ০ to ৯ | | A capital that isn't in these tables reads as its lowercase letter, so `B` is ব. Bangla has no q, so `q` and `Q` both type ক, the same as on Ridmik. ### Spellings the rules can't reach Some words aren't spelled the way they sound, so a list of them is checked before the phonetic engine runs. A match there always wins over whatever the transliterator would have produced. Two kinds of word need this: - **English loanwords.** "Keyboard" is written কিবোর্ড and "chair" চেয়ার by convention, not by sound. Left to the rules, "table" would come out তাব্লে instead of টেবিল. About 2,400 of these are listed by hand. - **Banglish as people actually type it.** Chat spelling drops vowels wholesale, and no phonetic rule can put back a vowel that was never typed. `tmr` is তোমার, `amk` is আমাকে, `trpr` is তারপর. Colloquial and regional inflections like `boltiso` (বলতিছো) are in the same list, which covers about 12,000 spellings drawn from a corpus of real Bangla chat. So are about 950 words with a reph spelled with a single r, like `dhormo` (ধর্ম) and `porjonto` (পর্যন্ত), since the rules keep a single r apart the way desktop Avro does. Some of them come from people's comments on a Bangla tech forum ([BanglaTLit](https://github.com/farhanishmam/BanglaTLit)): common shorthand and misspellings like `plz` (প্লিজ), `tk` (টাকা) and `akon` (এখন). The list stays out of the way where the rules already spell a common Bangla word. `fire` is ফিরে, `nice` is নিচে and `make` is মাকে, as they are in Avro on a computer, and not the English word that happens to share the letters. | you type | you get | without the list | | --- | --- | --- | | `tmr` | তোমার | ত্ম্র | | `amk` | আমাকে | আম্ক | | `tmi` | তুমি | ত্মি | | `table` | টেবিল | তাব্লে | A listed spelling shows up in the composing text as soon as you finish typing it, not when you hit space: `tmr` reads তোমার at the r. That's the same answer the space would have committed, so nothing changes under you at the last moment. The list doesn't take a spelling away from a word the rules already write. Where the rules spell a word people use, that word comes first and the listed one sits next to it on the strip: `ta` is তা and then টা, `kot` is কত and then কোট, `dosh` is দশ and then দোষ. The exceptions are chat spellings that mean one word far more often than the other, such as `ase`, which stays আছে with আসে second. A capital that changes the letter also goes around the list and the dictionary, because you picked that letter on purpose: `moN` writes মণ whatever the list says about `mon`. A capital that spells nothing of its own, like the `M` in `Mon`, doesn't count. You can switch this off per language if you'd rather have the letter-for-letter reading (see **Spellings** under [Options](#options) below). See [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for how Bangla's dictionary and these lists fit into suggestions generally. ## Typing with Khipro **Borno phonetic** is the keymap of Codepotro's Borno keyboard, read by rules of WM Keyboard's own and ranked against the same word list as Avro, so the strip and Space behave as they do on Avro. It wears the same QWERTY grid, and turning it on next to Avro lets the globe key flip between them. The keys that differ from Avro are `c` for চ and `ch` for ছ, `z` for য, `y` after a consonant for য-ফলা and `w` for ব-ফলা, `kkh` for ক্ষ, `gg` for জ্ঞ, and `A` or `xx` for চন্দ্রবিন্দু. The reph takes `rr`, so `korrm` is কর্ম and `korm` is করম. `ng` is ং, and ঙ্গ before a vowel, so `bangla` is বাংলা and `ranga` is রাঙ্গা. Consonants join only where Bangla usually writes them together, and `n` becomes the nasal of the row it joins: `onk` is অঙ্ক, `onj` is অঞ্জ. Digits come out in Bangla and `$` is ৳. Holding `t` types ৎ, `l` types ঃ, `a` types ঁ and `g` types the hasant. `,,` types the hasant too, and `,.` and `,..` add a joiner after it. `/` after a consonant types the nukta, where Borno itself uses `./`. Two backslashes keep what's before them in English, so `facebook\\-er` is facebook-এর. Glide typing works as it does on Avro, against the Banglish word list. Borno's own language model isn't included: the suggestions, the context model and the optional neural readings are WM Keyboard's. A known stem keeps a common ending the way Borno's does, so `chelegulo` is ছেলেগুলো. **Khipro Classic** (ক্ষিপ্র), listed under the name the Khipro team gave it once Khipro Dark came out, is a compositional layout made by the [Khipro team](https://khipro.khiproteam.com/). It is not phonetic. Every key sequence has exactly one meaning, so the keyboard never guesses which word you meant: what you type is what you get, and the space bar commits it exactly as the composing text shows it. There is no known-spellings list and no dictionary substitution on this layout, and autocorrect never touches it. The suggestion strip still helps you finish a word. Its first word is the one you typed, and after it come Bangla words that start with it, from the Bangla dictionary and from words you have typed before. The grid is QWERTY, with the two changes the Khipro team asks for on a touchscreen. Khipro never uses shift, so the key where shift would be is /. The ; key sits between the spacebar and the দাঁড়ি key. You type everything in lowercase. Two keys change the letter before them: - `f` gives a letter's partner: `t` is ত and `tf` is ট, `tff` is ঠ, `df` is ড, `sf` is ষ, `rf` is ড়, and `kf` is ক্ষ. - `/` is the slicer. It breaks a conjunct back apart (`ks` is ক্স, `ks/` is কস), makes খণ্ড-ত after `t` (`t/` is ৎ), and puts চন্দ্রবিন্দু on the vowel before it (`ca/d` is চাঁদ). Press it twice for a real slash after a vowel. Two more keys control how letters join: - `;` separates two parts that would otherwise join or combine: `budh;bar` is বুধবার, where `budhbar` would make a conjunct, and `amar;i` is আমারই. Press it twice for a real semicolon. - `?` is the blinder. The text after it stays in Latin letters, so `?hello` is hello, until `;` switches back to Bangla. A `?` at the end of a word stays a question mark (`ami?` is আমি?); press it twice to put one in front of more text, which then stays Latin too. Hold / for the backslash, which does the same. The দাঁড়ি key takes `f` too: । then `f` is ॥, and `ff` is ৺. Hold the দাঁড়ি key for ॥ directly, and for ZWJ and ZWNJ, the invisible joiners that control how two letters connect. | You type | You get | |---|---| | `ami` | আমি | | `kobita` | কবিতা | | `valw` | ভালো | | `baxladesh` | বাংলাদেশ | | `kkhipr` | ক্ষিপ্র | | `dhrrm` | ধর্ম | | `mae` | ম্যা | | `du;oo;kh` | দুঃখ | | `j,,` | জ় | The complete mapping, with every conjunct, is in the [Khipro documentation](https://khipro.khiproteam.com/documentation/). The keyboard runs the Khipro team's own published definition of the layout, so it types the same as Khipro on Linux, Windows and HeliBoard. ### Khipro updates The Khipro team keeps fixing the layout between app releases, so the keyboard doesn't wait for a release to pick up their fixes. The app ships with the definition as it stood when that version was built. The [data repository](https://github.com/wasi-master/wmkeyboard-data/tree/main/data/bn/khipro) publishes the Khipro team's current one, covering Classic and Dark for both the screen keys and a hardware keyboard. While you type with Khipro, the keyboard looks for a newer set once a day and installs it without asking. A check is a 2 KB file, and a new set is about 230 KB. When data saving is on and its **Downloads** row isn't on Allow, the check waits for a connection data saving leaves alone. A set goes in only if all of it holds up. Every file has to match the size and SHA-256 the set's manifest gives. Each layout definition also has to type every case of the Khipro team's own test table for it the way they say it should. If anything fails, nothing changes and the layout keeps typing as before. The new rules apply from the next word, and they keep working before you unlock the phone after a restart. Glide typing works out the spellings it reads again under the new rules. When an app update brings a definition as new as the downloaded one, or newer, the bundled one takes over again. **Layout rules** on Bangla's own screen (see [Options](#options)) shows which rules you're typing with and their date. Tap it to check right away. **Use the bundled rules** goes back to the ones the app came with. The daily check then leaves that set alone until a newer one is published, though **Layout rules** still fetches it if you tap it. Without a connection, or on the build with no internet, download the folder's files elsewhere and import them together with **Import from a file** on Bangla's own screen: the four `.mim` files, the four `.tsv` tables and `bn_khipro_specs.json`. Loose or in one zip, they go through the same checks. On the build with no internet, **Layout rules** says so and doesn't check, and the daily check never runs. On a hardware keyboard, a word you start from the physical keys uses Khipro's desktop version instead, because a physical keyboard has no Bangla digits or দাঁড়ি key: `123` gives ১২৩, `.` gives ।, `..` gives a full stop, and `$` gives ৳. Words you type on the screen keys are not affected. **Glide typing** works on Khipro too. A stroke over the grid is read as a Khipro spelling and comes out as the Bangla word it spells: swipe `ami` for আমি, `kwthay` for কোথায়. The keyboard works out the Khipro spelling of every word in your Bangla word lists (the bundled one, a downloaded one and any you imported) by running Khipro's own rules backwards and keeping only the spellings Khipro turns back into the word, with the inherent `o` written or left out, so `ekhn` and `ekhon` both reach এখন. Words you have taught the keyboard are included too. Working that out for a 450,000-word list takes minutes of every core, so it is done ahead of time: the first swipe fetches the spellings of the downloadable list from the [data repository](https://github.com/wasi-master/wmkeyboard-data) (about 4 MB, `bn_khipro_glide.txt.gz`), and the phone only spells the few words that file doesn't list, which takes seconds. Swiping starts once that is done; afterwards it is saved and only redone when a list changes. Without a connection, or on the build with no internet, the phone works every spelling out itself, which takes a few minutes in the background with a notification counting the words, unless you import the file (it is in the Bangla language pack). The slicer / is part of the swipe: draw through it where চন্দ্রবিন্দু or খণ্ড-ত goes, so `ca/d`-style strokes reach চাঁদ and a stroke ending on / after `t` reaches হঠাৎ. Words that need another non-letter key, like the separator ;, are typed as usual. ### Khipro Dark **Khipro Dark** is the Khipro team's newer edition, made for touchscreens, and ships as its own layout beside Khipro Classic. It has the same keys and the same grid. The difference is conjuncts: Classic joins consonants as you type them and you break a conjunct with `/`, while Dark leaves consonants apart and joins them only when you press `/` after them. Most everyday words have no conjunct, so on Dark they need no separator at all: `lagbe` is লাগবে, where Classic would join গ্‌ব. Press `/` once to join the last two consonants, twice for the last three, three times for the last four: | You type | You get | |---|---| | `ntrz/` | নতর‍্য | | `ntrz//` | নত্র্য | | `ntrz///` | ন্ত্র্য | | `cndr//bind/u` | চন্দ্রবিন্দু | | `kks/` | কক্স | `kf` is still ক্ষ and `gg` is still জ্ঞ with no slash, and Classic's shortcut pairs stay when you join them: `nc/` is ঞ্চ and `ttf/` is ট্ট. The slash keeps its other jobs where there is nothing to join: after a vowel it is চন্দ্রবিন্দু (`ma/` is মাঁ), and after a `t` that follows a vowel it is খণ্ড-ত (`t/` is ৎ). Where `t` follows a consonant, `/` joins the two instead, so add `f` after it for খণ্ড-ত: `pott/f` is পতৎ. Turn it on from Bangla's own screen, as a card beside Khipro Classic; with both on, the 🌐 key or a spacebar swipe flips between them. A hardware keyboard reads a word against Dark's desktop version, the same way it does for Classic. Glide typing works on Dark too and reads the same Classic spellings, so a stroke never needs a `/`: swiping `lagbe` still gives লাগবে, and a conjunct like ন্ত্র comes out joined, as Classic would write it. Key hints work on Khipro too (see [Options](#options)): with them on, each key shows the Bangla it will add, including the letter that `h` or `f` changes the previous one into. The second word on the strip is the keys exactly as you pressed them, modifiers included: `prshn/` beside প্রশ্ন. Khipro's `f` and `/` change the letters before them, so this is where you check whether a modifier went in. Tap it to write the keys as they are, in English letters; the space bar still writes the Bangla word. Turn it off with **Show the keys you pressed** on Bangla's own screen (see [Options](#options)). Khipro has its own **Type English words as English on Khipro** switch, separate from Avro's and off by default, so turning English words on for Avro leaves Khipro writing Bangla. With it off, the space bar always writes the Bangla the keys spell, and an English word only goes in when you tap its chip on the strip. With English picked under **Also suggest from** and the switch on, keys that spell an English word commit as that word when what they spell in Bangla is not a word the list or your dictionary knows: `hello` stays hello, and `ami` is still আমি. The English word leads the strip, with the Bangla reading right behind it for the times you meant that. With **Show the keys you pressed** off, a word typed in letters alone still gets a chip of those letters right behind the Bangla reading, so a name or a word no dictionary has can still go in as English with a tap. Khipro has no reading to weigh, so the rule is that plain; it does not use the context scoring the phonetic layout does. Keys typed into the middle of a word are never taken as English: put the cursor between চ and ত in কিঞ্চত and type `if`, and you get the ি kar, not *if*, with English left on the strip as a chip. Picking from the strip there adds no space either, so the word stays whole as কিঞ্চিত. ## Choosing a layout Bangla ships thirteen layouts. A fresh install turns on one of them, Avro, and only if your phone's own language list has Bangla in it. What starts enabled is read off the phone rather than fixed (see [What ships](https://wmkeyboard.pages.dev/languages/overview/#what-ships)). Turn the rest on whenever you want them, from Bangla's own screen under **Languages**. There's one exception: a keyboard you enabled from Android's settings and then typed on without ever opening the app. The setup wizard never ran, so the built-in fallback stands, which is Avro alongside English QWERTY. - **Avro phonetic**: the QWERTY grid described above, and the default layout when you add Bangla by searching for it. - **Khipro Classic**: the compositional layout described above, on QWERTY letters with / in place of shift. - **Khipro Dark**: the same grid with Khipro's newer edition, where consonants join only when you press / (see [Khipro Dark](#khipro-dark)). - **Avro flick (3×3)** and **Khipro flick (3×3)**: the [English flick board](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys), nine keys with the letters on flick arms, typing through Avro or Khipro Classic instead of English. Every letter Avro and Khipro read is on it, the same as on QWERTY, so nothing is lost but the key count. - **প্রভাত (Probhat)**: the fixed Bangla layout popular on Linux, following the standard desktop keymap as shipped by Avro Keyboard and OpenBangla. You type Bengali letters directly, with nothing transliterated. Every layout that types letters directly completes and corrects them from the Bangla word list, the same list Avro reads. - **জাতীয় (National / Jatiya)**: the Bangladesh national standard fixed layout, also following its desktop keymap. - **বিএসটিআই (BSTI)**: the revised national standard, BDS 1738:2018, in the touch arrangement of Ekushey's [Bangla BSTI Mobile](https://github.com/ekushey-org/bangla-bsti-mobile). It looks like Jatiya but is its own grid. - **বৈশাখী (Baishakhi)**: another fixed Bangla layout, typed directly with nothing transliterated. - **বর্ণনা (Bornona)**: another fixed Bangla layout, typed directly with nothing transliterated. - **ইনস্ক্রিপ্ট (InScript)**: the Bengali InScript layout, the same Indic-script keyboard standard the app ships for many other Indian languages. - **মায়াবী (Mayabi)**: a fixed layout on a 12-key-wide grid, with the kar signs down the left of the top two rows and shift giving each key's partner letter. - **স্যামসাং বর্ণমালা (Samsung)**: the alphabet grid from Samsung Keyboard. The letters run in alphabet order across ten columns: the vowels in a block on the left and the consonants on the right, from ক down to হ, with no shift key. A vowel key types its kar after a consonant (ক then আ gives কা) and the full vowel at the start of a word. ড়, ঢ়, য় and ৎ are on the hold of ড, ঢ, য and ত. Like Samsung's, it has no 🌐 key, so switch away from it with a spacebar swipe. languages/probhat-key-grid — প্রভাত (Probhat) active: Bengali letters typed directly, no phonetic conversion. On Probhat, shift mostly gives you the aspirated partner of a consonant (দ→ধ, ট→ঠ, গ→ঘ, ক→খ) but not uniformly. Some shift pairs are vowel-length pairs (ি→ই, ু→উ, ী→ঈ) and a few are unrelated letters that share a key for space reasons (র→ড়, ন→ণ, স→ষ). The 10th key on the home row carries ে with ো on shift and ৈ/ৌ/এ on long-press, and the া key holds আ. A handful of less common marks (ৎ, ঞ, ঁ, ৃ, ৗ, ঽ) live as long-press alternates on a phonetically related key rather than getting a dedicated slot. Jatiya's letter keys also match its desktop layout. Independent vowels that sit on a desktop AltGr key are reached here as a long-press on their matching kar sign: long-press ু for উ or ঊ, long-press ি for ই or ঈ, and so on. BSTI keeps most of Jatiya's letters where they are. On the home row া and ব swap places, and shift there gives the reph (র্) on the ৃ key, অ on the া key and চন্দ্রবিন্দু (ঁ) on the হসন্ত. The bottom row opens with a র-ফলা key (্র, with ্য on shift), then ো and ে with ৌ and ৈ on shift. A held key offers its own alternate first and then its shift partner's, which is where the standard's common conjuncts live: ঙ্ক on ঙ, ক্ষ on ক, জ্ঞ on জ, ঞ্জ on হ, ঙ্গ on গ, ষ্ণ on স, হ্ম and ঃ on ম, ব়্য on র, and ৎ on ত. Desktop BSTI also turns হসন্ত followed by a kar into the full vowel. Here the full vowels are on the kar keys' holds instead, so a হসন্ত always stays a হসন্ত. On the fixed layouts a kar key reads what comes before it. After a consonant it types the kar (ক then া gives কা), at the start of a word the vowel itself (আ), and after another vowel the আ-কার and এ-কার keys type the য়-glide instead, because a bare া or ে can't follow a vowel: কা then া gives কায়া, দি then ে gives দিয়ে. For a real আ or এ there, hold the key and pick it, or turn on **Tap twice for আ or এ** (Languages & layouts / Bangla / Clusters / Tap twice for আ or এ, off by default): then pressing the key again straight away turns য়া into আ and য়ে into এ. All thirteen layouts type দাঁড়ি (।) from the full stop key beside the spacebar rather than a full stop (on Khipro, it comes after the ; key), with the ASCII `.` moved to that key's long-press for numbers, file names and web addresses. Every layer but the letters is where numbers are typed, so on the ?123 pages and on an Fn, Number or secondary layer the same key stays a plain `.` for decimals and দাঁড়ি moves to its long-press instead. On the eight fixed layouts, `?` comes first in that popup and `.` right after it, so a plain hold on the key types a question mark; Avro and Khipro already have one on the hold of m. The two invisible joiners are on the fixed layouts too. Hold হসন্ত (্) or the দাঁড়ি key and the popup ends with **ZWNJ** and **ZWJ**, drawn by name because neither character has a shape of its own. ZWNJ after a হসন্ত keeps two consonants from joining, so ক ্ ZWNJ ষ shows ক্‌ষ rather than ক্ষ, and উদ্‌যাপন keeps its দ apart from the য. ZWJ asks for the half form instead, as in র‍্যাব. Desktop Jatiya and Probhat keep these on AltGr and shift keys a phone doesn't have, so the hold is where they go here. Every other fixed Bengali-script layout, the Assamese, Sylheti and Bishnupriya Manipuri ones included, has them in the same two places. Khipro has them on its দাঁড়ি key. বিসর্গ (ঃ) sits on the long-press of the colon key on the symbols layer, on all thirteen. It's drawn as a colon and does a different job, and the symbol layers are one grid shared by every language, so the colon's popup is where it goes. All eight fixed layouts share the same cluster-editing behavior as Avro and Khipro (see [Editing conjuncts and clusters](#editing-conjuncts-and-clusters) below), and the same font choice applies across all thirteen (see [Options](#options)). Switching between them works exactly like switching between any other enabled layouts: spacebar swipe, hold-drag picker, or the 🌐 key. See [Switching languages](https://wmkeyboard.pages.dev/languages/switching/) for the full mechanics. ### Shörolipi Banglish, Bangla's romanized entry, has a second layout besides its plain QWERTY: **Shörolipi (সরলিপি)**, the Khipro team's [romanization of Bangla](https://github.com/KhiproTeam/shorolipi). It writes Bangla in Latin letters, one letter per sound, and fits the whole alphabet on 26 keys by putting six letters with marks where English has letters Bangla doesn't need: ṛ and ḍ in place of q and w, ö (অ) in place of f, ŋ (ঙ, ং) in place of z, ë (অ্যা) in place of x and ṭ in place of v. Hold any of those six for the plain English letter, so English words still go in without switching. The vowels a, e, i, o, u, ö and ë have their nasal form (ã, ẽ and so on) on the hold too, and the full stop's hold ends with the combining tilde for any other letter. What you type stays in Latin letters: `pöṛa` is পড়া and `kãca` is কাঁচা, written the way Shörolipi spells them, not converted to Bengali script. ## Editing conjuncts and clusters Bengali script builds conjuncts (যুক্তবর্ণ) by joining consonants with a hasant (্): ক্ষ, স্ত্রী, জ্ঞ. By default, backspace over already-typed Bengali text removes one character at a time, which can leave a half-formed cluster on screen: জ্ঞ with one backspace becomes জ্, not জ. Languages & layouts / Bangla / Clusters / Conjunct-aware backspace Turning on **Conjunct-aware backspace** changes this: a whole conjunct cluster is deleted in a single press instead. স্ত্রী (a triple conjunct) disappears as one unit, ক্ষ as one unit, and a bare trailing hasant with nothing joined to it yet still deletes alone. The setting is off by default, and it applies to committed Bengali-script text across all ten Bangla layouts alike, which share the same cluster logic. It only affects text already committed to the field. Deleting through a word you are still composing on Avro or Khipro always removes one Latin character per press. This toggle isn't unique to Bangla. Every language on a cluster-forming script gets its own copy of it, and Devanagari, Tamil, Khmer and others work the same way. It's set per language, so Bangla deleting whole conjuncts doesn't commit you to Hindi doing the same. languages/conjunct-backspace-toggle — Conjunct-aware backspace, under Languages → Complex scripts. Off by default. ## Emoji search in Bangla Bangla is one of the two languages (with English) whose search keywords are baked directly into the bundled emoji catalog, so searching emoji by their Bangla name works with nothing to download. A larger keyword pack for Bangla is downloadable too, and it stacks on top of the bundled set rather than replacing it. See [Emoji search in your language](https://wmkeyboard.pages.dev/emoji/customization/#emoji-search-in-your-language) and [Emoji picker](https://wmkeyboard.pages.dev/emoji/picker/) for how the ranking and language-merging work. ## Writing ANSI (Bijoy) text Before Unicode, Bangla was typed with Bijoy into fonts like SutonnyMJ, and a lot of design software, print shops and older documents still expect it. That encoding, usually called ANSI, stores each Bangla glyph as a Latin character and leaves the font to draw it as Bangla. আ is stored as `Av`, and আমার সোনার বাংলা as `Avgvi †mvbvi evsjv`. Turn on **Allow ANSI output** on Bangla's own screen (see [Options](#options)) and every Bangla layout gets an **ANSI** button beside the suggestions: Avro, Khipro and all the fixed layouts. Tap it and everything the keyboard writes in Bangla goes into the text box as ANSI instead of Unicode, whether you typed it, picked it from the strip, swiped it or dictated it. The button stays lit while it's on. Tap it again to go back to Unicode. - The word you're typing stays in Unicode while it's still being composed, and turns into ANSI when you finish it with a space, a punctuation mark or a pick from the strip. ANSI puts some vowel signs in front of their consonant (কি is `wK`), so a word only converts right once it's whole. - On a layout that types letter by letter without composing, each letter rewrites the word as you go, so the vowel signs still land in the right place. - **ANSI version** picks the encoding. Version 2 is the usual one, matching SutonnyMJ and most Bijoy fonts. The fonts don't all put every letter in the same place, so if a conjunct comes out wrong in yours, try version 1 or 3. - ANSI text is Latin letters as far as the keyboard is concerned. Next-word predictions and taking a correction back with backspace don't work across it, and the keyboard doesn't pick an ANSI word back up for editing when you backspace into it. Backspace removes one ANSI character at a time, the way Bijoy always did. - ANSI only reads as Bangla in an ANSI font. Everywhere else it's gibberish, so switch the button off when you leave that app. ## Options Languages & layouts / বাংলা · Bangla Open Bangla's own screen from **Languages** in the app, or search for it from **Add language**: languages/bengali-language-screen — Bengali's language screen: layout cards, numeral system, and dictionary/emoji status. - **Layouts**: one card each for Avro, Khipro Classic, Khipro Dark, Probhat, Jatiya, BSTI, Baishakhi, Bornona, InScript, Mayabi, and Samsung, each a small preview of that keyboard. Tap a card to turn it on or off; all eleven are independent. At least one layout has to stay enabled somewhere across your whole keyboard, so you can't switch off the last one from here. - **Key hints**: Languages & layouts / Bangla / Key hints / Show the letter each key types, a three-way choice (Off, Added, or Whole letter), off by default. It puts the Bengali letter a key is about to write in its corner. Unlike the settings around it, this one is shared. It's one setting for every language with a transliterating layout enabled, not only Bangla, and it shows only while such a layout, Avro or Khipro, is active. - **Key map**: a folded group just below the hints while Avro is on. Open it for the vowels, consonants, joins and signs, the same tables as the [Avro key map](#avro-key-map) above, so they work offline. **Full guide** at the bottom, Languages & layouts / Bangla / Key map / Full guide, opens this page in your browser. - **Suggestion strip**: Languages & layouts / Bangla / Suggestion strip / Keep the first two words in place, off by default, shown while Avro is on. Normally the first word on the strip is the one the space bar will write, so the words move as you type. Turn this on and the first two never move: on the left is what you typed, in English letters, and next to it is the Avro reading of it, letter for letter (`ami` → আমি, `tmr` → ত্ম্র). The suggestions come after them. The space bar still writes the same word as before, and that word is the bold one wherever it sits. A second row, **Suggestions after them**, picks what fills the rest of the strip: **Smart** (both languages, the one the word reads as first, with the other language's best word on the last chip when there is room), **Bengali only**, or **English only**. English only works without English under Also suggest from too, from the bundled English list. While this is on, the strip does not move its top word to the middle. - **Type English words as English on Khipro**: Languages & layouts / Bangla / English words / Type English words as English on Khipro, off by default, shown while Khipro Classic or Khipro Dark is on and English is picked under Also suggest from. The space bar writes keys that spell an English word, and no Bangla one, in English. Separate from Avro's switch (see [Typing with Khipro](#typing-with-khipro)). - **Show the keys you pressed**: Languages & layouts / Bangla / Suggestion strip / Show the keys you pressed, on by default, shown while Khipro Classic or Khipro Dark is on. Puts the keys you pressed, `/`, `;` and `f` included, second on the strip, right after the Bangla word. A tap writes them as typed. Turn it off and only a word of plain letters gets that chip, and only with English mixed in. - **Layout rules**: Languages & layouts / Bangla / Khipro's rules / Layout rules, shown while Khipro Classic or Khipro Dark is on. Says whether Khipro types with the rules the app came with or with a newer downloaded set, and from what date. Tap it to check for a newer set now. **Use the bundled rules** under it, shown while a downloaded set is in use, goes back to the app's own. See [Khipro updates](#khipro-updates). - **Avro candidate list**: Languages & layouts / Bangla / Suggestion strip / Avro candidate list, off by default, shown while Avro is on. Desktop Avro shows a list of words under the word you type, and this is that list, built from desktop Avro's own data (its search rules, its 737 endings and its autocorrect list, which ship with the keyboard, and its dictionary, which the keyboard downloads, about 580 KB, the moment you turn the list on) the way desktop Avro builds it. Until the dictionary is there, the list searches the keyboard's own Bangla words; the row under the setting shows the download and retries it if it failed. Every word desktop Avro would offer is in it, in Avro's order: first Avro's autocorrect spelling for what you typed, then every dictionary word the spelling could be (Avro's search is loose: an `o` can be ো, য় or nothing, so `vodor` finds ভোঁদড় and `dokan` finds দোকান), together with the words made by reading an ending off the end (`bondhuder` → বন্ধু + দের, with Avro's joins: মা + ের is মায়ের), ordered by how close each is to Avro's letter-for-letter reading, and last that reading itself. The keyboard's own Bangla word list is searched the same way, and the keyboard's fixed spellings and own reading are added, so the list can hold words desktop Avro lacks, never fewer than it has. Like desktop Avro, the keyboard remembers the word you picked for a spelling: pick কথা from the list once for `kotha`, and from then on কথা comes first and is what the space bar writes for `kotha`, until you pick something else. This works whether or not the candidate list is on, on any phonetic layout, and it is kept with your other learned data, so it pauses with learning and goes when you delete what the keyboard learned. It also survives a restart of the phone: before 0.6.6 the picks were read before the first unlock, when the keyboard can't reach its files, so after a restart they were neither loaded nor saved. **In the strip** keeps the word the space bar will write on the first chip and puts the list after it. **Bar above** puts the whole list in a row of its own over the strip, which you scroll sideways; the word the space bar will write is in bold there. The row stays up between words, so the keyboard does not change height as you type. The space bar writes the same word with every choice. - **Neural suggestions**: Languages & layouts / Bangla / Suggestion strip / Neural suggestions, off by default, shown while Avro is on. Adds up to three readings from IndicXlit, a small model that knows loose spellings such as `valobashi`, right after the word the space bar writes, which stays as it is unless the next switch is on. A 13 MB download shared by every language that has it, plus ONNX Runtime. On chat spellings the known-spellings list doesn't cover, it puts the right word in the strip's first four places 80% of the time instead of 52%. See [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions). - **Let it choose the word**: Languages & layouts / Bangla / Suggestion strip / Let it choose the word, off by default, shown while Neural suggestions is on. When Avro's spelling is not a known word and the known-spellings list has nothing for it, the space bar writes the model's best listed reading instead (`ahongkari` অহংকারী rather than আহংকারি), with Avro's spelling second in the strip. Right first word on chat spellings the list lacks: 53% instead of 42%. It can turn a name spelled exactly to Avro into a listed lookalike. See [Let it choose the word](https://wmkeyboard.pages.dev/languages/phonetic/#let-it-choose-the-word). - **Numerals**: two buttons, **০-৯** and **0-9**, and Bengali digits win by default. Switch to `0-9` if you'd rather see Latin digits on the number row while typing Bangla. Where the digits you pick actually get typed is a separate, global setting under Layout & size → Numerals. It defaults to native digits in text fields only, and plain ASCII in number, phone, date and time fields. - **Also suggest from**: pick other enabled languages to draw secondary suggestions from alongside Bangla. With English picked, Avro's strip also carries the word as you typed it in Latin letters, and an **English words** group appears right below: **Type English words as English** makes the space bar write `hello` as hello, and **Switch on the suggestion strip** shows or hides the small button that turns that on and off while you type. See [English on Avro and Hindi phonetic](https://wmkeyboard.pages.dev/smart/mixed-language/#english-on-avro-and-hindi-phonetic). - **Dictionary**: Bangla ships with a bundled dictionary, and a downloadable wordlist of about 451,000 words gives better suggestions. You can import your own list too. The downloaded list's frequencies are counted over 4.8 million sentences of Bangla news, web and Wikipedia text from the [Leipzig Corpora Collection](https://wortschatz.uni-leipzig.de/en), so words the known-spellings list doesn't cover rank by how common they are: `hoyechilo` commits হয়েছিল and `puroshkar` পুরস্কার. The bundled list's frequencies are added on top, which lifts the chat Bangla that news rarely writes. The phonetic index Avro and Banglish look words up in comes ready-made for the downloaded list, ranking included, from the data repository (`bn_phonetic_v1r2.wmpi.gz`, about 9 MB, fetched right after the list) and is read in place from storage, so the keyboard no longer folds all 451,000 words in memory each time it starts. Only the bundled list and your imported words are still folded on the phone, and until the file is there (no connection, or the build with no internet) the downloaded list is folded on the phone as before. The file is in the Bangla language pack, so [Offline imports](https://wmkeyboard.pages.dev/privacy/offline/) can install it too. See [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for how sizes compare across languages. - **Emoji**: download the larger Bangla emoji-keyword pack here, which names about 3,900 emoji. The bundled catalog's Bangla keywords already work without it. The pack arrives on its own if [Download automatically](https://wmkeyboard.pages.dev/emoji/customization/) is on. - **ANSI output**: Languages & layouts / Bangla / ANSI output / Allow ANSI output, off by default. Puts the ANSI button on every Bangla layout, see [Writing ANSI (Bijoy) text](#writing-ansi-bijoy-text). **ANSI version** appears below it once it's on, with three choices and version 2 as the default. - **Spellings**: Languages & layouts / Bangla / Spellings / Use known spellings, on by default. This is the list described above, and it's what makes `table` commit টেবিল rather than তাব্লে, and `tmr` commit তোমার rather than ত্ম্র. Switch it off if you want the letter-for-letter reading for every word. With it on, Avro's capitals still get you the exact letters you type. The setting is per language, so turning it off for Bangla says nothing about any other script. - **Dictionary words**: Languages & layouts / Bangla / Spellings / Correct to dictionary words, on by default. This is the phonetic fold described above, the step that turns `asi` into আছি when you press space. It runs whether or not autocorrect is on, because it's part of how Avro typing reaches real words. Switch it off and space always commits exactly what the Avro rules spell (`asi` is আসি); the dictionary words stay in the suggestion strip for you to tap. Known spellings are separate, so `tmr` still gives তোমার unless you turn that off too. - **The words before**: Languages & layouts / Bangla / Spellings / Use the words before, on by default. One spelling can stand for several words, and the one or two words you typed just before pick between them, on the strip and for what space writes: "ki pora" decides between পড়া and পরা by what came before. The evidence is your own typing and, where the language has one, the downloaded word-pair data. A known spelling only trades places with another known spelling of the same buffer, so the composing text is still what space writes. Turn it off if a word comes out differently after certain words than you expect. - **Clusters**: Languages & layouts / Bangla / Clusters / Conjunct-aware backspace, covered above, off by default. One more setting for Bangla lives outside this screen: - The **Bengali font** row, at Appearance / Keyboard font / Bengali font, is one of the per-script font sections. It shows only while a Bengali-script language is enabled, and it's separate from the general keyboard font. Your choice applies to all ten Bangla layouts at once. The section offers the automatic Noto face, eight Bengali-focused Google Fonts, anything in your font library that covers Bengali, and an import button for a `.ttf` or `.otf` of your own. Every script section takes an import, so this isn't Bengali's alone. ## Details & edge cases **"Banglish" is a different language, not a mode of this one.** Selecting Banglish · Bangla (Romanized) from the language catalogue gives you a plain Latin QWERTY that stays in Latin script (it never converts to Bengali script) and pulls suggestions from its own romanized word list. It has its own emoji keyword pack too: the Bangla keywords in the Latin spellings people chat in (`hasi` and `hashi` both find 😀, `biral` finds the cats, `bhat` and `vat` find 🍚), with the English keywords alongside. That's for people who want to text in Bangla using Latin letters and keep it that way. Avro, covered on this page, is the opposite: it converts what you type into Bengali script. Don't confuse the two if you're looking for one and land on the other in search results. **A few other languages share the Bengali script** but aren't the same language entry: Assamese, Bishnupriya Manipuri, and Sylheti all use Bengali-script layouts and Bengali-style numerals, but none of them bundle a dictionary the way Bangla does. If you're typing one of those, look for it by its own name in the language catalogue rather than under Bangla. **The near-tie protection can feel inconsistent if you don't know it's there.** Because a phonetic sibling only overrides the literal transliteration when it's at least twice as frequent, two words that are close in frequency won't always resolve the way you expect. This is deliberate, so the keyboard never silently swaps in a different word than what the composing preview showed you a moment before you hit space. It only applies to spellings the fixed list doesn't already name, since a listed spelling settles the commit before frequency is consulted at all. **The layout editor shows both shift labels at once for Probhat and Jatiya keys**, even though the real keyboard only shows one label per key state. Many fonts render a bare vowel sign (া, ি) as an orphaned, hard-to-read mark on its own, so the editor shows the base and shift labels side by side purely so you can tell keys apart while editing. This doesn't change what's drawn on the actual keyboard. **A rearranged Bangla layout still types Bangla.** If you open Probhat, Jatiya, or Avro in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and move keys around, the result still draws suggestions from the Bangla dictionary and still composes Bengali script. A layout's language comes from what it's based on, not from its key arrangement. --- # Burmese URL: https://wmkeyboard.pages.dev/languages/burmese/ Section: Languages Summary: Type Burmese in Unicode, write Zawgyi for the apps and friends that still use it, and convert Zawgyi text you paste, select or find in a text box. Burmese ships three layouts. **မြန်မာ (Burmese)** is the standard Myanmar3 arrangement and is what you get when you add Burmese. **မြန်မာ (Myanmar, letters row)** gives the consonants a row of their own, and **T9 (မြန်မာ)** is the keypad (see [Ambiguous layouts](https://wmkeyboard.pages.dev/languages/ambiguous/#t9-in-other-languages)). All three type Unicode. The key beside the spacebar types the section mark ။, with `.` first on its long-press, and the digit keys can type Burmese digits (see [Digits](https://wmkeyboard.pages.dev/languages/overview/)). ## Typing order You can type a syllable in the order it's drawn or in the order Unicode stores it, and either way it lands in Unicode. ေ and medial ra (ြ) are drawn to the left of their consonant, so many people type them first. Type ေ and then န and you get နေ, not a ေ with a dotted circle in front of န. A medial typed after that goes in front of the ေ too, so ေ, မ, ြ gives မြေ, and ေ, က, ျ gives ကျေ. Until the consonant arrives the ေ stands on its own, so it shows a dotted circle for that one keystroke. Converted [Keyman](https://wmkeyboard.pages.dev/languages/keyman/) layouts keep their own ordering rules. ## Turning it on Languages & layouts / မြန်မာ · Burmese / Layouts Add Burmese and the standard layout is on. Turn on the other two cards on the same screen if you want them in the switch ring. ## Zawgyi and Unicode A lot of Burmese text is still written in **Zawgyi**, the font encoding most phones and websites in Myanmar used before Unicode. Zawgyi uses the same characters as Unicode but stores them in the order they are drawn, so ေ comes before its consonant and medial ra takes one of several shapes depending on the letter under it. Text written for one reads as broken letters in the other. မြန်မာ in Unicode is the same five letters as ျမန္မာ in Zawgyi, just stored differently. The keyboard always types, suggests and corrects in Unicode. What it can do for Zawgyi is write it into a text box that wants it, notice Zawgyi where it would otherwise read it as Unicode, and convert between the two. It tells the encodings apart with the detector from Google's [myanmar-tools](https://github.com/google/myanmar-tools), the one Android and Facebook use, and converts with that library's rules. ## Writing Zawgyi Turn on **Allow Zawgyi output** on Burmese's own screen (see [Options](#options)) and every Burmese layout gets a **Zawgyi** button beside the suggestions. Tap it and what you type goes into the text box as Zawgyi, whether you typed it, picked it from the strip or pasted it from the clipboard panel. The button stays lit while it's on. Tap it again to go back to Unicode. - The word you're typing stays in Unicode while it's still being composed, and turns into Zawgyi when you finish it. Zawgyi puts some signs in front of the consonant they belong to, so a syllable only converts right once it's whole. In an app whose font is Zawgyi, the word therefore looks wrong until you finish it. - A layout that types letter by letter rewrites the last few letters each time you add one, so ေ moves in front of its consonant as soon as the consonant arrives. - Suggestions and next-word predictions keep working. The keyboard reads the text before the cursor back into Unicode first, so it sees the words you typed and not their Zawgyi order. - Text that is already Zawgyi, such as a message you copied from a Zawgyi chat, goes in as it is instead of being converted a second time. - Backspace removes one Zawgyi character at a time, and the keyboard doesn't pick a word back up for editing when you backspace into it. - Zawgyi only reads right in a Zawgyi font. Switch the button off in apps that use Unicode. ## When the text is already Zawgyi The keyboard checks two places for Zawgyi and offers to deal with it on the suggestion strip. - **After a paste.** Paste Zawgyi from the clipboard panel or the clipboard chip into a box that isn't being written in Zawgyi and the strip shows **Pasted Zawgyi. Convert to Unicode**. Tap it and the pasted text is replaced with its Unicode. The chip goes away when you type on or tap its ✕. It works on any layout. - **In the text box you type into.** On a Burmese layout, when the text before the cursor is Zawgyi, the strip shows **This text is Zawgyi. Write Zawgyi here**. Tap it and the keyboard writes Zawgyi in that text box until you leave it, even with the Zawgyi button off, so your reply matches the conversation. Either way, the keyboard reads that text as Unicode for its suggestions. The chip is offered once per text box. The keyboard only speaks up when it's sure. It needs a few letters to go on, and short phrases that spell something in both encodings never raise a chip. On upstream's test text no Unicode line was read as Zawgyi. ## Converting text - **Selection actions.** Select Burmese text and the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/) offers **Zawgyi ⇄ Unicode**. It converts whichever way the text needs: Zawgyi becomes Unicode, Unicode becomes Zawgyi. Undo puts the original back. - **The clipboard.** In the [clipboard panel](https://wmkeyboard.pages.dev/tools/clipboard/#picking-several-clips), pick clips and press **Zawgyi ⇄ Unicode** on the bar above them. Each clip with Burmese in it is converted the way it needs and keeps its place in the history. Clips an app marked as a password are left alone. ## Unicode key font A phone set up for Zawgyi usually has a Zawgyi font in place of its Myanmar font, and that font draws Unicode in the wrong order. The keys and the suggestions are Unicode, so on such a phone they can look broken. The keyboard normally draws Burmese in Noto Sans Myanmar from the phone's font provider, which fixes this where the provider is there, but many of the phones that use Zawgyi don't have it. Turn on **Unicode key font** (it appears once Zawgyi output is allowed) and the keyboard draws Burmese in its own copy of Noto Sans Myanmar, whatever font the phone uses. The font comes from WM Keyboard's data repository (195 KB) the first time the keys need it, and the keys use the phone's font until it lands. A Myanmar font you picked under [Fonts](https://wmkeyboard.pages.dev/themes/fonts/) still comes first. ## Options Languages & layouts / မြန်မာ · Burmese - **Allow Zawgyi output**: Languages & layouts / မြန်မာ · Burmese / Zawgyi / Allow Zawgyi output, off by default. Puts the Zawgyi button on every Burmese layout, see [Writing Zawgyi](#writing-zawgyi). - **Unicode key font**: Languages & layouts / မြန်မာ · Burmese / Zawgyi / Unicode key font, off by default, shown once Zawgyi output is allowed. See [Unicode key font](#unicode-key-font). - **Zawgyi ⇄ Unicode** on the selection bar is on by default, and only shows on Burmese text. Switch it off under Advanced / Selection actions / Actions. ## Details & edge cases - The detector is a statistical model, so it judges a whole stretch of text rather than one letter. Digits don't count as Burmese for it, and text with no Burmese in it is never offered anything. - Converting Unicode to Zawgyi and back gives the text you started with, with two exceptions that come from the conversion rules. A ၄ standing on its own comes back as ၎, because Zawgyi writers type the digit four for that letter. And in a word like မှဖြစ် the medial ra can come back on the consonant before the one it belonged to. - Unicode allows ် and ့ in either order. Converted text comes back with ့ first, which is the standard order and looks the same. - Zawgyi is a Burmese encoding. The Shan, Mon, Pa'O and Rakhine layouts don't get the button. --- # Chinese, Japanese & Korean URL: https://wmkeyboard.pages.dev/languages/cjk/ Section: Languages Summary: Composer-based CJK input with a candidate bar and on-device learning. Chinese and Japanese aren't typed directly: you type a *reading* (pinyin, bopomofo, a radical code, romaji) and pick the character it means from a candidate bar. Korean is different: Hangul jamo assemble into syllable blocks on their own, so it types and suggests like any other language. This page covers both. languages/cjk-pinyin-candidate-strip — Chinese Pinyin: the candidate strip replaces the usual word-suggestion row while you're composing a reading. ## Two different input flows **Conversion input methods** (Pinyin, ClearFlow Pinyin, T9 Pinyin, Zhuyin, Cangjie, Cangjie Quick, Stroke for Chinese; Jyutping, Yale and Cantonese Pinyin for Cantonese; Romaji, Godan, Flick, Kana JIS and 50 keys for Japanese) hold what you type in a composing buffer and replace the entire suggestion strip with ranked character/word candidates. Nothing commits until you pick a candidate, press space, or otherwise flush the buffer. **Korean** types the other way. `HangulComposer` composes jamo directly into syllable blocks (ㄱ + ㅏ + ㄴ → 간) with no reading step and no candidate list. There's nothing to convert unless you want Hanja, which [the Korean options](#hanja) can add to the strip. Five keymaps ship: the standard two-set (두벌식) grid, where one key is ㄱ whether it starts or ends a syllable and the composer decides by position, the two three-set (세벌식) grids, 390 and Final, whose keys say for themselves whether they are an initial or a final, and the two 12-key pads, 천지인 (Cheonjiin) and 나랏글 (Naratgeul). On a three-set grid a tense initial is typed by pressing the plain one twice (ᄀ ᄀ → ㄲ), and a vowel after a final never pulls that final into the next syllable. Syllables build the same way in the keyboard's own text boxes, such as the emoji, GIF and clipboard searches, Find and replace, and the AI prompt: backspace there takes back the last jamo rather than the whole block. The same goes for the other keyboards that build letters from keys, Vietnamese Telex and VNI and Khipro. Korean typing gets the ordinary word-suggestion and autocorrect strip instead, backed by a real downloadable dictionary (see [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/)). If you've used a Chinese or Japanese IME before, don't expect Korean to work the same way. It's a direct phonetic keyboard. ### Hanja Korean can offer the Hanja for the word you are typing. Open **Korean options** on the language's screen, download the **Hanja (漢字) dictionary** (about 7 MB, 300,000 entries) and turn on **Offer Hanja**. It's off by default, since most Korean text has none. The choices sit behind the first word on the strip. A tap writes one in place of the Hangul with no space after it, and nothing is learned from it. An ending stays in Hangul: 학교에 offers 學校에. With few slots on the strip you see only the first few, so turn on the scrolling suggestion strip to reach the rest. ### Fix spacing Selecting Korean text can offer **Fix spacing**, which corrects the spaces between words: 나는학교에간다 becomes 나는 학교에 간다. It runs on the phone with the [Kiwi](https://github.com/bab2min/Kiwi) analyser, and nothing leaves the phone. It needs two downloads from **Korean options**: the **Spacing correction library** (about 18 MB) and the **Spacing correction model** (88 MB, about 100 MB unpacked), and the action stays out of the selection row until both are there. The first use in a while takes a few seconds, since the model opens and takes hundreds of megabytes of memory, and it's closed again a minute after you stop. Only phones with a 64-bit processor can run it, and it isn't in the F-Droid build. **천지인 (Cheonjiin)** is the phone keypad, laid out the way Samsung's is. Vowels are drawn from three strokes, ㅣ, ㆍ and ㅡ, in the order you would write them: ㅣ ㆍ is ㅏ, ㆍ ㅣ is ㅓ, ㆍ ㅡ is ㅗ, ㅡ ㆍ is ㅜ, a second dot gives the y-vowel (ㅣ ㆍ ㆍ is ㅑ), and a ㅣ after a vowel gives ㅐ, ㅔ, ㅚ, ㅟ and the rest. A dot that isn't part of a vowel yet waits on screen as ㆍ or ‥, and a third dot goes back to one. Each consonant key holds two or three letters, picked by tapping it again: ㄱㅋ once is ㄱ, twice ㅋ, three times ㄲ. To type the same consonant twice in a row (the ㄱ ㄱ in 먹고), either pause for a second before the second tap or press Space between them. A space straight after a consonant tap only ends that letter and types nothing; press it again for a real space. The .,?! key cycles through its four marks the same way. Delete takes back one tap: a whole consonant, or one stroke of a vowel (ㅏ back to ㅣ). **나랏글 (Naratgeul)** is the other phone keypad, laid out the way Samsung's is. It has keys for six consonants, ㄱ ㄴ ㄹ ㅁ ㅅ ㅇ, and for the vowels ㅏㅓ, ㅗㅜ, ㅣ and ㅡ. Tap ㅏㅓ once for ㅏ and again for ㅓ, and ㅗㅜ the same way. Every other letter is built from these with two keys that change the last letter you typed. 획추가 adds a stroke and goes round: ㄱ ㅋ, ㄴ ㄷ ㅌ, ㅁ ㅂ ㅍ, ㅅ ㅈ ㅊ, ㅇ ㅎ, and ㅏ ㅑ, ㅓ ㅕ, ㅗ ㅛ, ㅜ ㅠ. 쌍자음 doubles a consonant and undoubles it: ㄱ ㄲ, ㄷ ㄸ, ㅂ ㅃ, ㅅ ㅆ, ㅈ ㅉ. On a vowel it gives the y-form, the same as 획추가: ㅏ ㅑ, ㅓ ㅕ, ㅗ ㅛ, ㅜ ㅠ, and ㅐ ㅒ, ㅔ ㅖ. Both work on a syllable you are still building, so 가 becomes 갸, 롬 becomes 롭 and then 롶, and 각 becomes 갂. A letter that can't stay in the syllable moves out of it: 밥 doubled is 바ㅃ, and 낛 with a stroke is 낙ㅈ. ㅐ, ㅒ, ㅔ and ㅖ have no key: type the vowel and then ㅣ (ㅏ ㅣ is ㅐ), and 획추가 or 쌍자음 on ㅐ gives ㅒ. Once a word is finished with Space or a mark, the two keys do nothing to it. Both keys are also in the [layout editor's](https://wmkeyboard.pages.dev/languages/custom-layouts/) action picker, for a pad of your own. **10 키 (10 key)** is the same pad in Gboard's arrangement: ▶ ends a consonant's cycle without waiting and without a space, so ㄱ, ▶, ㄱ is two ㄱ, and . and , sit either side of ㅇㅁ in place of the .,?! cycle. **천지인 플러스 (Cheonjiin Plus)** builds vowels from the same three strokes as 천지인, and gives every consonant a key of its own, so most letters take one tap. Tap ㅋ ㅌ ㅍ ㅎ ㅊ again for the tense letter beside them: ㄲ, ㄸ, ㅃ, ㅆ, ㅉ. **베가 (Vega)** is a phone keypad where every key holds a cycle of letters, vowels included. Tap ㄱㅋ for ㄱ, ㅋ, ㄲ and ㅏㅑ for ㅏ, ㅑ. ㅣㅡ runs ㅣ, ㅡ, ㅢ. As on 천지인, a pause or a Space ends a cycle, so ㄱ, Space, ㄱ is two of them, and ㅐ is ㅏ then ㅣ. **모아키 (Moakey)** types a whole syllable in one stroke. Drag a consonant key towards a vowel: right for ㅏ, left for ㅓ, up for ㅗ, down for ㅜ, either upper corner for ㅣ, either lower corner for ㅡ. Bend the drag without lifting for the rest. Right and back is ㅐ, forward again ㅑ, back again ㅒ, and left works the same way for ㅔ, ㅕ, ㅖ. Up and back is ㅚ, forward again ㅛ, and up then right is ㅘ. Down and back is ㅟ, forward again ㅠ, and down then left is ㅝ. A lower corner then up is ㅢ. Tap a consonant to end the syllable before it, and one backspace takes back one stroke. **모아키 양손 (Moakey two-hand)** adds ㅣ, ㅡ and ㆍ keys: ㅣ joins the vowel before it (ㅏ ㅣ is ㅐ) and ㆍ gives its y-form (ㅏ ㆍ is ㅑ, and ㆍ again is ㅏ). 나랏글 and 베가 also come centred, with the letters in the middle columns and punctuation down the left. ## Chinese input schemes Chinese carries ten typing layouts. Adding it from the language catalogue turns on Pinyin alone, and the other nine are a tap away on Chinese's own screen: languages/cjk-cangjie-keys — Cangjie: radical keys build a shape code; the strip fills in once the code is complete. | Layout | Reading unit | Buffer commits | |---|---|---| | Pinyin | Romanized syllables (`ni`, `hao`) | Prefix: a multi-syllable buffer offers the whole phrase plus its leading syllables | | ClearFlow Pinyin | The same syllables on the [ClearFlow](https://wmkeyboard.pages.dev/languages/overview/) grid | Prefix | | T9 Pinyin | Same syllables on a 9-key/T9 grid (`ABC`/`DEF`/`GHI`…) | Prefix | | Zhuyin (Bopomofo) | ㄅㄆㄇㄈ… symbols, with four dedicated tone-mark keys (ˇˋˊ˙) | Prefix | | 12-key Zhuyin | The same symbols on ten keys of three or four each. The tone key only ends a syllable, since the dictionary has no tones | Prefix | | Cangjie (倉頡) | Up to five radical keys, a–y, each printed as its radical glyph | Whole buffer: one code run is one character | | Cangjie Quick (速成) | Just the first and last radical of a character | Whole buffer | | Wubi 86 (五笔) | Up to four root components, on the letters a–y. Each key's corner shows its stroke zone and root, and z stands in for one you aren't sure of | Four keys at most: one code run is one character or phrase | | Stroke (笔画) | Five stroke-class keys (一丨丿丶乙) plus a `*` wildcard for an uncertain stroke | Whole buffer | | 12-key stroke | The same strokes on a phone keypad, with 通 as the wildcard | Whole buffer | Pinyin, T9 Pinyin, and Zhuyin are all prefix-commit: type `nihao` and the strip offers 你好 first, then leading-syllable candidates like 你 alone. Tapping one consumes only the part of the buffer it covers and re-converts the rest, so you don't have to clear the buffer between characters. Cangjie, Cangjie Quick, and Stroke are the opposite: shape and stroke codes map to exactly one character each, so a commit always consumes everything you've typed. Each letter on the Pinyin layout carries a digit or symbol on long-press, shown in its corner as on the English layout: the digits on the top row, then `@ # ¥ % & - + ( )` across the middle row and `* " ' : ; ! ?` across the bottom one. With the number row on, the top row's corners stay empty, since its digits are already above it. To give a key other alternates, open the layout in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and change that key's **Press and hold** list. Wubi's letters are grouped by the first stroke of the roots on them: 一 on g f d s a, 丨 on h j k l m, 丿 on t r e w q, 丶 on y u i o p and 乙 on n b v c x. A code spells a phrase as well as a character: `khlg` offers 中国 and `wyth` offers 信息. An exact code comes first, then whatever starts with it, commonest first. A fifth key starts the next code, so `wythkhlg` offers 信息, and picking it leaves `khlg` converting. The table is Wubi 86 only; a Wubi 98 code spells something else. Like Cangjie and Stroke, Wubi does not learn from your picks. Zhuyin, 12-key Zhuyin and T9 Pinyin don't need their own dictionary. They reuse the Pinyin pack and translate each segmented syllable to its pinyin spelling. Cangjie and Cangjie Quick share one radical-code table. Stroke and Jyutping (below) each have their own pack. **Picking syllables on T9 Pinyin.** Each key on the T9 pad stands for three or four letters, so the keys you press could spell several syllables, and the keyboard guesses which. The column down the left of the pad shows what the next syllable could be while you type. WXYZ GHI ABC MNO lists xiao, xian, zhao and zhan, then xia and zha, then xi, yi and zi: longest first, with the keyboard's own guess at the top. Tap one and that syllable is settled. The strip then offers only characters read that way, and the column moves on to the next syllable, so a phrase the strip keeps getting wrong (优点真遗恨 for 有点小遗憾) can be pinned down one syllable at a time. The text you are typing shows a `'` between syllables, so you can see how your keys were split. Delete takes back the last key you pressed, and once only settled syllables are left it unsettles the last one, back to its keys. While you aren't typing a reading, the column lists punctuation (,。?!、:; and more) to tap instead. The column is in the [layout editor's](https://wmkeyboard.pages.dev/languages/custom-layouts/) action picker too, as "Pinyin syllable list", for a pad of your own. **ClearFlow Pinyin** is Pinyin with the letters where ClearFlow puts them: the commonest ones in the middle, and tall keys down both sides. It reads the same Pinyin dictionary and takes the same settings, so Jianpin, Fuzzy Pinyin, Double Pinyin and swiping all work on it. Shift's tall key is '词 here, as on the Pinyin layout, and the top corners type ! and ?. One thing stays QWERTY: [a missed key](#a-missed-key) is repaired from the keys beside it on a QWERTY board, so a slip on this grid is less often caught. ### Cantonese: Jyutping, Yale and Cantonese Pinyin Cantonese is a separate language entry (not a Chinese sub-option) with three layouts, one for each way of spelling it. All three are standard QWERTY plus a permanent digit row, all three are prefix-commit conversion schemes like Pinyin, and all three read the same 163k-entry dictionary. Adding Cantonese turns on Jyutping alone, and the other two are a tap away on its screen. | Layout | 你好 | 粵語 | 水 | How it differs from Jyutping | |---|---|---|---|---| | Jyutping (粵拼) | `neihou` | `jyutjyu` | `seoi` | | | Yale (耶魯) | `neihou`, `neihhou` | `yutyu`, `yuhtyuh` | `seui` | `j` and `ch` for `z` and `c`, `y` for `j`, `eu` for `oe` and `eo`, and `a` for an open `aa` (媽 is `ma`) | | Cantonese Pinyin (教院拼音) | `neihou` | `jytjy` | `soey` | `dz` and `ts` for `z` and `c`, `y` for `yu`, `oey` for `eoi`, and `oe` for `eo` | Tones are optional on all three, and they narrow nothing yet. Jyutping and Yale write them as a digit from 1 to 6 after the syllable. Cantonese Pinyin also takes 7, 8 and 9 for the entering tones, so 粵語 can be `jyt9jy5`. Yale can mark a low tone with an `h` after the vowel instead, as in `sihk` and `neih`, and you can leave it out: `neihou` could be `nei` + `hou` or `neih` + `ou`, and the keyboard reads both and lets the words decide. Yale and Cantonese Pinyin are Jyutping's dictionary under another spelling, so they need no download of their own. What you pick on one of them moves up on the others, and your own Cantonese word lists work on all three. They always convert on the built-in engine, even with [RIME](#the-rime-engine) picked for Cantonese, because RIME's Cantonese schema spells Jyutping. ## Double Pinyin For Pinyin typists who want fewer keystrokes, Double Pinyin encodes every syllable as exactly two keys (one for the initial, one for the final), which the composer expands to full Pinyin before segmenting as usual. Five real schemes are available: Microsoft, Sogou (identical key table to Microsoft), Xiaohe (小鹤), Ziranma, and Pinyin++. Turning one on replaces the syllable-typing rules on the Pinyin and ClearFlow Pinyin layouts. It isn't a separate keyboard, because the keys stay the same. ### Your own scheme Pick **Custom** in the scheme list, or open **Custom Double Pinyin scheme** below it, to set every key yourself: Languages & layouts / Chinese / Custom Double Pinyin scheme The first time you pick Custom, the editor opens on a copy of the scheme you were typing with (Xiaohe if you were on full Pinyin), so you change a working table rather than start from nothing. **Start from a scheme** in the menu swaps in another built-in table the same way. Nothing changes on the keyboard until you press **Save**, and saving also switches you to Custom. The editor always opens with three `#` comment lines that explain the format, and a scheme you paste or import without them gets them added on top. While the custom scheme has nothing but comments you type full Pinyin. The scheme is plain text in the same format as fcitx's `sp.dat`, so a scheme file written for fcitx works here unchanged, and one you export works in fcitx. The menu has **Copy** and **Paste** for sharing it as text, and **Import** and **Export** for files. One line per key: ``` 方案名称=My scheme [声母] zh=v ch=i sh=u [韵母] iu=q ing=k uai=k [零声母] =aeo ang=ah ``` - `pinyin=K` puts an initial or a final on key K. Several finals can share a key, and the one that makes a real syllable wins. The one-letter initials (b, p, m and so on) stay on their own keys unless a line moves them, so you only have to say where zh, ch and sh go. - `=keys` lists the keys that start a syllable with no initial, followed by the final's key, so `oj` is *an* when `o` is listed. A `*` among them is fcitx's rule for schemes without such a key: a one-letter final is typed twice (`aa`), a two-letter one as itself (`ai`), and a longer one as its first letter and its own key (`ah`). With no `=` line, a, e and o are the lead keys, as in the built-in schemes. - `pinyin=KK`, with two keys, spells one whole syllable and wins over everything else. - `#` starts a comment, lines in square brackets are only headings, case doesn't matter, and `ü` can be written `v`. Under the text, a status line names the first line the scheme skips and why (tap it to select that line), or else every initial and final that no key types yet. Only the first scheme in a file is read: fcitx's own `sp.dat` holds several in a row, each opened by its `方案名称=` line, so delete the ones you don't want or let the first one stand. ## Jianpin (简拼): initials are enough Nobody types 中国人民 as `zhongguorenmin`. **Jianpin** lets a syllable be its first letter alone, so `zgrm` reaches the same word, `wm` finds 我们 and `bj` finds 北京. It's on by default, the way every desktop and phone Pinyin IME has it, and it's a switch on the Chinese screen if you'd rather not. The rule is one letter, one syllable, boundaries included: `hd` means *h…* plus *d…*, never "any reading with an h and a d in it". That's what makes mixed input work too. Spell out as many syllables as you like and abbreviate the rest: `haod` finds 好的, `zguo` finds 中国, `beij` finds 北京. A syllable you type in full is matched exactly and never widened, so `hao` still means 好 and nothing beginning with *hao*. Three conventions borrowed from Sogou and Google Pinyin: - **`z`, `c` and `s` cover the retroflex initials too.** `zg` finds 中国 (zhōng guó) as well as 祖国 (zǔ guó). A typed `zh`, `ch` or `sh` reads both ways: `sh` is s + h (上海, 生活) and also the initial sh… on its own (是, 说), and `zhg` still reaches 中国. Both are on the strip and rank by likelihood. An apostrophe is your own boundary: `s'h` is s + h only. - **The vowels `a`, `e` and `o` are both a syllable and an abbreviation.** `aq` finds 安全. Typed on their own, the exact reading leads: `a` offers 啊 before 爱 and 安. - **A phrase beats a stitch.** Where the dictionary has the word (我们, 中国), it comes first; single characters strung together to cover the same letters rank below it. So the strip leads with what the abbreviation was for, and the ranking the issue asks for falls out of that: an exact reading first, then a partly spelled one, then pure initials. ### Everyday pairings A lot of Chinese isn't dictionary words. 我的 and 我不 are two single characters that turn up together constantly, and no dictionary lists either as an entry. Ranked one word at a time, `wbxh` came out as 亡八喜欢: two real words beat four, whatever the words are. The **Chinese word pairs** pack fixes that. It counts which words follow which in a large body of everyday Chinese (film and TV subtitles, plus web and news text), so the keyboard can tell that 不 after 我 and 喜欢 after 不 are what people write. With it, `wbxh` leads with 我不喜欢, `wbxz` with 我不想再, `wdmm` with 我的妈妈, `nine` with 你呢 and `hd` with 好的. It comes with the Pinyin dictionary when you download that, and if you already had the dictionary, a chip on the strip offers it once: "Download Chinese word pairs for better candidates". It's about 11 MB. T9 Pinyin and Zhuyin use it too. Without the pairs, from three abbreviated letters up and only where no single entry covers everything you typed, the strip carries a few stitched readings right after its first answer: whole-buffer ways of reading the letters as separate words, so 我的妈妈 for `wdmm` is still a tap away. Learning steers the search as well as the finished list. The characters you keep choosing for a reading are the ones paths get built out of, so the longer you type, the more the readings come out in your own words. Two things follow from abbreviations being real units: - **Candidates appear from the first key.** `n` alone offers 你, 那, 能 and so on, and `nih` already leads with 你好, so a whole word is often on the strip before its second syllable is finished. - **A half-typed trailing syllable is guessed, not ignored.** With `nihaoz` on the strip, the top candidate covers all six letters (你好在, say), where before it would have covered 你好 and left the `z`. Keep typing and the guess resolves; tap 你好 and only those five letters commit, the `z` stays. ### Two readings of the same letters Some letters split into syllables more than one way. `xian` is one syllable (先, 现) and also two (西安, Xi'an); `nine` is `nin` + `e` and also `ni` + `ne` (你呢); `fangan` is 方案. Pinyin marks the second kind with an apostrophe, which almost nobody types on a phone, so the keyboard reads both and the more likely reading wins: `xian` offers 先 and 现 first and 西安 a few places on, and `nine` leads with 你呢. Type the apostrophe and it's settled: `xi'an` is two syllables only, so 先 is no longer offered. Splits that need more abbreviations, or more syllables, than the letters require are left out. `xian` never reads as `xia` + `n` (下年), and `nihao` never as `ni` + `ha` + `o`. ### A missed key Where the letters you typed only make sense as a run of initials, the keyboard also tries the slips a finger makes: each letter as the key next to it, two letters the wrong way round, one letter too many and one too few. `xiqn` reads as `xian` (the `a` beside the `q` missed), `qign` as `qing`, `zhng` as `zhong` and `zhonng` as `zhong`, and the repair is offered alongside whatever the initials spell. It only looks where the letters need two or more bare initials to read at all, so ordinary pinyin and deliberate initials (`wbxh`) are never second-guessed. A repair is only offered while it reads nearly as well as the letters as typed, at least a tenth as likely, so a slip never pushes a real reading of your letters off the strip. Two slips come from the keys around the letters rather than among them (since 0.6.6). A thumb aiming for the space bar can catch the row above it, so one of `c`, `v`, `b`, `n` or `m` alone between two syllables can be read as a missed space and left out: `nihaobwo` offers 你好我. With Jianpin off, that letter used to have no reading at all and stopped the conversion where it sat. With Jianpin on it is also an initial (`b` for 不), and both readings are offered. A letter that starts a real syllable, like the `b` of `bu`, is never left out. And with [Fix number row slips](https://wmkeyboard.pages.dev/smart/autocorrect/#options) on, a digit typed in the middle of a reading is the letter under it on the number row, so `niha9` converts as *nihao*. ### Other spellings Some syllables have more than one spelling in use, and a few slips are so common that every Pinyin keyboard takes them. They all read as the syllable they stand for, with nothing to switch on: - ü as `u` or `v`: `lue` and `nue` for lüe and nüe, `jv`, `qv`, `xv` and `yv` for ju, qu, xu and yu. - The long forms that pinyin shortens: `liou` for liu, `guei` for gui, `duen` for dun. - `gn` for `ng` (`bagn` is bang) and `on` for `ong` (`zhon` is zhong). A spelling that is a syllable in its own right keeps its own meaning: `lu` is always lu. ### Before you finish the syllable The last syllable can be unfinished. `zho` at the end of what you typed also reads as zhong and zhou, `nia` as nian, niang and niao, so 中 and 年 are on the strip before you type the rest. This only applies to the last few letters, and only where they aren't a syllable already. ### Rare characters Every character has a place in the ranking from how often it shows up in a large body of text, and for rare characters that count is unreliable: one odd file is enough to put a character nobody writes at the top of its reading. The keyboard also knows which characters China's standard list of common characters (通用规范汉字表) includes and at which of its three levels. A character outside the list, or in its rarer levels, sinks below the everyday ones that share its reading. This list is part of the app, not a download. ### Dates and numbers Languages & layouts / (a CJK language) / (language) options / Date, time and numbers Some letters type things that aren't in any dictionary: - `rq` (日期) offers today's date: 2026年10月13日, 二〇二六年十月十三日, 2026-10-13 and 10月13日. - `sj` (时间) offers the time: 14:05, 14点5分 and 下午2点5分. - `xq` (星期) offers the weekday: 星期二, 周二 and 礼拜二. These come right after the best candidate for the same letters, so `sj` still leads with 时间. Picking one teaches the keyboard nothing. Type `v` and then a number, with a decimal point if you like, for the number in Chinese numerals: `v1234.5` offers 一千二百三十四点五, the financial 壹仟贰佰叁拾肆点伍, 一二三四点五 digit by digit, and for whole numbers the amount 壹仟贰佰叁拾肆元整. No pinyin syllable starts with `v`, so this never changes what pinyin reads. All of this is on by default and one switch turns it off. ### Narrowing the list by strokes When the character you want is far down a long list, narrow it by how the character is written. Hold '词 and slide to the backtick, then type the first strokes with the letters fcitx and Sogou use: `h` for 横 (一), `s` for 竖 (丨), `p` for 撇 (丿), `d` or `n` for 点 (丶) and `z` for 折 (乙). `yi` then `` `z `` leaves 乙 and the other characters that start with 折; the composing text shows the strokes as you type them. Picking a candidate ends the filter, and only the letters that candidate covers are used up. It needs the Stroke dictionary download, which is where the stroke order comes from. Without it the filter narrows nothing. ### The word separator key On the Pinyin layout the key left of z is '词 ('詞 with Traditional characters on), not Shift. It types the apostrophe that marks a syllable boundary, so `xi'an` and `s'h` take one tap each, the way Gboard's Pinyin keyboard has it. Hold it and let go to switch between Simplified and Traditional characters, see [Switching scripts from the keyboard](#switching-scripts-from-the-keyboard); the backtick is the second entry in the same popup. ### Swiping You can swipe on the Pinyin layout, with [gesture typing](https://wmkeyboard.pages.dev/typing/gestures/) on. A swipe spells a reading, either the full pinyin (`nihao`) or the initials of a word (`wm` for 我们), and the reading goes into the text you're composing as if you'd typed it, so the strip converts it the usual way and a second swipe carries the same sentence on. Nothing commits and no space follows. It needs the Pinyin dictionary, since that is where the readings come from, and it's off under a Double Pinyin scheme. ### The rest of it Jianpin is a matching rule, not a second dictionary. Its index is the ~410-syllable inventory grouped by first letter, so a run of initials costs a handful of binary searches per syllable rather than a scan. It has no effect under a Double Pinyin scheme, where a syllable is always exactly two keys, and it's independent of Fuzzy Pinyin: a syllable typed in full can still be matched fuzzily, an abbreviated one is expanded, and the two combine. Picks are learned under the reading the abbreviation resolved to, so choosing 我们 for `wm` also promotes it for `women`. ## Fuzzy Pinyin and lazy Jyutping Two independent toggles relax reading matching for imprecise typing: - **Fuzzy Pinyin** treats commonly-confused sounds as equivalent, so a syllable typed loosely still finds its characters. It's eleven switches rather than one: six initial groups (zh↔z, ch↔c, sh↔s, n↔l, r↔l, f↔h) and five nasal-ending groups (an↔ang, en↔eng, in↔ing, ian↔iang, uan↔uang), each with a row of its own and all on once the master switch is on. That way you can take the regional nasal endings without paying for n↔l on every syllable that starts with either. Switch one off and a **Match every pair again** row appears to put them all back. - **Lazy pronunciation (懶音)**, Cantonese-only, matches the sound mergers common in Hong Kong speech: n↔l, an initial ng- that can drop entirely (我 *ngo5* → *o5*), gw→g and kw→k before a rounded final only (國 *gwok3* → *gok3*, but never 誇 *kwaa1* → *kaa1*), and the coda mergers toward the alveolar place (-ng/-n, -m/-n, -k/-t and -p/-t). It also takes z↔j and c↔ch on the Jyutping layout, which aren't 懶音 at all but Yale-era romanization habit. The Yale and Cantonese Pinyin layouts leave those two out, since their own spelling already says which sound a key means. Every merger works in both directions, because hypercorrection is as common as the merger itself. Fuzzy Pinyin is off by default and lazy pronunciation is on, and they change ranking rather than what you can type. A precisely-typed reading still works exactly as before. Japanese has a matching of its own for the flick pad, covered under [Kana without marks](#kana-without-marks). ## Traditional characters and regional wording A single toggle switches candidate output from Simplified to Traditional Han, applied as each composer builds its candidate list (not as a find-and-replace afterward, which would break prefix-commit matching). Many Simplified characters have more than one Traditional form, and only the word tells which: 发 is 發 in 發展 but 髮 in 頭髮, 后 is 後 in 後來 but stays 后 in 皇后, 干 is 幹 in 幹部 but 乾 in 乾淨. The keyboard converts word by word with the [OpenCC](https://github.com/BYVoid/OpenCC) phrase and character tables (release 1.4.2), using OpenCC's own matching: the candidate is split into the longest phrases it contains, each phrase converts as a unit, and only a character that is in no phrase falls back to its most common form. A candidate the keyboard stitched from several words is split the same way, so 头发干净 comes out as 頭髮乾淨. A character typed on its own has no word to decide it, so Pinyin, T9 Pinyin, Zhuyin and Jyutping list each of its Traditional forms, the most common first: `fa` offers 發 and then 髮, `gan` offers 幹, 乾 and 干, `hou` offers 後 and 后. Cangjie and Stroke do not, because their codes describe the shape of one character. The character tables ship with the app. The phrase table is the **Traditional characters phrase table** pack, about 1 MB, which you download from the Chinese or Cantonese options page like the other tables below. It is one download: the row on either page is the same file. Without it, Traditional output still works, but one character at a time: each character takes its most common form, so 头发 comes out as 頭發. Conversion itself never goes online. The tables load the first time you turn the toggle on, so for a moment after that the candidates can still show in Simplified. With Traditional on, a second control appears: a region picker (Standard / Taiwan / Taiwan wording / Hong Kong). **Taiwan** follows OpenCC's `s2tw`: Taiwan's character preferences (為, 裡, 著), and the word stays the one you typed, so `chuzuche` gives 出租車 and `jichengche` gives 計程車. **Taiwan wording** follows OpenCC's `s2twp`, which also swaps regional vocabulary: Taiwan's 計程車 instead of the mainland's 出租車 for "taxi," for instance, even though you typed the reading of 出租車. Vocabulary is matched inside a candidate too, so 出租车光盘 becomes 計程車光碟. It is a separate choice because a swap can also change the meaning: 支持 becomes 支援 and 文件 becomes 檔案 in every sense of the word. **Hong Kong** follows OpenCC's `s2hk`: the standard wording with Hong Kong's character preferences (説, 衞, 温). The Hong Kong choices come straight from OpenCC's tables and have not been reviewed by a Hong Kong writer yet; a wrong one is worth reporting. The region applies under the RIME engine too, whichever schema is in use. ### Switching scripts from the keyboard For a message in the other script you don't need the settings app. Two switches flip **Traditional characters** from the keys, and each one changes the same setting as the row on the options page, so it stays the way you left it: - **Hold '词** on the Pinyin layout and let go. A toast says which script the keyboard now writes. - **The 繁 button.** Turn on **繁 button** under Languages & layouts / Chinese / Chinese options / 繁 button (also on the Cantonese page; off by default). A 繁 button then sits beside the candidates on every Chinese and Cantonese layout, lit while candidates come out Traditional. Like the other buttons beside the words, it shows while the candidate strip is up, not while the toolbar is. A reading you are in the middle of typing switches with it: the candidates on the strip are drawn again in the new script, so you can type `toufa`, see 头发, switch and pick 頭髮. Any layout can carry the switch too: in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) it's the **Simplified / Traditional Chinese** action, and in a layout file it's `{"type": "han_script"}`. Both controls are on the Chinese and Cantonese options pages only, and they are one setting shared by the two languages. Japanese candidates never pass through this conversion, so the Japanese page does not show them. ## Handwriting layouts Chinese, Japanese and Korean each also ship a **Handwriting** layout, as do [296 other languages](https://wmkeyboard.pages.dev/tools/handwriting/#handwriting-as-a-layout), for the character you can't spell: a kanji whose reading you don't know, a name, a hanzi you only recognise by shape. Enable it like any other layout of the language, and the keys above the bottom row become a writing area. Write one or several characters, pause, and the best reading goes into your text while the others wait in the suggestion strip for a tap to swap them in. The bottom row keeps ?123, the globe, 、 and 。 (Chinese: ,), space, delete and enter, so you can switch back to your typing layout with one tap. Characters written one after another are never separated by a space. It is the same on-device recognition as the [Handwriting tool](https://wmkeyboard.pages.dev/tools/handwriting/), so the language needs its model downloaded once; the writing area offers the download itself. The F-Droid build reads Chinese and Japanese here with [zinnia](https://wmkeyboard.pages.dev/tools/handwriting/#zinnia-for-chinese-and-japanese), and has no Korean handwriting. ## Japanese input Japanese ships five typing layouts, all feeding the same composer, plus the handwriting one above: - **Romaji**: type romanized Japanese on a standard QWERTY layout. Keystrokes turn into hiragana as you type, by the same romaji table Google Japanese Input uses: doubled consonants become っ, yōon like `kya` become きゃ, and the foreign-sound spellings work too (`vo` ゔぉ, `thi` てぃ, `kwa` くぁ, `ye` いぇ), so `vo-karu` offers ヴォーカル. `nn` and `n'` are always one ん, whatever comes after: `konnya` is こんや and `kinnen` きんえん, which means こんにちは takes three n's, `konnnichiha`. A single n before a consonant is ん as well (`shinbun` しんぶん). The long-vowel mark ー has its own key at the end of the middle row, as on other Japanese keyboards, and a hyphen typed inside a word reads as ー too, so `ka-do` converts to カード. Between words a hyphen stays a hyphen. Each letter carries a digit or symbol on long-press, shown in its corner as on the English layout, with ¥ on `d` and % on `f` as Japanese keyboards place them. - **Flick**: a 12-key pad with kana glyphs directly on the keys. Flick a key in a direction to reach its row-mates (あ flicks left/up/right/down to い/う/え/お). - **Kana JIS**: each key emits a kana glyph directly, matching the physical JIS Japanese keyboard layout. Since 0.6.6 the ゛ and ゜ forms are a swipe away, so you don't have to tap a second key: swipe down on か for が (on every kana that takes ゛, う for ゔ included) and up on は, ひ, ふ, へ or ほ for ぱ, ぴ, ぷ, ぺ, ぽ. Swipe up on ⇧ to run 小゛゜ on the kana you just typed, the same as the ゛ key does. The swipes aren't drawn on the keys, which are too narrow for them. With the swipes in hand you can drop the ゛ and ゜ keys in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and give the space to something else. - **Godan** (五段): romaji on five rows, with the vowels a i u e o down the left and the consonants beside them, the way Gboard draws it. Letters without a key of their own are flicks: k right for `g` and up for `q`, s for `z` and `j`, t for `d` and `c`, h for `b`, `f` and `p`, m up for `l`, y up for `x`, w up for `v`. Flick most keys down for a digit, n up for ん and r for 。?!、. Small kana are the usual `x` spellings, or tap 小゛゜, which turns `ka` into が and `tsu` into っ. - **50 keys**: a key for every kana in gojūon order, with あ at the top right and the rows running leftward as a kana chart does. Hold a key for its marked and small forms: か for が, は for ば and ぱ, つ for っ and づ. 小゛゜ does the same to the kana you just typed. languages/cjk-japanese-flick — Japanese Flick: kana glyphs sit on the keys themselves, with directional flicks for the rest of the row. The Flick pad is an ordinary layout, so you can change it in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/). Each key's sheet has a 3×3 pad with a cell for every flick direction, the four corners included, and a [Shift field for each](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key) so the same pad can type katakana while Shift is on. The editor's preview draws a key's flicks around its edges, so you can see and move every kana on the pad, and the keyboard draws them on the keys the same way unless [Show flick arms on keys](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) is off. Any key of a Japanese layout can also be told to **become 小゛゜ after a kana**: while the last kana you typed has a small, ゛ or ゜ form, that key shows 小゛゜ and changes it, and once the word is done it goes back to its own job. Give that job to the globe or emoji key and the separate 小゛゜ key can go, which is how phone kana pads save the space. Either key cycles katakana as readily as hiragana, so a pad built on アイウエオ keys reaches ェ from エ and バ or パ from ハ. All five read the same kana or romaji into `JapaneseComposer`, which converts kana readings to kanji/word candidates using a Mozc-derived dictionary. If nothing in the dictionary matches (or before you've downloaded the pack at all), the reading still commits as plain hiragana, katakana, or half-width katakana, so every Japanese layout works as a bare kana keyboard with zero setup. A phrase the dictionary lists only in kana still gets its kanji reading offered: `またあした` puts また明日 on the strip next to the plain kana, so you don't have to convert また and 明日 one at a time. ### Kana without marks On Flick, Kana JIS and 50 keys, every small kana, ゛ and ゜ is an extra key or a hold. **Match kana without marks** lets you leave them off: type かつこう and the candidates still offer 学校, which the dictionary files under がっこう. Each plain kana is also read as the forms the 小゛゜ key would cycle it through (か as が, は as ば or ぱ, つ as っ or づ, や as ゃ, a vowel as its small form), anywhere in the word and as many times as the word needs, so しゆきよう reaches 授業. It only ever adds marks. A が or a っ you typed was asked for by name and is never read back as か or つ. Each guessed mark costs the candidate a little, so between two words of about the same frequency the one spelled exactly as you typed it comes first, while a clearly more common marked word leads: かつこう opens with 学校 ahead of the given name 葛洪, and かき still opens with 書き, 描き and 下記 before 鍵 arrives. Nothing the exact spelling offered is removed, so expect a longer list rather than a different one. What's left in the field while you compose is the kana you typed; the marks arrive with the candidate you pick. Picks are learned against the dictionary's reading, so 学校 moves up whether you next type it with its marks or without. Romaji is never widened. `ga` costs exactly what `ka` does on a QWERTY layout (or one flick on Godan), so `kaki` means かき and nothing else, which keeps the strip as tight as it was. ### Swiping on Kana JIS With [glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) on, you can swipe across the Kana JIS layout. A stroke spells a reading, which goes into the text you're composing as if you'd tapped its kana, and the strip converts it the usual way. Nothing commits and no space follows, so a second swipe carries the same sentence on. You don't have to visit ゛ or ゜ on the way. A stroke through か, つ, こ and う reads as かつこう, and [Match kana without marks](#kana-without-marks) finds 学校 in it. When the word you meant doesn't come up, draw the mark: pass over ゛ straight after か and the stroke reads as がつこう. With Match kana without marks off, that's the only way a swipe gets a mark. Small kana are always drawn on their full-size key (つ for っ, や for ゃ), and the candidates put the small form back. The readings come from the Japanese dictionary, so swiping waits until it's downloaded, and only readings common enough to be swiped are in the list: a rare name still has to be tapped. Conjugated forms the dictionary doesn't list, such as わかった or いきました, don't come out of a single stroke yet. 50 keys glides the same way, and so does a layout of your own with a key for most kana. 50 keys has no ゛ to pass over, so its swipes rely on Match kana without marks. Romaji, Godan and Flick don't glide. ## Punctuation Every Japanese and Chinese layout carries its language's punctuation, whichever input method you type with. - **Beside the space bar.** The comma key types 、 in Japanese and , in Chinese, and the full stop types 。. Hold the comma for the quotes: 「 」 『 』 in Japanese; “ ” 「 」 《 》 in Chinese, alongside 、 ! ?. - **Flick.** Flick や left for 「 and right for 」. Hold 、 for 『』, (), ・ and 〜. - **?123.** The first symbols page swaps the ASCII quotes and the Spanish ¿ ¡ for the language's own marks. Japanese gets ・ 「 」 、 : ! ?, Chinese gets 、 “ ” : ; ! ?, and the brackets become ( ). Hold a quote for the nested pair (『』, ‘’) or the other convention. - **{'=\\<'}.** The second page has the rest of the bracket family as keys of their own. Japanese: 『』【】, holding 〈〉《》 and 〔〕. Chinese: the title marks 《》 (hold for 〈〉) and the corner brackets 「」 (hold for 『』 and 【】), so Mainland and Taiwan or Hong Kong quoting are both a tap away. It also has 〜 and … (Japanese) or —— and …… (Chinese). © ® ™ moved onto ¶'s hold. - **Dashes.** Hold - on the first symbols page for ー, 〜 and ―― (Japanese) or —— and …… (Chinese) as well as the usual dashes. The `,` and `.` on the symbols page itself stay ASCII, because that is where numbers like 1,000.5 get typed. Their holds lead with the wide forms. A layout that writes its own symbols page keeps it exactly as written. ## Full-width space Japanese and Chinese text is spaced with the ideographic space (U+3000, 「 」), which is as wide as a character, rather than the narrow space between words in other languages. Turn on **Full-width space** in the language's options and the space bar types it. It is set per language, off by default, so you can have it for Japanese and not for Chinese. A space pressed while a reading is still waiting to convert picks a candidate as it always has; only a press with nothing composing types a space. ## The candidate bar languages/cjk-candidate-grid-expanded — The expanded candidate grid: every ranked candidate, wrapped to fit, covering the key rows while it's open. Whenever a conversion composer has a non-empty buffer, the usual word-suggestion strip is replaced by a horizontally-scrolling row of ranked candidates. They're sized to their text and packed from the left rather than spaced evenly, so the best candidate stays in a consistent spot instead of jumping around as the list changes length. Each new reading opens the row back at its left edge, however far the last one was scrolled. A chevron at the end opens an expanded view, and it sits outside the scrolling area so it never scrolls off. That expanded view is a wrapping grid, and it covers the key rows while it's open, the same way Sogou, Baidu, and QQ handle it. By then you're choosing a character rather than typing one. Neither view pages. Both scroll. Tapping a candidate (in the strip or the grid) resolves by its position in the ranked list, not by its text, because the same word can legitimately appear twice at different reading lengths (a Japanese reading might list 行 for both い and いき). Only the part of your typed buffer that the candidate covers gets deleted. The rest re-converts. Space is the fast path: it commits the top-ranked candidate for the leading syllables and moves on, so repeated space presses walk a whole sentence through without ever opening the strip. No path here adds a trailing space, since Chinese and Japanese don't put spaces between words. Enter converts nothing: it confirms the reading exactly as typed and does nothing else, so no line break and no send. In Japanese that is the hiragana you see, which is how you keep a word in kana; in Chinese it is the letters you pressed, so with Double Pinyin on, `nihc` goes in as `nihc` and not as the `nihao` the composing line spells out. That makes Enter the quick way to drop a Latin word into a sentence without switching keyboards. Any other commit (punctuation, moving the cursor away, switching layout, or leaving the field) ends the reading too. In Japanese it keeps the kana as typed, the way Enter does, and only space or a candidate you pick converts. A candidate you highlighted with the arrow keys or a stepping space bar still goes in. In Chinese it flushes the whole buffer by repeatedly taking the top candidate until nothing's left, so you're never left with pinyin letters in the text. Japanese is more forgiving about punctuation. Its punctuation (、。,.!?・…〜) typed after kana joins the reading instead of ending it, so なるといいです! stays one reading that space can still convert, and the mark goes in with it. Brackets still end the reading. Moving the cursor into the reading doesn't end it either, in Japanese and in Chinese typed on letters (Pinyin with Jianpin or Double Pinyin, Jyutping, Yale and Cantonese Pinyin). A tap inside the underlined reading puts the cursor there: what you type goes in at the cursor, Delete takes out the letter or kana in front of it, and the candidates follow the corrected reading. So a `j` typed for an `h` early in `mmbxjn` is fixed where it is, without typing the rest again. In Japanese 小゛゜ changes the kana in front of the cursor. Tap at the end of the reading to carry on there. Converting starts from the reading's beginning wherever the cursor is. Since 0.6.6 dragging along the space bar to move the cursor does the same: while a reading is open the cursor walks through it one letter or kana at a time and stops at either end, instead of ending the reading and losing its conversion. Confirm the reading first to move on through the rest of your text. With Mozc, this works until the arrow keys split the reading into segments; after that the space bar moves from segment to segment, the way the arrow keys do, and the candidates you picked for each one stay. On the 9-key pads, Zhuyin, Cangjie, Wubi and strokes a tap inside the reading still ends it. In Pinyin the list runs from the whole buffer down to its first letter. Every reading of everything you typed comes first, then the best readings of shorter stretches from the start, longest first, then the single characters for the first syllable. `wbxh` offers 我不喜欢 and 我不想和, then 我不想 and 我必须, then 我不, then 我. Each one commits the letters it covers and leaves the rest to convert. If a reading has no dictionary match at all, it always falls back to committing what you typed as-is (raw pinyin letters, kana, or a stroke code) rather than trapping you in an empty buffer. ### Earlier, later, never Hold a candidate in the strip or the grid and the strip turns into its menu: **Earlier**, **Later** and **Never show**, with ✕ to close it. Earlier counts it as picked, which puts it first for that reading. Later moves it one step down, behind the candidates it was level with, and also makes every sentence built with it a little less likely. Never show takes it off the list for good, along with every reading built through it, so blocking 亡八 also removes 亡八喜欢. The strip updates at once. It works on every conversion layout (Pinyin, T9 Pinyin, Zhuyin, Jyutping, Japanese and imported input methods), each in its own [learning history](#learning-from-your-picks). On an English word in the strip, Never show adds it to the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/suggestions/) instead. To undo the lot, clear the learned words below. ### One character of a word The hold menu on a candidate of two or more characters also shows its first and its last character. Tap one to type only that character, for the whole reading the word covered: hold 选择 and tap 选. This is the quick way to a character that is hard to find under its own reading (以词定字, fcitx's [ and ]). Nothing is learned from it. ### What comes next (联想) Languages & layouts / (a CJK language) / (language) options / Next-word suggestions (联想) Once a Chinese conversion is complete and nothing is composing, the strip offers the words that usually come next: after 我 it might offer 们, 的 and 是; after 喜欢, 你 and 吃. Tap one and it goes in as it is, with no space, and the strip offers what follows that. The words you have written after the same text before come first, then the common pairs from the word pairs pack, then the rest of a longer word the last character starts (无 offers 聊, from 无聊). It needs the word pairs pack, follows the strip's own switches like any other suggestion, and is on by default. Start typing pinyin and the strip goes back to converting. The suggestions come from the text in front of the caret, not only from your last tap. Move the caret into the middle of something you wrote, or backspace a character away, and the strip offers what usually follows the Chinese now behind the caret. Backspace a word you just converted all the way out and the strip shows the other conversions of the reading that spelled it. Delete 他 and it offers 他, 她 and 它; tap the one you meant and it goes back in where the old one was, and the keyboard learns the pick the same way as one made while composing. This works for the conversions typed into the current field, and a word only half deleted gets the ordinary next-word suggestions instead, since part of it is still in the text. ### English words and emoji Pinyin, romaji and Jyutping are typed on the same letters as English, so with **English** turned on under the language's [Also suggest from](https://wmkeyboard.pages.dev/languages/overview/#each-language-has-its-own-screen), the strip offers English words for those letters too. Type `hello` and *hello* sits right behind 和. When the top candidate already covers everything you typed, as 很 does for `hen`, the English waits behind the first three candidates instead. Space still commits the top candidate, never the English. Tapping an English word puts it in exactly as shown, in place of the whole reading, with no space after it, and counts it as a word you use, the way typing it on an English layout does. Words the keyboard has learned from you on an English layout are offered here too, ahead of the word list, so a name or a brand you type is there when you start spelling it. Only words that start with what you typed are offered, never corrections, since the letters are a reading first. T9, Zhuyin, Cangjie and Stroke layouts don't offer English, because their keys aren't the alphabet. Emoji come up the way they do on any other layout: the top candidate is looked up in the language's emoji keywords, so `xiao` showing 笑 brings 😄 alongside, and an English word brings its own. They need the language's emoji keyword pack, and they follow [Emoji suggestions](https://wmkeyboard.pages.dev/emoji/customization/#emoji-suggestions-while-typing) and the strip's own switches, since these are suggestions rather than the input method. Tapping one with **Keep word** as the insert mode commits the candidates first and the emoji right after (笑😄); **Replace word** puts the emoji in place of the reading. A long press does the other one, as it does everywhere. ### Picking with space, the arrows and Enter Languages & layouts / (a CJK language) / (language) options / Space steps through candidates Out of the box the space bar types the first candidate straight away. Turn on **Space steps through candidates** and it works the way Japanese input methods do instead: the first press highlights the first candidate, each press after it moves the highlight one along (wrapping back to the start at the end), and Enter types the highlighted one. Keep typing and the highlighted candidate goes in before the next reading starts; ⌫ drops the highlight and leaves you editing the reading again. The strip scrolls to keep the highlight in view, and it draws the same outline the keyboard uses for anything picked with the keys. The setting is per language, and off by default, so you can have it for Japanese and keep the quick space for Chinese. On a physical keyboard the candidates come up even though the on-screen keys stay hidden: just the strip appears while a reading is waiting to convert, and goes again once it's done. The arrow keys move the highlight whether or not the setting is on (→ or ↓ for the next candidate, ← or ↑ for the one before), and Space or Enter types it. Once a candidate is highlighted, 1 to 9 pick one by its place on the strip and Esc goes back to the reading. Before that the number keys are left alone, because Jyutping's tones and the T9 pad type digits into the reading. Enter only stops confirming the reading as typed once you've moved the highlight, so it still works for dropping in a word as you typed it. ### Where the reading shows, and switching language mid-reading Languages & layouts / Chinese / Chinese options / Show the reading in the text field The pinyin, zhuyin or Jyutping you type sits underlined in the text field until you pick a candidate, as it does on Gboard and Sogou. Turn **Show the reading in the text field** off and the field stays as it was: the reading shows at the start of the candidate strip instead, in front of the candidates, and only the candidate you pick goes into the field. That is how fcitx5 works. A long reading shows its end on the strip, where the next key lands. Move the cursor yourself, by tapping somewhere else in the text or selecting some of it, and the reading is dropped, as in fcitx5, so nothing you typed lands in a spot you didn't mean. The setting is shared by Chinese and Cantonese, sits on both options pages, and is on by default. Japanese always shows its reading in the field. Languages & layouts / Chinese / Chinese options / When you switch language mid-reading Switch to another language or layout with a reading still waiting, and by default its best candidates go in, as if you had pressed space until it was all converted. **When you switch language mid-reading** offers two other choices. **Type what you typed** puts the reading in as letters (`nihao`, or the zhuyin itself), the way fcitx5 does. **Discard it** throws the reading away, and nothing goes in. Like the row above, it covers Chinese and Cantonese together. ### The keyboard's own boxes The same thing happens in the keyboard's own text fields: the dictionary tool's search, the clipboard and emoji filters, the AI prompt, Find and replace, a plugin's box. They compose and convert exactly as the app's field does: romaji turns into kana as you type it, the 小゛゜ key cycles the last kana, the candidates appear in the strip above the keys, and a tap commits the part of the reading it covers and leaves the rest composing. Nothing reaches the app behind the panel, and a pick there is learned the same way a pick anywhere else is. These boxes used to take the keys straight: `ka` stayed `ka`, there was no way to add a dakuten, and no kanji was ever offered, so a Japanese user could not search the dictionary tool for anything. The candidate bar is not the ordinary suggestion strip wearing a different coat, and it isn't switched off by the things that switch that strip off. Turning **Suggestions** off, or typing into a field that asks for a silent strip (search and filter boxes, address bars, email fields, password boxes), leaves the candidates exactly where they are. For a conversion method the candidate list *is* the input method, so hiding it wouldn't quiet a guess, it would stop you typing the language at all. Password boxes convert too, and what they don't do is remember the pick afterwards, which is the same rule the rest of the keyboard's learning follows. ## Learning from your picks Every candidate you pick from the strip or the grid is remembered against the reading you typed, and so is the one a space press commits. Whatever goes in when you leave the buffer some other way (punctuation, moving the cursor) teaches the candidates nothing, since it's only the order the list already had. A Pinyin or Jyutping reading that goes in as the letters you typed, because you pressed Enter or nothing in the dictionary converts it, is learned as a word in Latin letters instead, the way a word typed on an English layout is: a spelling no list knows joins your personal dictionary once you've used it a few times, and from then on the strip's [English words](#english-words-and-emoji) offer it for those letters. The next time the same reading comes up, your previously-chosen candidates move to the front (most-picked first) while everything else keeps the order the dictionary gave it. In Pinyin they move to the front of their own group: a 我 you picked for `wo` comes first among the single characters for `wbxh`, never ahead of 我不喜欢. It's a reordering of your own history, not a blended relevance score, so a candidate you've never picked never gets promoted just because it's common in general. In Pinyin it learns three more things: - **Phrases you build.** When you commit one buffer in two or more picks, say 我的 and then 妈妈 for `wodemama`, the whole thing is learned as one phrase, so next time it's offered whole, and `wdmm` finds it too. Only short ones are kept, up to four syllables (or six single characters), and none where the word pairs already join every piece to the next. - **What you write after what.** Each pick is remembered as following the word before it, whether that was the pick before or the Chinese text in front of the cursor. Those pairs help decide between readings (`ta` after 她说 leans to 她) and come first in [what comes next](#what-comes-next-联想). - **The text before the cursor.** The first word of a new reading is ranked as following whatever Chinese is already written before it, using the word pairs pack and your own pairs. After a space, a punctuation mark or Latin text, the reading starts a new sentence. Nothing you taught it stays forever if you stop using it. After every 3,000 picks or so, each count that hasn't been used in that time is halved, and a count of one goes. A word you pick every day never fades, and one you picked a few times last year stops outranking it. Learning is scoped separately per reading system (pinyin, T9 Pinyin, zhuyin, Cantonese, and Japanese kana readings each keep their own history, with the three Cantonese spellings sharing one), because the same word can be spelled several different ways depending on which Chinese scheme you're using. Cangjie, Cangjie Quick, and Stroke don't participate in learning at all: their radical and stroke codes map deterministically to one character, so there's no ambiguity to learn from. This history is governed by the same setting as ordinary word learning: Privacy / Learning on this device / Learn from typing Turning it off, using incognito mode, or typing into a field that opts out of learning all stop new picks from being recorded, exactly as they do for the regular word suggestion history. ## Options Languages & layouts / (a CJK language) / (language) options Opening a Chinese, Cantonese, or Japanese entry from the Languages screen shows a dedicated options group (Korean gets a shorter one, covered under [Hanja](#hanja) and [Fix spacing](#fix-spacing)): | Setting | Default | Applies to | |---|---|---| | Conversion dictionary download | Not downloaded | Chinese, Cantonese, Japanese (see below) | | Traditional characters | Off | Chinese and Cantonese, shared between them | | 繁 button | Off | Same, a switch for the row above beside the candidates | | Show the reading in the text field | On | Chinese and Cantonese, shared between them | | When you switch language mid-reading | Type the first candidate | Same | | Regional wording (Standard / Taiwan / Taiwan wording / Hong Kong) | Standard | Same, only shown once Traditional is on | | Lazy pronunciation (懶音) | On | Cantonese only | | Match kana without marks | On | Japanese only, and only kana typed on Flick, Kana JIS or 50 keys | | Full-width space | Off | Chinese, Cantonese, and Japanese, each on its own | | Space steps through candidates | Off | Chinese, Cantonese, and Japanese, each on its own | | Jianpin (简拼) | On | Chinese only; ignored under a Double Pinyin scheme | | Next-word suggestions (联想) | On | Chinese only | | Date, time and numbers | On | Chinese only | | Fuzzy Pinyin | Off | Chinese only | | The eleven fuzzy pairs, one row each | All on | Chinese only, shown while Fuzzy Pinyin is on | | Double Pinyin scheme | Off (full Pinyin) | Chinese only | | Custom Double Pinyin scheme | Empty | Chinese only, used when the scheme above is Custom | To clear everything the keyboard has learned from your CJK picks: Privacy / Your data / Learned words CJK picks live in a file of their own, but the **Delete the learned words** button clears the lot in one action: your typed-word history, emoji usage, autocorrect statistics, the CJK pick history (with what you told the [candidate menu](#earlier-later-never)) and the per-app language-mix record. There's no way to clear the CJK half on its own. ### Conversion dictionaries are downloads, not bundled data None of the conversion dictionaries ship in the app. Until you download the matching pack, or [import a list of your own](#your-own-word-lists), a Chinese or Cantonese layout still composes and shows the buffer, but the candidate list stays empty. Japanese is the exception: its plain hiragana, katakana and half-width katakana forms are appended to every ranking, so they're on offer with no pack at all. You don't have to know that in advance. Adding Chinese, Cantonese or Japanese puts the packs its layouts need into the **Download language data?** prompt, sized with the rest (the setup wizard fetches them along with the word list). Switching on one of their layouts later, on the language's page or under More layouts, asks **Download the Chinese Pinyin dictionary?** when that layout's pack isn't on the device yet. And if a layout whose pack is missing comes up on the keyboard anyway, a chip appears on the strip straight away, before you've typed anything, naming the pack you need: "Download the Chinese Pinyin dictionary to type characters". Tapping it opens that language's page, where the pack's row is. The chip asks once per pack, and the ✕ dismisses it. It never appears in a password field. | Language | Pack | Approx. size | Coverage | |---|---|---|---| | Chinese | Pinyin (also covers Zhuyin & T9 Pinyin) | ~2.6 MB | ~121k entries, CC-CEDICT-derived | | Chinese | Chinese word pairs (comes with Pinyin, for [ranking whole readings](#everyday-pairings)) | ~11 MB | ~85k words, 700k pairs | | Chinese | Stroke | ~450 KB | Stroke-order table | | Chinese | Cangjie (also covers Cangjie Quick) | ~350 KB | ~29k characters | | Chinese | Wubi 86 | ~1.7 MB | ~93k characters and phrases, from the Jidian table in ibus-table-chinese. Characters past U+FFFF are left out | | Chinese, Cantonese | Traditional characters phrase table (optional, for [Traditional output](#traditional-characters-and-regional-wording)) | ~1 MB | ~50k phrases, OpenCC | | Cantonese | Jyutping (also covers Yale & Cantonese Pinyin) | ~3.4 MB | ~163k entries | | Japanese | Kana → kanji | ~42 MB | ~1.08M entries, Mozc-derived | When a new version of a pack comes out, its row offers the download again. Until you take it, the copy you already have keeps converting, so an app update never leaves you unable to type. The Pinyin pack got one with the October 2026 rescore, which ranks common characters like 齐 and 议 near the front of their reading instead of behind rare ones. Downloading it replaces the old copy rather than keeping both. The shape-based tables have a second source. **Import from FlorisBoard**, in the same group, reads a FlorisBoard language pack (`.flex`) and fills the Cangjie, Stroke or Wubi pack from the SQLite table inside it, with no download at all. Only those three land, since Wubi 98, Zhengma and the rest have no composer here; the app names whatever else it found back to you. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/). Each download is checksum-verified before it's used and, once on disk, needs no further network access. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for what the keyboard contacts and when, and [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for how the separate word-list system (which does have entries for plain Japanese and Korean word suggestions, just not for the conversion dictionaries here) works. ### Your own word lists Languages & layouts / (a CJK language) / Dictionaries / Custom dictionaries A list you import for Chinese, Cantonese or Japanese goes into the candidates, next to the pack. It only needs a reading on each line, and the formats people already have all carry one: - **CC-CEDICT** and **CC-Canto** (`關係 关系 [guan1 xi4] /relation/`). Chinese takes the Simplified form, the one the Pinyin pack lists, and **Traditional characters** turns it into the other; Cantonese takes the Traditional form and the Jyutping in braces. - **Rime** dictionaries (`.dict.yaml`), such as Luna Pinyin: `word`, the spaced reading, then a count or a percentage. The YAML header is skipped. - **Mozc and Google Japanese Input** user dictionaries: `reading`, `word`, part of speech. A katakana reading is read as hiragana. - The packs' own format, `reading`, `word`, `frequency`, one tab between each. - **Sogou's `.scel` cell dictionaries** (细胞词库), as Sogou publishes them: each word with its pinyin and Sogou's count. Tone numbers and tone marks are dropped and ü is spelt `v`, the same as in the packs. A line with no reading, such as a plain column of characters, is skipped, and the count on the Custom dictionaries screen is the number of lines that do have one. A word your list adds appears under its reading. A word the pack already has keeps its place in the pack's ranking, unless your list gives counts of its own (Rime's do), in which case it can move up but never down. Your [picks](#learning-from-your-picks) reorder both the same way. A list works on its own too: with no pack downloaded, the candidates come from your lists, and the missing-pack chip stops asking. **Use only my word lists** leaves the pack out once you have imported a list with something in it; with nothing imported it has no effect here, so it can't leave the language unable to type a character. A list goes live the next time you open a text field, and only the table for its own language is reloaded. #### Dictionaries made for fcitx5, Rime or Sogou Most Chinese dictionary projects on GitHub are built for the desktop input methods, and many ship two files per release: a `.dict` for fcitx5 and a `.dict.yaml` for Rime. Take the `.dict.yaml`. It is the Rime format from the list above, so it imports as it is: pick **Chinese**, open **Custom dictionaries** and import the file. Sogou's own `.scel` cell dictionaries import directly too, from Custom dictionaries or by opening the file with WM Keyboard; the import names them as a Chinese list. fcitx5's `.dict` is compiled, and the keyboard can't read it: it is libime's binary dictionary. Look for the Rime file or the `.scel` files in the same release, or convert the project's source text instead. Each file can be at most 32 MB. That rules out the whole-corpus builds, such as [fcitx5-pinyin-sougou-dict](https://github.com/blackteahamburger/fcitx5-pinyin-sougou-dict), whose `sougou.dict.yaml` is over 400 MB: the import refuses it, and splitting it wouldn't help, because the keyboard reads every list into memory when it builds the Chinese table. That project's release also carries `sougou_dict.tar.gz`, the raw Sogou cell dictionaries it was built from. Unpack it and import the `.scel` files for the topics you actually type, such as computing, place names or medicine. A few thematic lists of a few megabytes each give most of the benefit and load fast. ## The RIME engine Languages & layouts / Chinese / Conversion engine / Engine Chinese and Cantonese can convert on [RIME](https://rime.im/) instead of the keyboard's own engine. RIME is the open-source engine behind Weasel on Windows, Squirrel on macOS, fcitx5-rime and ibus-rime on Linux, and Trime on Android. It reads RIME *schemas*: a schema says which keys spell which readings and which dictionary they look up, so anything written for those keyboards types here too, including schemas you find on GitHub or make yourself. **Engine** on a language's page picks **Built-in** or **RIME**, for that language alone, and Built-in is the default. The layouts don't change: Pinyin is still the Pinyin board, Zhuyin still has its bopomofo keys, and the keyboard turns them into the keys RIME's schemas expect (bopomofo onto the Dachen key map, Cangjie radicals onto their letters, strokes onto `h s p n z`). Only the conversion is RIME's: the candidates, how far each one reaches into what you typed, and what it learns from your picks. RIME needs three things before it takes over, and the group lists them as they arrive: 1. **Its library**, about 3 MB. The Google Play edition fetches it from Play, the GitHub edition from the project's data repository, and the F-Droid edition from the same place once **Download feature libraries** is on under Privacy. Picking RIME asks for it, after the usual question on a metered connection. 2. **A schema pack.** Each row downloads one, together with **RIME essentials** (punctuation, symbols, a word-frequency table and the Simplified and Traditional tables every schema uses). Each pack is checked against a SHA-256 built into the app, like the conversion dictionaries. 3. **Compiling.** RIME turns schemas into fast lookup tables on the phone, which it calls *deploying*. The **RIME status** row starts it by itself as soon as a download finishes and shows each schema as it goes. **Compile** runs it again. Luna Pinyin takes seconds; Rime Ice, with its far bigger vocabulary, can take a minute or more and a few hundred megabytes of memory while it runs. Until all three are in place, and while a compile is running, the language keeps typing on the built-in engine, so switching never leaves you unable to type. | Pack | Schemas | Layout it serves | Licence of the data | |---|---|---|---| | Luna Pinyin (朙月拼音) | luna_pinyin and its Simplified, Taiwan and sentence variants, luna_quanpin, stroke | Pinyin, Stroke | LGPL-3.0 | | Rime Ice (雾凇拼音) | rime_ice, Double Pinyin schemes, t9 | Pinyin, T9 Pinyin | GPL-3.0 | | Bopomofo (注音) | bopomofo and its variants, terra_pinyin | Zhuyin | LGPL-3.0 | | Cangjie 5 (倉頡五代) | cangjie5, cangjie5_express | Cangjie | LGPL-3.0 | | Jyutping (粵拼) | jyut6ping3 | Cantonese Jyutping | CC BY 4.0 and ODbL | Each pack carries its upstream licence in a `licenses` folder inside the zip. The Rime Ice pack leaves out its Tencent word-vector list, the largest table, which would take minutes to compile on a phone. RIME's Lua plugin is not in this build either, so the Lua parts of Rime Ice (its date, calculator and correction scripts) are skipped and the rest of the schema works. It would make the library a third bigger and let any imported schema run its own code inside the keyboard, and the built-in engine has date, time and Chinese numeral shortcuts of its own. **Which schema a layout uses.** Once something is compiled, a picker appears per kind of layout: **Pinyin layouts**, **9-key Pinyin layout**, **Zhuyin layout**, **Cangjie layout** and **Stroke layout** for Chinese, **Jyutping layout** for Cantonese. **Automatic** takes the first downloaded pack that serves the layout, in the order of the table above. Cangjie Quick has no RIME schema here and stays on the built-in engine. **The 9-key pad.** Rime Ice's `t9` schema serves the 9-key Pinyin layout, and the [syllable column](#chinese-input-schemes) works as it does on the built-in engine: it lists what the next keys could spell, longest first, with the syllable RIME's first candidate reads there at the top, and a tap settles that syllable for RIME too. The text you are typing shows RIME's spelling of its first candidate, `xi'an` for 西安. Rime Ice is the only pack with a 9-key schema, so without it the 9-key layout stays on the built-in engine. **What comes next.** RIME has no next-word suggestions of its own, so once a conversion lands, Chinese gets the keyboard's own [what comes next](#what-comes-next-联想), whichever schema typed the text: your own habits first, then the **Chinese word pairs** pack, which you can download from the language's dictionaries. Cantonese gets none, because the pairs are Mandarin. **Your own schemas.** **Import RIME schemas** takes a zip of `.schema.yaml` and `.dict.yaml` files, the way most schema projects publish them on GitHub. The folder inside the zip doesn't matter: the keyboard unpacks the files beside the first schema it finds. A file one of the packs already provides is kept as the pack has it, and so is RIME's own `default.yaml`. An imported schema compiles with the packs and shows up in every picker. The same zip also installs through **Import** on the [Storage](https://wmkeyboard.pages.dev/reference/settings/about/) screen, or by opening it with the app, which recognises it by the schema inside. Each import gets its own row with a delete button. **Traditional characters** and **Regional wording** work here too: they switch on the matching filter inside the schema (Simplified, Traditional, Taiwan or Hong Kong forms), so a candidate is converted before the keyboard measures how much of your reading it covers. Cantonese stays in Traditional, as the built-in Jyutping dictionary is. **Words on the keys.** On the Pinyin layout, candidates float over the key that carries on their reading, read from the spelling RIME shows for each one, so a schema that shows no spelling floats none. After a commit, the words from what comes next float over the key that starts them, which needs the Chinese dictionary pack as well. **Learning.** RIME learns into its own user dictionary when you pick a candidate, and it learns whole phrases: picking 你好 and then 世界 for `nihaoshijie` teaches it 你好世界. Like the rest of the keyboard's learning, it learns nothing in incognito or a password field. **Delete RIME data** removes the compiled schemas, your imported ones and everything RIME learned; the downloaded packs stay and compile again. **Before the first unlock.** RIME's schemas and its user dictionary sit in the app's credential-protected storage, with your other learned words, so on a phone that has restarted and not been unlocked yet, Chinese types on the built-in engine. RIME takes over again after you unlock. ## The Mozc engine Languages & layouts / Japanese / Conversion engine / Japanese engine Japanese can convert on [Mozc](https://github.com/google/mozc) instead of the keyboard's own engine. Mozc is Google's open-source Japanese engine, the one inside Google Japanese Input and Gboard's Japanese keyboard. Where the built-in engine looks up words, Mozc converts whole sentences, so `kyouhaiitenkidesune` comes out as 今日はいい天気ですね in one pick. It also predicts as you type, splits a reading into its parts (文節) the way a desktop Japanese input method does, and learns from what you pick. **Japanese engine** picks **Built-in** or **Mozc**, and Built-in is the default. Every Japanese layout converts through Mozc once it's picked: Romaji, Godan, Flick (the 12-key pad), Kana JIS, 50 keys and any layout of your own that composes kana. The keys don't change. The keyboard still turns romaji into kana itself, the 小゛゜ key still cycles the last kana, and only the conversion is Mozc's. Mozc needs two downloads before it takes over, and the group lists them as they arrive: 1. **Its library**, about 3 MB. The Google Play edition fetches it from Play, the GitHub edition from the project's data repository, and the F-Droid edition from the same place once **Download feature libraries** is on under Privacy. Picking Mozc asks for it, after the usual question on a metered connection. 2. **The Mozc dictionary**, about 19 MB: Mozc's own system dictionary, connection costs and segmenter, built from Mozc's sources at the same release as the library. Picking Mozc starts this download too, and it is checked against a SHA-256 built into the app like the other conversion dictionaries. A phone that can't download gets an import row under it, with a link to the file to fetch on another device. The same file installs offline through **Import** on the [Storage](https://wmkeyboard.pages.dev/reference/settings/about/) screen. **Mozc status** says where it stands. Until both are in place, and before the phone's first unlock, Japanese keeps typing on the built-in engine, so switching never leaves you unable to type. The built-in kana dictionary above is not needed for Mozc and can stay or go. **The candidates.** While you type, the strip shows Mozc's predictions: whole-sentence conversions first, then words and phrases that cover only the start of the reading. Picking one of those commits that much and leaves the rest of the reading composing, with its own candidates already up. The plain hiragana, katakana and half-width katakana of the reading stay at the end of the list. The expanded grid pages further down the same list. After you pick, the strip offers what Mozc expects next. **Moving the boundaries.** The arrow keys work on the reading while it's composing, as they do on Gboard's Japanese pad. ← and → split the reading into its parts if it isn't yet, then narrow or widen the highlighted part by one kana. The field shows every part converted, with the highlighted one shaded, and the strip lists the candidates for that part alone. With selection on, the arrows move the highlight to the part before or after instead. Picking a candidate commits every part up to the highlighted one, each as it shows, and the rest stays split so the next part's candidates come straight up. Backspace or another key goes back to typing the reading. The arrow row and the text-editing pad both have these arrows. On a physical keyboard it works as on a desktop: Shift+←/→ resizes the highlighted part, and once the reading is split ←/→ move between parts while ↑/↓ step through the candidates. **Match kana without marks** works here too, through Mozc's own version of it, so かつこう still finds 学校. **Learning.** Mozc learns from each pick: which conversion a reading takes, where you put the boundaries, and what you type after what, which is how its predictions get better. Like the rest of the keyboard's learning, it learns nothing in incognito or a password field. What it learns is written to the app's credential-protected storage a few seconds after you pick. **Forget what Mozc learned** clears it and keeps the dictionary. ## Details & edge cases - **A Japanese word-list download exists but does nothing for typing.** Japanese has its own entry in the ordinary downloadable-dictionary catalog (see [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/)), but because Japanese is a conversion input method, the regular suggestion engine never runs for it. That download currently has no effect on any of the three Japanese layouts. Korean's word list, by contrast, is fully active, since Korean isn't a conversion method. - **The Traditional/region toggles show on the Japanese screen but are inert there.** `JapaneseComposer` never applies the Han-variant conversion, so leave those settings alone if you're a Japanese-only typist. They were placed above the Chinese-specific options rather than gated per-language, but they have nothing to act on for Japanese. - **Cangjie Quick needs the same dictionary as full Cangjie.** With only one key typed, Quick behaves like an ordinary prefix search, since the "last" radical isn't known yet. The second key turns it into a first-and-last shape match, which usually narrows to a handful of candidates. - **The candidate depth differs by scheme.** Pinyin, Zhuyin, T9 Pinyin, Jyutping, and Japanese all rank up to 100 deep in the expanded grid. Cangjie and Stroke cap at 24, because they look candidates up from a fixed code table rather than a ranked lattice. - **There's no "lock" or "pin" on a candidate.** Once you commit one, it only affects future ranking through the learning history above. There's no separate pinning feature to hold a candidate in place. --- # Custom layouts & the layout editor URL: https://wmkeyboard.pages.dev/languages/custom-layouts/ Section: Languages Summary: Edit any keyboard layout visually, or as raw JSON, and share the result. A layout is the key grid itself: which key sits where, what it types, what its long-press alternates are. WM Keyboard ships 1,732 of them, and every one is editable: rearrange a few keys, add a Ctrl key for a terminal app, or build a language layout from scratch, then share the result as a file. languages/key-layouts-gallery — The Key layouts screen: your own layouts, the shipped set, and an import row. ## Editing a layout Layouts of your own are always listed, on or off, and the switch at the start of each row's buttons turns one on or off without leaving the screen. A layout of your own also shows on the Layouts shelf of its language under [Languages](https://wmkeyboard.pages.dev/languages/switching/), next to the shipped ones, so either place works. Shipped ones appear here once you've turned them on under Languages. There's no edit button: tap the row itself and the visual editor opens. The grid renders with your actual theme, key shape, and font, so what you edit is what you'll type on. The ⋮ button at the end of a row holds the rest: export the layout to a file, duplicate it, and delete it (or, on a shipped layout you've edited, reset it). Swiping the row to the side shows the same three. On a layout of your own the menu also has **Share with everyone**, which sends it to the community addon repository; see [Sharing with everyone](https://wmkeyboard.pages.dev/addons/community/). Each layout row also has a chevron. Open it and the layout's layers list underneath, one row each: the nine typing layers and then every panel (Emoji, GIF, Sticker, Clipboard, Text editing, Trackpad, Numpad and, outside the F-Droid build, Handwriting), with a pencil on the ones this layout has authored and **Standard** on the ones it inherits. Tap a layer row and the editor opens on that tab. The list is closed by default, so the screen stays a list of layouts until you ask. **Start a new layout** is the first row under Your layouts, for a grid that isn't a rearranged anything. It builds an empty four-row skeleton and opens it. Only the plain character keys are cleared, so enter, shift, space and delete keep the grid usable while you fill it in. languages/layout-editor-grid — The visual layout editor, showing the Letters layer with a key selected. A layout has up to nine layers (Letters, Symbols, Symbols 2, Number, Phone, Date, Time, Date & time, and Fn), switchable with tabs across the top, followed by a tab for each of the [panels](#panel-layouts). The tabs are pinned with the grid, above it, so they stay where they are when a layer with more or fewer rows comes up. None of them has to be defined. Any layer you leave untouched falls back to the built-in grid for it, the shipped QWERTY's, so a layout that only rearranges letters still gets a working number pad and symbol page without you drawing three hundred keys yourself. The Number layer is also what the Numpad tool, and a long press on ?123, put on screen, so a layout that authors one gets its own pad there too, in the order it drew it. The five keypads (Number, Phone, Date, Time and Date & time) keep their shared keys in the same places: the digits, ⌫ in the right column just above ⏎, and space at the top of that column on every pad that has one. Only the characters each field needs, like * and # on Phone or : on Time, sit in different slots, so a change to a shared key is the same edit on every pad. Letters gets one extra guarantee the others don't, though. See [Details & edge cases](#details--edge-cases). A pencil icon on a tab marks a layer this layout has authored its own copy of. An unauthored tab shows the built-in grid live, with a note that editing it will fork a copy just for this layout. An alphabet that takes several pages, like the Hindi alphabetical board's 1/4 to 4/4, has a tab for each further page after the layers: **Page 2** puts that page in the grid to edit like the letters. Shift and the page key don't move the grid in the editor, so these tabs are the way there. **Reset this layer** on a page removes the page, and undo goes back one step on the tab you are on. A new page still takes the raw JSON: add the layer, then a key with a `layer` action that opens it. The grid stays pinned at the top of the screen, edge to edge, and draws the layer the way the keyboard draws it: the corner hints, the enter key's own icon, the language name on the spacebar, the press and hold shortcuts and accents your settings add to keys, and the rows the keyboard draws around the grid. With **Number row** on in [Layout & size](https://wmkeyboard.pages.dev/reference/settings/layout/), the number row is in the preview above the grid; on Symbols it is joined by the row the keyboard shows in place of the digits, and with **Arrow keys row** on, the arrow keys row is under it, or at the very top with **Arrow keys on top**. Tap a key on any of those rows to edit it, the same as a key in the grid. To change the number row or the arrow keys row for every layout at once, rather than in each one, use **Number row keys** and **Arrow keys row keys** in [Layout & size](https://wmkeyboard.pages.dev/reference/settings/layout/#number-row); a layout's own row still wins over them. On a small screen the grid can leave little room for the settings under it. Drag either of the two corner marks under the grid inward or up to shrink it, keys, labels and icons together, down to 35% of its full size; drag outward or down to bring it back, or double-tap a mark to return to full size. The size is kept for the next time you open the editor, on this phone only. Under the layer tabs on Letters, **Show as in** redraws the grid for an **Email address** or a **Web address** field, the two kinds of field that change keys on the letters: an email field puts `@` on the comma key, and a web address field keeps the comma but starts its press and hold, and its corner hint, with `/`. Both add `.com` and the other endings to the full stop, and a web address field's enter shows Go. To stop a key changing, open it and set **Field adaptation**, under More, to **Exactly as laid out**. A "Show the shift plane" toggle redraws every key with its `shiftLabel` (or an uppercased label) instead of the primary one, so you can check the shifted layer without switching layers. Keys set to "switch layout", "emoji panel", "toolbox" or "open a tool" draw an icon in the grid rather than the label stored on them. The globe key is the one you'll meet: its stored label is the emoji 🌐, but the keyboard has always drawn its own monochrome globe icon there instead, and now the editor does too, so the grid matches what you actually type on. A tool key wears the tool's own icon the same way, unless you give it a label, in which case the label wins. Change the key's action and it goes back to drawing its label. ### Editing a key Tap any key and its settings open on the page under the pinned grid, so the key and the grid stay on screen together. A strip at the top names the key and has its arrows, a duplicate button, **Add key after**, **Copy**, **Paste after**, **Delete** and a × that closes the settings. Under it the settings come in groups: **Label and action**, **Press and hold** (with the popup and the corner hint), and the folds **Flicks**, **Size and look** and **More**, which open with a tap on their heading and stay open on later keys. **Flicks** is open already on a key that has flicks. languages/key-edit-sheet — The key edit sheet: label, output, action, width, and long-press alternates. - **Label**: what's drawn on the key. On Shift and caps lock, a label replaces the arrow: write `A`, `ABC` or a word and the key shows that instead, lit in the accent colour while shift is on. Leave it blank, or write `⇧` or `⇪`, and the key keeps the arrow your icon pack draws. Any other action key left blank draws its action's icon, the same one the grid preview and the press and hold popup show. - **Output**: what it actually types; leave it blank and it types the label. - **Shift label**: leave it blank and Shift uppercases the label. - **Flick directions**: what a short swipe from the key in one of eight directions types instead of the tap, the way the [Japanese Flick](https://wmkeyboard.pages.dev/languages/cjk/#japanese-input) pad reaches い, う, え and お from あ, and the way the shipped **English flick (3×3)** layout keeps all twenty-six letters on nine keys, MessagEase style, with `o` carrying q, u, p, c, b, g, d and j on its eight arms. See [Flick keys](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) for how the gesture reads. A key with no flicks shows an "Add flick directions" row; tap it, or open a key that already has some, and a 3×3 pad appears with the key's own label in the middle and a cell for each of the eight arms, the way the key itself draws them. Tap a cell to edit that arm in the field under the pad; a cell with nothing in it shows a faint plus. **Swipes only**, above the pad, empties the middle: the key then has no centre character, a tap on it types nothing, and only its swipes do, the way an Unexpected Keyboard key with corners and no centre works. Leaving the label empty on a key with flicks does the same. Turning the switch off gives the middle the key's first flick character back. Leave the field blank for no flick that way, and **Remove all flick directions** under the pad clears the lot. The grid preview draws each flick's character at the edge or corner of the key it is flicked towards, so you can see the whole pad at a glance, and the keyboard draws them the same way unless [Show flick arms on keys](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) is off. A layer with flick keys has its own **Show flick arms here** switch below its rows, for showing or hiding them on that layer alone. Under each direction that types something is a **with Shift** field: what that flick types while Shift is on, the way the shift label works for the tap. Leave it blank and Shift makes the flick a capital letter, as it does for the label, and so does a swipe out to the arm and back again. A kana pad with a Shift key can so put katakana on its hiragana keys: あ with Shift types ア, and its left flick い types イ. The flick cross shows the shifted kana while Shift is on. A layout that uses the four corners needs this release or newer to open; an older build declines the whole file rather than guess. Keys that do something other than type get flicks too: Enter, Shift, ?123, ⌫, the globe and emoji keys, Space, and the rest, everything except a key set to repeat. On Space an arm takes its direction from the spacebar's swipes, and the directions without one keep them ([how](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys)). Each arm types the text in its field, or runs an action if you tap its bolt, and the tap stays the key's own action. So Enter with `?` on its flick up types one `?` and neither sends nor starts a new line. In the JSON these text arms are the same `flick` map a letter key uses. Any flick can also **do an action** instead of typing: tap the ⚡ button at the end of its field and pick one from the same list as the press and hold actions. **Several text editing actions** asks for a list and runs them in turn, so one flick can be start of line then page up, or up then right. A text editing action asks which operation next, so a key can move the cursor with its flicks. A flick that moves the cursor or deletes keeps going while you hold your finger on it, the same as the key for it, whether the move is a text editing action, a cursor tool such as Cursor word left, or an arrow key sent as a key press. Turn on **Hold a flick to repeat it** under Key press / Timing and a flick that types does the same: held out past the repeat delay, it types its letter again and again, at the **Repeating key interval**, until you lift. Action flicks work on action keys too, where they are the only kind: open a Select key, tap "Add flick directions", and give its arms the four arrows. The flick cross, the key's face and the grid preview show each action's icon. Choosing an action for a direction removes that direction's text. A modifier, ?123 or 🌐 key given action flicks flicks instead of starting its drag. The delete keys take flicks, so ⌫ can carry forward delete on its flick up: they keep their hold-to-repeat, and the delete swipe keeps every direction no arm points, so an up arm leaves the leftward word swipe as it was. A flick has to leave the key before the repeat starts. The arrow keys take flicks the same way, so ← can carry word left and ↑ page up, and holding one still keeps moving the cursor. That includes the arrow keys row and any key with **Repeat while held** on. The space bar takes flicks as well, and its swipes keep every direction no arm points. Change a text key to an action afterwards and its text flicks go quiet: the key neither shows them in the flick cross nor types them, though the JSON keeps them in case you change it back. The fields are `flick`, `flickShift` and `flickActions` in the JSON, keyed by direction: `left`, `up`, `right`, `down`, `up_left`, `up_right`, `down_left` and `down_right`. - **Becomes 小゛゜ after a kana**, on the keys of a Japanese layout: a switch, off by default, that makes the key stand in for the 小゛゜ key while the last kana you typed (hiragana or katakana) has a small, ゛ or ゜ form. The key shows 小゛゜ and a tap changes that kana; when the word is done, or the kana has no other form (な, ん), it goes back to what it was. Turn it on for the globe or emoji key and you can delete the separate 小゛゜ key and give its space to something else, the way phone kana pads do it. The preview marks such a key with a small 小゛゜ in its corner. The field is `kanaVariantWhileComposing` in the JSON. - **Action**: opens a picker with 39 options across seven groups: typing (types text, Shift, caps lock, Delete, forward delete, Space, Enter, a new-line key that always types a line break even where Enter would send, and the Japanese 小゛゜ key, which changes the last kana you typed to its small, ゛ or ゜ form, and the two Korean keys of the [나랏글 pad](https://wmkeyboard.pages.dev/languages/cjk/), 획추가 and 쌍자음, which add a stroke to the last Korean letter you typed (ㄱ to ㅋ, ㅏ to ㅑ) or double it (ㄱ to ㄲ), and the [T9 Pinyin](https://wmkeyboard.pages.dev/languages/cjk/) pad's syllable list, a scrolling column that lists the syllables your keys could spell while you type and the key's long-press characters the rest of the time; give it a few rows' height), layers (symbols page, letters page, emoji panel, the toolbox, which opens the same grid of tools as the button at the start of the toolbar, switch layout, switch keyboard, [switch to a specific keyboard](https://wmkeyboard.pages.dev/languages/switching/#switching-from-androids-system-menu) for a key that hands the field to one named keyboard app with no picker in between, Fn layer, "open a layout" for one of your [secondary layouts](#secondary-layouts), and "toggle a keyboard mode" for one of your [keyboard modes](https://wmkeyboard.pages.dev/typing/modes/#keyboard-modes)), modifiers (Ctrl, Alt, Meta; tap to arm, double-tap to lock, or [drag from it onto another key](https://wmkeyboard.pages.dev/typing/gestures/#dragging-from-a-modifier-key) to fire the combination in one movement, the same as dragging from Shift), keys apps understand (Tab, Esc, arrow keys), a text editing action (the same twenty-five operations the text editing panel's keys run: the four arrows, word left and right, page up and down, start and end of the line, start and end of the text, Select, Select all, Select word, Select line, Cut, Copy, Paste, Delete, Forward delete, Delete word, Delete word forward, [Retype word](https://wmkeyboard.pages.dev/smart/suggestions/#retyping-the-last-word) and [No space](https://wmkeyboard.pages.dev/typing/glide-typing/#the-space-after-a-glided-word), whose hold arms it for the next key, picked on a second screen the moment you choose it; the arrows, the word and page moves, the two deletes and the two word deletes repeat while held, the rest can hold to an alternate), chorded input (braille dot, morse dot, morse dash), and a catch-all group for an "Open a tool" key, a broadcast-intent key (fires an Android broadcast for automation apps like Tasker) and a "Nothing" key that deliberately swallows taps. Giving a key a different action clears the label and icon it had, so a key that was Del and is now Ctrl draws what the keyboard draws for Ctrl until you label it yourself. A key that switches to typing text keeps its label. The list is longer than the dialog, so it scrolls, and a rail down its right edge says so: one segment for each group, sized by how many actions the group holds, with the group you are reading lit. Drag the rail to move through the list quickly, and the group name follows your finger. - **Tool**, on an "Open a tool" key only: which of the keyboard's tools the key opens, picked from a list of every tool this build has. The key wears the tool's own icon, and pressing it does exactly what tapping that tool on the toolbar does. The tool doesn't have to be on your toolbar for the key to work, so this is a way to keep voice typing or the editing pad one press away without spending a toolbar slot on it. - **Layout**, on an "Open a layout" key only: which of your secondary layouts the key shows, picked from a list of them by name. The layout's name lands as the key's label unless you already wrote one. Pressing the key shows that grid in place of the letters; pressing it again, or any ABC key, goes back. - **Mode**, on a "Toggle a keyboard mode" key only: which of your keyboard modes the key turns on, picked from a list of them by name. The mode's name lands as the key's label unless you already wrote one, and an unlabelled key shows ◐. Pressing the key picks that mode the way the Modes tool does; pressing it again goes back to automatic. The same action can sit on a flick, a long-press alternate, a multi-finger swipe or a `?123` chord. The key holds on to the mode by its id, so renaming the mode keeps the key working, and a key naming a mode you've since deleted does nothing. In the JSON it is `{"type": "mode", "id": "…"}`. - **Width**: a slider, a minus / value / plus stepper, preset chips for 1 / 1.25 / 1.5 / 2 / 4, and a "Fill the row" button. The stepper is the one to reach for when a preset is close but not right: the buttons move by 0.05, and the field between them takes an exact value you type. Widths go to two decimals, so **1.43** (the width seven keys need to fill a ten-wide grid) is something you set here rather than in the JSON. **Share of the row** under the stepper says the same width as a percentage of the grid, and takes one: type `10` and a key on a ten-wide grid becomes 1.00 wide, `12.5` makes it 1.25. The layout still stores grid units, so the file reads the same either way. - **Covers N rows**: chips that make the key taller, so it reaches down into the rows below it. Leave it at 1 (the default, and what every shipped layout uses) for an ordinary key. Set it to 2 and the key draws twice as tall, and the row under it lays its own keys out *around* the column the tall key is standing in, the way cells flow around a `rowspan` in a table. That's how arrangements like ClearFlow put a full-height Enter down the right-hand edge. The chips stop at the number of rows left below the key, so a key on the last row doesn't offer the control at all. - On every key except a panel component: **Icon**, a picker that draws the icon in place of the label. It lists the keyboard glyphs first (arrows, editing, keyboard chrome), then the app's own icons, with each icon's name under it and a search box. The name is what a hand-written layout stores in `icon`. Names match case-insensitively, shorthand aliases like `delete` for `backspace` or `globe` for `language` also work, and an unrecognized name falls back to drawing the label. Every icon the keyboard draws anywhere of its own accord is in the list, so any tool's glyph can go on a key. The search box takes more than the stored name: an alias and the name of the tool that wears the icon both find it, so searching for "clipboard" lands on the paste glyph and "selection mode" on `SelectAll`. An icon on Shift, Enter, Delete or the globe key replaces the glyph the icon pack gives it. On the space bar the icon replaces the language name, and **Keep the language name** (`iconBesideLabel`) draws it before the name instead. - On text keys only: **Icon hint** (the same picker, for the corner hint, under Press and hold), and **Field adaptation** under More (none, comma slot, period slot, or exactly as laid out). An email field replaces the comma slot with `@` and a web-address field puts `/` first on its press and hold, while the period slot keeps its character and gains domain endings like `.com` on its long-press. "None" means *work it out from the key*, which is what every layout written before this control existed relies on: a `.` or a `,` on the bottom row fills its slot whether or not it says so. **Exactly as laid out** is the way to say you meant it: the key fills no slot and nothing substitutes its character, which matters on a language that ends sentences with something other than a full stop. On Japanese, Bengali, Hindi and the rest, a bottom-row `.` on the letters page is otherwise drawn as that language's own mark (`。`, `।`) with the full stop moved to its long-press. Every other layer (the symbols pages, an Fn or Number layer, a secondary grid) leaves the key alone and puts the mark on the long-press instead, so a decimal point is always one tap. - **Repeat while held**: a switch that makes holding the key keep doing what it does, the way holding Delete does. It waits out the same **Time before a key repeats** every other repeat does and then runs at **Repeating key interval**, both under [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/#timing). Arrow keys are the case it exists for: add a row of ← ↓ ↑ →, turn this on for each, and a hold walks the cursor instead of asking for one tap per character. It's offered wherever a second press does something the first didn't, so not on a layer switch, a modifier, a tool or the globe key, and not on the keys that already repeat by themselves. It costs the key its press and hold, so the alternates fields below vanish while it's on, though its flicks stay: a flick that leaves the key before the repeat starts is still a flick; what you typed into them is kept and comes back when you turn it off. The field is `repeatOnHold` in the JSON. - **Alternates for press and hold** (space-separated, so an alternate can't itself contain a space; multi-character entries like `.com` work fine, and the first one doubles as the small corner hint) and **Actions for press and hold**, a chip each for the entries in that same popup that do something instead of typing. Tap "Add an action" and pick from the same list the Action row offers, minus "types text" and "Nothing": Tab, a layer switch, a modifier, or "Open a tool" for voice typing, the editing pad, the clipboard and the rest. Tap a chip to change it, or the × to remove it. Both fields show on any key whose press and hold is free, which is every key except Delete, forward delete, Space and the braille dots, since those four hold to repeat or to chord. Enter is the one most people want: give it Tab, and a form is one keyboard away from filling itself in. Once a key has both, **Where the actions go** moves the actions from the end of the popup to the front or after any one of the alternates, so they can be one short slide from the key while a plain hold still types the first alternate. The field is `actionAlternatesAt` in the JSON. - **Action on press and hold**: one action that holding the key runs straight away, with no popup, such as Cut on X or forward delete on .. A delete or a cursor move repeats while you hold. It takes the key's press and hold, so the alternates fields go away while it's set and come back when you remove it. Not on Space or a key with flicks. The field is `holdAction` in the JSON. - **Columns in the popup**, once the key actually has alternates: a minus / value / plus stepper that fixes how many of them go on a row *for this key*. It starts at "From settings", which hands the key to the keyboard-wide **Alternate key columns** row under [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/#alternate-keys), and steps on through 3 to 11. It's per key because the right answer is: a `1` carrying a dozen fractions wants a grid five or six wide, while an `e` carrying four accents wants the single row it already gets. A number set here wins over the setting; every other key on the layout keeps following it. The field is `alternateColumns` in the JSON, where 0 means "from settings". - **Hint position**: moves this key's corner hint alone, one dp a press, for a glyph whose shape needs it. - **Hint size**: this key's corner hint as a multiple of every hint's size, from ×0.5 to ×3, for the one key whose hint needs to be bigger than the rest. **Same as every key** follows **Key hint font size** under Appearance. Hidden on a key whose hint is turned off. - **Label size**: how big this one key's caption is drawn, as a multiple of an ordinary letter. Automatic is the default and is what every shipped key uses: a letter is drawn large, and a multi-character mode label like `?123` is drawn smaller. Setting a size replaces that decision, so ×1 is a real instruction and means "draw this word at full letter size". Chips offer ×0.5, ×0.7, ×1 and ×1.3, and a slider covers ×0.3 to ×2. This is where a [HeliBoard label flag](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#label-flags-become-label-sizes) lands when you import a layout. - **The popup**, under the alternates: the press and hold popup as the keyboard draws it, in its columns, with the actions in their place among the characters. The entry a plain press and hold types is outlined. Entries your settings add rather than the layout (the press and hold shortcuts on a, c, v, x, z and y, the accents, a field's web endings) are faded, so you can see which ones are yours to change here. - **Corner hint text**, on text keys: what the corner of the key shows, in place of the first alternate, such as `,?!` on a full stop. It changes only the corner, not what a press and hold types or the order of the popup. Leave it blank for the first alternate. An icon hint wins when both are set. The field is `hint` in the JSON. - **Corner hint**: three ways this one key can answer the keyboard-wide **Press and hold hints** switch under [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/#press-and-hold-shortcuts). "From settings" is the default and follows it. "Never" turns the little hint off on this key alone, and nothing else about the key changes: long-press still opens the alternates, they're just not advertised in the corner any more. "Always" is the opposite, and the reason it exists: turn the hints off everywhere, then put them back on the two or three keys whose alternates nobody would guess. The row only shows up on a key that has a hint to draw (an icon hint, or character alternates on a key that opens the popup), so it doesn't take up a row on the other forty keys. In a hand-written layout the two halves are the `hideHint` and `forceHint` fields, and `hideHint` wins if a file sets both. The strip at the top of the key's settings deletes the key, duplicates it, adds a new key after it, or moves it. Left and right still swap it with a row neighbor, and up and down move it into the row above or below. The grid preview itself is draggable too. Press and hold a key, then drag it to any row, and a caret marks where it will land. Let go on the line between two rows, or above or below them all, and the caret lies flat across the grid: the key gets a new row of its own there, and a row the move leaves empty goes. **Copy** puts the key on the clipboard whole, with its flicks, icon, corner hint, alternates and size, and **Paste after** puts the keys on the clipboard straight after the key you have open. The clipboard carries them between layers and between layouts, so a key built on Letters can be pasted onto Symbols 2 or into another layout. **Select several**, beside undo and redo, picks more than one key: tap keys to pick them and tap one again to put it back. Picked keys get one section for all of them: **Copy**, **Paste after**, **Delete them**, and their width, label size and corner hint set in one go. Keys side by side in one row also move together, with the arrows or by dragging any one of them, which is how the four arrows at the top of Symbols 2 move as a block. Two fields exist only in the raw JSON, because they're rare enough that a dedicated control would crowd the sheet for everyone else. One is a clipboard action (select all / copy / paste / cut / undo / redo, fired on long-press instead of the alternates popup). The other is `multitap`, a list of what tapping the key again types, the way a phone keypad works: `"output": "ㄱ", "multitap": ["ㅋ", "ㄲ"]` types ㄱ, then ㅋ in its place on a second tap, then ㄲ, then ㄱ again. A tap only continues the cycle when it's the same key within a second of the last one; a pause, or any other key, starts over. The [Cheonjiin pad](https://wmkeyboard.pages.dev/languages/cjk/#two-different-input-flows) uses it for its consonants and its .,?! key. ### Rows and layers Select a key and a row action bar appears under the key's settings: **Add key** puts a new key at the end of that row, and the other two buttons duplicate the row or delete it (disabled once only one row is left). A row with no keys in it shows in the preview as an **Empty row** cell; tap it to select the row and delete it. A warning line shows if the row's total width doesn't match the layer's overall grid weight, red when the row is wider than the grid and squeezed, plain when it is narrower and drawn centred. **Row width** sets the row's width directly, every key keeping its share, so any row can be short and centred the way the second row of QWERTY is. A **Fit the row to the grid** button under the warning makes the row exactly the grid's width in one press: it scales every key in the row by the same factor, so a row whose keys are deliberately several different sizes keeps those proportions and still lands exactly on the grid. That's the one to use on a mixed-size row. "Fill the row" in the key sheet is the other answer, and it gives all the slack to the one key you have open. A row height slider (×0.4–×2.5, with a stepper and 0.75/1/1.25/1.5/2 presets) adjusts just that row, and the preview draws each row at the height you set it to. **Add a special key** puts a ready-made key after the selected one: Shift, caps lock, ⌫, forward delete, Ctrl, Alt, Meta, Tab, Esc, the arrow keys, the layer, emoji and toolbox keys, and every text editing operation by name, such as Copy, Paste, Select all or Word left. Each comes with its action set and the label or icon the keyboard draws for it. A row narrower than the grid is centred, with equal gaps at both ends. That's how a nine-key home row sits under a ten-key top row. The one exception is a row with a space bar on it: the space bar stretches to take up the slack instead, so the bottom row always reaches both edges of the keyboard however wide the letters above it are. It's the same rule the tablet widening uses. A row with no space bar, or with two, is centred as before. Below the grid: "Add a row" appends an empty one, "Reorder rows" opens a drag-reorder dialog, "Reorder keys in row N" appears once a multi-key row is selected, and "Reset this layer" (shown only on an authored layer) drops your changes and falls back to the inherited grid. ### Copying a layer Two rows above "Reset this layer" move one layer at a time. **Copy this layer** puts the grid you're looking at on the system clipboard, and **Paste a layer** replaces the grid you're looking at with whatever layer is on the clipboard. That covers four jobs with one pair of buttons: duplicate the Date pad into the Time tab, carry a symbols page from one layout to another, hand someone a layout's letters grid in a chat message, or keep a plain-text backup of a layer you're about to rewrite. The clipboard holds the same JSON the [raw editor](#reaching-the-raw-json) prints for that layer, wrapped in a small header naming the format and which layer it came from. That header is why pasting a shopping list is rejected rather than half-read, and why the confirmation can tell you what landed where. Copying works on an inherited layer too, which is the common case for sharing, since most layouts change the letters, or the letters and the symbols, and nothing else. A pasted layer goes through the same repair pass an imported layout gets, judged against the layer it lands in rather than the one it came from, so a key naming an action this build doesn't have is dropped and reported instead of breaking the grid. What it doesn't do is add keys. Paste a grid with no space bar into Letters and it stays exactly as you pasted it, with the missing keys named in the Problems list. Paste is one undo step, so a paste onto the wrong tab is one press to take back. Undo and redo hold up to 30 whole-layout snapshots for the session (not saved between visits), and everything you type into one key's settings coalesces into a single undo step, so relabeling a key isn't twenty tiny undos. There's no save button: every change writes through immediately, and the screen says so ("The app saves every change"). A "Problems" section at the bottom lists live validation findings as you edit. See [Details & edge cases](#details--edge-cases) for what gets checked. ### The bottom row and its settings The [Bottom row keys](https://wmkeyboard.pages.dev/reference/settings/layout/) settings change the bottom row of every layout as the keyboard draws it: **Emoji key instead of 🌐**, **Swap the comma and 🌐 keys** and **Keep the 🌐 key in one place** are all on by default, and **Show the 🌐 key** and **Comma key opens emoji** can change it too. The editor draws the row the same way the keyboard does, so with the default settings a shipped layout stored as ?123 · `,` · 🌐 · space shows ?123 · emoji · `,` · space in the editor as well. Under the grid, a note names the settings that change this layer's row, with a link to them. Moving or changing a key those settings moved or changed (the emoji key, the comma beside it, a spacebar they resized) turns on **Keep this bottom row as it is here** for that layer. The layer then keeps the row exactly as the grid showed it, your change included, and the settings leave it alone from then on, so a key you place stays where you put it. You can also turn the switch on yourself before editing. The switch stays on the page whenever any of those settings is on, so you can always turn it back off. Turn it off and the settings arrange the row again; on a layer you haven't changed in any other way, that also puts back the standard row. Keys the settings don't touch, like the full stop or Enter, edit in place without turning anything on. The switch is per layer. The symbols page a layout still inherits keeps following the settings, and since the letters were written down as the settings drew them, the two rows still match on the way into ?123. In the raw JSON it is `"bottomRowAsLaidOut": true` on the layer. ### Keeping a layer open Normally the keyboard goes back to the letters every time it opens: minimise it, switch apps, tap into another field, and whatever layer you were on is gone. **Keep this layer open** is a switch on every layer except the letters of an ordinary layout (and on the one grid of a secondary layout), and it changes that rule for that layer alone. The same switch sits in each [panel layout](#panel-layouts)'s editor, so the text editing pad, the emoji panel, the clipboard or the trackpad can stay open across fields the same way. With it on, the layer stays until you press a key or a tool that takes you somewhere else. Closing the keyboard is not that, and neither is a new field, an app switch, or the keyboard being shut down and started again in the background between one field and the next. It's a per-layer switch on purpose. One symbols page you actually live on can stay put while the same layout's Fn layer still springs back, with no list of "persist if…" conditions to maintain. The catch is in the switch's own subtitle: the keyboard can no longer take you off the layer by itself, so it needs an ABC key, a symbols key, or the Secondary layout tool on the toolbar. The Problems list repeats the reminder for as long as the switch is on, as a warning that stops nothing. ### Label font and size Two rows below the grid set the type for the whole layout, not one layer and not one key. **Label font** picks the face this layout's captions are drawn in. It offers the same three sources the [theme font](https://wmkeyboard.pages.dev/themes/fonts/) does: follow whatever is already set, a Google Fonts family, or a font from your installed library. On a layout whose language uses a script with its own font list (Bengali, Devanagari, Arabic and the rest), the picker narrows to faces that carry that script. The layout's font wins over the theme's, and loses to a font you picked for that script yourself under [Fonts](https://wmkeyboard.pages.dev/reference/settings/fonts/). For a Latin, Cyrillic or Greek layout that means the English font: set it to anything other than System default and it wins over this layout's font. The order runs: your own font for the script, then this layout's, then the theme's, then the automatic face for the script. Naming a font that isn't installed on a device costs nothing: it falls through to the next answer instead of leaving the keys blank, which is what lets a layout file carry a font across devices. **Label size** is a multiplier, not a size. Automatic is the default. Set ×0.8 and every label on this layout is drawn a fifth smaller than it would otherwise be, whatever that turns out to be: your Appearance font size, your accessibility font size and any size the theme sets are all still in effect underneath it. That is the point. A layout that replaced the number would quietly throw away larger type you set on purpose. The range is ×0.5 to ×2. **Label size on this layer** is the same control for one layer, and it sits above the layout-wide one. It replaces the layout size rather than stacking on it, which is what keeps the two independent: grow the letters to ×1.4 and the symbol page stays exactly where it was, as long as you give the symbol page a size of its own. Leave it on Automatic (the default on every layer) and the layer follows the layout. Setting it on a layer you haven't edited yet forks that layer's grid into this layout, the same as any other edit to an inherited layer. **Key height on this layout**, **Key spacing on this layout**, **Left padding on this layout** and **Right padding on this layout** give this layout sizes of its own, so a one-handed grid can be narrow and tall without dragging QWERTY along with it. Each one reads "Same as everywhere" with the size it is following until you set it; tap it for a slider, and **Same as everywhere** in that dialog drops the layout's own value again. The layout's sizes win over a theme's and over **Layout & size**, and lose only to a size you set for one screen shape under **Per-screen sizing**. The sizes travel with the layout when you share or export it. All three are stored in the layout, so they export with it and they apply only while that layout is the one you're typing on. Switching language switches them back. ### A theme of its own A layout can name a theme, and so can each of its layers. The **Theme** row sits under the layout's name in **The whole layout**, and a **Theme for the … layer** row sits with the other layer-scoped controls in **This layer**. Once either names a theme you made, an **Edit** button on the row opens it in the theme editor. Both open the same picker the [keyboard modes](https://wmkeyboard.pages.dev/typing/modes/) use, with every built-in theme and every theme you've made, so a custom theme is as pickable as a shipped one. **Inherit** is the default on both and means what it says: the layout takes whatever theme is set, and the layer takes the layout's. While a layout with a theme is on screen, the keyboard draws in it. It replaces the theme you set elsewhere and it replaces an automatic light-and-dark pair for as long as the layout shows, and your own selection doesn't change: switch to another language and its theme comes back. A layer's theme beats the layout's, so a symbols page can wear its own colours over a layout that has some of its own. The [panels](#panel-layouts) have the same row, **Theme for this panel**, on their tabs and in the shared panel editors: while that panel is open the keyboard draws in its theme, and a panel with none inherits the layout's. The preview in the editor draws in the theme the grid you're editing asks for. A theme you've since deleted is ignored rather than removed from the layout, so a layout exported with its theme still remembers the pairing. In the picker, **Inherit** sits alone at the top. **Default (system)** is not another way to inherit: it is the built-in Material You theme, and it's listed first under **Built-in themes**. Picking it pins that grid to Material You whatever theme you set elsewhere. While any layout you type with, one of its pages, one of its panel tabs or a shared panel layout names a theme other than the one you picked, the [Themes screen](https://wmkeyboard.pages.dev/themes/overview/) and the [theme editor](https://wmkeyboard.pages.dev/themes/editor/) list each one in a notice at the top, and its **Clear** button puts them all back to inherit. ### Different keys sideways **Show this key**, on a key's own page, keeps it to one way up: **Always**, **Upright** or **Sideways**. A key for one way up takes no room the other way and the rest of its row closes up, so a duplicate `g` and `v` for two thumbs can sit in the middle of a row only when the phone is turned sideways. The editor always draws every key so you can reach it. A row left with no keys goes altogether. **Layout when sideways** is a choice for the whole layout: while the screen is sideways the keyboard draws that layout's keys instead. It offers layouts of the same language that type the same way, since only the grid changes; the language, the suggestions and what the keys compose stay this layout's. Both choices work on a copy of a built-in layout too. Copy it first from the layout's ⋮ menu. ### Tablet keys **Add tablet keys automatically** is a switch for the whole layout, not one layer. On a tablet the keyboard widens every ordinary alphabetic grid by a column each side and fills the space with Tab, backslash, caps lock, a second shift and the rest, described in full under [size & position](https://wmkeyboard.pages.dev/typing/size-position/#on-a-tablet). It's on for every layout, including yours. Turn it off for a layout you've already laid out wide by hand, or one you want kept exactly as authored. If the switch says the setting has no effect, the widening already declines this layout on its own (it does that for flick pads, braille, morse, any grid with no shift key, and any grid with a key that covers more than one row), and the switch is only there to record your answer in case the layout changes later. Your phone grid is what the editor shows and what you're editing. The tablet keys are added on top of it at typing time, so there's no second grid to keep in step. ### Reaching the raw JSON "Edit as JSON" at the bottom of the grid editor opens the layout as text, for anything the grid editor has no control for: clipboard actions, or pasting in a grid you built elsewhere. The layout's **name**, its **language** and its **typing method** used to live here too. All three are rows in the grid editor now, so a duplicate can be renamed and re-languaged without touching the JSON. To type one language on another language's layout, QWERTZ for English say, there's a shorter way: **Use a layout from another language** on that language's screen under [Languages](https://wmkeyboard.pages.dev/languages/overview/) does the copy and the language change in one pick. The language picker is the same searchable registry dialog the foreign-layout import uses, and the language is what picks the dictionary, autocorrect, script rules, dictation and shift behaviour for the grid. Typing method defaults to *follow the language*, which is right for almost every layout. Override it only when one language has both a phonetic and a direct grid. languages/layout-json-editor — The raw JSON editor, with syntax highlighting and an Apply button. This screen works differently from the grid editor. It's a draft with an explicit **Apply** button rather than autosave, because half-typed JSON isn't a valid layout and there's nothing sensible to save as you type. The text starts as your layout, JSON-encoded. Applying it parses the text, and invalid JSON shows "That is not valid layout JSON.", jumps to the first error, opens **Problems** and stops there. A valid parse always keeps the id you were already editing, so pasting a different layout's id in is deliberately ignored and you can't overwrite a different layout by accident. It then runs the result through the same repair pass used on import (see below) and shows what got fixed. If nothing needed fixing, the screen closes for you. If something did, a dialog lists the changes, and the screen closes once you have read them. Back leaves without saving. "Edit as JSON" opens a full-screen code editor, the same one the plugin editor uses, and it knows the layout format. It's monospaced, on a code background, with the standard editor colors, numbered lines, the cursor's line lit and bracket pairs boxed. Long lines scroll sideways rather than wrapping, which keeps one number against one line; **Wrap** in the menu turns that off. Undo, redo and **Apply** sit in the top bar. It suggests as you type, from the format itself. Type `"` inside a key and the list shows the key's properties, the required `label` first, each with its type and default. Choosing one writes the property, its colon and a blank of the right kind (`""`, `[]`, `{}`, or `true` for a switch that is off by default) with the cursor in the blank, and when that blank has values of its own the list opens again. Pick `action`, then `tool`, then a tool, and you have written a tool key without typing a word. The values come from your device: tools, editing operations and panel components by their names in your language, icon names, your themes and fonts, your secondary layouts, and every language, found by its name as well as its id. In a row, a word like `shift` offers a whole shift key, and inside `layers` the list offers each layer you have not written yet, with a row ready to fill. Matching forgives shorthand, so `lp` finds `longPress`. **Suggest** on the code keys, Ctrl+Space or Ctrl+I opens the list anywhere. It checks as you type, too. A problem gets a squiggle and a colored line number, and **Problems** under the code lists them all; tap one to jump there. The checks follow what Apply does. An error is something Apply can't read, like a missing `label`, a number in quote marks, or an `op` this version does not have. A warning is something Apply survives by ignoring or changing it, like a misspelt property (named with the one it's close to), an action type it does not know, or a width past what the keyboard allows. Once the text parses, the grid editor's own checks run as well, so a letters layer with no delete key is flagged here the same as it is there. A strip under the code explains whatever the cursor is on: the property, with its type, its default and a sentence about what it does, or for an action's `type`, the action and the fields it takes. Tap the strip for the whole paragraph. On a soft keyboard, a row of code keys sits on top of it: `"`, `:`, `,`, both kinds of bracket, `true`, `false` and `null`, a second page of commands (duplicate or move a line, select, jump to a block's start or end, find, format), and two cursor keys you can slide like a trackpad. Arrange the row from **Code keys** in the menu. The menu also has **Find** (with replace, regular expressions, and case and whole-word matching), **Go to line**, **Fold all** and **Unfold all** (every object and list folds from a chevron in the margin), text size, **Copy** and **Paste**. **Format** re-indents the whole document with two spaces per level, and keeps an object or a list on one line while that line stays under 100 characters. A three-item list of long-press alternates stays a three-item list rather than becoming five lines, and a key is one line rather than six. The screen already opens that way, so Format is only for text you've pasted or hand-edited. QWERTY reads as 252 lines here where one field per line made it 1,142. On a hardware keyboard, the editor takes the keys Visual Studio Code uses, listed below. Typing gets a few editor habits too. A newline keeps the indentation of the line above and goes one step deeper after `{` or `[`. Typing `{`, `[` or `"` brings its closing half, typing a closing half where one already sits steps over it instead of doubling it, and backspacing over an empty pair takes both halves. None of this rewrites your text: what you typed stays exactly where you typed it until you press Format or Apply. #### Keyboard shortcuts With a physical keyboard, this editor and the plugin editor take the keys that Visual Studio Code uses on Windows and Linux, and ⌘ works as Ctrl. While the keyboard is attached, the menu shows the keys of each command beside it, and the row of code keys steps aside. | Keys | What they do | | --- | --- | | `Ctrl+F`, `Ctrl+H` | Find, or find and replace. A selection on one line becomes the search. | | `Enter`, `Shift+Enter`, `F3`, `Shift+F3` | The next or the previous result. In the replace field, `Enter` replaces the result that shows and `Ctrl+Alt+Enter` replaces them all. | | `Alt+C`, `Alt+W`, `Alt+R` | In the find bar: match case, whole words, regular expression. | | `Esc` | Closes the suggestion list or the find bar. In the code, `Esc` does not leave the editor. | | `Ctrl+G` | Go to line. | | `Ctrl+Space`, `Ctrl+I` | Suggest at the cursor. | | `Tab`, `Shift+Tab` | Indent at the cursor, or move whole selected lines in. `Shift+Tab` moves them back out. | | `Ctrl+]`, `Ctrl+[` | Move the lines at the cursor in or out. | | `Alt+Up`, `Alt+Down` | Move the lines up or down. | | `Shift+Alt+Up`, `Shift+Alt+Down` | Copy the lines up or down. | | `Ctrl+Shift+K` | Delete the lines. | | `Ctrl+Enter`, `Ctrl+Shift+Enter` | A new line below or above, with the same indent. | | `Ctrl+X`, `Ctrl+C`, `Ctrl+V` | With nothing selected, cut or copy the whole line, and paste it back in above the line of the cursor. | | `Ctrl+L` | Select the line. Each press adds the next line. | | `Shift+Alt+Right`, `Shift+Alt+Left` | Grow the selection to the word, the line, the block and the document, and shrink it back. | | `Home` | The first character of the line that is not a space, then the start of the line. | | `Ctrl+Home`, `Ctrl+End`, `Page Up`, `Page Down` | The start, the end, one screen up or one screen down. Add `Shift` to select. | | `Ctrl+Up`, `Ctrl+Down` | Scroll one line, and leave the cursor where it is. | | `Ctrl+Shift+\` | Go to the matching bracket. | | `Ctrl+Shift+[`, `Ctrl+Shift+]` | Fold or unfold the block at the cursor. | | `Ctrl+K Ctrl+0`, `Ctrl+K Ctrl+J` | Fold every block, unfold every block. | | `Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z` | Undo, redo. | | `Shift+Alt+F` | Format. | | `Alt+Z` | Wrap long lines, or stop wrapping them. | | `Ctrl+=`, `Ctrl+-`, `Ctrl+0` | Larger text, smaller text, the usual size. The size and the wrapping stay as you left them, in every code editor in the app. | | `F8`, `Shift+F8` | The next or the previous problem. | | `Ctrl+Shift+M` | Show or hide **Problems**. | | `F1`, `Ctrl+Shift+P` | Open the menu. | | `Ctrl+S` | Here, **Apply**. In the plugin editor, save the draft now. | | `Ctrl+/`, `Shift+Alt+A` | Plugin editor: a line comment, a block comment. JSON has no comments. | | `F5`, `Shift+F5` | Plugin editor: run or stop the plugin. | | `F12`, `F2` | Plugin editor: go to the definition, rename. | | `Ctrl+Shift+O`, `Ctrl+Shift+Y` | Plugin editor: **Outline**, **Console**. | The keys that the text field knows already keep working: `Ctrl+A`, the arrow keys with `Ctrl` and `Shift`, and `Ctrl+Backspace` to delete a word. The editor has one cursor, so the VS Code keys for more cursors, such as `Ctrl+D`, do nothing here. When WM Keyboard is your keyboard, it takes `Ctrl+Space` to change the language before the editor gets the key (see [Switching language](https://wmkeyboard.pages.dev/typing/hardware-keyboard/#switching-language)). Use `Ctrl+I` for suggestions, or turn that setting off. #### Every property and value Each section below opens to the full list for one part of the format: every property an object takes, with its type, its default and what it does, every action type with its fields, and every value a fixed list allows. The lists are read from the app's own code when these pages are built, so they match the version they describe. The editor's suggestions offer the same values as you type. ## Secondary layouts A secondary layout is a grid of your own that isn't a language: a symbols page arranged your way, a macro pad of Ctrl shortcuts, a strip of arrows and function keys. You reach it from a key or from the toolbar rather than from the 🌐 key, and it shows *over* the letters of whatever layout you're typing with, so the dictionary and autocorrect stay that layout's. It never appears under Languages, is never a stop in the language cycle, and has no on/off switch: it's live as soon as something opens it. **Start a secondary layout** heads its own group on the Key layouts screen. It builds three rows of blank keys plus an ABC key and opens the editor, and that's all it builds: nothing is copied from a shipped grid, and the repair pass that gives an ordinary layout its delete, space and enter keys leaves a secondary layout alone. A macro pad has no business being handed a space bar. The editor is the same one, minus the parts that don't apply: there's one grid rather than layer chips, no language or typing-method row, and no tablet widening. Two things open one: - **A key**, on any layout, set to the "Open a layout" action and pointed at the secondary layout by name. Pressing it shows that grid; pressing it again, an ABC key, or a ?123 key leaves it. The same action works as a press-and-hold alternate, so a hold on ?123 can reach your own symbols page without spending a key on it. - **The Secondary layout tool** on the [toolbar](https://wmkeyboard.pages.dev/tools/overview/). With one secondary layout it shows that one; with several it opens a picker panel listing them by name, and a tap on a name shows that grid. **Layout to show** on the tool's page pins one instead, so the tool skips the picker and always shows that layout; "Ask each time" is the default. The tool only appears once you have at least one secondary layout: until then its row on the Tools screen is greyed out and reads "Needs a secondary layout", and its switch cannot be turned on. It's lit whenever any secondary layout is up, however it got there, so a press then always takes it down. A secondary layout with no key that leaves it gets a warning in the Problems list ("make sure you have a way to exit this layout"), not a block: a fresh field closes it anyway unless you've also switched on [Keep this layer open](#keeping-a-layer-open), which is the combination the warning is really about. A key naming a secondary layout you've since deleted does nothing rather than opening something else. Secondary layouts export and import like any other layout file, keys and all. The "open a layout" keys inside one carry the *id* of the layout they point at, so a set of them shared together keeps working; a single one imported on its own has keys that point at nothing until you re-pick them. ## Panel layouts The emoji panel, the GIF and sticker panels, the clipboard panel, the text editing panel, the trackpad, the Numpad and the handwriting panel are layouts too: grids drawn by the same editor, with the same row tools, key sheet and undo, and the keyboard draws each panel from its grid. They live in two places, and the second inherits from the first: - **Shared panel layouts.** Layout & size / Key layouts / Panel layouts lists the eight panels (seven on the F-Droid build, which has no handwriting) and says **Standard** or **Your own** under each. Each one's editor is headed with the panel's own name ("Emoji", "Clipboard") rather than with the words "Panel layout", so the row and the page it opens carry the same word. These are what every layout uses unless it has a grid of its own. The same editor opens from the Emoji screen, the Clipboard screen, and the GIFs, Stickers, Text editing, Trackpad, Numpad and Handwriting tools' pages. **Use the standard layout** at the bottom puts the shipped grid back. - **A layout's own panels.** Every key layout's editor has a tab for each panel, after the Fn tab, on the same strip as Letters and Symbols. A panel tab shows the shared grid until you change something there, then it forks a copy that belongs to this layout only, exactly the way an untouched Symbols tab inherits the shipped page. A pencil on the tab marks a panel this layout owns. **Use the shared panel** at the bottom drops the copy again. So a Bangla layout can carry its own text editing pad while your other layouts keep the shared one, and the panel's own font size, "keep this layer open" switch and rows are all per layout there. Which grid the keyboard draws is decided when a field opens: the layout you're typing with, its own grid if it has one, else the shared one, else the shipped one. What makes a panel different from a key layout is the **component**: a cell that holds a live part of the panel rather than a key. The emoji panel has four of them (the category tabs, the search box, the emoji grid and the switch to the GIF and sticker panels), the clipboard panel has four (the search box, the grid or list switch, the strip of copied fragments and the history), the GIF and sticker panels have two (their search and results, which is the search box, the source and category chips and the results as one cell, and the same switch the emoji panel has), the trackpad and the handwriting panel have one each (the pointing surface, and the writing area with its language chip, undo and full-screen buttons on it), and the text editing panel and the Numpad have none, since they are all keys. A component is placed like any key: pick **Panel component** in the Action picker, choose which one, then give it a width and a row span. The preview draws it hatched with its name. Every other cell is an ordinary key with every option a key layout offers, so a panel can carry a bottom row of abc, space and backspace, an Enter key, a tool key, or a text editing action. Heights work differently from a key layout, because a panel always fills exactly the space the key rows would take, so the keyboard never changes size when a panel opens. Rows made of keys, or of the strip components (the tabs, a search box, the fragment strip), stay at key height, multiplied by their row height like any row. Rows that start the panel's body (the emoji grid, the clipboard history, the GIF or sticker results, the trackpad surface, the handwriting area) share whatever height the other rows leave, in proportion to their row heights. A strip with nothing to show, like the fragment strip when no clip has a code in it, takes no row at all. On the text editing panel there is no component, so all of its rows share the height by their row heights, which is how that pad has always been drawn. With the full-screen emoji panel on, a first row made only of the tabs, the search box and the switch moves up into the header beside the back button, the way the tabs always sat there. A GIF or sticker panel you haven't laid out yourself borrows its bottom row from the emoji panel: the row that holds the switch when it's keys and the switch alone, else the emoji panel's last row of keys, so a bottom row you rearranged goes with you from panel to panel. Once you edit the GIF or sticker panel, it is drawn as you laid it out. If it has no switch component, the switch sits beside its search box while the switch setting is on. The full-screen clipboard panel does the same with a first row made only of its search box and its grid or list switch. The switch hides while the history is empty, so a row holding just the switch and a hidden search box takes no room until there's a clip. **Keep this layer open** is on every panel's editor too: on, that panel survives the keyboard closing and a change of field, and is left only by a key, a tool or the panel's own abc key. Back is the exception: for a panel you asked to keep, the ⌄ in the navigation bar puts the keyboard away as it does everywhere else, rather than closing the panel, and the panel is still there next time. (Esc on a physical keyboard still closes it, since there is no ⌄ there to use instead.) The Problems list carries a reminder for as long as it's on. A few rules keep a panel drawable, and the editor lists them under Problems like a key layout's. A panel must hold its main component once (the emoji grid, the clipboard history, the GIF or sticker search and results, the trackpad surface, the handwriting area), and no component more than once. A component from another panel is not allowed, and neither are the two keys only a converted layout can use (a kana variant key, a Keyman key). Everything else a key layout offers works on a panel too: Shift and caps lock case the panel's own text keys and extend its cursor moves into a selection, the braille and morse keys type through the same engines they do on a grid, and ?123, Fn and "open a layout" close the panel on their way to that layer, the way abc closes it on the way to the letters. A drag off ?123 or abc closes nothing: it [shows that layer over the panel](https://wmkeyboard.pages.dev/typing/gestures/#dragging-from-123-or-abc) while you hold and types the key you lift on. The keyboard repairs what it draws rather than refusing: a missing main component is added in a row of its own, duplicates and foreign components are dropped, and an empty panel falls back to the shipped one. A panel without an abc key gets a warning and nothing more, since the toolbar and the Back button both leave a panel. With **Keep this layer open** on, though, Back no longer does, which is what the reminder above is about. Two things stay outside the layout. While you are typing into the emoji, GIF, sticker or clipboard **search**, or editing a clip, the panel shrinks to its compact form and the real key rows come back underneath, exactly as before; the layout is what the panel shows the rest of the time. And the full-screen setting for the emoji, GIF, sticker and clipboard panels still applies: on, the layout fills the whole keyboard under a slim back header. **Edit as JSON** works for a panel too, and the file names the panel and holds the grid in the same shape a key layout's layer does, with `"type": "field"` and a `kind` such as `emoji_grid` on a component cell. ## Layout files and sharing A layout exports as a `.wmlayout.json` file: the same envelope format every shipped asset layout already uses internally, tagged `"format": "wmkeyboard-layout"`. Export and duplicate are in the ⋮ menu on every row of the layout gallery, both for your own layouts and the built-in ones. **Export layouts in use**, under **Import a layout** at the bottom of the gallery, puts every layout that is on and every one of your own, secondary layouts included, in one `.wmlayouts.json` file, tagged `"format": "wmkeyboard-layouts"`, named like `Layouts-20261027-1405.wmlayouts.json`. **Import a layout** reads it back: the dialog lists the layouts inside and adds them all, beside any you already have. **Import a layout** also takes several files in one pick, layouts and files of several layouts alike, and lists everything they hold in that same dialog; the names of any files it could not read are said in a message after it. To act on several of your own layouts at once, long-press one under Your layouts or Secondary layouts. The rows turn into checkboxes: tap more of them, or use **Select all** in the bar at the bottom. The upload icon there saves the ticked layouts in one `.wmlayouts.json` file, the same file **Export layouts in use** writes, and the trash icon deletes them after asking once. While picking, a tap ticks a row instead of opening the editor; the close button or Back stops picking. Built-in layouts can't be picked, since what they have to delete is only your edits, and **Reset** on each does that. Each keeps the id it had when nothing on this phone uses it, so a key in one layout that opens another still opens it after the move. One layer travels on the clipboard instead of in a file, tagged `"format": "wmkeyboard-layer"`. See [Copying a layer](#copying-a-layer) for when that's the easier way to share a grid. The exported filename comes from the layout's name (stripped of anything that isn't a letter, digit, space, underscore, or hyphen), falling back to "layout" if that leaves nothing, followed by the date and time of the export: `Mine-20261027-1405.wmlayout.json`. The file is indented JSON, so it reads and compares cleanly in any text editor. The save dialog opens in the folder your last layout export went to, and importing opens in the folder you last imported a layout from, apart from where backups go. Some phones (Samsung among them) only follow that when handed a folder rather than a file, so the app hands them the folder. Import accepts `.wmlayout.json` files through the in-app "Import a layout" row, or by tapping one directly in a file manager or chat app, since WM Keyboard registers as a handler for the `.wmlayout.json` extension. Either path shows the same confirm dialog with the layout's name, and an imported layout is never switched on automatically. It's added to your layouts, and you turn it on under Layout & size when you're ready. Layouts from other keyboards (FlorisBoard, HeliBoard, FUTO, a Keys Cafe `.kcf` from Samsung Keyboard, or a layout file made for a Windows, Mac or Linux computer) go through the second import row instead; see [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/). The third row, **Import an input method**, makes a layout out of a `.cin` table, a Rime schema or an m17n `.mim` file; see [Input methods](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#input-methods-cin-rime-and-m17n). Import is deliberately permissive about almost everything except one thing: the format tag inside the file. Malformed values, missing fields, an oversized row: all of that gets silently fixed by a repair pass, and the fixes are listed in the confirm dialog so you know what changed. Only a file that isn't recognizably a WM Keyboard layout at all gets rejected. File type detection reads the file's content, not its extension. If a `.wmlayout.json` actually contains a settings backup (people do rename files), it imports as a backup, not a broken layout. ## Installing community layouts Layout is one of the addon types a [repository](https://wmkeyboard.pages.dev/addons/installing/) can offer. Installing one works like a manual import (it's added to your layouts under Layout & size, not switched on) except the confirm prompt asks "Turn it on so you can type with it?" specifically, since a layout you can't type with yet isn't much use installed. Turning it on adds it to the same enabled-layouts list the Your layouts toggle uses. Addon-installed layouts don't show up in the "Your layouts" group on the Key layouts screen, which is for layouts you built here. The switch that turns an installed layout on lives under Layout & size → Your layouts instead, and uninstalling it (from the [Addons](https://wmkeyboard.pages.dev/addons/managing/) screen) removes it the same way deleting a layout does. ## Options Layout & size / Key layouts This screen has two sections plus two import rows between them. **Your layouts** starts with **Start a new layout** and then lists every layout you've made or edited yourself, on or off. Duplicate leaves its result switched off (Import offers **Import and turn on** as well), and hiding an off layout would mean the thing you just made disappearing; the switch on each row turns it on in place. **Built in** lists the shipped layouts you've enabled (3,395 ship in total: 36 compiled built-ins, including QWERTY's English alternates like Dvorak and Colemak, plus 3,352 JSON assets covering the long tail of languages and scripts, including regional Latin standards like BÉPO and 862 converted Keyman keyboards). That's the one filtered group, so enable a layout for a language first under [Languages → Switching](https://wmkeyboard.pages.dev/languages/switching/) and it shows up here to edit. There's no default value to quote for "which layouts exist": a fresh install starts with no custom layouts of your own. The active layout defaults to QWERTY, and the initial enabled set is seeded from your phone's own language list plus QWERTY. See [Languages overview](https://wmkeyboard.pages.dev/languages/overview/#what-ships) for how that seeding works. ## Details & edge cases **Editing a shipped layout doesn't fork a new one.** Change a key on the built-in QWERTY or on a shipped Vietnamese Telex grid, and the edit is stored as an override under that same layout's id. It doesn't become a separate custom layout. That's why the delete button on a shipped row reads "Reset" instead of "Delete": there's no copy to delete, only an override to drop, which brings the original grid back. **Duplicating never activates.** Copying any layout (shipped or your own) creates a new layout and opens it for editing, but never switches to it. A half-finished copy becoming the live keyboard mid-edit is exactly the situation where you'd be stuck unable to type well enough to undo it. **Two passes keep you from getting locked out. One only reports, the other rewrites.** While you're editing, a continuous check flags problems without touching your grid. Blocking findings are a Letters layer with no keys at all or with no Delete, Space or Enter key, a layer with more than 8 rows, a row over 24 keys, a key with zero or non-finite width, a key set to cover fewer than 1 or more than 8 rows, a Braille key naming a dot outside 1–6, a key whose action this build doesn't recognise, and a cycled layer (Symbols, Symbols 2, Fn) with no way back to Letters. Seven findings are warnings that stop nothing: a missing Shift key, since a symbols-forward layout may not want one; an empty row; a row whose keys add up wider than the layer's grid; a key set to cover more rows than the layer has, which the keyboard stops at the last row; a key with neither a label nor an output, which types nothing and is deleted the moment the layout turns on; a layer set to [stay open](#keeping-a-layer-open), as a reminder that it needs a way off; and a [secondary layout](#secondary-layouts) with no key that leaves it. A secondary layout skips the delete, space and enter checks altogether, and the repair pass skips them too. That reporting pass is what the enable toggle consults, wherever it lives for that layout. Try to switch on a layout with a blocking finding and it refuses, with a dialog headed "Fix these problems in the layout editor first:" that names every reason. It's the same list the editor prints, so the two can't drift apart, and turning a layout off is never blocked. The second pass rewrites. It drops unrecognised keys, caps an oversized key or row, and guarantees the Letters layer has a delete, space and enter key by adding default ones if they're missing. It runs on import, when a layout becomes the one you're typing on, and again each time the keyboard builds the grid to draw. That last one is why editing a layout that's already switched on is survivable: your changes go live keystroke by keystroke, and the repair puts a backspace key back the moment you delete the last one. It's deliberately never applied to what's *stored* while you edit, because it would fight you, silently regrowing a row you just removed. **A split keyboard ignores row spans.** Split mode cuts every row near its own midpoint and pushes the halves apart, and a key belonging to two rows can't survive that: the two halves would part company underneath it. So on a split board a tall key draws as an ordinary one-row key and the rows below it fill the width, which is what the same grid looked like before you set the span. Turn split off to see the layout as authored. **Two version numbers, and they mean different things.** The file envelope has its own version (currently 1, unused for migration so far; only the format tag itself is checked on import). A layout's internal spec also carries its own version, separately, bumped when a stored field's shape changes. **Below is a real shipped layout** (the Afar QWERTY grid) rendered from its actual `.wmlayout.json` file, the same format export and import use: --- # Downloadable dictionaries URL: https://wmkeyboard.pages.dev/languages/dictionaries/ Section: Languages Summary: Wordlists for 430 languages, in size tiers, downloaded on demand. Most languages ship with no word list at all: suggestions come only from what you type. A downloadable dictionary adds instant completions in your chosen size, and protects its own words from autocorrect, entirely offline once it's on the device. The exception is the common typos a big list contains, such as "thw", which are still corrected (see [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/)). languages/dictionary-download-row — A language's Dictionary section: the built-in note, a size tier, and the Download button. ## What a dictionary changes Only English and Bangla ship a built-in dictionary inside the app itself. Every other language starts empty, learning words only from what you type and from any list you import yourself (see [Options](#options) below). The **Word list** row in the Dictionaries section on a language's own screen says which case you're in: - **Built-in list. A bigger list is available.**: downloading trades the small on-device list for a larger, more complete one. - **Built-in list**: no downloadable list exists for that language yet. - **For suggestions, autocorrect and glide typing**, with a Download button: nothing is built in, but a list is available. - **No Word list row at all**: nothing is available. The keyboard learns your words as you type, and you can still import a list. Bangla's built-in list, its phonetic index, and the English→Bangla transliteration map it feeds load only while Bangla is one of your enabled languages. Add it and they load. Remove it and they go. Nobody typing French and Japanese carries the Bangla machinery around for nothing. English is the exception and stays loaded, because it's what fields that force plain ASCII fall back to. A downloaded dictionary does two things at once: it adds its words as completions while you type, and it stops autocorrect from "fixing" any word that's in it. For English and Bangla, downloading replaces the built-in list outright, since the bigger file becomes the one the keyboard actually uses. Every other language has no built-in list to replace, so the download's words are combined with anything you've imported yourself. A word in either list gets completions and autocorrect protection, and downloading one doesn't clear the other. That's why French, German, or Spanish, none of which ship anything built in, can still get real completions once you fetch a list. [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/) needs the list too, since a swipe is decoded against it, and the keyboard puts a chip on the strip pointing here when a language has none. ## Two sources: AOSP and Frequency 23 languages have two word lists to choose from: Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Croatian, Italian, Latvian, Lithuanian, Norwegian Bokmål, Polish, Portuguese, Romanian, Russian, Serbian, Slovenian, Spanish, Swedish and Turkish. | Source | What it is | |---|---| | **AOSP** (default) | The curated list that Android's own open source keyboard (AOSP LatinIME) ships, under the Apache 2.0 license. Usually 150,000 to 240,000 words, with far fewer names, typos and subtitle noise than a counted list | | **Frequency** | The list this app uses for every other language, counted from subtitles and web text. Many more words, including more names, slang and misspellings | German's AOSP list has one change of ours. AOSP counted written text, so everyday words like `ich`, `du` and `mir` ranked far too low for typing, and a swipe for `mir` read `mit`. Those words are lifted using the Frequency list, and a few it lacked, like `verstehst`, are added. A list you already downloaded keeps its old figures until you download it again. For these languages the download dialog opens on **AOSP**, with a **Source** choice above the sizes, so you can pick **Frequency** instead. English's AOSP list comes in **United States**, **United Kingdom**, **Canada** and **Australia** spellings, and Portuguese has **Europe** and **Brazil** for both sources. With the Canadian, British or Australian list in use, the 726 American spellings the Canadian list respells (`color`, `center`, `favorite` and their forms) are never offered unless that list itself has them, or you added the word by hand. The word pairs and the context model learned from American text, so without this they offered `color` above `colour`. You can still type an American spelling and keep it. Switching source keeps the region where the other source has it. AOSP itself ships only the American and British lists, so the other two are made from them, keeping AOSP's word ratings and changing only the spelling. **Canada** is the American list with the roughly 730 spellings Canada writes differently swapped in: centre, colour, travelled, cheque, with -ize left as it is. **Australia** is the list LineageOS built from the British one (it adds words like ute and mozzie), with a dozen more Australian spellings such as program. Which spelling each region uses comes from the [English Speller Database](https://github.com/en-wl/wordlist) (formerly SCOWL), the data behind most English spell checkers. To change sources after a download, use the arrow button on the downloaded row. For these languages it opens **Change the word list**, the same dialog, and the list you have stays in use until the new one has finished. The row title says which list is on the device, for example "Word list (AOSP, United States)". A list you downloaded before AOSP lists existed stays what it is (a Frequency list) until you change it. AOSP rates each word on its own 0 to 255 scale. That scale is logarithmic, and the keyboard turns it back into counts on the same scale as a Frequency list, so the two rank words alike. AOSP's lowest-rated words are kept: the list is curated, so there's no noise tail to cut. Records AOSP marks as not being words (misspellings it keeps only to correct, like "aint") are left out. ## Choosing a size Download on the Word list row opens a dialog that asks how many of the most frequent words to keep. Each size says how many words it keeps and about how much it downloads: | Tier | Words kept | |---|---| | Small | 50,000 | | Medium | 150,000 | | Large (default) | 300,000 | | Everything | the whole list, however long it is | The source lists are sorted by frequency and downloaded over a streaming connection, so picking a smaller tier doesn't just trim the result: it stops the transfer early. Thai's full list is 4 million words and 41 MB compressed, but downloading Large only pulls the first 300,000 of them, a fraction of the total transfer. **Everything** is the one tier with no cap: it reads to the end of the list, which for a language like Thai means the whole 41 MB. Every count in the picker is an upper bound, which is why each one reads "up to". Two things can stop a download before its tier does, and both are deliberate: - **The one-timers are dropped** (Frequency lists only). The transfer stops at the first word that occurs only once in the source text. Those are overwhelmingly typos, names, and OCR noise, and letting them in would tell autocorrect that every misspelling is a real word. - **Some words are skipped**: anything over 48 characters, and anything containing a space. The gap can be wide on a smaller language. Welsh's list is 123,099 lines, but only the first 50,637 occur more than once, and 47,596 of those survive the other filters. So every tier from Small upward gives Welsh the same 47,596 words, and the number you see afterward is smaller than the number the tier promised. That's the list, not a failed download. Tiers that would all keep the same words collapse into one, and the picker disappears entirely for a language whose whole list is already smaller than Small, since there is nothing to trim. A list with no frequencies at all goes the other way and is always kept whole, whichever tier you pick, since it has no order to cut by. Bangla's 451,000-word list used to be one of these. It now carries counts from 4.8 million sentences of Bangla text, so its tiers keep its commonest words like any other language's. A word that text never uses is still in the list, at the lowest count, so **All** keeps every word. A Bangla list downloaded before the counts came still ranks by them, because they come with the phonetic index. Fetch the list again with the arrow button on its row to have them in the list itself, and to have the smaller tiers cut by them. The built-in list's frequencies are added to the counts either way (see [Bangla](https://wmkeyboard.pages.dev/languages/bengali/#options)). The tier you pick is remembered, so the dialog opens on it for the next language, and **Download all** uses it too. It starts at Large. Changing it doesn't touch a dictionary you've already fetched, but you don't have to delete one to move it to another tier either: a downloaded row has an arrow button beside its Delete that reopens the same dialog on the size that list is at now. See [Changing the size of a list you already have](#changing-the-size-of-a-list-you-already-have) below. A finished download reports what it actually kept, as an exact word count and file size. When the language also has word pairs (455 languages do; [Downloads](https://wmkeyboard.pages.dev/languages/downloads/) lists them), they download together with the word list, and the dialog says how much they add. A language with more than one list (the two sources above, and Portuguese's European and Brazilian lists or English's American and British ones) still has a single download slot. Fetching one replaces whichever is on the device, so only one list is active for a language at a time. ## Changing the size of a list you already have A downloaded **Word list** row carries an arrow button to the left of its Delete. It opens the same size dialog, already on the tier that list was fetched at, so swapping a Small list for a Large one (or a 41 MB Everything list for a Medium one) is one press rather than a delete and a fresh download. The arrows say which way the trade can go before you open anything: | Icon | What it means | |---|---| | Double arrow up | The list is at the smallest tier this language has. Only bigger ones are left | | Double arrow down | The list is at the largest tier. Only smaller ones are left | | Up-and-down arrows | Tiers exist in both directions | There's no button at all on a language whose whole list already fits inside Small, since the tiers all keep the same words and there is nothing to trade. Picking a size downloads that tier and replaces the file when it finishes. The list you already have stays in place and stays in use for the whole download, because the new one is only swapped in at the very last step. Cancel it, or lose your connection partway, and the old list is still there untouched. A failed attempt says so on the row, in red, and keeps the checkbox and the Delete beside it. Going *down* a tier still downloads: a smaller list is fetched fresh from the source rather than trimmed out of the one on your device, so it costs the transfer for that tier. Portuguese's two lists share the dialog and the download slot, so the same button is also how you move a downloaded Portuguese list between European and Brazilian without deleting it first. The tier you pick here is remembered as the app-wide default, exactly as the first download's is, so the next language's dialog opens on it too. ## Downloading, updating, and deleting Tap **Download** on the Word list row and choose a size to start it. Progress shows as an indeterminate bar rather than a percentage. A capped download stops before the file's full size, so there's no meaningful total to count up to. To cancel, tap the close icon while the download runs. The partial file is discarded and the row goes back to "Download." Once a dictionary is downloaded, the row has a checkbox, a size button (above) and a delete button. Deleting removes the file for that language. English and Bangla fall back to their built-in list, and every other language falls back to learned words plus any imported custom list. languages/dictionary-download-progress — A dictionary mid-download, with the indeterminate progress bar and cancel action. You don't need to restart the keyboard for a download to take effect. It goes live the next time you focus a text field after the download finishes. A download can fail for a few distinct reasons, each with its own message: no network connection, not enough free storage ("Delete some files, then try again"), an empty or unreadable word list, or a server that answered with an HTTP error code, which the row prints. A failed row shows **Retry** in place of Download. ## Emoji keyword packs Emoji search ships with English and Bangla keywords built in. A separate, much smaller catalog of 126 language keyword packs lets emoji search and suggestions work in other languages too. The pipeline is the same shape (stream, unpack, use locally), but the packs are small enough that there's no size tier, and unlike word dictionaries they can download automatically as soon as you enable a language. That toggle and the rest of the keyword-pack details live on [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/); this page covers word dictionaries only. ## Options Languages & layouts / (a language) / Dictionaries There's no global dictionary settings screen. Each language's own screen has one **Dictionaries** section, reached from **Languages** in the app. It has up to three items, and a row for the language that has nothing to offer is left out: - **Word list**: the built-in or downloaded list. - **Emoji keywords**: the keyword pack, downloaded or imported. - **Word pairs**: the bigram and trigram data that next-word predictions read. 455 languages have it, and the row doesn't show for the others. An item that is on the device has a checkbox and a delete button, and the word list has a size button between them. An item that is not has a **Download** button. The checkbox turns the item off and keeps the file: - Off on **Word list**, the keyboard forgets those words for that language and reads only lists you imported, plus what it learned from you. It's the same setting the Custom dictionaries screen shows the other way up as **Use only my word lists**. English and Bangla always have the checkbox, because their built-in list is on the device. - Off on **Emoji keywords**, emoji search and suggestions stop answering in that language. - Off on **Word pairs**, predictions stop reading the pack. While anything is still missing, the section heading has a **Download all (*size*)** button. It fetches every item that is not on the device yet, with the word list at the size you last picked, and it goes away once everything is downloaded. Below the items, **Custom dictionaries** opens the screen where you import your own lists. The **dictionary bar** on [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/#dictionary-bar) puts the word list and emoji keyword switches, and each imported list's own, on a row above the keys as chips, with a language filter at its left edge. A few related settings live elsewhere: - **Download data automatically**, at Languages & layouts / Language data / Download data automatically, is **on** by default, except on F-Droid builds, where it starts **off** so the app makes no connection nobody asked for. It's the master switch over everything the keyboard fetches by itself: emoji keyword packs and word-pair data. Word dictionaries aren't in that set, so it never downloads one. - **Emoji keywords auto-download**, at Emoji / Emoji keywords / Download automatically, is **on** by default. It's the emoji half of the switch above, and both have to be on for a pack to arrive by itself. It governs the emoji keyword packs, not word dictionaries. - **Custom dictionaries** (import your own word list), under Typing / Suggestions / Custom dictionaries. The same screen is also one tap from a language's own Dictionaries section, above. - **Personal dictionary** (words the keyboard has learned from your typing), under Typing / Suggestions / Personal dictionary. This is a different, unrelated store from anything on this page. ## Details & edge cases - **Downloaded and imported word lists are combined.** A downloaded wordlist and anything you've imported yourself live in separate on-disk stores, but the keyboard reads from both at once for every language, English and Bangla included. A word in either one gets completions and autocorrect protection. Downloading a list doesn't clear an import, and importing doesn't clear a download. - **Chinese, Cantonese and Japanese lists feed the candidates.** Those languages are typed by conversion, so a list imported for one of them joins its conversion table instead of a word list, and only lines that carry a reading count. See [Your own word lists](https://wmkeyboard.pages.dev/languages/cjk/#your-own-word-lists). - **Custom and personal dictionaries are different things.** A custom dictionary is a word list you import yourself. The personal dictionary is what the keyboard has learned from your own typing. Neither is the downloadable-wordlist catalog this page covers. See [Options](#options) above for where each one lives. - **The size shown in the wordlist table below is a download cap, not a storage promise.** It's the compressed size of the full remote file, used only as an upper bound while the progress bar is indeterminate. What actually lands on your device depends on the tier you pick and is reported as an exact figure once the download finishes. - **Romanized languages are their own catalog entries, not a toggle.** Fourteen languages (including Bangla, Hindi, Arabic, and Russian in Latin transliteration) have their own separate dictionary entry and their own selectable language (Banglish, Hinglish, Arabizi, and so on), distinct from the native-script version. Eleven of them (Banglish, Hinglish, and the Gujarati, Kannada, Malayalam, Marathi, Punjabi, Sinhala, Tamil, Telugu and Urdu lists) now also count words from the Dakshina dataset, romanizations native speakers wrote plus running text, so common words such as `hai` and `nahi` are listed and rank first. A romanized list you already downloaded stays as it is until you download it again. - **Downloads come from one place.** Every wordlist and every emoji keyword pack downloads from the same public data repository over a plain HTTPS connection, and nothing else is contacted. Once a file is written to disk, using it needs no further network access. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full picture of what this keyboard sends and when. - **Romanian lists are respelt on the way in.** Romanian writes ș and ț with a comma below, but text counted from the web and from subtitles is full of the Turkish cedilla letters ş and ţ that eight-bit code pages used in their place, and the spelling a word list carries is the one autocorrect commits. The keyboard replaces the cedilla with the comma while it reads any Romanian list, downloaded or imported, and merges the two spellings of a word into one entry, so `trăiești` is what you get. A Romanian list downloaded before this is repaired in place the next time the keyboard starts, with nothing re-downloaded; an **Everything**-size list is too big to rebuild on the device and keeps its old spelling until you download one again. No other language is touched: Turkish, Azerbaijani, Gagauz, Zazaki and Kurmanji all write a real cedilla. - **Downloadable wordlists keep their own licenses.** The bundled seed data (bigrams, the loanword map, the offensive-word filter) is hand-curated for this project, but each downloadable wordlist carries whatever license its own upstream source uses. - **One download here can ask before spending mobile data.** While [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is on (off unless you pick a trigger), its **Downloads** row, set to **Ask each time** by default, puts a size-naming confirmation in front of the **Download all** button. The individual Download buttons on the rows don't consult it, and neither do the automatic packs. They start on whatever connection is up. [Downloads & mobile data](https://wmkeyboard.pages.dev/languages/downloads/) has the full picture, including which downloads elsewhere in the app ask first. ## Every wordlist All {wordlists.length} downloadable wordlists, AOSP and Frequency. Word counts are the full list; the Small/Medium/Large tiers keep the most frequent 50k/150k/300k words, and Everything keeps them all. The size in the **Download** column links the file itself. On a phone that cannot download (the no-internet build, or a blocked network), fetch it in a browser and bring it in with **Import from a file** on the language's **Dictionaries** screen. That screen's **Get the files** button lists the same links, with copy and open buttons, plus the language pack that holds them all. [ w.id, w.name, w.source, w.words.toLocaleString('en-US'), { text: `${(w.gzBytes / 1024 / 1024).toFixed(w.gzBytes < 1024 * 1024 ? 2 : 1)} MB`, href: w.url, }, ])} extras={wordlists.map((w) => w.english)} placeholder="Filter wordlists…" caption="Generated from the app's dictionary catalog; sizes are the gzipped downloads." /> See also [Languages overview](https://wmkeyboard.pages.dev/languages/overview/) for enabling a language in the first place, and [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/) for the keyword-pack side of this same pipeline. --- # Downloads & mobile data URL: https://wmkeyboard.pages.dev/languages/downloads/ Section: Languages Summary: What a language downloads, when it downloads, and which downloads ask before spending your mobile data. Adding a language can pull files down. Most of them are tiny and one of them is 42 MB. This page lists exactly what gets fetched, when, and what it costs, so nothing on your bill is a surprise. languages/download-language-data-prompt — Download language data? The prompt shown as you add a language, with the rough size on it. ## What arrives on its own Two kinds of file, both small, both behind switches that ship on: - **Emoji keyword packs**, so emoji search answers in the language you're typing rather than only in English. 131 languages have one, Banglish included. They're gzipped JSON, mostly 70 to 100 KB, and the largest in the whole set is Banglish at 130 KB. - **Word-pair data**, the bigrams and trigrams predictions rank with, so the keyboard knows which word tends to follow which. 455 languages have a pack: English (about 1.1 MB), Bangla (about 1.8 MB) and Banglish (about 315 KB), and 49 more built from the [Leipzig Corpora Collection](https://wortschatz.uni-leipzig.de/en)'s web and news text: Spanish, French, German, Portuguese, Russian, Italian, Turkish, Indonesian, Vietnamese, Polish, Dutch, Ukrainian, Korean, Romanian, Greek, Czech, Hungarian, Swedish, Hebrew, Malay, Swahili, Tagalog, Nepali, Sinhala, Azerbaijani, Uzbek, Kazakh, Danish, Finnish, Norwegian Bokmål, Slovak, Bulgarian, Serbian, Croatian, Bosnian, Catalan, and every language with a [phonetic layout](https://wmkeyboard.pages.dev/languages/phonetic/): Hindi, Urdu, Marathi, Gujarati, Punjabi, Odia, Assamese, Tamil, Telugu, Kannada, Malayalam, Persian and Arabic. Each is 1 to 2 MB, a little less for Odia and Assamese, which the collection has least text for. Another 341 packs are built from open web text: [HPLT](https://hplt-project.org/), [MADLAD-400](https://huggingface.co/datasets/allenai/MADLAD-400), AI4Bharat's [Sangraha](https://huggingface.co/datasets/ai4bharat/sangraha) for Indian languages, Wikipedia, and [Tatoeba](https://tatoeba.org/)'s sentences. That covers Sanskrit, Maithili, Bhojpuri, Sindhi, Konkani, Santali, Mizo, Chhattisgarhi, Thai, Burmese, Khmer, Lao, Amharic, Tigrinya, Pashto, Kurdish, Uyghur, Tibetan, Hausa, Yoruba, Igbo, Zulu, Xhosa, Somali, Oromo, Quechua, Guarani and hundreds more. The best-covered are 1 to 2 MB, and a language with little text online gets a few hundred kilobytes or less. A few other languages still have only a small pack from Tatoeba, and some codes share a pack with another code for the same language, such as Dari and Iranian Persian. If you already have one of the small packs a new one replaced, the keyboard swaps it for the new one by itself, unless you turned off **Download data automatically** or deleted the pack. The same text gave 260 languages a longer, better-ranked word list. A word list you've already downloaded isn't replaced by itself: delete and download it again to get the new one. Both are gated on **Download data automatically** (Languages & layouts / Language data / Download data automatically, default **on**, except on F-Droid builds, where it starts **off**). Emoji packs have a narrower switch of their own on top of that, **Download automatically** under Emoji / Emoji keywords (also default **on**), and both have to be on for a pack to arrive by itself. The check runs from the keyboard rather than the settings app, and it reads your enabled-language list rather than running once at install. Add Tamil today and its pack is fetched the next time you type on the keyboard. Anything already on disk is skipped. A download that fails is left alone until the keyboard's process next starts, so a dead connection doesn't turn into a retry loop. Word lists join in under the same switch, but only on Wi-Fi or another unmetered connection, because they're the big ones. Without one a language has no glide typing and no suggestions, so an enabled language whose list never arrived gets it here at the size tier the add-a-language prompt uses. A language whose list ships with the app (English, Bangla) is skipped. So is one whose list you deleted: Delete sticks until you download that list again by hand. Downloads you ask for queue up rather than cancel each other. Adding two languages in the setup wizard fetches both word lists, one after the other. ## What waits for you to ask | Download | Where | Rough size | |---|---|---| | Word list, per size tier | Languages → *(a language)* → Dictionaries | Named per size in the dialog that Download opens. The full remote files run from a few KB to about 41 MB, and a size tier stops the transfer well short of that. [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) lists every one | | **Download language data?** prompt (word list, emoji keywords, word-pair data and, for Chinese, Cantonese and Japanese, the conversion tables their layouts need, in one go) | Shown as you add a language | Named on the dialog | | **Download the *(pack)*?** prompt | Shown as you switch on a Chinese, Cantonese or Japanese layout whose table isn't on the device | Named on the dialog | | **Download all** | Languages → *(a language)* → Dictionaries heading, while anything is missing | Named on the button | | Word list at another size | Languages → *(a language)* → Dictionaries → Word list → the arrow button on a downloaded list | Named per size in the same dialog Download opens | | Pinyin table | Languages → Chinese → Chinese options | About 2.6 MB | | Stroke table | Languages → Chinese → Chinese options | About 450 KB | | Cangjie table | Languages → Chinese → Chinese options | About 350 KB | | Traditional characters phrase table | Languages → Chinese → Chinese options, or Cantonese → Cantonese options (one file for both) | About 1 MB | | Jyutping table | Languages → Cantonese → Cantonese options | About 3.4 MB | | Kana to kanji table | Languages → Japanese → Japanese options | **About 42 MB** | | Contracted braille tables | Languages → Braille (6-key chord) → Braille (6-key chord) options | About 4 MB | | Niqqud model | Languages → Hebrew → Hebrew options | **About 308 MB** | | Tashkeel model | Languages → Arabic → Arabic options | About 21 MB | | Furigana readings | Languages → Japanese → Japanese options | About 24 MB | | Words with ё | Languages → Russian → Russian options | About 865 KB | | Turkish spelling rules | Languages → Turkish → Turkish options | About 15 MB, 38 MB unpacked | The keyboard reads a Pinyin, Jyutping or kana table once and keeps a faster copy of it next to the download, so these take a little over twice their size in storage: about 90 MB for the kana table. Deleting the table deletes the copy. Pressing the button is the consent, and nothing here starts on its own. That includes changing a downloaded list's size: the arrow button on the **Word list** row opens the same dialog, names the same per-size transfer, and downloads nothing until you pick one. Some of these name a size first and some don't. The add-language dialog and the **Download all** button each name the whole set they're about to fetch, the word-list dialog names each size, and the Japanese pack's row says "about 42 MB" because it's the one worth warning about. The Pinyin, Stroke, Cangjie and Jyutping rows describe what the table holds rather than what it weighs, so the column above is the place to check those. The only thing that can come between is the mobile-data question below. The add-language prompt is the one with a way out: **Download** fetches the set, **Not now** adds the language with nothing, and **Cancel** backs out of the language too. Word-list tiers and how downloaded and imported lists combine are covered on [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/). The CJK tables and what each one is for are on [Chinese, Japanese & Korean](https://wmkeyboard.pages.dev/languages/cjk/). Those five are the only ones the keyboard will point you at unprompted: bring up a Chinese, Cantonese or Japanese layout whose table isn't on the device and a chip on the strip names the pack and opens that row, before you've typed anything, because without it the layout converts nothing and nothing else on screen would say so. It still downloads only when you press the button. Several tables asked for at once, as adding Chinese asks for three, download one after another rather than all at the same time. ### Turkish spelling A Turkish word can take suffix after suffix, so no word list holds them all and a correct `evlerimizden` can look like a typo. **Turkish spelling rules** fixes that: with them the keyboard checks Turkish with Hunspell and [hunspell-tr](https://github.com/tdd-ai/hunspell-tr), leaves a correctly suffixed word alone and suggests `geliyorum` for `gelyorum`. Download the rules under **Languages → Turkish → Turkish options**, plus the spelling library if it isn't on the phone yet. ### Japanese furigana **Add furigana** on the selection bar writes a reading in brackets after each run of kanji, since a text field can't show ruby: 学校へ行きます becomes 学校(がっこう)へ行(い)きます. Download **Furigana readings** under **Languages → Japanese → Japanese options** first. It picks each word's commonest reading from Mozc's dictionary without parsing the grammar, so a word with several readings can come out wrong, and it's worth a read before you send it to a learner. ### Hebrew niqqud **Add niqqud** on the selection bar puts the points on selected Hebrew text, replacing any it already has. It needs two downloads, both under **Languages → Hebrew → Hebrew options**: the Phonikud model, about 308 MB, and ONNX Runtime if a voice engine or neural readings haven't brought it already. The model comes straight from Hugging Face, pinned to one checked version. It runs on the phone, so your text stays there. It takes a few hundred MB of memory while it runs and lets go a minute after the last use, and a long selection can take several seconds. Phonikud was trained for speech, so a few points follow how a word sounds today rather than formal grammar: expect to check anything that has to be correct by the book. ### Arabic tashkeel **Add tashkeel** on the selection bar puts the marks on selected Arabic text, replacing any it already has. It needs two downloads, both under **Languages → Arabic → Arabic options**: the CATT model, about 21 MB, and ONNX Runtime if something else hasn't brought it already. It runs on the phone. It reads Arabic only, so it stays away from a selection with پ چ ژ or گ in it, and it can get a name or a foreign word wrong. ## Without a connection Every download on this page can come in from a file instead. Each language's Dictionaries group ends with a **Language pack** row: **Get the files** links that language's pack, one zip with its word list, word pairs, emoji names and (for Chinese, Japanese and Cantonese) conversion tables, to fetch in a browser on any device, and **Import** installs it. It's the way to set up a phone with no connection, or the [build with no internet permission](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-internet-permission). Full detail is on [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/#language-packs). ## The one that checks your connection The **Downloads** row in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) (Advanced / Data saver / Downloads, default **Ask each time**) puts a size-naming confirmation in front of **Download all** while data saver is on. Data saver never turns on by itself unless you pick a trigger for it, such as **On mobile data or a metered network**. That's the one tap that fetches a word list, emoji keywords and word-pair data together, so it's the one most likely to surprise you. Set the row to **Allow** and the button goes straight out on whatever connection is up; set it to **Turn off** and it says so instead of downloading. The same row covers the Whisper and on-device AI model downloads, which used to have a switch of their own. Nothing else on this page consults it. The automatic emoji and word-pair packs start on whatever is up, and so does every individual Download button: a word list, an emoji pack, and the CJK tables, including the 42 MB Japanese one. For the automatic pair that's a few hundred kilobytes a language, which is why it's allowed to be quiet. For the manual ones the size is printed before you press. If your plan is tight, three habits keep it that way: - Turn **Download data automatically** off. Nothing is fetched until you ask for it on a language's own screen. - Answer **Not now** to the add-language prompt. Come back to **Download all** on the language's screen when you're on Wi-Fi. - Leave the CJK tables, the Japanese one above all, for a Wi-Fi moment. Nothing else about a language comes close to it except a word list taken at the Everything tier. Nothing here downloads in the background when the keyboard isn't running, and nothing resumes on a schedule. No job queue sits waiting for a better connection. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full accounting of what this keyboard contacts and when. ## What asks elsewhere in the app Three downloads outside the language screens treat a metered connection differently, and the line is drawn at size rather than by oversight: | Download | When it asks | What you get | |---|---|---| | Whisper voice models google | Metered connection **and** the model is 150 MB or larger | A dialog naming the model, its size, and the connection, such as **Download on mobile data?**, with a confirm button worded for that connection and a plain **Cancel** | | On-device AI models google | Metered connection **and** the model is 500 MB or larger | The same dialog | | Rotating photo backgrounds | Never asks. It's a setting instead: **Use mobile data**, off by default, in a theme's Online photos group | A rotation waits for Wi-Fi rather than prompting | Models under those thresholds go through without a word, as long as Data saver is set to allow downloads. The smallest Whisper model is about 42 MB and the smallest on-device AI model about 143 MB. One setting overrides the size line for both: the **Downloads** row in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), which is **Ask each time** by default, so while data saver is on every model raises the same dialog whatever its size. Set it to **Turn off** and no model downloads while data saver is on. Full detail on those two is on [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/) and [AI writing tools](https://wmkeyboard.pages.dev/tools/ai/). ## Options Languages & layouts / Language data | Setting | Where | Default | |---|---|---| | Download data automatically | Languages → Language data | On (off on F-Droid builds) | | Downloads (on a metered connection) | Data saver | Ask each time | | Download automatically (emoji keywords) | Emoji → Emoji keywords | On | | Download all | Languages → *(a language)* → Dictionaries | A button (no stored value), shown while anything is missing | | Dictionary size tier | Languages → *(a language)* → Dictionaries → Word list → Download, or the arrow button on a downloaded list | Large (up to 300,000 words), remembered once you change it | | Use mobile data (photo backgrounds) | Appearance → Keyboard themes → *(edit a theme)* → Board → Rotating background → Online photos | Off | ## Details & edge cases - **"Automatically" means the keyboard, not the app.** The automatic pass lives in the keyboard service and runs when your settings change while it's up. Sitting in the settings app after adding a language doesn't fetch anything. Opening a text field does. - **Nothing downloads before the first unlock.** The automatic pass is skipped entirely until the phone has been unlocked once after a restart, because the files it writes live in storage that isn't readable before then. Type on a still-locked device and the packs arrive later. - **A failed automatic download is not retried in that session.** The language is remembered as given up on for the life of the keyboard's process, so a bad connection costs one attempt rather than a stream of them. Restarting the keyboard (or the phone) clears the mark. - **Deleting a language's emoji keywords tells the automatic pass to leave it alone.** That's how deleting the pack sticks instead of it quietly reappearing on your next connection. - **Everything comes from one place.** Word lists, emoji keywords, word-pair data and the CJK tables all download from the same public data repository over plain HTTPS. No analytics, no per-download reporting, nothing else contacted. - **Sizes shown are the compressed transfer, not what lands on disk.** For word lists they're also an upper bound. The transfer stops early at words that occur only once in the source, so a finished download is often smaller than the number promised. --- # Fancy text URL: https://wmkeyboard.pages.dev/languages/fancy-text/ Section: Languages Summary: 𝔉𝔞𝔫𝔠𝔶 Unicode styles on one keyboard: pick a style from the strip, and what you see on the keys is what you type. Fancy Text isn't a language. It's 32 Unicode "font" styles on a single keyboard, plus a Normal style that types plain letters and any [fonts you make yourself](#your-own-fonts). Add it once, pick a style from the strip above the keys, and every letter key immediately shows the styled glyph. What you type is already 𝓯𝓪𝓷𝓬𝔂: there's no separate conversion step afterward, and the suggestion strip and autocorrect work on the plain letters underneath. languages/fancy-text-bold-field — The Bold style active in a text field: typing hello commits 𝐡𝐞𝐥𝐥𝐨 directly. ## Turning it on Languages & layouts / Add language Fancy Text lives in the same language catalogue as everything else, and [Languages overview](https://wmkeyboard.pages.dev/languages/overview/) covers how that catalogue works in general. Search for "fancy" (or scroll to "𝓕𝓪𝓷𝓬𝔂 · Fancy Text") and tap the row. That's the whole setup: one entry in your language list, one layout, one stop on the 🌐 cycle and the OS keyboard switcher. ### The Fancy text tool If you only want fancy letters now and then, the **Fancy text** toolbar tool does the same thing in one press, from the keyboard, without a trip to Settings. Pin it from Tools (it's in the Quick actions group) or find it in the toolbox. Press it and the fancy keyboard appears, adding Fancy Text to your language list if it wasn't there already. Press it again and you're back on the layout you came from, with Fancy Text taken out of the 🌐 cycle again so it isn't in the way of the languages you actually read. The tool's icon stays lit for as long as the fancy keyboard is the one typing, however you got there. Its own screen, at Tools / Fancy text, has three settings: - **Style to start with.** Pin one style and the tool always turns on in it. Left on "Last used", it starts from whatever style you picked last. Either way the strip over the keys still switches styles for the rest of the session, and a pinned style is never overwritten by it. - **Keep the language after you turn it off.** On, Fancy Text stays in your language list once the tool has added it, so a spacebar swipe reaches it too. Off (the default) takes it back out again. - **Turn off when the keyboard closes.** On by default. For one nickname or one message: leave the field and the keyboard comes back typing plainly. It only undoes what the tool itself turned on, so a Fancy Text language you added in Settings stays put. On a hardware keyboard the tool answers to the leader key followed by F; see [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/). The same switch also lives in Settings as the **Fancy text** toggle on Rows & bars, next to the row order, where the style strip can be dragged above or below the other rows. ## Picking a style While the fancy keyboard is active, a strip appears above the keys with every style written in itself: Normal, 𝐁𝐨𝐥𝐝, 𝘐𝘵𝘢𝘭𝘪𝘤, 𝔉𝔯𝔞𝔨𝔱𝔲𝔯, 🅒🅘🅡🅒🅛🅔🅓 and so on. Tap a chip and the keys restyle instantly. Normal, first in the strip, puts the plain letters back without leaving the fancy keyboard. The strip sits nearest the keys by default; Rows & bars / Row order moves it above the other rows. The pick is remembered until you change it. The dropdown on the strip's left edge names the current style. Open it and every style appears written in itself, exactly like the chips above; a screen reader still gets the plain name for each one. The same choice also lives in Settings, on the Fancy Text language screen, as a **Style** row. The full set, with the name each one shows: | Style | Style | Style | |---|---|---| | Bold | Sans-serif | Circled | | Italic | Sans Bold | Circled (Filled) | | Bold Italic | Sans Italic | Squared | | Script | Sans Bold Italic | Squared (Filled) | | Bold Script | Monospace | Small Caps | | Fraktur | Fullwidth | Superscript | | Bold Fraktur | Double-struck | Strikethrough | | Underline | Regional Indicator | Parenthesized | | Double Underline | Overline | Slashthrough | | Wavy Underline | Dotted | Upside Down | | Currency | Bypass | | ## Your own fonts Languages & layouts / Fancy Text / Your fonts When the style you want isn't in the list, make it. Fancy-text websites build many of their "fonts" out of look-alike symbols (ᗩᗷᑕ, ค๒ς, Λߢ), and any of them can become a style here. Tap **Add a font**, give it a name, and paste the font's letters: - **Small letters, a to z.** Required. Paste the 26 letters in order. If you paste a to z, A to Z and 0 to 9 in one go (62 characters, or 52 without digits), the keyboard shares them out over the three fields for you. - **Capitals, A to Z.** Optional. Left empty, shift types the small letters, the way Small Caps does. - **Digits, 0 to 9.** Optional. Left empty, digits stay plain. Each field counts what it read, so you can see straight away if a letter went missing. An alphabet that stops short is fine: the letters it doesn't reach type plain. Characters are counted the way you see them, so a letter with a stroke or an accent stacked on it counts once. To give one letter several characters, such as (a) or 【a】, separate the letters with spaces and each item counts as one letter. **Write backwards** is for upside-down fonts. It reverses the line so the text reads correctly with the phone turned round, and like the built-in Upside Down style it types letter by letter, without suggestions. A preview at the top of the form writes a pangram in the font as you go. Once saved, the font appears at the end of the strip over the keys, in the **Style** row, and in the selection actions' Fancy conversions, and the keyboard switches to it. Tap a font in the list to change it, or swipe it away to delete it. If the font you delete is the one in use, the keyboard goes back to Bold. You can keep up to 50 fonts. They're part of your settings, so a settings backup carries them to a new phone. ## The keyboard itself The fancy layout is a plain QWERTY grid: three letter rows plus a bottom row for ?123, comma, 🌐, space, period, and Enter. Comma, period, space and the other symbols always commit as plain ASCII, because only letters and digits get the styled substitution. There are no long-press popups on the letter keys. Digits come from the shared ?123 panel, or the number row if you have it on. ## Numbers Digits are styled too, wherever Unicode has digits for the style: Bold types 𝟏𝟐𝟑, Double-struck 𝟙𝟚𝟛, Sans-serif 𝟣𝟤𝟥, Sans Bold 𝟭𝟮𝟯, Monospace 𝟷𝟸𝟹, Fullwidth 123, Circled ①②③, Circled (Filled) ❶❷❸, Superscript ¹²³ and Parenthesized ⑴⑵⑶. The digit keys show the styled form, like the letters. Unicode has no italic, script or fraktur digits. The bold variants of those (Bold Italic, Bold Script, Bold Fraktur) borrow the bold digits, and Sans Italic and Sans Bold Italic borrow the sans ones, so a word and the number next to it keep the same weight. Italic, Script, Fraktur and every other style type plain digits. Parenthesized has no zero, so 0 stays plain there. Hold any digit key, on the symbols panel or the number row, for every styled form of that digit in one popup, whatever style is active: bold, double-struck, sans-serif, sans bold, monospace, fullwidth, circled, negative circled (❶), sans-serif circled (➀), parenthesized (⑴), with a full stop (⒈), superscript (¹), subscript (₁) and segmented LCD digits (🯱). The last few belong to no style, so the popup is the only way to type them. The key's usual alternates stay first, so a plain hold still does what it always did. Segmented digits are from Unicode 13, and older fonts show them as empty boxes. The base grid. The style strip restyles these labels live; the stored layout stays plain. ## How it works The layout stores plain letters, and the selected style substitutes each one at the last moment, both on the key labels and in what goes into the field. The keyboard itself keeps working with the plain word: that's what it suggests from, corrects and learns, and the style is applied to the word being typed and to everything committed into the field. Shift works exactly like any other layout: it uppercases the plain letter, and the style supplies its capital form where one exists. Two different Unicode techniques produce the styling, depending on the style: - **Precomposed characters**, mostly from the Mathematical Alphanumeric Symbols block, for Bold, Italic, Script, Fraktur, Double-struck, Sans, and Monospace (and their combinations). These sit outside the Basic Multilingual Plane, so they need a font with supplementary-plane coverage to render. See the gaps below. - **Combining marks stacked on plain ASCII letters**, for Strikethrough (a stroke mark after each letter) and Underline (a low-line mark after each letter). These stay inside the BMP, so they render reliably almost everywhere, at the cost of being two codepoints per letter instead of one. Circled, Squared, Fullwidth, Small Caps, and Superscript draw from a few other existing Unicode blocks. Circled (non-filled) is Enclosed Alphanumerics, Fullwidth is the Fullwidth Forms, and Small Caps and Superscript come from the phonetic and modifier-letter ranges, all of which are inside the BMP. Squared, Squared (Filled), and Circled (Filled) are the exception in that group: they sit in the Enclosed Alphanumeric Supplement, which is outside the BMP like the mathematical styles. No font ships with the app for any of this: rendering depends entirely on whatever font the app or system you're typing into already has installed, which is where the gaps below come from. Not every style has full alphabet coverage. Italic, Script, non-bold Fraktur, and Double-struck hit codepoints the Mathematical Alphanumeric block deliberately leaves empty, so those specific letters fall back to older, BMP-only symbols instead. Superscript goes further: Unicode has no capital superscript letters at all, and no superscript "q" in any case, so shift changes nothing there and "q" types a plain lowercase q. ## Details & edge cases **Case coverage varies by style.** Superscript and Small Caps have no capitals, so shift leaves them as they are. Unicode doesn't define capital forms for them. Squared, Squared (Filled), and Circled (Filled) are single-case too, since Unicode only assigned capital-shaped symbols for those blocks. Currency is single-case as well: it borrows currency signs and other symbols that look like the letters (₳ ฿ ₵ ₫), and leaves out letters a language writes with, such as ł and Ø, so a Polish or Danish word read back from the field is never mistaken for styled text. Circled (non-filled), Fullwidth, Double-struck, and every Fraktur/Script variant do have real upper/lower distinction. **A handful of letters fall back to an older block.** Double-struck is missing astral capitals for C, H, N, P, Q, R, and Z, and non-bold Fraktur for C, H, I, R, and Z, so both fall back to the Letterlike Symbols block for those. Non-bold Script is missing letters in both cases: e, g, o in lowercase and B, E, F, H, I, L, M, R in capitals. Italic has exactly one gap, the lowercase h. Bold Fraktur, Bold Italic, and Bold Script have no such gaps, since their Unicode blocks are complete. Small Caps has gaps of its own kind: Unicode never encoded a small-capital X, so "x" types a plain lowercase x, and "q" types ǫ, because the small capital Q Unicode added later is missing from most phone fonts and would show as a blank. Fancy Text is exempt from the keyboard's normal ASCII-only safeguard. Fields that request ASCII-only input (a common flag on username boxes, coupon codes, and some search fields) normally force WM Keyboard back onto a plain layout automatically, because the app storing that field can't hold non-Latin text. That check compares script IDs, and Fancy Text is technically registered as the Latin script (its keys are Latin letters, just styled), so it passes the check and stays active. If the fancy keyboard is your current layout when you tap into one of those fields, it keeps typing styled glyphs, which the field may silently reject or mangle. Switch to a plain layout first if you're not sure a field will accept anything but ASCII. **Suggestions, autocorrect and swipe typing use English.** Fancy Text has no word list of its own (the language screen's Dictionaries section has no word list row, only Custom dictionaries), so it borrows the English one. You type plain letters, and the strip suggests and corrects them as it would on an English layout. The word being typed already shows in the style, underlined like any word in progress. A space, a punctuation mark, a pick from the strip, an autocorrection and a swipe all go into the field styled, and a backspace straight after an autocorrection still puts back what you typed. The keyboard learns the plain spelling of what you type, never a styled word. Tap a different style while a word is being typed and that word switches style with it. Moving the caret back into a styled word doesn't pick it up for editing the way it does on other layouts: the field holds styled glyphs, and the keyboard only edits words it can read as plain letters. **Upside Down types letter by letter.** Its glyphs only read correctly once the whole line is reversed, so it keeps substituting each key as you press it, with no suggestions or autocorrect. **Everything the keyboard puts in the field is styled.** Besides typing, that covers text pasted from the clipboard panel, dictation and snippet expansions while the fancy keyboard is active. Emoji and punctuation have no styled forms and go in unchanged. The selection actions' own conversions are the exception: a Fancy or colour conversion of selected text goes in exactly as the action wrote it. **The keyboard's own search boxes get plain letters.** Typing into the emoji search, GIF search, or any other panel search while the fancy keyboard is active types unstyled letters, so those searches actually match. Only text committed to the app's field is styled. **Search and screen readers** in the receiving app are governed by that app, not by WM Keyboard. Styled Unicode letters aren't the same codepoints as their plain equivalents, so a search box or screen reader that only matches literal ASCII text won't recognize 𝕥𝕙𝕚𝕤 as "this." That's general behavior of the receiving app, not something this keyboard controls either way. **The base grid is an ordinary layout**, so you can open it in the visual layout editor to remap a key or add long-press entries. See [Custom layouts & the layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/). The styles themselves aren't separate layouts anymore, so they can't be edited individually: your remapped keys apply to every style. --- # Hindi URL: https://wmkeyboard.pages.dev/languages/hindi/ Section: Languages Summary: Type Hindi the way you text it: Latin letters in, Devanagari out. Plus InScript and Remington for typing the script directly. Hindi ships five layouts. **हिन्दी** is the standard InScript keymap and it's what you get when you add Hindi. **Hindi phonetic** is a QWERTY grid that turns romanized Hindi into Devanagari as you type, so `kaise ho` commits कैसे हो. **हिन्दी वर्णमाला** puts the letters themselves on the keys, four pages of them, with the vowel signs and half forms on press and hold. **Remington (GAIL)** is the typewriter layout. **Hindi Flick A (Gboard)** is a phone-pad board where a key types another letter when you flick it upward. This page is mostly about the phonetic one, because it's the one with moving parts. ## Turning it on Languages & layouts / हिन्दी · Hindi / Layouts / Hindi phonetic Add Hindi, open its screen, and tap the **Hindi phonetic** card under **Layouts** to turn it on. Tap the InScript card on the same screen to turn it off if you don't want both. Then do the one thing that makes the biggest difference: download the Hindi word list from the **Dictionary** row on that screen. Hindi ships no word list inside the app, and the next two sections explain why this layout cares about that more than most. Until the list is there, the suggestion strip shows a **Get the Hindi word list for better words** chip the first time you switch to the layout. Tap it and it opens the same screen. ## How it reads what you type Every key is where it is on the English layout. What you type shows up as Devanagari in the composing text straight away, with no separate conversion step, and the space commits it. | You type | You get | Why | |---|---|---| | `tum`, `aap`, `naam` | तुम, आप, नाम | "a" is the inherent vowel and writes nothing after a consonant; "aa" is आ | | `mera`, `bhi`, `tu` | मेरा, भी, तू | a vowel that ends a word is long, because Hindi never says a final short "a" | | `jao`, `khao` | जाओ, खाओ | an "a" running into "o" or "u" is long | | `hindi`, `lamba`, `rang` | हिंदी, लंबा, रंग | a nasal before the stop it belongs to is written as ं | | `namaste`, `dost`, `kya` | नमस्ते, दोस्त, क्या | these clusters join | | `karna`, `sakta`, `uska` | करना, सकता, उसका | these don't, because the ending is its own piece of grammar | | `pakka`, `baccha` | पक्का, बच्चा | a doubled consonant joins | | `roTi`, `paRhna` | रोटी, पढ़ना | capitals spell the retroflex letters exactly | | `Bahut` | बहुत | a capital that spells nothing reads as its lowercase self | ### Hindi phonetic key map Every key the rules read. The same tables are in the app, under **Key map** on Hindi's own screen (see [Options](#options)). **Vowels.** The first form is the letter on its own, the second the sign it becomes after a consonant. Shift gives the long vowels too, if you'd rather do that than double the letter. | Key | Letter | After a consonant | |---|---|---| | `a` | अ | nothing: the inherent vowel | | `aa`, `A` | आ | ा | | `i` | इ | ि | | `ee`, `ii`, `I` | ई | ी | | `u` | उ | ु | | `oo`, `uu`, `U` | ऊ | ू | | `e` | ए | े | | `ai` | ऐ | ै | | `o` | ओ | ो | | `au`, `ou` | औ | ौ | | `RRi` | ऋ | ृ | **Consonants.** | | | | | | |---|---|---|---|---| | `k` क | `kh` ख | `g` ग | `gh` घ | | | `ch`, `c` च | `chh` छ | `j` ज | `jh` झ | | | `T` ट | `Th` ठ | `D` ड | `Dh` ढ | `N` ण | | `t` त | `th` थ | `d` द | `dh` ध | `n` न | | `p` प | `ph`, `f` फ | `b` ब | `bh` भ | `m` म | | `y` य | `r` र | `l` ल | `v`, `w` व | | | `sh` श | `Sh` ष | `s` स | `h` ह | | | `z` ज़ | `q` क | `R` ड़ | `Rh` ढ़ | | | `ksh` क्ष | `gy`, `jn` ज्ञ | `x` क्स | | | **Signs.** `M` is ं (anusvara) and `H` is ः (visarga). You rarely need `M`: a nasal before the stop it belongs to is written as ं on its own, as `hindi` shows. A capital that isn't in these tables reads as its lowercase letter. ### Why Hindi is harder than it looks Bangla's Avro is a spelling scheme people learn: every cluster you type is a conjunct, and you press a key for the inherent vowel when you want it. Nobody learns a scheme for Hinglish. People write Hindi in Latin letters the way it sounds, and spoken Hindi drops its inherent vowel wherever it can. So the `rn` in `karna` is two syllables (करना) while the `st` in `namaste` is one conjunct (नमस्ते), and nothing in the spelling says which. The same goes for `t`, which is त in `tum` and ट in `roti`, and for a lone `a`, which is short in `nazar` and long in `kitab`. The layout handles that in three layers, in this order. 1. **A list of known spellings**, about 900 of them. Everyday words whose Latin spelling doesn't carry enough to get the script right (`theek` is ठीक, `pani` is पानी, `nahi` is नहीं), chat shorthand (`h` is है, `nhi` is नहीं, `kr` is कर, `bhut` is बहुत), and English loanwords (`school` is स्कूल, `doctor` is डॉक्टर). A spelling on the list is settled there, and it shows in the composing text the moment you finish typing it. 2. **The Hindi word list, if you've downloaded it.** What you typed and every word on the list are both reduced to a loose key. The key forgets aspiration, forgets whether a consonant is dental or retroflex, forgets conjuncts, forgets every "a" inside the word, and treats ड़ as either "d" or "r". Words with the same key are candidates and the most frequent one leads. Details you did type count against a candidate that contradicts them: `kaam` asks for a long vowel, so it gets काम even though कम is the commoner word, and `thak` gets थक rather than तक. Details you left out cost very little, because that's how everyone types. Nasal marks nobody writes are forgiven too, so `nahi`, `nahin` and `nhi` all find नहीं. 3. **The rules alone.** They make the likelier guess: clusters stay apart unless they open or close the word, double a consonant, come before "y", or are one of a short table of pairs that usually join (`tr`, `dr`, `sw`, an `s` before t/p/k/m, `r` before the last consonant of the word). A grammatical ending (`na`, `ne`, `ni`, `ta`, `te`, `ti`, `ka`, `ki`, `ke`, `ko`, `se`, `me`, `kar`, `wala`) never joins the stem before it. With the word list installed, layer 3 rarely decides anything. Without it, layer 3 decides every word that isn't on the list in layer 1, and it will sometimes guess wrong. That's why the strip also carries the other readings of what you typed: the same spelling with its last "a" long (`pyar` offers प्यार, `sarkar` offers सरकार), with every cluster joined, with none joined, and with the final vowel left short. Tap the one you meant. A word from the list only takes the commit from the rules' own reading when it's at least twice as frequent, the same near-tie protection Bangla has, so the keyboard doesn't swap the word you were looking at for one of about equal currency. None of this is part of autocorrect and the autocorrect toggle doesn't control it. The known-spellings list has its own switch under [Options](#options). ## Glide typing Swiping over this layout draws a romanization, not a Hindi word, so the stroke is decoded against romanized spellings and the result is turned into Devanagari. The known-spellings list is always there for that. The **Hinglish** word list (about a million romanized spellings, under Hinglish in the language catalogue) widens it a lot if you download it, and each spelling a swipe decodes goes through the same three layers above. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/). ## Converting text you already have With the layout on, selecting text puts two more chips on the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/). **Hindi** rewrites selected Hinglish in Devanagari, word by word, through the same three layers. **Hinglish** goes the other way and writes Devanagari back in Latin letters the way it's said, so करना comes out `karna` and नमस्ते comes out `namaste`. The [typing test](https://wmkeyboard.pages.dev/tools/utilities/) has a Hindi word list and quotations of its own, so it works before you've downloaded anything. ## Editing Backspace inside a word you're still typing removes one Latin letter per press, and the Devanagari redraws. Over text that's already committed it removes one code point, or a whole conjunct if you turn on Languages & layouts / Hindi / Clusters / Conjunct-aware backspace. That toggle is off by default and applies to every Hindi layout. ## हिन्दी वर्णमाला: the letters on the keys Languages & layouts / हिन्दी · Hindi / Layouts / हिन्दी वर्णमाला No transliteration, no keymap to learn: the Devanagari is printed on the keys, in alphabet order, and you tap what you want. Devanagari has far more letters, signs and conjuncts than fit on one grid, so they're spread over four pages of fifty keys. The key in the bottom-left corner of the fifth row says which page you're on (**1/4**), and each tap takes you to the next one; the fourth takes you back to the first. - **1/4**: the vowels अ to औ, the consonants क to श, then ष स ह, the four conjuncts ज्ञ क्ष श्र, ऋ, and nukta ़, anusvara ं and halant ्. - **2/4**: conjuncts: the `र` ligatures (क्र, ग्र, द्र, …), the reph forms (र्क, र्ग, र्च, …) and the `त` and `द` families. - **3/4**: more conjuncts: the `य` ligatures, then the न, म, प, ब, ल and ष families. - **4/4**: the rest of the script: candra vowels (ऍ ऑ ऎ ऒ), the Sindhi and Kashmiri letters (ॹ ॺ ॻ ॼ ॾ ॿ), the matras that aren't on the other pages, the Vedic accents ॒ ॑, and ऽ ॥ ॐ. **?१२३** reaches the numbers and symbols from any page, and **।** sits beside the space bar where the full stop usually is. The bottom row is the usual one otherwise, with a comma key next to **?१२३**, so the bottom row settings (the 🌐 key, comma as emoji) treat it like every other layout. Switching fields or closing the keyboard brings you back to page one. ## Options Languages & layouts / हिन्दी · Hindi - **Layouts**: InScript, Hindi phonetic, हिन्दी वर्णमाला, Remington (GAIL), and Hindi Flick A (Gboard), one card each, turned on and off independently. - **Key hints**: Languages & layouts / Hindi / Key hints / Show the letter each key types. Off, Added, or Whole letter. It puts the Devanagari a key is about to write in its corner. It's the same shared setting Bangla's Avro uses, and it shows only while a transliterating layout is active. - **Key map**: a folded group just below the hints while Hindi phonetic is on, with the same tables as the [key map](#hindi-phonetic-key-map) above. **Full guide** at the bottom, Languages & layouts / Hindi / Key map / Full guide, opens this page in your browser. - **Suggestion strip**: Languages & layouts / Hindi / Suggestion strip / Keep the first two words in place, off by default, shown while Hindi phonetic is on. Turn it on and the strip's first two words stay in place: what you typed in English letters on the left, the rules' Devanagari reading of it next to it, and the suggestions after them. The space bar still writes the same word as before, and that word is the bold one. **Suggestions after them** picks what fills the rest: **Smart**, **Hindi only**, or **English only**. The same setting on Bangla's screen is described on [Bangla](https://wmkeyboard.pages.dev/languages/bengali/#options). - **Neural suggestions**: Languages & layouts / Hindi / Suggestion strip / Neural suggestions, off by default, shown while Hindi phonetic is on. Up to three readings from IndicXlit, a small transliteration model, right after the word the space bar writes, which stays as it is unless the next switch is on. A 13 MB download shared by every language that has it, plus ONNX Runtime. See [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions). - **Let it choose the word**: Languages & layouts / Hindi / Suggestion strip / Let it choose the word, off by default, shown while Neural suggestions is on. When the rules spell no known word and the known-spellings list has nothing for the spelling, the space bar writes the model's best listed reading instead, with the rules' spelling second in the strip. Right first word on Aksharantar test words: 49% instead of 41%. See [Let it choose the word](https://wmkeyboard.pages.dev/languages/phonetic/#let-it-choose-the-word). - **Spellings**: Languages & layouts / Hindi / Spellings / Use known spellings, on by default. Switch it off for the letter-for-letter reading. Without a downloaded word list this leaves the rules on their own, so expect more wrong guesses. - **Dictionary words**: Languages & layouts / Hindi / Spellings / Correct to dictionary words, on by default. Off, space commits exactly what the rules spell instead of a word from the list that sounds like it, and the list's words stay in the suggestion strip. This runs whether or not autocorrect is on. - **The words before**: Languages & layouts / Hindi / Spellings / Use the words before, on by default. One spelling can stand for several words, and the one or two words you typed just before pick between them, on the strip and for what space writes: "kam" is काम or कम by what came before. The evidence is your own typing and, where the language has one, the downloaded word-pair data. A known spelling only trades places with another known spelling of the same buffer, so the composing text is still what space writes. Turn it off if a word comes out differently after certain words than you expect. - **Also suggest from**: with English picked, the phonetic layout's strip also carries the word as you typed it in Latin letters, and an **English words** group appears right below: **Type English words as English** makes the space bar write `hello` as hello, and **Switch on the suggestion strip** shows or hides the small button that turns that on and off while you type. See [English on Avro and Hindi phonetic](https://wmkeyboard.pages.dev/smart/mixed-language/#english-on-avro-and-hindi-phonetic). - **Numerals**: `०-९` or `0-9`. - **Dictionary**: the downloadable Hindi list has about 243,000 words. You can import your own as well, and imported words are found phonetically too. - **Clusters**: conjunct-aware backspace, above. ## Details & edge cases **Hinglish is a different language, not this layout.** Picking Hinglish · Hindi (Romanized) from the catalogue gives you a Latin QWERTY that stays Latin and suggests romanized words. Hindi phonetic is the opposite: it converts what you type into Devanagari. **The composing text can change its mind as you type.** `kar` reads कर, `karn` closes on a cluster and reads कर्न, and the `a` that makes it an infinitive opens it back up to करना. What you see at each point is what a space would commit at that point. **The phonetic machinery only loads if the layout is on.** Hindi with just InScript enabled builds none of it. **It's new, and the rule table is a draft.** The join table, the list of endings and the known spellings were written without a native speaker on hand. If a common word comes out wrong with the word list installed, [say so on the issue](https://github.com/wasi-master/wmkeyboard/issues/239) and name the word. --- # Keyman layouts URL: https://wmkeyboard.pages.dev/languages/keyman/ Section: Languages Summary: 862 key grids converted from the Keyman keyboards repository, covering languages nothing else on Android has a layout for. 862 of the layouts in WM Keyboard started life as [Keyman](https://keyman.com/) keyboards. That's where most of the language catalogue's long tail comes from: Adyghe, Hatran, Osage, Cypro-Minoan, and several hundred more that no other Android keyboard ships a layout for. They behave like any other layout once enabled. Each appears under its language in Languages & layouts, is editable in the layout editor, and exports as a file like any other. Before that, most sit behind a **More layouts** card at the end of the language's row of layout cards, so its own layouts stay up front. A Keyman keyboard for a language the keyboard already has, in the same script, sits with that language rather than as a second entry for it. The Indic romanization keyboard Keyman files under "Hindi (Latin)" is with Hinglish, the Brazilian Portuguese ones with Portuguese, the Canadian French ones with French, and so on for about forty of them. A language you had turned on under the old name is the same language now. That's Khmer Angkor, rendered from the same layout data the app ships. ## Where they come from [Keyman](https://github.com/keymanapp/keyboards) is an SIL Global project. Its repository holds around 930 keyboards, most of them for minority and historical languages, and the release-quality ones are MIT licensed. Every keyboard used here is credited by name and copyright holder in About / Open-source licences / Keyman keyboard layouts. WM Keyboard isn't affiliated with SIL Global or the Keyman project. We converted the key grids with our own code; these aren't the upstream files. ## What converts, and what doesn't A Keyman keyboard is two things: the visual key grid, and a set of rules that decide what each keypress actually types. **Only the grid is converted.** The rules are downloaded separately, which the next section covers. What comes across: - Every layer the keyboard defines. Besides letters, numbers and symbols that includes right-Alt pages, caps pages and the pages some keyboards keep for one consonant's vowel forms, which open from the keys that lead to them, just as in Keyman. - The shift and caps pages as their author drew them. Shift and caps lock work the way they do on every other layout here (one tap for one shifted key, a double tap to lock), and while shift is on you see and type the keyboard's own shift page, long presses and all. - Long-press alternates and flicks, each typing through the keyboard's rules the way it does in Keyman, not just the character on its label. - Keys that exist for the rules to act on, such as the vowel keys of the GFF Ethiopic keyboards, which turn the consonant before them into a syllable. - Multi-tap keys, where tapping a key again swaps what it just typed for the next character in its cycle. Each tap in the cycle goes through the keyboard's rules as if it were the first thing typed after the text before the cycle began, which is what Keyman does too. The GFF and GeezWord Ethiopic keyboards use this heavily. A tap continues the cycle when it's the same key within a second of the last one. Any other key, or a pause, starts over. What doesn't: - **Multi-tap on page keys.** A few keyboards let you tap the 123 key again to go on to a symbols or currency page. In Keyman the next tap counts as the same key even though the first tap already changed the page under your finger. Here it's whatever key is on the new page, so these keys only switch to their first page. - **Multi-tap on keys that switch pages as they type.** For the same reason, a cycle stops after the first tap when that tap changes the page, for example a key on the shift page, which drops shift again. Tapping again types that page's key. - **Flicks.** Keyman's eight flick directions all come across, the diagonals included, since WM Keyboard's [flick keys](https://wmkeyboard.pages.dev/typing/gestures/#flick-keys) have eight directions too. A flick whose direction isn't one of the eight compass points is dropped and counted rather than guessed at, because typing the wrong character is worse than the gesture doing nothing. Keyboards converted before the diagonals existed were not re-run, so a few of the shipped ones still carry only their four edge flicks. - **Fonts.** Keyman packages ship their own. WM Keyboard uses [its own font system](https://wmkeyboard.pages.dev/themes/fonts/) instead, which already covers these scripts. ## Typing rules This is the part worth understanding before you enable one of these layouts. On a **positional** keyboard, each key types the character printed on it. Those work correctly with nothing extra, and that's most of the 862. On a **mnemonic** keyboard, the printed key caps and the typed output are deliberately different. A mnemonic Khmer keyboard has you press the key where `k` sits on a US layout to get ក, and a set of rules performs that mapping. Those rules aren't in the app. The rules are downloaded per keyboard, from keyman.com, when you ask for them. The grids are their own download on the GitHub and Google Play editions: about 2 MB, once, for all 862. It starts by itself when a Keyman layout is switched on. Before that, a Keyman layout's card shows a **Get Keyman layouts** button in place of its picture, and pressing it fetches them without switching anything on. Until they arrive a Keyman layout that is on draws QWERTY, and after an app update that changed the grids, the previous ones stay in use until the new ones are here. The F-Droid edition and the offline build carry the grids in the app and never make this request. Languages & layouts / (a language) **When you switch a layout on, the app asks.** If that layout needs rules and doesn't have them yet, a prompt offers to fetch them there and then, which is the moment you're deciding to type in the language rather than the moment you notice the output looks wrong. Choose Not now and nothing is lost: the row is still there. The **More layouts** page shows every Keyman layout the language has as a card with a small preview of the keyboard, in your theme. Tap a card to turn that layout on or off. With more than six layouts a search box sits at the top, and once any of them is on, **All** and **On** filters show either the whole list or only the ones you use. Layouts drawn for a phone come first. Copies of a desktop keyboard, with the full number row and 13 or more keys across, come after them, since they squeeze a computer's keyboard onto a phone screen. The order never changes when you turn one on, so a card doesn't move out from under your finger. Under each card is that layout's typing rules: **Typing rules ready** once they are on the phone, or a **Get typing rules** button while they are not. The button turns red when the layout is already on without its rules, because that is the case where your typing comes out wrong. The delete button next to downloaded rules asks before it removes them. Once a Keyman layout is on, a **Typing rules for (layout name)** row also sits under the layout cards on the language's own screen, and does the same thing. Nothing is fetched until you ask. Until you do, the layout types its key caps. The grid is still right, the long-press alternates are still there, and nothing damages your text. It isn't doing the character mapping its author wrote. If you enable one of these and the output doesn't match the caps, that's this. Rules are only fetched for keyboards whose upstream licence permits it, and the downloaded file has to parse as a keyboard before it's kept. keyman.com is always asked first. If it doesn't answer, or no longer offers a keyboard, the rules come from a copy the project keeps in its [data repository](https://github.com/wasi-master/wmkeyboard-data/tree/HEAD/keyman) instead: the same files, unmodified, for the 812 bundled keyboards that have any. That copy is checked against the checksum listed beside it before it's used. It may be a version or two behind, and the next time keyman.com answers with something newer, pressing the row again replaces it. ### How closely it matches Keyman On a phone, Keyman types through KeymanWeb, and that is what the rules here are checked against: random typing on each keyboard's own touch layout, keys, long presses, flicks and page changes, fed to KeymanWeb and to WM Keyboard, compared after every keystroke. Of the 811 bundled keyboards whose rules can be downloaded, 800 type exactly what KeymanWeb types. The other 11 contain rules their authors marked for Keyman on computers only, or for KeymanWeb only, and the downloadable rules are the computer version, so those keyboards type what Keyman on a computer would. One difference is on purpose. In Keyman a shift key stays down until something releases it; here shift lets go after one key, as on every other layout, unless the keyboard itself decides when shift ends. 50 keyboards, most of them from the FirstVoices collection, are published without the file the rules come from, so there is nothing to download for them and they type their key caps. ## Undetermined languages Five keyboards in the upstream repository declare no language at all. Rather than invent one, WM Keyboard skips them unless the script by itself identifies the language, which is how one of the IPA keyboards gets in. You can still add any of the five by importing its file, and you pick the language yourself when you do. ## Importing one yourself Layout & size / Key layouts / Import from another keyboard Pick a `.keyman-touch-layout` file. The app reads the grid, then shows you what the conversion did and asks which language the layout belongs to. See [Importing from other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/) for how that dialog works. A touch layout file holds no rules, so a layout imported that way types its key caps. You can also open a **`.kmp` keyboard package** straight from a file manager. A package carries both the grid and the rules, so it's the one route that gives you a working keyboard in a single step, and it's how you'd install a keyboard that isn't in the bundled set at all. The app reads the grid out of the compiled keyboard inside the package, installs the rules alongside it, and tells you which of the two it found. A package built for desktop computers only has no on-screen keyboard in it, so there's nothing to add. The app says so rather than adding an empty layout. ## Reporting a bad layout These were converted in bulk, and nobody on the project reads most of these languages. If a grid is wrong, a key is in the wrong place, or a character is missing, [open an issue](https://github.com/wasi-master/wmkeyboard/issues) and say which layout. Upstream fixes flow back in when the conversion is re-run. --- # Music, braille & Morse URL: https://wmkeyboard.pages.dev/languages/notation/ Section: Languages Summary: Four notation systems that live in the language catalogue but aren't languages: musical notation, chorded braille, Morse code, and IPA. Four entries in the language catalogue are notation systems rather than spoken languages: musical notation, chorded braille, Morse code, and the International Phonetic Alphabet. Each behaves exactly like a language (its own layout, its own switch-ring slot, its own settings screen), but what you're typing is notation, not prose. [Fancy text](https://wmkeyboard.pages.dev/languages/fancy-text/) is the one other entry that isn't a language, and it gets its own page. languages/notation-music-field — Musical notation active in a text field: note durations, rests, and clefs on the key grid. ## Musical notation Turn on **♪ · Music notation** and the keys commit symbols from Unicode's Musical Symbols block plus the everyday BMP note characters (♩♪♫♬): durations, rests, an augmentation dot, the three clefs, sharps, flats and naturals (single and double), a barline, and a fermata on the main layer: A second layer, reached with the Fn key, holds the dynamics and hairpins (𝆏 through 𝆓), ornaments (grace notes, the trill, the turns), navigation marks (segno, coda, dal segno, da capo), breath and caesura, pedal marks, repeat signs, the double and final barlines, and the quarter-tone accidentals. A ♪ key on that layer returns you to the main letters layer: fn — Music notation: the Fn layer (dynamics, ornaments, repeats) Most of these glyphs sit in the Supplementary Multilingual Plane, which very few device fonts carry: that's the whole reason Music notation is its own script rather than riding an existing one. WM Keyboard pins it to Google's **Noto Music** face automatically, so the symbols render correctly even on a phone whose system font would otherwise show blank boxes. The choice is automatic and you can't pick another. Unlike a language that rides the Latin script, Music notation gets no font row under Accessibility fonts. A glyph the pinned font doesn't cover falls back to the system font on a per-character basis rather than going blank everywhere. Music notation is uncased, so there's no shift key, and it doesn't compose. Every key commits its symbol directly. ## Chorded braille languages/notation-braille-chord — Braille active: chording dots 1 and 4 together commits the letter c. **⠃⠗⠇ · Braille** works nothing like the other three: instead of one key per character, it's a six-key chord, the same layout a Perkins brailler uses. Two columns of three keys represent the six dots of a braille cell, and you press some combination of them together and lift together to spell one character: Press a dot key and it fires immediately. Braille dots have no long-press timer, unlike every other key in the app: the press itself is gathered into the chord, with no wait to see whether it turns into a hold. As you hold more dots down, they accumulate into a pending cell. The cell commits the instant your last finger lifts, however many dots you pressed and in whatever order. Decoding follows uncontracted Grade-1 English braille: the standard a–z cells, a punctuation set (comma, semicolon, colon, period, exclamation and question marks, apostrophe, hyphen), dot 6 alone as a capitalize-next-letter indicator, and dots 3-4-5-6 together as a number-mode indicator that turns the following cells into digits until a non-digit cell ends it. A cell that doesn't match any of those falls back to committing the raw Unicode braille pattern itself, so no chord you type is ever silently dropped. It's also a way to enter Braille Patterns block characters that Grade-1 doesn't otherwise cover. ### Contracted braille To type contracted braille, download **Contracted braille tables** (about 4 MB) under **Settings → Languages → Braille (6-key chord)**, then pick your language under **Contractions**: Unified English Braille, American EBAE, Arabic, German, Spanish, French and several others. The translation comes from [liblouis](https://github.com/liblouis/liblouis), and it works offline. Contractions only mean something once the word is whole, so cells wait instead of typing one by one. The strip above the keys shows the cells and what they read as so far, and the word types when you press space or any other key: ⠮ then space types "the ". Backspace takes back the last cell while a word is pending. Capitals, numbers and punctuation follow the table you picked. With **None**, or until the tables are downloaded, the keyboard stays on the uncontracted decoding above. Like Music notation, braille gets an automatic pinned font (**Noto Sans Symbols 2**, chosen because it covers the full Braille Patterns block) with no font picker of its own. Braille is uncased and doesn't compose. If you switch fields, switch layouts, or open a panel mid-chord, the half-finished chord is dropped rather than left stranded. A dot whose pointer gets stolen out from under it by the app's glide-typing gesture handler is different. That finger still counts as lifted, so the chord isn't dropped. It completes and commits exactly as if you'd lifted the finger yourself, which keeps the chord engine from waiting on a finger that already left. ## Morse code languages/notation-morse-strip — Typing morse: the strip shows the sequence so far and, live, the letter it currently spells. **· − · Morse code** reduces the keyboard to two enormous keys (dot and dash) plus the usual bottom row: Tap dots and dashes to build up a sequence. Stop for a moment and it decodes and commits on its own. Tables cover the letters, digits, and common punctuation from the ITU-R M.1677-1 international Morse standard. While a sequence is pending, it takes over the suggestion strip: a centered readout of the sequence in ·/− glyphs, with the letter it currently decodes to spelled out alongside it whenever the sequence so far is itself a valid code (for example `·· i`, one dot short of `s`). It's the same idea as the accent hint that appears above a dead-key in progress on other layouts, just for Morse. Pressing any key other than dot, dash, backspace, or shift flushes whatever's pending first, so the timer never races your next keystroke. Backspace while a sequence is pending edits the sequence itself, dropping its last signal rather than deleting already-committed text. Only once the sequence is empty does backspace fall back to a normal delete. Morse code doesn't have a script of its own: it rides the Latin script, so autocapitalize and your shift state still apply to the letters it decodes, even though there's no shift key on the Morse layout itself. The pause between your last signal and the sequence committing is 750 ms. It's the one number that decides whether the layout is usable at all: too short and sequences commit mid-letter, too long and you wait on every letter. The keyboard reads it from a stored value that accepts 300 to 2000 ms, but no screen in the app draws a row for it yet, so 750 ms is what you get today. Try keying S, O, S as three consecutive letters. The strip has an opinion about it. See [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#send-an-sos). ## IPA **IPA · Phonetic** puts the International Phonetic Alphabet on a dedicated four-layer layout, noticeably more complete than a token "phonetic symbols" afterthought: The main layer holds pulmonic consonants, several with long-press alternates for aspirated or released variants (for example p long-presses to pʰ, ⁱp, and p̚). From there, a dedicated key jumps to a vowel layer, which itself jumps to a second consonant layer covering clicks, implosives, and ejectives: symbols — IPA: the vowel layer symbols2 — IPA: clicks, implosives, ejectives, and other consonants A fourth layer, reached from the main consonant layer or the vowel layer (not from the second consonant layer), holds superscript and combining diacritics (aspiration, nasalization, voicing, dentalization, and more) plus suprasegmentals like primary and secondary stress and vowel length marks: fn — IPA: diacritics and suprasegmentals An IPA key on every non-main layer returns you to the consonant layer. IPA is uncased and composes 1:1, including its combining diacritics: there's no dead-key step. Unlike Music notation and Braille, IPA doesn't get a pinned font: it rides the Latin script's font, because Latin faces already carry the IPA Extensions block along with the spacing and combining modifier letters IPA needs. That means IPA text renders in whatever key font you've picked under Accessibility fonts, same as any Latin-script language. ## Options Languages & layouts / Add language None of the four are enabled on a fresh install. What you start with is read off your phone's own language list (see [What ships](https://wmkeyboard.pages.dev/languages/overview/#what-ships)), and no phone language maps to a notation. To add one, open **Languages**. Tap **Add language**. Search for it by name: "music", "braille", "morse", or "ipa" each find their entry directly. You can also scroll to their native-glyph names (♪, ⠃⠗⠇, · − ·, IPA). Tap an unadded entry and its one layout is enabled immediately, then its own language screen opens. That's the same generic screen every language gets, with a **Layouts** toggle (just the one here) and an **Also suggest from** picker for blending in another enabled language's suggestions. None of the four gets a **Numerals** group. That group only appears for a language whose own digits aren't `0-9`, and all four use plain Latin digits, so there is nothing to choose and the group isn't drawn. languages/notation-add-language-search — Add language: searching "morse" finds Morse code in the full catalogue. Once enabled, a notation layout takes a slot in the normal switch ring exactly like any language: cycle to it with the 🌐 key or a spacebar swipe. See [Switching languages](https://wmkeyboard.pages.dev/languages/switching/) for how the cycle itself works. Nothing about it changes for a notation layout. For the language catalogue in general (search, numerals, "also suggest from," and everything else every language screen shares), see [Languages overview](https://wmkeyboard.pages.dev/languages/overview/). There's no build restriction on any of the four: all ship as ordinary bundled layout assets in every build. ## Details & edge cases **None of the four bundle a dictionary, a downloadable wordlist, or an emoji-keyword pack.** Their language screen's Dictionaries section holds only the Custom dictionaries row, and there's no suggestion strip beyond Morse's own live-decode readout: you're typing symbols directly, not composing words for a language model to correct or predict. **They aren't grouped together anywhere in the app.** Music, braille, Morse, and IPA are four ordinary entries in the same flat, searchable language list as every spoken language. Settings has no dedicated "notation" category. This page groups them because they share a "notation, not prose" character, not because the app does. **Font pinning is the real distinguishing feature of Music and Braille.** Both exist as their own script chiefly so the keyboard can force a specific Google font (Noto Music, Noto Sans Symbols 2) that reliably covers their Unicode blocks. Most device fonts don't. IPA and Morse need no such pin because they draw from ranges the Latin font faces already cover. **Braille's chord has no tap-vs-long-press ambiguity on its dot keys**: pressing fires the dot on the down-stroke, not after the usual long-press timer. Holding a dot key does nothing extra, so digits come from the dots-3-4-5-6 number indicator or from the ?123 layer. Every other key on the braille layout (the bottom row) behaves normally. **A half-typed braille chord, pending word of contracted braille or pending Morse sequence never survives a context change.** Switching fields, switching layouts, or opening any panel drops it, so you never come back to a stray dot or signal you don't remember starting. --- # Phonetic typing in more languages URL: https://wmkeyboard.pages.dev/languages/phonetic/ Section: Languages Summary: Roman letters in, native script out, for Marathi, Gujarati, Punjabi, Odia, Assamese, Tamil, Telugu, Kannada, Malayalam, Persian, Arabic, Greek and Tibetan. Bangla has [Avro](https://wmkeyboard.pages.dev/languages/bengali/), Hindi has [Hindi phonetic](https://wmkeyboard.pages.dev/languages/hindi/) and Urdu has [Urdu phonetic](https://wmkeyboard.pages.dev/languages/urdu/). Thirteen more languages now have the same kind of layout: a QWERTY grid where you type a word the way you'd write it in a chat, and the keyboard writes it in the language's own script. Type `vanakkam` on Tamil phonetic and you get Tamil; type `el7amdulillah` on Arabic phonetic and you get الحمدلله. | Language | Layout | You type | You get | |---|---|---|---| | Marathi | Marathi phonetic | `tumhi`, `tyala` | तुम्ही, त्याला | | Gujarati | Gujarati phonetic | `tame`, `chhu` | તમે, છું | | Punjabi | Punjabi phonetic | `mainu`, `ohda` | ਮੈਨੂੰ, ਉਹਦਾ | | Odia | Odia phonetic | `tume`, `kemiti` | ତୁମେ, କେମିତି | | Assamese | Assamese phonetic | `tumar`, `xokolu` | তোমাৰ, সকলো | | Tamil | Tamil phonetic | `naan`, `neenga` | நான், நீங்க | | Telugu | Telugu phonetic | `nenu`, `meeru` | నేను, మీరు | | Kannada | Kannada phonetic | `naanu`, `namma` | ನಾನು, ನಮ್ಮ | | Malayalam | Malayalam phonetic | `njaan`, `ningal` | ഞാൻ, നിങ്ങൾ | | Persian | Persian phonetic | `kheili`, `mikonam` | خیلی, می‌کنم | | Arabic | Arabic phonetic | `3arabi`, `kayf` | عربي, كيف | | Greek | Greeklish | `kalimera`, `8elw` | καλημέρα, θέλω | | Tibetan | Wylie | `bod skad`, `bkra shis` | བོད་སྐད, བཀྲ་ཤིས | ## Turning one on Languages & layouts / your language / Layouts Add the language, open its screen, and turn on its **phonetic** card under **Layouts**. The layout the language already had stays the default, so adding a language still gives you the keyboard you'd expect for it; the phonetic one is a choice. Then download the language's word list from the **Dictionary** row on the same screen. None of these lists ships inside the app, and the next section explains why the phonetic layout cares more than most. Every key is where it is on the English layout. What you type shows up in the native script in the composing text straight away. Once the strip has ranked the word, the composing text shows the word the space will commit. Translate's own text box spells the same way, so you can type the text to translate in your own script. The keys stay left-to-right on Persian and Arabic while the text runs right to left, as on Urdu phonetic. ## How a word is read Three layers, the same three Hindi and Urdu phonetic use, in this order: 1. **A list of known spellings**, a few hundred words for each language: the commonest words whose roman spelling doesn't carry enough to get the script right, chat shorthand, and English loanwords written in the script (`school`, `phone`, `doctor`). A spelling on the list is settled there, and it shows in the composing text the moment you finish typing it. A capital that changes what the rules spell goes around the list, since you chose that letter on purpose. 2. **The language's word list, if you've downloaded it.** What you typed and every word on the list are reduced to a loose key that forgets what roman spelling never says (vowel length, dental against retroflex, aspiration, the letters a script keeps for its loanwords) and keeps those details only to rank the candidates. With the list installed, the right word comes first for most chat spellings: Marathi 90%, Gujarati 82%, Telugu 78%, Punjabi 75%, Kannada 60%, Malayalam 56%, Odia 54%, Tamil 48% (measured on about 200 everyday chat spellings per language). 3. **The rules alone**, for every word that's in neither. They make the likelier guess, and the strip offers the other readings next to it, such as the same spelling with a vowel read long or with a different closing vowel, so you can tap the one you meant. Folding a whole word list into those keys is work, and a phone that does it at every keyboard start spends tens of megabytes on it. So the [data repository](https://github.com/wasi-master/wmkeyboard-data) does it once, ahead of time, for every language here and for Hindi and Urdu, and publishes the result beside the word list (`_phonetic_v1r1.wmpi.gz`, `v1r2` for Bangla and Assamese, 1 to 9 MB). The keyboard fetches it right after the word list and reads it in place from storage, so it costs almost no memory. Only lists you imported yourself are still folded on the phone. Until the file is there (no connection, or the build with no internet), the keyboard folds the downloaded list itself, as it always did. You can import the file instead: it is in the language's language pack, and [Offline imports](https://wmkeyboard.pages.dev/privacy/offline/) installs it. The rules alone get about half of everyday words exactly right: Tamil 66%, Kannada and Malayalam 60%, Marathi 57%, Punjabi 57%, Gujarati 55%, Telugu 53%, Odia 42%, Persian 67%, Arabic 46%. The rest are lexical facts no rule can know, which is what the first two layers are for. In the first two layers the words before count too: one spelling can stand for several words, and the one or two you typed just before move up the reading that usually follows them, on the strip and for what space writes. The evidence is your own typing (every word pair you commit is remembered) and the language's word-pair data, which all eleven have as a 1 to 2 MB download in the language's **Dictionaries** group, next to the word list. Without it, it is your own habits only, and those start empty. A known spelling only ever trades places with another known spelling of the same buffer. Languages & layouts / your language / Spellings / Use the words before turns it off. A spelling none of that recognizes may be a typo. The keys are checked against the romanized spellings people write for the language, as an English typo is checked against English words, so a slip of one key still reaches the word. See [Bangla](https://wmkeyboard.pages.dev/languages/bengali/) for how it decides. It works on any language here with romanized spellings to check against, and it follows the autocorrect switch for that language: with autocorrect off the fix is only offered on the strip. The layers above are not part of autocorrect, and the autocorrect toggle doesn't control them. The known-spellings list has its own switch, Languages & layouts / your language / Spellings / Use known spellings, and so does a space committing a dictionary word instead of the rules' spelling: **Correct to dictionary words** in the same group. ## Neural suggestions Languages & layouts / your language / Suggestion strip / Neural suggestions The three layers above know the words their lists know and the spellings their rules expect. Loose spellings fall between them: `valobashi`, `bhalobasi` and `bhalobaashi` are all ভালোবাসি to anyone who reads Bangla. **Neural suggestions** adds a fourth source for that, IndicXlit, a transliteration model by [AI4Bharat](https://github.com/AI4Bharat/IndicXlit) that learned from millions of romanized words how people write 21 Indic languages in Latin letters. It is off by default, and it is the same switch on every language that has it: Bangla, Hindi, Urdu, Assamese, Marathi, Gujarati, Punjabi, Odia, Tamil, Telugu, Kannada and Malayalam. Turned on, the strip shows up to three of the model's readings right after the word the space bar writes. Readings that are in the language's word list come first. The word the space bar writes never changes (unless you turn on [Let it choose the word](#let-it-choose-the-word)), and the model never corrects anything by itself: its readings are only offered, and a tap writes one. So it helps most when you scroll or glance past the first word, and costs nothing when the first word was already right. Measured on held-out words, the right word in the first four places of the strip: | Language | Without | With | |---|---:|---:| | Bangla, chat spellings the known-spellings list doesn't have | 52% | 80% | | Hindi, chat spellings the known-spellings list doesn't have | 88% | 93% | | Bangla, Aksharantar test words | 30% | 80% | | Hindi, Aksharantar test words | 55% | 84% | | Urdu | 43% | 73% | | Marathi | 60% | 86% | | Gujarati | 58% | 87% | | Punjabi | 55% | 75% | | Odia | 33% | 66% | | Tamil | 39% | 81% | | Telugu | 57% | 90% | | Kannada | 51% | 89% | | Malayalam | 32% | 77% | The other languages were measured on 2,000 words of the Aksharantar test set each, with the language's word list installed. Aksharantar spells long vowels doubled (`aa`, `ee`), which Avro reads differently from most of these layouts, so Bangla's Aksharantar row starts low; the chat rows are the fairer picture for Bangla. The model is one download of 13 MB, shared by every language, from the data repository's `models` release on GitHub, checked against a SHA-256 built into the app. It also needs ONNX Runtime, the same part [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) runs on, about 33 MB, which the Google Play build gets from Play and the other builds from Maven Central. Turning the setting on starts both downloads under one progress bar, runtime first, and the size the switch quotes counts the runtime until it is on the phone, so a first download reads about 46 MB rather than 13. The row under it shows how far they are and starts them again after a failure; once the model is here, tapping the row or swiping it offers to delete it. Deleting turns neural suggestions off on every language that had them; the model is also a row of its own under **Downloads** on the storage screen. Everything runs on the phone, on a thread of its own, and nothing you type leaves it. A reading that takes longer than the strip can wait for appears a moment later, while you are still on the word. The last row of the group is the **Romanized dictation model**, the same model family trained the other way round, from the script back to Latin letters. It has nothing to do with typing: it is for dictating this language in Latin letters (Banglish, Hinglish), where it spells the words the spelling lists don't know, and it is a download of its own. See [Hinglish, Banglish and other romanized languages](https://wmkeyboard.pages.dev/tools/voice/#hinglish-banglish-and-other-romanized-languages). ### Let it choose the word Languages & layouts / Bangla or Hindi / Suggestion strip / Let it choose the word Bangla and Hindi have a second switch, under Neural suggestions and off by default. With it on, the model may also pick what the space bar writes, but only in one case: the rules spell something that is not a word in the language's word list or among your own words, and the known-spellings list has nothing for what you typed. Then the model's best reading that is in the word list goes first in the strip, and a space writes it. The rules' spelling moves to second place, one tap away. Whenever the rules spell a known word, or the known-spellings list has the spelling, nothing changes. The text in the field still shows the rules' spelling while you type, as it does when the space bar is about to write a dictionary word that sounds the same. The strip's first word is what a space writes. If you press space before the model has answered, the keyboard waits up to 30 ms for it, so typing fast does not change which word you get. Measured with the known-spellings list on, how often the space bar writes the right word: | Words | Without | With | Made worse | |---|---:|---:|---:| | Bangla, Aksharantar test words (4,000) | 25.0% | 56.4% | 22 | | Bangla, chat spellings the known-spellings list doesn't have (2,500) | 41.8% | 52.8% | 31 | | Hindi, Aksharantar test words (4,000) | 40.9% | 48.6% | 24 | | Hindi, chat spellings the known-spellings list doesn't have (692) | 78.2% | 80.1% | 0 | What it gives up is Avro's rule that the space bar writes exactly what you spelled. A name or rare word spelled exactly to the rules, but missing from the word list, can turn into a listed word that looks like it: `ananto` আনান্ত becomes অনন্ত, `bharotpur` ভারতপুর becomes ভরতপুর. On words spelled exactly to the rules and missing from the list, that happened to about one in six in Bangla and one in fifteen in Hindi. For words the list has, it happened to 2 of 305 in Bangla and none of 427 in Hindi. Urdu gained one point, too little for that trade, so it does not have the switch. It does nothing while the strip is fixed to the word as typed, or while desktop Avro's candidate list is shown in its own row, since the first word there would then disagree with the space bar. ## What each language does ### Marathi, Gujarati and Punjabi These follow Hindi phonetic's model: you write the word the way it sounds, the inherent vowel drops in speech, and a final vowel is long. Each language has its own grammatical endings the rules keep clear of a conjunct. - **Marathi** reads `z` and `zh` as झ (`maza`, `tuza`), `L` as ळ, and knows its own postpositions and verb endings: `tyala` त्याला, `karnar` करणार, `karun` करून, `gharachya` घराच्या, `mitranno` मित्रांनो. - **Gujarati** reads `z` as ઝ and `x` as ક્ષ (`Axar`, `Laxmi`), writes the neuter and infinitive endings with their nasal (`hu` હું, `karvu` કરવું, `kartu` કરતું), and the locative too (`gharma` ઘરમાં). - **Punjabi** is written in Gurmukhi, which has almost no conjuncts: a doubled consonant is the addak (`pakka` ਪੱਕਾ), a nasal is a tippi or a bindi by the vowel before it (`mainu` ਮੈਨੂੰ, `ton` ਤੋਂ), and a y-glide is a vowel carrier rather than ਯ (`pyar` ਪਿਆਰ, `gya` ਗਿਆ, `duniya` ਦੁਨੀਆ). ### Odia, Tamil, Telugu, Kannada and Malayalam These languages pronounce every vowel, so people type every vowel: `amma`, `nanna`, `avan`. A consonant straight after a consonant is a conjunct, and a word-final consonant takes a virama (Tamil's pulli, Malayalam's chillu). - **Short and long e and o** are separate letters in the four Dravidian languages. Type `E` or `O` for the long one if you want to be exact; otherwise each language's rules pick the likelier one from context (`nenu` నేను, `beku` ಬೇಕು). - **Tamil** has no separate voiced or aspirated letters, so `g d b` and `k t p` write the same Tamil letter. `zh` is ழ, `L` ள, `R` or `rr` ற, `ndr` ன்ற (`nandri` நன்றி), and a single stop between vowels is doubled the way Tamil writes it (`iruku` இருக்கு). - **Malayalam** follows the Mozhi conventions people already use: `t` ട, `th` ത, `d` ഡ, `dh` ദ, `nt` ന്റ (`ente` എന്റെ), `ng` ങ്ങ, `nj` ഞ, `zh` ഴ, and a final `m` is ം. - **Telugu** reads `th` and `dh` as the plain dentals (`thammudu` తమ్ముడు), and a final `m` as ం (`manam` మనం). - **Kannada** reads `th` as ತ and knows its plural `-galu` and the long first vowels of its commonest words (`beku` ಬೇಕು, `helu` ಹೇಳು). - **Odia** writes ଯ where a word opens and ୟ elsewhere, ୱ for `w`, and spells its nasal clusters as conjuncts (`ranga` ରଙ୍ଗ, `pancha` ପଞ୍ଚ). ### Assamese Assamese uses Avro's rules, because people romanize it the way they romanize Bangla, with three changes: every র is ৰ, an `x` is the Assamese স (`xokolu` সকলো, `axom` অসম), and a `w` gliding into a vowel is ৱ. ### Persian `kh` is خ, `ch` چ, `zh` ژ, `gh` ق (`Gh` for غ). Short a, e and o aren't written inside a word, a final `e` is the silent ه (`khoone` خونه), and `i` is always the long ی. The verb prefixes می‌ and نمی‌ are written with the zero-width non-joiner the language uses (`mikonam` می‌کنم, `nemitoonam` نمی‌تونم). ### Arabic Arabizi works, digits and all: `2` ق, `3` ع, `3'` غ, `5` خ, `6` ط, `6'` ظ, `7` ح, `8` ق, `9` ص, `9'` ض. A number you type on its own stays a number. `al`, `el` and `il` before a consonant are the article ال (`elyoum` اليوم, `essalam` السلام), `g` is ج, `ch` is ش, and a closing `a` is ا with ة and ى offered beside it. `2` is ق rather than the hamza of most charts, because the people who type `2` most mean the glottal stop their dialect says for ق (`2albi` قلبي); the real hamza words are in the known-spellings list. ### Greek Greeklish doesn't care which spelling you use: `kalimera` and `kalhmera`, `thelw`, `8elw` and `thelo`, `kserw`, `xerw` and `3erw` all find the same word, accent included, because the word list settles whether an i is ι, η, υ, ει or οι. `b` is μπ, `d` is δ and `h` can be η or χ. A number on its own stays a number. The accent and the right spelling come from the Greek word list, so download it from the language's page first: without it you get the rules' plain reading, `καλημερα`. There's no romanized list yet, so swiping isn't decoded on this layout. ### Tibetan Wylie is an exact system rather than a guess, so there's nothing to rank: what you type is what you get. The space bar types a tsheg, `/` a shad, and `'` the a-chung. Stacks follow the rules of Tibetan spelling (`bsgrubs` བསྒྲུབས), `.` keeps two letters apart (`g.yag` གཡག) and `+` forces a stack (`pad+ma` པདྨ). Capitals are the Sanskrit letters and long vowels of Extended Wylie (`Ta` ཊ, `A` ཨཱ). ## Languages with rules only 77 more languages have a phonetic layout named after them: `kamal` writes कमल on Maithili phonetic, కమల on Lambadi (Telugu) phonetic and কমল on Chittagonian phonetic. | Script | Languages | |---|---| | Devanagari | Ahirani, Awadhi, Bagheli, Bagri, Bhili, Bhojpuri, Bodo, Bundeli, Chhattisgarhi, Dhundhari, Dogri, Garhwali, Godwari, Gondi, Gujari, Halbi, Harauti, Haryanvi, Kangri, Kannauji, Kashmiri, Konkani, Korku, Kurukh, Lambadi, Magahi, Maharashtrian Konkani, Maithili, Malvi, Mandeali, Marwari, Merwari, Mewari, Nimadi, Rangpuri, Sadri, Shekhawati, Sindhi, Surjapuri, Varhadi, Wagdi | | Bengali | Bishnupriya Manipuri, Bodo, Chittagonian, Garo, Kok Borok, Manipuri, Mundari, Santali | | Gujarati, Odia, Kannada, Telugu | Bhili; Halbi; Konkani, Lambadi, Tulu; Gondi, Lambadi | | Arabic | Bagri, Balti, Brahui, Dakhini, Dogri, Eastern and Southern Balochi, Gujari, Kashmiri, Marwari (Pakistan), Northern and Southern Hindko, Pahari-Potwari, Pashto, Saraiki, Shina, Sindhi | | Other | Yiddish in Hebrew letters, Mongolian in Cyrillic, Santali in Ol Chiki, Manipuri in Meetei Mayek | These work from a table of sounds, with none of the grammar rules the languages above have, so treat the first spelling as a guess. Capital letters pick the retroflex sounds, as on the other Indic layouts, and on Sindhi, Saraiki and Pashto they and doubled letters also reach the letters Urdu doesn't have: `dd` is ڏ in Sindhi and ݙ in Saraiki, and `x` is ښ in Pashto. Mongolian follows chat spelling, where `q` is ө and `v` is ү. All of them rank against a word list when one is installed, so the strip leads with a word the language has rather than the rules' guess: `odor` finds өдөр on Mongolian even though the rules wrote о. Many of these languages have no list to download. Among the Arabic-script ones and the last row, only Pashto, Sindhi, Saraiki, Yiddish, Mongolian, Santali and Manipuri do, and for the others the strip shows what the rules wrote and their other readings, until you [import a list](https://wmkeyboard.pages.dev/languages/dictionaries/). A list in another script than the layout's ranks nothing. ## Glide typing Swiping over a phonetic layout draws a romanization, so the stroke is decoded against romanized spellings and turned into the script, as on Avro. Marathi, Gujarati, Punjabi, Tamil, Telugu, Kannada, Malayalam and Arabic each have a romanized word list in the language catalogue that widens what a swipe can find. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/). ## Switching mid-word If you notice halfway through a word that you're still on your Latin keyboard, switch to the phonetic layout: with **Carry the word over** on (the default), what you already typed is converted too. See [Switching languages](https://wmkeyboard.pages.dev/languages/switching/#switching-mid-word-onto-a-phonetic-layout). ## Details & edge cases **These are new, and every table is a draft.** The rules, the known-spellings lists and the loanword lists were written and measured against each language's word list and a hand-written set of everyday chat spellings, not reviewed by native speakers. If a common word comes out wrong with the word list installed, [open an issue](https://github.com/wasi-master/wmkeyboard/issues) and name the word and the spelling you typed. **Some word lists are better than others.** Tamil's has no frequencies at all, so the keyboard breaks ties by how close a candidate is to what the rules spell. Malayalam's comes from subtitles and Gujarati's, Marathi's and Kannada's from encyclopedic text, so spoken forms are thinner there, and the known-spellings lists lean on chat forms for that reason. **The phonetic machinery only loads if the layout is on.** A language with just its script layout enabled builds none of it. **There are no selection-bar conversion chips for these languages yet.** Bangla and Hindi have chips to convert selected text between roman and native script; these languages don't. --- # Switching languages URL: https://wmkeyboard.pages.dev/languages/switching/ Section: Languages Summary: Four ways to switch: spacebar swipe, hold-drag picker (list or carousel), globe key, system switcher. Once more than one layout is enabled, there are four ways to move between them on the touchscreen: swipe the spacebar, hold the spacebar and drag through a list, use a dedicated key, or reach for Android's own input-method switcher. All four call the same underlying switch. Pick whichever suits your hands, because nothing about what a switch *does* changes. A physical keyboard adds a fifth: its own language key, or Ctrl+Space. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/#switching-language) for those. However you make it, the switch lands right away. A half-typed word is committed as it stands (unless the new layout is a phonetic one and **Carry the word over** is on, see below), the keys redraw, and the suggestion strip refills from the new language's dictionary immediately, so you don't have to type a letter first to stop being offered words from the language you just left. languages/spacebar-resting-label — The resting spacebar shows the current language's name, with switch-direction arrows on either side. ## Swiping the spacebar space-swipe — Swipe the spacebar to switch language. The spacebar has two independent swipe slots, each set under Typing / Gestures: - **Quick swipe on the spacebar**: a swipe that starts moving right away. Defaults to **Language**. - **Press and hold, then swipe**: hold briefly, *then* swipe. Defaults to **Cursor**. languages/spacebar-language-preview — Mid-swipe: the language preview shows every enabled layout, current selection highlighted. Each slot can be set to Nothing, Language, Cursor, Numpad, or Keyboards (Android's list of keyboard apps, see [Spacebar gestures](https://wmkeyboard.pages.dev/typing/gestures/)). Which slot fires depends on *when* the drag starts, not on how far you drag. The reference point is the long-press delay, 300ms by default. A swipe that crosses the touch-slop threshold before that delay uses the quick-swipe slot, and one that starts after it uses the hold + swipe slot. Once the language preview or the picker list is up, any further drag switches language whatever the slot is set to. You are looking at a language chooser by then, so the drag does what it looks like it does. That never overrides your hold + swipe slot, because the chooser only appears on a hold when that slot is set to Language, or to Nothing with the quick swipe on Language (see below). With exactly two layouts enabled, a swipe toggles to the other one. Dragging further in the same direction doesn't toggle again. Reverse direction to switch back. With three or more layouts, a quick flick moves exactly one layout over, however fast or far it travels, so the one in the middle is always a flick away. To go further, keep the swipe moving: after a short pause (about a fifth of a second) each further 44dp of horizontal drag steps to the next one in your switch order, while reversing direction answers straight away. After a quick flick the preview stays up for a moment after you lift, so even a flick shows which layout it landed on. **Keep the new language on screen**, beside the other spacebar settings in Typing › Gestures, sets how long: half a second by default, up to a full second, or Off to have it go the instant you lift. A slow swipe that rested on its layout for a quarter of a second or more has already shown it, so the preview goes the instant you lift whatever that is set to. Either end of the list resists wrapping around, since a wrap costs 2.5× the travel of a normal step, so the boundary layout "parks" until a deliberate extra pull cycles you back to the start. (Two layouts of the same language still toggle rather than cycle. The count that matters is enabled layouts, not distinct languages.) While a language swipe is in progress, a preview pops up above the spacebar showing every enabled layout as a row of chips: the current selection bold and highlighted, the rest dimmed. With more than five layouts enabled it shows a five-chip window centered on your selection instead of the whole list. Release to commit the highlighted layout. A tap with no movement always types a space. The resting spacebar label follows **The spacebar shows** (also under Typing → Gestures), which offers Language (the default), Layout, or Both. In Language mode, a transliterating layout such as Avro always shows its own name instead of the shared language name. Avro, Probhat and Jatiya all display as বাংলা, so the language name alone wouldn't say which method is under your fingers. In Both mode, a transliterating layout shows the combined form instead, such as বাংলা (Avro phonetic). That's the same as any other layout whose name differs from its language. Otherwise, whenever two enabled layouts share a language, the label adds the layout name alongside it so you can tell them apart. The ◀▶ hint arrows need three things at once: **Arrows on the spacebar** on (the default), one of the two swipe slots set to Language, and more than one layout enabled. On a spacebar too short to fit the arrows beside its label, such as the one on the symbols layer, the arrows are left off so the label stays readable. ### Naming a layout yourself Any switched-on layout can go by a name you choose. Open its language under Languages & layouts, and under the layout cards there is a **Name for (layout)** row for each layout that is on, subtitled with what the keyboard shows for it now. Tap it, type the name, and tap **Save**. A swipe on the row offers the same **Rename**, plus **Use the automatic name** once you've set one. Your name replaces the automatic one everywhere the keyboard names the current layout: the resting spacebar, the swipe and 🌐 preview, the hold-drag picker, and the Switch order list in settings. **The spacebar shows** and the same-language rule above no longer apply to that layout, so "Work" stays "Work" whichever mode is set. A custom **Spacebar text** still wraps it, with `%s` standing for your name. Clear the field, or tap **Use the automatic name** in the dialog, to go back. The names are saved with your other settings and come back with a backup. ### Typing speed on the spacebar Turn on **Typing speed on the spacebar** under Typing / Gestures and the spacebar shows how fast you're typing, as "42 wpm" next to its label. It's off by default. - The number is your speed over the last few words, up to the last 30 seconds, not your lifetime average. It needs two words and two seconds of typing before it shows anything. - It changes each time a word goes in: a word you type and end with a space or a mark, a suggestion you pick, a glided word, or a two-thumb word. Each new reading moves the number half way, so one fast or slow word doesn't make it jump. - A pause of more than 5 seconds clears it, and the spacebar goes back to its plain label until you've typed two more words. - It counts each word's letters and one space after it, the usual five characters to a word. Over 250 wpm is held at 250. - It never shows in a password field or while incognito is on, and nothing you type there is timed. Going into one of those fields, or coming back out, starts the count again from nothing. - Each field gets its own count too. Moving to a different field or closing the keyboard clears the number; the same field reloading itself keeps it. - Nothing is kept: no number, no text. So it works with [Statistics](https://wmkeyboard.pages.dev/reference/settings/statistics/) turned off, and it starts again from nothing when the keyboard restarts. ## Holding to drag through the list space-hold — Hold the spacebar, then drag to pick a language without lifting. Two things have to be true first: more than one layout enabled, and **Press and hold, then swipe** set to Language, or set to Nothing while **Quick swipe on the spacebar** is on Language. With neither slot on Language, holding the spacebar never opens the picker, and turning on **Swipe down to hide the keyboard** doesn't change that: the hold repeats the space, as it does with that switch off. That slot is the setting for what a hold *does*, so pointing it at Cursor or Numpad hands it the hold outright and no picker opens. Otherwise the picker swallows the very drag you asked for, which is what used to happen. A fresh install ships that slot on **Cursor**, so this gesture is opt-in: set the slot to Language (or Nothing, if you would rather the hold only ever open the picker and your quick swipe is on Language) to get it. With that done, press and hold the spacebar. Shortly before the long-press delay would fire (250ms, or your long-press delay if that is shorter), one of two things opens: - **The inline preview** from the section above, if you have four or fewer layouts enabled and the quick-swipe slot is set to Language. - **A scrollable picker list**, once you have more than four layouts enabled, or if you've reassigned the quick-swipe slot to something other than Language. ### Keeping the preview past four layouts Typing / Gestures / Picker for more than four layouts The switch to the list at five layouts is a setting. **Picker for more than four layouts** ships on. Turn it off and the hold keeps the sideways inline preview however many layouts you have on. The preview shows five layouts at a time and slides along as you drag, so the ones off screen are still in reach, and the hold-drag keeps moving the same way the swipe does. The quick-swipe rule still applies: with the hold slot on Language and the quick swipe on something else, the hold opens the picker at any length. The 🌐 key's long press always opens the picker. Once the list is open, drag vertically to walk it one row at a time. The first bit of finger movement after the list appears is absorbed as calibration, so drift from before the hold fired never selects a neighbor by accident. Lift your finger after moving and the highlighted row is selected. Lift without moving at all and the list stays open, so you can tap the row you want. ### Keeping the drag sideways: the carousel Typing / Gestures / Language picker shape The list has one awkward side. The swipe that switches language moves left and right, but the list walks up and down, so the moment you have enough layouts to need it the same gesture changes direction on you. **Language picker shape** offers a **Carousel** instead of the list. It is the same picker turned sideways: one strip of chips above the spacebar, centred on the layout you are typing in, with the rest scrolling off either end. With the carousel on, a spacebar hold-drag walks left and right, one chip per 44dp, the same step the plain swipe uses, and the strip slides to keep the highlighted chip in the middle so your finger never has to chase it. Everything else about the hold is unchanged: the first bit of movement is still absorbed as calibration, lifting after a move selects the highlighted chip, and lifting without moving leaves the strip up so you can tap a chip or scroll it by hand. The 🌐 key's long press opens the same carousel. The setting ships on **List**, so nothing changes until you pick the carousel. It only decides what the picker looks like. The four-layout threshold above (and whether it applies at all), and the quick-swipe slot, still decide whether the picker or the inline preview opens. A second setting takes this whole gesture away on purpose, the same way the hold + swipe slot does. Put characters into **Spacebar long press keys** under Typing → Gestures and the hold opens [your own popup](https://wmkeyboard.pages.dev/typing/gestures/#your-own-keys-on-the-spacebar-long-press) instead of the picker, which is what you want if you type in one language and would rather have your emoji there. The globe key and the spacebar swipe still switch language. Clear the field to get the picker back. The last row, **Other keyboards…**, isn't a layout. It opens Android's own "Choose input method" sheet, which is how you hand the field to a different keyboard app entirely. It sits below the scrolling part of the list so it's always in reach however many layouts you have on, and the hold-drag walk skips it: it's a tap-only row. In the carousel it is the keyboard glyph past the divider at the strip's end, and the same rules apply. ### A way out of the switcher Languages & layouts / Switching language / Extra stop in the switcher With two languages on, say Bangla and English, holding the spacebar shows just those two. **Extra stop in the switcher** adds one more stop after the last language, so the preview reads Bangla, English, then a gear. Drag onto the gear and lift, and the keyboard opens the Languages screen, where you add, remove and reorder what the switcher holds. Set it to **Other keyboards** instead and the last stop is a keyboard glyph that opens Android's "Choose input method" sheet, the same one the picker's **Other keyboards…** row opens. It ships on **None**. A hold-drag reaches the extra stop, and so does a swipe along the spacebar when the swipe switches language. A quick flick still moves one language at a time, so the swipe lands on the gear only when it keeps going past your last language. With two languages and the extra stop on, both the hold-drag and the swipe walk three stops in a line rather than toggling between two, so a flick left from the first language no longer jumps to the second. The list and carousel pickers show the gear as a row of their own (a gear icon in the carousel), next to **Other keyboards…**, when it is set to Language settings. ## Using the globe key A fresh install replaces the bottom-row 🌐 key with an emoji key. Every built-in layout defines that key, but the **Emoji key instead of 🌐** setting is on by default, so it opens the emoji panel instead of switching language until you turn it off. Language switching still works the whole time. It lives on the spacebar (above) and the Languages screen (below) instead. Layout & size / Bottom row keys / Emoji key instead of 🌐 languages/globe-key-toggle — Layout & size → Bottom-row keys: turning off Emoji key instead of 🌐 restores the language-switch key. Turn this toggle off in Layout & size / Bottom row keys to get the language key back. With it off, the bottom-row key behaves as a dedicated language switcher: - **Tap** cycles to the next enabled *layout* (not language) in your switch order, wrapping from the last back to the first. Three layouts of the same language still count as three separate stops. - **Long-press** opens the same picker the spacebar hold-drag uses (the list, or the carousel if you chose it), regardless of how many languages you have enabled. ### Switching from the toolbar The **Switch language** tool does what a tap on 🌐 does: one tap moves to the next layout in your switch order. Pin it to the toolbar, or leave it in the toolbox, when you want the switch away from the keys, for example so a stray tap beside the comma never changes language. It's off the keys entirely, so it works whatever the bottom row holds and whether **Emoji key instead of 🌐** is on. Find it under Tools / Switch language. ### Going back to the last language Layout & size / Bottom row keys / 🌐 goes back to the last language With many languages enabled, you often type only two or three of them at a time, and a different two or three depending on who you're writing to. Cycling in a fixed order then means tapping past languages you aren't using. Turn on **🌐 goes back to the last language** (off by default) and the key goes by what you used most recently instead, the way Alt+Tab moves between windows: - **One tap** goes back to the layout you used before this one. Tap again and you're back where you started, so any two layouts toggle, wherever they sit in your switch order. - **Quick taps**, each within about 0.8 seconds of the last, go further back: the second tap lands on the layout you used before those two, and so on. From the second tap, a list shows the layouts most recently used first, with the one you're on highlighted. Stop tapping, or type anything, and the list closes. Only the layout you stop on counts as used, so tapping past a language on the way doesn't push it up the list. Every switch you make counts, whether it came from the spacebar, the picker or the key, so the list is ready when you turn the setting on. Layouts you've never switched to come last, in switch order. With two layouts enabled this works the same as the plain tap. A physical keyboard's language key follows this setting too. The spacebar swipe doesn't: it always steps through your switch order. There's a separate **Comma key opens emoji** toggle (off by default) that replaces the comma key with an emoji key instead. It's independent of the globe-key setting above. Don't confuse the two. ### Ignoring a 🌐 tap right after typing Layout & size / Bottom row keys / Ignore 🌐 right after typing The 🌐 key sits between the comma and the spacebar, so a fast thumb aiming for either lands on it now and then, and the language changes in the middle of a word. **Ignore 🌐 right after typing** (off by default) sets a short window, from 50 ms up to a full second: a 🌐 tap that comes sooner than that after a typed key does nothing. Letters, space, backspace and enter count as typing. Shift, the mode keys and the tools don't, and neither does 🌐 itself, so quick taps through the list with **🌐 goes back to the last language** still work. A deliberate switch comes after a pause, which is longer than the window, and a press and hold still opens the picker at any time. The window is greyed out while the key is hidden. ### Sliding from 🌐 onto a shortcut Key press / Press and hold shortcuts / Slide from 🌐 for shortcuts With **Slide from 🌐 for shortcuts** on (off by default), putting your finger on 🌐 and sliding it onto a letter runs that letter's editing shortcut when you lift: onto C copies, V pastes, X cuts, A selects all, Z undoes and Y redoes. They're the same six actions on the same keys as the [hold shortcuts](https://wmkeyboard.pages.dev/typing/basics/#hold-a-letter-for-a-quick-edit), including any keys you moved them to, but the slide works whether the hold shortcuts are on or off. A line follows your finger from the key, as with the Shift and Ctrl drags. Lifting anywhere else does nothing, and sliding back onto 🌐 before you lift switches language like a tap. The slide has to start before the hold opens the language picker; once the picker is up, the finger belongs to it. To take the key off the bottom row altogether, turn off **Show the 🌐 key** in the same group. The spacebar gets its width. Switching still works from the spacebar, so keep a swipe set to Language if you type in more than one. ## Reordering and trimming the ring Languages & layouts languages/languages-screen-order — The Languages screen: Your languages, Add language, and Switch order. Every key and gesture above cycles through the same ordered list: your enabled layouts, in switch order. A fresh install seeds that list from your phone's own language settings, taking one layout per language it recognises, and then adds QWERTY. The whole list is capped at four layouts, so a phone set to four other languages fills up before QWERTY gets a slot. A phone set to Bangla and English starts with Avro and QWERTY. A phone set to English alone starts with QWERTY on its own and has nothing to cycle to yet. Everything else in the catalogue sits inactive until you add it. See [Languages overview](https://wmkeyboard.pages.dev/languages/overview/) for how the full catalogue works and what's in it. The head of that list is also where the keyboard opens. On a phone set to French, the seeded ring is AZERTY (French) then QWERTY, and the first keyboard you see is AZERTY, with its spacebar label, dictionary and autocorrect. Reordering the ring moves that starting point with it, right up until the first time you switch language yourself; from then on the keyboard opens in whichever layout you last used, and reordering only changes what the next step lands on. On the **Languages** screen: - **Your languages** lists each enabled language once. Tap one to enable or disable its individual layouts, or to remove the language entirely (only available once you have more than one language enabled, so you can never empty the list). - **Add language** searches the whole catalogue and enables an unadded language's first layout. A language with data to fetch asks about the download first (see [Downloads & mobile data](https://wmkeyboard.pages.dev/languages/downloads/)), and its Cancel backs out of the language too. - **Switch order** appears once you have more than one layout enabled. Drag to reorder the raw layout list, so two English layouts (QWERTY and AZERTY, say) keep independent slots in the ring even though they're both "English." Any language can stay enabled without a stop in the ring, which suits one you want only for its words: a romanized language such as Banglish on your English keyboard, or English suggestions on a Swedish one. Open it under **Your languages** and turn on **Do not show in the language switcher**. The globe key, the spacebar swipe, the picker and Android's own keyboard picker then skip it, while its words keep showing on every keyboard that lists it under **Also suggest from**. With English, Banglish and a Bangla layout enabled, hiding Banglish leaves two stops: English with Banglish suggestions, and Bangla. With Swedish and English enabled and English listed under Swedish's **Also suggest from**, hiding English leaves one stop: the Swedish layout, with both dictionaries suggesting. If hiding would leave nothing to switch to, every layout stays in the ring. There's no "maximum visible" setting. (The one recently-used option, [🌐 goes back to the last language](#going-back-to-the-last-language), only changes which layout the key visits next, not which ones are in the ring.) The ring is exactly as long as your enabled-layout list, so reordering or removing is the only way to shorten it. Ring length changes behavior on its own at three thresholds: two layouts swipe as a toggle rather than a cycle, more than four turn the hold-drag gesture from an inline preview into a full list (unless **Picker for more than four layouts** is off), and more than five shrink the swipe preview to a sliding five-chip window. ## Switching from Android's system menu Two things open Android's keyboard picker straight from the keys: the **Other keyboards…** row at the bottom of the picker list, and any key you set to **Switch keyboard** in the layout editor (it's in the Layers group of the action picker, see [Custom layouts](https://wmkeyboard.pages.dev/languages/custom-layouts/)). Both put up the system's "Choose input method" sheet, which is where you change to a different keyboard app. If Android refuses to show the sheet, the keyboard opens the input-method settings screen instead, which reaches the same place. To skip the list, bind a key to **Switch to a specific keyboard** (same Layers group) and pick the keyboard app it goes to. One tap then hands the field straight to that keyboard. The choice is remembered by the app's id, so it keeps working when other keyboards come and go. If that keyboard has since been turned off or uninstalled, the key falls back to the system sheet. Before Android 9 there is no direct switch, so it always opens the sheet. The settings below are about the opposite direction: getting Android's own switcher to list *this* keyboard's languages. The **System language switcher** group on the Languages screen connects your switch ring to Android itself: languages/system-switcher-toggle — Languages → System language switcher: register every enabled layout as an Android input-method subtype. - **List languages in the Android switcher** (off by default) registers every enabled layout as an Android input-method subtype, so the system's "Choose input method" sheet lists each one and can switch to it directly. The two stay in sync either way: a switch you make inside the keyboard is mirrored out to Android, and a switch made from the system sheet is followed back into the keyboard. It ships off because the sheet prints a subtype's label where it would otherwise print the keyboard's name, so one language name in that row reads as if the keyboard were called that. Left off, switching stays entirely inside the keyboard. It still tells Android the language you're typing in, under the keyboard's own name, so a system spell checker set to "use system language" checks that language rather than the phone's. A romanized language (Hinglish, Banglish, Arabizi and the other `_rom` entries) is listed under the name it has in the app, "Hinglish · Hindi (Romanized)", rather than the "Hindi (Latin)" Android would derive from its locale, so it can be told apart from plain Hindi in the same sheet. Every other language keeps Android's own translated name. - **Show app name first** (off by default, shown only while the toggle above is on) puts "WM Keyboard" ahead of the language in the switcher's label instead of after it. Android still controls which part of the label is bold or greyed; this only changes the text order. - **Select languages in the system settings** (also shown only while the first toggle is on) opens Android's own list of this keyboard's languages, where each one can be ticked or unticked. See the callout below for when you actually need it. Registering a language with Android and that language being *switchable* are two different things. Android only lists the subtypes it considers enabled, and if nothing has enabled any, it picks them itself from your phone's system languages, which normally means exactly one. That is why the switcher can show a single entry however many layouts you have on. Android 14 added an API that lets a keyboard enable its own subtypes. On Android 14 and newer, WM Keyboard enables all of them for you and the switcher lists the lot with nothing to configure. Below that there is no such API. Open **Select languages in the system settings**. Tick each language you want in the sheet. A separate, unrelated toggle, **Remember language per app** (under Per-app language, off by default), has the keyboard remember whichever layout you last explicitly switched to in a given app and restore it the next time you type there. Suggestions and autocorrect switch along with it, to that language's words and its own **Also suggest from** list. Apps you haven't switched languages in yet follow whatever you last used globally, and a field that demands Latin input or advertises its own language still overrides the memory either way. Removing a layout from your list also drops it from every app that remembered it; those apps fall back to your last-used layout. To decide for one app yourself, press **Set the language for an app** in the same group, pick the app, then pick a language or **Remember last**. The rule wins over the toggle for that app, and **Remember last** works there even with the toggle off. You can still change language while you type in the app; the rule applies again the next time you come back from another app. **Forget remembered apps**, further down the Languages screen under Language data, clears that whole map after a confirmation. The row only appears once something is in it, and its subtitle counts how many apps have a language pinned. The feature itself stays on, so apps start being remembered again as you switch language inside them. **Link romanized languages automatically** sits in the same group and is on by default. It pairs a romanized language such as Banglish with the languages that share its script, so suggestions come from both. Adding a language only makes that language's own links, so a link you removed by hand between two languages already on your list stays removed. Turn it off and adding a language links nothing. ## Switching mid-word onto a phonetic layout Languages & layouts / Switching language / Carry the word over Phonetic layouts spell their script with roman letters: Avro and Khipro write Bengali, Hindi phonetic writes Devanagari, and Urdu phonetic and the [other phonetic layouts](https://wmkeyboard.pages.dev/languages/phonetic/) write theirs. Which means the letters you type on your Latin keyboard are the very letters those layouts take, and noticing halfway through a word that you never switched no longer costs you the word. **Carry the word over** is on by default. Type `tu`, switch to Avro, and what you already typed becomes তু; the `mi` after it finishes তুমি. Without it the `tu` commits as Latin and only the `mi` is Bengali, so the whole word has to be typed again. The letters are converted whether they were still being composed or already sitting in the field, and their capitals are dropped on the way, because the phonetic layouts spell retroflexes and aspirates with capitals and the `T` of a sentence-opening `Tumi` was not asked for as ট. A word with a digit, an apostrophe or a hyphen in it is left alone, as is anything longer than 32 letters, and so is a password field. One case argues the other way, and is why the setting exists: finishing an English word, *not* typing a space, and switching to write the next word in Bengali. That word is carried over too, so `hello` becomes হেল্লো. Nothing distinguishes it from the `tu` above (in both, the caret sits after a run of roman letters), so if that is how you switch, turn this off and switches commit the word as they always did. Only the phonetic layouts take a carried word. Switching to Probhat, Jatiya, a Latin keyboard or a Chinese or Japanese input method commits the word as it stands, since none of them spells with the letters you just typed. ## Switching from another app or a computer Automation apps such as Tasker, and `adb` on a computer, can switch too. They send an intent that names a layout or a language, or asks for the next or previous one, and the keyboard switches as if you had pressed 🌐. Only layouts you turned on can be reached this way. It is off until you turn on Privacy / Let other apps control the keyboard. See [Automation: adb and Tasker](https://wmkeyboard.pages.dev/reference/automation/) for the intents and a Tasker example. ## Options Languages & layouts | Setting | Where | Default | |---|---|---| | Quick swipe on the spacebar | Typing → Gestures | Language | | Press and hold, then swipe | Typing → Gestures | Cursor | | Arrows on the spacebar | Typing → Gestures | On | | The spacebar shows | Typing → Gestures | Language | | Language picker shape | Typing → Gestures | List | | Spacebar text | Typing → Gestures | Blank (live name) | | Typing speed on the spacebar | Typing → Gestures | Off | | Spacebar long press keys | Typing → Gestures | Blank (the hold opens the picker) | | Show the 🌐 key | Layout & size → Bottom row keys | On | | 🌐 goes back to the last language | Layout & size → Bottom row keys | Off | | Ignore 🌐 right after typing | Layout & size → Bottom row keys | Off (50–1000 ms when on) | | Slide from 🌐 for shortcuts | Key press → Press and hold shortcuts | Off | | Emoji key instead of 🌐 | Layout & size → Bottom row keys | On | | Comma key opens emoji | Layout & size → Bottom row keys | Off | | Your languages / Switch order | Languages | Seeded from your phone's language list, then QWERTY, capped at 4 layouts | | Carry the word over | Languages → Switching language | On | | Extra stop in the switcher | Languages → Switching language | None | | Remember language per app | Languages → Per-app language | Off | | Forget remembered apps | Languages → Language data | A button, shown once apps are remembered | | Link romanized languages automatically | Languages → Language data | On | | Do not show in the language switcher | Languages → any language | Off | | List languages in the Android switcher | Languages → System language switcher | Off | | Show app name first | Languages → System language switcher | Off | ## Details & edge cases - **A fresh install shows an emoji key where you might expect the globe.** It's the most common point of confusion with switching, and the callout above covers it. Switching itself is never disabled by the setting. Only the dedicated key's icon and behavior change. - **The globe key's tap cycles layouts, not languages.** If a language has three enabled layouts, tapping the key visits all three as separate stops rather than skipping straight to the next language. The same goes for the recently-used order: each layout is its own entry. - **With exactly two layouts enabled, swiping toggles instead of wrap-cycling.** Piling on more drag distance in the same direction won't toggle a second time. Reverse direction and you start a fresh toggle. (The count that matters is enabled layouts, so two layouts of the same language toggle too.) - **A held-but-unmoved spacebar or globe-key long-press leaves the picker open for a direct tap** rather than dismissing it, list and carousel alike. There's no need to drag precisely if you'd rather tap the language you want. - **Reordering affects layouts, not languages.** If you've added a second layout for the same language (AZERTY alongside QWERTY, say), Switch order treats them as independent stops, and moving one doesn't move the other. - **A subtype that Android hasn't enabled stays invisible.** Registering is ours to do and enabling is Android's, and before Android 14 only you could bridge the gap. If the system sheet lists one language on an older phone, nothing is broken. Those languages just haven't been ticked in system settings yet. - **The Android system switcher and per-app memory are independent settings.** You can have Android list every layout as a subtype while per-app memory stays off, or the reverse. Turning one off doesn't touch the other. --- # Urdu URL: https://wmkeyboard.pages.dev/languages/urdu/ Section: Languages Summary: Type Urdu the way you text it: roman letters in, Urdu script out. Plus the Urdu-lettered keyboards for typing the script directly. Urdu ships five layouts. **اردو** is the phonetic *key* arrangement (Urdu letters sitting where the roman letter that sounds like them would be), and it is what you get when you add Urdu. **Urdu phonetic** is the new one (issue [#496](https://github.com/wasi-master/wmkeyboard/issues/496)): a QWERTY grid that turns roman Urdu into the Urdu script as you type, so `aap kaise ho` commits آپ کیسے ہو. **کلیدی تختہ (NLA)** is the national standard keymap, **اردو (four rows)** spreads the letters over four rows, and **اردو T9** is the keypad. This page is mostly about the phonetic one, because it is the one with moving parts. ## Turning it on Languages & layouts / اردو · Urdu / Layouts / Urdu phonetic Add Urdu, open its screen, and tap the **Urdu phonetic** card under **Layouts** to turn it on. Tap the اردو card on the same screen to turn that one off if you don't want both. Then do the one thing that makes the biggest difference: download the Urdu word list from the **Dictionary** row on that screen. Urdu ships no word list inside the app, and the next two sections explain why this layout cares about that more than most. ## How it reads what you type Every key is where it is on the English layout. What you type shows up in the Urdu script in the composing text straight away, with no separate conversion step, and the space commits it. The keys stay left-to-right while the text runs right to left, which is how every roman-key method for an RTL script works. | You type | You get | Why | |---|---|---| | `din`, `kal`, `tum`, `mard` | دن, کل, تم, مرد | a short vowel is a diacritic nobody types, so it writes nothing | | `ka`, `ki`, `ke`, `hai`, `to` | کا, کی, کے, ہے, تو | a vowel that ends a word is long, because Urdu ends a word on a letter | | `ab`, `aap`, `ek`, `aur` | اب, آپ, ایک, اور | a vowel that opens a word takes its carrier: ا, آ, ای, او | | `koi`, `hua`, `jaao` | کوئی, ہوا, جاؤ | a vowel running into a vowel takes a hamza | | `yeh`, `woh`, `kya`, `liya` | یہ, وہ, کیا, لیا | …but a ی or و that is a *consonant* carries nothing | | `dekh`, `likh`, `phir`, `ghar` | دیکھ, لکھ, پھر, گھر | aspiration is do-chashmi he: ھ | | `khush`, `khabar` | خوش, خبر | `kh` opening a word is خ, not کھ | | `sunna`, `pakka`, `iss` | سنا, پکا, اس | a doubled consonant is written once: Urdu doubles with a shadda nobody types | | `accha` | اچھا | …and a doubled `c` is the aspirate, which is the word people mean | | `Theek`, `kaRa`, `DaDa` | ٹھیک, کڑا, ڈڈا | capitals spell the retroflexes exactly | | `Khabar`, `GhalaT`, `SubH`, `Zaroor` | خبر, غلٹ, صبح, ضرور | and the Perso-Arabic letters: خ غ ح ص ض ط ظ ق | | `Kitab` | کتب | a capital that spells nothing reads as its lowercase self | ### Urdu phonetic key map **Vowels.** Urdu has no vowel signs. A vowel is a letter, a diacritic, or nothing at all, so the form depends on where in the word it falls. | Key | Opening a word | Inside a word | Closing a word | |---|---|---|---| | `a` | ا | nothing | ا | | `aa`, `A` | آ | ا | ا | | `i` | ا | nothing | ی | | `ee`, `ii`, `I` | ای | ی | ی | | `u` | ا | nothing | و | | `oo`, `uu`, `U` | او | و | و | | `e`, `ay`, `ey` | ای | ی | ے | | `ai` | ای | ی | ے | | `o` | او | و | و | | `au`, `ou` | او | و | و | | `ua` | او ا | وا | وا | | `ui` | اوئی | وئی | وئی | **Consonants.** | | | | | | |---|---|---|---|---| | `b` ب | `bh` بھ | `p` پ | `ph` پھ | `f` ف | | `t` ت | `th` تھ | `T` ٹ | `Th` ٹھ | | | `j` ج | `jh` جھ | `ch` چ | `chh`, `cch` چھ | | | `d` د | `dh` دھ | `D` ڈ | `Dh` ڈھ | | | `r` ر | `R` ڑ | `Rh` ڑھ | `l` ل | | | `s` س | `S` ص | `sh` ش | `z` ز | `Z` ض | | `k` ک | `kh` کھ / خ | `q` ق | `g` گ | `gh` گھ | | `Kh` خ | `Gh` غ | `h` ہ | `H` ح | `zh` ژ | | `m` م | `n` ن | `N` ں | `v`, `w` و | `y` ی | | `'` ع | `x` کس | `c` ک | | | `N` is the nasalisation ں, which the rules never guess at (جان and ہیں are both "n" in roman), so type it when you want it, or let the word list settle it. A capital that isn't in these tables reads as its lowercase letter. ### Why Urdu is harder than Avro or Hindi phonetic Urdu's script does not write its short vowels. کتاب has no letter for the "i" of `kitab` and دن none for the "i" of `din`, so this transliterator writes *fewer* letters than it is given, the opposite of its Indic cousins, and the letters it does write are only the long vowels. Which leaves a pile of things roman spelling does not carry: - whether a lone `a` is a long ا or nothing at all (`kitab` is کتاب, `karna` کرنا); - whether a `t` is ت or ٹ, a `d` د or ڈ, an `r` ر or ڑ; - which of the Arabic letters a loanword uses (`khas` is خاص, `zaroori` ضروری, `galat` غلط, `subah` صبح); - whether a final `a` is ا or the ہ of a Perso-Arabic noun (`bacha` is بچہ, `karna` کرنا); - whether a final `n` is ن or the nasalisation ں (`jan` is جان, `hain` ہیں). The layout handles that in three layers, in this order. 1. **A list of known spellings**, about 580 of them. The commonest words outright (`hai` is ہے, `hain` ہیں, `mein` میں, `kuch` کچھ, `bahut` بہت, `acha` اچھا, `pani` پانی, `theek` ٹھیک), chat shorthand (`k` is کہ, `h` ہے, `nhi` نہیں, `kr` کر, `bht` بہت), and English loanwords written in the Urdu script (`school` is اسکول, `doctor` ڈاکٹر, `mobile` موبائل). A spelling on the list is settled there, and it shows in the composing text the moment you finish typing it. 2. **The Urdu word list, if you've downloaded it.** What you typed and every word on the list are both reduced to the word's consonant skeleton. That is how the Arabic script spells in the first place, and why it fits. The skeleton forgets every vowel, forgets aspiration (خ and کھ are one key), folds ت ٹ ط together, and د ڈ, ر ڑ, س ص ث, ز ذ ض ظ, ک ق, ہ ح. Words with the same skeleton are candidates, and what you *did* type ranks them: a vowel at the start or end of a word must find one written, a vowel inside it need not, and a capital that asked for ٹ or ق and did not get it counts heavily against a candidate. Habits are forgiven (a closing ہ read as "a", a ں left off, a long vowel typed with one letter), so `hai` finds ہیں as well as ہے, and `bacha` finds بچہ. 3. **The rules alone**, the table above. They write the literal reading: short vowels dropped, nothing retroflex, nothing Arabic. With the word list installed, layer 3 rarely decides anything. Without it, layer 3 decides every word that isn't on the list in layer 1, so expect `kitab` to read کتب and `pani` پنی until you download the list. That is why the strip also carries the other readings of what you typed: the same spelling with its last inner "a" long, with every one long, and with the first one long. Tap the one you meant. Every word in the downloadable Urdu list ships with a count of 1, so there is no "commoner word" for the keyboard to prefer when two of them fit what you typed equally well. It falls back on the shorter word, which is right more often than not and sometimes isn't. The spelling map in layer 1 is what covers the words you actually type most, and it is the thing to grow when something common comes out wrong. ## Glide typing Swiping over this layout draws a romanization, not an Urdu word, so the stroke is decoded against romanized spellings and the result is turned into the Urdu script. The known-spellings list is always there for that, and the **Urdu (Romanized)** word list (about 110,000 romanized spellings, in the language catalogue) widens it a lot if you download it. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/). ## Editing Backspace inside a word you're still typing removes one roman letter per press, and the Urdu redraws. Over text that's already committed it removes one code point, which on an Urdu word is one letter. ## Options Languages & layouts / اردو · Urdu - **Layouts**: اردو, Urdu phonetic, کلیدی تختہ (NLA), اردو (four rows) and اردو T9, one card each, turned on and off independently. - **Key hints**: Languages & layouts / Urdu / Key hints / Show the letter each key types. Off, Added, or Whole letter. It puts the Urdu letter a key is about to write in its corner. The same shared setting Avro and Hindi phonetic use, and it shows only while a transliterating layout is active. - **Neural suggestions**: Languages & layouts / Urdu / Suggestion strip / Neural suggestions, off by default, shown while Urdu phonetic is on. Up to three readings from IndicXlit, a small transliteration model, right after the word the space bar writes, which never changes. It is the strongest help for Urdu's unwritten short vowels: on test words, the right word reaches the strip's first four places 73% of the time instead of 43%. A 13 MB download shared by every language that has it, plus ONNX Runtime. See [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions). - **Spellings**: Languages & layouts / Urdu / Spellings / Use known spellings, on by default. Switch it off for the letter-for-letter reading, which on Urdu means a lot more wrong guesses than on the other two. - **Dictionary words**: Languages & layouts / Urdu / Spellings / Correct to dictionary words, on by default. Off, space commits exactly what the rules spell instead of a word from the list that matches its skeleton. - **The words before**: Languages & layouts / Urdu / Spellings / Use the words before, on by default. One spelling can stand for several words, and the one or two words you typed just before pick between them, on the strip and for what space writes: "kam" is کام after "mera" and کم elsewhere. The evidence is your own typing and, where the language has one, the downloaded word-pair data. A known spelling only trades places with another known spelling of the same buffer, so the composing text is still what space writes. Turn it off if a word comes out differently after certain words than you expect. - **Also suggest from**: with English picked, the phonetic layout's strip also carries the word as you typed it in roman letters, and an **English words** group appears below it. See [English on Avro and Hindi phonetic](https://wmkeyboard.pages.dev/smart/mixed-language/#english-on-avro-and-hindi-phonetic), which works the same way here. - **Numerals**: `۰-۹` or `0-9`. - **Dictionary**: the downloadable Urdu list has about 155,000 words. You can import your own as well, and imported words are found through the same skeleton. The skeleton index over the downloaded list comes ready-made from the data repository (`ur_phonetic_v1r1.wmpi.gz`, about 1.5 MB, fetched right after the list) and is read in place from storage, so the keyboard no longer builds it in memory at every start. Only your imported words are still folded on the phone. ## Details & edge cases **The اردو layout is also called "phonetic", and is a different thing.** Its keys are Urdu letters placed where the roman letter that sounds like them sits: a key *arrangement*. **Urdu phonetic** has roman letters on the keys and writes Urdu. Both are on Urdu's screen; turn on whichever you meant. **`kh` is خ at the start of a word and کھ everywhere else.** Both readings are everywhere in Urdu (خوش, خبر, خدا against کھانا, کھیل), but the aspirate is far commoner at the end of a word, where Urdu's own verbs put it (دیکھ, لکھ, رکھ, آنکھ), and the Perso-Arabic letter is commoner at the start. `Kh` is خ wherever you type it. `gh` goes the other way and stays گھ, because گھر alone outweighs the غ words; `Gh` is غ. **The composing text can change its mind as you type.** `k` is ک, `ka` is کا with the ا that closes a word, and the `r` of `kar` takes that ا straight back out again (کر). What you see at each point is what a space would commit at that point. **The phonetic machinery only loads if the layout is on.** Urdu with just the اردو keyboard enabled builds none of it. **There are no selection-bar chips for Urdu yet.** Bangla and Hindi have **To Bangla** / **To Banglish** and **To Hindi** / **To Hinglish** on the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/) for converting text you already have. Urdu has the machinery behind them (both directions are implemented) but no chip of its own; ask on the issue if you want one. **It's new, and both tables are a draft.** The rules, the spelling map and the English loanword list were written without a native speaker on hand, and measured against the word list rather than reviewed: the rules alone get about 62% of a 176-word test set exactly right, and the skeleton index puts the right word first for 44% of them and in the first three for 73%. If a common word comes out wrong, [say so on the issue](https://github.com/wasi-master/wmkeyboard/issues/496) and name the word and the spelling you typed. --- # Vietnamese URL: https://wmkeyboard.pages.dev/languages/vietnamese/ Section: Languages Summary: Type Vietnamese with Telex or VNI: plain keys in, accented letters and tones out. How the tone lands, how to take one back, and how English words survive. Vietnamese ships four layouts. **Tiếng Việt Telex** is what you get when you add Vietnamese, and it spells every mark and tone with ordinary letters. **Tiếng Việt VNI** does the same with digits. **Tiếng Việt** is a plain QWERTY with the accented letters on long-press, and **Tiếng Việt T9** is the keypad (see [Ambiguous layouts](https://wmkeyboard.pages.dev/languages/ambiguous/#thai-lao-and-vietnamese)). This page is about Telex and VNI, because they're the two that build letters out of keys. ## Turning it on Languages & layouts / Tiếng Việt · Vietnamese / Layouts Add Vietnamese and Telex is on. Tap the VNI card on the same screen if that's the method you learned, and turn Telex off if you don't want both in the switch ring. Then download the Vietnamese word list from the **Dictionary** row. Telex and VNI type fine without it, but the list is what gives you suggestions, lets you go back into a word you already typed, and, if you ask for it, [puts back a mark you missed](#filling-in-what-you-left-out). ## Telex The word you're typing shows up already composed, marks and all, and the space commits it. | You type | You get | | |---|---|---| | `aa`, `ee`, `oo` | â, ê, ô | circumflex | | `aw` | ă | breve | | `ow`, `uw` | ơ, ư | horn | | `w` on its own | ư | `tw` is tư | | `uow` + a final consonant | ươ | `nuocsw` is nước, `huowng` is hương | | `dd` | đ | | | `s`, `f`, `r`, `x`, `j` | sắc, huyền, hỏi, ngã, nặng | `tieengs` is tiếng | | `z` | takes the tone off | `toansz` is toan | The tone key can go anywhere after the vowels, and it doesn't have to be last. `tieengs` and `tieesng` both give tiếng, because the keyboard puts the tone on the right vowel by the usual rule rather than on the letter you typed it after. So `hoafng` is hoàng, and `thuowr` is thuở, since a vowel with a mark of its own takes the tone. A mark key doesn't have to come straight after its letter either. `dod` is đo, `tono` is tôn, and `nuocsw` is nước: the key looks back for the letter it names, as long as the word it makes could still be Vietnamese. `hello` has no such letter and stays as it is. `banana` does, and comes out as bânna unless [Only tone real syllables](#only-toning-real-syllables) is on. ### Taking a mark back Press the same key again and the mark comes off and the letter goes in instead. `hass` is has, `aaa` is aa, and `oww` is ow. For a tone there's also `z`, which removes the tone and types nothing. With no tone to remove, `z` is just a z. A `w` on its own is ư, and a second `w` turns it back into w. Every `w` after that is a w as well, so holding the key down types a run of them instead of flipping between ư and w. ### `uo` and `ươ` `huơ`, `quơ` and `thuở` are written with a plain `u`, while `hương`, `người` and `đường` horn both letters. They're the same keys until the end of the word, so the keyboard decides once it can see what follows: - `huow` is huơ, and `huowng` is hương, because a final consonant makes it ươ. - `nguowif` is người, because ng can't come before an open uơ. - After `qu` the u belongs to the qu, so `quow` is quơ and `quowng` is quơng. - `ww` after `uo` horns the u too, if you want ươ and the keyboard picked uơ. ### English on Telex Telex reads letters as marks, which gets in the way of English typed on the same keyboard. Two things keep the common cases readable: - A word that starts with `w` keeps its w when what follows can't come after ư. `why`, `when`, `we`, `with` and `web` stay English. - A doubled key is always an escape hatch: `hass` is has, `oww` is ow. For anything more, turn on the setting below, or switch to the plain **Tiếng Việt** layout for a stretch of English. ### Only toning real syllables Languages & layouts / Tiếng Việt · Vietnamese / Tone marks / Only tone real syllables Off by default. Telex normally puts a tone on anything with a vowel in it, so `fas` becomes fá even though no Vietnamese word starts with f. Turn this on and a tone key only marks a word that Vietnamese could actually spell. Anything else gets the letter instead: `fas` stays fas and `fix` stays fix. A word that picked up a mark but turned out not to be Vietnamese goes back to the keys you typed, so `banana` stays banana. It goes by Vietnamese spelling rather than by guessing at English, so `mix` is still mĩ, because that's a word. Abbreviations with no vowel keep their đ, so `ddc`, `ddt` and `ddh` still give đc, đt and đh. The setting covers VNI too. ## VNI VNI is the same engine with digits for the marks. | You type | You get | |---|---| | `a6`, `e6`, `o6` | â, ê, ô | | `a8` | ă | | `o7`, `u7` | ơ, ư | | `d9` | đ | | `1`, `2`, `3`, `4`, `5` | sắc, huyền, hỏi, ngã, nặng | | `0` | takes the tone off | A digit with nothing to mark is typed as the digit, so `banana1`, a street number or a model name keeps its numbers. `0` only does something when the word has a tone. Otherwise it's a 0. ## Backspace Backspace takes off the last letter you can see, not the last key you pressed. `huowngs` is hướng, and one press leaves hướn with the tone still on the ơ. `hif` is hì, and one press takes the whole ì, tone included. ## Going back into a word Put the cursor at the end of a Vietnamese word you already typed and you can carry on typing it. A tone key typed there marks that word rather than being added as a letter, so coming back to the end of `chao` and typing `f` gives chào. This needs a word list on the device (the same condition as for any other language), and it only kicks in for a word the keys could have typed. A word with marks Telex or VNI can't produce is left alone. ## Filling in what you left out Languages & layouts / Tiếng Việt · Vietnamese / Tone marks / Fill in left-out marks Off by default, because lots of people type Vietnamese without marks on purpose. Turn it on, with autocorrect on and the Vietnamese word list downloaded, and a word typed without its marks gets them back when the list has only one way to read it. `tieng` becomes tiếng, `khong` becomes không, `duong` becomes đường, and `ngieeng` becomes nghiêng. One backspace takes the correction back and leaves the word as you typed it. Two things it never does. A word the list already knows is never changed, so a real word with the wrong tone stays as typed. And a spelling with several readings is left alone: `toan` could be toàn, toán or toản, so the keyboard doesn't pick one. --- # Autocorrect URL: https://wmkeyboard.pages.dev/smart/autocorrect/ Section: Suggestions & correction Summary: Confidence-gated corrections that know when not to fire. Autocorrect fixes typos as you finish typing a word, but only when one candidate is clearly the right fix. A fat-fingered slip that's obviously "world" gets corrected. A word that sits between two real words is left alone for the suggestion strip to offer instead. This page is about that confidence gate, and about everything built around it: reverting a correction, the words autocorrect never touches, and the auto-space rules that ride along with it. smart/autocorrect-confidence-fix — A mistyped word corrected inline as the next word begins. ## How a correction is chosen Space and Enter trigger autocorrect. A full stop or other punctuation does not by default: ending a word with one commits it exactly as typed. Turn on **Correct the word before punctuation** and the marks that end a word (`.` `,` `!` `?` `;` `:` and the Bengali `।`) correct it the way space does, so `teh!` lands as `the!`. A slash, a bracket or any other symbol still leaves the word alone, and one backspace straight after the mark (and the space the auto-space rule put behind it) puts the word back as typed. Enter often sends the text as well, in a search box or a chat, so a correction it makes goes out before you can read it. Turn off **Correct the word before Enter** (see [Options](#options)) and Enter commits the last word as typed, while space keeps correcting. When you press space or Enter, WM Keyboard checks the word against every word one edit away: one letter dropped, added, doubled, swapped with its neighbour, or substituted. A word that is one letter short of a longer word ("hell" for "hello") counts as that same single edit. Two-edit candidates reach the suggestion strip but are never applied silently. Two independent sources score the candidates: the word lists (bundled, downloaded, imported, and any secondary language's) and your own personal dictionary of learned words. - **Two sources agreeing is enough on its own.** If the dictionary's best guess and your personal dictionary's best guess land on the exact same word, it's applied. The confidence factor below isn't even consulted. - **Otherwise, the best candidate has to clearly win.** It's applied only if it outscores the runner-up by the confidence factor (4× by default, see [Options](#options)). A close call is left alone rather than guessed at. With no runner-up at all, the candidate still has to be a reasonably common word: an unknown word one slip away from a rare one is left as typed. Every word list is measured on the same scale for this, so a downloaded list, whose word counts run into the millions, is no quicker to correct than the built-in English one. - **Two letters the wrong way round get the benefit of the doubt.** When no word clearly wins, a word that is what you typed with one neighbouring pair of letters swapped back is applied anyway. It must not be more than twenty times rarer than the best candidate, and no other swap may read about as well. So `wnat` becomes *want* even though *what*, a slipped `h` away, is the commoner word, `adn` becomes *and*, and `soemthing` becomes *something*, while `hwo`, which could be *who* or *how*, stays as typed. A swap keeps every letter you typed, which no other kind of slip can claim. This holds with the learned gate on or off, at every confidence setting but the most careful, 10×. - **The edit itself is weighted by how it happens.** Cheapest, and so likeliest, are three: a swapped pair of letters, a doubled letter (the hold-repeat slip in "helllo"), and a substitution toward a key right next to the one you meant. A dropped letter, or a stray one typed next to where your finger landed, costs noticeably more. A substitution or an insertion nowhere near the key you hit is the most expensive of all and rarely survives. The neighbour test uses the adjacency map for your active layout, so the same typo resolves differently on QWERTY than on AZERTY or Dvorak, and where the keyboard has your real tap coordinates it uses those instead of the map. - **A missing accent is not a typo.** Typing `z` where the word has `ż`, `e` for `é`, or `l` for `ł` costs almost nothing and does not count as an edit, because the accented letter is on the same key. So "juz" becomes "już", and a word with every accent left off ("zolw" for "żółw") is found as easily as a word with one. The strip agrees: when the letters you typed spell a word once its accents are back, that word comes first, ahead of commoner words that would need a letter changed or dropped, the way a word you typed in full leads its own slips. Portuguese "chale" offers *chalé* before *chave*, and "caminhao" offers *caminhão* before *caminho*. Turkish's dotless `ı` counts as an accent on `i`, along with `ş`, `ğ`, `ç`, `ö` and `ü`, so "haklisin" becomes "haklısın" and "guzel" becomes "güzel". Since 0.6.4 the same goes for any other letter on the key you pressed, the one its Shift types, the one in its corner or one behind its hold, in any script. On the Arabic layout `أ` and `إ` sit behind `ا`'s hold and `ؤ` behind `و`'s, so "اخبار" finds "أخبار" and "مسوول" finds "مسؤول" as surely as "juz" finds "już", and the same rule decides whether space puts them in. It runs one way only: a letter you reached with Shift or a hold is taken as meant. Marks such as the harakat on Arabic's Shift layer don't count, since a mark never stands in for a letter you tapped. - **The fix keeps your capitalization.** Typing "hte" mid-sentence corrects to "the". Typing "Hte" at the start of one corrects to "The." - **A word the list spells with a capital gets its capital.** Some downloaded word lists write words with a capital letter: every German noun ("Haus", "Zeit"), and the names of places and people. Type or swipe one in small letters and it is written with its capital, in the strip and when space commits it. A word the list has both ways ("essen" and "Essen") stays as you typed it. Backspace right after puts back what you typed, and the undo is remembered like any other. This is **Capitals from the word list** (see [Options](#options)). The built-in English list is all small letters, so the keyboard carries the capitals for it separately: days and months ("Monday", "January"), countries, cities and languages ("London", "English"), common first names, brands such as "iPhone", and acronyms such as "DNA" and "USA", about 2,400 in all. The word "I" is among them, so a lone "i" is written "I" even with **Fix missing apostrophes** off. A word English also uses in small letters, such as "may", "will" or "mark", is not among them. A downloaded English list adds its own on top. A capital you keep on a word in your personal dictionary, or wrote into Android's, is applied the same way (see [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#capitals)). A word your personal dictionary learned in small letters, the way you happened to type it, doesn't count as keeping it small, so a learned "i" never outvotes "I". A list you downloaded before this existed is converted on the device the next time the keyboard starts. The two candidates have to be far apart in score, which keeps autocorrect conservative by design. "helo" is evenly split between "hello" and "help", and "tesr" between "test" and "tear", so both are left for you to resolve yourself. An unambiguous slip like "wprld" resolves immediately. ## The learned gate The rules above are how the decision used to be made, and still are with **Learned correction gate** (see [Options](#options)) turned off. With it on, which is the default, the same candidates reach a single judgement instead of a chain of rules: a small model fitted offline that weighs everything at once and answers one probability, that the best candidate is the word you meant. What it weighs is what the rules weighed one at a time, and a few things they could not. The lead over the runner-up. What kind of slip explains the typo: a letter swapped with its neighbour, a tap a little wide of its key, a dropped letter, a letter no finger slip explains. How many of them, and how long the word is. Whether the word list and your own dictionary agree. What the [context model](https://wmkeyboard.pages.dev/smart/suggestions/#the-context-model) thinks, when one is downloaded: of the candidate, of what you typed as it stands, how many words it would put ahead of your spelling here, how sure it is of the next word at all, and how far its favourite among the candidates leads the next. How fast the word was typed, when **Typing rhythm** is on. Whether what you typed is a listed spelling the keyboard reads as a stand-in for another word. It was fitted on about nineteen thousand words from real sentences, typed by a finger that scatters around each key at three widths from careful to sloppy, with and without a context model in reach, and on words that had been taken out of the dictionary and typed right, which is what a name, a new word or a bit of slang looks like to the keyboard. Those last rows are the point of the exercise. Measured on sentences the fit never saw, the old rule rewrote 22 to 23% of such correctly typed new words; the gate rewrites under 1% of them on careful or fast typing and 2% on the sloppiest. It also fixes more of the real typos: on careful typing it applies 81% of them where the rule applied 60%, and the fixes it applies are right 99.4% of the time, against 99.7%. On fast typing recall goes from 50% to 67% and precision from 95.3% to 98.0%; on sloppy typing from 40% to 51% and 90.3% to 96.2%. With the context model downloaded the gate reads its view too, and that is where it earns its keep: on the same sentences it then applies 84% of the real typos on careful typing at 99.7% precision, 72% at 99.4% on fast typing and 58% at 97.5% on sloppy typing, and it rewrote none of the correctly typed new words at any width (0 of 300, 300 and 94). The **Autocorrect confidence** slider still rules it. The probability a fix has to reach is the slider's ratio against one: at the default ×4 a fix needs four chances in five, at ×10 ten in eleven, at ×1.5 three in five. A fix that falls short of that bar but clears about six tenths of it is offered as a chip rather than applied, as before. Everything else on this page holds under the gate: a known word is never corrected, a rejected pair stays rejected, a blacklisted word is never a target, and backspace undoes a fix the same way. The one rule that changes is two-source agreement: under the gate it is one of the things weighed rather than a bypass, since the fit found it a strong sign but not an infallible one. ## Words it never touches A word is never a correction target, whatever the confidence factor is set to, if any of these apply: smart/autocorrect-skip-allcaps — ALL-CAPS words pass through untouched next to a corrected lowercase one. - **It's shorter than three letters.** Too little signal to guess at safely. - **It's already a known word.** Anything in the active dictionary, a downloaded or custom wordlist, a secondary-language dictionary, or your own personal dictionary is left exactly as typed. A known word is never "corrected." There is one exception: a spelling that the word list holds only because people type another word without its accents. The Polish list contains "juz", but "już" is more than fifty times as common, so "juz" is corrected. When both spellings are common words, such as Polish "ze" and "że", the one you typed stays. The French list holds "cest", "jai" and "quil" as words for the same reason (real text is full of elisions typed without their apostrophe), and those are corrected the same way, to "c'est", "j'ai" and "qu'il"; Italian "cera" against "era" goes to "c'era" on the same terms, and so do the other eliding languages. The larger downloaded word lists also hold common typos, because the text they are counted from has typos in it: the Medium English list holds "wheee", "thw" and "teh". A spelling is treated as one of those typos when two things are true. First, it is further down its list than the 50,000 words the Small download keeps. Second, a word one slip away is at least a thousand times as common. So "wheee" is corrected to "where" and "thw" to "the", on any download size. A real word that is only rare, such as "cress" next to "dress", is left as typed. A word you added to Android's personal dictionary is never treated this way. A word nothing recognises doesn't become known just by being typed once: it has to be typed and left alone three times before it joins your dictionary, which is what stops a typo shielding itself from the fix it needed. See [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn) for that, and **Learn a word after** under Typing / Suggestions for a further threshold on top of it. - **It's an "-s", "-ed" or "-ing" form of an English verb the word list knows.** The bundled English list leaves out some verb forms. It has "corrected" but not "corrects" or "correcting", and "fixed" but not "fixes". So a form like "corrects", "correcting", "fixes", "carried" or "hoping" counts as a word whenever the list holds the verb along with another of its forms. That other form is the proof it is a verb: "thes" ends in "s" too, but no list holds "theed" or "theing", so "thes" is still corrected to "the". For "-ed" and "-ing" the proof has to be the other one of the two, because nouns take "-s" as well. Misspellings of a form the list does have are still corrected: "admited" next to "admitted", "fixs" next to "fixes", "stoped" for "stopped", and "begined", because "begin" has no "-ed". This only applies while English is part of your language mix. - **It's a contact's name or an installed app's label**, if [contact and app-name suggestions](https://wmkeyboard.pages.dev/smart/contacts/) have indexed it. - **It's in your [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), or offensive-word filtering catches it.** A blacklisted or filtered word is dropped from the candidate pool before scoring, so it can never be silently forced in as a fix. - **It's typed in ALL CAPS.** Acronyms and shouted words (ASAP, NOOO) are usually capitals on purpose. This is its own toggle, on by default (see [Options](#options)). A leading capital on an otherwise-lowercase word ("Wprld") is still corrected normally; only a fully-capitalized word is spared. - **The active language isn't English and has no bundled dictionary of its own.** The bundled English dictionary feeds autocorrect only while your active language is English, or has English configured as a secondary language. Type in French, German, or another language and corrections come from that language's own dictionary and whatever you've personally taught the keyboard. - **The field is a password field, or any structured field**: email, web address, number pad, phone pad. Autocorrect, like the rest of typing intelligence, is off in all of them. Missing apostrophes ("thats" → "that's") are fixed by a separate lookup that runs before autocorrect even looks at the word, so it's a known-word fix rather than a confidence-scored guess, and it wins whenever both could apply. The word with its apostrophe also leads the suggestion strip while you type, so what the strip shows is what the space bar is about to commit. Anything that ends a word applies it: the space bar, enter, and the full stop, comma, question mark and their family. English reads a fixed table of contractions. The spellings that are also words of their own, such as "id", "its", "were" and "well", are never rewritten, but the contraction ("I'd", "it's", "we're", "we'll") is offered on the strip right behind the word you typed, one tap away. Where the contraction is the commoner of the two, as "I'd" is next to "id", the word you typed is held up to its standing, so the pair stays on a three-slot strip ahead of completions such as "idea" and "identify". A glided or two-thumb word gets the same offer: swipe "id" and it is typed as "id", with "I'd" right behind it. "ill" is the exception: it is typed for "I'll" far more often than for the word, so it becomes "I'll" like the rest of the table. If you did mean "ill", one backspace right after puts it back, and from then on "I'll" is only offered behind it. German reads a table of its own for the *es* worn down to *'s* on a verb: "Wie gehts" becomes "Wie geht's", and "gibts", "habs" and "wars" take their apostrophe the same way. German words that only look like one, such as "wies" and "nichts", are left alone, and so are "aufs", "ins" and "fürs", which German writes without it. Seven languages that elide get the same fix from their word list instead of a table: French, Italian, Catalan, Occitan, Romansh, Corsican, Sardinian and Piedmontese: "cest" becomes "c'est", "lalbero" becomes "l'albero", "unamica" becomes "un'amica", "lhome" becomes "l'home", "lostal" becomes "l'ostal" and "sistadu" becomes "s'istadu". The keyboard knows which prefixes elide and which short words each one takes, so French "jet" is never split into "j'et" and Italian "sono" never into "s'ono". That table is deliberately thinner than a grammar book would write it, because a one-letter prefix in front of a short word usually spells a word: Occitan "ma" and "ta" are "my" and "your", Corsican "se" and "sa" are words in their own right, and each table was swept against its language's full word list to find them. A real word that happens to split, such as French "lune" or Italian "allora", stays as typed; "l'une" and "all'ora" sit behind it on the strip instead. It has its own toggle, "Fix missing apostrophes," in the same settings group, and no other language is read this way. Every language in the current mix is asked, not only the one in front: with Italian and English set as each other's secondary, "thats" becomes "that's" and "lalbero" becomes "l'albero" whichever of the two you are typing in. The word after an elided prefix still has to be a word of the language whose grammar allowed the split, so an Italian "d'" never reaches across the mix to make "dart" into "d'art". The same goes the other way for the English table: it only knows which spellings are English words, so when the field is being written in another language of your mix and that language has the word you typed, the contraction is offered on the strip instead of committed. With German and English set up together, "im" stays "im" in a German sentence, with "I'm" one tap away behind it, and an English sentence on the same keyboard still gets "I'm". Which language the field is in comes from the language you have in front, or from [language detection](https://wmkeyboard.pages.dev/smart/mixed-language/) when it is on. Offensive-word filtering and the suggestion blacklist also keep those words out of the plain suggestion strip, not only out of autocorrect. See [Suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) for the full mechanism. Hyphenated words are one word while you type them. A hyphen straight after a letter stays in the word being composed, so "well-pai" completes to "well-paid" and "что-" goes on to "что-то", instead of the strip starting over on the part after the hyphen. A hyphen the word ends on is only punctuation: "well-" then space leaves "well- " as typed, and a second hyphen in a row is a dash that ends the word. Putting the caret back after a compound picks up all of it, not only the last part. A compound whose parts are all known words, such as "hello-world", is never corrected, even when no word list holds it whole. The other way round, a compound typed without its hyphen is offered with it on the strip: "wellpaid" shows "well-paid". That holds even when the word list counts the fused spelling as a word, as the big Russian list does with "чтото", as long as the hyphenated form is at least fifty times as common; "online" is never offered as "on-line". Being on the strip does not mean the space bar will write it: that is still autocorrect's call, made the same way as for any other word. ## Undoing a correction smart/autocorrect-backspace-undo — Backspace right after a correction restores what you originally typed. ⌫ right after an autocorrected word undoes it, as long as nothing else has touched the text since. The cursor has to be sitting exactly where the correction left it, and the text right before it has to still read as the corrected word plus the space or line break that ended it. Backspace then deletes both and retypes what you originally typed instead. Move the cursor, tap elsewhere, or keep typing first and the undo is gone. Only the immediate next backspace can trigger it. A missing apostrophe the keyboard filled in ("dont" → "don't") and a lone "i" it made into "I" are undone the same way, and remembered the same way below: after the undo, the next "i" stays lower case instead of being capitalised straight back, and the repair waits on the suggestion strip behind what you typed instead. One undo is enough for an apostrophe, for as long as that undo is remembered: a repair has no confidence to handicap the way a guessed correction does, so where a correction undone once only fights uphill, a repair undone once stops being made by itself at all. A capital from the word list, the lone "I" among them, is the exception: one undo holds it back for the rest of that run of typing, and only a second undo retires it. That way a single stray undo can't leave every "i" in lower case for months, and a capital you let stand afterwards buys the undo back the way a kept correction does. A capital retired by two undos can't fire to earn that back, so if "i" has stopped becoming "I", open [Learned corrections](#learning-from-your-own-fixes) and delete `i → I` under **Corrections you undid**. Set it to **Never** there if you want "i" left alone for good. The same word won't be "corrected" again next time either way: undoing a correction retires that exact pair as soon as you keep the word you typed, and a second undo of it blocks the pair for good. That part is unconditional, because a rejection has to hold even with learning switched off. The undo only counts once you keep your word, by typing on after it or leaving the field. Holding backspace to clear the text also starts with that undo, but the next presses delete the restored word too, so nothing is remembered and "i" still becomes "I" next time. Reverting also counts double towards teaching the original word to your personal dictionary, which is the strongest of the signals that feed [that count](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn). It doesn't save the word outright any more. Undoing a correction is also what you do when the fix simply wasn't the word you wanted, and saving on the first undo turned out to be the shortest path from one stray swipe to a misspelling stuck in your dictionary. This follow-on teaching only happens if [learning from typing](https://wmkeyboard.pages.dev/smart/personal-dictionary/) is on, which it is by default. It's skipped in [incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) (when incognito is set to pause learning) and in a secure field. One related behavior is easy to mistake for this toggle. Arming Shift live re-cases every chip in the suggestion strip, not only an autocorrect candidate, to match what would actually be committed if you tapped it. Shift is a latch rather than a hold here, so the chips stay re-cased until the shift state clears. That's a strip-wide display rule rather than a way to reject a correction. See [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for how it works. ## What counts as rejecting a correction Backspace is the fastest way to reject a correction, but it is not the only one, and for a long time it was the only one the keyboard could see. Fixing a word three words later, selecting it and retyping, or tidying the sentence before sending all rejected the correction just as clearly and taught the keyboard nothing. Worse, they counted as successes, so the adaptive gate read a better record than autocorrect was earning and loosened where it should have tightened. A correction is now judged once the text around it has settled, on the same signals the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn) uses: you close the keyboard, send or clear the field, move to another field, or simply type far enough past it. - **You never went back into that text.** The correction stands, and counts as accepted. - **You did go back into it.** The keyboard reads the field once, at that point only, and looks for which spelling is there now. Your original word standing where the fix was is a rejection. The corrected word still there is an accept. This reading counts for less than a backspace, and it is thrown away entirely when your word is not a word the keyboard knows. That case is the trap it exists to avoid: you glance back, decide you mistyped, fix it by hand without noticing the keyboard already fixed it, and make the same slip again. The field then looks exactly like a rejection while nothing was rejected at all. A word that really is yours, a name or a nickname, joins your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) after a few sightings, and from then on its undos count in full. - **Neither is there, or the text is gone.** No verdict at all. Deleting the sentence, or sending a message before anything could be checked, teaches nothing rather than something invented. The read only ever happens at one of those settling points, never while you are typing. ## Taking a correction back later Backspace only reaches a correction in the moment it happens. One more letter and it is out of reach, which is awkward, because that is usually when you notice it. The word looks right at a glance, you read the sentence back a beat later, and by then the only way out is to select the word and retype it. So the keyboard puts the word you typed on a chip at the start of the suggestion strip. Press it and your word goes back, and the keyboard reads that exactly as it reads a backspace: the pair is retired, and your word counts towards your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/). It shows between words rather than in the middle of one, because putting the word back means editing text behind where you are typing. The chip remembers the last five corrections that earned one, not just the latest. Slips tend to come in runs, so when autocorrect has changed three words in a sentence, three presses put all three back, newest first: after each press the chip shows the next word it can restore. There is no counter on it; the word it names is the next one it will put back. Before each press rewrites anything, the keyboard finds the corrected word again and checks it is still the one it changed: still spelled the same, still sitting after the same few words it followed when it was corrected. Words you typed after it don't matter, and neither do the ones the chip already put back. If you edited the word yourself, or changed the text just in front of it, that correction is quietly skipped and the press goes to the next one down, so the chip never rewrites a word it can't be sure of. The same word appearing twice is fine as long as the two stand after different words. Each correction also has to stay within about a hundred characters of where you are typing, and moving the cursor back past one drops it along with any newer ones. The chip and everything it remembers are cleared when you end the sentence, select text, or move to another field. A correction that doesn't earn a chip of its own (see below), or a fix that only adds a capital such as "i" becoming "I", leaves the remembered ones in place. Backspacing a correction away the moment it lands drops it from the chip too, and leaves the older ones alone. There's a second way back, one that outlasts the sentence. Every autocorrected word also carries a mark the app itself can read, naming the word you typed. In an app that supports it, this draws the same brief underline other keyboards use. Tap the word anywhere in the document, however long after, and it offers your original spelling back. It has no expiry, because it lives in the text rather than on the strip. Not every app reads the mark, so where it doesn't, the chip above is the only way back. Not every correction gets a chip, and that is the point. A chip after every correction is one more thing to look past, and by the time the one that matters arrives you have stopped reading the strip. So the keyboard scores each correction it applies on two things: - **How sure it was.** A correction that only just cleared the confidence gate scores low. One that both the dictionary and your own lexicon named scores high. - **How far it moved the word.** A short word with one neighbouring letter changed scores high, because you can see what happened at a glance. A letter no finger slip explains, or a word split into two, scores low. A correction that scores under the bar gets a chip. **How often to offer the undo** under Typing / Automatic corrections is that bar: at the lowest setting almost nothing gets a chip, at the highest every correction does. **Undo a correction from the strip**, directly above it, turns the whole thing off. A listed spelling far rarer than a word one letter away, like `verg` next to `very`, is never rewritten, but it gets a chip as soon as you press space. A real word that only the next word proves wrong, like `Thus` in "Thus bug", can't be caught before that word lands, so **Re-check the previous word** asks then. Each fix is offered once per field: pass over the chip and it doesn't come back. The chip shares its slot with the join, revision and near-miss chips, and comes last of the four. All four wear the theme's selected-chip colour, the same fill a highlighted suggestion gets. Those three are about the word you are typing now. This one is about a word you have already left behind. ## How long an undo lasts Undoing a correction does two things. It puts your word back, which is immediate and never in doubt. It also tells the keyboard something about that exact pair of words, and **Remember my undos** under Typing / Automatic corrections sets how long that second part lasts. Only the pair is affected. Other fixes for the same typo carry on as before. | Level | What an undo costs the correction | | --- | --- | | Off | Nothing. The keyboard corrects the word again next time. | | Light | The pair needs far more confidence from then on. It is never retired. | | Normal | One undo stops the pair for the rest of that run of typing. Two retire it, and a retired pair is offered as a chip once more after a long quiet spell. | | Strict | The first undo retires the pair for good, however the keyboard read it, and it is never offered again. | Normal is the default and the level the rest of this page describes. Under it, a backspace on the correction stops that pair for the next twenty corrections and until you move to another field, whichever comes first. After that the counts on disk take over: a pair undone once fights uphill, a pair undone twice does not fire at all. Corrections you let stand count too, and three of them buy back one undo, so a pair you undid in a hurry is not sentenced for the six months the expiry clock takes. A retired pair is not gone forever. Once it has sat quiet for about forty saves of the learning file, it is let out on probation: it can reach the suggestion chip again, though it still never rewrites your word on its own. Accept it three times and it is an ordinary correction once more. This is what stops a single bad undo from silently retiring a fix you wanted, with nothing on the screen ever saying why. Pick **Light** if you find the keyboard gives up on corrections too easily. Pick **Strict** if you want an undo to be final. **Off** keeps the undo itself and forgets everything else. ### When you keep deleting the same fix Backspace right after a correction is not the only way to say no to it. Often the correction goes unnoticed for a moment, and you delete it letter by letter or with a word delete, type your word again, and get the same correction again. **Stop after I refuse a fix** under Typing / Automatic corrections ends that loop. Every undo or deletion of a correction counts as one refusal for that word at that place. Once a place has collected the set number of refusals (2 by default, up to 5), the word you type there goes in as you typed it, and the fix is not offered on the strip either. It is a local truce, not a verdict on the word: the same typo anywhere else in the text is corrected as usual, nothing is saved, and the count is gone when you move to another field. Set it to **Never** to turn it off; **Remember my undos** above still applies either way. ## Learning from your own fixes Most typos autocorrect misses end the same way: you notice the word, go back, and fix it by hand. That fix used to teach the keyboard nothing. Now it teaches two things. **The pair.** The keyboard remembers what you typed and what you changed it to, once the text around the fix has settled the way learned words do (see [Words wait their turn](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn)). Fix the same typo a second time and autocorrect makes that fix for you from then on, with the undo chip showing the first few times so you can see it happen. After one fix it offers the fix on the strip instead of applying it, and it will not apply a different guess of its own over a word you have already shown it the answer to. If autocorrect had changed the word to the wrong thing and you fixed that by hand, the pair it learns starts from your original typo, and the wrong correction is marked as rejected. A typo that is itself a real word ("form" for "from") is held to a higher bar: three fixes, and the words before it have to agree that the other word was meant. Otherwise every "form" would turn into "from". A word you have put on **Never suggest** doesn't count as real here: you have already said you never mean it, so two fixes are enough, as for any typo. **The slip.** Each pair is also read letter by letter, and the difference is counted as a habit of your hands on this layout: a `3` typed for an `e`, a `b` where the space bar was, a doubled letter missed. Once a layout has taught eight of those, autocorrect treats the slips you make as cheaper, so the same slip in a word you never fixed is understood too. A habit never makes a correction fire on its own; it only changes which words are in the running, and the confidence gate decides as before. Habits are learned only from typos you typed yourself, never from a real word swapped for another, from a swiped word, or from a word picked off the strip. **Where your taps land.** Separately, every word you type and leave standing records where your finger landed on each of its letters, and later taps are read against keys moved to match. This is the tapping twin of the glide typing hand model, and it learns from kept autocorrects as well: a correction you let stand is the plainest statement of which letter a tap was meant for. The keyboard learns a fix in three situations: you backspace into a word you just committed and retype it, you tap into a word and edit it in place, or you select a word and type over it. It follows only the edits it makes itself, and checks the word against the field once, when you leave it. A learned fix you then undo, by backspace or the undo chip, counts against the pair, and a pair undone twice is retired exactly like any other correction. Everything learned here ages out on the same clock as the rest of the personal dictionary. **Learned corrections** under Typing / Automatic corrections lists everything above one entry at a time, and each entry can be changed on its own: - **Fixes you taught** shows every pair with how often you made it. Tap a pair to change either spelling or the count, or use **Add a fix** to write one the keyboard never saw. The count decides what happens, the same as if you had typed it: at 1 the fix is offered on the strip, at 2 it is made for you, and a typed word that is itself a real word needs 3 and the words before it have to agree. A fix you set here doesn't age out, and saving it forgives any undo of it. If you have taught two fixes of one spelling, neither is made until one has at least twice the count of the other, and the row says so. - **Corrections you undid** shows each correction you backed out of and what the keyboard does about it now. Tap one to choose **Only when very sure**, **Stop for now** (it comes back on the strip after a long quiet spell, never applied) or **Never**, or delete it to forgive it. **Stop a correction** adds one you never had to undo, for a word the keyboard keeps changing into another. Editing this list doesn't change the revert rate behind the confidence gate, and [Remember my undos](#how-long-an-undo-lasts) set to Off ignores the list. - **Slips the keyboard has noticed** shows each slip with its layout and count, since a slip belongs to where the keys sit under your hand. Tap one to change its count, or delete it. A higher count makes that slip cheaper, and a layout still has to reach eight slips in total before any of them count. **Forget all learned corrections** clears the fixes and slips at once. **Learn from my fixes** turns the whole thing off. **Learn where my taps land** turns the tap model off on its own, with its own Forget button underneath. Both stores travel with the Dictionary section of an export, and are deleted with the rest of the learned data. ## Corrections it offers instead of applying Applying a correction and offering one are not the same question. Applying has to be nearly certain, because being wrong rewrites what you wrote and you may not notice. Offering costs you a chip you can ignore. So everything that came close to the bar without clearing it, which used to be discarded silently, is now offered as a chip at the start of the suggestion strip instead. Tap it to apply the fix; carry on typing and it goes away. The bar for a chip is about a third of the bar for a silent replacement, and it moves with every knob above it: raise the confidence slider and both go up together. A correction you have already rejected once is not offered, because being asked twice is worse than not being helped. The one exception is a retired pair that has sat quiet for a long time: it comes back as a chip rather than staying invisible forever. See [How long an undo lasts](#how-long-an-undo-lasts). Turn the chips off with **Offer corrections it isn't sure about** under Typing / Suggestions. Tapping the chip is itself evidence that the gate is too tight for that pair, so it feeds back the same way an accepted correction does. Backspace undoes it like any other correction. ## Auto-space and punctuation Two related rules ride along with autocorrect and share its "sentence in progress" bookkeeping. Each has its own toggle: - **Double space inserts a period.** Type two spaces in a row within a fraction of a second and the first space becomes `. `. Deleting a space and retyping it "undoes" nothing here; a following [Shift](#shift-cancels-a-pending-auto-space) is what cancels it. A separate, off-by-default setting can make a double space insert a tab instead, which takes priority over the period rule when both are on. - **Punctuation auto-inserts the space after it.** Off by default. When it's on, a sentence-ending or clause mark inserts a trailing space automatically: period, exclamation point, question mark, comma, semicolon, colon, and the Bengali `।`. You don't need a separate spacebar tap before the next word. A mark typed straight after a digit is part of the number and gets no space, so `5,000`, `3.14` and `5:00` stay whole. A full stop inside an email address gets none either, so `test@pm.me` doesn't come out as `test@pm. Me`; a comma or question mark after the address still does. A run of marks like "..." or "?!" isn't pulled apart into "... !", because the space from the previous mark is taken back before the next one lands. A closing mark does the same thing: type `"hi.` and the closing quote hugs the full stop rather than landing a space past it. This is also gated off in password and structured fields, same as autocorrect itself. ### A space that is already there Every rule that types a space after something, the space bar after a word, the space after a mark and the space after a glided word, checks first whether the text already goes on with a space. If it does, the cursor steps over that space instead of typing a second one, so it lands at the start of the next word. Put the cursor at `auto| space`, type `matic` and a space, and you get `automatic |space`, ready for the next word. The same goes for a word tapped from the strip. Before, the cursor stayed in front of the old space, so the next word ran into the one after it and every space and mark after that landed in the same wrong place. Web pages and app screens built on one are the exception: there the space is always typed. A page can show text after the cursor that isn't really there, such as Gmail's grey Smart Compose suggestion, and the keyboard has no way to tell the two apart. Stepping over that suggestion's first space made Gmail close the keyboard. A word the cursor only landed on is not a word you ended, so a space pressed there with nothing typed into the word is an ordinary space. ### Shift cancels a pending auto-space Right after either rule above inserts a space and auto-capitalization raises Shift for the next sentence, the very next Shift press does something different from lowering it. It deletes that trailing space, as long as nothing has changed the text since, and cancels that auto-capitalization too. When Shift wasn't raised for you, because the mark was a comma or auto-capitalization is off, the press arms Shift as usual and the space stays, so you can still capitalize the next word by hand. It's a one-shot. Press any other key first and the pending state clears, after which a plain space press only confirms the space already there rather than adding a second one. ### A mark takes the keyboard's space back Whenever the keyboard types a space for you rather than you typing it, a mark typed straight after it takes that space back so it hugs the word it belongs to. That covers the space after a glided word, the space after a word tapped from the suggestion strip, and the space the punctuation rule above inserts: `word:` and not `word :`. A space you typed yourself is yours, and no mark removes it. The marks that do this are the ones that end a word or build a longer one: the full stop and its family, the closing brackets, the percent sign, and the slash, backslash, apostrophe, dashes and underscore that spell "and/or", "hello's", "well-known" and "snake_case". `#` and `@` are not among them, because a hashtag and a mention attach to the word after them: `check this #cool` wants the space in front of the hash kept. In a web address field `#` and `@` join the list, along with `&`, `=`, `+` and `~`, since an address has no spaces anywhere in it. The rule runs before the keyboard decides what else the mark means, which matters for two of them. A colon on a word boundary also opens an emoji search, and an apostrophe goes into the word being corrected so that "hello's" stays one word. Both still hug the word first. If the emoji search does complete, the space comes back, since an emoji stands off the word in front of it: a swiped "party" then `:tada:` gives `party 🎉`. The straight double quote is the one mark that has to look at what came before it, because it is both ends of a quotation. The keyboard counts the quotes already on the line: an even count means this one opens, so the space in front of it stays, and an odd count means it closes, so the space goes. `he said "hello"` comes out right either way. The apostrophe is left out of this on purpose, since it is a letter inside a word and counting it would say nothing. Getting to the mark does not spend the space. A colon and a slash live on the symbols page, so reaching either one means pressing `?123` first, and that press changes only what is drawn. The same goes for shift, the Fn and number pages, and the globe key. Any key that puts text in the field does spend it, panels included, because what comes out of a panel is text like any other. ### A mark takes your own space back The rule above never touches a space you typed. **Remove a space before punctuation** is the setting that does, and it is on by default: with it on, `Hey .` lands as `Hey.` and `yes , no` as `yes, no`, the moment the mark is typed. It is narrow on purpose. The mark has to come after a word, a number or something that closes one (a bracket, a quote), so a mark at the start of a line or after another mark keeps its space, and a line break is never treated as a slip. A space in a password, an address or a number pad is left alone, same as every other rule here. One backspace straight after puts the space back. Since 0.6.6 so does a smiley: when the key after a `;` or `:` turns it into an emoticon, as in `;)`, `:(`, `:D`, `;P` or `:-)`, the space you typed comes back in front of it, so `nice ;)` stays `nice ;)`. If the keyboard put a space after the mark, only a closing mouth such as `)` counts, because a letter or an opening bracket there starts your next word. Which marks do this is yours to set. The list under the switch takes one character per mark with nothing between them, and starts out as the sentence and clause marks: `.,?!;:`, the Bengali `।`, and the Arabic comma, semicolon, question mark and full stop (`،؛؟۔`), which hug their words exactly as the Latin ones do. You do not have to edit it for French: the languages that keep a space in front of a mark are handled on their own, below, and are never hugged whatever the list says. ### The space in front of a mark French does the opposite of everything above. `!`, `?`, `;`, `:` and the guillemets are double marks there, and they take a space on both sides: `Bonjour !`, `Quoi ?`, `voici :`. **Space before French punctuation** is the switch for that, on by default, and it types the space for you and keeps every other rule from swallowing it again. It is a no-break space, so the mark can never wrap onto the next line on its own. That is why the rule exists in print. Type the space yourself and it is replaced with the no-break one; type none and you get one anyway; let the keyboard put one there after a swipe or a strip pick and it stays. One backspace straight after takes it out again, for the times you are quoting code rather than writing a sentence. The same guards apply as everywhere else on this page. The mark has to follow a word, a number or a closing bracket, so a mark at the start of a line, after a line break, or after another mark is left exactly as you typed it, and passwords, addresses and number pads are untouched. One knock-on: in these languages a colon after a word is punctuation rather than the start of an emoji shortcode, so `:tada:` opens the emoji search at the start of a line but not in the middle of a sentence. French quotation marks get the same treatment from both ends. `»` takes its space in front like any other mark, and `«` takes one behind, typed with the mark, so `«` then a swiped word gives `« oui` rather than `«oui`. Quoting in French is `« oui »` with no spacebar taps of your own. Seven languages are written this way and carry the rule: French, Breton, Occitan, Walloon, Picard, Norman and Franco-Provençal. Every other language, Haitian Creole and Catalan included, types exactly as it did before, because the switch has nothing to act on. ### A glided word joins an opening bracket Glide typing types a space in front of each word as well as after it, so two swiped words do not run together. That space is skipped when the character in front of it is one the word belongs against: an opening bracket, an opening quote, or the start of the line. `he said "` then a glided "hello" gives `he said "hello`. The double quote is read the same way as above, by counting the quotes on the line, so a closing quote still gets its space. In a web address field there is no leading space at all, since an address has no spaces in it. The joining marks work the same way, with one extra test. A slash, a backslash, a dash, an underscore, `&`, `=`, `+` and `~` hold two words together when they are up against the word behind them, so `The/` then a glided "And" gives `The/And`. The same marks stand between two words when they have a space in front of them, and then the glided word gets its space: `hello -` gives `hello - world`, while `well-` gives `well-known`. `#` and `@` are openers rather than joiners, because they attach to the word after them wherever they sit. `check this #` then a glided "cool" gives `check this #cool`. ## Brackets close themselves **Close quotes for me** is off by default and does the same for `"` and `'`, except right after a letter or number, where a quote is closing something or is an apostrophe. **Close brackets for me** is off by default. Turn it on and an opening bracket types its closing half behind the cursor, with the cursor left between the two, so you carry straight on inside the pair: press `(` and you get `()` with the caret in the middle, ready for what goes in it. Three presses behave differently once it's on: - **The opening bracket** types the pair. `(` `[` `{` do it, and so do the CJK and fullwidth brackets: `(` `[` `{` `〈` `《` `「` `『` `「` `【` `〔` `〖` `〘` `〚` `⟨` `⟦`. The pair only goes in where nothing follows the cursor but a space, a line break, the end of the text, a closing bracket or a mark like `.` or `,`. In front of anything else, a word, `@name`, `#tag`, `$5`, an emoji or a quote, you're putting a bracket around that text by hand, so only the opener is typed: `he (was`, not `he ()was`. - **The closing bracket**, pressed when that very character is already the next one in front of the cursor, steps over it instead of typing a second one. Typing both halves out of habit gives you `()`, not `())`. - **Backspace** on an empty pair takes both halves out in one press, so undoing the bracket undoes the closer that came with it. A pair with anything inside it deletes one character at a time, as always. Two kinds of character are deliberately left out. **Quotes** (`"`, `'`, `` ` ``, `«`), because the apostrophe in "don't" is the same key, and a keyboard that closed it would be wrong far more often than right; select some text and press the quote instead, which [wraps the selection](https://wmkeyboard.pages.dev/tools/text-editing/) in the pair. **`<`** for the same sort of reason: outside code it is a less-than sign, and `5 <` is nobody opening a bracket. The rule also stands down when the bracket lands in front of a word. `(` typed immediately before `world` is you putting brackets around that word by hand, and a closer dropped into the middle of it would only have to be deleted again. A space, a mark, another bracket or the end of the text all leave room for the pair. Passwords, email fields and number pads are untouched. Web addresses are not: a browser's address bar is a search box as often as an address box, and a bracket typed there pairs the same as anywhere else. The pair works the same in apps and web pages that handle typing their own way, Discord, browser-based editors and the like, where the cursor used to land outside the closing bracket. With **Automatic space after punctuation** on, a closing `)` gets the space after it too, typed or stepped over: `(see below)` then a word gives `(see below) word`. Only when nothing but a space or the end of the line follows it, so `((a))` and `(a).` don't come apart. `]` and `}` get no space, because `[text](link)` is a Markdown link and the space would break it. A closing quote gets the same space: `"exact phrase"` then a word gives `"exact phrase" word`. The curly `”` always closes. The straight `"` is counted the way the rule above counts it, so only the quote that closes a quotation gets the space, and the one that opens it stays up against the next word. In an app that turns your opening `"` into a curly `“` as you type, the straight quote that ends the quotation still counts as closing it. A full stop typed after the quote takes the space back, the same as after any other mark. A web address or a file name typed into a message closes back up. The space after a full stop goes in before the keyboard can tell a sentence from `google.com`, so when the word you type next is a domain ending or a file extension, the space comes back out as the word finishes: `google. com` becomes `google.com`, `notes. txt` becomes `notes.txt`, and `bbc.co. uk` becomes `bbc.co.uk`. The capital the full stop put on the word goes too. Endings that also start sentences (`it`, `in`, `me`, `us`, `go`, `log`) are left alone, and so is a dot that doesn't sit straight on a letter or digit, like `Wait... com`. One backspace puts the space back. In a browser's address bar the punctuation rule stays out, because a full stop or a colon there is part of an address. Once what you type there has a space in it, it is a search and not an address, and the closing `)` and closing quotes get their space as in a message. ## Lists carry on **Continue lists** is off by default. Turn it on and Enter on a list line starts the next line with the next marker, in any app, not only the [Notes](https://wmkeyboard.pages.dev/tools/notes/) editor. Type `1. milk`, press Enter, and the new line already reads `2. `. The markers it knows: - `-`, `*`, `+` and `•` carry on unchanged. - `1.` and `1)` count up, keeping whichever delimiter you used. - A task box, `- [ ]` or `- [x]`, carries on as an empty `- [ ]`; `☐` and `☑` carry on as `☐`. Indentation in front of the marker is kept, so a nested list stays nested. Enter on an item with nothing after its marker ends the list: the marker comes off and no new line is typed, the way a word processor lets you out of one. A number has to be three digits or fewer, so a line that opens with `2024.` is not read as a list item. The rule only runs where Enter makes a new line. In a chat box whose Enter key sends, Enter still sends; Shift+Enter there makes the line break, and that line break carries the list on. Passwords, email fields, web addresses and number pads are left alone. Bold, italic and the other inline styles are not converted. A plain text field has nowhere to draw them, and the chat apps that read `*bold*` or `**bold**` already do that themselves; select the text and the [chat formatting](https://wmkeyboard.pages.dev/smart/selection-actions/#chat-formatting) chips wrap it in the markers that app reads. ## Reducing typos before they happen Autopilot nudges which key a borderline tap resolves to, biasing toward whichever neighboring letter is more likely to continue the word you're typing. Fewer boundary mis-taps happen in the first place, before autocorrect would ever need to fix them. It has its own screen under the Suggestions settings group rather than sitting alongside autocorrect, since it needs suggestions on to have anything to bias toward. Its Strength slider sets how far a favoured letter reaches, and two optional views draw the touch areas it has claimed. See [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for the rest of how the suggestion engine ranks candidates. **Mistype tolerance** (100% by default, 50% to 200%) sets how far into a neighbouring key a tap may land and still be read as a slip of the key you meant. Every tap is scored by where it landed, so a tap near a key's edge can already count as its neighbour; raise this if you type fast and loose and want the keyboard to look further, lower it if you type carefully and want the key you hit trusted. It moves both the strip's candidates and autocorrect's pick, so it stays on the Automatic corrections screen, along with **Learn where my taps land**, when Autocorrect is turned off. **Fix several missed keys** (Careful by default) applies that reading to every letter of a word at once. Without it, each tap read as the key next door counts as one mistake, and autocorrect fixes one mistake per word. With it, a word typed fast with three keys a little wide, `rgw` with each finger just left of the key meant, is read as `the`. Only keys within reach of where your finger landed qualify (about one and a quarter keys at 100% tolerance, further as you raise it). **Careful** fixes those words, but a fix that also needs a letter added or removed is only offered in the strip. That mix is what most often turns a correctly typed word the keyboard hasn't learned yet, a new name or a bit of slang, into some other word; held back, such rewrites stay about as rare as they were without this setting. A word you keep typing is learned after a few uses, and from then on it's never changed. **Eager** applies it as well, for when your dictionary already holds every word you type. **Off** goes back to one fix per word. A word the keyboard knows is never changed, and typing with no tap positions, from a hardware keyboard or a paste, is corrected as before. ## Spell checking in other apps Android has a spell checker of its own, separate from the keyboard: it draws the red underline under a misspelt word in apps that ask for one, and offers fixes when you tap it. WM Keyboard can be that spell checker. Open the phone's **Settings**, search for **Spell checker** (usually under System, Languages & input), and pick **WM Keyboard spell checker**. It then checks words against the same word lists the suggestion strip uses: a word list you downloaded for the language, the built-in list for English, and the words the keyboard has learned from you. The fixes it offers are the ones autocorrect and the strip would give. A language with no word list on the phone gets no underlines at all, rather than every word marked wrong. For English it adds Harper's grammar marks on top. Nothing runs until you choose it there, and switching back is the same setting. See [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) for the details. ## Options Typing / Automatic corrections smart/autocorrect-options — The Automatic corrections group in Typing settings, including the confidence slider. | Setting | Default | Shown when | | --- | --- | --- | | Autocorrect | On | always | | Autocorrect this language (per language, under Languages & layouts) | On | Autocorrect on | | Autocorrect confidence | 4.0× (range 1.0×–10×) | Autocorrect on | | Adaptive confidence | On | Autocorrect on | | Typing rhythm | 0% (range 0%–100%) | Autocorrect on | | Undo autocorrect with backspace | On | Autocorrect on | | Undo a correction from the strip | On | Autocorrect on | | How often to offer the undo | 50% (range 0%–100%) | Autocorrect on, and Undo a correction from the strip on | | Remember my undos | Normal (Off / Light / Normal / Strict) | Autocorrect on | | Learn from my fixes | On | always | | Learned corrections | (a screen) | always | | Learn where my taps land | On | always | | Learned correction gate | On | Autocorrect on | | Mistype tolerance | 100% (50–200%) | | | Fix several missed keys | Careful (Off / Careful / Eager) | | | Skip words in capital letters | On | Autocorrect on | | Correct the word before Enter | On | Autocorrect on | | Correct the word before punctuation | Off | Autocorrect on | | Capitals from the word list | On | always | | Block offensive words | On | always | | Context-aware suggestions | On | always | | Context model | On | always | | Re-check the previous word | On | always | | Phrase suggestions | Off | always | | Add missing spaces | On | Autocorrect on | | Detect the language you type | On | always | | Detection strength | Balanced (Gentle / Balanced / Aggressive) | Detect the language you type on | | Fix number row slips | On | the number row is shown | | Fix missing apostrophes | On | always | | Double space | Full stop (Two spaces / Full stop / Tab) | always | | Double space speed | 400 ms (range 200–800 ms) | Double space is Full stop or Tab | | Automatic space after punctuation | Off | always | | Remove a space before punctuation | On | always | | Marks that pull the space out | `.,?!;:।،؛؟۔` | Remove a space before punctuation on | | Space before French punctuation | On | always | | Close brackets for me | Off | always | | Close quotes for me | Off | always | | Continue lists | Off | always | The table covers the rows this page is about. The same group also carries **Adapt to app tone** and **Space after a suggestion** (both described on [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/)), **Automatic capital letters**, and the two selection rules, wrap-with-a-pair and shift-re-cases-the-selection. The **Autocorrect** toggle is the master switch for the eighteen rows marked "Autocorrect on" above, from the confidence slider down to Fix number row slips. It is not a switch for the whole group. The apostrophe fix, both double-space rules, the auto-space after punctuation and the space-before-punctuation fix carry on working with autocorrect off, which is the point of them having their own toggles. Autocorrect is also reachable as a one-tap toolbar tool that flips the same switch without leaving the keyboard, showing a brief "Autocorrect on"/"Autocorrect off" confirmation. The tool is lit only when the language you're typing is corrected, so on a language switched off by itself (below) it shows off, and a tap turns autocorrect back on for that language rather than off for all of them. ### One language at a time Languages & layouts / your language / Autocorrect / Autocorrect this language Each language has its own switch, on by default. Turn it off for one language and words typed in it are never replaced, while every other language keeps being corrected. This is for typing two languages side by side where only one should be corrected, such as English next to romanized Bangla or a language whose word list doesn't know your spellings. The language follows the keyboard you're on, not the language [detected](https://wmkeyboard.pages.dev/smart/mixed-language/) in the field. Suggestions still show on the strip, and you can tap one. The switch only covers autocorrect itself: a phonetic layout's known spellings and dictionary words have their own switches in the same place (see [Phonetic typing](https://wmkeyboard.pages.dev/languages/phonetic/)). On a phonetic layout it does govern typos: a slip like `vako` for `valo` is corrected only while the switch is on. The main **Autocorrect** switch above still rules every language. While it's off, the per-language rows are greyed out and keep their own setting for when it comes back on. **Autocorrect confidence** is the ratio the top candidate must beat the runner-up by. It's a continuous slider from 1.0× to 10×, labeled with the exact multiplier (for example "×4.0"). Below 1.5× it also accepts words two letters away from what you typed, and at 1.0× the best candidate wins whatever the runner-up scores. A word already in a dictionary is still left alone, and **Adaptive confidence**, when on, can raise the bar again after corrections you undo. Lower makes autocorrect fire more eagerly, on less-certain calls. Higher restricts it to only the most unambiguous typos. Two-source agreement (see [How a correction is chosen](#how-a-correction-is-chosen)) always bypasses this slider, whatever it's set to, unless the learned gate is on, which weighs it instead. Under the gate the slider sets the probability a fix has to reach, the ratio against one; see [The learned gate](#the-learned-gate). **Learned correction gate** (on) is that gate. Off, the keyboard decides by the rules in [How a correction is chosen](#how-a-correction-is-chosen) alone. **Context model**, **Re-check the previous word** and **Phrase suggestions** are the neural context model's three rows; they are described on [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/#the-context-model), and the gate reads the model's scores when it is downloaded. Three more rows change what autocorrect does without you touching the slider: - **Adaptive confidence** (on) watches how often corrections get rejected and raises the bar when a lot of them do. It stays out of the way until twenty corrections have been judged, and it can move the gate anywhere from 0.85× the slider (a spotless record) up to 2.5× it, with the result always held inside the slider's own 1.5×–10× range. That is the whole of this row: it moves one global gate. The memory of individual corrections you undid is a separate setting, **Remember my undos**, described in [How long an undo lasts](#how-long-an-undo-lasts). - **Remember my undos** (Normal) sets how long the keyboard keeps an undo. It is the row to reach for if autocorrect either forgets a fix you rejected or refuses to fix a word you want fixed. See [How long an undo lasts](#how-long-an-undo-lasts). - **Stop after I refuse a fix** (2) leaves a word alone at the place where you have undone or deleted its correction that many times. See [When you keep deleting the same fix](#when-you-keep-deleting-the-same-fix). - **Learn from my fixes** (on) and **Learn where my taps land** (on) are the two halves of [Learning from your own fixes](#learning-from-your-own-fixes): the first remembers the typos you repair by hand and the slips behind them, the second where your finger lands on each key. **Learned corrections** opens the list of what has been learned, where each entry can be added, changed or removed. - **Typing rhythm** (0%, so off until you move it) measures how fast you typed the word. A quick sloppy burst makes autocorrect a bit braver. Slow deliberate typing, as with an unusual name or a word from another language, makes it hold back. The slider is how much the rhythm is allowed to sway things. - **Detection strength** decides how hard the language the keyboard reads the field as leans on ranking, from a light nudge at Gentle through Balanced to a heavy tilt at Aggressive. It reweights the word lists rather than moving the confidence gate, so it changes which word autocorrect reaches for, not how sure it has to be. See [Mixed-language typing](https://wmkeyboard.pages.dev/smart/mixed-language/). The other new rows are corrections in their own right rather than gate tuning: - **Add missing spaces** (on) splits a word you typed without its space when both halves are known words, the two are a pair the keyboard has seen together, and nothing else explains it. Off, the suggestion strip stops offering such two-word splits as candidates too. "ofthe" becomes "of the", and a letter next to the spacebar can stand in for the space itself, so "amibtomake" becomes "ami tomake". Two known halves alone are not enough, because the word lists carry every short fragment a corpus produced and an unlisted word ("config", "inbox", a name) that happens to break into two of them is far commoner than a missed space. So the pair has to be in the language's downloaded word pairs, or one you have typed yourself at least twice, and a word in your personal dictionary is never split. The second reading also needs the tap to have landed low on its key, toward the spacebar. Its first half can be a single letter only when that letter is a word at least as common as the one after it, which `i` and `a` are, so `ibwant` with the `b` tapped low becomes "i want". Since 0.6.6 a tap in the lower half of the key that didn't land that far down still counts for something: the split goes on the strip and on a chip you can tap, but is never applied on its own, so `tonthe` with the `n` tapped just below its middle offers "to the" where it used to offer nothing. A tap in the top half of the key is never read as a missed space. Until the pair is known a split stays a suggestion on the strip. Neither reading will ever end on a single letter: a downloaded word list is built from a corpus tokenised at the apostrophe, so it carries a bare "s", "t" and "don" as ordinary words, and without that rule "thats" reads as "that s". The first half may still be one letter, because "alot" really is "a lot". Backspace right after puts your text back, like any other correction. A space that took the place of a letter goes the other way: with `e` before the space and `ough` being typed, the strip offers *enough* as a chip that replaces both. That's a chip only, never applied on its own, and only while what you're typing is at least ten times rarer than the joined word, since a space before a real word was most likely meant: `i to` stays two words. Before 0.6.6 the fragment had to be no word at all, which a downloaded word list (some count `ough`) or a few sightings in your personal dictionary was enough to break. Two missed letters can turn into a full stop: both `b`s of *lobby* land on the space bar, the double space becomes `. `, and the field reads `lo. y`. Since 0.6.6 the word typed after a full stop that a double space put in gets the same chip, here *lobby*, when two of the letters beside the space bar would join the halves into a word. Tapping it takes the full stop out too. - **Fix number row slips** (on) treats a lone digit inside a word as a tap that landed one row too high, so "as3" can commit as "ase". The row is the one on your screen: a layout with a number row of its own (Eastern Arabic digits, say) has its own keys checked against the letters under them, since 0.6.4. Only a same-length, single-digit swap is ever trusted: a word starting with a digit, or one with two or more digits, is left alone. The letter has to be one the digit sits over or beside, so a `3` is read as `w`, `e` or `r` and nothing else. Before 0.6.6 any letter could stand in, and "ton3" stayed as typed because "tony", four keys from the `3`, was too close behind "tone" for autocorrect to choose. Chinese Pinyin and Jianpin read a digit in the reading the same way since 0.6.6: `niha9` converts as *nihao*. The row only appears while the [number row](https://wmkeyboard.pages.dev/reference/settings/layout/) is on, since that's the only time the slip is possible. - **Context-aware suggestions** (on) reorders the top candidates using the words before them, drawing on your own word pairs, common English pairs and the words already in this conversation. It only reorders where there's real evidence, and it never changes which word autocorrect applies. ## Details & edge cases - **Structured and secure fields are a hard gate, not a per-setting one.** Autocorrect, both auto-space rules, and the autopilot bias all share one underlying check: the field must be a plain text field, not a password field or a keypad (email, web address, phone, number). There's no way to enable autocorrect specifically in a password field. - **An autocorrected word earns no personal-dictionary credit** at the moment it's committed, so the engine's own guess isn't reinforced as if you'd typed it deliberately. Reverting it with backspace teaches the original word instead (see [Undoing a correction](#undoing-a-correction)). - **The revert window is exact, not fuzzy.** It only fires if the caret is still precisely where the correcting commit left it and the text immediately before it is still `"corrected word" + " "`, unchanged. Any cursor movement, or any edit elsewhere in the field, silently disarms it. Older corrections are reached through the [undo chip](#taking-a-correction-back-later), which keeps the last five. - **Two-source agreement bypasses the slider.** If your personal dictionary and the standard dictionary happen to name the same fix for a typo, it's applied even below the confidence slider's threshold. The slider only governs cases where the two disagree, or where only one source has an opinion. It can't resurrect a rejected fix, though: a blacklisted, filtered or retired candidate is dropped from the pool before this test ever runs. - **The confidence check is symmetric with the engine's internal default.** The slider's default position (4.0×) matches the value the underlying suggestion engine falls back to if a setting were ever missing, so a fresh install and an explicitly-set default behave identically. For the rest of the suggestion strip, including completions, next-word prediction, and how learning ranks your own words over time, see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). To keep a word out of both suggestions and autocorrect entirely, use the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). --- # Password manager chips & smart replies URL: https://wmkeyboard.pages.dev/smart/autofill/ Section: Suggestions & correction Summary: Saved logins from your password manager, and the system's suggested replies, both shown as tappable chips in the suggestion strip. Two different things can show up as chips inside the suggestion strip, and they arrive over the same Android API. The first is your password manager. Tap into a login field and it can offer its saved entries as chips instead of popping up its own dropdown over the keyboard. Tap one and the field fills. The keyboard itself never sees the username or password that went into it. The second is Android System Intelligence, which reads the conversation on screen and proposes short replies ("On my way!", "Sounds good"). Those show up beside your word suggestions rather than replacing them. They look similar, but the keyboard keeps them well apart. Each lane has its own toggle, its own limit on how many chips it may show, and its own rules about how much of the strip it gets. Wanting saved logins in the strip says nothing about wanting the system to read your messages, and the other way round. ## Password manager chips Focus a login field with a password manager set as your system autofill service, and its saved entries (if it has any for that app or site) appear as chips across the suggestion strip. The chips take over the strip completely while they're up, so word suggestions don't share the row with them. That's deliberate: a saved login answers the field directly, which beats anything the dictionary could offer. Tap one to fill the field the same way you would from the manager's own dropdown. There's a dismiss button (✕) at the end of the row if you'd rather ignore the chips and type or search normally. Tapping it clears the chips until the next response comes in, which in practice usually means the next field you focus. The keyboard asks for up to six chips and sizes them to the strip's height, and no single chip may be wider than half the screen. A long address is ellipsized rather than taking the whole row, so you can always see that there's another entry beside it. The row scrolls horizontally for the rest. Chips are also cleared automatically the moment you leave the field, so a login suggestion for one app never lingers into the next field you tap, in this app or another one. ### Which password managers work There's no allowlist of specific apps. Inline autofill chips use Android's standard inline-suggestions API, so any password manager that supports it and is set as your device's autofill service can offer chips this way: Google's built-in password manager, Bitwarden, 1Password, Dashlane, and others that implement the same platform API. The keyboard requests a chip size and count from whatever service is active. It doesn't know or care which one it's talking to. The feature needs Android 11 (API 30) or newer. On older versions, the setting doesn't appear and the suggestion strip behaves exactly as it always has. Your password manager's own autofill dropdown still works, but it isn't drawn inline. Both sides have to opt in. The keyboard declares that it can host inline suggestions, and your password manager has to support them too. If either half is missing, Android falls back to the manager's own dropdown drawn over the keyboard. ### When nothing comes up Typing / Suggestions / Password manager button Some apps and sites never ask Android to autofill their login screen, so neither chips nor a dropdown appear. A keyboard can't make that request for them: only the app can. What the keyboard can do is open the manager. In a password field with nothing from the manager on the strip, a button names the password manager set as your system autofill service, **Open Bitwarden** for example. Tap it, unlock the manager and copy the password; back in the field, the [paste chip](https://wmkeyboard.pages.dev/smart/chips/) offers it, masked. The ✕ hides the button for that field. It doesn't appear in a PIN or code box, or while a one-time code is on offer. On by default, Android 9 and later, and only with a password manager chosen in Android's autofill settings. When the manager is [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/), the button stays off. WM Pass answers the field itself, with **Unlock WM Pass** on the strip while its vault is locked, so a second button there would only get in its way. Typing / Suggestions / WM Pass tool in password fields Turn this on to get the button back as **Fill from WM Pass**. It opens the keyboard's own WM Pass tool instead of the app: the logins saved for the app in front come first, and a tap types one in without leaving the field. Off by default, and only seen when WM Pass is the autofill service. ## Smart replies When you tap into a reply box in a messaging app, Android System Intelligence may offer a few one-tap answers to whatever's on screen. Up to three of them appear at a time, and they're drawn by the system, not by the keyboard, the same way password manager chips are. Where they sit depends on what else the strip is doing: - **Nothing typed yet.** The replies take the row, with a ✕ to dismiss them. This is the case they're for: the message is on screen, the box is empty, and the reply is the whole answer. - **You've started typing.** They move to the tail of the strip, after the word candidates and any emoji predictions, behind a thin divider. Word suggestions keep their usual three slots; the reply row is width-capped and scrolls if all three don't fit. - **A recently-copied paste chip is also up.** The paste chip shrinks to its narrow form instead of stretching, and the replies take the rest of the row. Tapping a reply commits it through the system, exactly as it would from the reply surface the system draws itself. Not every app offers replies, and the system decides when it has something worth suggesting. An empty strip in a chat app usually means the system had nothing to offer, not that the setting is off. Replies aren't the only thing this lane can carry. The system also sends action chips, which are shortcuts into another app rather than something to type. They share the same toggle and the same three slots, and they're sorted behind plain replies, so a reply always gets the room first. ## Options Typing / Suggestions / Password manager suggestions Typing / Suggestions / Smart replies Both toggles are on by default, and both only show up in the Typing screen on Android 11 and newer. **Password manager suggestions** off stops the keyboard requesting inline suggestions at all. Your password manager's normal autofill dropdown still applies. It draws over the keyboard the way it did before this feature existed, instead of inside the strip. The setting has no effect until a password manager is set as your system autofill service, since without one there's nothing to request chips from either way. Turning it off also turns off smart replies, whatever that toggle says. Android makes one inline request per field and both kinds of chip come back down it, so asking for replies is also what sends your saved logins into the strip instead of into your manager's dropdown, so there's no way to accept one and decline the other. Off means the keyboard asks for nothing, and your manager draws its own UI again. **Smart replies** off stops the keyboard asking the system for replies. Nothing else about the strip changes, and login chips are unaffected. (The reverse isn't true, as above: login chips off takes replies with it.) Turning both off means the keyboard doesn't ask for inline suggestions at all. ## Details & edge cases - **The keyboard never sees a chip's contents.** Chips are drawn by the password manager or by the system, not by the keyboard. The keyboard only says how much space is available, a size and a chip count, and gets back an opaque view it displays as-is. Whatever username, password or suggested reply sits behind a chip stays inside the other process the whole time. - **Chips follow the keyboard's theme.** The request names the strip's own chip background and text colors, so a saved login looks like the word suggestions it sits among instead of a system-default pill dropped onto a dark board. Only colors are set (the sender still decides the text, the icon and the layout), and the very first request after the keyboard's process starts goes out without them, because the theme isn't resolved until the board has drawn once. Your password manager can ignore all of it, in which case you get its own styling, which is always legible. - **Arriving chips wake the strip up.** The bar above the keys rests on the toolbar (tool icons) when there's nothing to suggest, and chips from either lane count as something to suggest, so the strip takes the row when they land. That matters because neither kind of chip arrives while you're typing. A password field has no word candidates, and a smart reply comes before you've written anything. - **Login chips stay on in incognito.** The saved-login chips keep appearing while [incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) is on, whether you switched it on manually or it engaged automatically because the field asked for no personalized learning. They record nothing, the keyboard never sees what's in them, and a private browsing tab is exactly where you log in. Smart replies are the one kind that incognito does block: a proposed reply means the system read the conversation on screen. - **Password fields are exactly where these belong.** Secure and password fields aren't specially blocked from autofill, and neither is incognito. They're where you'd expect a password manager to have something to offer. (Password fields do suppress the ordinary word-suggestion strip, which is a separate and unrelated behavior. See [incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/).) - **Independent of the general suggestions toggle.** Turning off the main **Suggestions** toggle, the one for word predictions above the keyboard, doesn't affect either kind of chip. They're checked separately, so you can have one without the other. - **Tapping a chip vibrates and clicks like a key**, following your haptics and key sound settings. The chip is drawn by another app, so the keyboard never sees the tap itself; it notices the chips being taken away just before the field fills, and buzzes then. A password manager that asks you to unlock it first opens its unlock screen instead, and that tap doesn't vibrate. - **The ✕ clears both lanes.** Dismissing chips means "stop offering me things about this field", and both lanes are answering the same field, so both go until the next response. - **Requires Android 11 or newer.** Login chips also need a password manager currently set as the system autofill service. Anything missing means the strip behaves as if the feature weren't there. Available identically in every build of the keyboard. For everything else that shows up in the suggestion strip, see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). For how incognito mode and secure fields affect what the keyboard remembers and suggests, see [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/). --- # Suggestion blacklist URL: https://wmkeyboard.pages.dev/smart/blacklist/ Section: Suggestions & correction Summary: Words the keyboard must never suggest, enforced across every suggestion path. The suggestion blacklist is a list of words you never want offered back to you. A nickname you don't want autocompleted for a stranger, a correction you're tired of seeing, anything at all. Add a word to it and the keyboard keeps it out of the suggestion strip and out of autocorrect for good, while still letting you type and send it yourself whenever you choose to. smart/blacklist-editor — The suggestion blacklist editor, reached from Typing settings. ## Adding and removing words Typing / Suggestions / Suggestion blacklist Tap **Add word** at the top of the editor. A dialog opens with a **Word** field and a **Block in** row of chips: **All languages**, then one chip per language you have enabled. Type the word, pick where it applies, then confirm with **Add**. The **Add** button stays disabled until the field has something in it. The word is stored lowercased and trimmed, so capitalization and stray spaces in what you type don't matter. The list itself is sorted alphabetically, with a plain trash icon on each row to remove one. A word blocked in one language says so under the word ("Only in French"); a word blocked everywhere has no line under it. Once the list has at least one word in it, a **Clear the blacklist** row appears above it, which empties every list, global and per-language alike, after a confirmation. Past a dozen words a search field appears above the list, the same threshold the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen uses, and the list itself draws 100 rows at a time with a **Show N more words** row to reveal the rest, so a list hundreds of words long never composes all at once. You can also add a word without leaving the keyboard. Press and hold it on the suggestion strip and pick **Never suggest** from the menu that opens. The same menu offers **Suggest again** for a word that is already on the list, so a slip is one hold away from being undone. The rest of that menu, and the word card behind its **Edit** item, are described on [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip). ## Blocking a word in one language only Typing / Suggestions / Suggestion blacklist / Blocked from the keyboard ![Adding a word to the blacklist from the Add word dialog.](@assets/screens/smart/blacklist-add-dialog.webp) A word can be a nuisance in one language and perfectly good in another: a French word the English strip keeps guessing, say. Blocking it for all languages would take it away from French too, so the blacklist keeps two kinds of entry. A word blocked for **All languages** is never suggested, whatever you are typing in. A word blocked for one language is kept out of the strip only while that language's keyboard is active, and offered as usual everywhere else. The Add dialog asks which you want each time. The keyboard's own **Never suggest** cannot ask, since it is one item on a hold menu, so **Blocked from the keyboard** at the top of the editor decides for it: **All languages** (the default, and how the menu has always behaved) or **Current language**, which blocks the word for the language you were typing when you held it. Deleting a word that only a downloaded word list still knows follows the same setting, since that deletion works by blacklisting. **Suggest again**, and adding a word by hand from the strip's menu or the word card, take the word off every list it is on. There is no per-language undo on the keyboard, because the opinion being reversed is "never", not "never in French". A word blocked in one language is not forgotten from your personal dictionary, unlike a word blocked everywhere: the dictionary is shared between your languages, and the word is still wanted in the others. Before you add anything, the screen shows "No words in this list yet. Add a word above." The row back on the Typing screen reads "Words to never suggest or autocorrect to." Once the list has entries, that row's subtitle switches to a running count instead, like "3 words never suggested." ## What gets filtered A blacklisted word is matched case-insensitively and kept out of every place the keyboard offers a word back to you, well beyond the ordinary English suggestion strip: - **Completions**: dictionary, custom dictionary, secondary-language, personal-dictionary, contact-name, contact-email, and app-name completions, plus missing-space split suggestions, are all filtered before they reach the strip. - **Bangla (Avro) suggestions**: loanword-map hits, phonetic siblings, and the phonetic transliteration itself are filtered the same way, so the blacklist reaches transliterated composing too, not only plain-English typing. The composing preview that shows a listed spelling mid-word skips blacklisted spellings too. - **Next-word predictions**: the empty-composing bigram predictions that appear before you've typed anything are filtered as well. - **Glide typing**: a decoded stroke's candidates go through the same filter, so a blacklisted word can't arrive by swipe either. - **Autocorrect targets**: a blacklisted word is never chosen as the word autocorrect replaces your typo with, even if it would otherwise be the best-scoring candidate. The same holds for the two halves of a missing-space split correction. The blacklist never touches the word itself as text. Typing it, editing it, and sending it all work exactly as normal. Blacklisting only removes a word from being offered, which makes it a suggestion filter rather than a content filter. "Block offensive words," a separate toggle under Typing / Automatic corrections / Block offensive words, uses this exact same filtering mechanism. It runs against a bundled list of profanity and slurs instead of words you typed in yourself. Each of your languages has its own list, and one language's list doesn't hide a plain word of another: English's list flags `wel`, `nog` and `ging`, which are everyday Dutch, so they stay on the strip when Dutch is in your mix. A word flagged by a language whose list the keyboard can't check against, or one no language of your mix knows, stays hidden. A word you added to your personal dictionary by hand is never hidden by this filter. It's on by default, but it only shows up in the Typing screen while autocorrect is turned on, even though (like the blacklist) it also affects plain suggestions. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) for the rest of the automatic-corrections settings. ## Details & edge cases - **Nothing is blacklisted by default.** The list starts empty and you build it entry by entry. - **A word can be on more than one list.** Blocked in French by hand and later blocked everywhere from the keyboard, it shows twice in the editor, once per list, and the trash icon on each row removes only that entry. The row back on the Typing screen counts entries, so that word counts as two. - **The count and the filter are per language being typed.** Switching keyboards switches which per-language list applies; the global list applies throughout. - **It's mostly independent of your personal dictionary, with one automatic link.** The blacklist and the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) of learned words are two separate stores, and neither settings screen offers a shortcut to push a word from one into the other. But blocking a word everywhere does purge it from the personal dictionary and from the words still waiting to be learned, the moment the keyboard picks up the change, and it stays out of both while it remains blocked; blocking it in one language only filters the suggestion, leaving what the keyboard learned alone. There's also a link on the keyboard itself: **Delete** on a held suggestion forgets the word from the personal dictionary and, when the word is still in a downloaded or imported list, puts it on the blacklist as well, since that is the only way to unlist one word from a read-only list. As a candidate, meaning something offered to complete another word or something autocorrect would replace a typo with, the blacklist always wins, so the word stays suppressed either way. But if you type that exact word yourself, autocorrect leaves it alone regardless of the blacklist, the same as it would for any word already known to the keyboard. - **Removing a word un-blocks it immediately.** If a global block already purged it from your personal dictionary, taking it off the blacklist starts the word over rather than restoring what it had learned before. You don't need to touch the personal dictionary at all. - **A word doesn't need history to be blacklisted.** You can blacklist any word, learned or not. The blacklist doesn't check whether the word exists in any dictionary first. - **Backed up like any other setting.** The list lives in the same settings store as everything else in the app, so it's included automatically in a [settings backup](https://wmkeyboard.pages.dev/privacy/backup/). There's no separate export step for it. - **An email field is the one gap.** In a field the app marked as an email address, the strip is driven straight from your contacts' addresses rather than through the ordinary suggestion pass, and that shortcut doesn't consult the blacklist. Blacklisting an address keeps it out of the strip everywhere else, but not there. See [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) for what that path does. For the rest of the suggestion strip (completions, corrections, and next-word prediction), see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). For teaching the keyboard new words instead of blocking them, see [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/). --- # Smart chips URL: https://wmkeyboard.pages.dev/smart/chips/ Section: Suggestions & correction Summary: Inline answers above the keys: calculations, conversions, and useful bits pulled from your clipboard. Smart chips watch the last 48 characters before your cursor. The moment that text looks like something a tool can answer, the answer lands on the suggestion strip in place of word candidates. Tap the chip to type the answer, or its gear button to open the full tool with the same numbers already loaded. A few chips have nothing to type and only ever open a tool. Those carry no gear, and the whole chip is the door. The gear also stays hidden while its tool is switched off under Tools, since there would be nothing for it to open. The gear takes the text it answered out of your message on its way into the tool, because the tool types its own result in that spot. If you change your mind, close the tool without inserting anything and press backspace once: the first backspace puts `150 usd`, or `calculator`, back where it was. Typing anything first, or moving the cursor, gives up that undo. To keep the text every time, turn on **Keep the text when a chip opens its tool**; the tool's result then lands after your text. ## Doing the math Type an expression like `12*8` and the strip offers `96` as a tappable chip. It recognizes `+ - * / × ÷ − ^ % ( )`, `√`, `π`, and `e`, along with implicit multiplication (`2π`, `3(4+1)`). A plain division counts on its own, so `1/2` offers `0.5`. Tap the chip and the result replaces what you typed. Type `=` yourself and the answer is appended instead, leaving your sum in place, so `12*4=` becomes `12*4=48`. Permutations and combinations use the shorthand a calculator keypad uses: `5p3` offers `60` and `4c2` offers `6`. Both letters have to sit tight between two digits, so `page 5 c 2` stays a sentence. A few things deliberately don't trigger a chip. A bare number on its own isn't a sum, and `100%` on its own reads as "percent" rather than a sum to evaluate. A `%` with arithmetic around it does count, and reads as it does in the [Calculator](https://wmkeyboard.pages.dev/tools/utilities/#doing-the-math): `100+20%` offers `120`. Named functions and word-operators like `sin`, `sqrt`, `log`, or `mod` never show up as an inline chip either. Apart from `p` and `c`, the symbol scanner only looks for digits, operators, and the two symbols `π`/`e`. Anything with more letters than that works only by opening the Calculator tool itself. Some arithmetic gets written as a sentence, though, and a second scanner catches three shapes of it: a share of an amount (`15% of 200`, `15% on 200`, `15% tip on 4500`), a discount (`20% off 1500`, `20% off of 1500`), and a bill split (`split 4500 between 3`, `split 4500 3 ways`, `4500 split 3 ways`; `among` and `by` work as well as `between`, and `people`, `persons` or `friends` as well as `ways`). Each one produces an ordinary calc chip, and the gear opens the Calculator on the equivalent sum. ### Sums in number fields An amount, quantity or price box works too. Those fields only take digits, so a `×` typed into one would be dropped before the chip could read it. Instead the keyboard holds the sum itself. Type the first number, then an operator from the hold on the `-` key (or the minus itself): the number stays in the field, and the rest of the sum appears on the chip as you type it, `120 × 3 → 360`. Tap the chip, or press Enter or `=`, and the answer replaces the number in the field. Backspace edits the sum, and taking out its operator leaves the field as it was. Moving the cursor or leaving the field drops the sum unchanged. A field that only takes whole numbers gets the answer rounded, shown as `~3`, rather than losing its decimal point. PIN fields and phone fields are left alone. The chip's gear icon opens **Calculator** with your expression prefilled. Result precision (decimal places) is shared with the unit converter, and there's a separate degrees/radians switch for trig, set below in [Options](#options). ## Converting currencies and units smart/currency-chip — A currency chip: amount and source on the left, the converted amount on the right, gear button to open the full converter. Type an amount next to a currency (`150 usd`, `150$`, `$150`, or `150 dollars`) and the strip converts it into your other currency. Symbols, spelled-out words and 3-letter codes all work. Big amounts can be written the short way or the long way: `1.5k usd`, `2m dollars`, `3 crore taka`, `2 million dollars`, even `1 thousand 500 dollars`. `k`, `m` and `b` are thousand, million and billion, and `lakh` and `crore` work as well. If what you typed is already in your target currency, the chip flips and converts the other way, so either side of your currency pair gets an answer. The result shows a currency's name rather than its code (`Taka`, not `BDT`), unless you pick the symbol (`৳18,000.00`) or the code under **Write the result as** in the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) options. If exchange rates haven't loaded yet, the chip shows a brief spinner while they fetch. In the F-Droid edition it reads **Get the rate** instead and fetches only when you tap it, unless **Fetch rates as you type** is on in the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) options. Coins work the same way, and they're on out of the box. `1 btc`, `0.5 ETH`, `2 bitcoin`, `100 doge` and `100000 sats` all convert into your currency. Point your pair at a coin and the conversion runs the other way, so `1000000 bdt` becomes a fraction of a bitcoin, with enough digits to read. Tickers that double as ordinary English words, such as `DOT`, `LINK` and `NEAR`, only count in capitals, so "meet me in 1 near future" stays a sentence. Coin prices load the first time you type a coin amount, not before. Units follow the same shape. Type `1 ft` (or `1ft`) and the chip offers the same length in meters, across 14 categories: length, mass, temperature, area, volume, speed, time, data, energy, power, pressure, angle, frequency, and fuel economy. Spelled-out magnitudes work here too, so `1 thousand miles` converts as 1000 miles and `2 lakh km` as 200,000 km. Only the words, though: `5m` is five meters and `1.5k` stays out of unit territory, since the single letters already mean something metric. Short, easy-to-confuse abbreviations (`c`, `f`, `in`, `s`, and a couple dozen others) only count glued directly to the number, so `30c` triggers and `30 c` doesn't, and ordinary sentences don't turn into unwanted chips. The unit it converts into is whichever one you paired it with last time you used the converter. The first time, it falls back to a sensible default: feet to meters, kilograms to pounds, and so on. To pick the unit it answers in, name it after `in`, `to`, `into` or `as`: `1 mile in cm` gives `160934.4 cm`, and `30c to f` gives `86 °F`. The unit you name can be any spelling the converter knows, short ones included. One that measures something else, like `1 mile in kg`, gets no chip. Anything landing in feet is read as feet plus inches, since that's how people say it. `1m` becomes `3 ft 3.37 in`, not `3.2808399 ft`. The chip answers in the same style you asked in, so `1m` gets symbols back while `1 meter` gets `3 feet 3.37 inches`. **Feet and inches** under Tools / Unit converter / Options turns this off if you'd rather have the decimal. ### The chip fits itself to the strip A conversion chip prefers to echo what you wrote: type `100 dollar` and the left side reads `100 Dollar`, not `100 USD`. When the full answer doesn't fit the strip, the chip gives things up one at a time instead of cutting the number off mid-digit. First to go is a `.00` tail on the result, then exact decimals (a rounded preview wears a `~`, as in `~৳122,001`), then names shrink to codes and codes to symbols, then the font steps down a size. Only as a last resort does a spelled-out amount flatten to digits, so `1 thousand` becomes `1000`. Unit chips walk the same ladder. `1609.344 km` rounds to `1609.34 km` and then `~1609 km`, and a spelled-out unit shrinks to its symbol (`miles` to `mi`) before the amount gives up its words. A feet-and-inches answer walks its own version: it drops the spelled-out names first (`3 feet 3.37 inches` to `3 ft 3.37 in`), then the fraction of an inch (`~3 ft 3 in`), and finally the units themselves, for the prime marks everyone reads as feet and inches anyway (`~3'3"`). None of this changes what a tap types. The inserted amount is always the exact, full-precision result. Both chip types share the same wide look (`[icon] query → result [gear]`) and take the full width of the strip, since a recognized calculation or conversion is a better use of that space than word candidates. The date and weather chips further down this page are drawn the same way. ## Grouping long numbers Type a long run of digits on its own, like `1234567`, and the strip offers it back with separators in place. Tap the chip and the grouped number replaces what you typed. Which grouping you get follows the language you're typing in. English and most other languages get thousands, so `1234567` reads as `1,234,567`. Bangla, Hindi, Urdu, Nepali, Tamil and the other South Asian languages get the lakh and crore grouping, where only the last group holds three digits: `1,00,000`, `10,00,000`, `1,00,00,000`. **Grouping style** in [Options](#options) fixes one of the two if you'd rather not have it follow the language. A number typed in Bangla digits comes back in Bangla digits, and a decimal point you've already typed carries its fraction through untouched, so `1234567.89` offers `1,234,567.89`. The chip is drawn like the calculation and conversion chips, and its gear opens the Calculator with the number loaded. This chip is deliberately hard to trigger, since most long digit runs are not amounts. See [Why a chip stays quiet](#why-a-chip-stays-quiet) for the rules. ## Keyword chips open a tool Type a tool's keyword on its own (`wiki`, `calc`, and so on) and a narrower chip offers to open that tool, dropping the word you typed. The text-editing tool is the one exception: `edit` stays in the field, since that tool works on the text that's already there. This chip stays narrow instead of filling the strip, because the word you're typing might be meant as that word, and word candidates keep the rest of the row. smart/keyword-chip — Typing wiki offers a narrow chip to open Wikipedia, leaving room for word candidates. Each tool's own settings list the words that open it, and let you change or clear them. Matching ignores capitals by default, and a per-tool **Match capitals** switch makes that tool compare literally instead, which is how `AI` can open the AI tool without every auto-capitalised "Ai" doing the same. ## Chips that read the sentence Five more families watch for text that isn't a sum or a keyword but still sounds like a job a tool does. They ride the same master switch and each has its own toggle in [Options](#options). - **Resolve dates.** A forward-pointing phrase (`tomorrow`, `next friday`, `aug 14`, `the 15th`) gets a wide chip showing the date it lands on. A tap keeps your phrase and appends the date in brackets, so "next friday" becomes "next friday (21 Aug)". The gear opens the Calendar on that day. Only forward dates count, because "last friday" needs no annotating. **Date chip formats** (advanced) writes the date your own way: type one or more [date patterns](https://developer.android.com/reference/java/text/SimpleDateFormat), separated by `|`, such as `EEE d MMM | yyyy-MM-dd`. The chip shows the first and a tap writes it, so "tomorrow" becomes "tomorrow (2026-11-04)" with `yyyy-MM-dd`. With more than one, hold the chip to switch to the next. Month and day names follow the phone's language. A pattern that doesn't read leaves the chip's own wording. - **Answer weather questions.** `will it rain`, `how's the weather`, `how hot is it` and similar get a wide chip with the conditions on it. This one never types into your field, since the question was probably meant for the person you're writing to. Tapping it opens the Weather tool instead. It needs a weather location set, and shows a spinner while the forecast fetches. In the F-Droid edition it reads **Get the forecast** instead, and the first tap fetches the forecast rather than opening the tool, unless **Fetch the forecast as you type** is on in the Weather tool's options. - **Offer lookups.** `define serendipity` or `what does X mean` offers the Dictionary; `who is Tesla`, `what is X` or `tell me about X` offers Wikipedia. Narrow chip, and it leaves your question alone. - **Translate hints.** `how do you say`, `translate this`, or `in spanish` (sixteen language names are recognised) offers the Translate tool. Tap it after a sentence, as in `good morning in spanish`, and the hint words go from the field, the words before them on that line are selected, and Translate opens on them, already translated into the language you named, which also becomes its target. **Replace** then puts the translation where those words were. With nothing before the hint, it opens the tool and leaves your text alone. Turn on **Translate "in …" where it stands** under Tools / Translate to skip the panel: the tap puts the translation in place of the words and the hint, and the chip's Translate button still opens the tool. - **Ask home.** With a Home Assistant on the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/)'s list, a sentence that reads like a command for the house (`turn off the lights`, `dim the bedroom to 30%`, `lock the front door`, `open the blinds`) gets a chip that sends it to Home Assistant's Assist instead of leaving it in your message. Off with **Suggest commands as you type** on the tool's page. - **Location hints**, part of **Translate hints**. `I'm at`, `my location`, `where I am` or `here is my location` offers the [Location tool](https://wmkeyboard.pages.dev/tools/location/), so the place goes in right after your words. Narrow, and it leaves your text alone. - **GIFs for celebrations.** `happy birthday`, `congrats`, `eid mubarak`, `শুভ জন্মদিন` and a handful more offer a GIF search already pointed at the occasion. It falls back to the sticker panel when the GIF tool is off. Your own stickers work differently: they turn up by themselves when you type their title, a keyword or an emoji, and you send one with a single tap. See [Stickers as you type](https://wmkeyboard.pages.dev/emoji/stickers/#stickers-as-you-type). Each only fires when the tool it would open is switched on under Tools. If one of these switches is on while its tool is off, a note under the switch names the tool, with a **Turn on** button that switches it back on. The narrow ones are hints rather than answers, so each gets one shot per field: once you type past a lookup or intent chip, that tool won't hint again until you move to another field. One wiring quirk is worth knowing. **GIFs for celebrations** sits inside **Translate hints** rather than beside it, so turning the translate hints off takes the celebration GIFs with them, while turning the GIFs off leaves translate alone. ## Calling or texting a contact Typing / Smart chips / Call or text a contact Type the name of someone in your contacts and a narrow chip with their name and a phone icon comes up beside the word candidates. Tap the name and your phone app opens with their number already in the dialer, ready for you to press call. Tap the message button on the chip's right and your messages app opens addressed to them. The keyboard never places the call or sends anything itself, and the name you typed stays in your text, since you may have been writing it for the person you're chatting with. What counts as a name: - **The whole name, in any case.** `jane doe`, `Jane Doe` and `Mom` all work, so long as that's how the contact is saved. - **The first name alone, with its capital**, and only when no one else in your contacts shares it. `Jane` finds Jane Doe, but `jane` doesn't, and with two Marks saved `Mark` finds neither: a chip that has to guess who to ring would be worse than none. This is also what keeps `Will` the contact apart from `will` the word. - **The name right before the cursor**, or followed by one space. Type on and the chip moves on; punctuation straight after the name (`Mom,`) ends it too. A contact with several numbers is offered on the one you've marked as default in your contacts, and otherwise on the first one Android lists. Like the lookup and intent hints, each name gets one offer per field: once you type past it, that contact won't come up again until you're in another text box. The switch is **off** by default because it reads your address book. Turning it on asks for the contacts permission, after a screen explaining what's read. Names and numbers are held in memory only while the keyboard runs, are never written to a file, and never leave the phone. The chip doesn't show in password fields, or anywhere the strip is switched off. ## Repeated text Typing / Smart chips / Repeated text Type the same thing a few times in a row, `no no no`, `ok` on three lines, `hahaha`, and a narrow chip reads **Repeat "no"** with a small **×3** beside it. Keep typing the same word and the number ticks up instead of the chip reappearing. Tap the chip and the [Text repeat](https://wmkeyboard.pages.dev/tools/utilities/#repeating-text) tool opens on that text, with the same separator you used and a count ready that at least doubles what you typed (three becomes ten). The copies you typed stay in the field until you press **Insert** there. What Insert does with them is the tool's own setting: by default it takes them out and writes the whole count in their place, so the field ends with exactly that many; **Add after them** leaves them and writes the count after, with the separator between. What counts as a run: - **The same text, three times or more, ending at the cursor.** The number is a setting, 2 to 6. One trailing space or line break is fine, since a finished word usually has one; type anything else and the chip moves on. - **With a space, a comma or a new line between** any text counts, a single letter or emoji included: `a a a`, `5, 5, 5`, `😂 😂 😂`. - **With nothing between** the text has to be at least two characters and not itself a repetition: `hahaha` and `lollollol` fire, `!!!!!!`, `aaaaaa` and `😂😂😂` don't. A stretched letter or a doubled emoji is a message, not a request, and the tool's Stretch mode is a tap away. - **The shortest reading wins**, so `no no no no` is four of `no`, never two of `no no`. - **It has to start at the beginning of the text or after a space**: `xhahaha` is not a run. Like the other hints, each text gets one chip per field: type past it and `no` won't come up again until you're in another box. A keyword chip beats it (`wiki wiki wiki` still opens Wikipedia), and it beats a contact's name typed three times. The chip stays off in password fields and while the Text repeat tool is disabled. ## Pulling fragments out of your clipboard Open the [Clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) tool and a row of dashed-outline chips sits above the grid of copied items. It surfaces one-time codes, phone numbers, and links found buried inside your clipboard history, not only in your latest copy. The scan covers your recent clips, sorts codes before phone numbers before links, and shows up to a dozen chips at once. Long-press a chip to see the fragment highlighted inside the clip it came from. Tap it to paste only that fragment, leaving the full clip in history untouched. smart/clipboard-entity-strip — The Clipboard panel: dashed entity chips above the history, pulled out of several different clips. One fast path doesn't need the panel at all. Copy a bare one-time code, then tap into a box that asks for a code, and the strip offers it as a dashed **Code** chip in place of the usual "paste your last copy" chip. It's deliberately that narrow. A copied code counts as a secret, so it stays off the strip in an ordinary message field, and a code quoted inside a longer message never becomes a chip there at all: pulling a fragment out of a message is what the Clipboard panel above is for. The fast path is also OTP-only, so a freshly copied phone number or link shows as the ordinary full-clip paste chip until you open the panel for the fragment version. smart/otp-fresh-copy-chip — Copying a verification text offers only the code, not the whole message, right on the suggestion strip. Both surfaces are covered by the same setting (see [Options](#options)), and neither one scans a clip you've marked sensitive, or an image or file clip. A code you never copied has its own route to the strip. If you let the keyboard watch notifications, a verification code that arrives in one raises its own chip above the keys with no copy-paste step at all. That's a separate, off-by-default feature with its own page: see [One-time codes](https://wmkeyboard.pages.dev/smart/one-time-codes/). ## AI suggestions An optional chip, off by default, that comes from your [AI provider](https://wmkeyboard.pages.dev/tools/ai/#suggestions-in-the-strip) rather than from the text before the cursor. It offers one short continuation of what you're typing, in front of the word suggestions, and is marked with a sparkle so you can tell it from a word. A smart chip takes the strip over it. Tap it to insert the continuation, or hold it to hide AI suggestions in this field. It sends text without a tap, which is why it lives with the AI settings and says there exactly what each request carries. ## Why a chip stays quiet Each detector has guards against the shapes of text that look like a trigger but usually aren't: - **Dates** (`12/04`, `12/04/2025`, `2024-07`) never read as arithmetic. A zero-padded pair reads as a date. A lone `-` between two plain numbers is a sum (`12-4` offers `8`) unless either side has four digits or a leading zero, since `555-1234`, `2024-25` and `2024-07` are a phone number, a season and a month. Either way, an explicit `=` forces the answer. - **Order numbers and prices** don't turn into phone numbers. A leading `#`, `$`, `£`, or `€` rules a run of digits out. - **Years** don't turn into codes. A bare 4-digit value between 1900 and 2099 is rejected even next to a code-sounding word. - Google's `G-123456` SMS prefix is stripped before the code check, since it's branding rather than part of the code. - A code needs a nearby keyword (`otp`, `verification`, `code`, `pin`, `2fa`, and similar) within 40 characters. A deny-list (`zip`, `postal`, `hex`, `swift`, `ifsc`, and others) stops things like "zip code 90210" from qualifying anyway. - **`try`** in lowercase reads as the English verb, not Turkish lira. Type `TRY` in capitals to convert it. - Short unit abbreviations (`c`, `f`, `in`, `s`, `k`, and others) only count glued to the number, so "meet me in 5" or "about 30 c out" stay quiet. - **Long numbers** are only offered grouped when they read as a quantity. The run needs five digits at least and fifteen at most, and a leading zero rules it out, since that marks a phone number, an account or a PIN. It also has to stand on its own: anything joined to a letter, a `.`, `-`, `/`, `:`, `#` or `+` on its left is a version, a date, a ratio or an identifier, and a number that already carries separators is left as it is. Currency, unit and calculation chips all win over it, because a number that means something is worth more than a number that is merely long. And it stays out of fields that ask for a number at all, such as an amount box, a card form or a PIN, since those want the digits they asked for. - **Repeated text** needs three copies (or whatever the setting says) ending right at the cursor, and never reads a single stretched letter or a doubled emoji as a run. See [Repeated text](#repeated-text). - Unpunctuated digit runs longer than 11 digits aren't treated as phone numbers unless they start with `+`. That shape rule is what stands in for knowing where you live, so it steps aside once you've listed your own [phone formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/) and the masks decide the length instead. ## Options Typing / Smart chips smart/options — Smart chips settings under Typing: master switch and the four per-family toggles. - **Smart chips**: the master switch, default **on**. Turn it off and all twelve chip families below go at once. - **Calculate as you type**: default **on**. - **Convert currencies**: default **on**. - **Convert units**: default **on**. - **Tool keywords**: default **on**. - **Resolve dates**: default **on**. - **Answer weather questions**: default **on**. - **Offer lookups**: default **on**. - **Translate hints**: default **on**. - **GIFs for celebrations**: default **on**. - **Group long numbers**: default **on**. - **Grouping style**: **Automatic**, **Thousands** or **Lakh and crore**, default **Automatic**, which follows the language you're typing in. - **Call or text a contact**: default **off**. Needs the contacts permission. See [Calling or texting a contact](#calling-or-texting-a-contact). - **Repeated text**: default **on**. See [Repeated text](#repeated-text). - **Chip after this many repeats**: 2 to 6, default **3**. Shown while Repeated text is on; the same row lives on the Text repeat tool's page. - **Keep the text when a chip opens its tool**: default **off**. Leaves the typed amount, sum or tool keyword in place when the gear opens the tool. Each tool's own page (Calculator, Unit converter, Currency converter) links back here with a **Suggestion chip** row that shows whether its chip is on. The switches themselves live only under Smart chips. A few related settings live under each tool instead: - **Degrees** (Calculator, default **on**) and **Result precision** (0–12 decimal places, default **8**). Precision also shapes the unit converter's output. - **Decimal places** for currency (0–6, default **2**), **Write the result as** (Name, Symbol or Code, default **Name**) and **Refresh the rates every** (1–48 hours, default **6**), under Tools / Currency converter / Options. - **Convert coins** (default **on**), **Coin decimal places** (Auto to 12, default **Auto**), **Refresh coin prices every** (1–60 minutes, default **5**) and the **Coins** chip grid, under Tools / Currency converter / Cryptocurrency. The same screen picks which source each kind of rate is fetched from. - **Detect codes, numbers and links** (default **on**), under Clipboard, governs the Clipboard panel's entity strip. The fresh-copy code chip is a separate pair of settings on that same screen: **Suggest the last copy** (default **on**), since the code chip is a variant of that chip, and **Suggest copied codes** (default **Any field**), which is what allows a copied code onto the strip at all. ## Details & edge cases - A chip won't reconsider text it just inserted. After you tap one, the strip mutes until you edit the field again, so converting 150 USD to Taka doesn't immediately offer to convert the Taka amount right back. - Smart chips step aside for secure fields, fields that ask for no suggestions, any open panel, and CJK-style conversion composing, since a chip would otherwise steal the space a pinyin reading needs for its own candidates. A reading that spells a tool's keyword still offers that tool, as a chip behind all the candidates with an arrow after its name, unless **Shortcuts while converting** is off. - **Chips on the far side**, under Typing › Automatic corrections, moves the chips that share the strip with the words to the end of the strip, where a right thumb reaches them: a one-time code, a copied clip, a keyword, and the chip that joins two words or fixes an earlier one. The words stay in their order beside them. A sum or a date fills the strip either way. - On a conversion layout, every chip comes back once the reading is committed, so `12*4` answers on Pinyin as it does on English. A wide chip (a sum, a date, a conversion) covers the next words until you carry on typing. Every tool's keywords include Chinese, Japanese and Korean words, and a Chinese or Japanese keyword counts at the end of a longer run of text, so 我的笔记 offers Notes. - They're independent of word prediction. Turning off suggestions elsewhere doesn't turn off any of the smart chips. - [Power saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) can flip the master switch off (its own **Smart chips** toggle lives under Power saving, off by default), so every chip family goes at once, but it leaves clipboard entity detection alone. Codes, numbers, and links keep getting pulled out of your clips even while chips elsewhere are dimmed. - Exchange rates come from open.er-api.com (about 160 currencies, updated daily), with frankfurter.dev (European Central Bank, about 30 currencies) as a fallback. Coin prices come from Coinbase with the jsDelivr currency-api behind it. The F-Droid edition starts both from currency-api, which is open data, with Frankfurter behind the currency rates. None of them needs an account or a key, and any of them can be swapped for another under the tool's settings. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else touches the network. - A coin chip shows the ticker (`BTC`) where a currency chip shows a name (`Taka`), because coin names shorten badly. It also keeps five meaningful digits rather than two decimals, so a small fraction of a coin still reads as a number instead of `0.00`. - The currency and unit pair a chip converts into isn't set from a Settings screen, unless you name the unit in the text (`5 kg to lb`). It's remembered from whichever pair you last used inside the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) or [Unit converter](https://wmkeyboard.pages.dev/tools/utilities/) panel itself. - Chips answer what you typed. [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) answer what you selected: a different bar, a different trigger, and its own switch under Advanced. The two never share the strip, because nothing is being typed while text is selected. --- # Contact-aware typing URL: https://wmkeyboard.pages.dev/smart/contacts/ Section: Suggestions & correction Summary: Your contacts' names and emails, suggested where they help, never uploaded. WM Keyboard can complete names and email addresses straight out of your contacts, and app names straight out of your launcher, so `"was"` completes to Wasi and `"john"` completes to `john.doe@gmail.com`. All three sources are off by default, and everything they read stays in memory on the device. Nothing is written to disk and nothing is sent anywhere. smart/contact-name-suggestion — A contact name completing in the suggestion strip, mid-word. ## What gets suggested Three independent sources feed the suggestion strip, each with its own toggle in Typing settings: - **Contact names.** Every contact's display name is split into words (first name, last name, and any middle parts) and offered as a completion, the same way a dictionary word would be. After you finish typing one name, the next part of that same contact's name is offered as the next word: type "Wasi" and "Mollik" appears next, the way a learned bigram would. - **Contact emails.** Type the first couple of characters of an address, from the very start, the way you'd begin typing the name before the `@`. The full address is offered to complete. This works both in ordinary text and, with a second toggle, inside email input fields. - **Call or text.** Separate from completion: with **Call or text a contact** on under Smart chips, a finished contact name offers to open the dialer or your messages app for that person. See [Calling or texting a contact](https://wmkeyboard.pages.dev/smart/chips/#calling-or-texting-a-contact). - **App names.** The labels of apps that appear in your launcher complete the same way ("sign" → Signal), so you don't have to type an app's full name out to search or reference it. This needs no permission. It comes from the same package list Android uses to build your home screen and app drawer. A word that matches a known contact name or a known app name is never "corrected away" by autocorrect. Contact-email addresses aren't typed out character by character the way autocorrect operates, since you complete one by tapping it from the strip, so this exemption doesn't separately cover them and doesn't need to. ## Using it ### Contact names in the strip Turn on **Suggest contact names** and start typing a contact's first or last name. It completes like any other word, ranked above ordinary dictionary and learned words. It works in almost every language mode, not only English, since all it does is match against words extracted from your address book. The Bangla phonetic (Avro) layout is the exception. While you're composing a romanized buffer there, the strip is the Bengali candidate list and nothing else, so contact names, contact emails and app names aren't offered as completions. The name chaining still works: finish a name and the next part of it is still offered as the next word, because that path runs before the Bengali one takes over. ### Contact email completion Turn on **Suggest contact emails** and type the start of an address anywhere in ordinary text. Two characters are enough to trigger it, and a single letter deliberately isn't, or one keystroke would dump the whole address book onto the strip. The strip offers matching full addresses from your contacts, ranked above everything else in the strip. A second setting, **Contact emails in email fields too**, extends this into fields the keyboard recognizes as email inputs specifically. Those fields keep no composing buffer the way an ordinary text field does, because letters you type there commit straight to the field one at a time. That leaves the everyday word-completion engine with nothing to work with, independently of Typing / Suggestions / Suggestions in every field. This setting makes an exception for contact-email completions only. It reads the address-in-progress straight from the field instead of from a composing buffer, so the one place you're most likely to want an email address offered is the one place ordinary suggestions can't reach. It only has an effect while **Suggest contact emails** itself is on, and its row only appears in the settings list once that parent toggle is on too. It also needs the master **Suggestions** switch on, and it stands down in a password field like everything else. Tapping a suggested email in an email field commits the whole address with no trailing space, since an address is usually the entire field. It also stays out of your personal dictionary, because an address isn't treated as a word to learn. ### Addresses you type yourself **Remember email addresses you type** (off by default, no permission needed) covers the addresses that aren't in your contacts: your own, and the ones you sign in with. Each time you leave a field that asks for an email address, the keyboard keeps what it holds, as long as that's a whole address (an `@` with a name before it and a domain with a dot after it). The next time an email field opens, the addresses you've used most show on the suggestion strip before you type anything, and typing the start of one narrows the list. With **Suggest contact emails** on too, your own addresses come first and your contacts' follow once you've typed two characters. It learns only from fields that call themselves email fields, never from ordinary text, and it keeps your 50 most recently used addresses. Unlike everything else on this page, these are written to the phone's storage, in the learning data, so they survive a restart. Delete them from the Storage screen in the settings, which lists them as **Email addresses you typed**. ### App names in the strip Turn on **Suggest app names** and installed-app labels complete the same way contact names do, but ranked lower. Ordinary words that happen to also be app names (Files, Photos, Clock) shouldn't outrank the dictionary, and you type a contact's name far more often than you reference an app by name. App names complete in the suggestion strip only. They don't extend into the system-wide spell checker below. ## Options Typing / Suggestions / Suggest contact names | Setting | Default | Needs | | --- | --- | --- | | Suggest contact names | Off | Contacts permission | | Suggest contact emails | Off | Contacts permission | | Contact emails in email fields too | On (only shown while contact emails are on) | Nothing | | Remember email addresses you type | Off | Nothing | | Suggest app names | Off | Nothing | Turning on either of the two contact toggles requests the Contacts permission the first time, if it isn't already granted. Turning a toggle back off never prompts for anything. Both toggles use the same Android permission, so granting it for one covers the other automatically. App-name suggestions need no permission at all. They're covered by the app's existing ability to list installed launcher activities. Turning any of these three off clears its in-memory index immediately. There's no lingering suggestion from data the keyboard is no longer supposed to be reading. ## Contact names in the system spell checker WM Keyboard can also act as Android's system-wide spell checker, the service other apps' text fields use for the red squiggly underline. It's a separate component that you select yourself, under Android's own **Settings → System → Languages & input → Spell checker**, where it's listed as "WM Keyboard spell checker". Installing the app doesn't switch it on automatically. When selected, it reads the same **Suggest contact names** setting described above (there's no separate toggle for it) to decide whether to load your contacts. If that's on, the spell checker treats known contact names and emails as correctly spelled, so no red squiggle appears under a name it recognizes. It also puts a matching contact name first in the correction menu when you've mistyped one, ahead of the word-list fixes. Because it runs in its own process, it works this way in any app, even while WM Keyboard isn't the active keyboard. See the [system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) page for the rest of what it catches. ## Details & edge cases - **What's read.** A one-shot query against `ContactsContract` for contact display names, and a separate one for contact email addresses, each covering the whole address book in a single pass rather than filtering or searching incrementally. App names come from querying installed launcher activities, the same list your home screen's app drawer is built from. - **What's stored.** Nothing on disk for contacts or app names. Contact names, contact emails, and app names are held as plain in-memory lookup tables, rebuilt from scratch whenever the keyboard process starts with the relevant setting on, and dropped the instant you turn that setting off. If a query fails for any reason, such as a permission revoked mid-session or an unavailable provider, the source silently falls back to empty rather than crashing or retrying in a loop. - **What never leaves the device.** There's no network code anywhere in this path. Contacts, contact emails, and app names are read, indexed, and matched entirely locally. - **Typing a name is still "learning."** The memory-only guarantee covers the index this feature builds. If you type a contact's name as ordinary text, that word goes through the keyboard's general on-device learning path like any other typed word. That's a separate, on-by-default setting, unrelated to the toggles on this page. There's also a distinct, off-by-default setting that would also copy a learned word into Android's own shared personal dictionary, which other apps can read. Leave it off and this doesn't apply. See [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) for both. - **Before you unlock the phone.** Right after a reboot, before you've entered your device credential the first time, all three toggles behave as off regardless of their saved state. Android's contacts and app-label providers are themselves locked at that point and can't be read no matter what the setting says. Nothing about your saved preference changes. The moment you unlock, contact and app suggestions resume on their own. - **Name splitting is punctuation-aware.** A contact's display name is broken on spaces, commas, and similar separators, with stray punctuation trimmed from each piece. A name with a middle initial, or a bracketed label like "(work)", still indexes its meaningful parts rather than the punctuation around them. Very short fragments (a single letter) aren't indexed, which keeps initials from cluttering the strip. Matching is case-insensitive, and the name is still shown with its original capitalization when suggested. - **Only real-looking addresses are indexed.** A contact's stored email is only used if it has an `@` with something on both sides and no embedded spaces. Malformed entries in your address book are silently skipped rather than offered as junk completions. --- # Grammar check URL: https://wmkeyboard.pages.dev/smart/grammar/ Section: Suggestions & correction Summary: Grammar checking with Harper on the phone, GiellaLT's checkers for the Sámi languages and Faroese, or LanguageTool on a server for more than 30 languages. Grammar check runs your writing past Harper, the same open-source engine behind `harper-ls`, and surfaces fixable issues right above the keys as you type. Harper runs on the phone, so by default nothing you type leaves it to get checked. For other languages than English you can switch on [LanguageTool](#checkers-harper-and-languagetool), which checks on a server instead, and for North, Lule, South and Inari Sámi and Faroese you can download [GiellaLT's own checker](#giellalt-grammar-checkers), which runs on the phone. ## What it catches Harper checks English spelling, grammar, and style: agreement errors, punctuation and capitalization slips, redundant or repeated words, malapropisms, and a long tail of style nits. It reports twenty fine-grained kinds of issue, which the panel groups into four color-coded categories so an issue reads at a glance: - **Correctness**: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage - **Clarity**: readability, redundancy, repetition, word choice - **Engagement**: enhancement, style, miscellaneous - **Delivery**: formatting, regionalism, nonstandard Any of those twenty can be switched off, either one at a time or a whole category at once. See [Filtering what you see](#filtering-what-you-see). Each issue also lints against a specific English dialect: **American** (the default), **British**, **Canadian**, or **Australian**. You pick that independently of whatever language or layout you're typing with, since Harper only understands English. See [Options](#options) below for where to change it. ## Checkers: Harper and LanguageTool Tools / Grammar check / Checkers The **Checkers** list at the top of the settings page says who checks your writing. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), so rows can be dragged into another order, switched off, set up or removed. It starts with two rows: - **Harper**, on the phone and switched on. English only. - **LanguageTool**, switched off. It checks grammar, spelling and style in more than 30 languages, among them German, French, Spanish, Portuguese, Dutch, Polish, Ukrainian and Russian, but it runs on a server. Its row says where your text goes: **Sends your text to api.languagetool.org**. A check goes to the first checker that is on and knows the language of the layout you're typing with. Harper knows English; LanguageTool knows every language it has, and a language it lacks goes as `auto`, so the server works out what the text is. With the list as it starts, then, English stays on the phone and the other languages go to LanguageTool once you switch it on. Drag LanguageTool above Harper to have it check English too. When the first checker cannot answer (Harper isn't in this build, the server is down), the next one that knows the language answers instead. With no checker that knows the language, Harper checks anyway, as Grammar check always did. While LanguageTool answers, the panel's title reads **Grammar · LanguageTool**, or the nickname you gave the server. Its issues use the same cards, the same four categories and the same filter as Harper's: each LanguageTool category maps onto one of the twenty kinds (its typos onto Spelling or Typo, its casing onto Capitalization, its confused words onto Usage, and so on). English checks follow the dialect chip: American, British, Canadian or Australian. ### What goes to the server Every check sends the text in the field, its language code, your Premium username and key if you set them, and the two options below. Nothing else. The panel checks after each pause in typing, but never sooner than 1.2 seconds after the last key while LanguageTool is first in line. **Fix all**, a fix chip and the **Grammar** [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/) re-check the result the same way. These are never sent: - a **password field**, or any field an app marks as one - anything typed in **[incognito](https://wmkeyboard.pages.dev/privacy/incognito/)**, whether you turned it on or the field asked for it - anything while [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds **Online grammar check** In each of those cases Harper still checks English, and for other languages the panel says why nothing is checked. Every request appears in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) as **LanguageTool**. ### Servers and accounts Tap the LanguageTool row to set it up: | Field | What it does | | --- | --- | | Server address | Empty for LanguageTool's public server, `api.languagetool.org`. Or the address of a server you run yourself, for instance `http://192.168.1.20:8081` | | Username and API key | A [LanguageTool Premium](https://languagetool.org/premium) account. Give both or neither. With both and no address, checks go to the Premium server, `api.languagetoolplus.com` | | Picky mode | Also report the style and typography issues the server leaves out by default | | Your first language | A code such as `de` or `fr`. LanguageTool then also flags false friends from that language | **Test** asks the server for its list of languages, so a typo in an address shows up there rather than in the panel. LanguageTool's server is free software (LGPL), so you can run your own on a computer at home, or anywhere else, from the [jar](https://dev.languagetool.org/http-server) or one of the Docker images. Your text then never reaches a third party. Add it with **Add a provider / LanguageTool**: each server is its own row, with its own address and nickname. This is the same in every edition, F-Droid's included. The public server takes 20 checks a minute, 75 KB of text a minute and 20 KB a check. Grammar check keeps to that: a long note is cut into pieces of at most 18 KB at paragraph ends, then sentence ends, then spaces, and a piece already checked isn't sent again until it changes. If a minute's allowance is used up, the next check waits for it when that takes seconds, and the panel says the server is busy when it would take longer. Edit the text and it tries again. A Premium account or your own server is sent bigger pieces, up to 60 KB, with no pacing. ## GiellaLT grammar checkers Tools / Grammar check [GiellaLT](https://giellalt.github.io/), the language technology group at UiT The Arctic University of Norway, writes grammar checkers for the languages it works on. Five have a published release, and the grammar settings list them under **GiellaLT grammar checkers** at the Advanced level, each with a **Download** button: | Language | Download | |---|---| | North Sámi | 1.5 GB | | Lule Sámi | 375 MB | | Faroese | 353 MB | | South Sámi | 290 MB | | Inari Sámi | 186 MB | Each is one file holding the whole checker: its tokeniser, its rules, the speller it suggests spellings from and its messages. It is kept as downloaded and read in place, so its size is what it takes on the phone. The download is checked against the release's SHA-256 before it replaces anything. With a checker downloaded, it checks first whenever the keyboard is in its language, before anything on the **Checkers** list, and it needs no row there. It runs on the phone, so nothing leaves it. The panel's title then reads **Grammar · GiellaLT**, and each card shows GiellaLT's title for the error, its explanation (in your phone's language where the checker has one, else English) and its suggestions. The first check after the keyboard starts loads the checker, which takes a few seconds; later checks do not. A loaded checker also takes memory, roughly half its download: North Sámi's holds about 650 MB, so on a phone with 4 GB or less, Android may close the keyboard while it is loaded. The keyboard unloads it with its other models once it has been hidden for 2 minutes, and [Resources](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-keyboard-holds-in-memory) lists it while it is loaded, with a button to unload it sooner. The checkers run on GiellaLT's divvun-runtime, which is a download of its own the first time you download a checker. Unlike the rest of the app it is GPL-3.0, because its VISL CG-3 rule engine is. ## Using the panel smart/grammar-toolbox-entry — Grammar check in the toolbox, with the dialect chip and a live issue count. Open **Grammar check** from the toolbox to check whatever's in the field you're writing in. It isn't pinned to the toolbar by default, but two built-in [keyboard modes](https://wmkeyboard.pages.dev/typing/modes/) (**Chat** and **Writing**) promote it to the front of the toolbox, since it's one of the first things worth reaching for in either. The panel sits in a fixed strip above the key rows rather than covering them, so you can fix an issue by editing the text directly as easily as by tapping a fix chip. Open the panel and it checks the field's current text immediately, with no wait. After that, edits re-check after a short pause (see [Fully offline](#fully-offline-in-process) below). Each issue is a card: a colored dot and category label, the flagged text struck through, an arrow to up to five suggested fixes, and Harper's explanation underneath. An issue Harper has no fix for shows the explanation in the chips' place instead. From there: - **Tap a fix chip** to apply that one fix. The field re-checks straight after. - **Tap the card itself**, meaning neither a chip nor the dismiss icon, to jump the field's cursor to the issue. A multi-word span parks the cursor at its start. A single word with a real replacement gets selected, so you can type over it. Anything else parks the cursor right after the issue. - **Tap the copy icon** to copy Harper's explanation. - **Tap the sparkle** beside the dismiss icon, shown while the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) is on, to ask the AI about the issue. The AI panel opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#asking-about-another-tools-text) with the issue, Harper's explanation, its fixes and the sentence around it attached, so you can ask why, or whether the fix is right. - **Tap the dismiss icon** (**×**) to hide a card. It stays hidden only until the text changes and re-lints. Dismissing isn't remembered past that. - **Tap "Fix all"**, shown whenever at least one issue has a suggestion, to apply every issue's top fix in one go. Fixes go in back to front, so earlier issues don't shift the ones after them. An issue whose span overlaps one already fixed is skipped rather than risk mangling the text. - **Tap the dialect chip** in the header to switch American/British/Canadian/Australian on the fly. The same text re-checks immediately under the new dialect. - **Tap the funnel** beside the dialect chip to choose which kinds of issue are worth showing at all. See below. If nothing's flagged, the panel says so. If the field is empty, it invites you to type or to open a field with text in it. If issues were found but your filter hides every one of them, the panel says that instead, so a quiet panel is never ambiguous. ## Filtering what you see Harper is thorough, and thoroughness is not always what you want. If you don't care to be told about regionalisms, or you want spelling but not style suggestions, the funnel in the panel header turns individual kinds off. The filter is two levels deep: - **Tap a category row** (Correctness, Clarity, Engagement, Delivery) to switch every kind inside it at once. - **Tap the chevron** on a category to expand it onto its own kinds, then tap one to switch only that kind. - A category shows a **tick** when all of its kinds are on, a **dash** when only some are, and nothing when they're all off. Turning one kind of an off category back on leaves the category dashed rather than quietly re-enabling the rest. - Every row carries the number of issues of that kind in the current text, filter or no filter, so you can see what you're hiding before you hide it. - **Show every kind** at the bottom clears the filter in one tap. It only appears while something is hidden. The funnel turns accent-colored while any filter is active, so a panel that's hiding things says so from the header. Filtering is not the same as dismissing. A dismissed card comes back on the next re-check; a filtered kind stays gone until you turn it back on, and the choice is remembered across sessions and carried in your [backup](https://wmkeyboard.pages.dev/reference/settings/backup/). A hidden kind is hidden from everything the panel does, not only the cards: it's left out of the issue count in the header, and "Fix all" will not touch it. Turning off a kind of suggestion you disagree with means "Fix all" stops applying it, which is usually what you want. The same filter lives in settings, where the four categories are folds you can open onto their kinds. Changing it in either place changes it in both. ## Fully offline (in-process) Harper is a Rust engine compiled into `libharper_jni.so` and loaded directly into the keyboard's process. There's no server call anywhere in this path, checked or unchecked. All linting runs on one dedicated background thread, since Harper's internal linter state isn't safe to share across threads. Re-checks are debounced. After you stop typing, the panel waits before re-linting rather than running on every keystroke (see the **Re-check delay** setting below). Opening the panel or switching dialects skips the wait and checks right away. The very first check after opening the panel can take a beat longer, since building Harper's linter costs roughly 100 ms the first time. That cost is already paid by the time you open the panel if [Harper is also set as your system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/). Its own service warms the linter up on its own. Opening the Grammar tool alone doesn't pre-warm it. ## DeepL Write With a DeepL API Pro key set up under [Tools / Translate](https://wmkeyboard.pages.dev/tools/translate/#deepl) and **DeepL Write** turned on there, the panel gets one more button beside the filter. Tap it and DeepL rewrites the text in the field: grammar, wording, and the style you picked. Unlike Harper, DeepL Write reads German, French, Spanish, Italian, Portuguese, Japanese, Korean and Chinese as well as English. The answer takes the place of the issue list until you tap **Replace** or close it with its X. **Replace** swaps the text for DeepL's version. On a field longer than 2,500 characters, only the first 2,500 go to DeepL and only those are replaced. If you edit the text before the answer arrives, the answer is dropped, because it no longer matches what is in the field. Apart from [LanguageTool](#checkers-harper-and-languagetool), when you switch it on, this is the one part of Grammar check that goes online. The text in the field and your DeepL key go to DeepL, and only when you tap the button. Without a DeepL key, or with DeepL Write off, the button is not there and the panel works as described above. It's never there in a password field either, so a password can't be sent to DeepL. The same rewrite is available on selected text as the **DeepL Write** [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/). ## Options Tools / Grammar check smart/grammar-settings — The Grammar check settings screen: dialect, re-check delay, and the system-wide group. | Setting | Default | Range/options | | --- | --- | --- | | Checkers | Harper on, LanguageTool off | Harper, LanguageTool servers; see [Checkers](#checkers-harper-and-languagetool) | | English dialect | American | American, British, Canadian, Australian | | Re-check delay | 350 ms | 100–1500 ms | | Show Correctness issues | On | Per kind: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage | | Show Clarity issues | On | Per kind: readability, redundancy, repetition, word choice | | Show Engagement issues | On | Per kind: enhancement, style, miscellaneous | | Show Delivery issues | On | Per kind: formatting, regionalism, nonstandard | Each category is a fold. The heading's switch turns the whole category on or off; open it to reach the individual kinds, and the closed fold names the kinds still showing. These are the same switches as the panel's funnel. A **System-wide** group sits below these two, with a **Use Harper everywhere** button and (on Android 12 and up) an **Underline only, no fix popup** toggle. Those set up WM Keyboard as Android's system spell checker, which adds Harper's grammar marks in other apps on top of word-list spelling. See [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) for what that covers and how it differs from this in-keyboard tool. ## Details & edge cases - **Harper is English only.** It doesn't check any other language, and the dialect setting is global: it applies no matter which typing language or layout is currently active. For other languages switch on [LanguageTool](#checkers-harper-and-languagetool), or use the optional [DeepL Write](#deepl-write) button. - **The system spell checker is always Harper.** The Checkers list only decides who checks in the Grammar panel. What Android's spell checker marks in other apps comes from Harper on the phone, whatever the list says. - **Every build.** Grammar check and the spell checker's grammar marks are in every build. F-Droid compiles Harper from source. A build made without Harper's native library shows the tool as not in this build, and its spell checker checks spelling only. - **A missing engine fails safely.** If `libharper_jni.so` didn't load, the panel opens normally and says the grammar engine isn't in this build, rather than crashing or hanging on "checking." - **A fix only touches the words it flagged.** Applying a chip (or "Fix all") replaces the flagged spans and nothing else, so the rest of what you wrote isn't re-typed into the field. That's what keeps formatting alive in apps that have any: bold, colour, a checklist item in Google Keep. The one exception is an editor that won't tell the keyboard where its text starts. There's no way to address a span in one of those, so the fix falls back to rewriting the whole field, which flattens any styling it was carrying. - **Dismissing an issue is temporary.** A dismissed card comes back if you edit the text again and it re-lints to the same issue. There's no permanent "ignore this" list. - **"Fix all" only uses each issue's first suggestion.** For an issue with several possible fixes, use its individual fix chips instead if you want a different one applied. - **A filter hides issues, it doesn't stop the checking.** Harper still lints the whole text; the filter decides what's worth showing you. Turning kinds off doesn't make checking faster. - **An unfamiliar kind is always shown.** If a future Harper adds a kind of issue this version of the app doesn't know, it appears in the panel (under Engagement's color) rather than being silently swallowed by a filter that can't name it. - **A LanguageTool error takes the issues' place.** No connection, a refused key or a busy server shows as a line in the panel, unless the next checker on the list could answer. The next edit checks again. - **Re-check delay only paces this panel.** It has no effect on [the system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/), which always answers synchronously when another app's text field asks it to. For contact-aware, dictionary-based red squiggles in every other app on your phone, not only WM Keyboard's own writing surface, see [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/). For the everyday typo-fixing that runs on every language and every build, see [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/). --- # Mixed-language typing URL: https://wmkeyboard.pages.dev/smart/mixed-language/ Section: Suggestions & correction Summary: The keyboard reads which language the message in front of you is written in, and leans suggestions and autocorrect that way until you switch back. Plenty of people type two languages into the same box, on the same keyboard, in the same message. Banglish is the obvious case: "ami tomake" and "how are you" are both typed on a Latin keyboard, and both are ordinary sentences to the person writing them. Left alone, the suggestion engine treats one of those languages as the real one and the other as a stream of typos. **Detect the language you type** fixes that by reading the words already in the field. Type "ami tomake" on the English keyboard and it starts behaving like the Banglish one. Type "how are you" and it swings straight back, usually within two or three words. smart/language-detection-switch — The suggestion strip following a message mid-switch: English words, then Banglish ones. ## What it needs to work at all Detection is on by default, but it is inert unless the language you're typing has other languages to lean toward. That means **Also suggest from** has to name at least one other language on the language's own screen (see [Languages overview](https://wmkeyboard.pages.dev/languages/overview/)). Romanised languages get that link for free. Enable Banglish (`bn_rom`) alongside English and the two are paired both ways automatically, because a romanised language and its Latin-script neighbour are exactly the pair this feature exists for. A hand-made link you added yourself is never overwritten or duplicated by that pairing. With only one language in the mix, or with the field still empty, nothing shifts. The keyboard behaves exactly as if the feature didn't exist. ## How the field is read Every word committed in the field is credited to each language whose dictionary knows it. A word both languages know pushes neither ahead, which is what keeps names and shared loanwords from voting. Older words fade fast. Each new word decays the running tally to 70% of what it was, so roughly the last three words are in charge and a mid-sentence switch takes effect almost immediately. When you focus a field that already has text in it, the words already there are read in oldest-first, so the same decay leaves the words nearest your cursor in charge. A run of words nobody recognises (typos, names, a third language entirely) still decays the tally without adding to it, so the mix drifts back to neutral rather than hardening into a guess. Nothing here is written down: the reading is per field, held in memory, and thrown away when you move on. ## Where you are typing Detection starts every field from nothing, and nothing is exactly what an empty message box has. So the keyboard also keeps a tally, per app, of which of your languages the words you type there belong to, and starts detection in that app's fields from the tally: a new message in a chat app you write Banglish in leans Banglish before you have typed a word, and a new email starts out English. The lean is deliberately small. It is worth about one typed word of evidence at most, so the first word you type outweighs it and the second settles the matter. A chat app you also write English in is never fought: type an English word and the field is English. Apps with only a few dozen words of history lean less, the tally forgets slowly (roughly the last hundred words), and a word both languages know is credited to both and moves it nowhere, the same rule the field itself uses. **Remember each app's language** under Typing / Automatic corrections turns this off. The tally is private storage, wiped with the rest of the learned data, and never leaves the device. ## The three strengths Typing / Automatic corrections / Detection strength Once the field has clearly gone one way, the detected language gets a ranking boost and its rival takes the same size handicap. **Detection strength** is how big that swing is allowed to get: | Strength | What it does | | --- | --- | | Gentle | Reorders the strip only. The detected language's typo targets never overtake the on-screen language's common words, so autocorrect stays with the keyboard you're on. | | Balanced (default) | Lets a clearly detected language take the lead in ranking, and autocorrect follows for everything but the most common rival words. The ×4 confidence gate absorbs the contested middle. | | Aggressive | A full role swap. Autocorrect fixes typos in the detected language even against top-frequency words of the language the keyboard is set to. | Balanced is the shipped default and the one to leave alone unless something specific is annoying you. Go Gentle if autocorrect keeps reaching into the other language when all you wanted was better suggestions. Go Aggressive if you write whole messages in the language that isn't your keyboard's and you want it treated as the real one. Whichever strength is set, the swing scales with the evidence. One classified word moves the mix about halfway, three make it certain, and a field split evenly between the two languages moves nothing at all. ## English on Avro and Hindi phonetic A phonetic layout is the one place the two languages cannot share a keyboard the ordinary way. You type Latin letters and the layout turns them into Bangla, Devanagari or the Urdu script, so an English word has nowhere to go: `hello` comes out হ্যালো, and one English word in a Bangla message means switching layouts twice. Add English under **Also suggest from** on Bangla's, Hindi's or [Urdu's](https://wmkeyboard.pages.dev/languages/urdu/) own screen (or that of any language with a [phonetic layout](https://wmkeyboard.pages.dev/languages/phonetic/)), and that changes in two steps. **The strip always carries what you typed.** The word exactly as you typed it, in Latin letters, sits on the last visible chip of the strip. The Bangla or Hindi suggestions keep their usual places in front of it, so nothing you reach for moves. Tap the chip and the word is written in English. (If you would rather the typed word always sat on the left with the phonetic reading beside it, turn on **Keep the first two words in place** on the language's screen; see [Bangla](https://wmkeyboard.pages.dev/languages/bengali/#options).) Once the field has turned English (you tapped that chip, or the text already there is English) the strip leans the same way: the Bangla word stays first, because that is still what the space bar writes, and English completions take the rest. **Type English words as English** goes one step further, and is off until you turn it on. It sits on the language's own screen, in an **English words** group that appears under Also suggest from once English is picked there (Languages & layouts / Bangla / English words / Type English words as English), and it is set per language. Khipro has a switch of its own in the same group, also off by default, so this one only changes Avro (see [Typing with Khipro](https://wmkeyboard.pages.dev/languages/bengali/#typing-with-khipro)). With it on, the space bar itself writes an English word in English: | You type | You get | Why | | --- | --- | --- | | `kemon` | কেমন | only Bangla knows it | | `because` | because | only English knows it | | `hello`, `keyboard` | hello, keyboard | listed as English loanwords, so the spelling is English | | `to`, `are`, `man` | তো, আরে, মান in a Bangla or empty field; to, are, man once the field is English | both languages have the word, so the field decides | | `am` after `I`, `are` after `how` | am, are | the word before counts most: a pair English writes settles a close call, and so does a pair Bangla writes | | `I` on its own | I | on Avro a lone capital I spells the letter ঈ, which is never a word | | a name neither list has | the layout's own script | unless the last couple of words were English | The text in the field shows the same answer while you are still typing, so `hell` already reads hell and nothing changes under you at the space. An English word at the start of a sentence gets its capital if the field asks for one, since a script with no capitals never arms shift for you. A capital letter anywhere after the first always means Avro, because `T`, `D`, `N` and `O` are letters of their own there. What counts as "the field is English" is the same reading described at the top of this page, with one difference: a listed loanword like `ok` or `phone` counts for both languages, since it is as much Banglish as English. Writing `ok` does not turn the তো after it into "to". When the keyboard picks the wrong script for a word both languages have: - Press backspace straight after the word and it flips to the other script, the same way backspace takes back an autocorrection. It follows the same **Undo autocorrect with backspace** setting. - Or tap the other script's word on the strip, before or after the space. The undo chip offers it after the space. - Either way the keyboard remembers the spelling. After two flips it stops weighing that spelling at all and writes it your way, until you flip it back. The **English words** tool puts the switch on the toolbar, lit while it is on. It is there for the moment a Bangla word keeps coming out in English: tap it mid-word and the word in the field turns back before you press space. It switches the language you are typing at that moment. Tapped on a layout that is not phonetic, or with English missing from Also suggest from, it says so and changes nothing. The same switch also sits on the suggestion strip itself, as a small button beside the candidates, a size down from the toolbar's buttons and lit the same way. That is where it is needed: a word coming out in the wrong script is something you notice while it is being typed, which is when the suggestions have taken the row and the toolbar is behind the chevron. It shows on a phonetic layout with English picked under **Also suggest from** for that language, and goes away on every other layout. Put the **English words** tool on a toolbar row of its own and the strip drops its copy, rather than showing you the same button twice. The strip's button is pinned, so it cannot be dragged off the way a tool can. **Switch on the suggestion strip**, the second row of the same group, takes it away instead. English words then stay the way you set them, with nothing on the keyboard to turn them off by accident. After an English word, the next-word predictions come from English too (your own pairs, the bundled ones, and the English word-pair pack if you downloaded it). Which language each app's fields start out leaning towards is the same per-app tally described above. Glide typing on these layouts still decodes Bangla or Hindi only. ## Bangla, Hindi or Marathi on the English keyboard The other way round works too. Pick a language that has a [phonetic layout](https://wmkeyboard.pages.dev/languages/phonetic/) (Bangla, Hindi, Marathi, Urdu, Tamil and the rest) under **Also suggest from** on English's own screen, and the English keyboard reads each word you type through that language's phonetic rules as well. Its readings show up on the strip in its own script: type `marathi` and मराठी is one tap away, with no layout switch. Where a reading lands depends on who knows the word: | You type | The strip | Why | | --- | --- | --- | | `kemon` | কেমন right after the first chip | only Bangla knows it | | `because` | English only | only English knows it | | `to`, `hello` | English first, তো or হ্যালো on the last visible chip | both languages have it | | a word neither list has | its reading on the last visible chip | unless English still has words that start with it | The first chip never moves, and the space bar still writes what you typed in English letters; a reading goes in only when you tap it. A word you pick this way counts for its own language when the keyboard reads which language the field is in, so after a few Marathi words the readings of a word both languages know get weighed toward Marathi too. How good the readings are depends on that language's word list. With the list downloaded (from the language's own screen) the keyboard offers real words; without it only the phonetic rules and the bundled spellings are there to go on. A language only picked under Also suggest from still gets its word list and spellings read in, whether or not its phonetic layout is on. ## Options Typing / Automatic corrections | Setting | Default | Shown when | | --- | --- | --- | | Detect the language you type | On | Autocorrect on | | Detection strength | Balanced | Autocorrect on, and Detect the language you type on | | Remember each app's language | On | Autocorrect on, and Detect the language you type on | Both rows sit under Automatic corrections rather than Suggestions, because detection moves autocorrect's targets as well as the strip's order. That also means both are hidden by the **Autocorrect** master switch: turn autocorrect off and the two rows disappear from the screen. The switch you set stays set, though, so detection carries on reordering the strip. It's only autocorrect's half of the effect that goes away, and that goes away with autocorrect anyway. See [Typing settings](https://wmkeyboard.pages.dev/reference/settings/typing/#automatic-corrections) for the rest of that group. ## Details & edge cases - **It is per field, and per field only.** Switching apps, or tapping into a different box, starts the reading from scratch. There is no memory of "you write Banglish to this person". That's a different feature, the long-run per-language weighting described under [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/#learning-from-what-you-type). - **Detection never changes the keyboard.** The layout, the language name on the spacebar and the script you're typing all stay exactly where you put them. Only ranking and autocorrect targets move. To switch language, see [Switching languages](https://wmkeyboard.pages.dev/languages/switching/). - **A language has to clearly lead before it counts as the field's language.** For the learned-word weighting in particular, the detected language needs about a third more share than the language you're on before it takes over. A field with no clear leader stays with the keyboard's own. - **It's a different question from "which languages do you use".** The long-run mix moves over weeks and would never notice that this message switched to Banglish three words ago. This one is the fast signal, and the two run side by side. --- # One-time codes URL: https://wmkeyboard.pages.dev/smart/one-time-codes/ Section: Suggestions & correction Summary: A verification code arrives in a notification, and a chip above the keys types it. Off by default, and it needs notification access. Typing a verification code normally means pulling down the shade, reading six digits, going back to the app and hoping you remembered them. WM Keyboard can skip that: when a code arrives in a notification, a chip appears above the keys with the code and the name of the app that sent it, and one tap types it. This is off by default and it needs notification access, which is a broad grant. The rest of this page is what the keyboard does with it, and what it deliberately doesn't. ## Turning it on Typing / One-time codes / Codes from notifications Turn the switch on. A plain-language disclosure explains what the feature reads and why. The system's notification-listener screen opens next, where you grant the access. If you back out without granting it, an **Allow notification access** row appears under the switch as the way back. That row disappears once access is there. The grant is the same notification-listener permission the [media control tool](https://wmkeyboard.pages.dev/tools/media/) uses, but the two features are asked for separately, because they read very different things. Media control reads no notification content at all; the code chip reads notification text to find a code. Granting one doesn't turn the other on. Turning **Codes from notifications** off stops the keyboard reading notifications immediately, and drops any code it was holding at the same time. ## What gets read, and what's kept While the feature is on, the keyboard looks at each incoming notification's text: title, body, big text, the newest message of a conversation, inbox lines, sub-text, summary and ticker text, up to the first 1,000 characters. It skips its own notifications and anything ongoing (a media session, a download, a foreground service), since none of those carry codes. A code can arrive while the keyboard isn't listening, for instance when Android has closed it to save memory. So when notification access connects, and when you open a field with no code held, it also looks through notifications posted in the last 10 minutes, newest first, and stops at the first code. If a code is found, it goes into a single in-memory slot: the code, the posting app's name, the notification's key, and a capped copy of the message for context. Nothing is written to disk, nothing is sent anywhere, and a newer code replaces an older one rather than queueing behind it. Two codes in flight means the first one was abandoned, and offering a stale code is worse than offering none. ## What counts as a code The detector is deliberately hard to trigger. There is no "any six digits" rule, because delivery slots, order numbers and flight times all arrive as notifications too. Every pattern is anchored to a word that promises a code is next: `otp`, `passcode`, `verification`, `code`, `pin` on their own, and `verification code`, `security code`, `login code`, `sign-in code`, `2fa code` and similar pairings. Four anchored shapes run in precision order, so a message with several numbers gives up the right one: - `482913 is your Amazon OTP` - `Your OTP is 482913`, `code: 482913`, `PIN - 4829` - `OTP for account 2310990533 is 4279`, where the code follows the "is" rather than the account number - `use 482913 to sign in`, `enter 4829 to verify` Chinese, Japanese and Korean messages have their own code words: 验证码, 校验码, 动态码, 动态密码 and 口令 among others (traditional forms too), 認証コード and 確認コード, 인증번호 and 인증코드. A code right after one of them counts, with a few joining characters allowed in between, so `当前验证码为 467321`, `認証コードは123456です` and `인증번호 [482913]` all give up the code. ### Your own code words Typing / One-time codes / Your own code words When a sender's wording isn't on any list, type it here, commas between: `取件码, 暗号`. A number of 4 to 8 characters within about 40 characters of one of your words then counts as a code, through the same proximity pass and guards as the built-in words, except the deny list, since you chose the word yourself. Your words also apply to the codes the clipboard picks out of copied text. Empty by default. If none of those bites, the [clipboard detector](https://wmkeyboard.pages.dev/tools/clipboard/)'s keyword-proximity pass runs as a recall net, which is also what catches alphanumeric codes like `WX8Q2P`. A few guards apply throughout. A code has to be 4 to 8 characters. A bare four-digit value between 1900 and 2099 is rejected, because that's a year to a person and a code to a regex. The proximity pass leaves amounts alone, so `1500.00` in `OTP for Rs 1500.00 is 482913` isn't offered. Google's `G-` prefix is stripped as branding rather than treated as part of the code. And a deny list on the keyword itself stops "zip code 90210" and friends from ever qualifying. ## The chip The chip is drawn with a dashed outline, the same visual language the clipboard fragment chips use: this is something lifted out of somewhere else, not text you typed. It shows a small **Code** badge, the code itself, and the posting app's name, so you can see whose code you're about to trust before you trust it. The ✕ on the right dismisses it, which drops the code everywhere rather than only hiding the chip. It ranks high in the strip, above the clipboard and snippet chips, because the code is usually the whole reason you're in that field and it expires while everything else can wait. Only the [password-manager lane](https://wmkeyboard.pages.dev/smart/autofill/) beats it, since the platform may be offering the same code with more context. The chip hides itself, without dropping the code, when the current field doesn't qualify. One case does that: **Only in code boxes** is on and this field doesn't read as one. [Incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) doesn't hide it: reading a code out of a notification records nothing, and a login in a private tab is where the code is wanted. It hides rather than drops, because you're often one tap away from the box the code belongs in. A field counts as a code box when it asks for a number keypad, or when the app named it one: the hint it shows while empty, its accessibility label or its resource id saying `OTP`, `PIN`, `passcode` or `verification code`. The names that only look like codes don't count: a ZIP code, a country code, a promo code and a coupon code are all left alone. ## Typing into a row of boxes Plenty of apps and websites split a code across a row of single-character boxes that advance the focus for you. A code committed whole lands entirely in the first of them, which keeps one character and drops the rest. **Type codes one character at a time** (on by default) handles that. The keyboard types the first character and watches what the field does with it. If the focus moves on to a next box, each later character waits for its box to take over before it is sent, however long the form takes, so none lands in a box that is already closing. If the focus stays put, it is one ordinary field, and the rest of the code goes in at once. While a code is going in, the keyboard doesn't treat the cursor landing after each character as you going back to edit a word, so a later character can't replace the ones before it. This setting sits outside the rest of the group on purpose. It governs every path that types a code, including a code tapped from a [clipboard chip](https://wmkeyboard.pages.dev/smart/chips/#pulling-fragments-out-of-your-clipboard), which needs no notification access at all. ## Options Typing / One-time codes | Setting | Default | Shown when | | --- | --- | --- | | Codes from notifications | Off | always | | Allow notification access | Action row | Codes from notifications on, and access not granted yet | | Only in code boxes | Off | Codes from notifications on | | Offer a code for | 3 min (range 1–10 min) | Codes from notifications on | | Clear the notification | Off | Codes from notifications on | | Your own code words | Empty | Codes from notifications on | | Type codes one character at a time | On | always | **Only in code boxes** narrows the chip to the fields described above. It's off by default because some apps give their code box neither a number keypad nor a telling name, and there is no way to recognise those. A code that never appears where you need it is worse than one offered in a field you weren't going to use it in. Turn it on if you'd rather never see a code offered mid-sentence. **Offer a code for** is measured from when the notification was posted, not from when you saw it. Past that, the code is dropped rather than hidden. **Clear the notification** dismisses the code's notification once the chip has typed it, using the same access the capture does. A few apps re-post their code after a dismissal, which is the reason it's off by default. ## When no chip comes Check these in order: 1. **Codes from notifications** is off by default, so turn it on, and allow notification access when asked. 2. The notification has to carry the code. An app that says only "you have a new message", or a phone set to hide notification content, leaves nothing to read. 3. Android 15 and later can hide codes in notifications from apps like this keyboard, showing it "Sensitive notification content hidden" instead. When that happens the settings row says **Android hid a code from the keyboard**. The keyboard can't get around it. Some phones have a switch for it in Developer options. Otherwise copy the code from the notification, and the strip offers it from the clipboard. ## Details & edge cases - **The code never leaves the device and is never stored.** It lives in memory until it's used, dismissed, replaced by a newer code, or ages out. There's no history of past codes anywhere in the app. - **A code you used or closed doesn't come back.** Some apps re-post their notification when another message arrives or the group re-sorts, which looks like a new notification. The keyboard remembers which codes from which app it has already offered, so the same code from the same app stays gone, and the expiry counts from when the code first arrived rather than from the latest re-post. That memory goes when Android closes the keyboard, so a used code whose notification is still showing can come back until it expires. **Clear the notification** prevents that. - **The clipboard has its own, separate code path.** Copying a verification text offers a **Code** chip too, covered under [Smart chips](https://wmkeyboard.pages.dev/smart/chips/#pulling-fragments-out-of-your-clipboard). It needs no notification access, and it has its own setting under the Clipboard tool. Pasting a whole clip that is nothing but a code goes in character by character as well, on the same reasoning. - **Turning the feature off is immediate, not on next launch.** The flag the notification listener reads is flipped at the same moment as the setting, and any captured code is dropped with it. - **Nothing runs before your first unlock.** Between a restart and the first time you enter your device credential, the feature behaves as off whatever the switch says. The lock screen still redacts notification content at that point, and a chip printing a code out of one would put exactly that on the keyboard. --- # Personal dictionary URL: https://wmkeyboard.pages.dev/smart/personal-dictionary/ Section: Suggestions & correction Summary: Words the keyboard learns from you, and full control over them. WM Keyboard quietly builds a private, on-device vocabulary out of what you type: names, slang, abbreviations, anything the bundled dictionary doesn't know. Every word in it is safe from autocorrect. A word nothing recognises has to be typed a few times before it gets in, so one bad swipe doesn't put a misspelling in your dictionary for good. You can review that vocabulary, add to it or remove from it by hand, and wipe it entirely, all from Settings. smart/personal-dictionary-list — The Personal dictionary screen: learned and manually-added words, most-used first. ## What gets learned, and how strongly A word only joins your personal dictionary if every one of these holds at the moment you commit it: - **"Learn from typing" is on.** It is by default. - **Incognito isn't currently pausing learning.** Incognito goes active for one of two reasons. Either you switched it on yourself, or the focused field asked not to be learned from (Chrome's incognito tabs, private-browsing fields elsewhere) and "Follow private browsing" let that request through, which it does by default. Whether active incognito pauses learning is a separate switch, "Pause learning," and it's on by default too. That one doesn't live on the Privacy screen with the rest of these. It's under the Incognito tool's own settings (Tools / Incognito / While incognito). - **The field allows it.** Password fields and PIN entry are excluded, and so is every structured field (email, web address, number, phone, date, time, datetime), deliberately independent of whether the app is also hiding the suggestion strip. Only plain text fields qualify. Within a plain text field, how hard a word is taught depends on how it was committed: - **Tapped from the suggestion strip.** Twice the weight of a plain commit, since choosing a candidate deliberately counts for more than typing it out. - **Typed and committed normally.** The default weight. - **Autocorrected into place.** The corrected word itself is not added, because an autocorrect target earns no credit for being typed. The word pair around it is still recorded for next-word prediction either way. - **Backspaced right after an autocorrect fires.** Restoring your original spelling counts double towards learning that spelling, because deleting a fix is a clear "I meant what I typed." It no longer saves the word outright, since that was the fastest way for a stray swipe to get nailed into the dictionary forever. Nothing is lost by waiting: that exact correction is already blocked from firing again the moment you undo it. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/#undoing-a-correction) for the rest of that flow. A word needs at least two letters to be auto-learned this way. Adding one yourself (see below) has no such floor. ### Languages written without spaces Thai, Lao, Khmer and Burmese put no space between words, so what you type between two spaces is usually a phrase. The keyboard finds the words inside it and learns each one, with the word pairs between them, the same as it would for words you had spaced out. A run the keyboard already knows as a single word stays whole. The words before the cursor are read the same way, so next-word suggestions follow the last word you wrote rather than the whole line. Finding words in a run takes a moment longer than splitting at spaces, so for context the keyboard reads only the last 32 characters of a long run. ## Words wait their turn Nothing you type is written to your dictionary while you're still typing it. Every committed word is held first, and only counted once the text it landed in has stopped moving. That's what gives you a chance to read back what you wrote: a word you fix, delete or re-pick was never learned in the first place. Only what you do to a word counts against it. Backspacing through it, sweeping it away with a delete swipe or typing a fix over it drops it; putting the cursor on it, or walking the cursor back through it with a spacebar swipe, leaves it waiting, and it is counted with the rest when you move on. What differs between words is how many settled uses they need. A word something already recognises, whether that's the bundled dictionary, a wordlist you imported, a contact name, an app name, or your own dictionary, needs **one**. There was never anything in doubt about how it's spelled. A word nothing recognises has to be typed **three times** before it joins your dictionary, and each of those has to settle. Change that count with **Remember a new word after** under Typing / Suggestions, anywhere from 1 up to 10. A sighting that's undoing an autocorrect counts double, so one undo plus one ordinary use is enough at the default. Settling is what stops your slips being counted at all. A word you've just typed isn't evidence of anything yet, because half the time you're about to fix it, and a glide gives you the wrong real word often enough that counting it on the spot slowly turned the personal dictionary into a copy of the wordlist. So a word is held, unwritten, until one of these happens: - You close the keyboard. - You send or clear the field. - You move to a different field. - You keep typing far enough past it (about 500 words) that you're clearly not coming back. If you go back into that text before any of that, the word you change is dropped and counts for nothing: backspacing over it, deleting it with a delete swipe, or typing a fix on top of it. The word you merely land in is not decided by that alone. Tapping into it, or walking the cursor back through it with the spacebar, leaves it waiting, and it settles with the rest once you move on, so proofreading a sentence does not unlearn it and a word the cursor happens to sit on when you close the keyboard is still counted. The rest of the text you moved past is text you left alone, so tapping up to fix the first line of a note settles everything below it rather than throwing the whole session away. Re-picking a swiped word from the strip drops it too, because you just overruled the guess. So does deleting the word from your dictionary while it's still waiting. Two things skip the queue, because in both you've told the keyboard directly rather than by leaving text alone: tapping the `Add "yourword"?` chip, and **Add word** from the word menu or the Personal dictionary screen. Either way the word goes in once: the space you type straight afterwards finishes the same word you just added, so it isn't counted a second time on top. A word nothing recognises is invisible while it waits. It's never suggested, never shields itself from autocorrect, and never shows up on the Personal dictionary screen until it graduates. Sightings also go stale on their own, so the same slip made in March and again in September never adds up to a word. (A recognised word waiting its one turn is a different matter: it was already in a dictionary, so it's suggested and safe from autocorrect the whole time. All that's waiting is your own count of it.) If you'd like to see which words are still waiting, set **New word color** under Appearance / Toolbar. The strip then paints a word that nothing recognises in that colour while you type it, and stops the moment it's learned or you add it, so a word the dictionary still has to learn is told apart from one it already knows. ### Being asked instead If you'd rather decide each one yourself, turn on **Ask before saving new words** (Typing / Suggestions). Nothing is counted and nothing is saved. Instead, the moment you commit a word nothing recognises, the suggestion strip offers a chip reading `Add "yourword"?`. - **Tap the chip** and the word goes in right away, exactly as though you'd added it by hand. - **Tap the ✕** and the keyboard stops asking about that word, permanently. - **Ignore it** and it goes away by itself as soon as you type the next word or press backspace. Nothing is saved. The chip shares the strip with your word candidates rather than covering them, so it never gets in the way of finishing the sentence. Only one ask-first chip shows at a time: a snippet offer takes priority over this one. ## From the suggestion strip Press and hold any word on the suggestion strip and a small menu opens about it. Its items depend on the word and on what you are typing: - **Never suggest** puts the word on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). For a word already on it, the item reads **Suggest again** instead. - **Add "yourword"** appears while the word you are working on is not in your personal dictionary. It is about the word under the cursor, not the word you held: the suggestions are known words already, and the one you want in is the one you are writing. That covers the word you are typing right now and, with nothing being typed, the word the cursor is parked inside, which is when you usually spot the spelling your dictionary is missing. The word goes in spelled exactly as you typed it, counted as one use, and its capitals are kept (see [capitals](#capitals) below). If it was on the blacklist, it comes off. - **Delete "word"** appears for a word the keyboard can forget: one in your personal dictionary, one still waiting to be learned, one in Android's own dictionary, one you adjusted the rank of, or one in a word list you imported. It is forgotten from all of those. A word in an imported list is taken out of the list's file itself, so it does not come back with "Suggest again". If the word is also in a downloaded word list, which the keyboard cannot edit, it goes on the blacklist too, so it is gone from the strip either way. **Delete edits imported lists** under Typing / Suggestions is the switch: turn it off and imported lists are left as you imported them, and the word is blacklisted instead. - **Forget what you learned about "word"** appears beside Delete for a word that is in a word list and that the keyboard has also learned something about: how often you use it, its capitals, a rank you set, a swipe shape. That learning goes and the word stays, offered the way the lists offer it, so a word that picked up a capital it shouldn't have ("Toolbar") comes back in lower case without being blacklisted. It shows under the same **Delete** chip of **Word menu**. - **Search all words for "word"** appears when the cursor sits inside a word a swipe wrote and that swipe's path is still kept. Like **Add**, it is about the word under the cursor rather than the one you held. It reads the stroke again against every word list you have, past whatever limit glide typing normally works under, and fills the strip with the answers, which is the only way back to a word the decoder was never allowed to offer. See [Going back to a misread swipe](https://wmkeyboard.pages.dev/typing/glide-typing/#going-back-to-a-misread-swipe). - **Synonyms for "word"** lists words with the same meaning as the word you held, grouped by part of speech and, where the source gives one, by meaning. Tap one and it goes in place of the word, exactly as if you had picked it off the strip: it replaces the word you are typing, the word the cursor is in, or the word a swipe just wrote, and it takes that word's capitals, so "Happy" at the start of a sentence becomes "Glad". Held on a next-word prediction, it types the synonym as the next word. It appears for English words, on a keyboard with English among its languages. See [Synonyms](#synonyms) below for where the words come from. - **Suggest "word" higher** and **Suggest "word" lower** move the word one rank step up or down, the same steps the [slide](#sliding-a-held-word) and the word card set, without the gesture. Not offered for a word on the blacklist. - **Edit** is always there. It opens the word card. ### Sliding a held word Keep your finger down after the menu opens and slide it instead of letting go. The menu closes and a badge over the strip says what will happen when you lift: - **Up** lifts the word one rank step for every 24 dp (about 4 mm) you slide, and **down** sinks it the same way. The steps are the word card's own rank, from −10 to +10, counted from wherever the word already sits, so the badge reads `"form" rank +2`. Slide back to where you started and it reads **unchanged**, and lifting does nothing. - **Six steps down** (about 2.5 cm) is the boundary. The badge turns red and reads **Never suggest "form"**, and lifting there puts the word on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), exactly as the menu item does. **Suggest again** in the menu, or the blacklist screen, takes it back off. A word that is already on the blacklist doesn't slide: its menu stays up, and **Suggest again** there is how it comes back. The rank is stored in the same place the word card keeps it, so the card and the [personal dictionary screen](#adding-editing-and-removing-words) show it, and setting the card back to 0 undoes a slide. Nothing changes unless the finger moved a full step after the hold, so a hold you let go of in place still just opens the menu, and a quick swipe along the strip is still a swipe. Put the cursor inside a word and that word is itself the top candidate on the strip, so the menu for it is one hold away rather than a hunt for a chip that happens to be showing. Tapping that chip does nothing but put the strip away, since it would only replace the word with itself. See [When the cursor lands in a word](https://wmkeyboard.pages.dev/smart/suggestions/#when-the-cursor-lands-in-a-word). ### Synonyms The synonyms come from a list of sources, which **Synonym sources** under Typing / Suggestions puts in order. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag a source to move it, switch it off, remove it, or add it back. The keyboard asks the first source that is on. When that source has no synonyms for the word, or cannot be reached, it asks the next one, so one service being down or not knowing a word does not leave you with nothing. The panel says at the bottom which source answered. | Source | Where | What it knows | |---|---|---| | Vocabulary packs | on the phone | Wiktionary's synonyms for the words in the [vocabulary packs](https://wmkeyboard.pages.dev/tools/vocabulary/) you downloaded, grouped by meaning. Nothing is sent anywhere | | Datamuse thesaurus | `api.datamuse.com` | most English words, ranked by how well each one stands in for yours | | Wiktionary | `kaikki.org` | Wiktionary's full entry for the word, grouped by meaning | | Free Dictionary API | `api.dictionaryapi.dev` | the source the [Dictionary](https://wmkeyboard.pages.dev/tools/search/) tool uses | | Similar words | `api.datamuse.com` | words with a related meaning, for a word no thesaurus lists synonyms for. The panel says so when these are what you get | All five are on by default, in that order. Drag a source to move it and turn its switch off to never ask it. An online source receives the word you held and nothing else. A look-up happens only when you tap **Synonyms**, and [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) does not hold it back, the same as a vocabulary look-up. The keyboard remembers the last few answers, so opening the same word again asks nobody. The word card is a panel over the keyboard, and it is everything the dictionary screen can do to one word without leaving the keyboard. It lists everywhere the word was found: your personal dictionary with how often you typed it, the waiting room, each word list that has it with its place in that list ("#4,512 of 300,000"), an imported list, Android's dictionary, a contact, an app, or the blacklist. Under that come the word's own controls, and along the bottom the same actions as the menu, so a menu trimmed down in settings still reaches all of them. The card is also one tap away from the text itself. With [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, select a word and tap **Edit word** on the bar. The card opens for the selected word, and its **Add** adds that word while your dictionary does not have it yet. See [Look up](https://wmkeyboard.pages.dev/smart/selection-actions/#look-up). **Spelling** is the first control: the word as it is spelled now, a **Change** button, and a **Keep these capitals** switch. Pressing **Change** hands the keys to a bar in the strip's own row, since the card itself covers the keyboard and there is nothing under it to type with. The bar starts from the word as it stands and edits like a small text box: - **Tap** in it to put the caret there, **drag** across letters to select them, or **hold** to select the whole word. - **Swipe along the spacebar** to move the caret. With shift up or selection mode on, the swipe drags out a selection instead. A hardware keyboard's arrows, Home and End work too. - **Press shift over a selection** to cycle it through lower case, Title case and UPPER case, the same as in a text field. - **Glide a word** and it goes in at the caret, replacing any selection, with no space after it. Backspace deletes, the ✓ (or the Enter key) applies it, and the ✕ (or the back gesture) leaves the word alone. Nothing you type there reaches the app you are writing in. Applying a respelling moves everything the keyboard keeps about the word onto the new spelling: how often you typed it, the language it was last typed in, the word pairs it takes part in, and its rank adjustment. Change nothing but a capital and it is not a move at all, because every list keys the word the same way. It settles the spelling instead and pins it. A word your personal dictionary does not have, one from a downloaded list, has nothing to move: the new spelling is added by hand, which is what puts it in front of the list's own. **Keep these capitals** is the same switch as the dictionary screen's, for a word the keyboard has already learned; see [capitals](#capitals) below. Then the rank controls. Which of them the card shows is a setting, **Adjust rank changes** under Typing / Suggestions, set to **Both** by default: - **Learned weight** edits the same count the dictionary screen shows, with the same minus and plus buttons stepping a decade at a time. Lowering it to "Not learned" forgets the word. Raising it on a word the keyboard has not learned adds the word at that weight. It cannot push a word below a word list's own order, since the count only ever adds. - **Rank adjustment** keeps a separate up-or-down setting for the word, from −10 to +10, that the keyboard applies on top of every list. Any word can move either way, a word from a downloaded list included, and what the keyboard learned is untouched. Adjustments show up in a **Rank adjustments** group at the bottom of the dictionary screen. Tap one to type or step its value, or use its button to reset it. They never affect autocorrect, only the order of the strip. Which items the menu shows is up to you. **Word menu** under Typing / Suggestions is a row of chips for **Never suggest**, **Add**, **Delete**, **Synonyms** and **Suggest higher or lower**; turn one off and it stays off the menu. **Edit** cannot be turned off, because the card carries the other three anyway, and neither can **Search all words**, which only appears where there is a stroke to search at all. ## Capitals The dictionary keeps one spelling per word, and normally votes on it: type "Boston" with your own capital a few times and the word is offered as "Boston" from then on; type it in lower case often enough and the capital wears off again. The vote runs both ways. A word you have written in lower case for a long time has that standing behind it, up to the same cap the capital gets, so one "Keyboard" typed with Shift does not turn every later suggestion into "Keyboard"; it takes as many capitalised uses as the lower case had, at most eight. A word from one of your wordlists counts as written in lower case from the start, even before your personal dictionary has it: gliding "The" through Shift once adds "the", not "The", and it takes eight capitalised uses to turn it. Only a word the keyboard has never seen anywhere takes its capital on the first sighting, since there is nothing to outvote. A capital at the start of a sentence never counts, whether auto-capitalise put it there or you pressed Shift for it yourself, and neither does caps lock. So writing with automatic capitals off doesn't teach every word you open a sentence with as a name. Only a capital you typed is a vote. A swipe types no capital at all: your finger draws letters and the keyboard decides how to spell what it read. So a swiped word never votes on its own spelling, however it comes out. Neither does tapping a suggestion that is capitalised when what you typed was not: the chip is spelled the way the dictionary spells it, and choosing it is choosing the word, not the capital. To teach a capital, type it: press Shift, or glide through the Shift key (see [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/#capitalizing-without-leaving-the-stroke)). This is what stops a name you write both ways from getting stuck. Before, a swipe that came back as "Mark" was learned as "Mark", which made the next swipe come back as "Mark" more strongly still, and "mark" could never be written plainly again. A word you add yourself is different. Add it by hand, from the dictionary screen or from the strip's **Add** item, or respell it in the dictionary screen's edit dialog or on the word card. Either way its capitals are kept: no amount of later typing changes them. The row reads "capitals kept", and both the edit dialog and the word card have a **Keep these capitals** switch to turn that off, or to turn it on for a word the keyboard learned on its own. Keeping "you" in lower case this way is how to stop it ever being offered as "You" after a sentence start. It also wins over a capital from a word list or Android's personal dictionary, so respelling "Ok" as "ok" keeps it small everywhere, glide included. A kept capital is also how the word is written when you type it in small letters and press space, with autocorrect on: add "Wasi" and typing "wasi" writes "Wasi". A single-word entry with capitals in Android's dictionary works the same way. The one exception is a word your word lists also write in small letters: adding the name "Will" does not capitalise every "will" you type, so for such a word the capital stays a suggestion. Backspace right after puts back what you typed, like any correction. ## Why a learned word matters Once a word is in your dictionary, it's exempt from autocorrect. The correction engine treats anything in your personal dictionary the same as a word from the bundled dictionary, and never "fixes" it away. It also carries a heavy weight in suggestion ranking, so it competes with common words in the strip almost immediately rather than needing to be typed dozens of times first. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) and [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for how those two mechanisms work in full. That protection is why a new word waits. It also means a second threshold sits behind it. **Learn a word after** under Typing / Suggestions runs from Once up to 5, and asks for that many reinforcements of a word already in your dictionary before it's shielded. Ranking is unaffected either way, so a word below that threshold is still offered on the strip, but it isn't shielded from correction. With **Remember a new word after** doing most of the work now, most people can leave this one at Once. Alongside single words, the same learning pass builds word-to-word pairs, so typing "good" after "have a" can predict "morning". It builds three-word contexts as well, which win over the two-word ones when they match. It builds word-to-emoji pairs too, so a phrase that's usually followed by 🎉 can offer it. Pairs wait in the word cache with the words they are made of, and are learned from the text as it settled, so a word you fixed or re-picked never leaves a pair behind. Every word that ended up in the text counts toward them, one-letter words like "I" and "a" included, and so does a word autocorrect wrote for you: it is not counted as a word you typed, but the pairs on either side of it are real, and leaving it out would join its two neighbours into a pair you never wrote. They are only built out of words the keyboard recognises, at both ends, so a habit is never hung off a possible typo and a misspelling can't come back as a next-word suggestion. A separate, bundled set of generic English word pairs gives next-word prediction something to work with before you have any typing history of your own. None of those pairs are part of your personal dictionary. They aren't stored in it, don't show up on this screen, and can't be edited or cleared, and your own learned pairs always outrank the bundled ones once you have some. ## Adding, editing and removing words Typing / Suggestions / Personal dictionary Every word you've typed or added sits in one list, with the highest weight first until you select another order. Once the list passes a dozen entries, a search field appears above it, and the list itself draws 100 rows at a time with a **Show N more words** row to reveal the rest, so a dictionary of thousands of words never composes all at once. Each row starts with the language the word is filed under, then reads "Seen N times"; a word you put in yourself reads "You added this word · Seen N times", so it shows its count like any other. **Sort by** above the list sets the order: **Weight**, **Alphabetical** or **Date added**. The button under it says which way the list runs, and pressing it reverses that: highest or lowest weight first, A to Z or Z to A, newest or oldest first. Both alphabetical orders keep words that start with a capital letter at the top, so names sit together. Words with the same weight or the same date fall back to A to Z. The screen remembers the order for your next visit. **Date added** is when a word joined your dictionary, not when you last typed it, so a word you type every day stays where it joined. That makes **Newest first** the quick way to review what the keyboard picked up recently. Words saved by a version from before this order existed have no date of their own, so they sort by when you last typed them instead. Words learned in the same typing session share a date and sit A to Z among themselves. ### Sorted by language Every word the keyboard learns is filed under the language you were typing when it learned it, and moves to another language if you later type it there more recently. Once your words span more than one language, a row of chips appears above the list: **All words**, then one chip per language with how many words it holds, then **All languages** for words that belong to none in particular. Pick a chip to see only that language's words; search and the sort order work inside it. Languages you have turned on come first, in the order you set them; any other language the list still holds follows. **All languages** is where a word you added by hand goes unless you choose otherwise, and where words saved by an older version without a language end up. Nothing guesses a language for them, because a name like "Wasimaster" is equally at home in English and Banglish. To move a word, press its row and pick a language under **Language** in the edit dialog, or **All languages** to file it under none. A word you move stays where you put it: typing it later in another language no longer moves it, as it would a word you never filed. The language matters to suggestions in one way. A word filed under one language, that no dictionary knows, is offered a little less eagerly while you type another, and a word under **All languages** is offered the same everywhere. Moving a word changes only that; it does not delete the word, its weight or its word pairs. ![Adding a word by hand from the Add word dialog.](@assets/screens/smart/personal-dictionary-add.webp) **Add word** opens a dialog with the word, an optional **Shortcut** and its **Language**, **All languages** unless you pick one of yours. Confirm and the word goes in at a weight of one, or one more than it already had, marked as added by hand. Adding a word says it is a word, not that it is a frequent one, so it earns weight the way every other word does, by being typed. What the mark buys is that the word is immediately safe from autocorrect whatever the **Learn a word after** threshold says, and that it is the last thing compaction (see below) would ever remove. A trash icon on each row removes that word, and drops it out of any word pairs it was part of. A shortcut works the way it does in Android's own dictionary: type `asaik` and "as far as I know" is offered on the strip, which also helps with a word that is hard to glide, such as one with an apostrophe. It's kept as a [snippet](https://wmkeyboard.pages.dev/tools/snippets/) in a folder called **Personal dictionary**, where you can change it, turn the whole folder off, or delete it. **Press any row to edit it.** The dialog that opens holds the spelling, the weight, a **Keep these capitals** switch and the word's **Language**, and you can change any of them. Fix the spelling and the word keeps everything it had: its weight, the language it was learned under, and every word pair and triple it was part of, so "have a good" still predicts the corrected word afterwards. Respell a word onto one that's already in the list and the two merge into one entry. The weight is the same number the row reads out as "Seen N times", typed straight in or nudged with the minus and plus buttons beside it, which step by one below 10, by ten below 100, and so on up a decade at a time. Anything from 1 to 1,000,000 is accepted. Raising a weight makes the word win the strip sooner; lowering it does the reverse, and a word taken down to 1 is back in reach of **Remove one-off words** unless you added it by hand. Weight has no effect on autocorrect protection: every word on this list is shielded whatever its number, subject only to the **Learn a word after** threshold. **Remove one-off words** appears next to it whenever the list holds anything that's only been seen once, and clears the lot in one go after a confirmation that tells you how many there are. Older versions saved every new word the first time it was committed, so on an upgraded install this is where the stray swipes and mistyped words are. Words you added by hand are never in there, whatever their weight. **Remove words the lists know** appears beside it when some of your words are already in a word list: the downloaded or bundled list of the word's language, or one you imported. A word with no language counts when any of your languages' lists hold it, and a language set to read your own lists alone is checked against those only. After a confirmation with the count, those words go in one step. The keyboard still knows them from the list, but it forgets how often you typed each one, so they rank as the list ranks them. A word whose capitals you pinned goes only if the list writes it the same way, so `DNA` goes where the list says DNA and stays where it says dna. The rank changes you made from the word card (the list at the foot of this screen) are not touched. **Pick words to delete** opens a checklist of the words the list shows right now, so a search or a language chip above narrows it first. Nothing is ticked to start with: tick the words that should go, or tap **Select all** and untick the ones to keep. **Only words no list has** keeps to the words the keyboard knows only from your dictionary, which is where learned slips and misread swipes sit. The **Delete** button says how many words go, and pressing it is the confirmation. To add a lot of words at once from something you already wrote, open the [Learn from text](https://wmkeyboard.pages.dev/tools/learn-from-text/) tool in that note or draft. It lists the words the keyboard doesn't know yet, and adds the ones you keep checked exactly as **Add word** would. Words are capped at 32 characters, counted after the word is lowercased. Anything longer is dropped on the floor by both paths: typing it teaches nothing, and **Add word** closes the dialog as though it worked while the list stays exactly as it was. There's no error and no warning, so if a long entry never turns up in the list, count the characters. Every add, edit or remove here updates a version marker the running keyboard watches. If the keyboard is open in another app while you edit the list, it reloads your change instead of silently overwriting it the next time it saves. Before you've typed or added anything, the screen reads "Nothing here yet. Words appear as you type, if 'Learn from typing' is on in Privacy. You can also add a word above." Two related screens are easy to confuse with this one but do something different. The [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) is a separate never-suggest list. Blacklisting a word does purge it from this one, though neither screen offers a shortcut to push a word the other way. **Custom dictionaries** is for importing a bulk word list per language rather than words you've typed (see [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for that). ## Android's dictionary Typing / Suggestions / Personal dictionary Android keeps a personal dictionary of its own, shared by every keyboard and the spell checker. Two rows above the word list move words between it and yours, whenever you ask: - **Import from Android's dictionary** lists the words in Android's dictionary that aren't in yours yet, all checked. Uncheck any you don't want and tap **Add**. They go in as words you added by hand, and a word written there with capitals keeps them. An entry with spaces in it, like "on my way", comes in as separate words, the same way the keyboard already reads it. - **Export to Android's dictionary** lists your words that Android's dictionary doesn't have yet, most used first, and **Copy** writes the checked ones there. A message says how many went. An entry with spaces in it is read as its separate words everywhere, but its capitals are left on the phrase: "User Dictionary Manager" in Android's dictionary teaches the keyboard "user", "dictionary" and "manager" and nothing about how you capitalise them, so they are still suggested in lower case. A single-word entry written with capitals does keep them, and that includes a one-letter entry such as "I". Importing is the exception, since you pick the words yourself: each part comes in as you see it in the list, capitals and all, and unchecking the ones you do not want is the point of the list. Words match whatever their capitals, so "Boston" and "boston" count as one word and are never offered twice. Android only lets the keyboard currently in use touch its dictionary, so both rows need WM Keyboard to be your current keyboard. Otherwise they say so and do nothing. Neither runs by itself: to copy each new word as the keyboard learns it, turn on **Add words to the system dictionary** under Privacy. A third row, **Import a Gboard dictionary**, takes the `PersonalDictionary.zip` from Gboard's own Export (in Gboard: Settings, Dictionary, Personal dictionary, then Export in the menu). A dialog counts what is in it first. Its words come into this list as words you added by hand, capitals kept, each filed under the language Gboard had it under when WM Keyboard has that language (under **All languages** otherwise), and its shortcuts become [snippets](https://wmkeyboard.pages.dev/tools/snippets/) in a folder called **Gboard** that show their word as a suggestion, as Gboard does. See [Gboard's personal dictionary](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#gboards-personal-dictionary). ## Clearing everything at once Privacy / Your data / Learned words The row is called **Learned words** and its button reads "Delete the learned words". It asks first: a dialog spells out what goes and waits for you to confirm. It reaches wider than this word list. Seven files are cleared together, because each learning store lives on its own: your learned words, the new words still waiting to be counted, your rank adjustments, your emoji-usage history, autocorrect's memory of the corrections you undid, your CJK candidate-pick history, and the language-mix signal behind mixed-language typing. Like an add or a remove, the delete bumps the version marker a running keyboard watches, so a session open in another app re-reads your vocabulary from disk instead of saving the old copy back over the wipe. Typing statistics live under their own folder and aren't touched. Clear those from the Statistics screen instead. smart/personal-dictionary-clear — The 'Your data' group on the Privacy screen, with the one-tap clear button. The learned-word file this screen deletes is your personal dictionary, so this button empties the word list on this page along with everything else. The confirmation dialog says so: a word you add by hand goes into the same list as a word the keyboard picked up. What survives is your snippets and your imported word lists. ## Where it lives, and who else can see it Your personal dictionary is a single JSON file in the app's private storage, never uploaded anywhere on its own. Two things are true by default, and two are opt-in: smart/personal-dictionary-backup-toggle — The Dictionary section of a manual export, off by default. - **It's inert during direct boot.** Between a restart and your first unlock, the file is unreachable, since it lives in credential-encrypted storage. The keyboard runs with an empty, in-memory-only vocabulary: nothing is read, and nothing you type in that state is written anywhere. Once you unlock, the real file is reattached and the temporary in-memory copy is discarded rather than merged in. - **Nothing mirrors outside the app by default.** An opt-in toggle can also copy newly-learned words (not autocorrect targets) into Android's own shared personal dictionary, so other keyboards and spell checkers can see them too. It's off by default, one-directional, and forced off automatically during direct boot however you've set it, since nothing typed on a lock screen should be learned anywhere. A separate toggle, on by default, does the reverse for a different field of that same system list. It reads back any shortcut entries you've defined there (like "omw" → "on my way") and offers the full phrase as you type the shortcut. Glide the shortcut instead and the full phrase is typed straight away; while you swipe, the floating word, the suggestion strip and the words offered when you hold still show it as `omw = on my way`, and one backspace puts the shortcut back. A shortcut can also stand for a gap, such as a tab or an em space: its suggestion looks blank, and picking it types that gap with no extra space after it. To move words across by hand in either direction, see [Android's dictionary](#androids-dictionary). - **Android's own backup is opt-in.** Turning on "Back up with Android" includes your whole keyboard, learned words included, in Android's standard app-data backup and phone-to-phone transfer. - **A manual export leaves it out by default.** The Backup & restore screen's "Dictionary" section covers your personal vocabulary and the language each word is filed under, next-word patterns, rank adjustments, the fixes you have taught autocorrect and where your taps land, and it starts unchecked. A settings file you export and share out of habit carries no typing data along unless you deliberately opt in. Restoring that section does signal a running keyboard to reload, the same as adding or removing a word does. See [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/) for the rest of how incognito and secure fields work, and [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/) for the full export/restore and Android-backup picture. ## Options Typing / Suggestions / Personal dictionary | Setting | Default | Where | | --- | --- | --- | | Learn from typing | On | Privacy / Learning on this device | | Add words to the system dictionary | Off | Privacy / Learning on this device | | Use the Android dictionary | On | Privacy / Learning on this device | | Expand Android dictionary shortcuts | On | Privacy / Learning on this device | | Incognito mode | Off | Privacy / Learning on this device | | Follow private browsing | On | Privacy / Learning on this device | | Pause learning (while incognito) | On | Tools / Incognito / While incognito is on | | Learn a word after | Once | Typing / Suggestions | | Remember a new word after | 3 | Typing / Suggestions | | Ask before saving new words | Off | Typing / Suggestions | | Word menu | Never suggest, Add, Delete, Synonyms, Suggest higher or lower | Typing / Suggestions | | Adjust rank changes | Both | Typing / Suggestions | | Back up with Android | Off | Privacy / Android backup | | Include "Dictionary" in export | Off | Backup & restore / What goes in an export | The dictionary screen itself has no settings beyond the word list, its sort order, its language chips, its two buttons, the two Android rows and the per-word edit dialog. Every toggle that controls whether something gets learned lives on the Privacy screen rather than here, with two exceptions: pausing learning during incognito is under the Incognito tool's own settings, and the three thresholds that decide when a word gets in and when it's shielded sit with the rest of the suggestion sliders. ## Details & edge cases - **The in-app description undersells the gate.** "Learn from typing" describes itself as skipping "password fields," but the real gate is broader. Every structured field (email, web address, number, phone, date, time, datetime) opts out too, not only the secured ones. - **The backspace-revert path checks slightly different conditions than ordinary learning.** Restoring a word by backspacing right after an autocorrect checks only the setting, incognito, and that the field isn't a secure one. It doesn't ask for a plain text field the way normal learning does. In practice that costs you nothing, because the autocorrect it undoes could only have fired in a plain text field in the first place. It's also counted immediately rather than waiting to settle, since you've told the keyboard directly rather than by leaving the text alone. - **A blacklisted word is never learned while it stays blacklisted.** The personal dictionary and the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) are independent stores, but adding a word to the blacklist purges it from the personal dictionary and the words still waiting to be learned. Typing it afterward teaches the keyboard nothing while it remains on the list. The blacklist also wins as a candidate, so the word is never offered or autocorrected to either way. Take it off the blacklist and ordinary learning resumes. - **Words added before this version keep their old weight.** Earlier versions handed an added word a weight of 200 instead of marking it, so on an upgraded install those words read "Seen 200 times" without the added-by-hand note. Lower the weight from the edit dialog if it crowds the strip. - **Respelling a word you moved keeps its language.** A word you filed by hand takes that language with it to the new spelling, even when the spelling merges into a word filed elsewhere. A word you never filed fills in its language only where the other word has none. - **Android's dictionary has no languages here.** Words imported from Android's dictionary go under **All languages**, and an export writes every word for all languages, because Android files most entries under the phone's language whatever they are. - **Editing only the capitals of a word keeps everything else.** The respelling is compared after lowercasing and normalising, so changing only capitalisation is not a rename: the word keeps its weight, its pairs and its age, and only its kept spelling changes. - **Respelling a word keeps its date.** A word you respell, or merge into another, keeps the older of the two dates, so fixing a typo does not move it to the top of **Newest first**. - **A weight edit resets the word's age.** Compaction (see below) scores a word by its weight and how long ago it was last touched. Editing a weight counts as touching it, so a word you deliberately adjusted starts fresh on that clock. - **The list is capped, and stale words go first.** It holds 10,000 words before it starts trimming, and words you added by hand outlast the ones the keyboard picked up on its own. See [Dictionaries & words](https://wmkeyboard.pages.dev/reference/settings/dictionaries/#the-list-is-capped-at-10000-words) for how it chooses. - **A word arrives with the count it earned.** A new word that took three sightings to get in reads "Seen 3 times" on the list, not "Seen once", because the sightings it collected on the way in come with it. - **Turning learning off empties the waiting room too.** Switching off "Learn from typing", or switching on incognito while it's set to pause learning, throws away the words that were queued but not yet counted. They were typed while it was allowed, but none of them had been written anywhere. - **A language with no dictionary of its own is slower to learn from.** Nothing recognises any of those words, so every one of them waits its three sightings. Set **Remember a new word after** to 1 if you type mostly in a language the keyboard has no wordlist for. For the rest of the suggestion strip (completions, corrections and next-word prediction), see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). To keep a word out of suggestions entirely instead of teaching it, see the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). For how contact names interact with this same learning path, see [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/). --- # Search by meaning URL: https://wmkeyboard.pages.dev/smart/search-by-meaning/ Section: Suggestions & correction Summary: Find clips, notes, snippets, emoji, your own stickers and your photos by what your words mean, in any language, with a model that runs on the phone. Search by meaning gives the keyboard's search boxes a second opinion. You still get the usual matches for the letters you typed, and under them come the things that mean what you typed: "groceries" finds the note "Buy milk, eggs and bread", "kitten" finds the clip "My cat sleeps on the sofa all day", "skiing" finds your sticker of a skier, and "বিড়াল" or "gato" finds 🐈 with no keyword pack for either language. It works in more than 100 languages, and a query in one language finds things written in another. It is off by default, because the model behind it is a large download. ## Turning it on AI / Search by meaning Pick one of three: - **Off** (the default). Search works by letters only, as it always has, and nothing is downloaded. - **Text**, about 155 MB. Clips, notes, snippets and emoji are found by meaning. - **Text and pictures**, about 375 MB in all. Your own stickers and saved GIFs, pictures in your clipboard history, and the photos in the [gallery](https://wmkeyboard.pages.dev/tools/gallery/#searching-your-photos), are found by what they show as well. Picking Text or Text and pictures starts the download. On mobile data it asks first, the same way every other model download does (see [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)). A banner under the choice shows the progress, says when the model is ready, and offers **Try again** if the download stopped. **Delete the model** removes the files and turns the feature off. The **Search by meaning** row under About / Storage shows the same files with their sizes, and the indexes built from your things beside them; deleting the text model or the indexes there turns the feature off too, and deleting only the pictures model leaves it on Text. On the GitHub edition the first download also fetches the LiteRT interpreter the model runs on, unless offline voice typing or the background remover already did. The Play Store edition gets the interpreter from Play. The F-Droid edition has no LiteRT interpreter, so it has no search by meaning and the choice is not shown. ## Where it works Every match by meaning is listed after the matches by letters, best first, and only while the box still holds the words it was found for. | Box | What is searched | |---|---| | [Clipboard](https://wmkeyboard.pages.dev/tools/clipboard/#finding-a-clip-again) | the text of each clip, a link's page title, a file's name, and with pictures on, what a copied image or screenshot shows. Clips marked as passwords are left out. | | [Notes](https://wmkeyboard.pages.dev/tools/notes/#the-shelf-and-the-tags) | each note's title, tags and text. Locked notes are never searched by meaning. | | [Snippets](https://wmkeyboard.pages.dev/tools/snippets/#opening-the-panel) | each snippet's label, text and trigger, from every folder | | [Emoji](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji) | each emoji's English name and keywords, so a query in any language reaches them | | [My stickers](https://wmkeyboard.pages.dev/emoji/stickers/#searching-and-sending) (Text and pictures) | what each of your own stickers shows, in the pack chip you are on | | [Gallery](https://wmkeyboard.pages.dev/tools/gallery/#searching-your-photos) (Text and pictures) | what each photo in your library shows. This box has no letter matches: a photo has no words to match. | | [AI tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools) | the model's own question: **Search your clipboard** finds clips, never passwords or codes, **Read your notes** adds notes that say the same thing in other words, never locked ones, and **Read your snippets** does the same for snippets | With **Text and pictures**, **Tag my stickers** (on by default) also gives each of your stickers that has no title and no keywords a few tags read off its picture: the emoji whose names fit it best, and the words of those names. A photo of a cat gets 🐈 and "cat", so it comes up [as you type](https://wmkeyboard.pages.dev/emoji/stickers/#stickers-as-you-type) like a sticker you had tagged yourself. These tags are kept apart from yours and are never written into the pack. ## How it works The model is [EmbeddingGemma 2](https://huggingface.co/google/embeddinggemma-2) by Google, released under the Apache 2.0 licence. It turns a piece of text, or a picture, into a list of numbers that places it among everything else by meaning, so texts and pictures about the same thing land close together whatever their language. The keyboard compares your query's numbers with each item's and lists the ones that stand out. - **Everything stays on the phone.** The model runs on the phone's processor. Your queries, your clips, notes, snippets and pictures, and the numbers made from them are never sent anywhere. The only network request is the download of the model itself. - **It learns in the background.** When you open the clipboard, notes, snippets, sticker or gallery panel, the keyboard works through what is new or changed since the last time, a few hundred texts or a few dozen pictures at a time, on a low-priority thread. The gallery keeps going a few dozen photos at a time for as long as its panel stays open, newest first. Until an item has been read, it is found by letters only, and the AI tool does not find it. Emoji come ready, because their numbers are part of the download. - **It keeps out of the keyboard's way.** The model runs in a process of its own and needs about 350 MB of memory while it is loaded. It lets that memory go a few minutes after the last search, and if Android needs the memory sooner it stops that process. Typing never waits for it. - **A query waits for you to pause.** The model reads your words a moment after you stop typing, so the matches by meaning arrive a little after the matches by letters. The first search after the model has been let go takes a few seconds more, while it loads. ## Details & edge cases - **Turning it off deletes what it learned.** The numbers made from your clipboard, notes, snippets, stickers and photos live in the app's storage, out of backups. Turning the feature off deletes them. The model files stay until you press **Delete the model**. - **Photos follow the permission.** Your photos are read only with the [gallery's](https://wmkeyboard.pages.dev/tools/gallery/#opening-it) access to them. Take that access away and the next time the gallery opens their numbers are deleted. The Play Store build has no access to the library, so it has no photo search. - **The AI only sees what is safe to read.** **Search your clipboard** and the meaning halves of **Read your notes** and **Read your snippets** check every match against the clipboard, the notes and the snippets as they are now: a clip marked or shaped like a password or a one-time code, a locked note, and anything in the trash or deleted are left out even when their numbers are still in the index. All three start on **Ask**, like the other tools that read your things. **Search your clipboard** is only offered to the model while the feature is on and the text model is downloaded; without it, **Read your notes** and **Read your snippets** match words only. - **Short queries are skipped.** A one-letter query is matched by letters only. - **Nothing that stands out, nothing listed.** Every text is a little like every other to the model, so an item is listed only when it is clearly closer to your query than the rest are. A query about something you have nothing on finds nothing by meaning. - **Long texts are read by their start.** Only about the first 200 words of a long note or clip are read. - **Animated stickers** are read by their first frame. - **Checked downloads.** Each file is checked against a SHA-256 digest built into the app before it is used, so the download address can be a mirror. The files are published as the `models` release of the project's data repository, repackaged from Google's LiteRT files without changing a weight. See [Network](https://wmkeyboard.pages.dev/privacy/network/#downloads-dictionaries-models-and-addons). --- # Selection actions URL: https://wmkeyboard.pages.dev/smart/selection-actions/ Section: Suggestions & correction Summary: Select text and the keyboard offers what to do with it: edit it, reformat it, convert it, translate it, look it up, or hand it to another app. On the keyboard's bar, or from any app's selection menu. Selecting a phone number in a chat and then dialling it usually means copying it, leaving the app, opening the dialler and pasting. The keyboard already knows what you selected, so it can offer the things that shape of text is for, and a good deal more: the everyday edits, a find and replace, line tools, case and chat formatting, conversions, and a way to hand the text to your contacts, maps or calendar. Select `01712345678` and the bar reads **Select all, Call, Message, WhatsApp, Contact, Copy, Cut, Paste, Delete, Share, Format, Find, Replace**. Select a sentence and it reads **Select all, Copy, Cut, Paste, Delete, Share, Save as note, Format, Find, Replace, Repeat, Grammar, AI, Bangla, Fancy** and so on, with the actions that only make sense for what you selected (Sort on several lines, Colour on `#336699`, Zones on `5pm`) appearing when they do. This is off by default. It adds a bar that appears out of a gesture you already make for other reasons, so you have to ask for it. The same actions, with translation and the AI actions beside them, are also in the menu most apps show over selected text, with no keyboard open and nothing to switch on. See [In any app's selection menu](#in-any-apps-selection-menu). smart/selection-actions-phone — A selected phone number, and the bar the keyboard offers for it. ## Turning it on Advanced / Selection actions Turn on **Selection actions**. Select some text in any app. The bar appears while the selection is live and goes away when you clear it. To switch it on and off without opening settings, add the **Selection actions** tool to your toolbar. One tap flips the same switch, and a message says which way it went. Text that is already selected gets its bar straight away. ## In any app's selection menu The bar lives in the keyboard, so it only sees text in a box you are typing into. For text anywhere else (a web page, a message someone sent you, an email), select it and pick **WM Keyboard** from the menu that appears over the selection, the one with Copy and Select all. On some phones it is behind the menu's three dots, and some apps leave it out of their menu altogether (see [When the entry is missing](#when-the-entry-is-missing)). You do not have to be typing with this keyboard, and the bar does not have to be on. A sheet opens over the app with the text at the top and the actions under it, in three groups: - **Language.** **Translate** puts the text into the language the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool translates into, through the engine and services you set up there. Each of your [AI actions](https://wmkeyboard.pages.dev/tools/ai/) follows it (Explain, Summarize, Formal and the rest, your own included) and writes its answer in place of the text. **Ask AI** opens the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) with the text attached, and **Look up** opens the [Look up](https://wmkeyboard.pages.dev/tools/look-up/) card on it. - **Rewrite.** The actions from this page that change text without needing a text box: Format and its case options, Fancy, Sort, Unique, Number, Bullets, Check boxes, Digits, Zawgyi ⇄ Unicode, JSON, Base64, Decode and Remove trackers. - **Open in.** Call, Message, WhatsApp, Email, Open, Contact, Map and Calendar, for a phone number, an address, a link, a place or a date. The rewrites and the Open in actions come from the same **Actions** list as the bar, in the same order (see [Options](#options)), and each shows only where it applies. Translate needs the Translate tool switched on. The AI actions need the AI tool switched on and set up, with a provider or a downloaded model. Each action works on the text as the last one left it, so you can translate a paragraph and then make it shorter, or sort lines and then number them. The arrow at the bottom left undoes one step at a time. Then: - **Replace** puts the result in place of your selection. The app does the replacing, so it works in any app whose text box offers the menu. - **Copy** copies the result and closes the sheet. On text you cannot edit, such as a web page, there is no Replace, and Copy is the way to take the result with you. - The share button beside Undo hands the result to another app. Calendar opens your calendar app's editor here rather than the keyboard's own [Calendar tool](https://wmkeyboard.pages.dev/tools/calendar/), since there is no keyboard open to show it in. The sheet leaves out the bar actions that work on the text box itself (Select all, Copy, Cut, Paste, Delete, Find, Replace, Move up, Move down), the ones that need a keyboard panel or the keyboard's loaded dictionaries (Bangla, Banglish, Hindi, Hinglish, Grammar, DeepL Write, Repeat, QR, Search, Speak, Zones, Colour, Save as note, Edit word), and chat formatting, which depends on the app you are typing in. Nothing in the sheet is stored. Translate sends the text wherever the Translate tool sends it, which is nowhere when it translates on the phone. The AI actions do the same with the AI tool's provider, and [Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds them back on mobile data as it does in the keyboard: when it is set to ask, the sheet says so, and tapping the action again sends it. ### WM Translate For translating alone, turn on **WM Translate in the selection menu**. It adds a second entry that opens the same sheet with the translation already running. It ships off, because every entry is one more row in every app's menu and Translate is a single tap inside the WM Keyboard sheet anyway. The entry is only there while the Translate tool is switched on. ### Choosing the entries Advanced / Selection actions The group **In other apps' selection menus** has a switch for each entry the keyboard adds to other apps' menus: | Entry | Switch | Default | |---|---|---| | WM Keyboard | WM Keyboard in the selection menu | On | | WM Translate | WM Translate in the selection menu | Off | | Ask AI | Ask AI in the selection menu | On | | Look up | Look up in the selection menu | On | Ask AI and Look up are the same switches as on the [AI tool](https://wmkeyboard.pages.dev/tools/ai/#options) and [Look up](https://wmkeyboard.pages.dev/tools/look-up/#options) pages, repeated here so all four sit in one place. Turning a switch off takes its entry out of every app's menu. Turning off the Translate or AI tool takes their entries out too. These switches do not depend on **Selection actions** at the top of the screen, which is the keyboard's own bar. ### Choosing the buttons on the sheet The same group has a switch for four buttons on the sheet: **Translate on the sheet**, **Ask AI on the sheet**, **Look up on the sheet** and **Save as note on the sheet**. All four are on. **Save as note** keeps the text as a new note in the [Notes](https://wmkeyboard.pages.dev/tools/notes/) tool, and shows while that tool is switched on. The sheet can't offer **Find**, because Find searches the text box you are typing in and no text box is open while the sheet shows. ### When the entry is missing Not every app lists WM Keyboard, Ask AI or Look up in its selection menu, even with every switch above on. The menu is put together by the app you are in, and since Android 11 an app can only see the other apps on the phone it has asked to see in its own manifest. Chrome is allowed to see every app on the phone, so the entries are always in its menu. Other apps see WM Keyboard only when something they asked to see happens to cover it, and that varies from app to app: on one test phone Gmail and WhatsApp could see it, while Telegram and Google Keep could not. An app that can't see it builds its menu without the entries, and other apps' entries go missing along with WM Keyboard's: Google Translate, ChatGPT, a dictionary. Some apps also draw a menu of their own that has no place for other apps' entries at all. Where the entries are there, they can be the last ones in the list behind the three dots, after Ask Gemini, Read aloud and whatever else your apps add. Scroll that list before deciding they are missing. Nothing on WM Keyboard's side puts the entries back in such an app. Turning the switches off and on, reinstalling, or typing with WM Keyboard in that app leaves its menu as it was. Only that app's developer can add the request. The keyboard's own bar works in every app, because the keyboard reads the selection itself rather than waiting for the app's menu. Turn on **Selection actions** (see [Turning it on](#turning-it-on)), select text in a box you are typing into, and the bar offers Translate, AI and the rest. For text you cannot type into, the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen) can ask the AI about the screen or look up a tapped word, outside the Play Store build. Copying the text and pasting it into a box, where the bar can reach it, works everywhere. ## What the keyboard reads The keyboard reads your selection as a whole. That is the rule behind every result on this page, and it is deliberate: - `01712345678` is a phone number. - `call me on 01712345678 tomorrow` is plain text. You selected the sentence, not the number in it. - `example.com/page` is a link. `report.txt` is not, because `txt` is not one of the endings a bare domain is allowed to have. - Two lines are never one thing, whatever the pieces look like on their own. Pulling a fragment out of a longer span is a different job, and the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) already does it. That surface has the room to show you what it found and where it found it. This one does not, so it does not guess. Phone numbers use the same formats as the clipboard, under [Phone formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/). With a format saved, a number has to match it, which is what keeps an invoice total or a row of figures from offering to dial itself. With no format saved, the plain shape rules apply. On top of that reading, a few actions look at what the text holds: whether it has several lines, Bengali or Latin letters, digits in another script, a colour code, a date or time, a place. Those checks are cheap, and the dearer ones (dates, places) only run while an action that needs them is switched on. ## The bar The bar is one row of chips that scrolls sideways, in an order you control under **Actions** (see [Options](#options)). Every action is drawn only where it applies, so the row you see is shorter than the list: Call never appears on a sentence, Sort never appears on a single line. Two things sit outside the order. **Undo** is always first while there is something to undo. And the actions whose answer is a pick rather than a single result (Format on text, Fancy, Colour, Zones) open a short row of choices in place of the bar, with an arrow on the left to come back. | Action | Appears on | Shipped | |---|---|---| | Undo | Anything, after the bar changed it | On | | Select all | Anything not already the whole field | On | | Copy, Cut, Delete, Share | Anything | On | | Save as note | Anything, once the phone has been unlocked; never a password field | On | | Paste | Anything, while the clipboard holds text | On | | Format | A number, link or address the rewrite would change; any text | On | | Find | Anything, when the field could hold another copy | On | | Replace | Anything | On | | Repeat | Anything, while the Text repeat tool is enabled | On | | Snippet | Anything, while the Snippets tool is enabled. Opens your snippets: a pick replaces the selection, and `{selection}` in it reads what was selected | Off | | Sort, Unique, Number, Bullets, Check boxes | Two or more lines | On (Check boxes off) | | Move up, Move down | Anything short of the whole field | Off | | Grammar | Text, with the [Grammar](https://wmkeyboard.pages.dev/smart/grammar/) tool on | On | | DeepL Write | Text up to 2,500 characters, once [DeepL Write](https://wmkeyboard.pages.dev/tools/translate/#deepl) is set up | On | | Translate | Text, with the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool on | On | | AI, and the AI buttons | Text, with the [AI](https://wmkeyboard.pages.dev/tools/ai/) tool on and set up | On | | Bangla | Latin text, with Bengali among your languages | On | | Banglish | Bengali text | On | | Hindi | Latin text, with the [Hindi phonetic](https://wmkeyboard.pages.dev/languages/hindi/) layout enabled | On | | Hinglish | Devanagari text, with the Hindi phonetic layout enabled | On | | Digits | Digits in another script | On | | Zawgyi ⇄ Unicode | Burmese text | On | | Colour | A colour code | On | | Call, Message, WhatsApp | A phone number | On | | Email | An address | On | | Open | A link or an address | On | | Remove trackers | A link with tracking parameters, alone or in text | On | | QR | A link, with the QR generator on | On | | Contact | A phone number or an address | On | | Fancy | Text | On | | Search | Text, with the Web search tool on | Off | | Bold, Italic, Strike, Mono | Text, in a chat app whose syntax the keyboard knows | Off | | JSON | A JSON object or array | Off | | Base64, Decode | Base64 text; a percent-encoded link | Off | | Speak | Anything | Off | | Edit word | One word | On | | Zones | A time | Off | | Calendar | A date or time | Off | | Map | Coordinates or an address | Off | | camelCase, snake_case, kebab-case, CONSTANT_CASE | Inside Format's case options | Off | ## Undo Every rewrite the bar makes (a case, a style, a chat marker, a grammar fix, a conversion, a replace) is remembered while the selection stays live. **Undo** is the first chip whenever there is one, and each tap walks one step back: lower case, then Title Case, then bold, then three taps of Undo returns the original. The memory ends with the selection. Move it onto other text, clear it, or leave the field, and there is nothing left to put back. Before putting anything back the keyboard checks the field still reads as the last rewrite, so an edit you made in between is never overwritten. Cut and Delete take the selection away and are not undone here; your editor's own undo covers those. ## Editing **Select all** selects the whole field. A long press usually selects only one word, so this is the fast way to act on everything you wrote. In a web page it goes in as Ctrl+A, so an editor in the page, like a code editor that only draws the lines on screen, selects its whole text itself rather than losing a selection the keyboard made for it. **Copy**, **Cut** and **Paste** are the editor's own; Paste replaces the selection and only appears while the clipboard holds text. **Delete** removes the selection. **Share** opens the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/) with the selected text, or Android's when the keyboard's sheet is off. **Save as note** keeps the selection in the [Notes tool](https://wmkeyboard.pages.dev/tools/notes/) as a new note, or adds it to the note you edited last when Tools / Notes / Save as note from a selection says so; a toast says which. It appears only while the Notes tool is enabled. ## Find and replace **Find** jumps to the next place the selected text appears, wrapping round to the top, and keeps it selected so the bar stays and a second tap walks on. The match is brought into view, even in an editor that would leave it under the keyboard or off the screen. **Replace** opens the [Find & replace](https://wmkeyboard.pages.dev/tools/find-replace/) panel above the keys, the same one its toolbar tool opens, with two fields: what to find, already holding the selection, and what to replace it with. Tap a field and type into it; the keys go to the panel, not to the app. The current match is selected in the field itself, where you can see it, and the panel says which one of how many. Enter in the find field goes to the next match; Enter in the replace field replaces the current one. smart/selection-actions-replace — The Find and replace panel: two fields, the toggles, the buttons and the count. Three toggles: **Aa** matches case, **[ab]** matches whole words only (in any script, so a Bengali word is a whole word too), and **.\*** reads the find field as a regular expression, with `$1` in the replacement standing for the first group. A pattern that is not valid says so under the toggles, and one that takes too long on the text is stopped rather than left to hang. **Replace** swaps the current match and moves to the next. **All** replaces every match in one go, which Undo takes back in one go too. A field that does not report where its text is (a few web views) still lets you find, but not replace in place. **Repeat** opens the [Text repeat](https://wmkeyboard.pages.dev/tools/utilities/#repeating-text) panel with the selection as the text to repeat: pick a count and a separator there and Insert writes the copies at the cursor. It appears only while the Text repeat tool is enabled. ## Lines With two or more lines selected, four chips appear. **Sort** puts them in order, digits compared as numbers so `item2` comes before `item10`; sort a list that is already in order and it turns the other way round. **Unique** drops repeated lines. **Number** puts `1.`, `2.`, `3.` in front of each line, and **Bullets** a bullet. **Check boxes** puts `☐` in front of each line, a list to tick off. All three are toggles: a list that already has the prefix on every line loses it, and any one converts into the others, so a bulleted list becomes a numbered one in a tap. A ticked box (`☑`, `✅`) and a Markdown task box (`- [ ]`, `[x]`) count as boxes already. **Move up** and **Move down** swap the lines your selection touches with the line above or below, and the selection goes with them, so pressing again keeps moving them. They work on one line as well as several, which is how a single item in a list changes places with its neighbour. Both are off to start with; turn them on under Actions. ## Format **Format** rewrites the selection in place. What it does follows what the selection is: - **A phone number** is written back in your own saved format. If you keep `+880 1XXX-XXXXXX`, then `01712345678` becomes `+880 1712-345678`. The separators come from the format you wrote, not from a shape the keyboard invented. With no format saved, a number that names a country code becomes E.164 (`+8801712345678`) and any other number becomes its bare digits. - **A link** loses its tracking parameters. `utm_*`, `fbclid`, `gclid`, `igshid` and about twenty others go; everything else in the query, the path and the fragment stay exactly as they were. A link with no scheme gains `https://`. - **An address** is lower-cased. - **Anything else** opens the case options instead. The Format chip is left off when the rewrite would change nothing. A number already in your format, or a link with nothing to strip, does not get a button that does nothing. ### Case options On ordinary text, Format opens a row of chips in place of the bar: **lower case**, **Title Case**, **UPPER CASE** and **Sentence case**, plus, when you switch them on under Actions, the programmer's cases **camelCase**, **snake_case**, **kebab-case** and **CONSTANT_CASE**. Those four read their input in any convention, so `Hello World`, `hello_world` and `helloWorld` all mean the same two words. The arrow on the left goes back to the actions. smart/selection-actions-case — Format on plain text: the four case options, with the arrow back to the actions. Each one rewrites the selection and leaves the result selected, so you can try Title, then Sentence, then UPPER without reselecting between taps. None of them changes the spelling: UPPER CASE writes `ß` as the capital `ẞ` rather than `SS`, so lower case gets the word back exactly. Title case and Sentence case leave a word that is already all capitals alone, so an acronym in the middle of a sentence survives. `the NASA launch` becomes `The NASA Launch`, not `The Nasa Launch`. That rule is suspended when the whole selection is capitals, because somebody selecting `HELLO THERE` and reaching for Title case is fixing shouting, not protecting acronyms. **Fancy** opens the [Fancy Text](https://wmkeyboard.pages.dev/languages/fancy-text/) styles, each chip written in itself, and restyles the selection in the one you pick. ## Chat formatting **Bold**, **Italic**, **Strike** and **Mono** wrap the selection in the markers the app you are typing in understands, and a second tap takes them off again. They only appear in apps whose syntax the keyboard knows, because markers an app does not read arrive as punctuation: | Apps | Bold | Italic | Strike | Mono | |---|---|---|---|---| | WhatsApp, Viber | `*text*` | `_text_` | `~text~` | `` ```text``` `` | | Slack, Google Chat, Messenger, Rocket.Chat | `*text*` | `_text_` | `~text~` | `` `text` `` | | Threema | `*text*` | `_text_` | `~text~` | none | | Telegram | `**text**` | `__text__` | `~~text~~` | `` `text` `` | | Discord, Element, Beeper, Reddit, Mattermost, Zulip, GitHub | `**text**` | `*text*` | `~~text~~` | `` `text` `` | Mono on several lines uses the app's fenced block where it has one. These four are off to start with; find them under Actions. ## Convert - **Digits** turns figures in another script into `0` to `9`: `০১৭১২` becomes `01712`, and Devanagari, Arabic-Indic or Persian figures the same. - **Colour** appears on a colour code (`#336699`, `#f00`, `0xFF336699`, `rgb(51, 102, 153)`, `hsl(210, 50%, 40%)`), wears a swatch of the colour, and opens its other spellings; tap one to write it in place. - **JSON** pretty-prints a minified object or array with two-space indents, or minifies a pretty one. Key order and number literals survive either way. - **Remove trackers** appears on a link that carries tracking parameters (`utm_source`, `fbclid`, `gclid`, `si` and the like) and takes them off, leaving everything that says which page it is. `youtu.be/abc?si=XyZ` becomes `youtu.be/abc`. It also takes the trackers a known site adds only there, such as Amazon's `/ref=…` path or X's `?s=20`, and turns a redirect link such as Google's `/url?q=…` into the link it leads to. It also shows on a sentence or a paragraph with such a link in it, and cleans every link there while leaving the words alone, and on a single link of any length (before version 0.6.2 a link longer than 320 characters, which tracked links often are, never offered it). Add parameter names of your own under Clipboard / Copy rules, where **Remove trackers from copied links** also cleans every link as you copy it. It is on to start with; if you have already picked your own set of actions, switch it on under Actions. - **Zawgyi ⇄ Unicode** appears on Burmese text and converts it between Zawgyi, the old font encoding much Burmese is still written in, and Unicode, whichever way the text needs: Zawgyi becomes Unicode and Unicode becomes Zawgyi. It reads the whole selection to decide, and writes the result exactly as converted even in a text box the keyboard is [writing Zawgyi](https://wmkeyboard.pages.dev/languages/burmese/#writing-zawgyi) into. It is on to start with; if you have already picked your own set of actions, switch it on under Actions. - **Base64** decodes Base64 text, and **Decode** a percent-encoded link (`hello%20world` to `hello world`). Both are careful about what counts: an ordinary word is never mistaken for Base64, and `50% off` is not encoded. ## Language - **Bangla** turns Banglish into Bengali script, word by word: the spelling map first (`tmr` to তোমার), then the same phonetic reading the suggestion strip uses, which is what lets `asi`, `achi` and `achhi` all land on আছি, and only then a literal transliteration. Digits and punctuation stay as they were. It appears on Latin text while Bengali is one of your languages. - **Banglish** goes the other way, writing Bengali script back in Latin letters by pronunciation (তোমার to `tomar`, ধন্যবাদ to `dhonnobad`, কী to `ki`), with the most readable spelling from the map for words it knows. - **Hindi** does the same for Hinglish: the known-spellings list first (`theek` to ठीक, `nhi` to नहीं), then the downloaded Hindi word list through the same phonetic match the strip uses, then the rules. It needs the Hindi phonetic layout switched on, because that is what loads all three. On Latin text with both Bangla and Hindi available you get both chips, and you pick the language you wrote. - **Hinglish** writes Devanagari back in Latin letters the way it is said, not the way it is spelled: करना is `karna` and नमस्ते is `namaste`, because spoken Hindi drops the inherent vowel in the first and keeps it in the second. Long vowels are doubled inside a word and single at its end (`naam`, `theek`, `mera`, `ladki`). - **Grammar** runs the [Grammar](https://wmkeyboard.pages.dev/smart/grammar/) checker over the selection alone and commits every suggestion at once, offline with Harper. With [LanguageTool](https://wmkeyboard.pages.dev/smart/grammar/#checkers-harper-and-languagetool) switched on, the selection goes to its server when LanguageTool is the checker for your language, and never from a password field or in incognito. It shows a spinner while it works and says so when there is nothing to fix. - **DeepL Write** sends the selection to DeepL Write and puts DeepL's rewrite in its place, in the [writing style](https://wmkeyboard.pages.dev/tools/translate/#options) you picked. It shows only after you add a DeepL API Pro key and turn on **DeepL Write** under Tools / Translate, and it works in the F-Droid build too. Undo brings the original back. - **AI** opens the [AI](https://wmkeyboard.pages.dev/tools/ai/) tool on the selection. Under **AI buttons** you can give any AI action a chip of its own (Formal, Shorter, Friendly and Summarize are made for it): one tap runs it on the selection and the result lands in the AI panel, ready to apply. Applying replaces the selection, not the field around it. The AI's own Translate action reads **AI translate** as a chip, so it is not confused with the Translate chip below. - **Translate** opens the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool with the selection in its box and the translation under it, using the translation engine you picked there (online, or on the device). **Replace text** in the panel then replaces the selection, not the field around it. It sits just before AI on the bar and is on to start with; if you have already picked your own set of actions, switch it on under Actions. Opening the Translate tool from the toolbar with text selected does the same thing. ## Look up **Edit word** appears when the selection is one word and opens that word's [word card](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip), the same card that holding a word on the suggestion strip reaches through **Edit**. It shows where the keyboard found the word and how often you typed it, and carries Never suggest or Suggest again, Delete, the rank controls, the spelling and **Keep these capitals**. **Add** is about the selected word: it appears while that word is not in your personal dictionary, and adds it spelled as selected. Punctuation around the word does not count, so `"Hello,"` and `(don't)` are single words; `two words`, `2026` and `e.g.` are not. The card works on your dictionary, not on the text, so the selection stays as it was. It closes when the selection moves to other text or goes away. If you have already picked your own set of actions, switch it on under Actions. **Search** opens the [web search](https://wmkeyboard.pages.dev/tools/search/) tool with the selection as the query. **Speak** reads the selection aloud with the phone's speech engine, or with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for that language (in Bengali for Bengali text), and turns into Stop while it does. **Zones** appears on a time and shows it in each of the zones you picked under Time zones, UTC and your own to start with; tap one to write it in place of the selection. A selected `now` or `time now` counts as a time too, the current one, so selecting it gives the time in each zone right now. ## Open in **Call** and **Message** open your dialler and your messaging app with the number filled in. **WhatsApp** opens a chat with that number. **Contact** adds the number or address to your contacts. **Open** hands a link to your browser, and an address to your mail app. When the link belongs to an app instead (a YouTube link with YouTube installed, say), the chip wears that app's icon and a small outward arrow, so you know where the tap goes before you make it. **Email** writes a new message to the selected address. **QR** opens the [QR generator](https://wmkeyboard.pages.dev/tools/utilities/) with the link already in it. **Map** opens coordinates (`23.8103, 90.4125`) or something that looks like a street address in your maps app. **Calendar** adds a selected date or time (`14 Sep 2026`, `tomorrow 5pm`, `১৪ সেপ্টেম্বর`) as an event: the [Calendar tool's](https://wmkeyboard.pages.dev/tools/calendar/#adding-an-event) composer opens with the selection as the title and that date and time set, ready to save. With the Calendar tool switched off, or its **Save events** set to the calendar app, it opens your calendar app's editor instead. None of these send anything anywhere themselves: each hands the text to an app you picked and stops. ## From plugins [Plugins](https://wmkeyboard.pages.dev/plugins/overview/) can add their own buttons to the bar. They come after the built-in ones, each with the name and picture its plugin gave it, and a tap replaces the selection with what the plugin made of it; Undo puts it back. A few keep the selection and add their result after it instead, such as a word count. Clink's text actions install this way too: add a Clink actions repository under Addons, or open a `.clinkext` file (see [Clink actions](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions)). A plugin's script sees the selected text only when you tap its button, and never on a password field, where the bar does not appear at all. Each tap starts the script afresh, and it has no way to send anything off the phone; see [the plugin sandbox](https://wmkeyboard.pages.dev/plugins/security/). Plugins are off until you turn them on under Tools / Plugins / Manage plugins, and each plugin's actions can be switched off one by one under Advanced / Selection actions / From plugins. They also appear under Rewrite on the sheet the **WM Keyboard** entry in other apps' selection menus opens. ## Where the bar draws Advanced / Selection actions / Where it appears **Own row** is the default. The bar gets a row of its own above the keys, which arrives with the selection and leaves with it. The keyboard is one row taller while it is there, and the app is asked to scroll the selected line back into view as the row opens, so the text you are working on does not end up behind the keyboard. That row is reorderable like any other under [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/), so you can put it above or below the suggestion strip. While a panel such as Clipboard or Emoji is open, the row stays away: text selected under an open panel no longer makes the keyboard grow, and the row arrives when the panel closes. **Suggestion strip** puts the actions over the strip instead, which costs no height at all. There are no word suggestions to show while text is selected, so the strip has nothing to give up. With a panel open the strip goes back to the toolbar, because the arrow on that row is how you get out of the panel. ## Options Advanced / Selection actions | Setting | Control | Default | |---|---|---| | Selection actions | Toggle | Off | | Where it appears | Choice of 2 | Own row | | Show on every selection | Toggle | Off | | Show with nothing selected | Toggle | Off | | Read what is selected | Toggle | On | | Actions | Screen | 37 of 56 on | | Icon only | Screen | None | | AI buttons | Screen | None | | Time zones | Screen | UTC and your own | | WM Keyboard in the selection menu | Toggle | On | | WM Translate in the selection menu | Toggle | Off | **Show on every selection** keeps the bar up on a selection too long to read. The keyboard does not read a selection past about 4,000 characters, so by default such a selection takes the whole bar away, Copy and Paste included, even though neither of those ever needed the text read, because the app does that work itself. Turn this on and the bar stays, carrying Select all, Copy, Cut, Paste and Delete, whichever of them you have switched on. Everything else still waits for a shorter selection, since every other action begins by reading the text. **Show with nothing selected** keeps the bar's row on screen all the time, not only while text is selected. With just a caret in the field, the row holds Select all and Paste, whichever of them you have switched on. Tap Select all and the full row for the whole text replaces it, so Copy, Translate and the rest are one tap further on and you never have to long-press to select first. The keyboard stays the same height as you move between selecting and typing. This switch only exists with **Own row** picked: over the suggestion strip it would take your suggestions away while you type, so it is hidden there. Password fields still never show the row. **Read what is selected** is the entity half of the feature. Turn it off and every selection is read as plain text: you keep the edits, the case options and the conversions, and lose Call, Email, Open, QR and Contact. It is worth turning off if you find the guesses wrong more often than right, since the generic half is the half that always applies. **Actions** is the list of everything the bar can offer, grouped, searchable, each with its own switch, and with a pencil that lets you drag the actions that are on into the order you want. The full list is on the [settings reference](https://wmkeyboard.pages.dev/reference/settings/selection-actions/). **Icon only** picks the actions drawn as their icon with no name, for a shorter row once you know them by sight, and **Icon size** on the same screen makes those icons bigger or smaller. ## Details and edge cases - **The bar never appears over a password field.** Putting a password in front of a Share chooser is the mistake this feature should not make. - **An action is only offered when it can work.** WhatsApp is hidden when WhatsApp is not installed. Search, Translate, QR, Grammar and AI are hidden when their tools are switched off, which includes the tools that [power saving](https://wmkeyboard.pages.dev/typing/power-saving/) and the lock screen have already taken away, and AI is hidden until a provider is set up. Hiding a tool from the toolbox is not switching it off: turn off **Show in toolbox** on the tool's own page to keep, say, the AI chip here while the AI tool stays out of the toolbox grid. - **Nothing is stored and nothing is sent by the keyboard.** Every action either edits the field you are already in, or hands the text to an app you picked. Grammar and the Bengali conversions run on the device. The one exception is DeepL Write, which you set up yourself: it sends the selection to DeepL. - **Before your first unlock**, only the actions that touch the current field survive: the edits, the rewrites and Find. Paste needs a clipboard that is still locked, Speak needs a speech engine, Edit word needs the personal dictionary, which is locked too, and everything under Open in would have to start another app. - **A very long selection gets only the actions that need no reading.** Past about 4,000 characters the keyboard stops reading the selection, so the case options, the rewrites and the entity actions all stand down. With **Show on every selection** off, which is how it ships, the bar goes away entirely and the toolbar's own [Copy tool](https://wmkeyboard.pages.dev/tools/text-editing/) is the answer; with it on, Select all, Copy, Cut, Paste and Delete stay on the bar, because the app does their work and the length costs them nothing. - **The bar follows the selection, not the caret.** Extending a selection with [selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) or a [trackpad](https://wmkeyboard.pages.dev/tools/trackpad/) drag re-reads it as you go, so the actions change under your finger as the selection grows into a whole phone number. - **Updates keep your list.** An update that adds actions switches on only the new ones it ships on, and puts them at the end of your order. What you switched off stays off, and your order stands. (Up to 0.6.5, every update that added an action reset the whole list to the shipped one.) --- # System spell checker URL: https://wmkeyboard.pages.dev/smart/spell-checker/ Section: Suggestions & correction Summary: WM Keyboard as your Android spell-check service, in every app. WM Keyboard can also stand in as Android's own spell-check service, the one that draws the red underline in any app's text field, no matter which on-screen keyboard is active at the time. Spelling comes from the same word lists the suggestion strip reads, in every language you have a list for. For English, Harper, the offline engine behind the in-keyboard [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/) tool, adds grammar marks on top. ## Turning it on Installing WM Keyboard doesn't make it your spell checker. That's a separate, OS-level choice, the same way you'd pick a default browser. The quickest route starts inside the app: 1. Open the Grammar check tool's settings. 2. Tap **Use Harper everywhere**, under the **System-wide** group. Android's own Spell checker screen opens. On OEM builds that hide that screen, the input-method settings screen opens instead. 3. Select **WM Keyboard spell checker**. The same screen also opens from your phone's **Settings** directly: search for **Spell checker** (usually under System, Languages & input). There's one entry for WM Keyboard, offering a language for each word list it can download. ## What it checks - **Spelling, every language.** Each word is checked against the word list you downloaded for that language (English has a built-in one), plus the words the keyboard has learned from you. Case doesn't count against a word: "github" passes because "GitHub" is in the list. The fixes offered are the ones autocorrect and the strip would give. A language with no word list on the phone gets no underlines at all, rather than every word marked wrong. The same goes for a word in another alphabet: while it checks English, a Russian word is left alone rather than marked, and a Russian list leaves an English word alone. - **Grammar, English only.** Harper checks the sentence around the words and marks what it finds, such as a wrong "their" or a doubled word. On Android 12 and up these are reported as grammar marks rather than typos, and apps that tell the two apart draw them differently. Harper's own spelling rules are left out here, so the word lists stay the only judge of spelling, and a grammar mark never sits on top of a misspelt word. - **Every build.** A build made without Harper's native library still checks spelling; only the grammar marks are missing. Dialect (American, British, Canadian, Australian) isn't a separate choice on Android's screen. It follows the same **English dialect** setting the in-keyboard Grammar tool uses, so the grammar marks in other apps match what the panel would have flagged. Earlier versions listed two entries under the same name, one for each half. If you had picked the word-list one, pick **WM Keyboard spell checker** again after updating; if you had picked the other, nothing changes. ## How it differs from typing suggestions The system spell checker and WM Keyboard's own suggestion strip are separate mechanisms that share the same word lists and the same Harper engine underneath: - **Where it runs.** The spell checker is a service of its own inside the WM Keyboard app, wired to Android's text-service framework rather than to the keyboard. It works in any app, with any input method, and keeps working when WM Keyboard isn't your current keyboard at all. In-keyboard [autocorrect and suggestions](https://wmkeyboard.pages.dev/smart/autocorrect/) only run while WM Keyboard itself is the active keyboard. - **Reactive vs. proactive.** The spell checker only answers when the host app's text-service framework asks it to, underlining and offering fixes on request. [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) works the other way round. It fixes typos the moment you press space and offers next-word predictions as you type, gated by its own confidence threshold. - **What triggers an underline.** Harper's lints carry a span, an offset and a length, and the spell checker only reports one whose span covers text. A pure insertion, like a missing comma, has zero length and gets silently dropped here, even though the same lint would show up as a fixable chip in the in-keyboard Grammar tool. ## Contacts get priority Turn on **Suggest contact names** under Typing settings and the spell checker loads the same contact index the in-keyboard suggestion strip uses. It then treats that index specially in two ways: - A word that matches a contact's name or a contact's email address is never flagged as a typo. No underline appears under a name it recognizes, in any app. - A mistyped word close to a contact's name (a one-letter typo, or a prefix of one) is offered first in the correction menu, ahead of the word-list fixes. Addresses are only used to suppress the underline. A mistyped address is never corrected toward the right one. One toggle covers both: **Suggest contact names** loads names and email addresses for the spell checker. The separate **Suggest contact emails** toggle, also under Typing, only affects the in-keyboard suggestion strip. It has no effect here, which is easy to miss when the two settings sound like they'd overlap. See [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) for both toggles in full, including what gets read and how. ## Options Tools / Grammar check smart/spell-checker-options — The System-wide group on the Grammar check screen: Use Harper everywhere, and Underline only for Android 12 and up. | Setting | Default | Notes | | --- | --- | --- | | Use Harper everywhere | Action row | Opens Android's Spell checker picker; only shown where the build has Harper | | English dialect | American | Shared with the in-keyboard Grammar tool; changing it here changes both | | Underline only, no fix popup | Off | Android 12 (API 31) and up only | **Underline only, no fix popup** keeps the red underline but tells Android's text framework to skip the correction popup. Use it when you want a heads-up that something looks off without a list of replacements getting in the way. The row itself is hidden below Android 12 rather than shown-but-inert, since the underlying flag doesn't exist on older versions. ## Details & edge cases - **Re-check delay doesn't apply here.** The Grammar check screen also has a **Re-check delay** slider (100–1500 ms, default 350 ms). That only paces how often the in-panel Grammar tool re-lints while you're actively writing. The system spell checker has no debounce. It answers synchronously whenever the host app's framework calls in. - **Runs even when WM Keyboard isn't your keyboard.** The checker loads its word lists, Harper and, if enabled, your contacts itself, on its own schedule, rather than borrowing anything the IME set up, so it keeps working with any other keyboard app in front. It only needs to be selected as the system spell checker. Which on-screen keyboard you're using doesn't matter. - **Runs locally, like everything else.** The word lists are files on the phone and Harper is a native library loaded on-device, and there's no network client anywhere in the spell-checker code path. Contacts are read into memory the moment the toggle is on and dropped again the moment it's off, never written to disk. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for what does and doesn't touch the network elsewhere in the app. - **The Grammar check rows are in Settings search.** "English dialect," "Re-check delay," "Use Harper everywhere," "Underline only, no fix popup," and the four issue-type switches ("Correctness issues" and its three siblings) each turn up there, and each search result lands on the Grammar check screen. - **Which language gets checked.** Left on **Use system language**, Android checks in the language of the keyboard you're typing with, and only falls back to the phone's language when the keyboard doesn't name one. WM Keyboard always names the active layout's language, even with **List languages in the Android switcher** off, so Indonesian typed on an English phone is checked as Indonesian, by this checker or any other one. Pick a language on the Spell checker screen to pin it instead. A pinned language is the only one checked, so if Russian words get no underlines at all, check that it isn't pinned to another language and that Russian's word list is downloaded. - **Tapping a marked word in a browser hides the keyboard.** Chrome, Brave and other Chromium browsers open their own correction menu when you tap a marked word, and while that menu is open Android takes the keyboard down. Every tap on the word opens the menu again. Pick a fix from the menu, or tap just past the word to put the cursor there instead. --- # Suggestions & prediction URL: https://wmkeyboard.pages.dev/smart/suggestions/ Section: Suggestions & correction Summary: How the suggestion strip picks the words it offers you, and how it learns the ones that are yours. The suggestion strip is the row of tappable words above the keys. It offers completions while you're mid-word, corrections when what you typed doesn't look like a real word, and next-word predictions once you've finished one. All three come out of the same on-device engine, which also learns your own vocabulary and phrasing the more you type. smart/suggestion-strip-anatomy — Word candidates in the suggestion strip, mid-word. This page covers the strip itself: its layout, the completion/correction/prediction engine, and how ranking adapts to you. A few things that also live in the strip have their own pages: [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/)'s confidence math, the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen, the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), [smart chips](https://wmkeyboard.pages.dev/smart/chips/) (calculator, currency, unit conversion), [password-manager chips and smart replies](https://wmkeyboard.pages.dev/smart/autofill/), [contact and app-name suggestions](https://wmkeyboard.pages.dev/smart/contacts/), and [emoji suggestions](https://wmkeyboard.pages.dev/emoji/customization/). Each is linked from the relevant section below rather than repeated here. Everything on this page is about the Latin/English-style strip. A conversion IME like pinyin or kana→kanji draws a different, scrolling candidate row that isn't covered here. ## What's on the strip Word candidates fill three slots by default. **Number of suggestions** (see [Options](#options)) takes that as low as two or as high as six. Turn on **More slots for short words** and the strip grows past that number, by up to three slots, whenever the next words are short enough that every one still fits its share at full size: `a`, `is`, `it`, `in` can get five slots on a phone where `international` keeps three. Long words get the usual number again, so nothing is ever shrunk to make room. It does nothing while **Scroll the suggestions** or **Keep suggestions in place** is on. The strongest candidate, the one autocorrect would pick, sits in the middle with the runner-up to its left. That's the layout most keyboards use. Turn off **Best suggestion in the middle** and the candidates rank left to right instead. **Swipe down for more suggestions** (off by default) is for the words that don't fit. Swipe down on the strip while it has words and a grid of up to 24 candidates covers the keys, best first, the strip's own words leading. Between words, the predictions for the next word page the same way. Tap one to use it as if you'd tapped it on the strip. Swipe the strip again, or tap **Back to the keys** at the top of the grid, to close it. With this on, a downward swipe over suggestions opens the grid rather than hiding the keyboard; over an empty strip it still hides it if that setting is on. After the words the grid has two more groups, each under its own switch below that one. **Emoji in the grid** (on) adds up to 24 emoji that match the word, the same search the emoji panel runs; tapping one works like an emoji on the strip. **Fancy text in the grid** (off) adds the word written in every fancy text style, so you can pick a look by seeing it; tapping one puts it in place of the word, and nothing about it is learned. The fancy group only appears while the Fancy text tool is on, and never in a password field. **Swipe up for more suggestions** (off by default) works the way HeliBoard's does. Swipe up on the strip while it has words and up to 12 more candidates open just above it, over the app, without covering the keys. Keep your finger down and slide it onto the word you want; the word under your finger lights up. Lift on it to use it, or lift anywhere away from the words (back down on the strip, say) to close them without picking anything. The best of the extra words sit in the row nearest the strip, so they're the shortest slide away. **More suggestions in a grid** (off by default, shown once either swipe is on) lines the extra words up in columns, one per suggestion slot, the way the strip itself is laid out, for both the swipe-down grid and the swipe-up words. Off, each word takes only the width it needs. Slots are equal width, so a long word gets no more room than a short one. Rather than cut it off, the strip shrinks it to fit: the point size drops first, down to about two thirds of normal, and past that the letters themselves narrow a little. Most words that would once have read "internationali…" now fit whole, set smaller and tighter. The shrinking itself has no settings, though the size it shrinks from does: **Suggestion text size** under Appearance / Toolbar runs from 80% to 160%. Short words are never scaled, and a word that already fits is drawn at full size. **Suggestion spacing**, on the same screen, is the gap either side of each word inside its slot, 6 dp out of the box. Lower it and a long word has a little more room before it shrinks. If you'd rather the strip never shrank anything, turn on **Scroll the suggestions**. Every word is then drawn at its natural size and the row scrolls sideways when the words overrun the strip. The row also runs on past **Number of suggestions**, through every word the keyboard found for what you typed, up to two dozen, so a word that ranked fourth (an Avro reading, say) is a swipe away instead of nowhere. A slot still gets at least its equal share of the width, so a set of short words looks exactly as it does with the setting off. It's the setting for a narrow phone that wants five or six candidates: at fixed widths those slots are too tight to read. A new set of candidates always starts back at the left edge. The strip a panel's own search box gets (the clipboard, emoji, notes and dictionary searches, the to-do box) scrolls the same way and runs just as deep. Turn on **More suggestions arrow**, under the same setting, and when there are more words than slots a ▾ arrow at the end of the strip opens every word the keyboard ranked in a grid over the keys, the same grid Chinese Pinyin's arrow opens for its characters and **Swipe down for more suggestions** opens. Tap the arrow again to close it. It ships off, so the words keep that space; the swipe opens the grid either way. Neither the arrow nor the swipe is there under a panel's search box, where the panel is in the grid's place. smart/suggestion-strip-emoji-tail — Word candidates followed by predicted emoji. Up to four predicted emoji can ride after the word chips (see [Emoji in the strip](#emoji-in-the-strip)), and a row of punctuation marks can ride after that (see [Punctuation chips](#punctuation-chips)), but never both at once. The emoji tail always wins the shared space. Punctuation only appears when no emoji prediction is offered. The tail sits on top of **Number of suggestions**: three words and an emoji is four chips. Turn on **Emoji takes a slot** (see [Options](#options)) and the best emoji takes the last slot instead, the same width as a word, with one word fewer, so the strip never shows more chips than the number asks for. **Suggested emoji** sets how long the tail can get, from one emoji to four. The words share the strip between them by default: two words split it in half, and an emoji tail, the punctuation marks, a tool's keyword chip or a rewrite chip (join, revision, undo) takes room from them, so the best word can drift from one keystroke to the next. Turn on **Keep suggestions in place** and the strip is always cut into **Number of suggestions** slots of one width. A slot with nothing to show stays empty, and the best word keeps its slot. With **Best suggestion in the middle** on that is the second slot, the runner-up on its left and the third word on its right, exactly where they sit on a full strip. Anything that is not a word takes a slot of its own rather than pushing the words aside: a suggested emoji or the punctuation marks the last slot, a tool's keyword chip or a rewrite chip the first. It has no effect while **Scroll the suggestions** is on, since that strip sizes each word to fit. **Coloured suggestions** puts each word on a rounded pill in the theme's accent colour instead of separating the words with thin lines, with the best word's pill the deepest. With a Material You theme the colour comes from your wallpaper, the way SwiftKey shades its suggestion bar. A theme can give the pills a colour of their own instead of the accent: **Suggestion pills** and **Main suggestion pill** in the theme editor, on the suggestion strip's sheet. Two more colours mark single words rather than slots. Both sit under Appearance / Toolbar and both stay off until you pick one. **Autocorrect word color** paints the word autocorrect has decided to put in for a space, and no other word, so the colour is a promise about what the next commit types. **New word color** paints a word that no word list, your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/), your contacts or the spell checker knows. That is usually the word you are typing, which lets you see it would be new to the dictionary before you add it from its hold menu or let the keyboard learn it; the colour goes the moment either happens. Where both apply the autocorrect colour wins, and neither is used on a layout that turns your keys into another script, where the strip shows readings rather than the word as typed. When one of the keyboard's own text boxes has the keys (a panel's search bar, the AI prompt, Find and replace, a plugin's field), the strip moves to the row directly above the keys, since the panel has taken the toolbar's row. It shows the same words for the box's text that it would for your field, and tapping one replaces the word at the box's caret. The exception is the emoji panel's search box, which is querying the emoji catalogue rather than writing prose: its strip offers [emoji search terms](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji) instead of dictionary words. See [Panels and captured keys](https://wmkeyboard.pages.dev/tools/overview/#panels-and-captured-keys). The strip shares its row with the toolbar, and several other features can claim that row instead of word candidates. A [smart chip](https://wmkeyboard.pages.dev/smart/chips/) (calculator, currency, unit conversion) takes the full row when it has an answer. [Autofill chips](https://wmkeyboard.pages.dev/smart/autofill/) take it over completely on a login field. The system's [smart replies](https://wmkeyboard.pages.dev/smart/autofill/#smart-replies) take the row when nothing is typed, then ride the tail once you start typing. A "paste your last copy" or one-time-code chip can appear from the [clipboard tool](https://wmkeyboard.pages.dev/tools/clipboard/). Typing `:` turns the row into [inline emoji search](https://wmkeyboard.pages.dev/emoji/customization/#inline-emoji-search-and-shortcodes). Any of these counts as "the strip has something to show," so the row rests on suggestions rather than falling back to the toolbar. **Suggestion strip always visible** (see [Options](#options)) keeps the strip as the resting view even before any of them has anything to offer. ## Completions, corrections, and predictions One engine produces everything in the word-candidate slots, drawing from several sources depending on what you've typed: - **Completions.** While you're mid-word, every source that has a word starting with what you've typed contributes a candidate: the active dictionary, any downloaded or custom-imported dictionary, a secondary language's dictionary if you type bilingually, your own [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) of learned words, and (if you've turned them on) [contacts, contact emails, and app names](https://wmkeyboard.pages.dev/smart/contacts/). If the only thing wrong with what you typed is a missing space, as in "ofthe", an "of the" candidate is offered too. A word joined with a hyphen is completed a part at a time: `auto-sp` offers `auto-space` and `seventy-ni` offers `seventy-nine`, as long as the parts before the last hyphen are words, even when the lists don't hold the whole compound. - **Corrections.** If what you've typed matches no known word anywhere, the engine walks the dictionaries for words a short edit away: a letter dropped, added, doubled, swapped with its neighbour, or substituted. A word of five letters or more may be reached in two such edits; anything shorter gets one, since a two-edit fix on a four-letter word explains almost anything. Each candidate is scored by how plausible that slip is on your active keyboard layout, and by where your finger landed. The best-scoring one appears as an ordinary tappable candidate, not only as a silent autocorrect target. So an ambiguous typo like "helo", evenly split between "hello" and "help", still gets you both real words to pick from, even when [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) itself stays quiet because neither candidate clearly wins. A word that is already known keeps its two nearest one-letter slips on the strip, ranked below it unless the words before it favour the slip: "is" also offers "us", one key over. Up to 0.6.3 a known word got no corrections at all, which hid the word you meant whenever the slip happened to spell another word, as one often does in a big downloaded list such as Arabic's, and that included words from a secondary language's list. - **Predictions.** With nothing composing, right after you finish a word, the strip predicts what comes next instead. Your own learned pairs come first, the three-word context ahead of the two-word one. Next comes the following part of a contact's name, if the previous word matched one. Then your own gappy habits: what has tended to follow the word two back one word later, and the word three back two words later, so "how can someone" can still offer "help". Then a downloaded n-gram pack for your language, if you have one installed. Last is a small bundled set of common English pairs, so a fresh install has something to offer before you've typed anything of your own. If nothing knows the word you just typed, a name or a typo, the strip falls back to what tends to follow the word before it rather than going quiet. - **Openers.** An empty field, the space just after a full stop, and the start of a new line are all the start of a sentence, and the strip predicts from that the same way it predicts from a word: the openers you use come first, then a bundled set of common English ones, so there is something to tap before you have typed a letter. A new line counts because it starts a new item or paragraph, so the strip no longer offers words that would follow the last word of the line above. The word you pick is learned as an opener too, so the list becomes yours. Openers only appear with **Suggestion strip always visible** on. With it off, an empty field, a new line and the space after a full stop rest on the toolbar with no arrow back to the strip, and the candidates come up with the first letter you type. smart/suggestion-strip-ambiguous-correction — An ambiguous typo offered as two tappable candidates instead of being auto-corrected. Candidates from every source merge into one ranked list, and the sources don't count equally. Each is weighted well above plain dictionary frequency, so a source that's more clearly you (or more clearly what you're reaching for) dominates once it has anything to offer: | Source | Weight | | --- | --- | | Bundled/downloaded dictionary | baseline. A downloaded list counts words on a far bigger scale than the built-in one, so it's scaled down until its commonest word matches the built-in list's, keeping its own order | | Custom-imported dictionary | ×100, or scaled down the same way if the list carries real word counts | | Secondary-language dictionary | ×40, scaled 0.5×–2× by how much you mix that language in. A downloaded list with real word counts is capped lower, so its commonest word sits at half the primary's commonest and never buries a word you typed in full in your main language | | Your personal dictionary | ×500 | | App names | ×400 | | Contact names | ×3,000 | | Contact emails (2+ character prefix) | flat 4,000 | Tap a candidate to insert it (see [Options](#options) for the space that follows). It's cased to match what you typed: a leading capital gives you a capitalized candidate ("Hte" → "The"), and typing in all caps gives you the candidate in all caps ("HTE" → "THE"). Anything else is inserted as stored. When the chip is replacing a word that's already sitting in the field, it takes that word's capitals instead. This is the strip after a swipe or after handwriting, where the chips are other readings of the word that just landed, and it's also a word you tapped back into mid-sentence. So if auto-capitalization gave you "Van" at the start of a sentence and you swap it for "can" off the strip, you get "Can", and swapping a shouted "VAN" gets you "CAN". Only capitals that Shift, caps lock or auto-capitalization put there carry over. A name's or an acronym's own capitals are its spelling, not yours, so swapping "Joe" for "javelin" mid-sentence gives "javelin", and "QWERTY" for "qwery" gives "qwery". At the start of a sentence the new word still gets its capital. The same goes for the [recent words](#fixing-a-word-youve-typed-past) row and for a word picked from **Synonyms**. ## The context model Everything above reads at most three words back. **Context model** (Typing / Automatic corrections, on) adds a small neural language model that reads the whole sentence so far. It is a file you download per language, from the language's own screen under Languages & layouts (the **Context model** row, about 10 MB; the Download button also offers a **Large** English model of about 17 MB, which predicts the next word about a point more often and takes longer on each word on a slow phone, and the row's menu switches between the two later), and it runs on the phone: nothing you type leaves it. English has one, German has one, and so does romanized Bangla, whose model reads English and Banglish in the same sentence. A language without a model keeps everything else on this page exactly as it was. The model answers one question once per word you commit: given the sentence so far, how likely is each word of its vocabulary to come next. That one answer serves the strip three ways. - **Predictions.** Its likeliest next words join the next-word strip, after your own learned pairs and before the downloaded word pairs, so "I was wondering if you" offers "could" and "would" where the pairs alone could only see "you". Measured on sentences the model had never seen, top-one next-word prediction rose from 14% to 20% (top three from 23% to 34%), and the letters you could leave untyped by taking the first suggestion from 38% to 44% on careful typing. - **Ranking.** Its opinion of each candidate for the word you are typing joins the score the strip is ranked by, before the word pairs have their say: a candidate the model expects more than the strip's current leader moves up, one it expects less moves down, by a bounded amount. The keys you hit still outweigh it, so a far-fetched word the sentence happens to predict cannot climb over a word your taps spell. Measured on sentences the model never saw, the strip's first word is right about half a point more often on careful typing (87.2% to 87.5%), 3 points more on fast typing (79.5% to 82.7%) and 4 points more on sloppy typing (72.1% to 76.0%), where the keys say least. - **Autocorrect.** Its score for a candidate is one of the things the [learned gate](https://wmkeyboard.pages.dev/smart/autocorrect/#the-learned-gate) weighs before a correction is applied. Each model has a vocabulary of about twenty thousand words plus four thousand word pieces. A word outside it, a name, a term you taught the keyboard, a word from another language, is handled one of two ways. A model that carries word pieces spells it out of short letter groups and still gives it an opinion, so a name the sentence calls for is not held back for being new. While you type, this is done for the few words close enough to the leader for the model to change their order, four at most on each letter. A model without pieces gives no opinion: the word is neither lifted nor sunk, and your dictionary ranks it as before. The sentence it reads begins at the last full stop, question mark or exclamation mark, and it also knows where it is being read: a chat, an email, a search box or anywhere else, and whether the app is one you write casually in (the same lists **Adapt to app tone** uses), so a chat leans conversational and an email formal. **Phrase suggestions** (Typing / Automatic corrections, off) lets the model offer the next two or three words as one chip after a space, when it is sure of each of them in turn: "going to be" rather than "going". Tap the chip and all of them go in. Nothing is offered when the model is not sure, which is most of the time. **Re-check the previous word** (Typing / Automatic corrections, on) is the model reading backwards. The word you have just typed often shows what the word before it should have been: "I want to by" is fine until "a car" lands. Once the next word is in, the keyboard asks the model about the previous word's other readings, the words the strip had for it at the time, with both sides of the sentence in sight, and it keeps asking as the words after it land, so a reading that two or three later words make obvious is still found. When one fits twenty times better than what stands there, a chip offers it, the same chip the confusable rule uses for "their" and "they're". It never rewrites anything on its own, and it only ever offers a word the strip had already found for the taps you made, so it cannot invent a correction from the sentence alone. When you stop typing for a second with the strip empty, it also reads the last few words together once and offers the one swap among them that fits best, if any clears the same bar; the chip goes away as soon as you type on. ## Before you type **Words before you type**, under Typing / Suggestions, puts your own words on the strip while nothing is typed. At the start of a sentence they replace the usual openers. After a word, predictions keep their slots, your words fill any left empty, and the rest head the grid behind the strip. In a right-to-left script the best word starts at the right edge. **Right-to-left words start on the right**, in the same place, turned off keeps the strip and its buttons where they are in every other language. With the toolbox button on the strip, a swipe up on it opens the pinned tools, the same as the arrow. ## When the cursor lands in a word Put the cursor inside a word you have already written, by tapping it or by walking the caret back with the spacebar, and the strip stops predicting the next word and answers about that one instead. You get its completions and corrections, the same as if you were typing it, plus the word itself as the top candidate, which with **Best suggestion in the middle** on is the middle chip. A word you highlight gets the same answer, abbreviations with a full stop inside them included: select `i.e.` or `e.g.` and the strip offers that, not the words it showed before. A word that only ends in a full stop is left alone, because a chip picked over it would take the sentence's full stop with it. Right after a word counts too, as in `next word| to`. There the word is normally picked back up as if you were still typing it, so a letter you type goes on the end of it. Some apps won't let the keyboard do that, and in those the strip still answers about the word: picking a chip replaces it, and typing adds to the text. That chip is there for the hold, not the tap. Tapping it would replace the word with itself, so it does nothing but put the strip away. Holding it opens the [word menu](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip), which is how you add the word to your dictionary, open its card to respell or re-rank it, delete it, or block it, all without leaving the sentence. Holding any other chip reaches the same menu, and its **Add** and **Search all words** items are about the word the cursor is in rather than the chip you held. **Only offer new words** (Typing / Suggestions, off) takes that chip away, the same way it takes away the slot the word you are typing normally holds. With it on, every slot offers something different from what is already there, and the word menu is then only a hold on some other chip away. A word a swipe wrote gets one thing more: the strip leads with that stroke's other readings instead of respellings of the letters standing in the field. See [Going back to a misread swipe](https://wmkeyboard.pages.dev/typing/glide-typing/#going-back-to-a-misread-swipe). ## Retyping the last word **Retype word** is a text editing action for the moment a word went in wrong and you would rather type it again than fix it letter by letter. It is in the menu a held suggestion opens, as **Retype "word"** naming the word it would take, and you can also put it somewhere of its own: on a key or a flick in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), on a long-press alternate, a key of the [text editing panel](https://wmkeyboard.pages.dev/tools/text-editing/), a [multi-finger swipe](https://wmkeyboard.pages.dev/typing/gestures/) or a `?123` chord. It takes the word right behind the cursor back out of the field, with the one space after it if there is one, and leaves the cursor where the word began. The strip then shows that word first, so one tap puts it back exactly as it was. If you swiped the word, the other words that swipe could have been follow it, and taking one of them teaches the keyboard the same thing picking it right after the swipe would. A word you tapped out has no such list, so the strip offers only the word itself. Type anything and the strip goes back to ordinary suggestions. A space the keyboard added after a picked or glided word goes with it; a space you typed yourself stays. It only ever takes the word at the cursor, never another copy of it further back. Behind the cursor there has to be a word, or a word and one space; after a full stop, a line break or two spaces it does nothing. It also does nothing while text is selected, in a field that won't say what is behind its cursor or exactly where the cursor is, or when the field has changed since the keyboard last looked. A word still being typed is taken back whole, and the strip keeps what it was offering for it. ## Fixing a word you've typed past Turn on **Recent words** (Typing / Suggestions, off) and the three words you wrote last sit in a row of their own above the suggestion strip, the newest on the right. Each word is followed by what else it could have been, so fixing it is one tap. It is for the word you notice is wrong only after you have typed on. The strip keeps all its slots, and the keyboard grows by the row only while it has words to show; when they don't fit, the row scrolls, starting at the newest. Under Rows & bars / Recent words you can move the row in the row order, put the words at the start of the strip instead so the keyboard never grows for them, or have the row keep its height while it's empty so the keyboard doesn't jump between messages. What follows a word is what the keyboard had for it as you wrote it: the suggestions on the strip at that moment, and the other readings of its swipe if you swiped it. A word typed where nothing is suggested, such as a browser's address bar, kept nothing, so it gets the dictionary's corrections instead. Tap one and only that word changes, where it stands. The word you are still typing stays as it is, the cursor stays where it was, and the text after the word moves by the change in length. Your old spelling goes to the front of that word's choices, so the same row changes it back. A word on the row is a place in the text as well as a spelling, so if "form" appears twice, a tap changes the copy it was drawn for and not the other. If the text has changed since the row was drawn, such as an app rewriting it or you editing it by hand, the tap does nothing and the row is read again. The row holds only words you wrote in this field: it starts empty in every new field, and text that was already there never joins it, wherever you put the cursor. It also shows in a browser's address bar, where searches are typed, and never in password fields or while incognito is on. It is kept only while you type, never saved. ## Punctuation chips Turn on **Punctuation suggestions** (off by default) and a short row of marks (. , ? ! ') rides after the word candidates, set off by a thin divider. Tap one and it commits exactly as typing that key would, so a full stop or comma is one tap away without switching to the symbols layout. Those five marks are only the default. A **Punctuation marks** text field appears under the toggle and takes up to twelve characters, one chip per character, so a Bengali danda or the Spanish inverted marks can sit here instead. Clearing the field puts the five back. smart/punctuation-chips — Punctuation chips riding after the word candidates. The row only appears while word candidates are already showing, and only when no emoji prediction has claimed the tail instead. ## Shift re-cases the strip live Arm Shift and every chip in the strip re-cases in real time to match what would be typed if you tapped it right then. Shift is a latch rather than a hold, so one tap gives you Title Case, a double tap (caps lock) gives you UPPERCASE, and another tap goes back to lowercase. The chips follow the shift state, so they revert the moment it clears, which includes the letter or suggestion that spends it. The whole row follows, not only the top candidate. One thing is left alone: a contact-email suggestion, meaning anything containing `@`, since an address isn't meant to change case. smart/suggestion-strip-shift-recase — Suggestion chips re-cased to Title Case while Shift is held. This is a display rule only. It doesn't reject or change which candidate is offered, and it's unrelated to [reverting an autocorrected word](https://wmkeyboard.pages.dev/smart/autocorrect/#undoing-a-correction) with backspace. ## Emoji in the strip Matching emoji append to the end of the word candidates once you finish typing a whole word. The same mechanism can offer an emoji on an empty buffer once it has learned that a word or two-word phrase you often type is usually followed by one, and after an emoji it offers the words you tend to type next. Emoji suggestions have their own settings and their own page: see [Emoji suggestions while typing](https://wmkeyboard.pages.dev/emoji/customization/#emoji-suggestions-while-typing) for the toggle, the default, and how tapping or holding one behaves. What matters here is the tail of the strip, which fits up to four emoji. The emoji tail and the [punctuation chips](#punctuation-chips) both want that space, and emoji always wins. ## Learning from what you type Every word you commit, and every pair of words you type back to back, feeds a private on-device vocabulary. Both the completion ranking above and next-word prediction draw from it. How strongly a word is taught depends on how deliberately you typed it. Tapping a suggestion from the strip teaches harder than a word that was merely typed and committed, and a word autocorrect chose for you earns no credit at all, only the word pair around it. The [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) page covers that grading in full, along with adding, removing, and clearing what's been learned. What belongs here is what learning does to ranking: a learned word carries a ×500 weight (see the table [above](#completions-corrections-and-predictions)), so it competes with common dictionary words almost immediately instead of needing to be typed dozens of times first. If you type in more than one language, the keyboard separately tracks which secondary language you reach for and how often. Older commits decay geometrically, so the weighting follows your recent habit rather than your whole history. That's the 0.5×–2× scaling factor on secondary dictionaries in the table above. It's a deeper multi-language topic than this page covers, and it isn't tied to word-level personal-dictionary learning. A second, much faster signal reads the language of the field you're in right now and leans ranking toward it. That one has its own page: [Mixed-language typing](https://wmkeyboard.pages.dev/smart/mixed-language/). ## Removing an unwanted suggestion Press and hold the word on the strip. The menu that opens has **Never suggest "word"**, which puts it on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), and **Delete "word"** for a word the keyboard can forget: one you learned, one still waiting to be learned, one in Android's dictionary, one you adjusted the rank of, or one from a word list you imported. **Forget what you learned about "word"** sits beside it for a list word you have taught something: it drops the learning, its capitals and rank included, and keeps the word. **Synonyms** lists words with the same meaning, and tapping one puts it in place of the word. **Edit** opens the word card, which carries Never suggest and Delete and the rank controls besides. With [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, selecting a word in the text offers **Edit word**, which opens the same card for it. The [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip) page describes the menu and the card in full, and **Word menu** under Typing / Suggestions picks which of the optional items it shows. The same two lists are editable from settings, if you would rather not chase the word on the strip: the [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen for learned words, and the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) screen for words that should never be offered or corrected to, whether or not they were ever learned. ## Options Typing / Suggestions smart/suggestions-settings-group — The Suggestions group on the Typing settings screen. | Setting | Default | | --- | --- | | Suggestions | On | | Punctuation suggestions | Off | | Number of suggestions | 3 (range 2–6) | | Swipe down for more suggestions | Off | | Swipe up for more suggestions | Off | | More suggestions in a grid | Off | | Emoji takes a slot | Off | | Suggested emoji | 4 (range 1–4) | | Keep suggestions in place | Off | | Coloured suggestions | Off | | Scroll the suggestions | Off | | Suggestions in every field | On | | Suggestion strip always visible | Off | | Best suggestion in the middle | On | | Shorten long words | In the middle | | Numbers from the letter keys | Off | | Remember numbers you type | Off | | Space after a suggestion | On | | Space after a suggestion (per language, under Languages & layouts) | On (Off for Bangla) | | Block offensive words | On | | Context-aware suggestions | On | | Context model | On | | Re-check the previous word | On | | Phrase suggestions | Off | | Adapt to app tone | On | | Suggestion blacklist | Empty | **Suggestions** is the master switch for the whole strip described on this page. Turn it off and completions, correction candidates and next-word prediction all stop. [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) keeps working: while its own toggle is on, the word you're typing is still underlined as it's composed, and space or Enter still corrects it, fixes a missing apostrophe and learns it. Turn both off and letters go straight into the field with no underline. **Number of suggestions** is how many word slots the strip splits itself into. Three suits a phone; a tablet or a landscape screen has the room for more, at the cost of each candidate getting narrower. **Scroll the suggestions** drops the fixed slots for a row that scrolls sideways, so more candidates cost nothing in legibility (see [What's on the strip](#whats-on-the-strip)). **Suggestions in every field** overrides the handful of apps that ask the keyboard to hide suggestions on an ordinary text field. It also covers a browser's address bar: with it on, the words you type there are completed on the strip, though never autocorrected. Password fields and structured keypads (number, phone, and similar) never show suggestions whatever this is set to. **Space after a suggestion** adds a trailing space when you tap a candidate, the same as finishing a word yourself would. When the word you picked already has a space after it, as it does when you go back and fix a word mid-sentence, no second space is added. The caret moves past the space that's there instead, so the next word you type starts after it rather than running into the one you just picked. The same goes for a chip that replaces a word in the middle of a sentence, with the cursor inside the word or the word highlighted, and for one picked in a browser's address bar. In front of a mark that closes up onto the word, such as `?`, `,`, `.` or a closing bracket or quote, no space is put in: replace `question` in `A question?` and you get `A questionable?`. The space waits instead, and if you go on typing a word at that spot it goes in first, so `A questionable spa…?` comes out without a press of the spacebar. A mark typed there, or moving the cursor, drops it. A word typed into the middle of another one, with letters on both sides of the cursor, gets no space at all, since one would split the word in two. Each language also has its own **Space after a suggestion** switch, under Languages & layouts / your language / Spacing, on by default for every language but Bangla. Turn it off for one language and picks made while typing it go in bare, while every other language keeps the space. That suits Bangla, where a picked word often takes a case marker, postposition or suffix straight after it, and a kar picked in the middle of a word must not split it, so Bangla starts with it off. The main switch still rules every language, and the per-language rows are greyed out while it's off. None of these reach a conversion keyboard. Chinese, Cantonese and Japanese put the composer's own character candidates on the strip, and that list is how the language is typed rather than a guess about it, so it survives **Suggestions** being off, a field that asks for a silent strip, and a password box alike. See [Chinese, Japanese & Korean](https://wmkeyboard.pages.dev/languages/cjk/). **Numbers from the letter keys** (off) is for boards with the number row hidden, where each top-row letter carries a small digit in its corner. When every letter of the word you're typing sits on one of those keys, the strip's last slot also offers the number those digits spell: `qwe` on QWERTY offers `123` beside the words, and one tap types it. It reads the keys you pressed rather than the letters, so AZERTY's `azerty` spells `123456` and a layout with no digit hints offers nothing. A key's digit counts wherever its popup lists it, so `a` is still 1 with à ahead of it. The number is never the first slot, never what a space commits and never an autocorrect target. Two letters at least, so a lone `I` stays a word rather than an 8. Five digits or more are grouped the way the [number chip](https://wmkeyboard.pages.dev/smart/chips/) groups them, under its **Grouping style**, as long as **Group long numbers** is on. With the number row shown the digits have keys of their own, nothing is offered, and the row is hidden. **Remember numbers you type** (off) completes the numbers you type often, the way Samsung's keyboard does. A number of three digits or more that stands on its own is kept when you press space or enter after it, or leave the field. The next time you type its first digits, the strip shows the numbers you've typed that start with them, the most used first, and one tap replaces your digits with the whole number. No space follows it, since a number is as often followed by a full stop as by a word. A number is only one of its own after a space, at the start of the text, or after `+`, `(` or `#`, so the `880` of `+880` counts. Digits stuck to letters (`win10`) and the pieces of a number split by a comma, a colon or a dash (`1,234`, `12:30`, `0171-234`) are left alone. Nothing is kept from a password field, in incognito, or while **Learn from typing** is off. Number and phone fields have no strip at all, so this works in ordinary text fields. The list stays on the phone, holds up to 100 numbers, and is deleted with the other learned data under [Storage](https://wmkeyboard.pages.dev/privacy/data/). Seven of the rows above aren't in the Suggestions group at all. They live under Typing / Automatic corrections, and they're listed here because what they do is change the strip: - **Space after a suggestion** (on) is described above, and sits with the other spacing rules rather than with the strip. - **Block offensive words** (on, and only visible while autocorrect is on) uses the same filtering mechanism as the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) to keep a bundled list of profanity and slurs out of the strip, not only out of autocorrect. - **Context-aware suggestions** (on, also only visible while autocorrect is on) reorders the top eight candidates using the words you typed before them: your own learned word pairs and triples, a downloaded n-gram pack if you have one, common English pairs, and words already used in this conversation. It only reorders when there's real evidence to reorder on, so an unfamiliar context keeps the usual order. It never changes which word autocorrect would apply. - **Context model**, **Re-check the previous word** and **Phrase suggestions** (the first two on, the third off, all only visible while autocorrect is on) are the neural model described in [The context model](#the-context-model): the sentence-aware next words, the chip that re-reads the word before, and the few-words chip. - **Adapt to app tone** (on, and visible whatever autocorrect is set to) tilts ranking by where you're typing. Chat-speak like "lol" and "gonna" ranks a little higher in messaging apps and a little lower in email apps and email fields. It's ranking only: no word is ever hidden or blocked, and autocorrect doesn't change. The context model reads the same lists to know where it is. A physical keyboard can also pick a strip candidate by number without touching the screen (see [Hardware keyboard](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) for how that's bound). ## Details & edge cases - **A known word gets only its two nearest slips.** Anything in the active dictionary, a custom or secondary-language dictionary, or your personal dictionary is offered as-is, with its completions and at most two words one edit away, each scored as at most half as likely as the word you typed before context moves it. Wider corrections, two edits away, are only walked for a word nothing knows. Autocorrect never replaces a known word either way. - **A dictionary word and a contact name can collide on capitalization.** A contact's name keeps its own capitalization ("Wasi") independent of how a dictionary word would be cased. If the same word would otherwise appear twice, once lowercase and once capitalized, only the better-scored one is shown. - **Missing-space splits need a real word on both sides.** "ofthe" only becomes "of the" if both halves are independently known words. An arbitrary split of an unknown word never appears as a candidate. Turn off **Add missing spaces** under [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) and no split appears at all, so "history" half-typed is never offered as "his to". - **The word as typed keeps a slot.** Unless **Only offer new words** is on, the strip always holds what you have typed so far, exactly as typed, in the slot left of the middle, even when the engine has no idea what the word is, and in a browser's address bar as much as a text box. A long word the dictionary lacks no longer empties the strip. - **The word as typed can go first.** **Word as typed first** (Typing / Suggestions, off) draws it in the leftmost slot, wherever the engine ranked it, the way Multiling O Keyboard does. Only the drawing moves: the word a space writes is still the one in bold, and a hardware keyboard's digit badges follow the slots as drawn. - **A long word borrows room from short ones.** When one word would not fit its equal share of the strip and the words beside it are short, it takes the room they leave, so "international" next to "a" and "it" is drawn whole instead of shrunk. The short words keep exactly the width they need. While every word fits an equal share, the slots stay equal, so each word sits where your thumb expects it. A scrolling strip and **Keep suggestions in place** keep their own widths. - **Shrinking a word has a floor, and past it the ellipsis is back.** The two stages together fit roughly twice the slot width. A word longer than that is cut, and so is one in a slot squeezed narrow by a long smart reply or a full row of predicted emoji riding the tail. The cut goes in the middle of the word by default, as most keyboards do, so its start and its ending both stay readable: "Punct…tion". Set **Shorten long words** to At the end for "Punctuat…" instead. - **Only the word candidates scale.** Conversion candidates (Pinyin, kana, jyutping) keep their fixed larger size, since Han and kana need it, and they scroll instead of splitting the bar evenly. Predicted emoji, punctuation chips and the clipboard pill are all fixed-size too. - **The strip fades rather than snapping.** The composing buffer or field can empty out completely: you delete back to nothing, clear the field, or back out of a `:` emoji search. The last set of candidates stays visible and fades out over a short animation instead of disappearing instantly. - **A contact-email suggestion is never added to your personal dictionary**, since an address isn't a word to learn. Tap it inside a field the keyboard recognizes as an email input and it commits with no trailing space, because the address is usually the whole field. Tap it from an ordinary text field and it gets a trailing space like any other suggestion. [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) has that behavior in full. For the word-typo confidence math, and for how a correction is applied automatically instead of only offered, see [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/). [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) has the full story on what gets learned, how strongly, and how to review or clear it. To keep a specific word out of the strip entirely, see the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). --- # Emoji customization URL: https://wmkeyboard.pages.dev/emoji/customization/ Section: Emoji & expression Summary: Emoji fonts, keyword language packs, predictions and the emoji row. The emoji panel itself (search, skin tones, favourites) is covered in [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/). This page is about the settings that shape it: which font draws the emoji, which languages emoji search understands, when emoji get suggested while you type, and whether a dedicated row of them sits above the keys. emoji/settings-overview — The Emoji settings screen, with the live font preview row. ## Choosing an emoji font Emoji / Emoji style / Emoji font WM Keyboard ships no emoji font of its own. It draws emoji with whichever one you pick here. A preview row on the settings screen renders 😀😂🥰😎🤔👍❤️🎉 through the real rendering pipeline, so what you see there is what the keyboard shows, not a mockup. - **System** (default): your phone's own emoji pack. On Samsung phones that's Samsung's set, not Google's. - **Google**: fetches Noto Color Emoji, the stock-Android look, once through the system font provider and caches it on the device. - **Installed**: a list of every emoji font you've installed, picked with one tap. Fonts arrive there from [Addons](https://wmkeyboard.pages.dev/addons/installing/), such as Twemoji or OpenMoji, or from your own storage: **Add emoji font file** under the list takes a `.ttf` or `.otf`, keeps it, and selects it. That's the route on an offline build, and once a few are in the list you can move between them without picking a file again. Deleting the font you currently have selected falls back to System automatically. - **Custom**: a single font file you import yourself, kept separate from the library. Each import replaces the last one, so use Installed if you want to keep more than one. Text you send is always plain Unicode, whichever font you pick. The app or phone on the other end draws it with its own emoji font. This setting only changes how emoji look while you're composing them. An emoji font converted from a color format usually has no variation-selector table, so ❤️ (U+2764 + the invisible U+FE0F that makes it draw as color) can't resolve in it. WM Keyboard strips that invisible selector before handing the glyph to your chosen font, rather than let it fall back to the system font and look inconsistent with the rest of the row. Not every emoji font your phone can open is one it can draw. Android paints colour emoji from bitmap (CBDT) and layered (COLR) fonts, and the newer COLRv1 kind only from Android 13. A font whose pictures are stored as SVG or as Apple's `sbix`, or a COLRv1 font on an older phone, maps every emoji and then draws an empty space for each. WM Keyboard draws one emoji from the font when you pick it, and if nothing comes out it uses your phone's own emoji font instead, so the panel never turns into a grid of blank cells. The preview row shows the phone's emoji in that case: pick a different font file to get a different look. ## Fixing emoji that won't display Older phones and some OEM skins ship an emoji font that predates the newest emoji, and those show up as an empty "tofu" box. There's a one-tap fix for that, plus a toggle for tidying away whatever it can't reach. ### Getting a font that has them Emoji / Emoji style / Get the newest emoji A card sits under the font picker offering the current Noto Color Emoji, around 10 MB, from WM Keyboard's own addon repository. Tap **Download**. The font installs into the font library and selects itself, so the panel and the keys redraw with a face that actually has the new emoji in it. It selects itself once, as the download lands; a choice you make afterwards, Custom or System, stays put when you come back to this screen. The card then disappears. It comes back as **New emoji are available**, with an **Update** button, when the repository publishes a newer build. Under the card, **Add emoji font file** takes a `.ttf` or `.otf` from your phone, adds it to the font library and switches **Emoji font** to it, without having to pick **Installed** first. Switching **Emoji font** to Google is not the same thing, and that's the part that catches people out. A downloadable font is requested by family name with no version attached. The system font provider answers with whichever build of Noto it already has, which on a phone with a recent system emoji font is usually the older of the two, so the boxes stay. Installing a complete addon font like Twemoji or OpenMoji from [Addons](https://wmkeyboard.pages.dev/addons/installing/) works as well, and a **Download more** row right below the picker opens the catalogue filtered to emoji fonts. The download card is the shortcut to the font most people are after. ### Hiding the ones that are left Emoji / Emoji style / Hide emoji that your device cannot show This toggle (on by default) checks both your chosen font and the phone's own system font. It only hides an emoji from the panel, search, and suggestions when neither can draw it. An emoji your chosen font is missing but the phone's system font can still draw isn't hidden. It's quietly drawn in the system font instead, in a different style from the rest of the row, so it still gets through. An emoji you [added by hand](#adding-emoji-the-keyboard-doesnt-have-yet) is never hidden. ## Adding emoji the keyboard doesn't have yet Emoji / Emoji panel / Added emoji The emoji panel lists the emoji of the Unicode release the app was built with, Emoji 17.0 today. When Unicode publishes a newer one, its emoji can be typed on other phones before an update of WM Keyboard carries them. This screen is the way round that wait. Whatever you add here joins the panel at the end of the tab you pick. Panel search and inline `:word` search find it by its name and keywords, and it can be a favourite and shows up in Recent like any built-in emoji. There are two ways to add them: - **Add an emoji** opens a form. Paste the emoji (copy it from a message, a website or Emojipedia), give it a name and a few comma-separated keywords, and pick its tab. ZWJ sequences such as a new family or profession emoji paste the same way as a single character. The form refuses an emoji the keyboard already has and says which tab it is in. - **Import emoji-test.txt** adds a whole release at once. Download [emoji-test.txt](https://unicode.org/Public/emoji/latest/emoji-test.txt) from unicode.org in a browser, then pick it here. The keyboard adds every emoji in the file that it doesn't have yet, named and sorted into tabs by the file's own groups. Skin tone versions are left out: the panel shows one face per emoji, and the tone picker on a long press only knows the tones of built-in emoji. Press a row in **Your added emoji** to change it or delete it, or swipe it to delete. When an app update includes an emoji you added, the row says so. Nothing breaks in the meantime: the built-in entry takes over the slot and picks up your keywords, so you can delete yours whenever you like. Your phone's emoji font has to be able to draw a new emoji for it to look right. If it can't, the form and the row both tell you, and the emoji shows as a box or as its separate parts while you compose. It still goes into your message exactly as typed, so the person you send it to sees it if their phone is newer. [Getting a font that has them](#getting-a-font-that-has-them) fixes the look on your side. Added emoji are stored with your settings, so a [backup](https://wmkeyboard.pages.dev/reference/settings/backup/) carries them and sync copies them to your other devices. A list holds up to 500 emoji. ## Emoji search in your language Emoji / Emoji panel / Emoji keywords emoji/keyword-packs — The Emoji keywords screen, with per-language download and delete controls. Emoji search ships with English and Bangla keywords built in, so it works out of the box in both languages. For anything else, download a keyword pack. WM Keyboard's data repository carries packs for 126 languages, each a small gzipped file of `{emoji, name, keywords, category}` entries sourced from Unicode CLDR. Packs stack on top of the bundled English and Bangla keywords rather than replacing them, so downloading, say, Spanish adds Spanish search terms without losing the two you already had. The examples in this screen's own copy follow the languages you type in. The line ends "…all find 💰", and the words in front of it are up to three of your own: dinero and お金 for a Spanish and Japanese typist, पैसा and деньги for a Hindi and Russian one. It's the same feature either way. Showing you a script you don't read is a poor way to demonstrate it. Keywords stack, but names don't. Only one name fits in an emoji's long-press popup. A pack's display names are used while you're typing that pack's language, and the emoji's standard Unicode name is used otherwise, so a Spanish pack never renames your emoji while you're typing Bangla. **Download automatically** (on by default) fetches a keyword pack the first time you enable a language you type in, and skips anything already downloaded. Turn it off and packs only arrive when you tap Download next to a language yourself. A wider switch sits above it: **Download data automatically** under Languages & layouts / Language data, also on by default, covering every automatic download the keyboard makes. With that one off, packs stay put, and this toggle is hidden until it comes back on. Deleting a downloaded pack leaves a marker behind so automatic download won't quietly re-fetch it. If you want it back, download it again yourself. You can also import a pack by hand. Two formats work: a plain tab-separated file (`emoji`, comma-separated keywords, optional display name), or the data repository's own JSON file directly. WM Keyboard sniffs the format, so you don't have to convert anything first. Imported packs are kept separate from downloaded ones on disk, but merge into the same search index. ## Emoji suggestions while typing Emoji / Suggestions / Emoji suggestions emoji/word-suggestions — Emoji suggestions appended to the strip after typing a whole word. Turn this on (it's on by default) and matching emoji append to the end of the suggestion strip as you finish typing a whole word. "Birthday" offers 🎂🎉🥳🎁, and it works the same way for Bangla words. It also fires on an empty buffer from words you've just typed, learned over time, so a word like "you" can suggest ❤️ without you typing anything new. The keyboard learns emoji the way it learns words, as part of the phrase around them. Send "I love you ❤️" a few times and both "you" and "love you" start offering ❤️, the two-word phrase ranked ahead, so "thank you" can learn 🙏 while "love you" keeps ❤️. It works the other way too: after "❤️ see you", typing ❤️ offers "see" as the next word. Learning waits until the text settles, the same as for words, so an emoji you delete straight away teaches nothing. A word the keyboard doesn't recognise never gets an emoji attached, since it might be a typo. **Learn from typing** and incognito's **Pause learning** cover all of it. The chip doesn't vanish at the space, which is exactly when you'd reach for it. Type "happy birthday" and 🎂 is still there after the space, until the next word replaces it. Four sources feed the match, stacked so a hit from any one of them counts: - a small curated table, which is where the ordering judgements ("birthday" leads with 🎂, not 🥳) and the Bangla triggers live; - GitHub's own word-to-emoji data, which covers far more English than a hand-written table can: "hooray", "espresso", "workout"; - the `:shortcode:` names, which mostly earn their place on two-word matches nothing else can reach. "Alarm clock" offers ⏰, "crossed fingers" offers 🤞. Two-letter codes are left out on purpose, because `:it:` and `:us:` are flags, and firing them at anyone typing "it" or "us" is the wrong-emoji case this whole feature avoids; - exact keyword hits from the emoji catalog, in whatever languages you have keyword packs for. **Press an emoji suggestion**, the row under the switch, decides what happens when you tap one: - **Replace word** (default, Gboard-style): swaps the word you typed for the emoji. Typing "birthday" and tapping 🎂 leaves only 🎂. - **Keep word**: adds the emoji after the word instead, giving "birthday 🎂". **Long-press a suggestion to get the other one for that insert.** With Replace word set, holding 🎂 keeps the word and gives you "birthday 🎂"; with Keep word set, holding it replaces the word. The setting decides which of the two a tap does, and the hold is the one-off for the other, so neither costs a trip to settings. Under TalkBack the long-press is announced by what it will actually do, not as "long press". Both of those describe tapping while the word is still being typed. Tap the chip after the space and there's no word left to replace, so the emoji lands at the cursor under either setting: "happy birthday " becomes "happy birthday 🎂". A hold there does the same thing as a tap, since there's no word left for the two modes to disagree about. ## Inline emoji search and shortcodes Typing a `:` isn't part of the suggestions setting above. It has its own toggle, and that toggle lives on the Typing settings screen rather than the Emoji screen: Typing / Suggestions / Inline emoji search With this on (the default), typing a colon at a word boundary turns the whole suggestion strip into an emoji search: `:cat` offers 🐱 and friends, replacing ordinary word suggestions entirely while the search is active. Backspacing over the colon returns to normal suggestions. emoji/inline-colon-search — Typing a colon turns the suggestion strip into emoji search results. The same toggle also covers exact shortcode names. If you type the *closing* colon and the text between the two colons is an exact match (`:tada:`, `:thumbsup:`), the whole thing is replaced with the emoji immediately, with no tap needed. A name that's only a partial or unknown match is left as ordinary typed text. An unrecognized shortcode is never quietly eaten. An emoji you pick from the search, by tapping it or by closing the colon, counts as used the same as one tapped in the panel: it moves to the front of the Recent tab and the emoji row. ## The dedicated emoji row Emoji / Emoji row / Emoji row A row of your own emoji, similar to Gboard's, that sits between the toolbar and the keys. emoji/emoji-row-own — The emoji row in Own row mode, above the key rows. - **Off** (default): no emoji row. - **Button**: adds a toggle to the toolbar strip that swaps it for the emoji row on tap. - **Own row**: keeps the emoji row permanently visible above the keys. A [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/#keyboard-modes) can set the emoji row its own way: the Chat mode shows it as its own row, and Passwords turns it off. Every mode that sets a different value from yours is listed under the setting as a chip, like "Chat: Own row". Tap the chip to open that mode's editor. Once the row is showing (anything other than Off), three more settings appear: - **Emoji row content**: Most used (default), Recent, or Favourites. Whichever you pick, your favourited emoji always sort to the front. - **Emoji in the row**: a slider from 3 to 16, defaulting to 8. The row splits its width into that many slots and shrinks the emoji to fit, so a higher number packs more emoji in at a smaller size. - **Scroll the emoji row**: on by default. Swipe the row sideways for the emoji past the visible slots. Turn it off and the row never moves: it shows exactly that many slots and nothing past the last one, so a sideways swipe can't slide the whole thing out from under your finger. Before you've used any emoji there's nothing of your own to fill the row with, so it starts on a set of 24 common ones. That's deliberately more than fits across a phone, which is what scrolling is on by default for. They're the only entries the **Default skin tone** setting applies to; everything the row picks up from your own history is already an exact emoji, tone included. Where the row sits relative to the toolbar and the symbol row (above them, below them, in between) is a separate setting under Rows & bars on the settings home screen, not on this screen. ## Arranging the panel like your old keyboard Emoji / Emoji panel / Emoji categories / Arrange like Years on one keyboard teach your thumb where the emoji are. **Arrange like** copies that layout: pick Gboard, SwiftKey or HeliBoard and the panel takes that keyboard's tabs, in its order, with every emoji in the place it had there. **Unicode** (the default) is WM Keyboard's own order, straight from the Unicode release. Each keyboard groups emoji a little differently from WM Keyboard's catalogue, whose Nature tab holds both the plants and the sky and weather. Picking a preset moves emoji to the tab that keyboard keeps them on, and hides any tab left empty: | Preset | Tabs | What moves | |---|---|---| | Gboard | 9 | Hands and body parts join the faces on Smileys, and so do the moon faces and stars. Plants, weather and landscapes join Animals. Clocks join Objects, and instruments and cameras join Activities. Nature is hidden. | | SwiftKey | 8 | Faces and people share one Smileys tab. Plants join Animals, and the sky and weather join Travel. Nature and People are hidden. | | HeliBoard | 9 | Unicode's own groups: plants join Animals, and the sky and weather join Travel. Nature is hidden. | The grid changes with the order, so the same swipe scrolls to the same emoji you'd reach on that keyboard: | Preset | Columns | Row height | Emoji size | Scrolling | |---|---|---|---|---| | Gboard | 8 | 51 dp | 34 | One long list | | SwiftKey | As many as fit at 44 dp | 44 dp | 30 | A page per tab | | HeliBoard | 8 | 45 dp | 35 | A page per tab | These are each keyboard's values on a phone held upright at its default keyboard height, read from its own resources and code. Gboard's column count is a server setting that defaults to 8, and its rows grow when it shows fewer columns. All of them can be changed afterwards under [Emoji panel](https://wmkeyboard.pages.dev/reference/settings/emoji/) (**Columns**, **Row height**, **Emoji size**, **One long list**). The orders were read from Gboard 18.4, SwiftKey 9.13 and HeliBoard 4.1. A preset is a starting point, not a mode. It fills in the same tab order, hidden tabs and per-tab emoji order that you can [drag by hand](https://wmkeyboard.pages.dev/reference/settings/emoji/#emoji-categories), so you can keep rearranging afterwards. Once you've moved something or resized the grid the setting shows **Your own**, and picking a preset again asks before it replaces your changes. An emoji the other keyboard doesn't have, such as one from a newer Unicode release, goes next to the emoji it follows in the catalogue, on whatever tab that one sits on. ## Details & edge cases - **The font preview is exact, not illustrative.** The eight-emoji preview row on the Emoji font setting runs through the same shaping code the keyboard itself uses, including the ❤️ variation-selector workaround, so what you see there is what you'll get on the keys. - **Installed emoji fonts share storage with text fonts**, but are listed separately in the picker. A colour emoji font would make an odd choice for key labels, so the two lists never cross over. - **Added emoji have no tone or gender variants.** Each emoji you add, or that an emoji-test.txt import adds, is its own cell in the grid with no long-press variants. An import adds the man, woman and person forms of a new profession emoji as three separate cells. - **Bundled Unicode data, not user data.** Keyword packs, shortcode names, and the trigger words behind emoji suggestions are all static files. They ship with the app or download from WM Keyboard's own data repository, and none of it depends on what you type. - **Emoji predictions can drop under battery pressure.** Power Saving mode has its own setting that can suppress emoji predictions to save cycles. See [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/) for that behavior. - **Kaomoji and ASCII emoticons are a separate system.** They live in their own library behind the Kaomoji tab, ignore the emoji font setting entirely (they're plain text), and keep their own favourites and recents. See [Kaomoji & text art](https://wmkeyboard.pages.dev/emoji/kaomoji/). - **Two other keys can open the emoji panel directly.** The language-switch key can be replaced with an emoji key (on by default, under Layout & size), and the comma key can be too (off by default). Both are separate from the emoji row and the toolbar button described above. --- # GIFs URL: https://wmkeyboard.pages.dev/emoji/gifs/ Section: Emoji & expression Summary: Search GIFs from KLIPY and GIPHY and send them, with a copy-to-clipboard fallback when an app won't take them directly. The GIF tool searches KLIPY and GIPHY from a panel inside the keyboard, then sends the result straight into the field you're typing in. No switching apps, no copy-paste. On the F-Droid build it also searches Wikimedia Commons without any key at all, an educational corpus rather than reaction GIFs. Every other build needs a KLIPY or GIPHY key before the tool can search anything online, unless you turn on [GifCities](#gifcities), which needs no key. [Offline GIF packs](#offline-gif-packs) need neither a key nor a connection. emoji/gif-panel-trending — The GIF panel, showing trending results on open. ## Searching for a GIF Open the GIF tool and it fetches trending GIFs immediately, before you type anything. Close the panel or move to another text box and it keeps your search: it opens on that search next time, unless you turn off **Remember the last search**, which makes every opening start on trending. Start typing in the search box and results update live, 450ms after you stop. There's no Enter to press. While there's a query, an ✕ at the end of the search box empties it in one tap and brings trending back, with the keys still on the box for a new search. That's different from the [web and image search tools](https://wmkeyboard.pages.dev/tools/search/), which wait for you to submit the query; GIF and sticker lookups are cheap enough on KLIPY and GIPHY to search as you type. Results are laid out as justified rows of animated previews rather than a fixed grid: every preview keeps its own aspect ratio and nothing is cropped, so squarish results sit three across and a pair of wide ones fills a row on its own. **GIFs per row** sets how many share a row, from one to six, and **Gap between cells** the space around each one. The search box names where results come from. On KLIPY it reads "Search KLIPY" and on GIPHY "Search GIPHY", each with that service's small "Powered by" mark at its end, as both services ask of apps that use them. GIPHY's mark keeps its colours, so on a light theme it sits on its own small black plate. The same goes for the sticker panel. Before you've typed anything, a scrollable row of category chips sits above the grid: Trending first, then Reactions, Love, Happy, Sad and so on. Tap one to run it as a search, which is a way to browse without thinking of a word. Tap the active chip, or Trending, to go back. The chips come from the provider itself when it publishes categories, and from a built-in list when it doesn't, so the row is there offline too. It belongs to the default view only. Type anything and it steps aside for the results. With the full-screen picker turned off it also needs a certain amount of key height to be worth drawing, so a short three-row layout with the number row off loses it rather than losing the last row of the grid. Along the bottom sits the same row the [emoji panel](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers) ends in: ABC, a switch to emoji, GIFs and stickers, space and backspace. The switch jumps straight to the emoji or sticker panel, and so does a sideways swipe across the results: right for emoji, left for stickers. ABC goes back to the keys. Emoji / Emoji panel / Switch to GIFs and stickers moves the switch next to the search box instead, or turns it off, and the row keeps its ABC, space and backspace either way. If you rearranged the emoji panel's bottom row, the GIF panel takes your row. The row steps aside while you type a search, since the keys come back under the panel then. The GIF panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) too, so you can lay it out yourself: Tools / GIFs / Layout / Panel layout. The search box, the chips and the results are one component, **GIF search and results**, and every other cell is a key, so the panel can carry a different bottom row, a second row of keys, or the switch in a place of its own. Once you change it, it stops following the emoji panel. The panel is as tall as Emoji / Emoji panel / Panel height makes the emoji panel, so the three panels never change the keyboard's height between them. ### Favourites and recent GIFs Long-press a GIF and choose **Add to favourites**. A **Favourites** chip then leads the category chips, ahead of Trending, and tapping it shows every GIF you added, the newest first. To take one out, long-press it (in Favourites or anywhere else it shows up) and choose **Remove from favourites**. The chip only appears once you have a favourite. You can keep up to 500. The category chips are left out when the keyboard is too short for them, or in a field that can't take images. Then a **star** button sits at the end of the search bar instead: tap it to show your favourites, and again to go back. Turn on **Recent GIFs** and a **Recent** chip sits next to it, with the last 48 GIFs you sent from the panel, the newest first. It is off by default, because it keeps a record of what you sent. While [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on, sending a GIF does not add it to the list. Turning the setting off deletes the list. Favourites and recent GIFs are the provider's GIFs, so they normally load from the provider like any other result and need a connection. Turn on **Keep favourites on the phone** and the keyboard saves a copy of each favourite, so Favourites shows and sends with no connection, and a GIF stays even if the provider drops it. The copies download when you add a favourite or open Favourites, and only on a connection that [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) allows. Turning the setting off deletes the copies. The favourites stay, and load from the provider again. Favourites and recent GIFs are for the GIF panel only. The sticker panel has [your own sticker packs](https://wmkeyboard.pages.dev/emoji/stickers/) instead. Both chips sit in the category row, so they follow its rules: they go away while you type a search, and a short panel that has no room for the row has no room for them either. ### Making the panel taller A short bar sits across the top of the panel. Drag it up to make the panel taller, so more rows of results show at one time, and drag it down to make it shorter. When you let go, the panel keeps that height from then on. The bar sets the same **Panel height** as the emoji settings, so the emoji and sticker panels change with it, and the three still open at one height. The panel never covers more than most of the screen, and the bar is not there while you type a search, or where the screen has no room for a taller panel. ### Hiding the search while you scroll With **Hide the search when you scroll** on, scrolling down the results folds the search box and the rows of chips away, and the results get their room. Scroll back up, or pull down at the top of the results, and they come back. The panel itself stays the same height. While you type a search, the search box always shows. Off by default, and shared with the sticker tool. If neither provider has a key configured and no offline pack is installed, the panel shows a "needs an API key" notice with a shortcut into settings instead of a grid. The F-Droid build never hits that notice, since Commons is already searching. ## Sending a GIF emoji/gif-no-rich-media — A field that can't accept images. The panel dims and shows the standing notice. Tap a result and it downloads and lands in the field in one step. There's no separate "insert" confirmation. You don't have to wait for one to arrive before tapping the next. Tap several in a row and they queue up: the first downloads while the rest wait, each with a small number in its corner for its place in the line, and they go out one after another in the order you tapped them. Tap a numbered GIF again to take it back out of the queue. If you leave the field before the queue is done, the GIFs still waiting are dropped rather than sent somewhere else. What happens to each one depends on what the field will accept: 1. **The field doesn't take images at all.** Plain text boxes can't receive a GIF through Android's `commitContent` mechanism. The panel shows a standing notice, "This field does not accept GIFs. Try a chat app or a notes app.", and tapping a result does nothing beyond a short vibration. Nothing downloads. This is a pre-send check, not a failed attempt. 2. **The field takes images.** The full-size file downloads and commits using a MIME type the field advertises it can accept. With Tools / GIFs / Send GIFs as set to Sticker, a WebP result first offers WhatsApp's private sticker MIME (`image/webp.wasticker`) before falling back to plain `image/webp`. Android has no official "this is a sticker" flag, so this is a best-effort convention rather than a documented API. The PNG fallback only ever applies to a WebP-format download. If the field won't take `image/webp` but does accept PNG, a still WebP is re-encoded to a still PNG rather than failing outright. An animated WebP is skipped, since a PNG can't hold the animation. A plain `image/gif` download (what most GIF-panel results actually are) has no such fallback. If the field doesn't accept `image/gif`, it goes straight to the clipboard fallback below. 3. **Nothing matches.** If the field won't take any form the tool can produce, the file goes to the system clipboard instead, with a toast: "This app does not accept images here. The keyboard copied the image. Paste it instead." Paste it in manually from there. Animated GIFs can never actually become stickers. Android has no animated-WebP encoder, so a GIF-format result keeps sending as a plain image no matter what `Send GIFs as` is set to. Only sources that already provide WebP can take the Sticker path. ## Offline GIF packs The panel can also show GIFs kept on the phone, which need no key and no connection: [CleverKeys' offline GIF packs](https://github.com/tribixbite/CleverKeys/releases/tag/CleverKeys-GIF). Download a pack in your browser, then under Tools / GIFs / Providers press **Offline GIF packs** to open its screen, press **Import** and pick the zip. **Get the files** on the same row opens the release page. Once a pack is installed the panel gains an **Offline** chip. It is a grid of its own in every provider mode, never mixed in with online results, and like the user's own sticker packs it never asks about [mobile data](https://wmkeyboard.pages.dev/privacy/data-saver/). Search matches the words each GIF is tagged with; the category chips are the packs' own (Funny, Love, Sad and the rest), and one searches for everything filed under it. A search lists up to 240 GIFs. Tapping one sends it as any other result is sent, without a download. Only the **Discord community** packs (about 100 MB each, around 900 animated GIFs) carry GIFs to send. The **thumbnail** packs hold 80-pixel still previews and nothing else, so the import refuses them and says so. Each pack installed is listed with its size and a Delete. Importing a pack again replaces it. Everything about getting files onto a phone that cannot download is on [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/). ## Long-pressing a result Long-press a GIF instead of tapping it and a small menu opens over the grid. At its top are the GIF's name and, for a GIPHY result whose uploader GIPHY names, who made it ("by @name"). - **Add to favourites** puts the GIF in the [Favourites chip](#favourites-and-recent-gifs). On a GIF that is already a favourite the row reads **Remove from favourites** and takes it out. - **Copy** downloads the GIF and puts it on the system clipboard, so you can paste it wherever you like rather than into the field you happen to be in. It's the same thing the fallback in step 3 above does, except you asked for it instead of finding out afterwards that the field wouldn't take the file. - **Report** opens a mail draft to the maintainer naming the provider, your search term, and the result's id and URL, with room at the top to say what's wrong with it. Nothing is sent from the keyboard. The draft lands in your mail app, and you can read, edit, or abandon it. KLIPY and GIPHY answer a search out of their own catalogues, so a result nobody vetted can come back. This is where that goes. Back (or tapping outside the menu) closes it without doing anything. Saving to a pack is sticker-only, since a GIF can't become one. That row shows on the [sticker panel's](https://wmkeyboard.pages.dev/emoji/stickers/) version of this menu, not here. emoji/gif-long-press — Long-press menu over a GIF result: Copy and Report. ## Options Tools / GIFs GIFs are part of the **Stickers, GIFs and emoji mixes** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). One switch there takes the GIF panel off the toolbar and the toolbox along with the stickers and the mixes, and brings it back with the providers you had chosen. | Setting | Default | What it does | |---|---|---| | Providers | Depends on the build | Who the panel asks, in order: KLIPY, GIPHY, Wikimedia Commons or another MediaWiki, GifCities and offline GIF packs. Add, reorder, switch off or set up each one, and choose **Tabs**, **Mixed** or **One at a time**. See [Choosing providers](#choosing-providers). | | Full-screen picker | On | The toolbar, emoji row and symbol row step aside so the GIF grid gets the full keyboard height, with search moved up next to a back button. Shared with the sticker tool. | | GIFs per row | 3 | A slider from 1 to 6: the most GIFs side by side in a row. Fewer makes each one bigger. A row of wide GIFs can hold fewer than this. The sticker tool has its own. | | Gap between cells | 4 dp | A slider from 0 to 12 dp: the space around each result. Shared with the sticker tool. | | Panel height | Same as keyboard | How much taller than the keyboard the emoji, GIF and sticker panels open, up to 400 dp more. The same setting as Emoji / Emoji panel / Panel height and the [bar on top of the panel](#making-the-panel-taller). | | Remember the last search | On | Reopening the panel lands on the search and category chip it was left on. Off, it always opens on trending. Shared with the sticker tool. | | Hide the search when you scroll | Off | Scrolling down folds the search box and the chips away, scrolling up brings them back. See [Hiding the search while you scroll](#hiding-the-search-while-you-scroll). Shared with the sticker tool. | | Recent GIFs | Off | A **Recent** chip with the last 48 GIFs you sent. Turning it off deletes the list. See [Favourites and recent GIFs](#favourites-and-recent-gifs). | | Keep favourites on the phone | Off | Saves a copy of each favourite, so it shows and sends with no connection. Turning it off deletes the copies and keeps the favourites. | | Panel layout | Standard | The keys around the results. **Standard** ends the way the emoji panel ends. See [Panel layouts](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts). | | Send GIFs as | Image | Whether a tap tries to land the result as a sticker-style MIME first or always as a plain image. See the sticker-limitation note above. | | Content filter | High | Four levels (Off, Low, Medium, High) mapped to each provider's own content rating (High maps to G-rated results, Off to R). Shared with the sticker tool. | | Results for each search | 24 | A slider from 6 to 48. How many GIFs one search or the trending view loads. Shared with the sticker tool. | You only need one working provider for the tool to work: a KLIPY or GIPHY key, Commons (already in the list on the F-Droid build), GifCities, or an offline pack. **Send GIFs as** is only shown here, and the sticker tool's **Send stickers as** only on its own page. Each governs the panel it sits under, so neither page offers the other's setting. See [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) for that one. ## Choosing providers Tools / GIFs / Providers is the list of services the panel asks. It works like every other [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a row off to skip it, and use **Add a provider** for another one. - **KLIPY** and **GIPHY** need an API key, unless the build carries one. Paste a free key from `partner.klipy.com` or `developers.giphy.com` in the provider's setup. KLIPY replaced Tenor after Google retired Tenor's third-party API in mid-2026. The keys are shared with the sticker tool. - **Wikimedia Commons** needs no key. Its GIFs are mostly educational rather than reactions. Add it again with an address to search another MediaWiki as well. - **GifCities** needs no key. See [GifCities](#gifcities). - **Offline GIF packs** is the packs you imported. Its screen lists them, each with a Delete, and the row that imports more. **How to use them** decides the chip row: - **Tabs** (the default): one chip per provider, to switch between them. - **Mixed**: one grid that interleaves every provider evenly, with an **Online** chip for it and an **Offline** chip for your offline GIF packs. GIPHY keeps a chip and a grid of its own, because its terms forbid mixing its results with another service's. - **One at a time**: one grid from the first provider that answers. If it fails or finds nothing, the next one is asked. Offline packs keep their own chip. Without an offline pack or GIPHY next to another provider, Mixed and One at a time show no chip row at all. ## GifCities GifCities is the Internet Archive's search over the GIFs it saved from GeoCities home pages before GeoCities closed in 2009. Add **GifCities** under Tools / GIFs / Providers and it joins the panel as one more provider, with a chip of its own in Tabs mode or mixed into the Online grid. It needs no key and no account, so it works on every build. Expect the 1990s web rather than reaction GIFs: under construction signs, spinning globes, dancing figures, animated email icons. Results smaller than 32 pixels on a side (bullets, spacers) are left out. GifCities has no trending feed, so with the search box empty it shows a search for a different classic term each day. - **What it sends.** Your search goes to `gifcities.archive.org`. Previews and the file you send load from the Wayback Machine at `web.archive.org`. - **No content filter.** These GIFs carry no rating, so **Content filter** has nothing to apply to them. - **GIFs only.** The sticker panel never offers it, and it has no category list of its own, so the panel's built-in categories stay up. ## Details & edge cases - **Not pinned to the toolbar by default on a phone.** The onboarding wizard turns the GIF tool on for every persona, but a phone's out-of-the-box pinned toolbar only holds Emoji, Clipboard and Settings, so GIF sits in the [toolbox](https://wmkeyboard.pages.dev/tools/overview/) until you drag it onto the bar or open it from there. A tablet is the exception: its wider default bar pins GIF from the start. - **Trending, not random.** An empty search box always shows each provider's trending feed, not a blank grid, useful as a starting point before you type a query. - **24 results per fetch, and you can change it.** That's the number each provider is asked for on a search or a trending call, and the **Results for each search** slider moves it anywhere from 6 to 48. In Mixed mode every provider is asked for that many and the merged grid is then cut back down to it, so the number is what the grid holds in total, not what each source contributes. - **Two of these rows never grow a restore button.** Settings you've moved off their shipped value normally pick up [a small restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) next to the control. **Content filter** is the exception on this screen. It's drawn as a bare segmented row under a heading rather than as a full setting row, so there's nowhere for the button to sit. Put it back by picking High yourself. The provider list has **Reset to default** instead. - **No size cap on downloads.** Stickers cap the source file size. A GIF download has no maximum, so a very large or very long GIF still downloads in full before sending. - **Recently sent files are cached, not stored permanently.** Downloaded files are kept in a local cache, so re-picking the same GIF skips re-downloading it. The cache keeps only the 30 most recently touched files and deletes the rest, so it isn't a place to go looking for something you sent a while ago. - **Network use is opt-in and tool-scoped.** KLIPY and GIPHY are only contacted while the GIF panel is open: trending on open, live search as you type, and the download on tap. Nothing happens in the background. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full breakdown of what each online tool sends. For custom sticker packs, long-press-to-save, and the Local tab, see [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/). For the toolbar and toolbox in general, see [The toolbar](https://wmkeyboard.pages.dev/tools/overview/). --- # Kaomoji & text art URL: https://wmkeyboard.pages.dev/emoji/kaomoji/ Section: Emoji & expression Summary: A kaomoji tab with tag tabs, your own faces, favourites, search, suggestions and Emotic import (ノ◕ヮ◕)ノ*:・゚✧ Kaomoji are Japanese-style faces built from CJK and box-drawing characters, like `(╯°□°)╯︵ ┻━┻`. Western ASCII emoticons like `:-D` and `<3` are the simpler cousin. Both live in one library behind the Kaomoji tab at the end of the emoji panel. The library is yours to change: add faces, give them tags, sort them, star the ones you use, and bring in a library you built in [Emotic](https://f-droid.org/packages/futuristicgoo.emotic/). emoji/kaomoji-tab — The emoji panel's Kaomoji tab, with its tag tabs across the top. ## Turning the tab on Emoji / Emoji panel / Kaomoji tab The tab is off by default. Turn it on and a tab labelled `^_^` appears at the end of the emoji panel's tab strip, after Recent (or Most used) and every Unicode emoji category, so every tab you already know keeps its place. Kaomoji and emoticons share that one tab. If you would rather keep them apart, turn on Emoji / Emoji panel / Kaomoji / Separate Emoticons tab and a second tab, labelled `:)`, holds the ASCII faces. Each face knows which family it belongs to. A face you add is filed by its characters: printable ASCII alone makes an emoticon, anything else a kaomoji. You can change that in its editor. All the tab's own settings are on one page: Emoji / Emoji panel / Kaomoji ## Tag tabs A row of tags runs across the top of the tab. Tap one and the grid shows only the faces under that tag. The row scrolls sideways when it holds more than fits, and keeps the tag you picked in view. It never pages by swiping: a sideways swipe on the panel still moves between emoji categories, as it always did. The row starts with three tags of its own. Favourites holds the faces you starred and Recent the faces you used, latest first; each appears once it has something in it. All holds every face in your order. Your own tags follow, in the order you gave them. The keyboard ships 312 faces under 18 tags: Happy, Love, Sad, Angry, Table flip, Confused, Surprised, Cool, Cute, Animals, Actions, Sleepy, Greetings, Party, Apologies, Playful, Neutral and Figures. Moods the two families share, such as Happy and Love, are one tag holding both. A face can carry more than one tag and shows under each. Emoji / Emoji panel / Kaomoji / Open on picks the tag the tab opens on: the last one you used (the default), Recent, Favourites or All. If you prefer the tab as it used to be, turn off Emoji / Emoji panel / Kaomoji / Tag tabs. The tab then shows one long list with a heading for each tag. ## Edit buttons A narrow column of four buttons sits beside the grid. Search opens the search box. Add opens the editor for a new face, which starts under the tag you have selected. Edit puts the grid in edit mode: tap a face to change it, or hold a face and drag it to a new place, then tap the tick to finish. Tags opens the tag list in place of the grid. Emoji / Emoji panel / Kaomoji / Edit buttons puts the column on the left (the default) or the right, or turns it off. Under it, one switch for each button keeps only the ones you want. With the column off, the search button sits at the start of the tag row and the other three at its end, so you can still change your faces from the keyboard. Dragging in edit mode moves a face only among the faces that tag shows. Every face outside that tag keeps its place in All. The Recent tag is in time order, so nothing there can be dragged. ## Holding a face Hold a face and a bar of actions opens over the bottom of the grid. The star adds the face to Favourites or takes it off. The pencil opens its editor, and the copy button puts it on the clipboard without typing it. The bin asks once more, then removes the face, and the cross closes the bar. ## Adding and changing a face in the keyboard The editor opens in place of the grid, and the keys come back underneath it so you can type. Type or paste the face into the box at the top. Under it, a row of chips holds every tag: tap a chip to put the face under that tag, or tap it again to take it off. The box at the end of the row types a new tag; press Enter and it joins the face. The first chip switches the face between kaomoji and emoticon. The tick saves, the cross cancels, and the bin deletes a face that already exists (it asks once more). Enter in the face box also saves. ## The tag list The Tags button replaces the grid with a list of your tags, each with the number of faces under it. For each tag you can move it up or down the row, hide it from the row with the eye (its faces stay in All), rename it, or delete it. Deleting a tag takes it off its faces and does not delete the faces. Renaming a tag to a name that already exists merges the two. The + at the top adds an empty tag, and the gear opens the library page in the settings app. ## Search The search button opens a box with the matches above it, and the keys come back underneath. It searches tags and the faces themselves. A tag that matches exactly comes first, then tags that start with what you typed, then tags that contain it, then faces whose characters contain it. Press a match to type it, or press Enter to type the first one. The emoji panel's own search lists matching faces too, under a Kaomoji heading after the emoji. Turn that off with Emoji / Emoji panel / Kaomoji / Kaomoji in emoji search. ## Kaomoji suggestions Emoji / Emoji panel / Kaomoji / Kaomoji suggestions This is off by default. Turn it on, and when the word you are typing is the name of a tag, the suggestion strip offers up to two faces from that tag in front of the emoji suggestions. Your starred faces come first, then the ones you use most. Add a tag such as `shrug` or `tableflip` to the faces you reach for, and typing that word puts them one tap away. A tag shorter than three letters never triggers this. A face from the strip goes in the same way an emoji suggestion does: after your word, or in place of it, as Emoji / Suggestions / Press an emoji suggestion decides. Holding it does the other. ## The library in the settings app Emoji / Emoji panel / Kaomoji / Faces The Faces screen lists every face, filtered by a row of tag chips. Drag a face by its handle to move it, press it to change its text, tags, family and star, and press the bin to delete it. Add a face from the row at the top. Emoji / Emoji panel / Kaomoji / Tags The Tags screen does the same for tags: drag to reorder the tag row, the eye to hide a tag, the pencil to rename it, the bin to delete it. Both screens and the keyboard write the same file, so a change in one shows in the other the next time it opens. ## Importing from Emotic and other files Emoji / Emoji panel / Kaomoji / Import The import reads an Emotic export, a file this keyboard exported, or a plain list. Emotic 0.1.11 and later export a `.tar.gz` file; older versions export the bare `emoticdb.sqlite` database. The keyboard reads both. It brings in every face with its tags, keeps Emotic's order for the faces and the tags, and drops the repeats Emotic's database can hold. A plain list has one face on each line. A tab after the face, followed by tags with commas between them, files it under those tags. Before anything changes, a dialog says how many faces and tags the file holds. **Add** puts the new faces after yours and gives a face you already have any tags it was missing. **Replace all** empties the library first. Emotic also keeps reaction images, which it calls emotipics. When the export carries some, the keyboard puts them in a new sticker pack named Emotic, with their Emotic tags as the stickers' search words. Images Emotic marked as excluded stay out. See [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) for using the pack. Treat an archive from someone else with care. The keyboard reads only the database and the image folder out of it, never writes a file outside its own cache, and refuses an archive past 512 MB or an image past 20 MB. ## Exporting, backing up and resetting Emoji / Emoji panel / Kaomoji / Export saves your faces, tags, hidden tags and stars to a JSON file. How often you used each face stays behind. The library also has its own Kaomoji section in [backups](https://wmkeyboard.pages.dev/privacy/backup/), on by default for an export and for automatic backups. Sync leaves it out unless you add it, because every tap on a face changes it. Emoji / Emoji panel / Kaomoji / Reset the library goes back to the faces the keyboard ships, after one confirmation. It deletes the faces you added, your changes, your stars and your recent faces. Until you change something or use a face, the library is the built-in set and no file exists. A later version that adds faces to the built-in set shows them to you only while that is still true. ## Inserting long faces safely A cell shrinks its font size step by step until the whole face fits on one line. It never wraps and never trails off with an ellipsis, since half a kaomoji reads as garbage rather than a smaller version of the same face. The glyph stays invisible for that first layout pass, so you never see it flash past its own edges before settling. Tapping a cell commits the face as one atomic insert. Any word you were mid-typing is flushed first with autocorrect turned off, and then the face goes in as plain text in a single step. It never passes through autocorrect or auto-capitalization, so `(¬‿¬)` reaches the field exactly as shown. If Emoji / Emoji panel / Return to the keyboard after you insert is on, tapping a face closes the panel and drops you back on the keys, exactly like a real emoji tap does. ## Details & edge cases - Faces stay out of emoji history. A face counts for the Kaomoji tab's Recent tag and for the suggestion order, and never for the emoji panel's Recent, Most used or the emoji row. - The emoji font setting does not apply here. Faces render as plain text with no font override. Emoji / Emoji style / Emoji font only touches genuine Unicode emoji glyphs. - The built-in faces stick to glyphs Android ships. They use characters covered by Android's bundled fonts (Noto Sans CJK and Noto Sans Symbols), so every one renders out of the box. A face you add can use anything, and may show as boxes on a phone without a font for it. - In the continuous list, with Emoji / Emoji panel / One long list on, the Kaomoji section starts with its tag row, and that row carries the search, add and tags buttons at its ends. Faces cannot be dragged in that list; use edit mode with the list off, or the Faces screen. - After a restart, until you unlock the phone once, the tab shows the built-in set, since your library file cannot be read yet. - The library, the import and the suggestions work the same in every build. For the rest of the emoji panel (search, skin tones, favourites, and recents for actual Unicode emoji), see [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/). For the emoji row and emoji fonts, see [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/). --- # Emoji mixes URL: https://wmkeyboard.pages.dev/emoji/mixes/ Section: Emoji & expression Summary: Put two emoji together as one sticker, from a source you add: over the emoji panel, on the suggestion strip, and in the Mixes panel. An emoji mix is two emoji drawn as one picture, sent as a sticker. WM Keyboard doesn't come with any mixes and doesn't fetch any on its own. It shows mixes from a source you add yourself, and until you add one, nothing about mixes appears on the keyboard. Emoji / Emoji mixes ## Adding a source Tap **Add a source** and paste an address, or use **Import a file** or **Import a folder**. The keyboard works out what kind of source it is from its shape: - **List of mixes.** A file that names each pair and the address of its picture. It can be JSON, where each entry has an image address and the two emoji, a plain list of image addresses, or plain text with two emoji and an address on each line. The keyboard downloads it once, packs it into an index of under a megabyte on the phone, and from then on knows every mix the list has. This is the kind that lets the keyboard list all the mixes of one emoji. - **Image address.** One working address of a mix picture. The keyboard reads the pattern that every other picture follows from the folders and names in it. It needs a list of mixes beside it to know which pairs exist, and it points that list's pictures at the host you named. - **Mixer.** An address that answers any two emoji with their mix. Paste one example, or write a pattern with `{left}` and `{right}` where the emoji go (`{left_hex}` and `{right_hex}` take code points such as `1f603`, and `{size}` takes a size in pixels). When you add it, the keyboard asks it once for a pair that has no mix, to learn the picture it sends when there is none. A mixer alone works in the Mixes panel. To get mixes on the suggestion strip, add a list of mixes too. - **Imported images.** A zip file or a folder of pictures named for their two emoji, such as `1f603_1f601.png`, `u1f603_u1f601.webp` or `😃_😁.png`, or kept in dated folders. They are copied onto the phone and work with no connection. Each source has a switch. Sources answer in list order: tap one to fetch its list again, move it up, or remove it along with its files. After a restart, mixes come back once you unlock the phone for the first time. Lists and imported images are stored behind the lock screen, so the keyboard can't read them before then. ## Where mixes show **Over the emoji panel.** Pick an emoji in the emoji panel and a row of its mixes rises over the grid, the mix of it and the emoji you picked just before it first. The button at the end of the row opens the Mixes panel on that emoji. **On the suggestion strip.** When the text before the cursor ends in one or two emoji written together, their mixes show on the strip, the mix of the two first. **How the strip shows them** picks thumbnails, one chip that opens a row, or the row on its own. With **Replace the emoji** on, sending a mix takes the emoji it was made from back out of the text. That happens only after the mix has arrived, so a mix that can't be fetched leaves the text alone. **The Mixes panel.** A tab beside emoji, GIFs and stickers on the [panel switch](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers), and a tool you can pin to the toolbar. Two slots sit at the top with their mix beside them. Pick an emoji from the bottom rows and it goes into the slot that is lit; the grid in the middle lists every mix of the first emoji. The emoji in the bottom rows come in the emoji panel's order, one category at a time: the category tabs sit in the panel's header, where the GIF and sticker panels have their search box, or above the bottom rows when the GIF and sticker tools' **Full-screen picker** is off. Your recent emoji get the first tab when any of them have mixes. The lit slot shows a small cross while it holds an emoji. Tap it, or long-press either slot, to take that emoji out; with only the second slot filled, the grid lists that emoji's mixes instead. The search button opens a box that finds emoji by name as you type: tap one, or press Enter for the first, and it goes into the lit slot. The swap button trades the two emoji, and the shuffle button picks a random emoji and one of its mixes. Long-press the shuffle button to choose which categories it picks from, with one column of ticks for the first emoji and one for the second, so a shuffle can always put an animal with a food, say. A column always keeps at least one tick, and **Every category** ticks them all again. Some sources include the magic wand 🪄, which mixes with every emoji: its mix draws the other emoji again in an older, rounder style. With motion on, the mix lands with a small burst and the grid of mixes rises in, one cell after the other. With motion off under [Appearance](https://wmkeyboard.pages.dev/reference/settings/appearance/), all of that is still. Your own [stickers](https://wmkeyboard.pages.dev/emoji/stickers/) come before mixes: if the text asks for one of your stickers, that offer shows instead. ## Saving a mix Long-press any mix for its menu: - The top of the menu names the two emoji the mix was made from. - **Star** puts it in a row at the top of the Mixes panel. - **Add to my stickers** copies it into a sticker pack named Emoji mixes, tagged with both emoji, so it is offered as you type with no connection. - **Save to photos** puts it in Pictures/WM Keyboard. ## Privacy and data use The keyboard only talks to the hosts of sources you added. A mix's address names its two emoji, which is all the request carries, and every request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as Emoji mixes. Pictures fetched to send are kept in the cache, up to 400 of them, and **Clear fetched mixes** empties it. Lists and imported images stay until you remove their source. While [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds media on the connection you're on, the strip and the panel show only mixes from imported images. **Fetch lists again each month** downloads each list again once it is a month old, only on a connection that is not metered. It is off by default. ## Settings Emoji mixes are part of the **Stickers, GIFs and emoji mixes** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). With the bundle off, this screen and the mixes panel are gone, and the sources you added wait for it to come back. | Setting | Default | What it does | |---|---|---| | Sources | none | The sources you added, in the order they answer. | | Over the emoji panel | On | A row of mixes when you pick an emoji in the panel. | | On the suggestion strip | On | Mixes when the text ends in one or two emoji. | | How the strip shows them | Thumbnails | Thumbnails, One chip, or A row above. | | Replace the emoji | On | Sending a mix takes its emoji back out of the text. | | Mixes panel | On after the first source | The tab on the panel switch and the toolbar tool. | | Order | Your emoji first | Your recent and most used emoji first, or the source's own order. | | Send as | Sticker | Sticker where the app takes one, otherwise a picture. | | Size in the Mixes panel | 72 dp | How big each mix is in the panel's grid. | | Fetch lists again each month | Off | The monthly download described above. | | Clear fetched mixes | n/a | Empties the cache of fetched pictures. | --- # Emoji picker & search URL: https://wmkeyboard.pages.dev/emoji/picker/ Section: Emoji & expression Summary: Semantic emoji search in your language, skin tones and dual-tone pairs, and favourites and recents in the emoji panel. The emoji panel does more than lay emoji out in a grid. Search understands synonyms and typos in English and Bangla, every toned emoji remembers the variant you last picked for it, and your favourites and recents ride at the front of their own tab. ## Opening the panel Emoji / Access / Emoji button in toolbar The most direct route is the emoji button in the toolbar (on by default). Tap it and the panel opens over the keys. If you would rather have another tool there, or several, **Buttons beside the suggestions** on the same screen swaps it for the clipboard or adds any other tools beside it, in the order you drag them into, with their own size if you want one. If you've turned on a [dedicated emoji row](https://wmkeyboard.pages.dev/emoji/customization/#the-dedicated-emoji-row), its own launcher icon opens the same full panel. Two more toggles replace a bottom-row key outright with an emoji key, so opening the panel takes one less tap: Layout & size / Bottom row keys / Emoji key instead of 🌐 **Emoji key instead of 🌐** is on by default, so the 🌐 key opens the emoji panel instead of switching language. Language switching doesn't disappear. It moves to a spacebar swipe, set to Language by default, so you get it back for free. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/). **Key in place of 🌐**, under the same switch, can put a tool there instead of the emoji panel, such as Stickers. **Comma key opens emoji**, under the same group, is off by default. Turn it on and the bottom-row comma key becomes an emoji key too, with comma itself demoted to a long-press alternate. Its own info text warns that turning this on also removes the emoji tool from the toolbar, since the key is meant to replace it. Drag the tool back into the toolbar if you want both. **Press and hold enter for emoji**, also in that group, is off by default and leaves every key where it is. Turn it on and holding Enter opens the emoji panel when you lift, for a bottom row that has no emoji key. A tap on Enter is unchanged. More on [Key press settings](https://wmkeyboard.pages.dev/reference/settings/keypress/#press-and-hold-shortcuts). ### Tabs, pages and the long list Each category has a tab in the strip across the top. With few enough tabs they share the strip's width. When there are too many to fit at a comfortable size (the history tab, the categories, and the Kaomoji tab if you turned it on), every tab keeps that size and the strip scrolls sideways under your finger instead of squeezing them. The tab you're in is kept in view as you move through the panel. By default each category is a page, and you swipe left or right to go to the next. Emoji / Emoji panel / One long list makes the panel one list instead, with each category's name as a heading, so one long swipe up runs through every emoji. The tabs jump to a heading and follow the list as you scroll. Emoji / Emoji panel / Panel height opens the emoji, GIF and sticker panels taller than the keyboard, so more fit on screen while the letters stay compact. The three share the height, so switching between them doesn't move the keyboard. A short bar across the top of the panel drags the same height taller or shorter, [as on the GIF panel](https://wmkeyboard.pages.dev/emoji/gifs/#making-the-panel-taller). Full screen panels have no bar. ### Switching to GIFs and stickers Emoji / Emoji panel / Switch to GIFs and stickers The bottom row of the panel carries a switch with three buttons: emoji, [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) and [stickers](https://wmkeyboard.pages.dev/emoji/stickers/). Tap GIF or sticker and that panel takes the emoji panel's place, with the same bottom row and the same switch under its results, so you can go back and forth without the toolbar. The setting above moves the switch to the top row, next to the category tabs (next to the search box on the other two), or turns it off. The GIF and sticker panels keep the emoji panel's ABC, space and backspace in every case. A button shows only while its tool is on. With both the GIF and the Sticker tool off there is nowhere to switch to, so the switch is not drawn at all; if your panel layout places it, the layout editor says so and links to the GIF tool, where you can turn it on. With Emoji / Emoji panel / Open the last used panel on, the emoji key reopens whichever of the three you left open, the way most keyboards treat emoji, GIFs and stickers as one panel. Typing `:cat:` is a different feature entirely: it searches or auto-commits an emoji from inside the suggestion strip while you type, without opening this panel at all. See [Inline emoji search and shortcodes](https://wmkeyboard.pages.dev/emoji/customization/#inline-emoji-search-and-shortcodes). ## Searching for an emoji Tap the magnifying-glass tab at the start of the tab strip. The panel switches into search mode and a search pill appears. With the full-screen panel on (the default) the pill takes the header row beside the back button, in place of the tab strip. With it off, the pill sits under the results instead, right above the keys that type into it, so the emoji stay at the top of the panel where your thumb left them. The pill is not a text field you type into directly. Tap it, and the letter keys underneath become a query editor: what you type appends to the pill instead of going to your document, and the keyboard's own backspace key deletes from the pill. A small backspace icon inside the pill deletes from your actual text field instead, without leaving search. Tapping the pill again unlatches typing, and the tab strip returns once the query is empty, so backspacing your query away is the way back to browsing. An ✕ at the end of the pill, while it holds a query, empties it in one tap: the full grid comes back, and the keys stay on the pill for a new query. Search understands more than an exact keyword match, layered in this order, where matches on more than one query word add up: 1. **An exact shortcode.** `tada` finds only 🎉, unambiguously, ahead of anything else. 2. **A shortcode prefix.** `thumb` starts narrowing toward `:thumbsup:` and its neighbors. 3. **An exact keyword.** `cat` matches every emoji tagged with the word "cat". 4. **A synonym.** A small hand-curated list expands common searches. `party` also surfaces 🍾 via "celebration", and `happy` also reaches emoji tagged "joy" or "cheerful". Bangla concepts expand the same way (খুশি → হাসি, আনন্দ). 5. **A keyword prefix.** `cele` starts matching "celebration" before you finish typing it. 6. **A one-typo fuzzy match.** `hapy` still finds 😀, for query words of four letters or more. Turn on **Find any character by its Unicode name** under Emoji / Emoji style (off by default) and the same search also reaches tens of thousands of other characters by the words of their official names: `em dash` finds —, `copyright` finds ©, `lambda` finds λ and Λ, `ogonek` finds ą, ę and the rest, and `g clef` finds 𝄞. Music and maths symbols and old scripts are in it. Chinese, Japanese and Korean ideographs are not, because Unicode names them by number only. Those come after the emoji results, and a character the catalogue already knows as an emoji is listed once. Tapping one inserts it like an emoji; a hold shows its name. ### Characters on the suggestion strip You don't have to open the panel for a character whose name you know. Turn on **Suggest characters by their Unicode name** in the same place (off by default) and the strip offers a character when the words you just typed are exactly its name. Type `snowman` and ☃ appears beside the emoji chips; type `em dash` and — does. Tapping the chip puts the character in place of its name, so `em dash` becomes — with no words left behind. A trailing "sign" or "symbol" can be left off, so `degree` offers °, `euro` offers € and `section` offers §. Greek letters go by their own names: `lambda` offers λ and `capital omega` offers Ω. A name can run to four words, the one you're typing and three before it. Only whole names count, which keeps the strip quiet while you write ordinary sentences. That's also why the setting is off by default: a few names, such as degree and section, are everyday words too. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the emoji whose names mean what you typed come after all of these, in any language: "gato", "猫" and "rocket launch" find 🐈 and 🚀 with no keyword pack for Spanish or Chinese and no keyword that says "launch". English and Bangla keywords are merged into the same index, so a query never needs to name a language. বিড়াল and "cat" surface the same set of cat emoji, and that holds for every cat-family emoji in the catalog, not only the smiley ones. Typing in a language you haven't installed a keyword pack for still works for anything carrying English or Bangla tags. To download a pack for another language, see [Emoji search in your language](https://wmkeyboard.pages.dev/emoji/customization/#emoji-search-in-your-language). ### The strip completes your query While the search box has the keys, the suggestion strip sits in the row directly above them, and what it offers comes from the emoji catalogue rather than from your language's word list. Type `ca` and it offers `car`, `cat`, `card`, `cash`, but not `cable` or `cathedral`, which are perfectly good words that no emoji is tagged with and that would leave you staring at an empty grid. Everything on the strip finds at least one emoji. It completes against everything the search itself understands: keywords in every language you've got a pack for, shortcode names like `tada`, and the curated synonyms above. A second word narrows to terms that share an emoji with the first, so `red hea` offers `heart` ahead of `headphone`. Tapping a term replaces the word at the caret and leaves a space, ready for the next one. ## Picking a skin tone Emoji / Skin tone / Default skin tone Long-press any emoji in the panel grid (search results included) to open its variant popup. If Emoji / Emoji panel / Emoji descriptions is on (the default), the popup opens with the emoji's name at the top, so near-identical faces are easy to tell apart. Most toned emoji offer a single row of six choices: the neutral base plus five Fitzpatrick skin tones. Tap any one to insert it. A small number of two-person emoji, like 🤝 and 👫, open a dual-tone picker instead. It has two independent swatch rows, one per person, so each side of a handshake or hug can carry its own tone. Whichever variant you tap becomes that emoji's new default. The picker remembers your last choice per emoji and shows it from then on, in the grid and in your recents. Two settings shape how tones work everywhere else: Emoji / Skin tone / Override with last used **Default skin tone** sets the tone every toned emoji gets when you haven't picked one for it yourself. That covers the panel grid, the suggestion strip, search results and the starter set in the emoji row alike. The [setup wizard](https://wmkeyboard.pages.dev/start/setup/#running-the-setup-wizard) asks for it on its Emoji page, so it's usually set before the first emoji you insert; this row is where you change it later. **Override with last used** (on by default) keeps your per-emoji memory ahead of that default, so an emoji you've already toned by hand stays the way you left it. Turn it off if you'd rather the default tone win everywhere and your per-emoji picks only apply to that one insertion. Emoji with gender or role variants (a runner shown as 🏃‍♀️ or 🏃‍♂️, a doctor as 👨‍⚕️ or 👩‍⚕️) collapse into the same long-press popup, so the grid isn't cluttered with every combination as its own cell. The popup then stacks one six-cell row per member, so a single tap picks the member and its tone together. The two-person emoji are the exception: their dual-tone picker puts the members in a row of their own at the top, above the two tone rows. ## Favourites, recents, and most used Emoji / Emoji panel / History tab The first tab in the panel shows your history, once you have at least one recent or favourited emoji. **History tab** chooses what fills it. **Recent** orders by when you last used an emoji, and **Most used** ranks by frequency. **Both** shows the two at once: your 16 most used emoji under a **Most used** heading, then the recents under **Recent**. Favourited emoji are pinned to the front either way. **Off** removes the tab, so the panel opens on the first emoji category and every category keeps a fixed place. The keyboard still remembers what you use, for the emoji row and emoji suggestions. The history tab doesn't reorder while you tap through it. What you pick shows up the next time the tab comes into view: when the panel opens, or when you close or clear a search, so an emoji you found by searching is in Recent as soon as the grid is back. Long-press any emoji to star it as a favourite from the same popup used for skin tones: the star toggle sits right above the variant grid. Once you have two or more favourites, a **Reorder favourites** row appears in that popup too, opening a drag-to-reorder list so you can put your most-used ones first. Long-pressing a cell on the history tab specifically also offers **Remove from recents**, for pulling a one-off emoji back out without waiting for it to age off the list. For clearing the whole list at once: Emoji / Emoji panel / Clear recents button Off by default. Turn it on to add a "Clear recents" button to the Recent tab. It only appears on the Recent view, not Most used, since a frequency ranking isn't something a single tap should wipe. ## Reordering the tabs and the emoji Emoji / Emoji panel / Emoji categories The panel ships its category tabs in Unicode's own order. **Emoji categories** is where you change it: drag a category to move its tab, press the eye to hide a tab you never open, and press a category to open its emoji and arrange those too: press and hold one, then drag it where you want it. Hiding a category hides only its tab. Those emoji are still found by search and still offered in the suggestion strip. The history tab is not in that list: it orders itself, with your favourites pinned first as above. The Kaomoji tab is not either. It is switched on and off by its own toggle, always sits at the end, and sorts its tags on its own page. Full reference: [Emoji categories](https://wmkeyboard.pages.dev/reference/settings/emoji/#emoji-categories). ## Animated emoji Emoji / Emoji panel / Animated emoji Around 880 emoji have an animated version published by Google as part of [Noto Animated Emoji](https://googlefonts.github.io/noto-emoji-animation/). Long-press any of them in the panel and the popup loops the animation, with a **Send animated emoji** button under it that sends the 512×512 GIF into the field you're typing in. The button normally appears once the animation has loaded, so it doesn't offer to send something you haven't seen. The one exception is when no preview is coming at all, held back by data saving's "ask each time" or lost to a failed fetch. The button still appears there, since pressing it is the only way to answer that. The popup stays open afterward, preview and all, so you can fire off two in a row or pick a skin tone next. Two files exist for every animation, and the popup uses them for different jobs. Opening it fetches the WebP (around 350 KB) to loop in the preview. Pressing the button fetches the GIF (around 800 KB) and sends that. Long-press is also how you reach skin tones and favourites, so most long-presses send nothing and shouldn't cost the larger file. The GIF is what goes out because an app that accepts `image/webp` may still draw only its first frame, which would make a "send animated emoji" button a liar. Both come from `fonts.gstatic.com`, and nothing is downloaded until you long-press. Nothing about you goes with either request: it's a plain fetch of a fixed URL, with no query, no key, and no identifier. The button is deliberately absent in three cases: - **The emoji has no animation.** Flags, most ZWJ sequences, and plenty of symbols aren't in the set. The app ships the list of which ones are (`assets/emoji/animated.txt`, about 8 KB) rather than asking the server, so a long-press never stalls on a request that was going to 404. - **The app can't take images.** A GIF goes through the same rich-content path as a sticker, so a field that accepts no images (a plain search box, most password fields) doesn't get the offer. See [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/#sending-a-gif) for what does and doesn't accept them. - **The setting is off.** Turn **Animated emoji** off and long-press goes back to skin tones and favourites only. Sending one counts as using that emoji, so it lands in your recents like an ordinary tap would. ## Sending an emoji as a picture Emoji / Emoji panel / Send emoji as image Under the animation (and on every emoji, animated or not) is **Send emoji as image**: the emoji itself, drawn at 512×512 in whichever [emoji font](https://wmkeyboard.pages.dev/emoji/customization/#choosing-an-emoji-font) you've picked, sent as a transparent WebP. It answers the oldest emoji problem there is: the other person's phone drawing your emoji as something else, or as an empty box. Send it as a picture and they see the one you saw. The picture is drawn in the font the keyboard itself draws with, whichever that is: an imported font, one installed from an addon repository, Google's Noto Color Emoji, or the phone's own set. Noto is fetched once from the same system font provider Compose draws it with, then kept. Everything else is already on the device, so this normally works offline and sends nothing anywhere. How it goes out follows the sticker tool's own setting, so one preference governs every sticker the keyboard sends: Tools / Stickers / Send stickers as Left at **Sticker** (the default) WhatsApp receives it as a real sticker. Set it to **Image** and it arrives as an ordinary picture in the message box instead. That's the thing to try if an app does something unhelpful with a sticker, like sending it instantly or closing the keyboard afterward. Like the animated button, this one only shows on a field that accepts images, and it can be switched off on its own. The animations are **Noto Animated Emoji** by Google, licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The credit is repeated under the send button in the app, and the full notice is in Settings → About → Licences, under the downloaded data packs. ## Details & edge cases - **Inserting doesn't close the panel by default.** Emoji / Emoji panel / Return to the keyboard after you insert is off, so you can tap several emoji in a row (including several results from the same search) without the panel folding away after each one. Turn it on to jump straight back to the keys after a single insert. The same setting also governs the clipboard panel. - **Full-screen panel is the default, not an opt-in.** Emoji / Access / Full-screen emoji panel is on out of the box: the toolbar, emoji row, and symbol row all step aside, a slim back header takes their row, and the panel's layout fills the rest. Turn it off to keep the toolbar within reach while picking emoji. - **Search and the panel grid both respect "Hide emoji that your device cannot show."** An emoji neither your chosen font nor the phone's system font can draw is filtered out everywhere: panel, search, and suggestions alike. See [Fixing emoji that won't display](https://wmkeyboard.pages.dev/emoji/customization/#fixing-emoji-that-wont-display). - **Kaomoji are a separate system.** With the Kaomoji tab on, the panel's search lists matching faces under a Kaomoji heading after the emoji, matched by tag or by the face's characters. They never join the emoji history. See [Kaomoji & text art](https://wmkeyboard.pages.dev/emoji/kaomoji/). - **The panel is a layout.** Its category tabs, search box and grid are cells of a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts), with an abc, [GIF and sticker switch](#switching-to-gifs-and-stickers), space and hold-to-repeat backspace row along the bottom so you can type a word, drop in an emoji, and keep typing without leaving the panel. Open Emoji / Access / Panel layout to move the tabs, drop the search box, give the grid the whole height, or add keys of your own. - **Every build.** Search, skin tones, and favourites aren't gated by build: the same emoji panel ships everywhere. ### Coverage The bundled catalog is Emoji 18.0 and covers 1,923 base emoji across ten categories (activities, animals, flags, food, nature, objects, people, smileys, symbols, travel). Of those, 332 carry Fitzpatrick skin-tone variants, five uniform tones each, and 19 of that 332 also support independently-toned pairs like 🤝. Together that's 2,040 additional toned sequences on top of the base count. An emoji newer than your phone's emoji font draws as a box until the font catches up; picking a newer font under **Emoji font** fixes that. For the settings that shape all of this (which font draws the emoji, keyword packs for other languages, word-typing predictions, and the emoji row), see [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/). --- # Sticker generator URL: https://wmkeyboard.pages.dev/emoji/sticker-generator/ Section: Emoji & expression Summary: Describe a sticker and the phone draws it offline, or an image service you pick does: the engines, the phone's models, prompts in any language, the safety filter, and importing your own model. The sticker generator draws stickers from a sentence you type. By default the model runs on the phone itself, so it works offline and the picture never goes anywhere until you send it. You can also have an image service draw them instead: two free ones that need no account, the big paid ones with your own key, or a Stable Diffusion server you run. You pick a result, the background comes off, the [sticker editor](https://wmkeyboard.pages.dev/emoji/stickers/#the-sticker-editor) opens on it, and from there it goes into one of your packs and out like any other sticker. Open it from Tools / Stickers / Your stickers / Sticker packs / Create stickers, from **Create from a prompt** on a pack's page, or from the keyboard: in the sticker panel's **My stickers**, the sparkle chip beside the + chip opens it, saving into the pack the panel is showing. ## Engines Who draws is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), **Engines**, at the bottom of the generator's screen. Each row is one engine you switch on, order, and set up with a key or an address; tap a row for its own screen, where **Test** checks the key with a request that draws nothing and costs nothing (fal.ai has no such request, so it has no Test). A new install lists **On this phone** switched on, and Pollinations and AI Horde switched off: those two send your prompt away, so they wait until you turn them on. With more than one engine on, chips above the prompt pick the one that draws, and the line under them says where the prompt goes. Set the list to **One at a time** under its **List settings** and a tap tries the engines in order instead, moving to the next when one fails or has no answer. | Engine | Key | What it is | |---|---|---| | **On this phone** | none | The models below, downloaded or imported. Nothing leaves the phone | | **Pollinations** | optional | Free and needs no account. Without a key it uses Pollinations' old host, which picks the model itself (Sana, today) and puts a small pollinations.ai mark in a corner. A key from [enter.pollinations.ai](https://enter.pollinations.ai) moves it to the newer API, where the model field takes any model it lists | | **AI Horde** | optional | Free Stable Diffusion on volunteers' graphics cards. Anonymous use waits in a queue, and the status line shows your place in it; a free key from [aihorde.net](https://aihorde.net/register) moves you up. Asked for 512 × 512 | | **OpenAI Images** | yours, or the AI list's | GPT Image (`gpt-image-1-mini` when the model field is blank). Draws on a transparent background, so no background removal is needed. Paid per picture | | **Gemini Images** | yours, or the AI list's | Google's Nano Banana models (`gemini-2.5-flash-image` when blank). One picture per request | | **Grok Imagine** | yours, or the AI list's | xAI's `grok-imagine-image-2.0` | | **Stability AI** | yours | Stable Image Core when blank; type `ultra`, or an SD 3.5 model such as `sd3.5-large`, to use those. A picture its filter blurs is held back | | **Black Forest Labs (FLUX)** | yours | FLUX from its makers, `flux-2-klein-4b` when blank; any of their endpoint names works, such as `flux-2-pro` | | **Recraft** | yours | Illustration, icon and vector-style models, the service's own default when blank | | **Replicate** | yours | Any text-to-image model there, as `owner/name` (or `owner/name:version`); FLUX Schnell when blank | | **fal.ai** | yours | Any image model's fal id, `fal-ai/flux/schnell` when blank | | **Together AI** | yours | FLUX and other open models, FLUX.1 Schnell when blank | | **Hugging Face** | yours | Models on Hugging Face's own inference servers, Stable Diffusion 3 Medium when blank. The token needs the Inference Providers permission | | **Cloudflare Workers AI** | yours, with the account id | FLUX.1 Schnell when blank, or another `@cf/` image model. The account id goes in its own field | | **Automatic1111, Forge or SD.Next** | optional | Your own web UI, started with `--api`. A key of `user:password` is its `--api-auth`. The model field switches the checkpoint for that one request | | **ComfyUI** | optional | Your own ComfyUI. Queues a plain text-to-image workflow with the checkpoint you name, or the first one the server lists | | **OpenAI-compatible images** | optional | Anything with OpenAI's image API, such as LocalAI, DeepInfra or Nebius: the address up to the version, and a model name | OpenAI Images, Gemini Images and Grok Imagine borrow the key you gave the same service on the [AI list](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) when their own key field is empty, so one account works for both. Every engine but the phone can be added twice, for a second key or a second server, and works on every build, F-Droid's included: each is a plain web request, with no company's code in the app. An online engine gets the prompt in English with the style's words, and the seed, the size and how many pictures you asked for; nothing else about you. Its pictures come back to the app, are shrunk to at most 1024 pixels a side and shown with the phone's, and are deleted when you leave the screen unless you save one. Each request appears under **Sticker generator** in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/). On a metered connection, [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)'s **Cloud AI** row decides: at **Ask each time** the screen says so and a second press of **Generate** is the yes; at **Turn off** only the phone's model draws. With an online engine, **Pictures per tap** on **Auto** draws one, since each may be paid for. The negative prompt goes to the engines that read one; GPT Image, Gemini and Grok Imagine leave it out. Size, steps and guidance are the phone's model's settings and disappear while an online engine is picked. ## Models on the phone, and what each phone gets Two models come with the app's catalog. Neither is in the APK: the phone downloads the one you pick, after you accept its licence. | Model | Download | How it draws | Runs on | |---|---|---|---| | **SDXS Dreamshaper** | about 880 MB, plus the 87 MB picture filter | one step, at 384 × 384 or 512 × 512 | every phone, on the processor | | **Stable Diffusion 1.5 (NPU)** | about 710 MB from Qualcomm, plus the picture filter | 20 steps with guidance, at 512 × 512 | Snapdragon 8 Gen 3, 8 Elite and 8 Elite Gen 5, on the neural processor | SDXS is the default everywhere. It was distilled to draw in a single step, and it draws clean, outlined cartoon subjects on a white background, which is what a sticker wants and what the background remover lifts cleanly. Measured on a computer and scaled to phones, one picture takes roughly: | Phone | 384 × 384 | 512 × 512 | |---|---|---| | Recent flagship (Snapdragon 8 Gen 3 or 8 Elite, Dimensity 9300, Tensor G4) | under 1.5 seconds | about 1 to 2 seconds | | Mid-range (Snapdragon 7 series, Dimensity 7000 series) | about 2 to 2.5 seconds | about 4 seconds | | Older or low-end | about 5 to 6 seconds | about 10 seconds | The first picture after the generator opens takes longer, because the model has to load. It then stays loaded for a few minutes, so the next prompts are fast. While loaded it takes one to two gigabytes of memory, in a process of its own: if Android needs that memory back it ends the generator, never the keyboard. **Stable Diffusion 1.5 (NPU)** is Qualcomm's own build of the original Stable Diffusion for each chip's neural processor, about 2.5 seconds a picture on an 8 Elite by Qualcomm's figures. It is offered only on the three chips above, and it uses the same Qualcomm runtime as [running AI models on the NPU](https://wmkeyboard.pages.dev/tools/ai/#local-models). It is not better at stickers than SDXS. Choose it for what SDXS can't do: a negative prompt, a guidance strength and a step count. Its first picture also waits for the NPU runtime to download, if nothing has fetched it yet. The model rows show what is downloaded. The bin deletes a model; About / Storage / Sticker generator lists them too. ## Writing a prompt Describe one subject: *a red panda drinking coffee*, *a happy avocado giving a thumbs up*. The style chips under the box add the look, so you don't have to type it. **Sticker** (the default) asks for a die-cut cartoon with bold outlines on white; the others are **Cartoon**, **Kawaii**, **Pixel art**, **3D**, **Line art**, **Emoji**, and **No style** for a prompt that already says how it should look. Under the box, a counter shows how many of the model's 75 tokens the prompt and its style take. The model reads no further than that. When a prompt runs past it, the words it won't read are named in red, so you know to shorten it, rather than finding out from a picture that ignored them. ### Prompts in other languages The models understand English. A prompt in another language is translated first, and the English is shown under the box, where you can correct it before the next tap. The [on-device translators](https://wmkeyboard.pages.dev/tools/translate/#on-device-translation) are asked first (Firefox Translations, NLLB-200, Google's), so with a model for your language downloaded nothing leaves the phone. Only if none of them can translate it does the prompt go to the online translators on your translate list. A prompt in Latin letters counts as English unless Google's language identifier, where the phone has it, is sure it isn't. ### Improve with AI With an [AI provider](https://wmkeyboard.pages.dev/tools/ai/) set up, **Improve with AI** asks it to rewrite your idea as a short, visual prompt: one subject, what it looks like and does, its colours. The rewrite replaces what you typed, so you can edit it or press Generate as is. This sends your prompt to that provider. The on-device AI model is not offered here, being too slow for a quick rewrite. ## Generating **Generate** draws several pictures at once, each with its own seed, and each appears as soon as it is done. Flagships draw four, mid-range phones two, small phones one, and the NPU two; **Pictures per tap** under **Advanced** changes that. **Cancel** stops within a fraction of a second. With the NPU model, the pictures fade in as rough previews while the steps run. Tap a finished picture, then **Cut out and edit**. The background remover lifts the subject off the white, and the editor opens with it, ready to crop, erase or add a border before you save it into a pack. The sticker goes into the pack you came from; opened from elsewhere, into the last pack you used, the first pack, or a new **Generated** pack when there is none. The generated pictures themselves are scratch and are deleted when you leave the screen; only the ones you save are kept. ### Advanced - **Size**: 384 × 384 is faster and plenty for a sticker; 512 × 512 is SDXS's own size and holds more detail. - **Seed**: the number every picture starts from. **Keep the seed** draws the same picture again for the same prompt, which helps when you are adjusting the wording; off, every tap tries new ones. - **Steps**, **Guidance** and the **Negative prompt** (what to leave out) appear only for a model that uses them: the NPU model, or a model you imported. SDXS draws in one step without guidance, so it has none of them. - **Remove the background**: on by default. Off, the editor opens on the picture as drawn. A picture that came back with a transparent background (GPT Image draws them that way) skips it. The F-Droid build has no background remover, so there the editor's eraser does the job. - **Safety filter**: see below. - **Draw on the GPU**: off by default, and listed only on a phone with a GPU the generator can use: an Adreno, or another GPU with Vulkan 1.2 (Mali, Xclipse, PowerVR). On, the on-device models (not the NPU model, which has its own) run on the graphics chip through OpenCL, or Vulkan where the phone has no OpenCL, at the same full precision as on the processor, so a seed draws nearly the same picture. The first picture after switching it on waits while the GPU compiles its code, which is kept for next time. Whether it is faster depends on the phone, so compare it with a generation on the processor. If the GPU cannot load a model, the processor draws it. ## The safety filter Every prompt is checked before anything is drawn, as typed and in its English translation, against a list of sexual terms, and a match is refused. With **Safety filter** on (the default), every picture then goes through an on-device classifier (Falconsai's NSFW detector, 87 MB, downloaded with the first model), and one it judges unsafe is never shown: its place says it was held back. Both checks run on the phone. A word list can refuse a harmless prompt that happens to contain one of its words; rephrase it. The prompt check applies to online engines too. Their pictures are not run through the phone's classifier; instead **Safety filter** turns on the service's own filter wherever it has a switch for one (Pollinations, AI Horde, fal.ai, Together AI, Replicate and FLUX's strictness), and a picture a service refuses or blurs is held back the same way. Every paid service also applies its own rules whatever the switch says. ## Licences The models' weights come with their own licences, which you accept before the download: SDXS is under the CreativeML Open RAIL++-M licence, Qualcomm's Stable Diffusion 1.5 under the CreativeML Open RAIL-M licence and its NPU runtime under Qualcomm's. Both RAIL licences forbid using the model for illegal, harmful or deceptive content, and those restrictions carry over to whatever you make with it. SDXS comes from the project's data repository as an MNN conversion of [IDKiro/sdxs-512-dreamshaper](https://huggingface.co/IDKiro/sdxs-512-dreamshaper); Qualcomm's build comes straight from Qualcomm and is never copied by this project. See About / Licenses. ## Importing a model Any Stable Diffusion 1.x or 2.x checkpoint can be imported once it has been converted on a computer; the phone never converts one itself. The converter is `tools/imagegen/convert_sd.py` in the [source repository](https://github.com/wasi-master/WMKeyboard). With Python and a few packages: ```sh pip install torch diffusers transformers safetensors onnx MNN python tools/imagegen/convert_sd.py my_model.safetensors my_model --name "My model" --steps 20 --guidance 7 --taesd ``` The source can be a `.safetensors` or `.ckpt` file, a diffusers folder, or a Hugging Face model id. Use `--steps 4 --scheduler lcm` for an LCM model, `--steps 1 --timesteps 999 --guidance 0` for a one-step model like SDXS or SD-Turbo, and `--taesd` to swap the model's image decoder for the much cheaper TAESD, which looks the same at sticker size. `--int8` halves the files at a small cost in faithfulness. The script calls MNN's converter library directly rather than its `mnnconvert` command, which reports every conversion to Alibaba Cloud. The output folder holds five files: `text_encoder.mnn`, `unet.mnn`, `vae_decoder.mnn`, `merges.txt` and `wmimage.json`. Copy them to the phone, zipped or not, then **Import a model** and pick the zip or all five files. The generator checks that each network loads and takes the inputs `wmimage.json` describes before it lists the model; a model that doesn't fit is turned away with the reason. Imported models run on the processor, like SDXS. A 4-step LCM model at 512 × 512 takes several seconds a picture on a flagship and about 20 on a mid-range phone; an ordinary 20-step model with guidance makes 40 full-size passes, which on a phone's processor means minutes a picture. **Stable Diffusion 1.5 (NPU)** can be imported the same way, from the build [Qualcomm AI Hub](https://aihub.qualcomm.com/) publishes for your chip: the zip it downloads, or its `text_encoder.bin`, `unet.bin`, `vae.bin` and `metadata.json` picked together, alongside `merges.txt` (the same CLIP tokenizer SDXS uses, from this project's [data repository](https://github.com/wasi-master/wmkeyboard-data/releases#release-models)). Qualcomm's zip doesn't carry it; when SDXS or another imported model is already on the phone, its copy is used and the zip imports on its own. It needs no `wmimage.json` — it runs on the schedule above — and it must be the build for your phone's chip, or the neural processor turns it away when it loads. This is the only route to the NPU model on the **offline build**, which has no network to download it with. Its runtime still has to be installed first, under Storage / Downloads. While the generator's engine library is missing, the **Engine** group says so and its **Get the files** lists the libraries to fetch and import, the NPU's too when that model is picked. A model on the phone whose files can't be read says so under **Generate**, instead of leaving the button off. The checkpoint's own licence still applies to an imported model. --- # Stickers URL: https://wmkeyboard.pages.dev/emoji/stickers/ Section: Emoji & expression Summary: Sticker search, custom packs, stickers offered as you type, and saving stickers straight from the keyboard. The sticker tool searches KLIPY and GIPHY. Where the app on the other end supports them, results go out as real stickers, and you can build your own packs from photos already on your phone. It sits right next to [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) in the toolbox and shares most of its settings. ## Searching and sending emoji/sticker-panel-unsupported-field — The standing notice a field that can't take images shows, with results dimmed underneath. Open the tool from the toolbox, or from the toolbar once you've pinned it there. Type to search KLIPY and GIPHY, or switch to **My stickers** for packs you've built yourself. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) set to **Text and pictures**, a search in **My stickers** also finds your stickers by what they show, after the ones whose names or keywords match. Tap a result to download and insert it. While it downloads, the tile dims and shows a spinner over the preview. Tap more while it does and they queue up behind it, numbered in the order they'll be sent, then go out one by one. Tapping a numbered sticker again takes it out of the queue. This works for your own stickers too, and the queue is dropped if you move to another field before it finishes. The emoji and GIF panels are one tap away. The bottom row under the results holds ABC, a switch to emoji, GIFs and stickers, space and backspace, the same row the [emoji panel](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers) ends in. A swipe to the right across the results goes to the GIF panel. That setting can put the switch next to the search box instead, or turn it off; the ABC, space and backspace stay. Like the GIF panel, the sticker panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) you can rearrange: its component is **Sticker search and results**. It opens as tall as the emoji panel's **Panel height** setting makes it, and like the GIF panel it has a [bar across its top](https://wmkeyboard.pages.dev/emoji/gifs/#making-the-panel-taller) that drags it taller or shorter. **Hide the search when you scroll** folds the search box and the chip rows away while you scroll down the results, [as on the GIF panel](https://wmkeyboard.pages.dev/emoji/gifs/#hiding-the-search-while-you-scroll). With the search box empty, a scrollable row of category chips (Trending, Love, Happy, Sad, Cute…) sits above the grid. Tap one to run it as a search, and tap the active chip to go back to trending. An ✕ at the end of the search box, while it holds a query, empties it and goes back to trending in one tap. It works the same as the [GIF panel's row](https://wmkeyboard.pages.dev/emoji/gifs/#searching-for-a-gif), and like that one it steps aside as soon as you type. **My stickers** doesn't get the row, because its pack chips already fill that job. Android has no dedicated "this is a sticker" flag. The only signal a receiving app gets is the file's MIME type, and WhatsApp in particular only recognizes its own private `image/webp.wasticker` type, not plain `image/webp`. So with **Send stickers as** set to Sticker, the keyboard offers that WhatsApp type first and falls back to a normal image if the field doesn't advertise it. A source that isn't WebP, an animated GIF result say, can never go out with the sticker MIME, because the keyboard sends a GIF as the GIF it is rather than re-encoding it. Those always send as a plain image whatever that setting says. Your own still stickers keep the shape you made them in, so most apps get them as they are. WhatsApp and Messenger are the exception: both want a sticker as a 512×512 square, so a sticker sent into either goes out as a copy letterboxed into that square, with transparent bars on the short sides. The copy is made once and kept in the cache, so sending the same sticker again doesn't redo it. A small sticker would have to be stretched to fill that square. Once the upscaler is on the phone (it downloads the first time you use **Upscale** in the [sticker editor](#the-sticker-editor) or the [photo editor](https://wmkeyboard.pages.dev/tools/gallery/#the-editor)), a still sticker under 384 pixels on its long side is redrawn two or four times larger first, and arrives sharp. That takes a moment on the first send and none after, since the square is kept in the cache like the plain one. If the upscaler takes longer than 20 seconds or fails, the plain square goes instead. Without the upscaler nothing is downloaded for this, and the sticker is stretched as before. If the field can't take images at all, the tool doesn't download first and fail after. It vibrates once and shows a standing notice. Results stay visible underneath at 45% opacity, so you can still long-press one to save or copy it (see [Long-pressing a sticker](#long-pressing-a-sticker) below). An animated sticker is its own case. Accepting `image/webp` and playing an animated one are two different things, and nearly every chat app does only the first: Messenger and Telegram both take the file and show one frame of it. A field can't be asked which kind it is, so the keyboard goes by the one type that is a promise. Where the field offers WhatsApp's sticker type, an animated sticker goes out as the animated WebP it is, and WhatsApp plays it. Anywhere else that takes GIFs, it goes out as a GIF, because a GIF moves in all of them. The keyboard draws the animation frame by frame and writes a 512×512 GIF with a transparent background. A toast says so, since the first send of a sticker takes a moment. The GIF is kept in the cache, so sending the same sticker again is immediate. A GIF is a rougher picture (256 colours a frame, and edges that are either there or not), and in Telegram it arrives as a GIF and not as a sticker. That is the price of it moving. If the GIF can't be made, the WebP is sent after all, still. If you've moved to another field by the time the conversion is done, the sticker is not dropped into a field you didn't send it to. If the field does take images but rejects a still sticker, the keyboard tries a format the field does take before it gives up: a still WebP goes as a PNG. Only when nothing gets through does the file land on the system clipboard with a toast telling you to paste it. That's the same thing the clipboard tool's own image and file commits do when a field refuses them. ## Long-pressing a sticker Long-pressing a KLIPY or GIPHY result opens a menu with three kinds of row. Above them, a GIPHY sticker whose uploader GIPHY names says who made it ("by @name"). - **Save to a pack**, shown as "Save to a new pack" if you have none yet, or "Save to ``" for each pack you've made. The keyboard can't pull a sticker back out of a chat someone sent you. To keep one of those, save it to your phone from the chat app, then add it with the **+** chip under [My stickers](#my-stickers-and-your-own-packs). - **Copy** puts the sticker on the system clipboard instead of into the field, to paste wherever you like. - **Report** opens a mail draft to the maintainer naming the provider, your search term, and the sticker's id and URL, with room at the top to say what's wrong with it. Nothing leaves the device on its own. The draft sits in your mail app for you to read, edit, or drop. KLIPY and GIPHY search their own catalogues, so results arrive unvetted, and this is where a bad one goes. [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) have the same menu, minus the save rows. Long-pressing a sticker from **My stickers** starts with what the sticker is and what finds it: its title (or "No title"), the pack it's in, its keywords, and its emoji. Below that it shows **Change title and keywords**, which opens the app on that sticker's pack with its title and keywords already up to edit, so a keyword you think of mid-chat is two taps away; save, and Back returns you to the chat. Then "Manage packs" (which jumps straight to the pack management screen covered below) and **Copy**. There's no save row, since you already have it, and no report row, since it's your own file and there's nobody to report it to. emoji/sticker-long-press-save — Long-press menu over a KLIPY result: save to a pack, copy, or report. ## My stickers and your own packs emoji/sticker-pack-manage — A sticker pack's management screen, with reorder and export controls. **My stickers** is always its own tab or chip. Your packs are never interleaved into the KLIPY or GIPHY grid, in either source layout mode. With more than one pack, a second row of filter chips (All, then one per pack name) appears above the grid, and search there matches a sticker's title, its keywords and its pack name together. The **+** chip at the end of the source chips' row, beside **My stickers**, is the quick way to add a sticker. It opens the app on the pack with the photo picker already up, so adding one sticker is a tap and a pick. The pack is the one you filtered the grid to, or your only pack if you have one. Under **All** with several packs, it asks which pack first. It sits on the source chips' row so that a single pack, with nothing to filter, costs no row of its own. When **My stickers** is your only source and there are no source chips, the **+** chip starts the pack filter row instead. With no packs yet, the empty grid's **Manage packs** button is the way in instead. Beside the **+** chip, the sparkle chip opens the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/): describe a sticker and the phone draws it offline, or an [image service you pick](https://wmkeyboard.pages.dev/emoji/sticker-generator/#engines) does, into the same pack. Building and managing packs happens in the app, not the keyboard, under Tools / Stickers / Your stickers / Sticker packs. - **New pack** starts one empty. **Import a pack** reads a `.wmstickers` file, or a `.wastickers` file made for WhatsApp. **Signal sticker packs** adds a pack made for Signal from its link, as described [below](#signal-sticker-packs). **Telegram sticker sets** does the same for a Telegram set, as described [below](#telegram-sticker-sets). **WhatsApp stickers** brings in a WhatsApp pack file or the stickers WhatsApp has saved on the phone, as described [below](#whatsapp-stickers). - **Create stickers** below the list of packs, and **Create from a prompt** inside a pack, open the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/). - Inside a pack, **Add stickers** opens the photo picker for up to 30 images in one trip. Each one is processed automatically. A still image keeps its own shape: anything over 1024 pixels on its long side is scaled down to that, a smaller one stays the size it is, and either way it's re-encoded as WebP, trying progressively lower quality (90 down to 50) until it lands under 400 KB. An animated source, GIF or animated WebP, is stored byte-for-byte instead, since re-compressing one would only cost quality. Those are capped at 2 MB rather than the 400 KB still budget. An animated PNG (APNG) is the one animation that is converted: nothing on Android plays an APNG and no app takes one as a sticker, so its frames are read out and rebuilt as a 512×512 animated WebP, aiming for WhatsApp's 500 KB animated budget. If the conversion fails, the first frame is kept as a still, and the result says how many stickers that happened to. Any single source file over 12 MB is refused before processing even starts. - Pick exactly one still image and it opens in the [sticker editor](#the-sticker-editor) first, instead of being added as-is. Pick several and they're all added the old way, because an editor thirty times over isn't a flow anyone wants. Either way the photo behind each sticker is kept for later editing, as described below. - Per sticker, you can set its **title** and its **keywords**, reorder it within the pack, move it to a different pack, or delete it. The title is the sticker's name, and it's what a screen reader reads out for it. Keywords are anything else you'd want to type to find it: words, short phrases or emoji. Put a comma between words and phrases ("cat, good night"). Emoji need nothing between them, so "😂🐱" is two keywords. Both fields feed the search in **My stickers**, and both can [offer the sticker while you type](#stickers-as-you-type). - To delete several stickers at once, press and hold one in the pack. That starts selecting: each tick shows in the sticker's corner, a tap adds or removes another sticker, and a bar along the bottom counts them. **Select all** ticks the whole pack, and **Delete** asks once, with the count, before removing them all. Back or the cross leaves selection without deleting anything. - Per pack, you can rename it, **export** it as a `.wmstickers` file to share or back up, or delete it. Deleting warns you to export first if you want to keep a copy. - To act on several packs, long-press one to start picking, then tap the others, or use **Select all** in the bar at the bottom. The upload icon saves the ticked packs in one `.zip` holding a `.wmstickers` file per pack; the trash icon deletes them after asking once. While picking, a tap ticks a pack instead of opening it, and the close button or Back stops picking. - A pack with stickers in it also has **Share with everyone** (the globe), which sends it to the [community repository](https://wmkeyboard.pages.dev/addons/community/) for anyone to install. Only share pictures you made or may pass on. - A pack tops out at 200 stickers. You can have up to 50 packs in total. ### Keywords from AI A pack you imported often comes with no keywords at all, which leaves its stickers out of [stickers as you type](#stickers-as-you-type). **Suggest keywords with AI** fills them in, using the provider picked in the [AI tool](https://wmkeyboard.pages.dev/tools/ai/). It's on a pack's page, for every sticker in it or, while you're selecting, for the ones you picked. On the **Sticker packs** list it covers every pack, or the packs you picked. - A sheet opens first and sends nothing until you press **Start**. It names the provider and how many requests the run takes: one per sticker. **Anything to add** is passed on to the model, for example "these are for my family chat" or "add Bengali slang". - The model is asked for up to ten keywords per sticker, in the [languages you type in](https://wmkeyboard.pages.dev/languages/overview/): the feeling or situation the sticker fits, what it shows, and a few emoji. It doesn't repeat the keywords the sticker already has. - Claude, OpenAI, Gemini, Grok, Ollama, LM Studio and OpenAI-compatible services see the sticker itself, sent as a still picture up to 512 pixels wide. An animated sticker sends its first frame. The model on your phone sees it too if it can read pictures, and otherwise works from the words. Brave Answers, DeepSeek and Privatemode only get the sticker's title and its pack's name, and each card says when a suggestion came from the words alone. - Each suggestion is a chip. Tap the ones you want, and type your own in **Your own keywords** under it. **Save** adds what you chose to each sticker's keywords and keeps the ones it had. Stickers you chose nothing for stay as they were. - **Stop** ends the run. The suggestions that already came back stay, and **Start** carries on with the rest. - With [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holding cloud AI back, the run stops at the first sticker and says why. ### The sticker editor The editor opens two ways: picking a single still image from **Add stickers**, or tapping a sticker you already have and choosing **Edit the image**. Animated stickers don't get it at all, since there's no still to edit and the editor works on one picture, not on a run of frames. The sticker keeps whatever shape the crop gives it, up to 1024 pixels on its long side, and there are four tabs: - **Crop**: the whole picture with a frame over it. Drag an edge or a corner to change the frame, or drag inside it to move it. The frame can be any shape, and what's inside it is what's kept, in its own proportions. Nothing is squared off or stretched. - **Erase** and **Restore**: a round brush with a size slider. Erase clears pixels to transparent, and Restore paints them back. A ring the size of the brush appears in the middle of the canvas when you switch to either tab or move the slider, then fades once you stop, so you can see what you're about to paint with before you paint with it. **Undo** goes back one stroke at a time, twelve deep. - **Border**: the white or black outline that hugs the cut-out shape, WhatsApp-style, with a width slider from 0 (off) up to 24. The brush and border sizes are measured as if the sticker were 512 pixels on its long side, so the same value looks the same on a small sticker and a large one. It traces whatever is left after erasing, so it's most useful once the background is gone. The border is a setting rather than a stroke. Sliding it back to 0 is its own undo, which is why the Undo button doesn't touch it. **Remove the background** does the cutout in one tap. It's in every build but F-Droid's, and even there the model isn't in the APK, so the first tap offers a download with a progress bar and then runs. On a phone with Google Play services the model is Google's and comes from Play services on demand. On a phone without them, or with a stand-in such as microG that doesn't carry that module, the app downloads its own instead: U²-Net-P, an open (Apache-2.0) 5 MB network, fetched from the project's data repository, checked against a fixed SHA-256 before it's used, and run entirely on the phone. On the Play Store build the interpreter that runs it is itself an extra part that Google Play delivers first (about 2 MB, shared with offline voice typing), and the one progress bar covers both. Google's is the better of the two, which is why it goes first where it's available. The downloaded model shows up as **Background remover** under Stickers on the [storage screen](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-app-is-using), and deleting it there only means the next cutout downloads it again. If no model can be fetched, the brushes still work and the screen says so. If it finds nothing recognizable as a subject, or would keep the entire image, it says that too and leaves your picture alone rather than erasing everything. In the lite build the button isn't there at all. **Upscale** redraws a small sticker larger and sharper than stretching it would. It appears below the crop step when what you cropped is 640 pixels or less on its long side: four times the size for 256 pixels or less, twice otherwise, and the row shows the size it will make. A sticker is still kept at up to 1024 pixels when you save. The work happens on the phone, with waifu2x's network for drawings, and transparent edges are redrawn too, so the outline stays crisp. A bar shows the progress and **Cancel** stops it. The first time, the upscaler downloads: a 2.2 MB network from the project's data repository, checked against a fixed SHA-256, and on the GitHub and Play builds the engine the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/) runs on, if that isn't on the phone yet. After an upscale the larger picture is the one the sticker comes from: anything you erased is scaled to match, Undo can't go back past it, and a later **Edit the image** starts from it. The network shows up as **Upscaler** under **Sticker generator** on the [storage screen](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-app-is-using). Within one editing session an edit is destructive. Cropping again after you've erased something warns you first, because a new crop throws the erasing away. Nothing is written until you press Save. Between sessions it isn't. When you add a sticker from a photo, the keyboard keeps that photo (resized to 1536px on its long edge, as WebP) alongside your packs, together with the edit you made: where you cropped, what you erased, the border, and even the brush size you were using. **Edit the image** reopens the photo with all of that still on it, so a second pass picks up where you left off instead of starting over. Change the border, widen the crop, erase a bit more, then save again. A resumed edit opens on the **Erase** tab rather than **Crop**, since the crop is already made and a stray drag there is the one gesture that would throw the erasing away. **Undo** starts empty each time you open the editor, because it only covers the strokes of this session. "Crop again" is what clears an old cut-out, and it asks first. Stickers that arrived some other way (saved from KLIPY or GIPHY, imported in a pack) have no separate photo behind them, so they reopen as themselves. The first time you edit one, the version you're replacing is kept as its original, and from then on it resumes like any other. None of this leaves the phone. Exporting a `.wmstickers` pack writes the manifest and the finished stickers only, the settings backup's sticker section walks the same list, and Android's own cloud backup and device transfer skip the folder entirely. It does take up space, so About / Storage / Sticker packs lists it as its own row ("Images you made stickers from"). Delete only that row to get the space back while keeping every sticker. Deleting it costs the resumable edits too, and those stickers go back to reopening as themselves. ## Stickers as you type You don't have to open the sticker tool to send one of your own stickers. Type its title in a chat, or one of its keywords, or one of its emoji, and the keyboard shows it. Tap it and it's sent. This is only for stickers in your own packs, and it all happens on the phone. Nothing is searched online. - **The title counts only when you've typed all of it.** A sticker called "grumpy cat" shows up after "grumpy cat", not after "grumpy". If you want a shorter way in, add "grumpy" or "grr" as a keyword. A keyword can be as short as you like. - **Words have to be whole words.** A sticker with the keyword "cat" shows up for "my cat" but not for "concat" or "cats". Capitals don't matter. - **Emoji work anywhere.** A 😂 keyword fires right after "lol😂", with no space needed. Skin tones don't matter either way, so a 👍 sticker shows up for 👍🏽 too. - **The longest match comes first.** With one sticker called "happy birthday" and another with the keyword "birthday", typing "happy birthday" shows both, the first one first. Within one match, stickers whose title it is come before stickers that only have it as a keyword. Up to 24 show at once. - **Only in fields that can take a picture.** If a chat app's message box doesn't accept images, nothing shows, because a sticker couldn't be sent there anyway. Password fields never get it. - **Untagged stickers can be tagged for you.** With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) set to **Text and pictures** and **Tag my stickers** on, a sticker with no title and no keywords gets a few from what it shows, such as 🐈 and "cat", and comes up for them. Your own keywords are never changed. - **It works from the emoji panel too.** Pick an emoji in the panel that one of your stickers has as a keyword or title, and that sticker shows in a row above the panel. In the panel only emoji count. Words you typed before you opened it don't. **Where they show** has three choices: - **Tray** (the default) is a row of large stickers at the top of the keyboard, above every other row. It slides in when something matches and slides away when nothing does, and the word suggestions stay where they are. The ✕ at the end hides it. To put it somewhere else, drag **Sticker suggestions** in Rows & bars / Row order. It can go anywhere, below the keys included. - **On the strip** puts three small stickers on the suggestion strip, next to the word suggestions. If there are more, a **+N** button opens the tray with all of them. - **One chip** is a small chip on the strip that says how many stickers match. Tap it to open the tray. In the two strip styles, a long press on the stickers or the chip hides them. **Hold a sticker in the tray to see it bigger.** It shows large above the keyboard, with its title under it. Keep your finger down and slide along the tray, and each sticker you pass shows large in turn. Lift your finger and it closes. Nothing is sent, so tap the sticker when you've found the one you want. A slide that starts before the hold scrolls the tray as usual. Turn this off with **Hold to see them bigger**, and a hold does nothing. **After you send one** decides what happens to the text that asked for the sticker. **Remove the typed text** (the default) takes the title, keyword or emoji back out of the message box, so "see you 👋" becomes "see you " once the 👋 sticker has gone out. **Keep the typed text** leaves it where it is. Either way, once you've sent a sticker or hidden the tray, the same text doesn't bring it back. Type something else and it's live again. The sticker goes out the same way it would from the sticker panel, with **Send stickers as** deciding between a sticker and a plain image. ### Importing a `.wmstickers` file A `.wmstickers` file is a ZIP with a `pack.json` manifest and one image per sticker. Opening one, from **Import a pack** or from a file manager, first shows every sticker in it on a page of its own, all ticked. Tap a sticker to leave it out, or use **Select none** and tick the few you want. Only the ticked stickers are converted and added when you press **Add**, which matters for a pack of animated stickers: each animation takes a while to convert, and the ones you leave out cost nothing. The same page opens for a `.wastickers` file. Pick several files at once, or a `.zip` of packs saved with the bar's upload icon, and the page is skipped: every sticker of every pack is added, and one message counts the packs and names any file it could not read. Import forgives hand-written or slightly malformed manifests, repairing what it can rather than rejecting the whole file, and it reports what happened: - Imported successfully, with a note of anything it had to fix along the way. - "The app cannot read any sticker in that pack." if the manifest named stickers but none of them survived. Import reports that rather than quietly creating an empty pack. - "That file is not a WM Keyboard sticker pack." if the format tag doesn't match. - "You already have 50 sticker packs. Delete one first." if you're at the pack limit. An import is also capped against oversized or booby-trapped files: at most 500 entries and 64 MB total per archive. ### Signal sticker packs Packs made for Signal (and for Molly, which is Signal under another name) can become packs of your own. Signal has no sticker shop. A pack is public to whoever holds its link, and the link is all there is: `https://signal.art/addstickers/#pack_id=...&pack_key=...`. The second half of that link is the key the stickers are encrypted with, so the link is both the address and the way in. Open Tools / Stickers / Your stickers / Sticker packs / Signal sticker packs. There are two ways in from there: - **Add a pack from a link** takes a pasted link. The `sgnl://addstickers?...` form that Signal itself opens works too. You can also share a pack link to WM Keyboard from a browser or a chat, or paste it into **Import from a link** on the Add-ons screen. All of them end up in the same place. The field never fills itself from the clipboard. - **Find packs on signalstickers.org** opens the community's gallery in your browser. That is where the thousands of packs people have made are listed, with its own search and its Latest, Trending and Most viewed orders. Press **Add to Signal** on a pack there and Android asks which app should open it. Pick **Add stickers to WM Keyboard**. If Signal and Molly aren't installed, it opens WM Keyboard straight away. The gallery is run by volunteers, not by Signal and not by this project, and the keyboard never reads it: you browse it like any other website. The keyboard offers no list of packs of its own, not even the ones Signal ships with. A sticker stays the work of whoever drew it, and the packs built into Signal are art Signal commissioned and keeps the rights to. So a pack only gets here because you brought its link, the same as any picture you add to a pack yourself. Add packs you may use. What opens next is a preview and not an import: the pack's name, who made it, how many stickers it holds, and the stickers themselves. Every sticker starts ticked, and a tap on one leaves it out; **Select all** and **Select none** sit above the grid. Only the ticked stickers are downloaded and converted, and with some left out the button reads "Add 12 stickers" instead. Nothing is kept until you press **Add this pack**. After that the pack sits in **Your packs** like any other. You can rename it, edit its stickers, export it as a `.wmstickers` file, and send from it in every app, not only in Signal. Each sticker keeps the emoji its maker gave it as a keyword, so typing 😂 in **My stickers** finds the ones tagged with it, and typing 😂 in a chat [offers them](#stickers-as-you-type). Open the same link again and the preview offers to open your copy instead of adding a second one. The stickers come from Signal's sticker storage, the same place a Signal client gets them from when you tap a pack link there. No Signal account is involved and nothing about you is sent, only the pack's id. Every file is checked against the key from the link before it is opened, so a file that was changed on the way is refused. A link whose key doesn't fit says so, and so does a pack that Signal no longer has. While [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is on, the preview asks before its first request. That one answer covers the pictures and the import. Stickers are made to fit the same way as any other you add: a still WebP no more than 1024 pixels on its long side and under 400 KB is kept as it is, and anything else is resized and re-encoded. Animated Signal stickers are APNG files, which Android can't play and which no other app accepts as a sticker. Each one is converted to an animated WebP as it is added, frame for frame and with the same timing, so it moves in **My stickers** and goes out to WhatsApp as a real animated sticker. Other apps get it as a GIF, as described under [Searching and sending](#searching-and-sending). In the preview, before you add the pack, animated stickers already play, so you can tell what kind of pack it is. Every frame is encoded again, so this is the slow half of adding an animated pack, and it is what the "Adding N of M" half of the progress row is counting. On Android 8.1 and older the grid shows an animated WebP as a still, but it is still sent animated. ### Telegram sticker sets Sticker sets made for Telegram, and its sets of custom emoji, can become packs of your own too. A set is shared as a link, `https://t.me/addstickers/` (or `https://t.me/addemoji/` for emoji), and the name at the end is all there is to it. Unlike Signal, Telegram has no open storage that a set can be read from. The one way in that needs no Telegram account is a bot: a free Telegram account meant for programs, which the Bot API lets ask for any public set. So you make a bot once, and the keyboard asks Telegram for sets through it. The keyboard never signs in to your own Telegram account and does nothing else with the bot. Open Tools / Stickers / Your stickers / Sticker packs / Telegram sticker sets. 1. Press **Make a bot with @BotFather**. It opens Telegram's bot that makes bots. Send it `/newbot`, then a display name, then a username that ends in `bot`. 2. @BotFather answers with a token, which looks like `123456789:AAE...`. Copy all of it into **Bot token**. The field says so if what you pasted can't be a token. 3. **Add a set from a link** now takes a pasted link, or only the set's name. In Telegram, open a set, press the menu, and press **Copy link** to get one. The `tg://addstickers?set=...` form Telegram opens works too, and Android offers WM Keyboard as **Add stickers to WM Keyboard** when you press **Open in Telegram** on a `t.me/addstickers` page in a browser. You can also share a set link to WM Keyboard, or paste it into **Import from a link** on the Add-ons screen. The field never fills itself from the clipboard. The token is yours and stays on this device. It is left out of backups unless you turn on **Include API keys**, the same as the other keys. There is no built-in token: one token shared by every copy of the app would be one that Telegram could block for everybody at once. If you revoke the token in @BotFather, the screen tells you that Telegram no longer takes it. As with Signal, what opens is a preview: the set's title, how many stickers it holds, and the stickers, with the animated ones already playing. Video stickers show the still Telegram made of them. As with Signal, every sticker starts ticked and a tap leaves one out. Leaving out the animated and video stickers you don't want is what makes a big set quick to add, since those are the ones converted to animated WebP on the way in. Nothing is kept until you press **Add this set**. Each sticker keeps its emoji as a keyword, so it is [offered while you type](#stickers-as-you-type) like any other. Opening the same set again offers your copy instead of a second one. The keyboard offers no list of sets of its own. Sets are other people's work, so add the ones you may use. Telegram has three kinds of sticker, and each becomes one you can send in every app: - **Still stickers** are WebP pictures, kept as they are when they are small enough, and resized like any other picture when they are not. - **Animated stickers** are `.tgs` files: animations drawn from shapes, made in After Effects and saved for the Lottie player. No other app accepts one. Each is drawn frame by frame, at up to 30 frames a second, and saved as an animated WebP. - **Video stickers** are short `.webm` videos with a see-through background. Android's video decoder throws the transparency away, which would put every one of them on a black square, so the keyboard reads the transparency out of the file on its own and puts it back on each frame. They become animated WebP as well. Turning animated and video stickers into WebP is the slow part of adding a set, and the "Adding N of M" half of the progress row counts it. A sticker that cannot be converted is kept as its first frame, and the result says how many that happened to. Like animated Signal stickers, the result goes to WhatsApp as a real animated sticker and to other apps as a GIF. The requests go to Telegram's Bot API, `api.telegram.org`, and carry your bot token and the name of the set. While [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is on, the preview asks before its first request. ### Emotic reaction images [Emotic](https://f-droid.org/packages/futuristicgoo.emotic/) keeps reaction images, which it calls emotipics, beside its emoticons. Importing an Emotic export from Emoji / Emoji panel / Kaomoji / Import puts its faces in your [kaomoji library](https://wmkeyboard.pages.dev/emoji/kaomoji/#importing-from-emotic-and-other-files) and its images in a new pack named Emotic. Each image keeps its Emotic tags as keywords, so it shows up in search and [as you type](#stickers-as-you-type). Images marked as excluded in Emotic are left out, and an image past 20 MB is refused. ### WhatsApp stickers WhatsApp doesn't share its sticker packs with other apps. A pack gets into WhatsApp through another app's content provider, and only WhatsApp is allowed to read that provider; the packs WhatsApp itself ships, and the ones you've marked as favourites, live in its private database. No app can read either without root, and this keyboard doesn't try. Two things on the phone *are* open, and Tools / Stickers / Your stickers / Sticker packs / WhatsApp stickers reads both. Nothing leaves the phone, and WhatsApp doesn't learn about any of it. - **Open a .wastickers file.** This is the pack file the sticker-maker apps export: Sticker Maker, Personal Stickers for WhatsApp, and the desktop tools that imitate them all write it. It's a plain ZIP with a `title.txt`, an `author.txt`, the tray icon as a small PNG, and one WebP (or PNG) per sticker. Export or share a pack from the app you made it in, then open the file here, tap it in a file manager, or share it to WM Keyboard from the share sheet. It opens on a page of every sticker in the pack, with its title and who made it at the top, all ticked. Untick the ones you don't want and only the rest are added. The title becomes the pack's name. The author is shown once and not kept, because a pack here has no author field. The tray icon is left out, found by its name (`tray.png`, `cover.png`, `icon.png`) or, when it's named something else, by being the one PNG among WebPs. A pack made entirely of PNGs keeps every picture, so a tray named by a timestamp in one of those arrives as a small extra sticker rather than a real sticker being dropped. **Import a pack** on the Sticker packs screen takes the same file, so you don't have to know which of the two formats you were handed. - **Add the stickers that WhatsApp saved.** WhatsApp keeps a copy of every sticker you've sent or received under `Android/media/com.whatsapp/WhatsApp/Media/WhatsApp Stickers` (WhatsApp Business uses `com.whatsapp.w4b` instead). That folder carries a `.nomedia` marker, so the photo picker that **Add stickers** uses never shows these files. The system's folder picker does, and this row opens it there. Confirm the folder and every WebP in it shows on a page, newest first, with the newest 200 ticked, since a pack holds 200. Untick what you don't want, or tick others instead, and the ticked ones become one pack called "WhatsApp stickers". Tick more than 200 and the pack keeps the first 200, and the result says how many were left out. Stickers that were made for WhatsApp are already 512×512 WebP files, so they're kept byte for byte, animated ones included. The keyboard reads the folder once and keeps no access to it afterwards. - **Pick stickers for one of your packs** opens the system's file picker in the same folder with multi-select on, and adds the ones you choose to a pack you already have, or to a new one it asks you to name. The precise route when the folder holds far more than you want. Neither the file nor the folder carries emoji or keywords, so stickers arrive with none. Give them some from the pack's own screen if you want them [offered while you type](#stickers-as-you-type). Since WhatsApp packs are other people's work as much as Signal packs are, add the ones you may use. Sticker packs are an optional section of the app's full settings backup. They're off by default, because images make the backup file large. Turn them on in the Backup screen if you want packs included, or export a pack individually from its own screen instead. ## Options Tools / Stickers Stickers, GIFs and emoji mixes are one bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the three panels, the pack screens and the generator leave the keyboard and the app together; your packs stay on the phone. Stickers isn't pinned to the toolbar by default. It lives in the toolbox alongside GIFs until you pin it. Reach the settings below from **Tools** in the app. - **Sticker packs** jumps to the pack management screen above. Only shown under Stickers, not under GIFs. - **Full-screen picker**, under Tools / Stickers / Layout, default **on**. The toolbar, emoji row, and symbol row step aside and the search box moves into the row they leave behind, so the grid gets the full keyboard height. Shared with the GIF tool. - **Stickers per row**, under Tools / Stickers / Layout, 1 to 6, default **3**. The most stickers side by side. Fewer makes each one bigger. The GIF tool has its own **GIFs per row**. - **Gap between cells**, under Tools / Stickers / Layout, 0 to 12 dp, default **4 dp**. The space around each sticker. Shared with the GIF tool. - **Panel height**, under Tools / Stickers / Layout, default **Same as keyboard**. The emoji panel's setting, here too because the bar on top of the panel drags it. - **Remember the last search**, under Tools / Stickers / Layout, default **on**. Reopening the panel lands on the search and category it was left on; off, it opens on trending. Shared with the GIF tool. - **Hide the search when you scroll**, under Tools / Stickers / Layout, default **off**. Scrolling down the results folds the search box and the chips away, and scrolling up brings them back. Shared with the GIF tool. - **Panel layout**, under Tools / Stickers / Layout. Opens the sticker panel's layout editor. **Standard** ends the way the emoji panel ends. - **Providers**, at the top of Tools / Stickers: KLIPY, GIPHY and **My stickers**, in the order the panel asks them. Add, reorder, switch off or set up each one, as in every [provider list](https://wmkeyboard.pages.dev/tools/providers/). KLIPY and GIPHY need an API key unless the build carries one, and their keys are shared with the GIF tool. KLIPY replaced Tenor after Tenor's API was retired. - **Offer your stickers as you type**, under Tools / Stickers / Suggest while typing, default **on**. Only shown under Stickers. See [Stickers as you type](#stickers-as-you-type). While it's on, two more rows sit under it: - **Where they show**: Tray, On the strip or One chip, default **Tray**. - **After you send one**: Remove the typed text or Keep the typed text, default **Remove the typed text**. - **Hold to see them bigger**, default **on**: hold a sticker in the tray to see it large, and slide to see the others. - **Send stickers as**, under Tools / Stickers / Sending, Sticker or Image, default **Sticker**. Only shown under Stickers, since it governs what the sticker panel commits. The GIF panel has its own **Send GIFs as** on its own page. - **How to use them**, in the provider list: Tabs, Mixed or One at a time, default **Tabs**. Tabs gives every provider its own chip, including My stickers. Mixed collapses the online providers into one "Online" chip and keeps My stickers as a chip of its own. GIPHY keeps its own chip in Mixed mode too, since its terms forbid mixing its results with another service's. One at a time shows the first online provider that answers and asks the next one only if it fails. - **Content filter**, Off/Low/Medium/High, default **High**. Maps to KLIPY's and GIPHY's own content rating. - **Results for each search**, under Tools / Stickers / Results: a slider from 6 to 48, default **24**. It sets how many stickers one search or the trending view loads. Your own packs ignore it, so **My stickers** always shows everything that matches, however long the list. Shared with the GIF tool. Both tools are among the ones the setup wizard suggests enabling, and neither needs a particular build. What an API key gates is the online half. The GIF panel has nothing to show without one, while **My stickers** needs no key at all, so the sticker tool never lands on a "needs an API key" notice the way the GIF tool can. ## Details and edge cases - **The WhatsApp sticker MIME is an unofficial, undocumented contract.** `image/webp.wasticker` appears in no public spec. It's a private convention both WhatsApp and Gboard happen to implement, and it can change without notice, which is why there's a fallback chain rather than a single hardcoded rule. - **A field that only takes images, not stickers, still gets a normal image.** The sticker-versus-image choice is best-effort on top of whatever the field actually advertises. Nothing is dropped over a MIME mismatch alone. The clipboard fallback only kicks in when every image MIME candidate is rejected. - **Save and report don't apply to your own stickers.** Long-pressing something from My stickers offers pack management and Copy only. There's nothing to save (you already have it) and nothing to report (nobody served it to you). - **A source already at spec is left alone.** If a picked image is already a still WebP of 1024 pixels or less on its long side and under 400 KB, it's stored as-is rather than re-encoded. - **Stickers made before this change stay square.** They were letterboxed into 512×512 when they were added. Open one in the editor and drag the crop frame to the shape you want, then save. Where the photo it was made from is still on file, the editor opens that, so nothing is lost to the old square. - **One process, one store, but two independent views of it.** The keyboard and the settings app run in the same app process and share the same on-disk pack store, so every mutator saves immediately rather than batching. Each side still caches its own snapshot of the packs. The keyboard checks a cheap size-and-timestamp signal against the manifest and only reloads when that changed, rather than re-parsing it on every keystroke. - **Content filter doesn't get a restore button.** Every other row on this screen grows [a small restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) once you move it off its shipped value. The filter is drawn as a bare segmented row under a heading rather than as a full setting row, so there's nowhere to hang one. Undo it by picking High back yourself. The provider list has **Reset to default** instead. - **Packs need the device unlocked.** Sticker packs live in app-private storage that isn't available before you unlock the device after a restart, so the sticker tool isn't usable from a locked-screen field the way some other tools are. See also [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) for the tool this one shares its settings and layout with, and [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/) for the other route into expressive content on this keyboard. --- # The toolbar URL: https://wmkeyboard.pages.dev/tools/overview/ Section: Tools Summary: One row, many tools: how the toolbar works and how to make it yours. WM Keyboard ships 90 tools (panels, sensors, generators, and one-tap actions), but only a handful sit on the toolbar at once. The rest wait one tap away in the toolbox, and you decide which is which by dragging tools between the two. tools/toolbar-and-toolbox — The toolbar's default row (Emoji, Clipboard, Settings) with the toolbox grid open below it. ## Pinning and reordering The toolbar is a strip of pinned tools. The toolbox, behind the grid button on the toolbar, holds every other enabled tool that isn't pinned, and a tool never sits in both places at once. Out of the box a phone pins three: **Emoji**, **Clipboard**, and **Settings**. A tablet starts wider, with five on a small tablet (GIF and Voice join the three) and seven on a large one (Translate and Text edit as well); see [Size & position](https://wmkeyboard.pages.dev/typing/size-position/#on-a-tablet). Everything else starts in the toolbox, ranked roughly by how often people reach for it: expressive media first, then everyday text helpers, then keyboard tweaks, with the specialty and sensor tools last. Open the toolbox and hold-and-drag a tool: - Drag a toolbox tool up onto the toolbar to pin it. - Drag a tool around inside the toolbox grid to reorder it there. - Drag a pinned toolbar tool back down into the toolbox to unpin it. - Drop a tool on the middle of another tool to put the two in a folder. See [Folders](#folders). The middle is small on purpose: anywhere else on it, the tool takes that place. With a folder open, the star makes its toolbar button run the first tool on a tap and open the folder on a hold. It's the same drag-to-pin model Gboard uses. The first time you open the toolbox, a hint explains the drag. Dismiss it and it only resurfaces occasionally as a reminder. Picking **Recommended** rather than **Everything** during onboarding only decides which tools are *enabled*, meaning visible anywhere at all. It leaves the pinned row alone. The row stays at whatever your device shipped with until you drag something onto it yourself. If a tool you expected isn't in the toolbox, open Tools and turn it back on. A keyboard [mode](https://wmkeyboard.pages.dev/typing/modes/) can carry its own pinned toolbar, separate from this global one. Switching into that mode swaps the whole row. The modes page covers how that works. ## Hold a tool for a second action Tools / (any tool) / Press and hold on the toolbar A pinned tool answers three gestures: a tap runs it, a hold that stays put does a second thing, and a hold that drags picks it up. The hold runs when you lift your finger, and only the hold: the tap's action does not run as well. Out of the box, the tools that come in pairs hold to their partner, the same pairs HeliBoard uses: | Tool | Hold runs | |---|---| | Clipboard | Paste | | Paste | Clipboard | | Copy | Cut | | Undo | Redo | | Redo | Undo | | Select word | Select all | | Select all | Select word | Every other tool holds to its own settings page. The cursor tools have pairs too (left and right to a word left and right, up and down to page up and down, word left and right to the start and end of the line), but their hold repeats the move, so the pairs only take over once **Hold to repeat on the toolbar** is off; see [Text editing](https://wmkeyboard.pages.dev/tools/text-editing/). All of it is yours to change. Open any tool's own page and the **Press and hold on the toolbar** row picks another tool for its hold to run: hold Text edit to open the clipboard, say. The row shows what a hold does right now. The picker's first row, **Default**, says what the tool does untouched. **Settings page** gets the settings page back on a tool that has a pair, and **Does nothing** is for a tool whose hold you would rather have do nothing at all: it still buzzes, and nothing opens. On a tool that can [float over other apps](#tools-in-a-floating-box), **In a floating box** opens the tool itself in a box over the app. The picker fills the screen. The tools sit in the same groups as on the Tools page, with a search field over them that matches a tool's name or its description. Tap a group's heading to fold it away; a folded heading still shows how many tools it holds. The key and swipe action picker in the layout editor works the same way. Each picker opens again with the groups folded and scrolled the way you left them, until the app is closed. The first time, it opens on the current choice. How long a press has to last before it counts as a hold is **Press and hold delay**, under Appearance / Toolbar. At **System** it follows the touch and hold delay in your phone's accessibility settings; slide it up to 1.5 seconds if holds keep firing when you meant to tap, or down to 100 ms for a quicker hold. It applies to every hold on a tool, in the toolbox as well as on the bar. The third gesture is optional too. **Rearrange by dragging**, under Appearance / Toolbar, is on by default; switched off, a hold that drifts stays a hold and ends as whatever a stationary one would, and the toolbox is where you rearrange the bar instead. - It changes the toolbar only. In the toolbox a hold always opens the tool's settings, and the Tools screen still lists every tool, so the settings page never goes out of reach. - The bound tool runs whether or not it is on the toolbar or even enabled. Binding it is reason enough. - The row is on every tool's page, pinned or not, so you can set a hold before you pin the tool. It does nothing until the tool is on the bar. - Cursor tools that repeat on hold, Selection mode while its hold arms the mode, Trackpad while its hold opens the pad, and Voice while its hold picks a [voice typing mode](https://wmkeyboard.pages.dev/tools/voice/#starting-dictation), say so instead of offering a second action. One gesture cannot do both. See **Repeat cursor tools on hold** under [Typing](https://wmkeyboard.pages.dev/reference/settings/typing/). - Binding a tool to itself is refused, since that is a slow tap. - Nothing is shown while you hold. The buzz says the hold registered; the tool washes out and the drag notice appears only once your finger moves. ## Tools in a floating box A few tools can open in a box of their own that floats over whatever app you're in and stays there when the keyboard closes: **Calculator**, **Unit converter**, **Currency converter**, **Clock**, **To-do list**, **Notes** and **Translate**. The box shows the same panel the tool has in the keyboard, with the same sums, timers, tasks and notes, since it's the same tool. There are two ways to open one: - Bind a tool's hold to it: on the tool's page, set **Press and hold on the toolbar** to **In a floating box**. Holding the tool on the toolbar then opens it in the box. - From the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures): its action list has a **Floating boxes** group, so a tap, a hold or a quick menu action can open, say, **Calculator in a box** straight over the app, without the keyboard coming up first. Drag the box by its title bar to move it, and drag the grip in its bottom right corner to resize it. It opens where you last left it, at the size you last gave it. If the open keyboard would cover it, the box moves up to sit above the keys, and goes back to its place when the keyboard closes. The cross in its title bar closes it. The arrows next to the cross move the tool into the keyboard as its panel, for when you want the keys under it. To get the box out of the way without closing it, tuck it into the side of the screen, the way the floating button tucks: push it past the left or right edge by its title bar, or tap the line in its title bar to send it to the nearer side. A small tab with the tool's icon stays on that edge. Drag the tab up or down to move it along the edge, and tap it to bring the box back where it was. The tool's button on the toolbar brings it back too. While the box is tucked, the keys type into the app's field. Only one box is open at a time; opening another tool in it swaps what it shows. While a tool is in the box, its button on the toolbar doesn't open a second copy in the keyboard: the box gives a small bounce to show where the tool already is. The box never takes the focus away from the app, so the keyboard keeps typing into the app's field. A tool's own text box is the exception: tap a note, a task's name or the text to translate, and the keys type there, as they do with the tool open in the keyboard. If the keyboard is closed, tapping the text box brings it up. **Insert** in the calculator and the converters types the result into the app's field while the keyboard is open, and copies it to the clipboard while it's closed. Translate's **Insert** and **Replace** do the same with the translation. Translate shares its text box with the keyboard's own search panels, such as GIFs and web search. If you open one of those while Translate is in the box, the box keeps showing its last translation and waits. Its text comes back, ready to type into, once the keyboard is down to its keys again. Text you selected and sent to Translate from the selection bar goes into the box, too. Tools / Close floating boxes on a tap outside For a box that comes up for one sum and goes, turn on **Close floating boxes on a tap outside**. A tap anywhere else on the screen then closes it, and the tap still reaches the app underneath. Taps on the keyboard don't count, so typing into the box's text box doesn't close it. The setting is off by default, and a tucked box ignores it. The box draws over other apps through the same **Display over other apps** permission the floating button uses. The first time you open a box without it, the keyboard explains why and opens Android's page for it. ## Tools on the home screen Tools / (any tool) / Add to home screen A tool with a panel can have an icon of its own on your home screen, so the level, the compass or the unit converter can stand in for an app you no longer need. **Add to home screen** on the tool's page asks your launcher to pin it. Tapping the icon opens a blank text box with the keyboard up and that tool already open on it. Whatever the tool types, such as a sum, a translation or a password, lands in the box, and **Copy** and **Share** under it take it from there. A second tool's icon opens in the same screen over a fresh box. The tool opens in WM Keyboard only, so if another keyboard is in use the screen says so and offers to switch. The Clock, Notes and To-do list pin their own screens in the app instead. ## What does this button do? Inside a tool's panel, most buttons are bare icons: the clipboard's filter and list switch, a refresh arrow, a swap, the speaker in the dictionary. Press and hold any of them and its name pops up above it for a moment. A hold that shows the name doesn't press the button, so it's safe to ask before you tap. Buttons that already have a hold of their own keep it, and a button with its name written on it doesn't need one. ## Two ways to draw the toolbox Appearance / Toolbar **Toolbox layout** picks between them: - **Icons** (the default) is the grid you've seen above: a round icon per tool with its name in small text underneath, 4 to a row. Compact, and the most tools on screen at once. - **Pills** gives each tool a wide rounded row instead, two to a row. The icon sits on the left in the tool's own color and the name reads across the middle at full size. Tools that *open* something get a small arrow on the right, and the arrow is the reason to use this layout. **Appearance** and **Clipboard** take you somewhere. **Flashlight** and **Undo** just do their thing and leave the keys where they are. The arrow tells you which is which before you tap. Two settings apply to pills only. **Pills per row** goes from 1 to 3: one gives every name the full width, and three really only fits in landscape. **Fill pills with the tool color** paints the whole pill in the tool's accent color and flips the icon and name to white, or to near-black where the accent is too pale for white to read on. It needs **Colorful tool icons** (under Tools) on, since that's where the colors come from. Either layout still drags the same way: hold a tool to pick it up, drop it on the toolbar to pin it or elsewhere in the grid to reorder. The tool that follows your finger is drawn in the shape it will land in, and changes between them as you move: a full-width pill with its name over a pill toolbox, narrowing into the toolbar's circle as you carry it up to the bar, and widening again on the way back down. The grid closes up behind it as you go, and the drop keeps whatever arrangement you were looking at. The tool stays where you let go of it rather than flicking home and back. ## Most used or recently used first The grid follows the order you drag it into until you change **Toolbox order** under Appearance / Toolbox. **Most used** puts the tools you open most often first; **Recently used** puts the one you opened last first; **A to Z** sorts them by name, in the keyboard's language. Every way of running a tool counts: a tap on the toolbar or in the toolbox, a search, a key bound to the tool, a hardware shortcut. Tools you have never opened come after the rest, in your own order, so a fresh install looks the same either way. The grid sorts itself each time the toolbox opens and holds still while it is open. Tap Flashlight in the toolbox and it stays where it was until the next time you open it. A folder sits where its first tool falls, and the tools inside it keep the folder's own order. Dragging still works, but while the grid is sorted by use a drop rearranges your own order rather than the grid you see; **Your order** brings that arrangement back. The search has its own **Search order before typing** on the same screen. It decides how the search lists every tool before you type anything, so set it to **Recently used** and the tool you want is often waiting at the top. Once you type, the names that start with what you typed come first, as before. ## Scrolling or pages By default the toolbox is one grid that scrolls up and down. Turn on **Swipe the toolbox in pages** and it splits into fixed pages you swipe sideways instead, with dots underneath showing where you are. **Tools per page** sets how many each one holds (12 by default, anywhere from 4 to 40). To move a tool onto another page, drag it to the left or right edge of the toolbox and hold it there for a moment: the page turns under your finger, and you can keep going or drop it there. The trade is muscle memory against reach. Paging keeps every tool at the same spot on the same page every time, so you learn where things are instead of hunting for them mid-scroll. Scrolling gets you to the far end faster. Paging works with both layouts. Pick a page size that's a multiple of your row size and every page comes out with full rows. Ask for more tools than fit the panel's height and the page will scroll as well as swipe, which rather defeats the point. ## Folders Launchers with their own shortcut list, a secondary launcher for one, offer **WM Keyboard tool** there and add any tool with its own icon. **Add to home screen** on a tool's page does the same from here. A long toolbox is easier to scan in groups. Hold a tool, drag it over the middle of another one, and let go when that tool lights up: the two become a folder in that tool's place. The edges of a tool still mean "put it beside this one", so reordering works as before; only the middle files it. Drop more tools on the folder to add them, and drop a toolbar tool there to unpin it straight into the folder. A new folder is called **Folder**. Tap it to open it in the toolbox, and the header shows what you can do with it: - Tap the name to rename it. The keys come back under a shorter toolbox, the way they do for the other search boxes on the keyboard, and Enter or the check button keeps the name. Dictionary and Grammar might become "Language", for example. - Drag the tools inside to change their order in the folder. - Drag a tool onto the header to take it out of the folder. The header turns into a drop target while you drag. - The pin button puts the folder on the toolbar, after your pinned tools. A tap on it there opens the toolbox at that folder. Tap the pin again to take it off. You can also drag the whole folder up onto the toolbar to pin it. - The ungroup button breaks the folder up, and its tools go back to cells of their own. The toolbox animates all of this. A folder opens by growing out of its cell and settles back into that spot when you leave it. A tool held over another makes that one swell to meet it, and the tool in your hand shrinks a little as if about to drop in. A new folder pops into place, and one that takes a tool gives a small bounce. **Reduce motion** under Accessibility turns every one of these off. Hold a folder and drag it to move the whole folder around the grid. Back, the arrow in the header, or the system back gesture leaves the folder before it closes the toolbox. A folder left with a single tool is no longer a folder, and that last tool goes back to a cell of its own. Folders are saved with your settings, so a backup and restore brings them back. One set of folders is shared by every [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/); a mode with its own tool order just moves each folder to where its first tool falls. ## Searching the toolbox While the toolbox is open, the toolbar shows a magnifier at its far end, after your pinned tools. Tap it and the search takes over the whole keyboard, the toolbar included, with a back button of its own at the left of the search box. The keys come back underneath, and you type the name with the keyboard itself. Every tool that matches is listed, including the ones inside folders, with names that start with what you typed first. Tap one to use it, or press Enter to open the first match. Results fade in one after another, and slide to their new places as you keep typing. Close the search with its back button, its X button or the system back gesture, and the toolbox is the way you left it. To take the magnifier off the toolbar, turn off **Toolbox search** under Appearance / Toolbox. To have it next to the back button instead, turn off **Search button at the far end** on the same screen. ## Browsing every tool Tools All 91 tools have a settings screen under `Settings → Tools`, each with its own **Enabled** switch plus whatever options it carries. **Colorful tool icons** sits at the top of that screen and is on by default. It tints each tool's icon with the tool's own accent color, in the toolbox grid and down this settings list. The pinned row on the keyboard itself is the exception: those icons take the theme's toolbar-icon color, so the bar stays one color whatever this is set to, unless you turn on **Color the pinned tools** under [Toolbar](https://wmkeyboard.pages.dev/reference/settings/appearance/). Turn the setting off and the toolbox falls back to that same plain foreground color. Below that, the list is grouped by what the tools do: **Panels**, **Scanners**, **Online tools**, **Create and convert**, **Keyboard modes**, **Cursor**, **Quick actions**, and **Utilities**. Any tool a future group forgets to claim lands in an **Other** catch-all, so nothing goes invisible. A row's switch enables or disables the tool everywhere at once: toolbar, toolbox, and hardware leader key. Tapping the row itself opens that tool's own settings. To keep a tool working but out of the toolbox grid, open its page and turn off **Show in toolbox**. It still runs from the toolbar if pinned, from its keyword, from [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) and from the hardware leader key; the grid just loses one cell. Each tool has its own switch. A small tune icon marks the tools with settings beyond the switch. Most have them. Four don't, because their real options live on other screens: Settings, Sound & haptics, One-handed, and [Resize](https://wmkeyboard.pages.dev/tools/resize/). Related pages cover most of these tools in depth: [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/), [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/), [Snippets & text expansion](https://wmkeyboard.pages.dev/tools/snippets/), [Notes](https://wmkeyboard.pages.dev/tools/notes/), [To-do list](https://wmkeyboard.pages.dev/tools/todo/), [Text editing & cursor tools](https://wmkeyboard.pages.dev/tools/text-editing/), [Trackpad](https://wmkeyboard.pages.dev/tools/trackpad/), [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/), [Camera tool](https://wmkeyboard.pages.dev/tools/camera/), [Gallery](https://wmkeyboard.pages.dev/tools/gallery/), [Send files](https://wmkeyboard.pages.dev/tools/send/), [Voice typing](https://wmkeyboard.pages.dev/tools/voice/), [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/), [Scanner (OCR, QR, documents)](https://wmkeyboard.pages.dev/tools/scanner/), [Translate](https://wmkeyboard.pages.dev/tools/translate/), [Search & Wikipedia](https://wmkeyboard.pages.dev/tools/search/), [AI tools](https://wmkeyboard.pages.dev/tools/ai/), [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/), [Utility tools](https://wmkeyboard.pages.dev/tools/utilities/), [Internet speed test](https://wmkeyboard.pages.dev/tools/speed-test/), [Media controls](https://wmkeyboard.pages.dev/tools/media/), [Music ID](https://wmkeyboard.pages.dev/tools/music-id/), [Calendar](https://wmkeyboard.pages.dev/tools/calendar/), [App launcher](https://wmkeyboard.pages.dev/tools/app-launcher/), [Resize](https://wmkeyboard.pages.dev/tools/resize/), and [Instruments](https://wmkeyboard.pages.dev/tools/instruments/) (flashlight, compass, level, moon phase, weather). The master tool list is being built out on [Reference → Tools](https://wmkeyboard.pages.dev/reference/settings/tools/). ## Panels and captured keys Most tools that need input open a panel that captures the keyboard: search boxes, the AI prompt, the typing-test scorer, a plugin's own text field. While that panel is focused, what you type goes into its box rather than into the app behind it. Backspace is where this matters most. Word-deleting the field behind an open panel would edit text you can't currently see, so the keyboard routes deletes into the panel for as long as it's open. Space and Enter stay in the box too: Enter runs the box's own action (search, next match, keep this spelling) rather than dropping a newline into the app behind the panel. ### A captured box is a real text field Those boxes type like the app's own field, not like a one-way buffer: - **A caret you can move.** Tap anywhere in the text to put the caret there, and the next letter, backspace or swipe lands at that spot rather than at the end. The spacebar scrub, a hardware keyboard's arrows, Home and End, and a layout's own arrow keys all move it, and Del deletes forward from it. A delete swipe on ⌫ works there as it does in an app's field: it highlights what letting go will delete, by letter or by word, and dragging back gives it back. Long text scrolls to follow the caret instead of trailing off behind an ellipsis. - **Selection.** Long-press a word to select it. A handle at each end drags the selection wider or narrower, and a bar over it offers **Cut**, **Copy**, **Paste** and **Select all**. Long-press where there is no word, or anywhere in an empty box, and the caret moves there with a **Paste** bar over it (plus **Select all** once there is text); a tap anywhere else puts it away. The toolbar's **Paste** tool and a layout's Paste key paste into the box too, at the caret, for as long as it has the keys. Typing, a swipe, a suggestion or backspace replaces the selected text, as in any field, and a tap puts the caret back. Shift with a hardware keyboard's arrows selects too. The handles and the bar are drawn inside the keyboard, never as a window of their own over the app. - **Glide typing.** Swipe a word into a search box the same way you swipe one into a message. Each swipe adds a word, spaced off the word or the mark before it (`hello,` then a swipe gives `hello, world`, while an opening bracket keeps the word against it); the last word of a query just waits for its space until the next swipe, so the results are never held back a word. A chained swipe through the spacebar lands one word per leg, exactly as it does in a field. The two numeric boxes, the calculator's expression and the converters' amount, are the exception: there is no word list to decode a stroke against on a keypad. - **Punctuation spacing.** With **Automatic space after punctuation** on, a mark typed into a box you write sentences in (the AI prompt, chat and reply box, a note's body, the clipboard's edit box, a caption, the calendar's notes, Home Assistant's Assist) is followed by a space, as in a message. A space pressed straight afterwards doesn't double it, and a second mark takes it back, so `?!` and `.)` stay together. Search boxes get no space after a mark: it would hold the results back a word. - **Voice.** A mic at the right end of the box's suggestion strip dictates into the box, at the caret. See [Dictating into the keyboard's own boxes](https://wmkeyboard.pages.dev/tools/voice/#dictating-into-the-keyboards-own-boxes). - **Suggestions.** The box gets the suggestion strip in the row directly above the keys, with the same slots, the same size and the same words the field's own strip would offer. Tapping one replaces the word at the caret. The panel gives up that row rather than the keyboard growing one, so opening a search box doesn't move the keys under your thumbs. The emoji panel's search box is the one that reads differently: it's searching the emoji catalogue rather than writing prose, so its strip offers terms that catalogue can answer. See [Searching for an emoji](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji). - **Undo.** Once you've changed the text in one of these boxes, an undo arrow appears at the right end of its suggestion strip, before the mic. Each press takes back one step: a burst of typing counts as one step, and a paste, a picked suggestion or a cut is a step of its own. The strip and its arrow show while suggestions are on. It covers the note editor, the clipboard's edit box, the AI prompt and chat, find and replace, and every search box. The arrow goes away once there's nothing left to undo, and a box that opens on new text starts with no history. After an undo, a redo arrow appears beside it and puts the step back; typing anything new drops what was undone. A swipe or key bound to **Undo** or **Redo** acts on the box the same way. ### Typing in the app with a tool open The box that has the keys carries a small switch at its right end, the same round button on every tool with a box of its own: the clipboard's search and edit box, Combine, find and replace, notes, the dictionary, the calculator and converters, translate, the AI prompt and chat, a plugin's text field, and the rest. Tap it and the keys go to the app's field behind the keyboard instead, with the tool still open. The button fills in, and the box fades and loses its caret, so you can see at a glance which field is taking the keys. Search the clipboard, then fix the address bar under it without closing the search. Everything a key can do follows the switch together: letters, swipes, Backspace and forward delete, Space, Enter, the arrow keys and cursor tools, **Undo** and **Redo**, **Paste**, **Copy**, **Cut** and **Select all**, and a hardware keyboard. No action splits off to the other field. The calculator and the converters fold down over the key rows while the switch is on, since their own keypads only type into them. Tap the switch again, or tap the box itself, and the box takes the keys back with its caret where you left it. Moving to another box in the same tool, or opening another tool, starts that box with the keys. The typing test and the toolbox search have no switch: their boxes are the whole point of the screen. ### Putting the keys down The keys that come up under a tool's box cover most of the tool: the translation under Translate's box, the clips a search found, the rest of a note. To see it all again, put the keys down. The box keeps its text and its caret, and the tool takes back the room the keys had, so the keyboard stays the same height. Any of these does it: - The keyboard button at the right end of the box's suggestion strip, after the undo arrow and the mic. - The system Back button or gesture, with **Back puts a tool's keys down first** on (Appearance / Toolbox, off by default). The first Back then puts the keys down and the next one closes the tool. Off, Back closes the tool at once. The AI chat's message box and a plugin's text field always put the keys away on Back. - A swipe down on the spacebar, with [**Swipe down to hide the keyboard**](https://wmkeyboard.pages.dev/typing/gestures/) on, and the **Hide keyboard** tool or key. With a tool's keys up, these put down only the keys and leave the tool open. Tap the box and the keys come back, with the caret where you tapped. Moving to another box, or opening another tool, brings the keys up for that box. A hardware keyboard still types into the box while the keys are down. The typing test and the toolbox's own boxes have no way to put the keys down: their keys are the point of the screen, and Back closes them. On a short screen, a phone turned sideways most of all, the panel is what gives way. A captured box with the keys back underneath, or a tool that grows above the keyboard for more room (the AI reply panel, an AI chat, or any tool you have dragged taller), keeps the keyboard to at most 80% of the screen's height, and the panel shrinks to fit rather than squashing the keys. If the screen still can't hold both, the panel is measured against what's left once the keys are paid for, so the keys under a captured box always keep their full height. It never shrinks below its own header, so the box and the back arrow always stay in reach. ### Making a tool taller A tool that takes the keyboard's place (AI, AI reply, translate, the converters, the dictionary and the rest) has the same short bar across its top edge. The calculator, both converters and the calendar all open at the keyboard's own height, so switching between them doesn't move the app above; their keypads and the month grid shrink to fit instead. Drag it up for a taller tool, down to go back to the height it opens at. Where you let go is kept, and it's one height for every such tool. The clipboard and the GIF and sticker panels keep their own bars and heights, described on their pages. With no room to grow, the bar isn't drawn. ### Making a panel over the keys taller A panel that shrinks so the keys fit under it has a short bar across its top edge: the clipboard while it's [stacked over the keys](https://wmkeyboard.pages.dev/tools/clipboard/#clipboard-and-keys-together) or searched, a [snippet search](https://wmkeyboard.pages.dev/tools/snippets/#opening-the-panel), the dictionary's, the GIF and sticker searches, the web search, a note being written and the rest. Drag the bar up and the panel grows over the app, so more results fit above the keys; the keys stay where they are. Drag it down to go back to the panel's own compact height. Where you let go is kept, and it's one height for every such panel, so the keys sit at the same place whichever one is up. The same 80% ceiling applies, and with no room to grow the bar isn't drawn. The height is also a slider, **Height over the keys**, under Clipboard. With TalkBack on, the bar offers **Make the panel taller** and **Make the panel shorter** as actions. Nothing typed into a captured box is learned. A search query isn't writing, so it never reaches your personal dictionary, your word pairs or your typing stats. Sensor and generator tools work differently. Compass, Level, Moon phase, Weather, Calendar, Password generator, Media controls, Camera, and the scanners all replace the key rows outright instead of intercepting keystrokes. There's no letter row to type on while they're open, so there's nothing to intercept. Calculator, Unit converter and Currency converter fall between the two. Each draws its own keypad where the key rows were, and each also takes a physical keyboard's keystrokes into its own display: digits and operators build the expression or the amount, backspace trims it, and Enter runs `=` in the calculator instead of reaching the field behind. Handwriting is the other in-between case. While ink is waiting to be recognized, backspace throws that ink away rather than deleting text you already committed. To back out of any open panel: - Tap the back arrow that replaces the toolbox button while a panel is open. - Press the system **Back** button or gesture, which closes the panel instead of dismissing the keyboard. - On a hardware keyboard, press Esc, if [Esc closes the tool](#hardware-shortcuts-and-the-leader-key) is on (it is by default). ## Which tools work before you unlock your phone **Direct boot** is the window right after a restart, before you've entered your PIN or pattern for the first time. The keyboard can't reach its stored data yet. Some tools work anyway. WM Keyboard exposes 50 of its 90 tools during that window: everything that's pure arithmetic, a sensor reading, or a control over the keyboard's own on-screen state. That's Emoji, Text editing, Trackpad, Numpad, Symbols, One-handed, Split, Floating, Keep on screen, Resize, Hide keyboard, Appearance, Autocorrect, Sound & haptics, Incognito, Selection actions, English words, Find & replace, Text repeat, Modes, Fancy text, Secondary layout, Language, Undo, Redo, Power saving, Calculator, Unit converter, Password generator, QR generator, Flashlight, Compass, Level, Moon phase, Copy, Cut, and all fourteen cursor tools. Paste is the one clipboard button left out: the clipboard itself is credential-encrypted, so before the first unlock the button would be there and do nothing. The other 40 sit it out until you unlock. That covers anything backed by a file on disk (Clipboard, Snippets, Notes, To-do list, Camera, Read back, downloaded speech or AI models), anything that needs a credential or the network (Translate, the online searches, AI, the paired computers of KDE Connect, the signed-in homes of the Smart home tool, Send files), anything that reads a content provider behind the lock screen (Calendar), and anything that has to launch a separate activity (Settings, Statistics, the document scanner). None of it is a toggle. The split is automatic, and it only applies before that first unlock. **Hide toolbar & clipboard on lock screen** under Appearance / Toolbar is a different thing, and it's off by default. It hides the *entire* toolbar rather than filtering it, any time the screen is locked, including long after you've unlocked the phone once since the last restart. Turn it on if you'd rather not have a pinned tool or the suggestion strip visible while you reply to a notification from the lock screen. Everything comes back the moment you unlock, even in the text box the keyboard was open in when the screen turned off, and even when a fingerprint unlocks the phone straight from a dark screen. ## Hardware shortcuts and the leader key Typing / Physical keyboard With a physical keyboard attached, double-tap Ctrl, the default leader. Then press a letter to jump straight to a tool without touching the screen. About two dozen tools have a default letter: Emoji is E, Clipboard is C, Voice is V. Press T after the leader to open the full toolbox instead, or ? to expand the on-screen hint into a two-column legend of every binding. [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) has the full mechanics, plus how to rebind the leader and every letter. The leader is one switch in a broader hardware-keyboard group. Arrow-key navigation inside an open panel and whether Esc closes a panel are separate switches in the same place, each on by default. ## Options Appearance / Toolbar - **Show the toolbar**: on by default. Turning it off reclaims its height for the keys, but also hides suggestions and every pinned tool, so Settings warns you before you do it. - **Swipe down to hide**: off by default. A downward flick anywhere on the toolbar dismisses the keyboard. - **Only toolbar with hardware keyboard**: off by default. With a physical keyboard attached, drop the on-screen keys and keep just the toolbar strip. - **Reverse order for RTL languages**: off by default. Mirrors the pinned tool order when the active layout's script runs right-to-left. - **Tool layout**: Spread out by default. Fixed size packs the pinned tools to the left at one size, Spread out splits the width evenly, and Scroll keeps each tool at a comfortable size and lets the bar scroll sideways. - **Hide toolbar & clipboard on lock screen**: off by default. See [above](#which-tools-work-before-you-unlock-your-phone). - **Toolbar top padding** and **Toolbar bottom padding**: 4dp and 0dp by default, each adjustable from 0–24dp. They add space above and below the tools, and the keyboard grows by the same amount. - **Tool labels**: off by default. Shows each tool's name under its icon. The label is 10sp by default. - **Reset pinned tools**: restores the toolbar to Emoji, Clipboard, and Settings, after a confirmation. This always resets the *global* row, even while a mode with its own pinned toolbar is active. To change what a mode itself pins, edit that mode instead. On a tablet, reset writes the shipped three and the tablet's own default immediately re-expands it, so you get five or seven back rather than three. It touches the pinned row alone; the grid has its own row below. - **Reset toolbox order**: puts the toolbox grid back in the order it shipped in, after a confirmation. Only shown once you've dragged something. See [below](#details--edge-cases). - **Tool circle radius**: 20dp by default (a full circle behind each icon, Gboard-style). Lower values give a rounded square, and 0 removes the background entirely. - **Tool background shape**: Rounded by default. The outline of that background, from the same set of shapes the keys and popups use. Only shown while the radius above is over 0, since at 0 there's no background to shape. - **Tool background width**: 38dp by default, adjustable from 38–64dp. 38 is the classic circle. Wider stretches it into a pill, which pairs well with a low corner radius and a scrolling bar. - **Toolbox layout**: Icons by default. Pills is the wide-row alternative, see [above](#two-ways-to-draw-the-toolbox). - **Toolbox grid size**: 4 tools per row by default, adjustable from 3–6. Icons layout only. - **Pills per row**: 2 by default, adjustable from 1–3. Pills layout only. - **Fill pills with the tool color**: off by default. Pills layout only, and needs Colorful tool icons on. - **Toolbox order**: Your order by default. A to Z sorts by name. Most used and Recently used rank the grid by use; see [above](#most-used-or-recently-used-first). - **Search order before typing**: Your order by default. The same three choices, for the search's list before you type. - **Swipe the toolbox in pages**: off by default. See [above](#scrolling-or-pages). - **Tools per page**: 12 by default, adjustable from 4–40. Only shown once paging is on. ## Details & edge cases - A tool that's disabled under Tools disappears from the toolbar, the toolbox, *and* its hardware leader-key letter all at once, since there's a single switch, not three. - Every tool is in the F-Droid build, though three read with open-source stand-ins for the Google libraries it leaves out. QR scan reads with zxing-cpp. Text scan reads with PaddleOCR on MNN, which covers Chinese, Japanese and Latin script. Handwriting reads Chinese and Japanese with zinnia and nine other scripts with PaddleOCR's text recognisers on MNN; languages in other scripts, Bangla among them, have none. Grammar needs Harper's, which F-Droid compiles from source, so it stays. A build-gated tool gets no settings row at all, so you never meet a switch that won't turn on. - **Document scanner** is in every build and on every phone. It opens Google's scanner, an activity inside Google Play services, where that can run, and the keyboard's own [built-in scanner](https://wmkeyboard.pages.dev/tools/scanner/#scanning-a-document) everywhere else, F-Droid's build included. - **Reset toolbox order** puts the grid back in the order it shipped in, after a confirmation. It sits under Appearance / Toolbar right below Reset pinned tools, and only appears once you have actually dragged something. It leaves the pinned row and which tools are enabled alone. **Reset pinned tools** is the mirror of it. - Toolbar reordering is per-installation, not per-language. Pinning a tool applies everywhere, not only while a particular language is active. A keyboard mode's own toolbar override is the one exception. See [above](#pinning-and-reordering). - The toolbox shows only tools that are both enabled *and* not already pinned, so enabling a tool that's already pinned won't create a second entry in the toolbox. - A tool pinned to the toolbar stays a member of its folder but isn't shown inside it. Unpin it and it is back in the folder. A folder whose tools are all pinned or hidden disappears from the grid until one of them comes back. - The search lists results only. While it is open you can tap a result or hold it for its settings, but nothing can be dragged in a filtered grid. - With paging on, a drag reorders within the page you're holding it over. The pages don't flip under your finger, so moving a tool to another page means dragging it onto the toolbar first, then off again on the page you want. Dragging up onto the toolbar to pin works from any page. - The pill layout's arrow follows what the tool actually does, not what kind of tool it is. **Document scanner** gets one because it opens a full-screen scanner, and **Settings** gets one because it opens the settings app, even though neither is a keyboard panel. - Tool icon colors apply in the toolbox and the settings list, and on the toolbar too with **Color the pinned tools** on, and only when **Colorful tool icons** is on. With it off, those icons use the theme's plain foreground color, whatever per-tool override you've set. --- # AI tools URL: https://wmkeyboard.pages.dev/tools/ai/ Section: Tools Summary: A local LLM running on your device, or bring your own API key: your choice. The AI tool runs a fixed set of one-tap writing actions against whatever text is in the field you're typing in: rewrite, summarize, translate, and more. Where they run is up to you. Pick a cloud provider with your own API key, a self-hosted Ollama or LM Studio server on your network, or a model that downloads once and answers entirely on the device. tools/ai-panel-idle — The AI panel's action chips, ready to run against the text in the field. ## Using it Open **AI** from the [toolbar or the toolbox](https://wmkeyboard.pages.dev/tools/overview/). It isn't pinned by default, so the first time you'll probably reach it through the toolbox. Typing its `ai` keyword on the suggestion strip works too. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press A. In the settings app, the **AI** row on the Home screen collects everything AI in one place: this tool and [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/), each with its switch, then the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the settings assistant and the history. Press **AI writing tools** there to reach the provider, key and model. The panel opens on a row of action chips. Eleven ship with the keyboard, and the list is yours to change: rename them, rewrite their prompts, turn off the ones you never use, reorder them, or add your own. See [Your own actions](#your-own-actions) below. - **Rewrite**, **Summarize**, **Translate**, **Improve**, **Formal**, **Shorter**, **Friendly**, **Fix grammar**, **Explain**, **Continue**: each needs text to work on and is greyed out on an empty field. - **Custom**: the one chip that stays live on an empty field. Tapping it opens an instruction box on the keyboard itself. Whatever you type there is the prompt for that run, so Custom can write brand-new text from nothing instead of transforming what's already in the field. In a password field the chips that read the field are greyed out, and no action runs at all, Custom included. The text in a password box never goes to a provider, the same rule [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) follows when it reads a field. If nothing is configured yet, the panel shows a short explanation in place of the chip row. You get an **Open settings** shortcut when no provider is set up, which is where a fresh install starts, or a **Download a model** shortcut when On-device is selected and nothing has come down yet. A model that's downloading right now takes over that empty state with its progress. Once you're past the empty state, a one-line progress strip sits above the chips, so a download you started in Settings stays visible while you carry on typing. tools/ai-need-setup — The AI panel's empty state, before any provider or local model is set up. **What gets sent:** selected text wins if you've selected any, and otherwise the whole field goes. **Continue** is the exception. It always reads back from the cursor, since continuing needs to know what came before rather than what comes after, and **How much the AI reads back** sets how far: 500 to 32,000 characters, default 4,000. That limit only applies with nothing selected. A selection is always sent as it is. Tap a chip on a truly empty field with no selection and you get "There is nothing to work on. Type some text first." instead of a request. While a request runs, the panel names the step it's on instead of showing a bare spinner: Preparing, Connecting, Waiting for the model, and Reasoning for a model that thinks before it answers. A request can spend most of its time waiting on a slow connection or a long field, so the step is worth seeing. **Waiting for the model** is the one you'll meet most. It starts the moment your text is on its way and lasts until the first words come back, which for a cloud provider is nearly the whole run. Once a result streams in, two buttons commit it. **Replace** swaps the field's text for the result. **Insert** drops it in at the cursor instead. Continue is the one exception to Replace: deleting the text you just asked it to continue would defeat the point, so Replace appends there. A **Delete markdown** checkbox, shown only when the result contains any and checked to start with, removes headings, bullets, quotes, code fences, inline code, links, and bold/italic syntax from what gets committed, and writes LaTeX maths out in Unicode, so `$x^2$` lands as `x²`. Uncheck it to keep the raw markdown. Reasoning output between `` tags is filtered out of both Replace and Insert either way, so only the answer itself lands in your field. If a model stops mid-answer because it hit the length ceiling, the panel says so and points at the setting to raise. That's worth knowing about: a truncated answer otherwise looks exactly like a finished one, and you'd only find out by reading your own half-rewritten text. ### Seeing what changed tools/ai-result-changes — A Fix grammar result in the Changes view, with the old spellings struck through. A finished result gets a **Result** / **Changes** pair of chips. Changes compares the answer against the text the action ran on and marks what the model added and what it took out, the way `git diff` does. It's the one thing the plain result can't show you: after a Fix grammar run, the spelling it corrected is gone from the screen. Deleted text is struck through, added text is underlined and slightly bolder. Shape rather than colour alone is deliberate, so the view stays readable on a photo-background theme, in a high-contrast theme, and if you're colour-blind. A changed space or paragraph break is drawn with a visible mark, since an empty highlight tells you nothing. Under the text sits a count of how many parts changed. "Parts" because they're words in prose and single characters in a language that doesn't put spaces between words. Two cases can't be compared, so they don't offer the chips: **Continue**, which only ever read the text before your cursor, and any run that wrote from an empty field, where there's no original to compare against. Very long pairs of texts aren't compared either. The panel shows the plain result and says why. The comparison also waits until the answer has finished streaming, since half an answer reads as "everything after this was deleted". The chips are on by default. **Open a result on the changes** (off by default) makes the panel start there instead. tools/ai-result-ready — A streamed result with Replace, Insert, and the strip-markdown checkbox. ### Reporting a result Every finished result also carries a **Report** button, at the bottom right of the panel next to the Delete markdown checkbox. Tap it if a model produced something offensive, dangerous, or plainly wrong, and it opens a pre-filled email to the developer with a blank line at the top for what went wrong. Nothing is uploaded when you tap it. The report is a draft in your own mail app, and you can read every line, edit it, or throw it away before sending. What it fills in for you: - Your complaint, once you type it in the space at the top. - Which action ran, which provider and model answered, and (for Custom) the instruction you gave. - The app version, Android version, and device model. - The text that went in and the text that came out, each trimmed to the first 4,000 characters. That last one is worth a second look before you hit send, since it quotes whatever was in the field. The button only appears once a result has finished streaming. Half an answer isn't the thing you'd be reporting anyway. For bugs in the keyboard itself rather than something a model wrote, use the **Feedback** rows in [About](https://wmkeyboard.pages.dev/reference/settings/about/) instead. ## Choosing where it runs The services the AI can use are an **AI providers** list at the top of the tool's settings, the same kind of [provider list](https://wmkeyboard.pages.dev/tools/providers/) the translate and search tools keep. **Add a provider** offers Anthropic (Claude), OpenAI, Google Gemini, xAI (Grok), DeepSeek, Brave Answers, Privatemode AI, Ollama, LM Studio, an OpenAI-compatible service, and On your device. Each row holds its own key, address and model, set up in the sheet that opens when you tap it, with a **Test** button that sends the service one word and shows whether it answered. One row answers at a time. Pick it under **In use**, just below the list, or with the [model picker](#options) on the AI panel; the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) and the settings assistant can each send to another row without changing it. A service can be on the list more than once: a second Anthropic key for work, or two Ollama servers, one at home and one at the office. Give each a nickname, and the picker shows it. Switching a row off takes it out of the picker. With nothing on the list, or every row off, the panel says the AI isn't set up yet, even if keys from before are still stored. tools/ai-settings-providers — The AI providers list, with a service's key and model in its setup sheet. ### Cloud providers Claude (Anthropic), OpenAI, Gemini, Grok, and DeepSeek are bring-your-own-key only. Unlike some of the other network tools, WM Keyboard ships no built-in or proxied key for any of them. Add the service and paste your own key into its setup: - **Claude**: from `console.anthropic.com` → API keys - **OpenAI**: from `platform.openai.com` → API keys - **Gemini**: has a free tier at `aistudio.google.com` - **Grok**: from `console.x.ai` → API keys - **DeepSeek**: from `platform.deepseek.com` → API keys Each service's setup also has a **Model** field. Leave it blank and the app falls back to a built-in default, shown right in the field's hint: `claude-sonnet-5-5` for Claude, `gpt-6-luna` for OpenAI, `gemini-3.8-flash` for Gemini, `grok-4.7` for Grok, `deepseek-flash` for DeepSeek. Type a model name here to use something else. DeepSeek's reasoning models stream their thinking on a separate field. The app folds that into the same `` handling everything else uses, so **Show model reasoning** and the reasoning progress readout behave there exactly as they do elsewhere. ### Brave **Brave** uses Brave's Answers API, which runs a web search before it writes each answer. That makes it the provider to pick for questions about facts, prices or recent events, and a poor fit for pure writing jobs like Proofread, where the search adds cost and nothing else. - **API key**: from `api-dashboard.search.brave.com`. The key needs Brave's **Answers** plan. A key that only has the **Search** plan is refused, and the panel shows Brave's own reason. - **Leave the key blank to reuse the web search key.** If you already gave the [web search tool](https://wmkeyboard.pages.dev/tools/search/) your own Brave key, the AI tool uses that one. The key built into some builds is never used for answers, only for searches. With neither key, the row says it needs one. - **No Model field.** Brave has one model, so there's nothing to pick. - **One message per request.** Brave doesn't accept a system prompt or earlier turns, so the app sends the action's instructions, and in a [chat](#chatting-instead-of-transforming) the earlier messages as a transcript, together with your text as one message. - Brave bills each answer for the search it ran plus the tokens in and out. The usage and citation markers it writes into the stream are removed before you see the answer. ### Privatemode **Privatemode AI** runs its models inside confidential computing, and its requests are encrypted end to end, so the service operator can't read them. Get a key at `privatemode.ai`, which has a free tier. The row has three fields: - **API key**: your Privatemode key. - **Server address**: leave it blank for the keyboard to encrypt each request itself, or give the address of your own `privatemode-proxy`, for example `http://192.168.1.20:8080`. With a proxy the key is optional, since the proxy can hold it. - **Model**: blank means `openai/gpt-oss-120b`. **List models** fetches the others your key may use. With no proxy address, the keyboard does what Privatemode's proxy does before a request leaves your phone. It fetches the deployment's published manifest and asks the service to attest itself, and the attested deployment has to run exactly that manifest. It then agrees a secret with Privatemode's secret service, using a fresh post-quantum key pair (ML-KEM-768 with X25519), and checks that the answer is signed by a certificate of the deployment's mesh CA. Each request is encrypted with that secret, and the answer is decrypted as it streams in. An answer that comes back unencrypted, cut off or altered is refused. The secret is renewed every 45 minutes. The proxy also verifies the hardware report itself, against AMD's and Intel's certificates, and compares the deployment's measurements with the manifest. The keyboard doesn't. So the encryption keeps your text from everything between your phone and the service that holds the attested mesh CA, but if Privatemode's API lied about its attestation, the keyboard wouldn't catch it. That's why the row says **Encrypted on this phone, attestation not checked**, and why nothing on the phone ever says "verified". For the full check, run `privatemode-proxy` on a computer you trust and give its address. Through a proxy, the row says **Through your Privatemode proxy**, and the request travels from your phone to the proxy as an ordinary OpenAI-style call. Use `https://`, or keep the proxy on a network you trust. In the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), a lock in the top bar shows while Privatemode answers. Tap it to see which of the two it is. Pictures aren't sent to Privatemode, and extra headers aren't added to its encrypted requests, because everything it reads outside the encryption is fixed by its protocol. ### Any other OpenAI-compatible service **OpenAI-compatible service** is the escape hatch, and it reaches far more than the named services do. Anything that speaks OpenAI's chat-completions shape works: OpenRouter, Groq, Together, Mistral, Cerebras, self-hosted vLLM. Add one row for each service you use. Three fields: - **Server address**: the address up to and including the version segment, for example `https://openrouter.ai/api/v1`. The app adds `/chat/completions` itself. That split exists because services disagree about the prefix (`/v1`, `/openai/v1`, `/api/v1`), so guessing it would break half of them. - **Model**: required here, unlike the named providers. There's no sensible default to guess for a service the app knows nothing about. - **API key**: optional. A gateway on your own network often wants none. An `http://` address works, for a service on your own network, and that traffic is plain HTTP. Keep the server on a network you trust. ### Usage and balance Each AI service's own screen has a **Usage** group: the requests and tokens this phone sent this month, any per-minute allowance the service reports, and for DeepSeek and OpenRouter a **Check what is left** button that asks for your balance. See [Usage and credit](https://wmkeyboard.pages.dev/tools/providers/#usage-and-credit). ### Extra headers Some services want more in a request than a key. OpenCode Go, for one, reads a session header and a User-Agent of its own, and a gateway in front of a provider may want its own token. An **Extra headers** group on each service's own screen holds them: press the service's row on the list. The model on your device has none. Tap **Add header**, then fill in a name and a value. Each edit saves as you make it, and the value stays hidden until you tap the eye. - **Yours are sent last.** A header here replaces the app's own of the same name, whatever its capitals, so a `User-Agent` or `Authorization` you add is the one the service sees. - **`{session}` stands in for a session id.** Type it anywhere in a value, for example the name `x-opencode-session` with the value `{session}`. One panel action, its lookups included, sends one id, and a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) sends the same id for every message in that conversation. - **Each kind of service has its own list.** Switching to another kind shows that kind's headers, not the last one's. Two rows of one kind share theirs. - A name with a space or other character a header can't hold, or a value with a line break, is left out of the request rather than failing it. - A backup without secrets leaves the headers out, the same as API keys. ### Self-hosted servers **Ollama** and **LM Studio** point at a server on your own network instead of a cloud API. No key is needed, only an address like `http://192.168.0.10:11434` (Ollama) or `http://192.168.0.10:1234` (LM Studio). Set `OLLAMA_HOST=0.0.0.0` so Ollama listens beyond localhost. Turn on LM Studio's local-server option. Add one row for each server you run. Ollama defaults its model field to `qwen3` when you leave it blank. LM Studio has no default model to fall back to, so it uses whatever the server currently has loaded. Traffic to a self-hosted server is plain HTTP, and the app allows cleartext traffic app-wide to support it. Keep the server on a network you trust. ### Local models google The **On your device** provider runs a model entirely on the phone, no network involved. It's the only provider that needs a download first, and the one that **Add a provider** doesn't offer at all in the F-Droid build. tools/ai-settings-local-catalog — The on-device model catalog, with tier badges and a size/RAM subtitle per model. The catalog has two lists. The first is eight models in LiteRT-LM's format, the engine the keyboard has always used and still the default. They're ordered best-first rather than smallest-first: | Model | Params | Size | Tier | Gated | |---|---|---|---|---| | Gemma 4 E2B | 2B | 2.59 GB | Recommended | | | Gemma 4 E4B | 4B | 3.66 GB | Recommended | | | Gemma 3 1B | 1B | 584 MB | Untested | Yes | | Qwen 2.5 1.5B | 1.5B | 1.6 GB | Standard | | | Qwen 3 0.6B | 0.6B | 347 MB | Experimental | | | Qwen 2.5 0.5B | 0.5B | 546 MB | Experimental | | | Gemma 3 270M | 270M | 304 MB | Untested | Yes | | SmolLM2 135M | 135M | 143 MB | Experimental | | Each row names the licence its weights are published under, and **Licence and model page** under an undownloaded row opens the repository where the text is. Gemma 3 comes under Google's Gemma Terms of Use; Gemma 4, Qwen and SmolLM2 are Apache-2.0. About, Licences lists the same under downloadable data packs. Two of the eight are gated and need a Hugging Face account. Paste a token into the **Access token** field under **Hugging Face account**, above the catalog. Then accept the model's license on its Hugging Face page, once per model. #### GGUF models on llama.cpp Below them sits a second list, **GGUF models (llama.cpp)**. These run on [llama.cpp](https://github.com/ggml-org/llama.cpp), a second engine that loads any GGUF file, the format most open models are published in. Each row says **Runs on llama.cpp**, and its name ends in "(GGUF)", which keeps it apart from a LiteRT-LM model of the same name in the panel's and the chat's model pickers. Picking one of these models is how you pick the engine: the keyboard routes every request to whichever engine runs the model in use. On the F-Droid build this is the only list: it has llama.cpp but not LiteRT-LM. | Model | Params | Size | Tier | Licence | |---|---|---|---|---| | Qwen 2.5 VL 3B | 3B | 2.77 GB | Untested | Qwen Research License | | Qwen 3 1.7B | 1.7B | 1.83 GB | Untested | Apache-2.0 | | Llama 3.2 1B | 1B | 808 MB | Untested | Llama 3.2 Community License | | Gemma 3 1B | 1B | 806 MB | Untested | Gemma Terms of Use | | Qwen 3 0.6B | 0.6B | 639 MB | Experimental | Apache-2.0 | | Qwen 2.5 0.5B | 0.5B | 491 MB | Experimental | Apache-2.0 | | SmolVLM 500M | 500M | 546 MB | Experimental | Apache-2.0 | | SmolLM2 360M | 360M | 386 MB | Experimental | Apache-2.0 | | Gemma 3 270M | 270M | 292 MB | Experimental | Gemma Terms of Use | None of them is gated, so no Hugging Face token is needed, Gemma 3 included. Each one is fetched from a fixed commit of its repository and checked against a SHA-256 before it counts as downloaded. A file that doesn't match is deleted and the row asks you to download it again. The Qwen 3, Qwen 2.5 and SmolLM2 files come from the makers' own repositories, Gemma, Qwen 2.5 VL and SmolVLM from ggml-org (the llama.cpp project), and Llama 3.2 from bartowski's conversion of Meta's model. Qwen 2.5 VL 3B and SmolVLM 500M read pictures. Each brings a second file, its projector (the `mmproj` file), which the download fetches first; the sizes above count both. Qwen 2.5 VL reads the text in a chat screenshot well, but needs a phone with 8 GB of RAM, and its licence allows research use only. SmolVLM is small enough for most phones, and tells you what a picture shows, but often gets the words in a screenshot wrong. What's different about them: - **GPU.** With **Compute** on **GPU**, a GGUF model runs on the phone's GPU: on a Snapdragon chip (an Adreno GPU) through llama.cpp's OpenCL code, which Qualcomm wrote for these GPUs, and on a Mali, Xclipse or PowerVR GPU through llama.cpp's Vulkan code. A phone with neither runs it on the CPU's big cores, whatever that row says. See [Compute](#compute-and-gguf-models) below. - **Pictures, for the models that read them.** Qwen 2.5 VL and SmolVLM look at a picture themselves: a screenshot in the [reply tool](https://wmkeyboard.pages.dev/tools/ai-reply/), and the pictures in the newest message of a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures). For every other GGUF model, the reply tool reads a screenshot another way, as it does for any model that can't see. - **The model's own chat template.** llama.cpp formats each conversation with the template stored in the GGUF file, so a model you import is prompted the way its makers trained it. Text from your field is kept as text: it can't open a turn of its own, even if it contains the template's markers. - **Stop really stops.** A LiteRT-LM answer runs to its end in the background after you press Stop. A GGUF answer ends at the next word, and what it had written is what the model remembers. - **Memory is checked first.** Before a model loads, the keyboard compares what it needs (the file, plus room for the context) with the memory free on the phone. If it's short, the context shrinks first. If even that won't fit, you get a message with both figures instead of a crash. A loaded model is unloaded after three minutes with nothing to do, and the keyboard lets go of it earlier when it hides. llama.cpp itself is an extra part of a few megabytes. The Google Play edition downloads it from Play with the first GGUF model, and the GitHub edition fetches it from the project's own release. Until it's there, the GGUF list shows a banner with a **Download** button. Google Play sometimes wants your OK before it downloads a part like this, for example on mobile data. It can only ask inside the app, not over the keyboard, so the chat says so, and the question appears the next time you open WM Keyboard's settings. Every model carries an advisory minimum-RAM figure behind the scenes. When your phone's total RAM falls under it, the app appends "May be too large for this device" to that model's subtitle. It never blocks the download and never shows you the raw number. That warning is the only per-device signal the catalog gives you up front. There's no published speed benchmark, because how fast a model answers depends on the phone as much as on the model. What you get instead is a progress readout with elapsed seconds, once a request is actually running. Tapping **Download** starts a resumable download. Cancel it, lose signal, or background the app, and the next attempt picks the transfer up where it left off rather than restarting. Only one download runs at a time. On a metered connection, a model of 500 MB or more asks for confirmation first. Don't need the catalog? **Import your own** takes any `.litertlm` or `.task` file, which LiteRT-LM runs, and any GGUF file, which llama.cpp runs. That covers one you converted yourself or downloaded elsewhere in a browser. A GGUF is recognised by its contents, so one that lost its `.gguf` extension on the way still imports and gets the extension back. Anything else is rejected. The same goes for **Import files** under Storage, which also takes a catalog GGUF and checks it against its SHA-256 like a download. An imported GGUF uses the chat template inside it; one with no template is prompted in ChatML, the format most small open models use. A GGUF vision model you import reads pictures when its projector is imported with it: select the model and its `mmproj` file together, or import one after the other. The projector is not a model, so it gets no row of its own. It pairs with the imported model whose file name it shares, or, when there is only one imported GGUF, with that one. Deleting the model deletes its projector too. A **Compute** row picks **CPU** or **GPU** for inference, and CPU is the default. GPU is best-effort. If it fails to initialize, runs out of GPU memory partway through an answer, or takes the keyboard down with it, the engine falls back to CPU on its own and keeps that model on the CPU from then on. It tries the GPU again after a system update, an app update or a fresh download of the model. Some GPU drivers keep a failed model's memory after the model is closed, which can leave the whole phone slow. When that happens, the keyboard restarts itself a few seconds after you close it, so the memory is freed. It doesn't do this while a screen of the app is open or a model is downloading. #### Compute and GGUF models **GPU** applies to a GGUF model on a phone with an Adreno GPU, the GPU in Snapdragon chips, or with any other GPU whose driver speaks Vulkan 1.2 (Mali, PowerVR, Samsung's Xclipse). On an Adreno it runs llama.cpp's OpenCL code, which Qualcomm tuned for the Snapdragon 8 Gen 1 and newer; older Adreno GPUs that support OpenCL 2.0 run it too, with less gain. On the other GPUs it runs llama.cpp's Vulkan code. How that compares with the CPU depends on the GPU and its driver: public measurements range from much faster on recent Mali and Xclipse GPUs to slower than the CPU on older Mali ones, so try both on your phone. **NPU** runs a GGUF model on the NPU of a Snapdragon 8 Gen 3, 8 Elite or 8 Elite Gen 5 through llama.cpp's Hexagon code, a download of its own of about 6 MB (11 MB on the phone), in place of the GPU build. The NPU works best with models quantized as Q4_0 or Q8_0; the parts of a model it cannot run go to the CPU. It is new in llama.cpp and has not been measured on a phone here, so compare it with **GPU** on yours. On any other phone **NPU** means **GPU** for a GGUF model. Each GPU build of llama.cpp is a download of its own, fetched only on a phone it runs on: the OpenCL build, about 10 MB, on a Snapdragon, and the Vulkan build, about 13 MB to download and 42 MB on the phone because it carries its compiled shaders, on the others. It comes when you pick **GPU** with a GGUF model on the phone, or the first time a GGUF model answers with **GPU** picked. Until it's there, GGUF models run on the CPU. It's loaded the first time a GGUF model runs after the keyboard starts, so if one already ran on the CPU, the GPU takes over after the keyboard restarts, which it does by itself a few seconds after you close it, as above. Failures are handled the same way as for LiteRT-LM models: a model that fails on the GPU moves to the CPU and stays there until the next system or app update, and a GPU driver that takes the keyboard down while starting is not used again until then. The GPU makes the biggest difference to reading a long prompt, such as a chat screenshot or a long text to rewrite. Writing the answer is limited by memory speed, so it gains less, and for the smallest models the CPU can be as fast. Qualcomm tuned models quantized as Q4_0 first. The catalog's Q4_K_M and Q8_0 files also run on the GPU, and with some Snapdragon 8 Gen 2 and Gen 3 drivers llama.cpp keeps the last layer of a K-quant model on the CPU, where those drivers compile it badly. On a phone with a Snapdragon 8 Elite (SM8750, the Galaxy S25 series) or 8 Elite Gen 5 (SM8850, the Galaxy S26 Ultra), running Android 12 or later, the row has a third choice, **NPU**, which runs a model on the phone's AI accelerator. The NPU needs a build of the model compiled for that chip, and only Gemma 3 1B and Gemma 3 270M have one. With **NPU** selected, **Download** on those two rows fetches the NPU build (689 MB and 461 MB on an SM8750) instead of the standard file. A model you already have gets a **Get the NPU build** button. Every other model runs as it does with **GPU**. So does a model whose NPU build fails to start, and the app keeps that model off the NPU until the next system or app update. A failure partway through an answer only moves the next try in that session to the GPU, because a prompt longer than the build's window fails the same way. The NPU build runs only on the NPU, so a model whose only file is its NPU build offers **Get the standard build** when Compute is CPU or GPU. The NPU runtime is Qualcomm's QNN. The Google Play edition downloads it from Play the first time you pick **NPU**. The GitHub edition first asks you to accept Qualcomm's licence, then fetches only your chip's files from Qualcomm's own package on Maven Central, about 30 MB. The F-Droid edition has no NPU at all. A **Context size** row sets how much text the model is loaded with, prompt and answer together, in tokens. It defaults to the model's own value, or 4,096 tokens for a GGUF model, whose own value is often far more than a phone can hold. This is the only length lever on-device. Unlike a cloud API there's no per-response ceiling to set, so the window is what bounds an answer. A larger window costs memory, and changing it reloads the model. ## Your own actions Tools / AI writing tools / Actions on the panel The eleven chips on the panel are a list you own. The screen lists them in panel order, with a checkbox to turn one off, an edit button, and a **New action** row at the bottom. Drag the handles in **Order of the actions** to rearrange them. The panel follows that order exactly. Any of these can also be a button on the [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/#language) bar: pick them under Advanced, Selection actions, AI buttons, and one tap runs the action on whatever is selected, with the result landing here ready to apply. Formal, Shorter and Friendly were added with that in mind. An action that ships with the keyboard is never deleted, only changed. Editing one saves your version under the same identity, so **Reset** in its editor drops your version and the original comes straight back. Turning one off leaves it on this screen, greyed on the panel and one checkbox away from returning. Only actions you wrote yourself get a **Delete**. The editor holds a name, the prompt, and a handful of behaviour switches: - **Type the prompt at every run**: the keyboard asks you for the prompt instead of using a saved one. This is what makes **Custom** work the way it does. - **Start from the saved prompt**: only shown under the switch above, and it brings the prompt field back. The instruction box then opens with that text already in it, so you adjust a template rather than retype it. Useful for something you run often with a small variation each time: "reply to this politely, and mention that…". The saved text wins every time the box opens, not only the first, since a template you see once isn't a template. Leave it off and the box reopens on whatever you typed last. - **Works with an empty field**: with nothing in the field, the action writes new text instead of changing text. - **Read the text before the cursor**: the action reads only what's in front of the cursor rather than the whole field. A selection always wins over this. - **Add the result to the end**: Replace appends rather than overwriting. Pair it with the switch above and you have **Continue**. Keeping them separate is what makes "append but read the whole field" something you can build. - **Answer with the text only**: on for everything except **Explain**, whose answer is prose about your text rather than a replacement for it. Hidden once **Write the whole prompt** is on, since a raw prompt has nothing left for the switch to act on. ### About the prompt field You write the *task*, not the whole prompt. The app wraps what you write in a fixed frame. That frame says who the model is, and it says your field's text is material to work on and never instructions to obey. Unless you turn it off, it also says to answer with the result and nothing else. That's on purpose. The middle part is what stops a model from treating text in your field as commands aimed at it, and that text is often an email you're replying to or a message someone sent you. A text box shouldn't be able to delete that rule by accident. **The prompt that the app sends** below the editor shows you the assembled result, so it's visible without being editable. Write `{target}` anywhere in a prompt and it becomes the translate target language, which is how the shipped **Translate** action works. Because the token is stored rather than the language, changing the target language updates every prompt that mentions it. **Write the whole prompt** is the deliberate way out for anyone who wants to write the safety wording themselves. The app then sends your text exactly as you typed it. Prompts you'd already customised carry over automatically, and the app checks its own work. A prompt it recognises is unpicked back into a task. Anything it doesn't recognise is kept verbatim under this switch rather than guessed at. ## Letting the model look things up Tools / AI writing tools / What the model can do A model answers from what it was trained on, which is a fixed moment in the past. Turn one of these on and it can stop guessing: before it answers, it asks the keyboard to go and find out. - **Let the model search the web**: on by default, but the row only appears once you've set up a search service. The model writes its own search (usually a better one than the question you asked), reads the results, and answers from them. Searches go through whatever backend the [search tool](https://wmkeyboard.pages.dev/tools/search/) already uses (your SearXNG instance, your Tavily key, or Brave) and count against that service's quota like any other search. - **Let the model read a web page**: off by default. The model gives one address and gets the text of that page back. It needs no search service, so it works on its own: paste a link, ask "what does this say?", and the model fetches it. - **Lookups per answer**: 1 to 5, default 3. How many times the model may go and look something up before it has to answer with what it has. Each round is another request carrying everything found so far, so this is the setting that decides what a curious model costs you. Both tools work in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/). While one runs, the progress readout says what it's doing and on what: **Searching the web for "…"** with the model's own query, or **Reading example.com…** with the site it opened. ### Why page reading is the one that's off **Let the model search the web** sends a query the model wrote. **Let the model read a web page** opens an address, and that address can have come from the text you were typing: a link in a document someone sent you, say. That makes it the one tool here that something in your own field could point somewhere, so it waits until you ask for it. When it does run, the page is fetched as plain text and cut short at about 6,000 characters. No scripts run, nothing is sent *to* the page, and only `http://` and `https://` addresses are opened at all. A model that invents a `file://` or `content://` address gets a refusal, not your storage. ### What your provider needs to support Most providers do tool calling properly, as part of their API: Claude, OpenAI, Gemini, Grok, DeepSeek, Ollama, LM Studio and any other OpenAI-compatible service. Two can't: - **Brave** takes a single message with no system prompt and has no tool field at all. - **On-device models** have no tool API in the local runtime. For those two the keyboard falls back to explaining the tools to the model in words and watching its answer for a reply in the agreed format. The reader is deliberately forgiving about that format, because local models mangle it: `tool_call` is a special token in several chat templates, so the template rewrites the very thing the keyboard asked for, and a marker can come back with its brackets moved, doubled, or missing altogether. All of those are read, and anything that looks like one is hidden from the answer whether or not it turns out to be a call, because machinery the model wrote for the keyboard is never yours to read. It works, and a capable local model will use it, but a small one still ignores the format sometimes, or asks you to narrow a request it could have searched as written. The settings group says so when you're on one of those providers. If lookups matter to you, a provider with real tool support is far more reliable. Thinking models add one more rule. Before a model calls a tool it may reason, and several providers make the keyboard hand that reasoning back, untouched, along with the tool's answer: Gemini 3 attaches a signature to every call, Claude signs its thinking blocks, DeepSeek wants its reasoning text, and Gemini reached through OpenRouter or an OpenAI-compatible address hides the same signature in other fields. The keyboard keeps each model's turn exactly as it arrived and sends it back that way. Some providers reject the request if anything is missing. ### Seeing what it looked up An answer that used a tool says so. A line sits over the result, in the panel and in every chat answer: **Searched the web for "…"** or **Read example.com** for a single lookup, or **Used tools 3 times** when there were several. No line means the model answered from what it already knew. Tap the line to unfold it. Each lookup is listed with what came back: - **A search** shows the query and every result the model was handed, by title and site. **No results** if the search came back empty. - **A page read** shows the page's title and its full address, so you can check it's the page you think it is. - **A lookup that failed** is marked as failed, with the reason: **Web search is not set up**, a network error, the model asking for something that isn't a web address, and so on. The model is told too, and usually says it couldn't check. Tap a result to open it in your browser, or press and hold to copy its address. Only `http://` and `https://` addresses ever open. Anything else the model wrote can be copied but not opened. The lines aren't part of what **Replace** or **Insert** puts in your field. In [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) the record is saved with the answer, so reopening a conversation later still shows what each answer was built on. The panel's actions keep it only while the result is on screen. Every search and every page fetch is also a row in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/), filed under **AI tool use** rather than **Web search**: you didn't run that search, the model did, and the log should say which. ## The keyboard's own tools Tools / AI writing tools / Keyboard tools Besides searching and reading pages, the model can use the keyboard's own tools, the same ones on your toolbar, through the same services and with the same settings. Ask "what's 18% of 2,450 in euros?" and it works the sum out on the calculator and converts it at a live rate instead of guessing either. Each tool has its own **Run**, **Ask** or **Off**, the same three choices an [MCP server's tools](#run-ask-or-off) have: - **Run**: the model uses it whenever it needs to. - **Ask**: the model's request stops and waits for you, with what it is about to do and the values it will send. See [Allowing a call](#allowing-a-call). - **Off**: the model isn't told the tool exists. | Tool | What it does | Starts on | |---|---|---| | **Image search** | Finds pictures through the [search tool's](https://wmkeyboard.pages.dev/tools/search/) service. Only listed once one is set up. | Run | | **Weather** | Today's and tomorrow's weather for any place, or the place saved in the [weather tool](https://wmkeyboard.pages.dev/tools/instruments/#weather) when the model names none. | Run | | **Wikipedia** | Reads the summary of the best-matching article, in the Wikipedia language the [encyclopedia tool](https://wmkeyboard.pages.dev/tools/search/#reading-wikipedia) uses. | Run | | **Dictionary** | Looks a word up in the [dictionary tool's](https://wmkeyboard.pages.dev/tools/search/#looking-up-definitions) sources, in your order. | Run | | **Translate** | Translates with the [translate tool's](https://wmkeyboard.pages.dev/tools/translate/) engine: on your device, online, or on your device first, as set there. | Run | | **Currency converter** | Converts money at live rates from the [currency tool's](https://wmkeyboard.pages.dev/tools/utilities/#converting-units-and-currencies) sources, coins included. | Run | | **Unit converter** | Converts lengths, weights, temperatures and other measurements. | Run | | **Calculator** | Works an expression out exactly, on your phone, with the [calculator's](https://wmkeyboard.pages.dev/tools/utilities/#doing-the-math) degree and precision settings. | Run | | **WM Keyboard docs** | Searches this documentation for questions about the keyboard itself ("how do I import a Chinese dictionary?") and reads the best-matching sections, with their links. Sends only the search words, to this site. See [Using the docs with AI](https://wmkeyboard.pages.dev/reference/ai-access/). | Run | | **Read your calendar** | Lists your events for the days the model asks about, up to a month at a time. Needs the [calendar tool's](https://wmkeyboard.pages.dev/tools/calendar/) access. | Ask | | **Read your notes** | Searches your [notes](https://wmkeyboard.pages.dev/tools/notes/) and reads up to five that match. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, notes that say the same thing in other words follow under "Close in meaning". A locked note's text stays hidden. | Ask | | **Read your snippets** | Searches your [snippets](https://wmkeyboard.pages.dev/tools/snippets/) by their name, trigger, tags and text, and reads up to five that match, or the five newest when the model asks for none in particular: each one's name, how it is typed and its text, cut short. A [template](https://wmkeyboard.pages.dev/tools/snippets/#template-variables) or [pattern](https://wmkeyboard.pages.dev/tools/snippets/#pattern-triggers) is read as written, `{date}` and `$1` included, and never expanded. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, snippets that say the same thing in other words follow under "Close in meaning". | Ask | | **Search your clipboard** | Finds the clips in your [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/) that mean what the model asks about, in any language, and reads up to eight short excerpts. Passwords, one-time codes and deleted clips are left out. Only listed while [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) is on and its model is downloaded, and it only finds what the keyboard has already indexed, which happens when the clipboard panel opens. | Ask | | **Your location** | Reads where your phone is, rounded to the precision the [location tool](https://wmkeyboard.pages.dev/tools/location/) shares at, with the address if you have that on. | Ask | | **Add to your calendar** | Adds an event, see [Adding calendar events](#adding-calendar-events). | Ask | | **Save a note** | Adds a new note. | Ask | | **Set an alarm** | Sets an alarm in the [clock](https://wmkeyboard.pages.dev/tools/clock/), once or on repeat days. | Ask | | **Start a timer** | Starts a countdown in the [clock](https://wmkeyboard.pages.dev/tools/clock/). | Ask | | **Open an app** | Opens an installed app by its name, from the [app launcher's](https://wmkeyboard.pages.dev/tools/app-launcher/) list. | Ask | | **Control media** | Plays, pauses or skips what is playing, or says what it is. Needs the [media tool's](https://wmkeyboard.pages.dev/tools/media/) notification access. | Ask | | **Send to your computer** | Sends text or a link to a computer paired through [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/). Only listed while KDE Connect is on. | Ask | | **Read your home** | Lists what is on or open across every home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/), or whatever matches a home, room, device or sensor name. Only listed while a home is set up. | Ask | | **Control your home** | Works one device on any home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/): lights, fans, temperature, scenes, locks. The model finds the device with **Read your home** first. With a Home Assistant on the list it can also send one plain sentence to Assist. | Ask | | **Read text in a picture** | For a model that can't see pictures: reads the text in one you [sent in the chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures), with ML Kit on your device google, or the [scan text tool's](https://wmkeyboard.pages.dev/tools/scanner/) online engine if you picked that. | Run | | **Search by picture** | Uploads a picture you sent to the [camera tool's](https://wmkeyboard.pages.dev/tools/camera/) search site (Google Lens unless you changed it) and gives you the link to the results. The model can't read that page itself. | Off | The tools that read what's on your phone, and the ones that change something, start on **Ask**. A web page the model reads could carry instructions of its own, and **Ask** means nothing it says reaches your calendar or your notes without you seeing the request first. None of the tools can delete or edit anything: they only read, add or start. **Always allow** on the request card switches that tool to **Run**. The calendar tools, and the alarm, are told today's date and your time zone, so "tomorrow at five" lands on the right day. Every tool that is on is described to the model in every request, whether it gets used or not. That costs a little each time, and a small model on your device copes best with a short list, so turn off what you don't want it reaching for. While one runs, the line over the answer says what it is doing: **Checking the weather in Dhaka…**, **Converting 12 USD → EUR…**. Unfolded, a Wikipedia lookup lists the article it read, and an image search the pages its pictures came from. ### Adding calendar events Tools / AI writing tools / Keyboard tools / How events are added **Add to your calendar** works one of two ways: - **Open the app**, the default: your calendar app opens with the event filled in, and nothing is saved until you press its own Save. It needs no permission. The model is told the event isn't saved yet, so it says so. - **Add directly**: the keyboard writes the event into your calendar itself. Picking it asks for permission to add calendar events, with the usual explanation first, then lets you choose which calendar they go in. The first writable one, primary first, is used until you pick. ## MCP servers Tools / AI writing tools / MCP servers MCP (the Model Context Protocol) is a standard way for a server to offer tools to a model. Add a server here and the model can use its tools in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the same way it uses the two lookups above: to read your notes, search a wiki, add a calendar event, or whatever the server does. The tools run on that server, not on your phone. ### Adding a server Press the **+** button. The editor asks for: - **Name**: what the list calls it. Leave it blank and the host name is used. - **Address**: the server's `https://` or `http://` address, for example `https://example.com/mcp`. - **Type**: **Auto**, **Streamable HTTP** or **SSE**. Auto works for almost every server. It tries Streamable HTTP, the current transport, and falls back to the older SSE one when the server answers like an old server. Pick SSE for an old server that Auto can't reach. Its address often ends in `/sse`. - **Headers**: sent with every request to this server. This is where a token goes. A typical one is the name `Authorization` with the value `Bearer` followed by a space and your token. Values are hidden until you tap the eye. **Save** stores the server. **Connect** saves it too, then asks the server for its tools and reports back: "Connected to *server name*: 12 tools", or the reason it couldn't. A server you just added says **Not connected** in the list until a Connect has worked, and the model is offered nothing from it until then. Change the address later and the tool list no longer describes the server, so the editor asks you to connect again. ### Pasting a JSON config Most desktop apps that use MCP keep their servers in a JSON file. Copy that file's text, then press **Paste a JSON config** on the server list. The keyboard reads the formats in common use: Claude Desktop and Cursor's `mcpServers`, VS Code's `servers`, the inner map on its own, or a single server. It shows what it found before anything is saved, then **Add 2 servers** adds them and connects each one. ```json { "mcpServers": { "notes": { "url": "https://notes.example.com/mcp", "headers": { "Authorization": "Bearer your-token" } }, "files": { "command": "npx", "args": ["-y", "some-file-server"] } } } ``` Here **notes** is added. **files** is listed as skipped: it starts a program on a computer (the `command` line), and a phone has nothing to start it with. A server with no address, or one that isn't `http` or `https`, is skipped too, with the reason beside it. ### Run, Ask or Off Once a server is connected, its editor lists every tool, with the server's own description and badges for what the server says about it: **Read only**, or **Can change or delete data**. Each tool has three choices: - **Run**: the tool runs when the model calls it, without asking you. - **Ask**: the answer stops and waits for you to allow the call. - **Off**: the model isn't told the tool exists. A new tool starts on **Run** if the server marks it read only, and on **Ask** otherwise. That default matters because a tool can send mail or delete a file, and text in the field you're editing could try to talk a model into calling one. Your own choice always wins over the server's marks. The list row for each server sums it up ("12 tools: 3 run, 7 ask, 2 off") with a switch that turns the whole server off without deleting it. ### Allowing a call When the model calls a tool set to **Ask**, an approval card appears in the panel or in the chat. It names the tool and the server and shows exactly what the model wrote as the tool's input. Three buttons: - **Deny**: the call doesn't happen. The model is told you said no and answers without it. - **Allow once**: this call goes ahead. - **Always allow**: this call goes ahead, and the tool is set to **Run** from now on. ### What is sent, and where Nothing goes to a server until the model calls one of its tools, or you press Connect. A call sends the tool's name and the values the model wrote for it straight to your server, with your headers. It doesn't go through any WM Keyboard service. Whatever the server sends back is handed to the model. Every request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **MCP servers**. ### Limits - **No sign-in yet.** A server that wants you to log in through OAuth can't be used. If it gives out tokens or API keys, add one as a header instead. - **Only `http` and `https` servers.** Local servers that start as a program (`command`, `npx`, `uvx`) need a computer to run on. - **Every tool costs prompt space.** The name, description and input format of each tool that isn't **Off** go with every request, so 30 tools can cost more than the text you asked about. The server list warns you past 20. Turn off the tools you don't need. - **Text only.** Images, audio and links a tool sends back aren't shown to the model. It is told that something was there. ## Suggestions in the strip Tools / AI writing tools Off by default. Turn on **AI suggestions** and the provider you set up here offers one short continuation of what you're typing, as a chip in front of the word suggestions. The chip carries a sparkle and an accent outline, so it never passes for a dictionary word. The word suggestions stay where they are, and a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) still wins the strip when one comes up. A request goes only after a word ends (a space or a punctuation mark) and you stop typing for about 0.6 seconds. In Chinese and Japanese, which put no space between words, a committed Han or kana character counts as a word end too, so the chip can follow 你好 with nothing typed after it. On a pinyin, kana or other conversion layout it waits until the reading is committed and the candidates have left the strip. Any key you press cancels it. The answer has 1.5 seconds to arrive; after that the chip doesn't appear and nothing tells you, since a late suggestion is for text you've already moved past. The chip shows an answer only if it is one line of at most 48 characters that doesn't just repeat your text. The request asks the model not to think first, where the provider has a way to say so. A model that reasons anyway, such as DeepSeek's default or OpenAI's reasoning models, gets up to 512 tokens and 5 seconds instead, since its reasoning comes out of the same budget as the answer. It normally asks once a word has ended. **Ask while typing a word** (off by default, because it sends many more requests) also asks mid-word. While a Chinese or Japanese reading is still unconverted it asks a different question: what the reading most likely spells in your sentence. The chip then shows those characters, and tapping it puts them in place of the reading, like picking a candidate. If the model takes the reading for Latin text there is no chip, and shape codes such as Wubi, Cangjie and strokes aren't sent at all. **Ask after a word ends** can then be turned off. Each has its own **Wait before asking** and **Give up after** time, and a model that always thinks first is skipped mid-word, though a reading waits for it, up to the same 5 seconds. Thai, Lao, Khmer, Burmese, Tibetan, Javanese and Balinese count as spaceless, like Chinese and Japanese: their letters can end a word, and answers in them stop at 16 characters. A conversion you tap is learned like a picked candidate, unless learning is off, you're in incognito or it's a password field, so the same reading ranks it higher next time. On the built-in Pinyin engine that holds even for a word the dictionary lacks, as long as you typed full pinyin and the word has one character per syllable. On RIME and Mozc it's learned only if the engine lists the word too. Tap a continuation to insert it after your text, with a space in front where one is missing (never next to Chinese or Japanese). Since 0.6.6 the word it ends on is learned once, as if you'd typed it, so a word the model found for you counts toward your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) the way your own do. When the continuation finishes a word you'd started, the whole word is learned. Only that word: the rest is the model's phrasing, so no other word and no word pair comes from it. Nothing is learned with learning off, in incognito or in a password field, and nothing you've learned is ever sent to the provider. Hold it to hide AI suggestions until you move to a different field. **Show after the words**, off by default, keeps the chip at the end of the strip instead of in front of the words, so a chip arriving late never moves a word you're about to tap. What each request carries: - **Text to send**: the last 120 characters before the cursor by default, cut forward to a whole word. The slider goes from 40 to 500. - **Send only the last word**: sends that word and nothing else. Less goes out, and the suggestions get worse. - A short system prompt asking for a continuation in the same language, with no explanation and no quotes. No request is made at all in a password field, in a field that turns suggestions off, in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), when no provider is set up, or while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds cloud AI back. With an **On-device** model the text stays on the phone. A phone model can't be stopped part way, so no new request starts while one is still running, or while the AI panel is using the model. The first request also has to load the model, which can take longer than the 1.5 seconds. ## Chatting instead of transforming Tools / AI writing tools / Chat with the model The panel's actions all do the same shape of thing: take the text in a field, hand it back changed. When you want an actual conversation instead, tap **Chat** on the panel's header. The panel swaps its actions for a conversation with any model you've already set up here: ask something, then **Insert** the answer into what you're writing. **Actions** on the same header swaps back, and the panel reopens on whichever you used last. The same chats open full-screen in the settings app, at the path above or from a launcher shortcut of their own. Both places share one list of saved conversations. See [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/). Two more ways in work from any app. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen), set to **Ask AI about this screen**, opens a chat as a sheet over the screen you're on, with that screen's text attached when screen reading is on. **Ask AI** in the text selection menu opens the same sheet with the selected text; **Ask AI in the selection menu** below takes it out of that menu. The phone's assistant gesture can open it too, with **Use as the phone's assistant** below. **WM Keyboard** in the same menu runs your actions on the selected text directly (Explain, Summarize, Shorter and the rest) and can put the answer in place of the selection; see [In any app's selection menu](https://wmkeyboard.pages.dev/smart/selection-actions/#in-any-apps-selection-menu). ## Memory AI / Memory The AI can keep short facts about you between chats: where you live, what you do, how you like answers. A chat saves them, with an **Undo** under the answer each time, and the panel's actions and AI reply are handed them too, so a reply or a fresh piece of writing can use them. Actions only read memory, never save to it, and are told to leave the facts out unless the task needs them. A model on your phone gets memory while it's on; a cloud or self-hosted service gets it only after you turn on **Use with cloud models**. Incognito fields get none. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) for the whole of it. ## History Tools / AI writing tools / History Off by default. Turn it on and the keyboard keeps a record of each run: what you sent, what came back, which action and model, how long it took, how much of that was reasoning, and whether you used the answer. The screen searches across the input, the answer and the model name. It filters by action, and expands a row so you can copy either side or delete it. Failed runs are recorded too, with the error the panel showed, since "it keeps failing" is the most likely reason to open the screen at all. Four things have to be true before anything is written down, and any one of them being false is the end of it: 1. You turned the history on. 2. The device has been unlocked at least once since it restarted. 3. The field isn't a password box. 4. [Incognito](https://wmkeyboard.pages.dev/privacy/incognito/) isn't in force, from either the setting or a field that asked for it, like a private browser tab. API keys are never part of a record. It carries the model's name and nothing about the connection. **Number of records to keep** defaults to 100 and tops out at 500. The oldest falls off once it's full, and each side of a record is capped at 4,000 characters. **Delete all history** clears the lot, and so does turning the setting off. An off switch that quietly leaves the log on disk isn't an off switch. ## Options Tools / AI writing tools Like every tool, AI has an **Enabled** toggle (on by default) and, if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are on, a per-tool icon color override. Below that: - **Keyword**: the word that offers this tool on the suggestion strip when you type it alone. Defaults to `ai`. - **Extra headers**: on each service's own screen, none to start with, see [Extra headers](#extra-headers). - **AI providers**: the services and models the AI can use, see [Choosing where it runs](#choosing-where-it-runs). Empty on a fresh install, which lands on the empty-state screen until you add one. Until you edit it, the list holds every service your earlier settings had a key or an address for. - **In use**: which row answers, shown once the list has two. The model picker on the panel changes it too. - **Max response length**: a token ceiling, roughly ¾ of a word each, picked from steps rather than a slider: 1k through 128k, plus **Provider maximum**. Default 8,192. A model the app takes for a reasoning model gets 4× this ceiling on its own, capped at 131,072, because reasoning tokens come out of the same budget before the answer starts. That guess is a substring match on the model ID, for strings like "thinking", "qwen3", "grok-4" or "deepseek-v4". The On-device provider swaps this row for **Context size**. - **Translate action's target language**: free text, defaults to **English**. - **Show model reasoning**: off by default. Turning it on streams a reasoning model's raw `` passages instead of the step-by-step progress readout. It's for the panel's actions only: the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/#reasoning-and-tool-calls) always keeps reasoning in a folded step over the answer. - **Model picker on the panel**: on by default. Adds a horizontally-scrolling row to the panel itself for switching between every configured provider and downloaded local model in one tap. Under it sits the name of the model in use: press it and the keyboard asks the provider what it offers, then lists those to pick from. **Choose from the list** beside the Model field on a provider's page does the same in settings. It's hidden automatically whenever there are fewer than two to choose from. - **Show a changes view**: on by default, see [Seeing what changed](#seeing-what-changed). **Open a result on the changes** below it is off. - **AI suggestions** (under **Suggestion strip**): off by default. With it on, **Send only the last word** (off) and **Text to send** (120 characters) set what each request carries. See [Suggestions in the strip](#suggestions-in-the-strip). - **Let the model search the web**: on by default, and only shown once a search service is set up. **Let the model read a web page** below it is off, and **Lookups per answer** defaults to 3. See [Letting the model look things up](#letting-the-model-look-things-up). - **Keyboard tools**: Run, Ask or Off for each of the keyboard's own tools the model can use, see [The keyboard's own tools](#the-keyboards-own-tools). The information tools start on Run. - **MCP servers**: the servers whose tools the model can use, see [MCP servers](#mcp-servers). None are set up to start with. - **Chat with the model**: opens the conversation screen, see [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/). - **Enter sends the message**: off by default. In the chat on the keyboard, Enter starts a new line and the send button sends. Turn this on to send with Enter instead. - **Auto-rename chats**: on by default. The model that answered names each chat from your first message and its reply, in your language, and can rename it later when you ask and accept. See [Naming chats](https://wmkeyboard.pages.dev/tools/ai-chat/#naming-chats). - **Ask AI in the selection menu**: on by default. Puts **Ask AI** in other apps' menu for selected text. Turn it off to remove the entry; turning the AI tool off removes it as well. - **Use as the phone's assistant**: off by default. Lists WM Keyboard among Android's digital assistant apps; once you pick it there, the assistant gesture opens a menu of chat actions over the app you're in. **Choose the phone's assistant**, under it, opens Android's page for the pick. See [Using it as the phone's assistant](https://wmkeyboard.pages.dev/tools/ai-chat/#using-it-as-the-phones-assistant). - **Actions on the panel**: the whole action list, see [Your own actions](#your-own-actions). - **History**: off by default, see [History](#history). ### On the History screen Tools / AI writing tools / History One more row lives on the History screen, under a **Carry-on actions** heading. Up to 0.6.3 that heading read **Downloading models**, left over from a Wi-Fi switch that has since moved: model downloads on a metered connection are set in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), on the **Downloads** row, which starts at **Ask each time**: the size-keyed prompt (500 MB for a language model, 150 MB for a Whisper one) still fires on top, so a smaller model asks too rather than coming down without a word. Set the row to **Turn off** and no model downloads on mobile data at all. - **How much the AI reads back**: 4,000 characters by default, anywhere from 500 to 32,000. This is how far back from the cursor a carry-on action such as **Continue** reads. More characters give the model more of your writing to follow, and cost more tokens and more time on every run. It has no effect when you've selected something, because a selection is always sent as it is. ### What the On-device provider adds google Adding **On your device** to the list puts the whole model manager on that row's own screen: press **On your device** to open it. [Local models](#local-models) covers the catalog, **Compute** and **Context size**. Three more things sit on that screen and nowhere else: - **Hugging Face account**, holding an **Access token** field. Blank by default, stored the same way an API key is, and only the gated models need it. Under the field, **Get a token** opens `huggingface.co/settings/tokens` in your browser. - **Import your own**, an **Import model file** button that opens the system file picker. It takes `.litertlm` and `.task` files only, and anything else is rejected with a message under the button. - **Free up**, labelled with a size (like "Free up 584 MB"). It only appears when there are orphaned files, meaning model directories left behind by an app update that dropped a model from the catalog. No row can reach those files any more, so the button is the only way to delete them. They're reported rather than deleted for you, on the grounds that it's a multi-gigabyte download you already paid for. Above that sits a line reporting how much storage your models take in total, partial downloads included. That figure is usually bigger than the one beside **Your models**, which only sums the finished ones you can see. The first time a model loads, the engine also builds working files from it, about the model's own size again, and keeps them so later loads are fast. They count as a cache: **Settings → About → Storage** lists them as **AI model working files** under **Caches**, and **Free up** there clears them, at the cost of one slower load. Deleting a model deletes its working files with it. ### About "Provider maximum" This one is worth understanding, because the old default was the reason a long **Improve** used to come back with only the first part rewritten. **Provider maximum** leaves the ceiling out of the request entirely and lets the service apply its own, which for a modern model is far more than any number you'd pick by hand. It's the right choice for long text. Anthropic is the one exception: its API requires the field, so the app asks for a large number there instead. If the model you picked allows less, Anthropic rejects the request and names its real limit in the error. The app retries once at that number. Nothing has streamed at that point, so all you see is the answer. Ollama had never been sent a limit at all before this. It gets one now, and **Provider maximum** there means what it always did. ## What leaves your phone The panel itself sends nothing until you actually tap an action chip: opening the tool, or typing in the field behind it, sends nothing anywhere on its own. - **On-device**: the text you run an action on never leaves the phone. Response length is bounded by the local model's own context window instead of a server-side limit. - **Privatemode**: the text is encrypted on your phone before it's sent, or by your own proxy. See [Privatemode](#privatemode) for what the phone does and doesn't check. - **Any other provider** (cloud or self-hosted): the text you run an action on is sent to that provider, only at the moment you tap the action. API keys and server addresses are stored on the device, not in any WM Keyboard service. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for how this fits the keyboard's broader per-tool approach to anything that leaves the device. - **AI suggestions, when [Suggestions in the strip](#suggestions-in-the-strip) is on**: every time you stop typing after a word, the last 120 characters before the cursor (or the number you set, or only the last word) go to the same provider, with no tap. Never from a password field, a field that turns suggestions off, or incognito. Off by default, and an on-device model keeps it on the phone. - **Dictation, when [Tidy dictation with AI](https://wmkeyboard.pages.dev/tools/voice/#tidying-dictation-with-ai) is on**: each phrase you dictate goes to the same provider as soon as it lands, with no tap, to have its filler words taken out. Its text only, never the audio. Off by default, and an on-device model keeps it on the phone like any other action. - **Report**: builds a draft in your mail app and hands it over. The app has no reporting server and sends nothing itself, so a report only exists if you send the mail. API keys are never part of it. - **AI memory**: with an on-device model, goes nowhere. With a cloud or self-hosted provider, the saved facts picked for a run go with it only once you've turned on **Use with cloud models**, which starts off. Incognito fields never send them. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/). - **History**: goes nowhere. It's a file in the app's private storage, off unless you turn it on, never written from a password field or in incognito, and deleted when you turn it off. It's deliberately left out of the backup file too, so it can't ride along in something you export and mail to yourself. There's no section for it to travel in, unlike the learned dictionary, which is a section of its own that you tick or leave alone. See [Your data](https://wmkeyboard.pages.dev/privacy/data/). - **A lookup the model asked for**: goes to the service that answers it, and only when the model actually asks. A search carries the query the model wrote, not your field text, to whichever search backend you set up. A page fetch carries nothing but the request for that page. Both show in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **AI tool use**, and both are off entirely while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is holding web search back. - **Search by picture**: uploads the picture to the search site picked in the camera tool, and only when the model calls it, which it can't until you turn the tool on. - **One of the keyboard's own tools**: goes where that tool always sends it, and only when the model calls it. The weather goes to Open-Meteo, a translation to the translate tool's service (or nowhere, on your device), a rate lookup to the currency tool's sources, and so on. The calculator and the unit converter send nothing. Each request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under the tool it belongs to. What the tool hands back goes to your AI provider with the rest of the conversation: your events, a note, your location. That is why the tools that read your things start on **Ask**. - **A tool on an MCP server**: goes to the server you added, and only when the model calls that tool or you press Connect. A call carries the tool's name and the values the model wrote for it, with your headers. See [MCP servers](#mcp-servers). - **The changes view**: is computed on your phone. Comparing two pieces of text is arithmetic, not a request, so nothing is sent to build it. ## Details & edge cases - **Not every row here can be reset.** The **Enabled** switch and four of the AI switches (Show model reasoning, Model picker on the panel, Show a changes view, Open a result on the changes) grow a restore button once you move them off the default listed above, and so does the Translate target-language field. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). **Keyword** does it differently: change the word and a **Reset to defaults** row appears under the field, naming the word it would put back. Max response length and Context size are drawn as chip rows rather than settings rows, so neither has a button. The AI providers list has a **Reset to default** row of its own once you change it, which puts it back to what your earlier settings make. - **Offline, an on-device model can stand in.** With [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) on and a [local model](#local-models) downloaded, an AI action set to a cloud provider or a server runs on the phone when there is no connection, or when the server does not answer. A short message says so the first time. AI chat always uses the provider you picked. - **AI isn't pinned to the toolbar or the onboarding wizard's starter set by default.** You add it yourself from the toolbox once you want it, the same as most non-essential tools. - **Not available on the lock screen.** AI needs credentials the direct-boot-safe mirror of your data deliberately doesn't carry, so it's one of the tools that only works after you've unlocked the device once since restart. - **The Writing keyboard mode pins AI first** on its toolbar for apps it recognizes as writing-heavy (see [Keyboard modes](https://wmkeyboard.pages.dev/typing/modes/)). The pin shows in every build, same as the tool itself. Only the On-device provider is missing from the F-Droid build. - **A reasoning model's guess is a substring match** against the model ID, on strings like "thinking", "-r1", "qwen3", "gpt-5", "gpt-6", "grok-4", "deepseek-v4". It can be wrong in either direction. A false positive only costs a larger token ceiling. A false negative risks a response cut off mid-thought. - **Custom has no stored default prompt.** Every other action's prompt can be edited and still has a built-in fallback. Custom's instruction only ever exists for the run you type it for. - **On-device answers never claim to have been cut off.** The local engine reports no stop reason, so there's nothing to read, and the app says nothing rather than guessing. Every other provider does report it. - **The changes view can't spot moved text.** A paragraph the model relocated reads as a deletion where it was and an insertion where it went. Detecting moves properly needs a second pass that git only does on request. The actions that move text, Summarize and Rewrite, reword it at the same time, so that pass would fire rarely and mislead when it did. - **Comparisons drop to whole characters for scripts without spaces** (Chinese, Japanese, Thai, and others). Word-sized pieces would make an entire sentence a single highlight there. Mixed text uses characters throughout, which is noisier but never wrong. - **Local models are advisory about RAM, never a hard gate.** A phone under a model's stated minimum can still download and run it. You get a warning in the catalog first. - **A model that will not stop looking things up still answers.** When it has used up its **Lookups per answer**, the last request is sent with no tools offered at all, so the model has to write something with what it found. It isn't shown an error. - **The round limit is a ceiling, not a target.** Most answers use none. A model only calls a tool when it decides it needs to, and nothing is sent anywhere on a run where it doesn't. - **A failed lookup is handed to the model, not to you.** A search with no backend, a page that 404s, an address that isn't one: the model is told in a sentence and carries on, usually by saying it couldn't check. A broken page doesn't fail the whole answer. - **What a lookup found isn't kept in a chat's transcript.** Only the model's answer is saved. Ask a follow-up and the model has its own earlier reply to go on, not the search results behind it, so it may look again, which is usually what you'd want anyway. - **Tools are off on a local model mid-conversation until the chat restarts.** A local chat session carries its instructions from the moment it opened, so turning a tool on part-way through takes effect on the next conversation rather than the next message. - **No shared or built-in key for any cloud provider.** Claude, OpenAI, Gemini, Grok, DeepSeek and Brave each need your own API key. There's no free built-in quota here, the way some of the other network tools offer one. - **A model download dies with the app's process.** It resumes rather than restarting, but it isn't a foreground service, so backgrounding the app for long enough can stop it. That hasn't changed. The panel just shows you it's happening now. --- # AI chat URL: https://wmkeyboard.pages.dev/tools/ai-chat/ Section: Tools Summary: A full back-and-forth conversation with whichever model you set up, on the keyboard's AI panel or in the settings app, as one shared list of chats. The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) runs one-shot actions against the text in a field. Chat is the other half: a normal back-and-forth conversation with the same model, kept on your device. You can chat in two places, and they're the same chats. The keyboard's AI panel has a **Chat** mode, for asking something and dropping the answer straight into whatever you're writing. The settings app has a full-screen version, for longer conversations. Start one on the keyboard, carry it on in the app, come back: there's one list, and an answer that's still streaming in shows up on both. ## Chatting on the keyboard Open the AI tool from the toolbar. The panel's header has two chips, **Actions** and **Chat**. Tap **Chat**. The panel remembers which one you left it on, so if chat is what you use, the AI button opens chat from then on. tools/ai-chat-keyboard — Chat mode on the AI panel: the conversation, the newest answer's buttons, and the composer. Tap the message box and the keys come up underneath, with the last few messages still in view above it. The keyboard gets taller while you type so the conversation doesn't disappear. On a short screen the conversation gives up room first: the keys under the message box always keep their full height. Tap the conversation, or press Back, and the keys step aside again so you can read. Your half-typed message stays where it was, even if you close the panel. Glide typing, suggestions and the caret all work in the message box the way they do in any of the keyboard's own fields. Nothing you type there reaches the app behind the keyboard. Your own messages sit in bubbles on the right. Answers have no bubble: each one runs the full width of the panel, as long as it is, so a table or a block of code gets all the room there is. ### Getting an answer into your text The newest answer always shows its buttons, a row of plain icons under it. Tap any older message to bring out its own. Long-press an icon to see its name. - **Insert** puts the answer in the field at the cursor, with markdown markers stripped, since a text field won't render them. A table keeps one line per row, its cells between bars. - **Copy** copies the answer as written. Long-pressing a bubble copies it too, in one move. - **Write again** throws the newest answer away and asks for another. - **Share** sends the answer to another app as a Markdown file. See [Sharing an answer](#sharing-an-answer). - **Edit**, on your own newest message, pulls it back into the message box along with the answer it got, so you can fix the question and send it again. - A code block gets its own **Copy** and **Insert**, which take just the code, exactly as written. Answers are drawn with their formatting: headings, lists, bold, inline code and code blocks. While an answer is still streaming, a code block waits for its closing fence before it offers its buttons. Maths written in LaTeX is drawn as maths. A formula on its own line, between `$$` signs, inside `\[` and `\]`, in a ` ```math ` block or as a bare `\begin{aligned}`, is set the way a textbook sets it: fractions stacked, roots under their bar, limits over and under a sum, matrices in brackets that grow with them. A wide one scrolls sideways: its cut-off edge fades out and a thin bar under it shows how much more there is, as on a wide table or code block. A formula inside a sentence, between single `$` signs or `\(` and `\)`, sits on the line: `$x^2$` is a small raised 2, and a fraction or a matrix is drawn in place at the size of the text around it. A dollar amount is left alone, so "it costs $5 or $10" stays a sentence. When an answer is inserted into a field, which can't draw any of this, the maths is written out in Unicode instead: `x²`, `√(x+1)`, `(a+b)/c`. ### The message box The message box is one rounded box. What you write is at the top; along its bottom edge sit its controls: the **Reasoning** switch at the start (see [The Reasoning switch](#the-reasoning-switch)), and the mode button, **+** and the send arrow at the end (see [Modes and answer formats](#modes-and-answer-formats)). While an answer streams, the send arrow becomes **Stop**. ### The + menu The **+** button in the message box opens a row of icons over it. Long-press an icon to see its name. - **Pictures from the gallery** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/), or the system photo picker in the Play Store build. Pick several at once. - **A file** opens the same file manager, or the system file picker in the Play Store build, for a text file, a recording, a video or a PDF. See [Sending files](#sending-files). - **Take a photo** opens your camera app, and the photo you take is added. The first time, it asks for the camera permission. - **Copied picture** takes the newest picture in your [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/). - **Newest screenshot** takes the last screenshot on the phone, whenever you took it. The first time, it asks for access to your photos, and the screenshot is added once you come back from allowing it. - **Send the text of the field** quotes what you're working on. With text selected in the field, it takes the selection; otherwise it takes the whole field. A line above the message box says which, and how long it is. The model gets your message followed by that text, clearly marked off, and the bubble notes that text went with it. Tap it again to drop the text. It's greyed out over an empty field, and it never reads a password field. - **Paste** pastes your latest clip at the caret. The photo picker, the file picker and the camera are system screens, so the keyboard steps aside while one is open and picks up where it was when you come back. The file manager, Copied picture and Newest screenshot don't leave the keyboard at all. Up to four pictures and four files can go with one message. They wait above the message box with a cross on each to take it off, and a message can be pictures or files alone. Sent, they show at the top of your bubble, on the keyboard and in the app alike. What the model does with them is the same as in the app: see [Sending pictures](#sending-pictures) and [Sending files](#sending-files). While [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on, or over a password field, the menu offers only the field's text and Paste. Play Store builds leave out **Newest screenshot**, since they don't ask for the photos permission a screenshot needs. An empty chat offers a few openers: **Reply to this** and **Explain this** quote the field for you, **Help me write** starts the sentence. They only fill the message box. Nothing is sent until you send it. ### Asking about another tool's text Wikipedia, Dictionary, Vocabulary and Grammar check can hand their text to the chat. Each has an **Ask AI** chip (on Grammar check, a sparkle on each issue card), and in Wikipedia, Dictionary and Vocabulary a long press on the text selects a passage with **Ask AI** on the bar over it. Either way the AI panel opens in chat mode on a new conversation, with the text attached the way the paperclip attaches the field's, and the line above the message box names where it came from, for example "About Wikipedia: Cat". The keys come up under the message box, and three openers stay on screen while you type: **Explain simply**, **Summarise** and **Hard words**. The text goes with your first message only. The chips show only while the AI tool is on, and not on the lock screen. ### Switching chats The chip in the header with the chat's title opens your list of chats: tap one to open it, **New chat** to start fresh, **Delete** twice to remove one. Swiping a chat to the left shows a red delete button behind it, which removes it in one tap. The pencil on a chat renames it. The name turns into a field with the keys under it; type the new name and press Enter or the tick, or the cross or Back to keep the old one. To clear out several chats at once, long-press one. That starts picking: each tap ticks a chat on or off, and the row above the list shows how many are picked, a **Select all** button, and **Delete**, which deletes them all on its second tap. The cross, or Back, stops picking. The plus button starts a new chat from anywhere. The arrow button opens the current chat in the settings app, which is the place to go when you'd like a full screen or selectable text. ### The Enter key By default Enter starts a new line, and the send button sends. If you'd rather Enter sent the message, turn on Tools / AI writing tools / Enter sends the message. ### When chat isn't offered On the lock screen the panel shows only its actions. Chats are stored encrypted behind your unlock, and a transcript isn't something to draw over a lock screen. A chat started while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on is never saved. You can read it and carry it on until the keyboard's process ends, and then it's gone. ## Chatting in the app Tools / AI writing tools / Chat with the model It sits in a group headed **Chat** on the AI tool's settings screen, under the reasoning and panel options. There's a faster way in too: long-press the app icon on your launcher and pick **AI chat**, which lands you on the conversation list without going through Home. The **AI** screen on the settings home list has a round chat button in its bottom corner that opens the same list. If you'd like that button on every settings screen, turn on AI / Chat button on every screen. On a screen that already has a button of its own, such as the plus that adds something, the chat button sits above it in a smaller size. It stays off the chat screens and the Settings assistant, which are chats already. tools/ai-chat-list — The conversation list, newest first, with the New chat button at the top. The list is your saved conversations, most recently updated first. Each row is the chat's title and how long ago you touched it, with a pencil that renames it and a delete button that asks first and names the chat it's about to remove. Swiping a row to the left shows the same delete button behind it. Long-press a chat to start picking several. Taps then tick chats on and off, and a bar at the bottom shows how many are picked, with **Select all** and **Delete**. Delete asks once for the lot. Back, or the cross on the bar, stops picking. **New chat** is both a button at the top of the list and a plus in the top bar. Nothing saved yet? You skip the empty list entirely and land in a new chat, because an empty list with one button on it isn't worth a screen. A chat is only written down on your first send, so opening one and backing straight out leaves no row behind. ### Naming chats Until it has a better name, a chat is called after the first line of your first message, cut to 48 characters on a word boundary. With Tools / AI writing tools / Auto-rename chats on, the default, the model that answered names the chat once its first answer is in: a few words on what it's about, in the language you wrote your question in, even when the answer came back in another one. It's one more short request to the same model, with your first message and the answer, and it isn't made when [Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) would stop or question a send. If it fails, the first-line name stays. You can also ask the model to rename a chat ("call this chat Trip to Sylhet", "give this a better name"). It proposes a name with its **Rename chat** tool, and a card under the answer asks **Rename this chat to "…"?** with **Accept** and **Decline**. Nothing changes until you accept. On the keyboard, closing the keyboard while the card is up counts as Decline. The tool is offered only while Auto-rename chats is on. Rename a chat with its pencil, on the keyboard or here, to give it a name of your own, up to 80 characters. A name you typed, or one you accepted, is never replaced by an automatic one. ### Sending pictures The **+** button in the message box opens a sheet with **Pictures from the gallery**, **A file**, **Take a photo** and **Paste**. The gallery is the system photo picker, which needs no permission; **Take a photo** asks for the camera the first time. Pick up to four pictures for one message; they wait above the message box, each with a cross to take it off again, and a message can be pictures alone. In a sent message they show above its text. What the model does with them depends on what it can take: - **A cloud or self-hosted model that reads pictures** (Claude, OpenAI, Gemini, Grok, Ollama, LM Studio, any OpenAI-compatible service) is sent the pictures with your newest message. Older pictures aren't sent again with every reply after it; the model remembers what it said about them. - **An on-device GGUF model that reads pictures** ([Qwen 2.5 VL and SmolVLM](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp), or one you imported with its `mmproj` file) is given the pictures of your newest message itself, on the phone. Older pictures stay numbered marks, as below. - **Any other on-device model, or a provider with no place for pictures** (Brave and DeepSeek among them) can't take one. It is told each one is there, numbered, and can use **Read text in a picture** from the [keyboard's own tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools) to read what it says. Either way, with **Search by picture** turned on the model can look a picture up on the web through the [camera tool's](https://wmkeyboard.pages.dev/tools/camera/) search site, and hands you the link to the results. The keyboard's chat has the same menu, plus the clipboard's picture and your newest screenshot: see [The + menu](#the--menu). ### Sending files **A file**, in the **+** menu, opens the [file manager](https://wmkeyboard.pages.dev/tools/files/) on the keyboard and the system file picker in the app or in the Play Store build. Up to four files go with one message, each shown as a chip with its name. - **A text file** (plain text, Markdown, CSV, JSON, source code and the like) is read when you pick it, and its text goes with your message, marked off with the file's name, to any model. Up to 20,000 characters of it are kept; the rest is cut. - **A recording, a video or a PDF** is copied into the chat, up to 14 MB. Gemini is sent the file itself with your newest message. Every other model is told a file of that name and type is there, but can't open it. - **A picture** picked this way joins the message's pictures, as if you'd picked it from the gallery. Anything else, such as a ZIP or an app, is refused with a line saying why. ## The Settings assistant The **Settings assistant** is a chat about the keyboard itself. Open it from the assistant button at the top of the settings search screen, which carries over whatever you had typed into search, or from Tools / AI writing tools / Settings assistant. Describe what you want in your own words, such as "I keep triggering long presses" or "set up a mode for typing codes", or tap one of the three questions an empty conversation suggests. They're drawn at random from a list of common ones each time you open it, and the shuffle button deals three more. It searches the same index the search screen uses and explains the settings it finds, with formatting, lists and links drawn as they are in the chat. It never takes you anywhere on its own. A setting it names in its answer is a link: tap it and a sheet slides up with the row's icon, what it does, the path of screens down to it and, for the settings it can read, the value it has now. A row it wants you to look at gets the same card in the conversation. **Open** on either takes you there, with the row flashing on its screen as a search result does, and back brings you to the conversation where you left it. It only ever names and links rows that exist; a link to a row it never searched for reads as plain text, and a row it makes up is refused. For a question a settings row can't answer, such as which dictionary files Chinese can import, it searches [this documentation](https://wmkeyboard.pages.dev/reference/ai-access/) and answers from the sections it finds, with links to them. It only sends the search words. For a short list of common settings it can also propose a change: autocorrect, suggestions, automatic capitals, the automatic space after punctuation and after a picked suggestion, glide typing, two-thumb typing, the number row, key vibration, key sound, typing speed on the spacebar and the long-press delay. A proposal is a card in the conversation with the setting's current and proposed value, and nothing changes until you press **Apply**. Apply checks the saved setting again first, and if it has changed since the proposal was made, it refuses and asks you to ask again. If the setting can't be saved, the card says so and keeps its **Apply**. Each proposal applies once. **Undo** on an applied card puts the old value back, unless you have changed the setting by hand in the meantime, in which case it leaves your change alone. Anything else it can only open, for you to change yourself. It runs on any model the [AI chat](#picking-the-model) can use: a cloud or self-hosted provider you have set up under AI, or an on-device model you have downloaded. The one answering is shown under the title; tap it to pick another, or to set up a new provider. Like the chat's model chips, that choice is the assistant's alone and leaves the keyboard's own provider as it is, and it lasts until the app is closed. Until you pick, it prefers the keyboard's own provider when that is a cloud or self-hosted one, then the first of those, and only then an on-device model, since a cloud model calls the assistant's tools far more dependably. On an on-device model the assistant searches the settings for your question itself before the model starts, so the model has the matching rows in front of it even if it never asks for a search, and it skips the documentation search. If its answer still names no setting, the closest match is shown as a card anyway. Answers are slower than on a cloud model, since each one is worked out on the phone, but nothing leaves it. Don't want it? Turn off AI / Offer the Settings assistant. The button on the search screen, both of its rows and its search result go, and it sends nothing anywhere. A question it was still answering is stopped, and its answer never arrives. Turn it back on from the same row, which search still finds. Your providers and keys stay as they are. The conversation is kept only while the app is running and is never saved. With a cloud or self-hosted provider, an empty conversation names it and says what goes to it. What it sends to the provider is your messages, its own answers, the setting titles, places and the few values above, and any documentation it looked up; no key, address or other secret is readable through it, and the app lock covers it like the rest of AI. ## Chatting over another app Two ways in open the chat as a sheet over whatever app you're in, with the app still showing above it. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen)'s **Ask AI about this screen** brings the text on screen along when its screen reading is on. **Ask AI** in any app's text selection menu brings the selected text. Either way it waits above the message box, where **View** shows all of it and **Delete** drops it, and the first message you send carries it. The conversation lands in the same list as the rest. If you don't want **Ask AI** in the selection menu, turn off **Ask AI in the selection menu** in the [AI tool's options](https://wmkeyboard.pages.dev/tools/ai/#options). Turning the AI tool itself off takes it out too. Either way **Look up** and the other apps' entries move up into the space it leaves. ## Using it as the phone's assistant Tools / AI writing tools / Use as the phone's assistant WM Keyboard can be the app Android opens for the assistant gesture: a long press on the home or power button, a swipe up from a bottom corner, or a headset's assistant button, whichever your phone uses. Turn on **Use as the phone's assistant** on the AI tool's page, then pick WM Keyboard under **Choose the phone's assistant**, the row that appears under it. That opens Android's own page, usually **Default apps › Digital assistant app**. The switch alone changes nothing; until you pick WM Keyboard there, the phone keeps the assistant it had. It is off by default, and it goes with the AI tool: turn the tool off and WM Keyboard leaves the list. The gesture then opens a short menu at the bottom of the screen, over the app you were in: - **Ask about this screen** opens the chat with the screen's text attached, as the floating button's **Ask AI about this screen** does. - **Explain this screen** and **Translate this screen** do the same with a question already in the message box ("Explain what is on this screen.", or "Translate this into" your phone's language). Change it or send it as it is. - **Ask by voice** listens with your [voice typing](https://wmkeyboard.pages.dev/tools/voice/) engines, then opens the chat with what you said in the message box, and the screen attached when there is one. - **New chat** opens the chat with nothing attached. Every item ends in the same chat sheet as the other ways in, with the AI tool's models, its [tools](https://wmkeyboard.pages.dev/tools/ai/) and its history, so a question that needs a tool (set a timer, add a calendar event, look something up) works the way it does anywhere else in the chat. Nothing is sent until you send it. The three screen items only appear when Android hands the assistant the screen's text, which it does when **Use text from screen** is on in its assistant settings (behind the gear next to **Digital assistant app**). Without it, a line under the menu says so, with a button to that page. Password fields are never read, and at most 8,000 characters come along. Android gives every assistant a speech recognizer of its own and makes it the phone's while the assistant is picked. WM Keyboard's is the one [Voice input for other apps](https://wmkeyboard.pages.dev/tools/voice/#voice-input-for-other-apps) uses, so while WM Keyboard is the assistant, apps that ask Android's speech recognizer are heard by your voice typing engines too. With **Phone's recognizer** as the engine, their requests go on to the recognizer you had before. Turning on the assistant doesn't put WM Keyboard's voice input window in front of other apps' microphone buttons; that is still **Voice input for other apps**' own switch. ## Modes and answer formats A message can ask for more than an answer. Each message goes out in a **mode**, and its answer comes back in a **format**. Both stay picked from one message to the next until you change them, on the keyboard and in the app alike. - **On the keyboard**, the button with the sliders in the message box opens a row of modes and a row of formats over it. While either is something other than plain chat, the button wears the mode's icon and a line beside it names what's picked, so a research run never starts by surprise. - **In the app**, the message box has a **Mode** chip and a **Format** chip next to **Reasoning**; each opens a menu. The chips scroll sideways on a narrow screen. | Mode | What it does | Format it uses on Auto | |---|---|---| | **Chat** | The chat as it always was | Whatever the model writes | | **Deep research** | Plans sub-questions, searches the web, reads pages and writes a report with numbered sources. See [Deep research](#deep-research) | Markdown report | | **Summarize** | Sums up what you give it. Links in your message (up to three) are read first, so every model gets the pages, on-device ones included; quoted text and files are summed up as they are | Bullet points | | **Compare** | Compares the things you name point by point and ends with a verdict when there is one. A model with web search looks up facts that may have changed | Table | The formats are **Auto format**, **Plain text**, **Markdown**, **Bullet points**, **Table**, **JSON** and **Code**. Auto leaves the shape to the mode. Any other format is an instruction added to your message, not to the model's standing prompt, so it applies to that message and the answers that follow it, and it doesn't throw away an on-device model's session. Plain text is the one to pick when the answer is going straight into a field. **Edit** on your own message brings its mode and format back with it, and **Retry** and **Write again** run it the way it first ran. ### Deep research Deep research answers a question from the web the way a research agent does, in steps you can watch. It needs two things: a cloud or server model that can call tools (any provider except **On your device** and Brave), and a [web search provider](https://wmkeyboard.pages.dev/tools/search/) (SearXNG, Tavily or Brave). With either missing, a line above the message box says which, and the send button waits. 1. **Plan.** The model breaks your question into sub-questions, from the general (what it is, why it matters) to the specific (details, numbers, recent changes), and gives each one to three searches. The plan appears as a checklist at the top of the answer. 2. **Gather.** One sub-question at a time, its searches run and the best results are read, no more than two pages from one site. Each search and each page is a step on the chain, as a tool call is, and its sub-question is ticked once it's covered. A page that can't be read, or is a PDF or a video, is skipped. 3. **Look again.** On Standard and Deep, the model reads short notes from the pages so far and either names what is still missing as new searches, which join the checklist, or says it has enough. 4. **Write.** The report streams in: a short direct answer, a section for each sub-question, a conclusion. Every fact carries the number of the source it came from, like `[3]`. The list of **Sources** under the report is added by the keyboard, not written by the model, and holds only the pages the report cites, each a link. A source the report never cited isn't listed. The model doing every step is the one picked in the chat. The plan and the look-again ask it not to reason, to save time; the report follows the **Reasoning** switch. Tools / AI writing tools / Deep research depth sets how far a report goes: | Depth | Sub-questions | Searches in the first round | Looks again | Pages read | |---|---|---|---|---| | Quick | up to 3 | 3 | never | 4 | | Standard (default) | up to 4 | 6 | once | 8 | | Deep | up to 6 | 9 | twice | 14 | Each page is a request to its site, and up to 6,000 characters of it go to the model, with about 48,000 characters of page text in all for the report. Deeper reports take longer and cost more tokens. A Standard report on a fast cloud model takes about half a minute. Turn on Tools / AI writing tools / Review the research plan first to see the plan before anything is searched. A card under the plan asks **Research these questions?** with **Start** and **Cancel**. Cancel ends the run with a **Retry**; rephrase the question if the plan missed the point. With the setting off, the plan runs as soon as it's made, and **Stop** ends a run at its next step, keeping the steps so far. [Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) covers a research run twice: as a cloud AI request and as a web search. When it would hold either, the run is held, and **Retry** is the yes. When data saving is on and allows the run, it reads half as many pages. ### Suggested next questions Under the newest answer from a cloud or server model, up to three chips suggest what you might ask next. A tap puts the question in the message box to send or change; nothing is sent until you do. The model writes them at the end of the same answer, so they cost a few dozen tokens and no extra request, and they're taken out of the answer before it's shown, copied or inserted. On-device models are never asked for them. Turn them off with Tools / AI writing tools / Suggest follow-up questions. ### Sharing an answer The **Share** icon under an answer hands it to another app. In the app it offers a **Markdown file**, a **PDF** or the **Text**; on the keyboard it shares the Markdown file. The file opens with the chat's title and your question, then the answer as written, sources and all. The PDF is laid out on A4 pages, with headings, lists, code and tables set apart. Files are made in the app's cache and shared from there. ## Picking the model A scrolling row of chips above the transcript lists everything you can talk to right now: models you've downloaded on-device first (when **On your device** is on the AI providers list), then every row of [that list](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) that is on and set up. Set up means the thing it needs is filled in: an API key for the cloud services, an address for Ollama or LM Studio, and both an address and a model name for an OpenAI-compatible service. Two rows of one service show their nicknames. With fewer than two to choose from, the row doesn't appear at all. tools/ai-chat-conversation — Model chips over a conversation, with an answer streaming in. Two things worth knowing about that row: - **It doesn't touch the keyboard's own setting.** Chatting to Gemini while the AI tool is set to Claude leaves the tool on Claude. The chip is the chat's choice and nothing else reads it. - **It's frozen while an answer is coming in.** The chips grey out until the answer lands or you stop it, since swapping models halfway through a reply has no sensible meaning. Your pick is remembered for next time, and each conversation also remembers the last model that answered in it. ## Sending, stopping, retrying Type in the box and hit send. The app's message box is laid out like the keyboard's: what you write at the top, with the **Reasoning** switch, **+** and send along its bottom edge. While the answer streams, the send button becomes **Stop**. ### The Reasoning switch **Reasoning**, at the bottom left of the message box, turns a model's reasoning on or off for your next messages. It's one setting for both chats, on by default, and it only shows for a model it does something for: | Model | On | Off | |---|---|---| | Claude | Claude thinks as much as it decides the question needs, and a summary of its thinking shows in the [Reasoning step](#reasoning-and-tool-calls). Models before Claude 4.6 get a thinking budget of half the maximum response length | No thinking. Claude Sonnet 5.5 is set to think only between tool calls, its own way of turning thinking off. Claude Opus 5.5, Fable and Mythos can't stop thinking, so they're set to the lowest effort | | Gemini | Gemini's thoughts come back and show in the Reasoning step | Thinking off on Gemini 2.5 Flash and Flash-Lite. Gemini 2.5 Pro and Gemini 3 can't stop thinking, so they're set to the least they allow | | Ollama | The model's own default, which for a thinking model is to think | No thinking | | OpenAI reasoning models (GPT-5, GPT-6, o-series) | The model's own default | Low reasoning effort, the least every one of them accepts | | On-device reasoning models (Qwen 3 and the like) | The model reasons, as before | `/no_think` is added to your message, which those models read as "answer straight away" | Other providers have no field for it that they all agree on, so the switch isn't there for them, and neither is it for models with no reasoning to switch: Claude 3 models before 3.7, and Gemini 1.5 and 2.0. If a reasoning model spends its whole answer reasoning and writes no reply, usually because it reached the maximum response length, the turn shows as failed with a **Retry**, and what it reasoned is still in its Reasoning step. - **Stop** keeps what arrived. The partial answer stays in the transcript, labelled "Stopped", rather than vanishing as if you'd never asked. A LiteRT-LM model's generation is a native call that can't truly be interrupted. It finishes quietly in the background instead, and the model's own memory of the conversation is kept in step with what you can see. A [GGUF model](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp) stops at once, and remembers exactly the partial answer. - **A failed turn** shows the error in its own bubble with a **Retry** button. Only the newest failed message gets one, because retrying something from the middle of a transcript would answer out of order. Retry re-sends the message the failure was answering, so nothing is duplicated. - **Leaving doesn't cancel anything.** Navigate away mid-answer and the generation carries on. Come back and it's either still streaming or already in the transcript. Answers run the full width of the screen with no bubble around them, and your own messages sit in bubbles on the right, as on the keyboard. Every message has a **Copy** icon under it, and the text itself is selectable, which the keyboard's version can't offer. The newest answer adds **Write again**, and your own newest message adds **Edit**, both exactly as on the keyboard. Answers render the same formatting on both, and a code block has a **Copy** of its own. There's no Insert here, because there's no field behind this screen to insert into. A Play Store build adds a **Report** icon, a flag, under every finished answer. It follows the same route the panel offers under [Reporting a result](https://wmkeyboard.pages.dev/tools/ai/#reporting-a-result): a draft opens in your own mail app holding the provider, the model, the message the answer replied to, and the answer itself, with a blank line at the top for what went wrong. Nothing is uploaded when you tap it, so a report only exists once you send the mail. Your own messages carry no Report button, and neither does a failed turn, because neither one is a generated answer. With no mail app installed, a message says so instead. GitHub and F-Droid builds show no Report button here at all, since Play's AI-generated-content policy is the only reason for it. The keyboard panel's own Report button has no such restriction. ### Reasoning and tool calls What happened before an answer sits over it as a short chain of steps, joined by a line that runs down into the answer. Each step is folded to one line until you tap it. This works the same on the keyboard and in the app. - **Reasoning** is what a reasoning model thought before it answered. While it's thinking the step reads **Reasoning…** with a spinner. Tap it to read the reasoning, drawn with its formatting, maths included, and dimmed so it never reads as the answer. A model that reasons again after each tool call has all of it gathered here. - **A tool call** gets a step of its own, one for each time the model used a tool (see [Letting the model look things up](https://wmkeyboard.pages.dev/tools/ai/#letting-the-model-look-things-up)). Its line says what it did, such as **Searched the web for "…"** or **Reading example.com…**, over the tool's own name, such as `web_search call`. Tap it to see the arguments the model wrote and what came back: for a search or a page, the results by title and address, where a tap opens one in your browser and a long press copies the address; for any other tool, the text the model was handed. A tap on the arguments or the output copies them. A call that failed says why. The steps are saved with the answer, so they're still there when you come back to the chat, and they stay even on a turn that failed or that you stopped. A lookup cut off by **Stop** is marked "Stopped". A call that has to ask first shows its question under its step, open, whether or not the step is. The **Show model reasoning** setting is for the panel's actions; the chat always keeps reasoning, folded. An on-device model gets a live session per conversation, seeded with the saved transcript, so it keeps the context of what you've already said. A GGUF model is given the whole conversation in its own chat template on every turn, and only the new message costs time, because the turns before it are still in its memory. When a conversation outgrows the context, its oldest exchanges are left out first. Switching conversation, model, [compute backend or context size](https://wmkeyboard.pages.dev/tools/ai/#local-models) closes that session and opens a fresh one, seeded the same way. ## Nothing set up yet With no downloaded model and no configured provider, the chat screen says **Nothing to chat with yet** and offers one button back to the AI tool settings. That's the same setup you'd do for the keyboard panel: see [Choosing where it runs](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs). On the F-Droid build the on-device models are the [GGUF ones](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp) only: the LiteRT-LM list needs Google's runtime, which that build leaves out. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're not sure which one you have. ## Memory A chat can remember things about you for the chats after it. When you mention something that will stay true, like where you live or how you like answers, the model saves it as one short fact, and a line under the answer says **Saved to memory** with an **Undo** beside it. Ask it to remember or forget something and it does. Every saved fact is on AI / Memory, where you can edit it or delete it. An on-device model reads and saves memory while it's on. A cloud or self-hosted service only does once you turn on **Use with cloud models** there. A chat started in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) never touches memory. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) for what's saved, what's refused and where it's kept. ## What's kept, and where Conversations live in one file in the app's private storage, in the same folder as the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history). It shows up as **AI chats** on the storage screen under About / Storage, so you can at least see what it costs you. [Your data](https://wmkeyboard.pages.dev/privacy/data/) covers what else is in there. Two rows decide whether that file exists at all. Both sit on the History screen, under Tools / AI writing tools / History, in a group headed **Chats**: - **Keep your conversations** is on by default. Turn it off and chats live only until you close the app, and whatever is already saved is deleted with it. That's the same contract the AI history switch has. - **Delete all chats** clears every saved conversation after a confirmation, without turning the feature off. Three caps keep the file from growing without limit: | What | Cap | What happens past it | |---|---|---| | Conversations | 50 | The one you touched longest ago drops off | | Messages per conversation | 200 | The oldest messages drop off the front | | Characters per message | 20,000 | The rest is cut | Text you attached from a field is saved with its message, under the same 20,000-character cap, because the conversation wouldn't make sense to the model without it on the next turn. Pictures are kept as files next to the conversations, scaled down to 1,280 pixels on the long side first, and are deleted when nothing refers to them any more: when the conversation is deleted, for example. With **Keep your conversations** off they go in the app's cache instead. Recordings, videos and PDFs you sent are kept the same way, unscaled; a text file's text is saved inside its message. Deleting a conversation from the list removes it for good. There's no undo, which is why the dialog names the chat first. ## What leaves your phone Exactly what the AI tool sends, and only when you hit send: - **An on-device model** sends nothing. The whole conversation stays on the phone. - **Any other provider** gets the conversation so far, because that's what makes a reply follow on from the last one. Your saved [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) goes with it only if you've turned on **Use with cloud models**. Pictures you sent go with your newest message, to a provider that takes them. That includes any field text you attached with the paperclip, and only the text you attached: the keyboard's chat never reads the field on its own. A fixed instruction rides along with it: answer in plain text, and keep it as short as the question allows. Your API keys stay on the device. - **Deep research and Summarize** also go to the web. Deep research sends the searches its plan made to your [search provider](https://wmkeyboard.pages.dev/tools/search/), then fetches each page it reads straight from its site, and the pages' text goes to your AI provider with your question. Summarize fetches the links in your message the same way. Nothing goes to a page but an ordinary request for it. - **The assistant's menu** reads the screen's text only when Android sends it, and keeps it on the phone until you send a message that carries it. Its **Ask by voice** hears with your voice typing engine, which keeps the audio on the phone or sends it where that engine sends it. - **The transcript itself** goes nowhere. It's deliberately left out of settings backups, the same as the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history) and for the same reason: your own conversations shouldn't ride along in a file you export and mail to yourself. See [Your data](https://wmkeyboard.pages.dev/privacy/data/). ## Details & edge cases - **Max response length applies here too.** For a cloud or self-hosted model, the AI tool's ceiling bounds a chat reply the same way it bounds a panel result, reasoning-model multiplier included. See [About "Provider maximum"](https://wmkeyboard.pages.dev/tools/ai/#about-provider-maximum). On-device, the **Context size** row is what bounds it, exactly as on the panel. - **The chat prompt isn't one of your actions.** The panel's [action prompts](https://wmkeyboard.pages.dev/tools/ai/#your-own-actions) are for transforming text in a field, which is a different job. Chat has its own fixed prompt and nothing on this screen edits it. - **Nothing here is written to the AI history.** That log records panel runs. A conversation is only in the chat file. - **The launcher shortcut is one of four.** Long-pressing the app icon offers Tools, Languages, AI chat and Themes, and nothing else. The `wmkeyboard://settings/` scheme behind those four reaches three more screens (Typing, Appearance and Search) that no shortcut points at. See [Deep links & launcher shortcuts](https://wmkeyboard.pages.dev/reference/deep-links/). - **Deleting a chat mid-answer is handled.** If a generation finishes for a conversation you've already deleted, the answer is dropped rather than resurrecting the row. - **Reasoning is saved apart from the answer.** It's kept with the message for its folded step, under the same 20,000-character cap, but never sent back to the model on later turns, and **Insert** and **Copy** take the answer alone. - **Tool calls are saved short.** A saved answer keeps each call's arguments and the first 4,000 characters of what came back, the titles and addresses of search results, and why a call failed. A follow-up question doesn't see any of it again: the model only has its own earlier answer to go on. Related: [AI tools](https://wmkeyboard.pages.dev/tools/ai/) covers the providers, the on-device model catalog, the panel's actions, and the history. --- # AI memory URL: https://wmkeyboard.pages.dev/tools/ai-memory/ Section: Tools Summary: Short facts about you that the AI saves in a chat and uses again in later chats and AI actions. You can read, edit and delete every one, and they stay on your phone. AI / Memory Without memory, every chat starts from nothing: you tell the AI where you live, what you do and how you like answers, and the next chat has forgotten all of it. AI memory keeps those facts. When you mention something about yourself that will still be true next week, the model in the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) can save it as one short line, such as "Lives in Dhaka" or "Prefers short answers". Later chats are handed the saved lines, and so are the keyboard's [AI actions](https://wmkeyboard.pages.dev/tools/ai/) and [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/). Every fact is on one screen, where you can read it, change it or delete it. Nothing in memory comes from anywhere else, so that list is everything the AI remembers about you. ## How a fact gets saved The model decides, while it answers. It's told to save only what is about you and will stay true: your name, where you live, your work, your preferences, the people and projects you mention often. It also saves what you ask it to remember ("remember that I'm vegetarian"), and deletes what you ask it to forget. If a saved fact has changed, it rewrites that one instead of adding a second. A save shows under the answer as a line saying **Saved to memory** with the fact, or **Memory updated** when an old fact was reworded. **Undo** on that line takes it back: a new fact is deleted, and a reworded one gets its old words back. The line then says **Taken back**. It works the same on the keyboard's chat and in the app. Saving the same fact twice doesn't make two lines. A fact that says no more than one already saved is left alone, and one that says more ("likes green tea" after "likes tea") replaces the old wording under the same entry. Some things are never kept, whatever the model tries. A line that looks like a password with its value, a PIN, a one-time code, a payment card number, or an API key or token is refused, and the model is told why. So is anything longer than 300 characters, since a memory is one fact and not a note. ## Which models get it AI / Memory / Use with cloud models - **A model on your phone** always gets memory while **Remember things about me** is on. Nothing leaves the phone. - **A cloud or self-hosted service** gets it only when you also turn on **Use with cloud models**, which is off to start with. With it on, the facts picked for a request go to that service with the request, and its chats can save new facts. With it off, a chat with that service neither sees nor saves memory. The facts are the most personal thing the keyboard keeps about you, gathered over many chats. Sending them along with every request is a choice to make yourself, which is why the cloud switch starts off. Memory itself starts on, because until you flip that second switch it only ever reaches a model running on your phone. ## What a chat or an action is handed While you have up to 30 facts, and they're short, every request gets all of them. Past that, each request gets the 20 that fit it best: the ones closest in meaning to what you asked, when [search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) is on and its model is downloaded, or the ones sharing words with it otherwise, then the most recently saved. A chat picks its facts once, on its first message, and keeps them for the rest of the conversation. That keeps an on-device model's session and a service's prompt cache from starting over on every message. A change you make on the memory screen reaches the next message of a chat already open. AI actions and AI reply read memory but never save to it. The text they work on is the field's, which may be someone else's message or a document, so nothing in it is taken as a fact about you. They're also told to use the facts only where the task calls for them, so **Fix grammar** doesn't sign your name to the text. ## Where it doesn't apply - **Incognito.** A field in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), from the setting or from an app asking for it, neither reads nor saves memory. A chat started in incognito is a private one, and it never touches memory either, even after incognito ends. - **Before you unlock.** Memory is kept in storage that only opens once you've unlocked the phone after it started. Until then the AI has no memory at all. - **Password fields.** The AI tool doesn't run in one. ## The memory screen AI / Memory - **Remember things about me** turns memory on or off. Off, no chat or action reads or saves it. The facts you already have stay until you delete them. - **Use with cloud models** is described above. - **Add memory** opens a box for a fact of your own, with an optional category such as "work" or "food". The same checks apply as to the model's saves, so a password is refused here too. - Each saved fact is a row, newest first, with where it came from (**Added by you** or **From a chat**) and when it last changed. Tap a row to edit it. Swipe it left for **Edit** and **Delete**, or use the delete button on the row. - **Delete all memories** clears the list after asking. The [app lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/) can put a fingerprint in front of it, and in front of the whole screen. ## Where it's kept The facts are one file in the app's private storage. They go nowhere unless you send them: - **Backups** carry them only when you tick **AI memory** in what goes in a backup, which is off to start with. See [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/). - **Sync** never carries them. AI memory isn't one of the sections a sync can pick. - **Search by meaning** keeps a small index of the facts next to its others, so it can find the closest ones. **Delete all memories** deletes it too. ## Limits | What | Limit | |---|---| | Facts kept | 500. Past that the model is told memory is full, and a fact you add is refused. | | Length of one fact | 300 characters | | Facts sent whole | Up to 30, and 3,000 characters together | | Facts sent past that | The 20 that fit the request best | --- # AI reply URL: https://wmkeyboard.pages.dev/tools/ai-reply/ Section: Tools Summary: Draft an answer to a message somebody sent you: paste it, type it, or pick a screenshot of the chat, then choose a tone. The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) works on text you have already written. **AI reply** works the other way round: it reads what somebody else sent you and drafts answers for you to pick from. It is for the moments when you know what you want to say and not how to say it, or when you do not know what to say at all. Nothing is ever sent on your behalf. The tool writes drafts; tapping one puts it in the message box, exactly as if you had typed it, and you still press send yourself. ## Getting their message in A keyboard can read the box you are typing in. It cannot read the conversation above it, because those messages belong to the chat app, and no keyboard can reach into them. So the first thing the panel asks for is their message, and there are four ways to hand it over: - **Copy it.** Long-press their message in the chat app and copy it. Anything you copied in the two minutes before opening the panel is already there when it opens, and anything you copy while it's open is added as you copy it. This works whether or not the clipboard keeps a history; without one, only the latest copy is picked up on opening. **Paste copied** adds the most recent copy by hand. - **Screenshot** shows the recent screenshots on your phone as a grid of pictures, newest first, each marked with how long ago it was taken. Tap the one of the chat. - **Type** opens a box on the keyboard. Type or paste the message, press Enter, and it is added. - **Use the selection** works in the few apps that let you select the conversation text itself. With Shizuku or root there is a fifth way, in the F-Droid and GitHub builds only. Turn on Privacy / Permissions / Shizuku or root / AI reply reads the chat and the messages on screen are added as the panel opens, with bubbles on the right marked as yours and the rest as theirs. See [Shizuku or root](https://wmkeyboard.pages.dev/reference/settings/permissions/#shizuku-or-root). ### Several messages at once A reply usually needs more than one line of the conversation. While the reply panel is open, anything you copy is added to it automatically, so you can switch to the chat app, copy three messages one after another, come back, and all three are waiting. Screenshots can be picked up the same way; see below. The gathered messages are drawn like the chat they came from: theirs on the left, yours on the right in the theme's accent colour. Each carries a **Them** or **Me** label; tap the label to flip it. This matters more than it looks: nothing the keyboard can see says who wrote a copied line, so a message of your own that is read as theirs produces a reply to yourself. Tap a message's text to edit it in the box on the keyboard; **Save** puts it back, and saving it empty removes it. Swipe a message sideways, or tap its **×**, to drop it, or tap **Clear** to start over. Between two messages sits a small **Join** button: when one message arrived as two pieces, tap it to make them one again. A message with no real word in it is outlined in red and marked **No real words?**. That is usually what reading a screenshot makes of a sticker, a profile picture or a line cut off at the edge. Only words in Latin letters are judged, against the dictionaries your keyboard has loaded. Messages in other scripts, emoji and numbers are never marked. **Remove unclear** at the front of the row clears them all at once. A screenshot sent as a picture has no **Them** or **Me** label. It holds both sides of the conversation, and the model reads who said what from where each bubble sits. ### Screenshots A screenshot is the fastest way to hand over a whole conversation, because the bubbles' own left-and-right alignment says who said what. How it is read depends on your AI provider, set under Tools / AI reply / How screenshots are read: - **Automatic** (the default) sends the picture to providers that accept pictures (Claude, OpenAI, Gemini, xAI, Ollama, LM Studio and OpenAI-compatible servers) and reads it on the device otherwise. With an [on-device model](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs), Gemma 4 E2B and E4B look at the picture themselves, without it leaving the phone, and so do the GGUF models [Qwen 2.5 VL 3B and SmolVLM 500M](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp). A model you imported is tried the same way (a GGUF one sees only with its `mmproj` file beside it); if it turns out not to read pictures, the screenshot is read on the phone instead and the drafts are written from that, with a note in the panel saying so. - **Send the picture** always sends it. The model reads the alignment itself, so no labelling is needed. - **Read on this device** never sends the picture. The text is read on the phone and arrives as separate messages you can relabel. google: the on-device reader is not in the F-Droid build. Once a screenshot is in the conversation, a **Screenshot goes as** row above the tones switches it between **Picture** and **Text** for this reply. Text reads it on the phone straight away, so you can check and relabel every line. Picture puts the lines read from that screenshot back together into the picture. Picture only shows when the chosen model can take one. Your choice is remembered as the setting above: Picture as **Automatic**, so switching later to a model that can't read pictures falls back to text instead of failing, and Text as **Read on this device**. If you switch to a model that can't read pictures after adding one, the screenshot is read on the phone when you pick a tone. When a screenshot is read on the device, the parts that are not the conversation are left out: the status bar and the chat app's top bar (the contact's name, "Active now"), message times and dates, "Seen" and "Delivered" marks, the message box's own placeholder, and the app's own notices inside the thread, such as "Replied to you", reaction banners, "This message was deleted", "Photo" and "Voice message" labels, call records, unread and encryption notices, and group events. A screenshot taken while the keyboard was on screen has the keyboard in it, and everything from the keyboard's top edge down is skipped. The keyboard remembers where its top edge was at each moment, so a screenshot taken with the plain keyboard up is cut at the plain keyboard's top, even when you pick it later from the taller reply panel. Lines of one bubble that wraps are put back together; when that misses one, use **Join**. The keyboard cannot take a screenshot for you: capturing the screen needs a system permission dialog, and an input method that opens one loses the box it was typing into. Take it the usual way, with the hardware buttons, and pick it with **Screenshot**. To skip that step, turn on Tools / AI reply / Add screenshots automatically. It's off by default. With it on, a screenshot taken while the panel is open is added on its own, and so is the newest one from the two minutes before you open it. This works whatever **Screenshots in the clipboard** under Clipboard is set to, and a screenshot caught for a reply is not added to the clipboard unless that setting is on. It needs the same permission to read your images as the **Screenshot** button. Reading screenshots from the gallery needs permission to read your images. Tapping **Screenshot** without it asks for it straight away, and the grid opens once you allow it. If you said no, the panel shows an **Allow** button to ask again. ## Picking a tone Once there is something to reply to, the tones appear at the bottom of the panel under **Next: pick how to reply**, in one row you can swipe sideways. They stay there however long the conversation gets, and the conversation above them scrolls to the newest message as you add one. One tap writes the drafts: | Tone | What it does | | --- | --- | | **Casual** | A relaxed text, the way friends message each other | | **Quick** | Two to six words, or a single emoji when it fits | | **Keep it going** | Answers, then asks one easy question back so the chat continues | | **Sounds good** | Says yes and confirms any plan, time or place | | **Say no** | Declines clearly and kindly, without repeated apologies | | **Funny** | Playful banter that still answers what they said | | **Excited** | Hyped or congratulating, the way a friend texts back | | **Supportive** | Shows you understood how they feel, without unasked-for advice | | **Apologise** | A short, sincere apology without excuses | | **Polite** | Courteous and professional, for someone you do not know well | | **Flirty** | Warm and lightly flirty | | **Custom…** | Asks what the reply should say, then writes it in that direction | **Custom…** turns the box on the keyboard into an instruction box: type something like "say I'm running late, suggest 8:30" and tap **Write**. Tones are editable, and you can write your own under Tools / AI reply / Tones. Reorder them, turn some off, change what one does, or add a new one with **New tone**. A tone you write can also ask for its instruction each time, the way **Custom…** does. Changing a shipped tone keeps a **Reset** to bring the original back. The drafts take the place of the conversation, and each one appears as soon as the model has finished writing it, rather than all three at the end. Tap one to put it in the message box. ### Starting from your own words If you have already typed something in the message box when you open the panel, such as "cant fri, busy", the drafts say that, in the tone you pick, instead of inventing a different answer. The panel shows **From your draft** above the tones so you know it is being used. The draft you pick replaces what you typed. If you typed more in the meantime, it is added at the cursor instead and your text is left alone. The tone they were written in stays highlighted; tap another under **Try another tone** for a different set, or **Messages** to go back and fix a message first. ## What it writes Under Tools / AI reply: - **Replies per run**: how many alternatives one run writes, up to five. A small on-device model is asked for one, because asked for three it writes the same reply three times. The recommended on-device models, Gemma 4 E2B and E4B, get the full number. - **Length**: short, medium or long. - **Emoji**: match the other person's use of them, allow them, or avoid them entirely. - **About you**: anything the drafts should know about how you write. This is the single setting that most changes whether a reply sounds like you. - **Reply language**: blank follows the conversation, including its script, so Bengali written in Latin letters is answered the same way. - **Conversation sent**: how much of the gathered conversation one run sends. When it will not all fit, the oldest messages go first. The provider, API key and model are the AI tool's own, under Tools / AI. There is only one set of them. When more than one is set up, the panel shows the same **Model** row as the AI tool at the top, with your downloaded on-device models and every cloud provider you have a key for. Picking one there picks it for the AI tool too. The row is hidden while drafts are being written, and turns off with **Model picker on the panel** under Tools / AI. ## Clearing up afterwards Feeding this tool leaves other people's messages in your clipboard history and screenshots of your conversations in your gallery. So when you tap a draft, the copies and screenshots that reply was built from are taken back out again. It is on by default, it is a checkbox in the panel if you want to keep one for a particular reply, and it is a setting under Tools / AI reply / Clear what it used. Clipboard entries are the keyboard's own, so those always go. A screenshot in your gallery is yours: from Android 11 an app may only delete pictures it created itself, so Android shows its own dialog asking whether WM Keyboard may delete it. The reply is already in the message box by then, so answering the dialog loses nothing. On Android 10 and older, or when the dialog can't be shown, the panel says plainly that the screenshot is still in your gallery. ## Privacy - Nothing leaves the device until you tap a tone. Gathering messages, labelling them and reading a screenshot on the device are all local. - Their message is treated as data, never as instructions. A line in a conversation that tells the model to ignore its instructions is answered as part of the conversation, which is what someone sending it would deserve. - The tool refuses to run in a password field. - Drafted replies are not kept in the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history), even when the history is on. A record of a rewrite is your own writing; a record of a reply is somebody else's message. Turn it on under Tools / AI reply / Keep drafted replies in the AI history if you want them kept. - On a metered connection, [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) holds the request the same way it holds any other cloud AI run. - Choosing an on-device model keeps the whole thing on the phone, screenshots included. Gemma 4, Qwen 2.5 VL and SmolVLM read the picture themselves; other on-device models get the text read off it on the phone. - The draft you had started in the message box is sent along with the conversation, since the drafts are written from it. --- # App launcher URL: https://wmkeyboard.pages.dev/tools/app-launcher/ Section: Tools Summary: Open any app, or a specific screen inside one, straight from the keyboard. Full screen, in a floating window, in split screen, or two at once as a pair. The app launcher puts your app drawer inside the keyboard: a searchable grid of every launchable app, your work apps included, sorted into folders if you like, with the ones you pinned and the ones you opened recently in a row on top. Tap an app to jump to it. Hold one for a menu that opens it in a floating window or in split screen instead, pairs it with a second app, or takes you inside it to open a specific screen, like a settings page or a compose view, without going through the app's own navigation first. It's handy mid-conversation: someone asks for a booking reference, you hop to the airline app, copy it, and the keyboard is still where you left it when you come back. tools/app-launcher-grid — The app grid with a pinned row on top and search in the header. ## Opening the panel Open **Apps** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. Typing `apps` or `launch` offers the tool on the suggestion strip too. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press J. ## The grid - **Search** sits in the header. Tap it and the keys come back under a shortened panel, filtering live over app names and package names as you type. - **Split screen pairs**, if you saved any, get their own row on top. See [split screen pairs](#split-screen-pairs) below. - **Folders** come first in the grid, before the apps that are in none. Tap one to open it. See [folders](#folders) below. - **Work apps** sit in the grid with a briefcase on their icon. See [work apps](#work-apps) below. - **Pinned and recent apps** lead the grid in their own row. Pin and unpin from the menu a hold opens, from the grid or from the shortcut row alike. Recents track the last apps you opened from this tool, ten by default and adjustable from 4 to 20, and they never leave your device. - **Tap** launches the app, the way **Open apps** in the options says: normally by default. The panel closes itself, since you are about to leave the app you were typing in anyway. - **Hold** opens the app's menu, described next. - **Hidden apps** stay out of the grid and the shortcut row. Hold an app, open its page from the menu, and tap the eye to hide it. Search still finds hidden apps, shown dimmed, so you can open one or show it again from its page. ## The hold menu Hold any app, in the grid or in the shortcut row, and a small menu opens over the panel. Tap outside it to close it. - **Open**: a normal, full-screen launch. - **Open in a floating window**: opens the app in a floating window the way your phone's maker does it: ColorOS's floating window on OPPO, OnePlus and realme, the pop-up view on Samsung, the small window on Xiaomi and vivo, and so on. See [floating windows](#floating-windows-and-split-screen) for how the keyboard finds the way that works, and what happens on a phone with none. - **Open in split screen**: puts the app beside the one you are typing in. - **Pair in split screen with…**: starts a [pair](#split-screen-pairs). A banner asks for the second app, and your next tap picks it. Tap **Cancel**, or the first app again, to back out. - **Pin app** or **Unpin app**. - **Put on the toolbar** or **Take off the toolbar**: gives the app a button of its own on the keyboard's toolbar. See [Apps on the toolbar](#apps-on-the-toolbar). - **Add to folder**, or **Take out of folder** for an app that is in one. See [folders](#folders). - **Screens, hide and App info…**: the app's page, below. With **Open screens inside apps** off it reads **Hide and App info…**. The two split screen items show wherever the keyboard has a way into split screen: Android 12L (API 32) or later, Huawei's own split screen, the [split screen helper](#split-screen-helper), or [Shizuku](#shizuku-and-root). They hide once the [check](#how-the-keyboard-finds-what-works) has found that none of them works on your phone, and they never show for a work app. ## Apps on the toolbar The apps you open all the time can sit on the toolbar itself, one tap from the keys with no panel to open first. Hold the app in the panel and pick **Put on the toolbar**. Its icon joins the toolbar after your pinned tools and [folders](https://wmkeyboard.pages.dev/tools/overview/), in the panel's icon shape, with its name under it when the toolbar shows names. A tap opens the app the way **Open apps** below says, normally unless you picked a floating window or split screen. Hold the app in the panel again and pick **Take off the toolbar** to remove it. The toolbar takes up to six apps, in the order you put them there, and says so when it's full. An app you uninstall drops off the toolbar on its own. To take every app off at once, use **Apps on the toolbar** in the options below. With **Tools in landscape** standing the tools in a [column down one side](https://wmkeyboard.pages.dev/typing/size-position/#what-landscape-starts-at), the apps follow them there in the same order. ## Folders Folders group apps the way a home screen does: a "Banking" folder for the bank apps, a "Travel" one for the airline and the maps. The grid shows each folder as a tile with up to four of its apps' icons and its name under it, ahead of the loose apps. An app lives in one folder at most. - **Make one**: hold an app, pick **Add to folder**, then **New folder**. The folder opens with its name box up and the keys under it; type a name and press Enter or the check. Leave it empty and the folder keeps the name "Folder". - **Fill it**: hold another app, pick **Add to folder**, and tap the folder. An app already in a folder moves to the new one. - **Take an app out**: hold it, in the folder or in the grid, and pick **Take out of folder**. A folder left with no apps goes away. - **Rename, reorder or remove**: open the folder and use the bar above its apps. Tap the name to type a new one, the arrows move the folder one place earlier or later in the grid, and the bin removes it. Holding a folder's tile offers the same in a menu. Removing a folder puts its apps back in the grid; it never uninstalls or hides anything. Back, on the panel or the system's, leaves a name box, then the folder, then the panel. Search ignores folders: it finds an app wherever you filed it. ### Folders from your home screen The folders on your home screen belong to the home screen app, and Android gives no other app a way to read them, so the keyboard can't copy them over. What it can read is the category each app declares for itself on the Play Store, such as Games, Social or Maps. **Add to folder** → **Sort all apps by category** makes a folder per category, named the way your phone names it in your language, for every app that is in no folder yet. Apps you filed by hand stay where they are, a category joins a folder of the same name if you have one, and a category with a single app gets no folder of its own. Many apps declare no category at all; those stay loose. Categories need Android 8 or later. Folders are kept on this device only. They name apps that are installed here, so they don't travel with [settings sync](https://wmkeyboard.pages.dev/reference/settings/backup/) the way your other choices do; they do go into backups. ## Work apps If your phone has a work profile, its apps show in the grid next to your personal ones, each with a small briefcase on its icon. An app installed in both profiles shows twice, once per profile, and each copy has its own pin, hidden state, folder and recent entry. Tapping a work app opens it in the work profile. When work apps are paused, from the quick settings tile or the work tab of your home screen, the work apps show dimmed. Tapping one asks Android to open it, and Android answers with its own "Turn on work apps?" prompt. Only the home screen app may switch the profile back on directly, so the keyboard leaves that decision to the system. A work app opens normally or in a floating window. Split screen and pairs aren't offered for work apps, because the system call that opens an app in another profile has no way to ask for split screen. A work app's page lists the screens the work profile shows to launchers, and **App info** opens the work profile's own settings page for it. The keyboard needs no extra permission for any of this. Android lets any app list and open the apps of the other profiles of the same person, through the same launcher list the home screen uses. Android 15's private space is the exception: only the default home screen app can see it, so its apps never show here. Turn **Work apps** off in the options to keep the grid to your personal apps. ## An app's page The last item of the hold menu opens the app's page: the app's icon, name and package, a pin toggle, a hide toggle, an **App info** shortcut to the system settings page, and, with **Open screens inside apps** on, every screen (activity) the app declares. Screens the app allows others to open are listed first. Tap one to open it directly. Android blocks the rest for anyone but the app itself, and those stay hidden unless you turn on **Show private screens**. Then they show dimmed with a lock, so you can explore what an app contains without wondering why a tap did nothing. This works without any special permission. The keyboard sees the same launcher list the home screen does, and for those apps it can read the full screen table. ## Floating windows and split screen Every phone maker opens apps floating or side by side its own way, and Android gives no single way that works everywhere. The keyboard carries the ways the main Android skins use, learned from the launchers, docks and split screen apps that already do this, and finds out which one your phone accepts. ### How the keyboard finds what works The first time you open an app floating, or in split screen, the keyboard checks. It opens a small test window of its own, **Checking how this phone opens apps…**, the way it would open your app, and looks at where Android put it: a window smaller than the screen counts as floating, half the screen as split. It goes down the list for your phone until one works, keeps that one, and then opens your app. This takes a second or two, once. After that every floating or split launch goes straight the way that worked. **Check what works on this phone**, in the options, runs the same check on demand and shows the result, for example "Floating: ColorOS floating window. Split screen: Android split screen". It also copies a line with your phone's model, Android version and result to the clipboard, which helps a lot in a bug report from a phone the keyboard hasn't met yet. The keyboard checks again by itself after a system update, or when you turn on the split screen helper, Shizuku or root, since any of those can change the answer. If nothing works, the app opens normally and a short message says so. On a phone where the check found no floating window, the hold menu says "Opens full screen on this phone" under that item. ### What it tries, by phone | Phone | Floating window | Split screen | |---|---|---| | Stock Android 12L and later (Pixel, Nothing, Motorola, LineageOS and others) | Only with [Android's freeform windows](#androids-freeform-windows) on, [Shizuku or root](#shizuku-and-root), or the [emulated window](#emulated-window-experimental) | Android's split screen | | OPPO, OnePlus, realme (ColorOS, OxygenOS, realme UI) | ColorOS's floating window, the same one the Smart Sidebar opens | Android's split screen, then the [split screen helper](#split-screen-helper) | | Samsung (One UI) | Pop-up view | Android's split screen | | Xiaomi, Redmi, POCO (MIUI, HyperOS) | Xiaomi's small window, asked for the way its own sidebar does, then sized for it | Android's split screen, then the helper | | Huawei and Honor | Their floating window | Their own split screen, then Android's | | vivo and iQOO | vivo's small window, then Android's freeform window | Android's split screen, then the helper | | Meizu, Nubia and RedMagic | Their own floating window | Android's split screen | | Chromebooks, Samsung DeX, tablets in desktop mode | A normal resizable window | Android's split screen | Android's split screen means one thing on paper and three in practice. On Android 12L and early 13 the keyboard asks for it directly. From Android 13's first quarterly update, Android ignores that request unless it comes from a screen of an app, and a keyboard has none, so the keyboard opens an invisible screen of its own for a split second, which makes the request and closes. Android sometimes needs the two apps opened in the other order, so the check tries both. ### Split screen helper Some phones still open the app full screen. Those phones may still have split screen behind the **Split screen** item in the Recents menu, and an accessibility service is the only way another app can press that switch. **Split screen helper** is that service. With it on, opening an app in split screen first puts the app you're typing in into split screen, then the app you picked opens in the free half a little over half a second later. A pair waits for its first app to appear, then presses the switch. Before pressing the switch, the service checks where the app windows sit on screen, to see whether split screen is already up, because pressing it then would close split screen instead. While a pair opens it also waits for the first app's window, by the app's name alone. It never reads what is in any window, does nothing at any other time and keeps nothing. Turn it on from the **Split screen helper** row on this page, which explains the service first and then opens Android's accessibility settings, or from [Permissions](https://wmkeyboard.pages.dev/reference/settings/permissions/). Stock Android dropped that switch in 12L, so the helper isn't offered on Pixels and similar phones from 12L on; it is on every other phone, and the check uses it only where your phone still lists the switch. It isn't in the Google Play build. On Android 7 to 12, where a keyboard can't start split screen by itself, turning it on is what brings the split screen options in. ### Shizuku and root [Shizuku](https://shizuku.rikka.app/) is a free app that lets other apps run a few system commands with the rights a computer has over adb, without rooting the phone. With Shizuku running and WM Keyboard allowed in it, the keyboard gains two more ways in: - **Floating** through Android's own window command, sized afterwards to about 70% of the screen. This is the way that works on HyperOS 3, which turns down floating requests from apps. - **Split screen** through the command Android's split screen itself answers to, on Android 14 and later: the keyboard opens the app, brings yours back, and moves the app into the second half. It won't do it while split screen is already up. The **Shizuku** row in the options walks you through it: get Shizuku, start it, then allow WM Keyboard. On a rooted phone, **Use root** does the same through your root manager instead, which asks the first time. Root is never touched until you turn that on. Neither runs anything but those few window commands, and the settings ones below. ### Android's freeform windows Most phones have floating windows built into Android but switched off, behind the developer option **Enable freeform windows**. With it on and the phone restarted, the plain Android floating window works on any skin. The **Android freeform windows** row turns it on (and **force activities to be resizable** with it, so apps that ask for full screen still float). Writing that setting needs a permission only a computer or Shizuku can grant: with Shizuku or root the keyboard grants it to itself; otherwise the row copies the one `adb shell pm grant` command to run from a computer. Either way the phone needs a restart afterwards. On a phone that has freeform windows built in, the row says so and does nothing. ### Emulated window (experimental) Some phones have no floating window at all: stock Android phones, most Motorolas, HyperOS 3. For those, **When a floating window isn't possible** offers a stand-in. **Open normally and say why** is the default. **Emulated window (experimental)** draws the app inside a window of the keyboard's own: the app runs on a second, invisible screen, its picture is shown in a small window you can drag by its title bar, and your touches are passed through to it, the way Mi-FreeForm does it. The title bar has Back, **Open full screen**, which puts the app back on the main screen, and Close. It needs Shizuku, which starts the app on that second screen and passes your touches in, and **Display over other apps**, which the keyboard asks for when you pick it. It is a stand-in and behaves like one: typing inside the window may bring the keyboard up on the main screen or not at all, apps that block screenshots show black, and some apps refuse to open on a second screen. Closing the window puts the app back among your recent apps. ## Split screen pairs A pair is two apps you often use side by side, like a video and your notes, or a chat and a translator. Tap the pair's chip and the two apps open side by side. The first app ends up on top (or on the left), the second below (or on the right). To make one, hold the first app, pick **Pair in split screen with…**, then tap the second app. The chip shows both icons, overlapped, and both names until you give it one. Hold a chip to open it or remove it. Tools / App launcher / Split screen pairs lists every pair. Tap one to name it, and use its buttons to swap its sides, move it up or down the row, or delete it. A pair whose app you uninstalled leaves the row but stays in the list, so it comes back if you reinstall the app. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures) can open a pair too, from a gesture or its quick menu, with **Open an app pair**. Pairs need split screen, so they only show where split screen does. From Android 13's first update the keyboard's invisible screen opens both apps itself, a third of a second apart. Where the keyboard opens them directly, the second app waits about two thirds of a second, and just over a second on Android 16, which refuses a second split request for a second after split screen opens. With the [split screen helper](#split-screen-helper), the keyboard waits for the first app to appear (at most about 2.2 seconds on Android 15 and on Xiaomi, Redmi and POCO phones), then presses the switch, then opens the second app. ## Options Tools / App launcher The screen opens with the two rows every tool has, plus the keyword group that only the tools you can summon by typing carry. The app launcher is one of them: - **Enabled**: which way this starts depends on how you answered the setup wizard. The app launcher is only in the "show me everything" starter set, so the two simpler answers leave it off until you turn it on here. - **Icon colour**: only shown while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) is on globally (it is by default). It overrides this tool's icon colour, a deep purple out of the box. - **Trigger words**: "apps, launch" to begin with, under a **Keyword shortcut** heading. A **Match capitals** toggle sits below it, off by default, and a **Reset to defaults** row turns up once you've edited the words. Then the launcher's own group: - **Open apps**: what a tap on an app does. **Normally** by default, **Floating**, or **Split screen** (where the phone has a way into it). The hold menu offers all of them whatever you pick. - **Split screen helper**: says whether the [split screen helper](#split-screen-helper) is on, and turns it on. Not on stock Android 12L or later, or in the Google Play build. - **Check what works on this phone**: runs the [check](#how-the-keyboard-finds-what-works) and shows how this phone floats and splits apps. Copies the result for a bug report. - **When a floating window isn't possible**: **Open normally and say why** by default, or **Emulated window (experimental)**. See [Emulated window](#emulated-window-experimental). - **Shizuku**: Shizuku's state on this phone, and the next step: get it, start it, or allow WM Keyboard. See [Shizuku and root](#shizuku-and-root). - **Use root**: off by default, and only on a rooted phone. Lets the window commands go through your root manager where Shizuku isn't running. - **Android freeform windows**: turns on Android's own floating windows, which takes a restart. See [Android's freeform windows](#androids-freeform-windows). - **Split screen pairs**: says how many pairs you have and opens their list. Only where split screen works. - **Sort order**: **A to Z** by default, or **Recent first** to lead with the apps you opened most recently. - **App names**: on by default. Off leaves bare icons with no label underneath. - **Apps per row**: **Auto** by default, which fits as many apps as the keyboard width holds. Pick a number from 3 to 8 to fix it. Icons that don't fit their cell shrink to fit. - **Icon size**: 42 dp by default, anywhere from 30 to 60. On Auto, bigger icons also mean fewer apps per row. The pinned and recent row keeps its own size. - **Icon shape**: **Circle** by default, **Rounded square**, or **As on the phone** to keep the shape your home screen gives each icon. - **Recent apps**: on by default. Turning it off stops the tracking and deletes the stored list. - **How many recent apps**: 10 by default, anywhere from 4 to 20. Lowering it drops the extra entries straight away rather than hiding them. Pick a multiple of your grid width and the row fills cleanly with no gap at the end. - **Open screens inside apps**: on by default. Off leaves the app's page as its pin, hide and **App info** actions, without the screen list. The hold menu still reaches the page either way, since hiding an app has no other door. - **Show private screens**: off by default, and only shown while **Open screens inside apps** above is on. There's no screen list to add it to with that switch off. Lists the screens other apps may not open, dimmed. They're debugging fare, and a tap on one does nothing. - **Apps on the toolbar**: says how many apps have a button on the toolbar. Tap it to take all of them off at once. See [Apps on the toolbar](#apps-on-the-toolbar). - **Hidden apps**: says how many apps you hid. Tap it to bring all of them back to the grid at once. One at a time goes through the app's own page, which a search reaches even while the app is hidden. - **Work apps**: on by default. Lists the apps of your work profile too. Off keeps the grid to your personal profile. Without a work profile it changes nothing. - **Open the only match**: off by default. When a search leaves a single app, it opens once you stop typing for a moment, without a tap. A key typed in that moment restarts the wait, so a name you are still spelling doesn't open half-way. It waits while you are choosing the second app of a pair. - **App folders**: says how many folders you have. Tap it to remove all of them at once and put their apps back in the grid. Folders are made and changed in the panel itself. ## Privacy notes - The app list is read from the system on your device and never leaves it. - Recents, pins, hidden apps, folders and split screen pairs are stored as plain package names in the keyboard's own settings, nowhere else. A work app's entry adds the work profile's number to its package name. - The tool is unavailable before the first unlock after a reboot, like every tool that starts other apps. - The check's answer is kept on this device only and never synced: it is a fact about your phone, not a preference. - Through Shizuku or root the keyboard runs only window commands: starting an app in a window mode, resizing or moving its task, reading the task list to find it, and the two freeform settings. The emulated window adds a second screen of the keyboard's own and the touches you make on it. The app launcher is the **App launcher** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool and the split-screen pairs screen leave the keyboard and the app until the bundle is switched back on. --- # Offline voice (Asr4All) URL: https://wmkeyboard.pages.dev/tools/asr4all/ Section: Tools Summary: English dictation on your phone that writes the words while you speak, with punctuation and capitals. Offline Asr4All is a dictation engine that runs entirely on your phone and listens as you speak. Where [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/) record a phrase and transcribe it once you stop, Asr4All decodes the audio while it arrives: the words appear in the field, underlined, and under the microphone as you say them, about a second and a half behind you, and when you stop the phrase is typed almost at once, because nearly all of it is already done. A second pass inside the model adds punctuation and capital letters, and repairs the mistakes a fast first pass makes. Once a model is downloaded, no audio leaves the phone. It is English only. While a layout in another language is in use, dictation uses the system recognizer instead, the same way Whistle hands over its other languages. ## Asr4All, Whistle or Whisper | | Asr4All | Whistle | Whisper | |---|---|---|---| | Languages | English | English, German, French, Spanish, Italian, Dutch, Polish | all 99 of Whisper's | | Words while you speak | yes | no | while it transcribes, on its whisper.cpp models | | Punctuation and capitals | from its own second pass | from the model | from the model | | Download | 38, 72 or 123 MB | 16.9 MB | about 40 MB to 1.5 GB | | Longest phrase | two minutes | 30 seconds | 30 seconds, two minutes on whisper.cpp models | | Words to listen for | no | yes | on whisper.cpp models | | Devices | every phone, every build | 64-bit ARM phones, not the F-Droid build | every phone; the F-Droid build has the whisper.cpp models only | Pick Asr4All for English when you want to see the words as you go and get the phrase the moment you stop. ## Setting it up Voice typing Add **Asr4All** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download a size from the **Asr4All models** group there: - **Small** (38.6 MB): quickest, and the model makers' choice for older and low-power phones. - **Medium** (72.5 MB): more accurate, still comfortable on most phones. - **Large** (123 MB): the most accurate. There is no separate switch for which one dictates: the largest size on the phone does. To go back to a smaller one, delete the larger. Each download comes from Hugging Face at a fixed revision, and both of its files are checked against their published SHA-256 before they count. A size that is part-way down resumes where it stopped. ## How it hears The model is a streaming recogniser. Every 0.64 seconds of audio it commits the next 16 frames of text, each 40 ms long, looking 0.76 seconds further ahead to be sure of them, and carries what it has heard so far into the next step. So the cost is the same for every second you speak, however long the phrase runs, and a phrase can run to two minutes before the countdown under the microphone ends it. The words go into the field as they are recognised, underlined and without punctuation, the way the system recognizer's do, and the line under the microphone shows them too. When you stop, the second pass settles the last few words, and the finished phrase replaces the underlined one with its full stops, commas, question marks and capitals, then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). A key typed in that last moment keeps the words as they stood and drops the second pass. In **Type while you speak** (see [Typing while you dictate](https://wmkeyboard.pages.dev/tools/voice/#typing-while-you-dictate)), nothing goes into the field until the phrase is finished, so there the words are on the line under the microphone only. **Where a phrase ends.** The model has no ear for pauses of its own. With **Stop after a pause** set, the **Phrase end detector** on the Voice typing screen listens for it: by default loudness, the way Whisper's clips end, and Silero instead once ONNX Runtime and Silero's 1.8 MB file are on the phone. FireRedVAD, a model of your own and **Smart Turn** work here too; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase). On a desktop, its output matched the model makers' own reference decoder word for word, punctuation included. Speed and accuracy on a phone are not yet measured by this project. ## Details & edge cases **Not for other languages.** A dictation that listens for several languages (see [Language and engine](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine)) is Asr4All's only when every one of them is English; otherwise it goes to the system recognizer. **No words to listen for.** The model has a way to keep words a biasing decoder put there, but the decoder itself is not published, so **Words to listen for** has nothing to feed and does not show while Asr4All is the engine. **The runtime is built in.** Asr4All runs on [ExecuTorch](https://github.com/pytorch/executorch), built for these models alone, so it adds about 3.8 MB on a 64-bit ARM phone, shared with [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/), where the general-purpose library would add over 10 MB. **The licence.** The model weights come with their own licence, which allows dictation and personal use and forbids surveillance, profiling and similar uses. Its text is in the app's About screen, with the other credits. **Every build.** The runtime is in every build; F-Droid compiles it from source. A build made without it does not offer the engine. --- # Calendar URL: https://wmkeyboard.pages.dev/tools/calendar/ Section: Tools Summary: A month view of your device calendar where you add, change and delete events without leaving the keyboard, with up to two of eight non-Gregorian calendars alongside Gregorian. The Calendar tool opens a full month grid inside the keyboard, or the same month as an agenda down a day rail. Browse any month, and up to two non-Gregorian calendars ride alongside the Gregorian dates over the grid and inside each day cell. Tap a day to see its date across every calendar you've picked, plus whatever's on your device calendar for that day as a row of cards. You can also add an event, change or delete one (repeating ones included), invite guests, and type an event into the app you're writing in, all without leaving the keyboard. tools/calendar-month-grid — The Calendar panel: month grid with Bengali day numbers, today's events as cards below. ## Browsing the month Open **Calendar** from the toolbar, or from the toolbox if you haven't pinned it. The header carries the month and year as its title, a **Today** chip, a **Month | Agenda** switch, a round **+** for a new event and the tool's settings. Swipe sideways across the grid to step a month: the grid slides out and the next one slides in. Tap the title to drop a month picker instead, with the year between two arrows and the twelve months as chips, so a date far away is two taps rather than a dozen swipes. **Today** jumps straight back and reselects today. The weekday row along the top tints your weekend in the accent color, and which days those are is a setting that starts off from your phone's region (see [Options](#options)). Tapping a day cell doesn't leave the panel. It updates the summary and the cards below the grid. The selected day sits on an accent disc that springs across the grid to whichever day you tap next, today's cell has a thin accent ring, and a small dot sits under every day that has something on. On the selected day the dots take the colors of its events' calendars. Press and hold a day to start a new event on it. If either of your two chosen calendars has day numbers that don't line up one-to-one with the Gregorian grid, its day number rides inside the cell as a small second number next to the Gregorian one. Bengali dates render in Bengali numerals (০–৯). The first day of a Chinese lunar month shows the month name instead of a bare "1", since that's the only cue in the grid that a lunar month just turned over. Below the grid, the selected day shows its full Gregorian date, weekday and a relative label (Today, Tomorrow, Yesterday, "in N days," or "N days ago") on one line, and the same date in whichever alternate calendars you've picked under it. ## Today's events The bottom of the panel reads events straight from your device calendar for whichever day is selected. Until you grant calendar access, it shows a one-line prompt with an **Allow** button instead of any event data. Once granted, it queries in the background every time you change the selected day: a blank row while loading, "No events" with a **New event** button if the day is clear, or a row of event cards you swipe through sideways otherwise. The cards pop in one after another each time you pick a day. Each card is tinted with its calendar's color and carries a stripe of it down the left edge (the accent color where the calendar doesn't provide one). The time runs along the top as a start and end, "9:00 – 10:00 AM", with an **All day** badge in its place for all-day events and a small repeat mark on the right for recurring ones; then the title, then the place if the event has one. Recurring events are expanded per occurrence, so a weekly meeting shows up on every day it actually lands on rather than only its first occurrence. Events in calendars you've hidden in your calendar app stay hidden here too. Tap a card to open the event. The event's own face shows it as a larger card in the same color with when and the title, then how it repeats, the place, notes, guests (with who said yes, no or maybe) and reminders as lines under it; the calendar it's in is named in the header. Along the bottom runs a bar: **Edit** as the main button when you can change the event, with **Insert as text**, **Open in calendar app** and **Delete** as round buttons beside it. For an event you can't change, **Insert as text** takes the main spot. tools/calendar-day-events — A day with events: tinted cards, each with its time, title and place. ## Agenda The **Agenda** side of the header's switch shows the same month as a list. A rail down the left names each day that has something on (**Today**, **Tomorrow**, **Yesterday**, or the date), with a dot on the rail per day and the accent dot on today, and the day's events sit beside it as the same tinted cards. The day you've selected is the one whose cards are loaded, with a ring around its dot; a day you haven't opened yet shows a quiet card with its date that loads it when tapped, and once a day has been opened its cards stay while you move around the month. Swiping sideways moves a month here too. A month with nothing on offers **New event** in the middle, and until calendar access is granted the agenda shows the **Allow** prompt in its place. ## Adding an event Tap the round **+** in the header, or press and hold a day in the grid. The composer opens on the selected day with the title already taking your typing, and the panel folds down so the keys show underneath; while you type, the folded panel shows the event's preview card so you keep your bearings. **Done** (or Enter) moves you from the title to the place. The composer is a preview card over a tray of chips. The card is the event as it will look in the month, tinted with its calendar's color: when it is, its title (or **Title** as a hint until you type one), its place, and a line in the accent color when it overlaps something already in your calendar ("Overlaps Standup"). It updates as you change anything. A tap on the card sends the keys back to the title. Under it, each piece of the event is a chip that reads what it holds, or its hint while empty: the when, **Add a place**, **Add guest** (or how many guests), how it repeats, the reminders, which calendar (with its color dot), **Add notes**, and **Busy** or **Free**. Tapping a text chip sends the keys to it. Tapping any other unfolds its editor under the tray, one at a time, and the open chip is drawn selected: - **When** holds **All day**, which turns the times off so the start and end name whole days; **Starts** and **Ends**, each with a date chip that opens a small month and a time chip that opens the same spinning drums the Clock tool uses; and the length chips (15 min up to 3 h), which set the end for you. Moving the start moves the end with it, so the length stays the same. If the end lands before the start, the **Ends** label and the card's time turn the accent color and the main button refuses until it's fixed. When the event's time zone isn't the phone's, a chip names the zone the times are in. - **Busy** or **Free** flips on a tap. - **Repeat**, **Reminders**, the calendar and guests each have their own editor; see below. A new event starts with the length and reminder you set in [Options](#options), in your default calendar. The bar along the bottom holds **Add to calendar** as the main button (**Save changes** when you're editing), with **Insert as text** and **Open in calendar app** as round buttons beside it. The event goes straight into your calendar, the panel goes back to the month with that day selected, and an **Undo** bar takes it back out again if you change your mind. **Cancel** in the header throws the draft away. If you close the panel halfway through, the draft is kept. Open the tool again and a **Resume** line at the top brings it back. ### Reminders The reminders chip lists what's set, or **No reminder** while there are none. Tapping it unfolds the choices: at the start, 5, 10, 15 or 30 minutes, 1 or 2 hours, 1, 2 or 7 days before, with the ones already set drawn selected. A tap adds or removes one. Some calendar accounts limit how many reminders an event can carry, and the remaining choices grey out once you reach that limit. ### Repeating events The repeat chip unfolds a set of chips worded for the event's own day: daily, weekly on that weekday, every weekday (Monday to Friday), monthly on that date, monthly on its weekday ("the third Monday"), monthly on the last such weekday when it falls in the last week, and yearly. **More options** opens the rest: repeat every 2 weeks or every 3 months, pick several weekdays, and end it never, on a date, or after a number of times. Move a weekly event to another day and its repeat day moves too, so a Monday meeting dragged to Tuesday keeps landing on Tuesdays. A repeat rule written by another app that this editor can't show (several dates a month, say) appears as **Custom repeat** and is saved back exactly as it was unless you pick a different rule. ### Guests The guests chip unfolds the guest list and **Add guest**, which opens a box for a name or an email address. With contacts allowed, typing a name lists the matching people with their addresses, and a tap adds one. Without contacts, type the whole address and press Enter. Each guest is a chip with a cross to take them off again. Guests are written to the event the same way your calendar app writes them, which means your calendar account (Google Calendar, for one) may email them an invitation when it next syncs. The composer says so under the guest list. A calendar that only lives on your phone sends nothing. ### Insert as text The **Insert as text** button types the event into the app you're writing in, as one line: the title, the date and time in your own format, and the place, like "Standup, Mon 19 Oct, 9:00 – 10:00 AM · Room 4". It's on the bar of the event's face and on the composer's, so you can share a plan before or after saving it. ## Changing and deleting events Open an event and tap **Edit**. The composer opens with everything filled in, including its guests, reminders and repeat rule, and its main button reads **Save changes**, which writes them back to the same event. **Delete** on the event's bar removes it, with **Undo** afterwards. Events you can't change show a note instead of Edit and Delete: one you were invited to by someone else (unless they let guests edit), or one in a read-only calendar such as holidays or a shared calendar you can only view. ### Repeating events Changing or deleting one occurrence of a repeating event first asks which ones you mean: - **This event** changes or removes just that day. The rest of the series is left alone. - **This and following** ends the series the day before and, for a change, starts a new series from that day with your edits. A series that was set to run a number of times keeps the right total between the two halves. - **All events** changes the whole series. A new time or a new day moves the series' first occurrence by the same amount, so a series that began in March still begins in March. If you changed the repeat rule itself, **This event** isn't offered, since a single day can't repeat. **Undo** can put back a single changed or removed day, a series that was cut short, and a deleted event. ## Calendar permission The keyboard asks for calendar access in two steps, and never at install or first launch. Reading comes first: tap **Allow** in the panel and you can see your events. Writing comes the first time you save or delete an event, and is asked for together with reading. Your event waits while the dialog is up, and is saved as soon as you come back with the grant. Refuse it and the composer says so and offers **Open in calendar app** instead, which hands the event to your calendar app's own editor to save there. An on-screen keyboard service can't request runtime permissions directly. So each of these opens an invisible trampoline activity that shows a short explanation of what the access is for, then the system permission dialog, then closes itself the instant you answer. The [camera tool](https://wmkeyboard.pages.dev/tools/camera/) uses the same pattern for its own permission. If you revoke calendar access in system Settings and come back to the keyboard, the panel notices as soon as it regains focus and drops back to the Allow prompt. Guest suggestions ask for contacts access the same way, from the **Suggest from contacts** link under the guest box. The tool reads names and addresses only while that box is open and keeps nothing afterwards. The tool writes only what you save, change or delete yourself. Nothing is changed in the background. Calendar is left out of the keyboard's direct-boot-safe tool set. It has to query a content provider that isn't available before your first unlock after a restart. Open the toolbox on a still-locked device and Calendar isn't listed there at all, so there's no broken panel to walk into. The arithmetic and sensor tools (the calculator, the unit converter, the compass, the level, the moon phase) are still there, but anything that reads the calendar provider is not. ## Options Tools / Calendar Like every tool, Calendar opens with a **Turn on** switch and, while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are on globally, an **Icon colour** override (a soft red by default). Under those sits the **Keyword shortcut** group: **Trigger words**, pre-filled with "calendar, schedule", a **Match capitals** toggle off by default, and a **Reset to defaults** row once you've edited the words. Then the tool's own settings: tools/calendar-options — Calendar options: First and second calendar pickers, and the Hijri day adjustment slider. - **First calendar**: the default depends on your phone's region. That's the Bengali calendar in Bangladesh, era years in Japan, Hindu (Saka) in India, Hijri across much of the Arab world, and **None** where no second calendar is in common use. Choose from Bengali, Hijri, Chinese, Hebrew, Hindu (Saka), Persian (Solar Hijri), Buddhist (Thai solar), Japanese (era years), or None. Its day number is the one that also shows up inside each day cell. Buddhist and Japanese days line up one-to-one with Gregorian, so if you pick either the cell falls back to Second calendar's day number instead, where that one has its own. - **Second calendar**: the same nine choices, also region-defaulted. Bangladesh gets Hijri alongside the Bengali calendar, Japan gets the Chinese calendar alongside era years, and most places get **None**. It shows in the month header and under the selected day, alongside First calendar. - **Weekend**: which weekday initials the month grid tints. Seven presets: Saturday & Sunday, Friday & Saturday, Thursday & Friday, Friday, Saturday, Sunday, or no weekend at all. The default comes from your phone's region: Friday and Saturday across much of the Middle East, Friday in Bangladesh, Thursday and Friday in Iran and Afghanistan, Saturday in Nepal, Sunday alone in India, Mexico and the Philippines, and Saturday and Sunday everywhere else. - **Hijri day adjustment**: a slider from −2 to +2 days, default 0. It only appears when First or Second calendar is set to Hijri. The tool computes Hijri dates with the tabular (arithmetic) calendar rather than real moon sighting, so real Islamic months can start a day or two off from the tables. Use this to match your local authority's sighting-based calendar. Under **New events**: - **Save events**: **In the keyboard** (the default) writes new events straight into your calendar. **In the calendar app** makes Save open your calendar app's editor with the event filled in, for you to save there; it also makes the "Add to calendar" [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/) go to the calendar app. Changing and deleting events always happens in the keyboard. - **Default calendar**: which calendar a new event starts in. **Primary calendar** picks the first one you can write to, primary calendars first. Until calendar access is allowed, the row offers to ask for it. - **Event length**: 15 minutes up to 4 hours, default 1 hour. - **Reminder**: none, at the start, or 5 minutes up to 1 day before, default 10 minutes. Accuracy varies by calendar: Chinese dates are computed astronomically (real new-moon and solar-longitude calculations), so they're exact for the modern era. Hebrew, Persian, Hindu (Saka), Buddhist, and Japanese are all exact arithmetic conversions. Hijri is the only one that's an approximation, which is why it's the only one with an adjustment slider. The same First calendar / Second calendar / Weekend pickers also appear on the first-run setup page during onboarding, if you've kept Calendar enabled: anything you set there is the same setting, just asked up front. All three already open on the region's guess, so that page is where you see it and correct it. The calendar and the clock are the **Calendar and clock** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools and the clock's settings screen leave the keyboard and the app until the bundle is switched back on. ## Details & edge cases - Insert as text types an event in Gregorian dates only. The alternate calendars are shown in the panel but not inserted. - Selecting a date or time in your text and using **Add to calendar** from the [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) opens this composer with the selection as the title and the date and time already set. A time zone named in the selection ("5pm EST") is kept, and the composer shows which zone the times are in. - Opened from a date [smart chip](https://wmkeyboard.pages.dev/smart/chips/) ("next friday"), the panel lands on that day, and the header's **+** starts a new event there. - The month grid, the agenda and the composer play their motion (the disc's spring, the cards popping in, the grid's slide) only while the keyboard's animations are on; with reduced motion everything lands in place at once. - Calendar isn't pinned to the toolbar out of the box, and it's only in the "show me everything" starter set that setup lands, not the minimal or the recommended one. Pick either of those two simpler answers and Calendar starts switched off, so you'll want to turn it on yourself under Settings → Tools. - Reopening Calendar from the toolbar while the panel is already open closes it, same as most other panel tools. - **The region defaults are a guess, and only ever a starting one.** They come from the same permission-free signals as [suggested languages](https://wmkeyboard.pages.dev/languages/overview/#suggested-languages): the SIM's country, then the network's, then your locale's region. Those are consulted only until you pick something. Once you have, your pick is what sticks, wherever the phone travels. - Nothing about the Calendar tool touches the network. Your calendar account syncs the events you add or change the way it always does. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what the keyboard itself sends. --- # Camera tool URL: https://wmkeyboard.pages.dev/tools/camera/ Section: Tools Summary: A viewfinder inside the keyboard for quick photo capture and insertion. The Camera tool swaps the key rows for a live viewfinder, so you can snap a photo and send it into whatever you're typing in without ever leaving the keyboard. all: it's built on plain CameraX, not ML Kit, so it ships in every build. ## Taking a photo Open **Camera** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. Camera isn't in the toolbar's default pinned set on a phone, where only Emoji, Clipboard, and Settings are. So the first time, you'll probably reach it through the toolbox or by typing its trigger keyword. See [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/). The viewfinder fills the width of the panel and behaves like any camera app: - **Tap anywhere to focus.** A tap starts focus-and-metering at that point. - **Pinch to zoom.** A zoom pill ("1.2x" style) appears top-center once you're past 1.05x, and the pinch is clamped to whatever range the current lens supports. - **Flash**: a chip on the bottom-left, shown only if the active lens reports a flash unit. Tapping it cycles Off → Auto → On. - **Self-timer**: the next chip over, cycling Off → 3s → 10s → Off. Once it's running, a large countdown digit appears at the center of the frame, with a feedback tick each second, before the shutter fires on its own. - **Front/back switch**: bottom-right, and only shown if the device has both a front and a back camera. [Start with the selfie camera](#options) below decides which one the tool opens on. - **Pick a photo**: the picture button beside it opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/), or Android's photo picker in the Play Store build, and the photo you choose lands on the confirm step as if you had just taken it. It's there while the confirm step has something to do with a photo besides sending it: a server under [Translate photos](#translating-a-photo), the [Search button](#search-by-photo-options), or a camera opened from web search. - **Shutter**: the button in the middle. Tap it to take the photo, or let the self-timer run out. ### Scanner modes The camera is also the way into the [scanners](https://wmkeyboard.pages.dev/tools/scanner/). A rail of round buttons down the right edge of the viewfinder switches between **Photo**, **QR and barcode scanner**, **Text scan (OCR)** and **Document scanner**, with the mode you're in lit. The same rail is on the QR scanner and on Text scan, so from any of them you reach the others in one tap. Document starts the document scanner, which opens over the keyboard as it does from its own tool. That means one Camera button on the toolbar is enough; the scanners can stay in the toolbox. The separate tools still work. Each one opens the camera straight in its own mode, so a QR button you've pinned keeps doing what it did. A scanner you've turned off under [Tools](https://wmkeyboard.pages.dev/reference/settings/tools/) leaves the rail too. The rail isn't shown on a camera opened from web search or on a clip's picture, where there's only one thing to do. Turn it off with **Switch modes in the camera** under the options below. **Spot QR codes in photo mode** makes the camera behave like a phone's own camera app. Point it at a QR code or a barcode in photo mode and a chip appears at the top of the viewfinder with what the code says. Tap the chip and the camera switches to the QR mode, which reads it with all its usual buttons. Taking a photo works the same as ever meanwhile. It's off by default, because reading frames uses more battery, and it needs the QR scanner's reader on the phone; where that's still a download, the QR mode offers it. ### What you see is what you get There's no manual crop step, no drag handles, and no filters, whatever the word "crop" suggests elsewhere. The viewfinder is taller than the visible panel, because it spills upward to fill the panel's width at the sensor's aspect ratio. The moment you tap the shutter, WM Keyboard slices the raw capture down to exactly the region that was on screen. What you framed is what ends up in the photo. If you'd rather keep everything the sensor saw, turn on **Send the whole photo** in [Options](#options). The photo is then the full 4:3 frame, top and bottom strips included, and the confirm step fits all of it inside the panel instead of drawing it where the viewfinder had it. The capture itself stays modest. Photos are capped at the longest edge **Photo size** names, which is 1000, 1600 (the default), 2400 or 3200 px, rather than the sensor's full resolution. A photo headed straight into a chat message doesn't need 12+ megapixels, and a smaller capture processes faster. Each shot is written to the app's private storage as a JPEG before the confirm step appears. ## Confirm, retake, or send After the shutter fires, the frozen photo is drawn at the exact spot and size the live preview had it. The confirm screen looks identical to the moment you took the shot, apart from a **Retake** and **Send** button pair underneath. - **Retake** discards the file and goes straight back to a live viewfinder. - **Send** hands the photo off (see [Where the photo goes](#where-the-photo-goes) below) and closes the panel back to your keys. - **Search** is a third button, off by default. Turn on **Search button** under [Search by photo](#search-by-photo-options) and it sits after Send. It looks up what is in the photo instead of sending it. See [Searching with a photo](#searching-with-a-photo) below. - **Translate** appears once a provider is set up under [Translate photos](#translating-a-photo). It replaces the photo with a copy whose text is translated. If you took the photo with the front camera and [Mirror selfies](#options) is on, the saved file is flipped horizontally to match what you saw while framing. Camera sensors record selfies un-mirrored by default: text reads the right way round, but the shot looks flipped against the live preview. Shutter sound and haptic feedback on the controls, timer ticks, and shutter are each their own setting. See [Options](#options). ## Translating a photo Pointed the camera at a menu or a sign in another language? Add a [Rens](https://git.maid.zone/stuff/rens) server under Tools / Camera / Translate photos, and the confirm step gets a **Translate** button. Rens is a free front end to Google Lens: your own server, or [a public one](https://git.maid.zone/stuff/rens#instances). - **What it does.** The photo goes to the server, which has Google Lens translate the text in it, and comes back as a new picture with the translated text drawn over the original. It replaces the photo on screen, so **Send** sends the translated one. **Retake** starts again. - **Keeping it without sending.** A translated photo has two round buttons at the top right: **Copy** puts the picture on the clipboard, and **Save to the gallery** writes it to `Pictures/WM Keyboard`, whether or not [Save to the gallery](#options) is on. - **A photo you already have.** Tap **Pick a photo** in the viewfinder and choose one, then **Translate**. A picture you copied is quicker still: press and hold it in the [clipboard panel](https://wmkeyboard.pages.dev/tools/clipboard/) and tap **Translate**. The camera opens on that picture and translates it at once, without needing the camera permission, and its back button returns to the clipboard. - **Only some of the text.** Lens translates every word it finds. To translate one line, read the photo with [Text scan](https://wmkeyboard.pages.dev/tools/scanner/#scanning-text-ocr) instead, keep the words you want, and tap **Translate** there. - **Which language.** The language the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool translates into. Lens detects the language of the photo. - **More than one server.** **Translate photos** is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): the next server is asked when one fails. With **How to use them** on **Choose in the panel**, a chip at the top right of the photo picks the server. - **Privacy.** Rens strips the photo's metadata and renames it before it passes the photo on, so Google sees the server and not your phone. The server still sees the photo. - **Data saver.** It counts as a web search. On a connection where data saver asks first, the first press of Translate says so and the second sends the photo. Each translation shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Search by photo**. ## Putting the place on a photo With the [Location tool](https://wmkeyboard.pages.dev/tools/location/) on, the viewfinder's control rail carries a pin next to the timer. Tap it and the photo is stamped: a band along the bottom with the place on one line and the coordinates, the time and the Plus code under it, white on a dark fade. A line over the viewfinder shows what the band will say before you shoot, or that the keyboard is still finding you. The stamp is in the picture itself, so every chat app shows it, and so do screenshots and forwards. The pin remembers whether it was on. The stamp follows the Location tool's settings: its [precision](https://wmkeyboard.pages.dev/tools/location/#precision), so a city-level stamp says only the city, and its address lookup. The Location panel's camera button opens the camera with the stamp on for that one photo. The confirm step has **Add the place as text** above Retake and Send. On, Send types the place into the box first, in the Location tool's default format, and then sends the photo. Many chat apps take the text in the box as the photo's caption; others send it as a message of its own. That switch is remembered too. Either one asks Android for location access the first time, the same way the Location tool does. Nothing is read while both are off. ## Where the photo goes Tapping **Send** inserts the photo the same way clipboard images, GIFs, and stickers do: through Android's `commitContent` API, matched against whatever image types the field you're typing into says it accepts. If the field doesn't advertise support for any compatible image type, the photo is copied to the system clipboard instead, with a toast: > This app does not accept images here. The keyboard copied the image. Paste it instead. Either way, the send closes the panel and hands you back the keys. ## Searching with a photo Saw a plant or an insect and want to know what it is? **Search** looks up what's in the photo: similar pictures, the name of the thing, and pages about it. There are two ways to get the button: - **From web search.** The [Web search](https://wmkeyboard.pages.dev/tools/search/#web-and-image-search) panel has a camera button beside its search bar. It opens this camera, and after the shutter the confirm step offers **Retake** and **Search**, with no Send. Back returns you to the search panel. This works whatever the setting below says. - **On the camera itself.** Turn on **Search button** under [Search by photo](#search-by-photo-options), and every photo you take gets Search next to Retake and Send. Where Search takes the photo is up to **Search with**: - **Google Lens**, the default. The photo opens in Lens, inside the Google app, which shows its matches right away. If the Google app isn't installed, which is the case on most phones without Google's apps, the share sheet opens instead. - **Web**. The keyboard sends the photo to the search site you pick under **Search site** and opens that site's results page in your browser. You don't need the Google app for this. The sites are **Google Lens** (on the web), **Bing**, **Yandex**, **TinEye**, and **Custom**. Before the upload, the photo is scaled down to at most 1280 px on its longest side, which is about what the sites work at anyway. The keyboard shows "Sending the photo to the search site" while it uploads. - **Share sheet**. Android's own share sheet opens every time, so you pick the app: Lens, a plant identifier, a shopping app, anything that takes an image. The panel closes once the search is on its way. If it can't start, the photo stays on screen so you can try again or send it after all. That covers a failed upload, no app to take it, and a data saver block. None of the search sites has a public way to upload a photo. **Web** does what each site's own "search by image" button does, so a site can stop working when it changes how that works. You'll then see "The search site did not send a results page", and picking a different site is the fix. **Custom** is for a server of your own, or any service you'd rather use. Two more fields appear when you pick it: - **Upload address**: where the keyboard posts the photo, as multipart form data. - **Photo field name**: the form field the photo goes in. `image` by default. The server answers with a redirect to its results page, with the results address as the whole reply, or with JSON like `{"url": "…"}`. A relative address counts from the upload address. The keyboard opens only `http` and `https` addresses, whatever the server sends back. Only **Web** uses the keyboard's own network connection, so it's the only route that answers to [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/): it counts as a web search. On a connection where data saver asks first, the first press of Search shows "This connection is metered. To send the photo, press Search again." and the second press sends it. Each upload appears in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Search by photo**. Lens and the share sheet give the photo to another app, and the keyboard itself sends nothing. By default that's the *only* copy that persists anywhere. The private file used to hand the image off is scratch storage, not a saved photo. On each panel open, anything beyond the four most recent capture files is deleted, which only matters if the app was killed mid-confirm. Want a permanent copy? Turn on **Save to the gallery**, off by default, and every photo you send or search with is also written to `Pictures/WM Keyboard` in your device's shared gallery, once, even if you search with it and then send it. See [your data on device](https://wmkeyboard.pages.dev/privacy/data/) for how WM Keyboard's on-device storage is organized more generally. Capture, crop, save, and send are all local. The camera tool uses the network only for a search with **Search with** set to **Web** (see [Searching with a photo](#searching-with-a-photo)) and for **Translate** (see [Translating a photo](#translating-a-photo)). [Network policy](https://wmkeyboard.pages.dev/privacy/network/) has the wider picture. ## Granting camera access tools/camera-permission-gate — The permission gate shown before camera access is granted, with the Allow camera pill. The first time you open Camera, you'll likely see a short message instead of a live feed: > The camera tool needs permission to use the camera. The app takes a photo only when you press the shutter. Tapping **Allow camera** starts a small trampoline activity that exists for one reason. An IME service cannot show Android's runtime permission dialog itself, so this activity comes up instead. It shows a disclosure first, headed "Camera access for the camera tool", spelling out that the camera only runs while the tool is on screen and that no photo is uploaded anywhere. **Continue** hands you Android's own permission prompt. **Not now** leaves without asking for anything. Either way the activity closes the moment you answer, and the keyboard re-checks the permission as soon as it regains focus. You don't need to reopen the panel. This same trampoline is shared by the [OCR and QR scanner tools](https://wmkeyboard.pages.dev/tools/scanner/): granting camera access from any one of the three covers all three, since they all request the same `android.permission.CAMERA`. Android stops showing its own prompt once you've denied camera access twice ("Don't ask again"). The disclosure still appears if that has happened, with a line saying so, and its button reads **Open system settings** instead of Continue. That takes you to WM Keyboard's permission page, which is the only place left to grant it. On a device with no camera at all, the panel shows "Your device has no camera." in place of the viewfinder. WM Keyboard installs and runs fine either way. Nothing about the app requires a camera. ## Options Tools / Camera tools/camera-options — Camera's Options screen: selfie camera, mirroring, gallery saving, shutter sound and haptics. Like every tool on the [full tool list](https://wmkeyboard.pages.dev/reference/settings/tools/), this screen opens with a **Turn on** switch. An **Icon colour** override follows it, but only while [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) are on globally. It's pink by default, and it becomes two rows instead of one if gradient tool colours are on as well. Under those sits the **Keyword shortcut** group, which every tool that ships a built-in keyword gets: - **Trigger words**: a text field pre-filled with "camera". Type that word on its own and the suggestion strip offers to open the tool, dropping the word (see [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/)). Commas separate more than one word. Clear the field and the tool stops offering itself this way. - **Match capitals**: off by default, so "camera", "Camera" and "CAMERA" all match. Turn it on and only the exact spelling in the field above counts. That's worth doing for a short trigger word that's also ordinary text. - **Reset to defaults**: only appears once you've edited the trigger words, and puts them back to "camera". Its subtitle shows what you'd be going back to. The tool's own settings, in these groups: **Options** - **Start with the selfie camera**: off by default. Opens the panel on the front camera instead of the back one. - **Self-timer**: Off by default. The timer the panel opens on, so a habitual 3s user doesn't re-pick it every time. The in-panel chip still steps Off → 3s → 10s. - **Photo size**: 1600 px by default, from a choice of 1000, 1600, 2400 and 3200. It's the longest edge of the photos you take, and the camera picks the closest size its sensor offers. Larger photos hold more detail, which matters for a picture of a document somebody has to read, and they take longer to capture and to send. - **Mirror selfies**: on by default. See [Confirm, retake, or send](#confirm-retake-or-send) above for what this does and why it exists. - **Send the whole photo**: off by default. It skips the crop described in [What you see is what you get](#what-you-see-is-what-you-get) and sends the full 4:3 frame, including the strips that ran off the top and bottom of the screen. The live viewfinder is unchanged, so this is the one case where the photo holds more than you framed. The confirm step fits the whole picture inside the panel so you can see what you're about to send. - **Save to the gallery**: off by default. Also copies every capture into `Pictures/WM Keyboard`, on top of sending it. **Location** - **Stamp the place on photos**: off by default. The same switch as the pin on the viewfinder. See [Putting the place on a photo](#putting-the-place-on-a-photo). - **Add the place as text**: off by default. The same switch as the one on the confirm step. **Search by photo** - **Search button**: off by default. Adds **Search** after Retake and Send on the confirm step. The camera button in web search offers Search whatever this says. - **Search with**: **Google Lens** by default, then **Web** and **Share sheet**. See [Searching with a photo](#searching-with-a-photo) for what each does. - **Search site**: shown only while Search with is **Web**. **Google Lens** by default, then **Bing**, **Yandex**, **TinEye** and **Custom**. - **Upload address** and **Photo field name**: shown only for **Custom**. Where your server takes the photo, and the form field it reads. **Translate photos** - The servers that translate a photo for the **Translate** button. Empty by default, which hides the button. See [Translating a photo](#translating-a-photo). **Feedback** - **Shutter sound**: on by default. Plays the camera click when a photo is taken. - Vibration on the shutter, the controls and the timer countdown follows the keyboard's own haptics setting under [Key press › Haptics and sound](https://wmkeyboard.pages.dev/typing/feedback/). There is no separate switch for the panel. **Scanner modes** - **Switch modes in the camera**: on by default. The rail of Photo, QR code, Text and Document over the camera and the scanners. See [Scanner modes](#scanner-modes). - **Spot QR codes in photo mode**: off by default. A chip over the viewfinder when the camera sees a code; tap it to read the code. The camera and the gallery are the **Camera and gallery** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on. ## Details & edge cases - **No manual crop, no filters, no multi-shot.** One photo per open of the panel. Retaking replaces rather than appends. - **Search isn't an in-keyboard result list.** Every search route ends in another app or in your browser, which is where the results are worth reading. Nothing comes back into the field you're typing in. - **Not pinned or recommended by default.** Camera ships enabled, but it isn't in the toolbar's default pinned set and isn't part of the onboarding wizard's "Recommended" starter selection either. If you went through setup and picked "Recommended" rather than "Everything" on the tools page, you'll need to turn Camera on yourself under Settings → Tools. - **No direct-boot support.** Camera doesn't work on the lock screen before you've unlocked your device once after a restart, since it depends on the app's private storage, which isn't available that early. - **Every build, no gating.** Camera has no edition restriction anywhere in its code path. The scanners and handwriting are in the F-Droid build too, with open-source readers where they use Google ML Kit elsewhere. --- # Clipboard manager URL: https://wmkeyboard.pages.dev/tools/clipboard/ Section: Tools Summary: History, pinning, search and rich media, with password hygiene built in. The clipboard tool keeps a running history of what you copy: text, images, files and more. A paste from ten minutes ago is still one tap away instead of gone the moment you copy something new. On a phone it's one of only three tools pinned to the toolbar out of the box, alongside Emoji and Settings. A tablet starts wider, because three icons across a twelve-inch bar is mostly empty space. A small tablet pins five (Emoji, GIF, Clipboard, Voice, Settings) and a large one pins seven, those five plus Translate and Text edit. Clipboard is in all three sets. tools/clipboard-panel — The clipboard panel: staggered two-column cards, entity chips for a fresh code or link, and a search pill. ## Opening the panel Tap **Clipboard** on the toolbar: the clipboard with lines of text on it. The empty clipboard beside it, if you pinned one, is the **Paste** tool, which pastes your latest copy without opening anything. If you unpinned Clipboard, open the toolbox and tap it there. See [the toolbar](https://wmkeyboard.pages.dev/tools/overview/) for how pinning works. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press C. To open it with the [search](#finding-a-clip-again) already taking your typing, bind the **Clipboard search** action to a flick, a key's long press or a toolbar button's hold. It's an action rather than a tool, so it never shows on the toolbar or in the toolbox: you'll find it under **Other** in those pickers. It turns the search bar on if it was off. Cards lay out in a staggered grid, two columns by default, rather than a fixed one, so a short text clip doesn't get stretched to match a tall image beside it. Pinned clips sort first by default ([flip that](#options) to put them last instead, or turn on **Keep pinned clips in place** to leave them where they were copied, wearing their pin, the way SwiftKey shows them). Tap any card to insert it. Copying the same content again moves the existing card back to the top rather than adding a duplicate. ### Making the panel taller The panel opens as tall as the keyboard. A short bar sits across its top edge: drag it up and the panel grows over the app, so more clips fit and there's less to scroll. Drag it down to shrink it again, as far as the keyboard's own height. The app stays still while you drag and moves once, when you let go. Where you let go is kept, and the panel opens that tall from then on. It's the same setting as **Panel height** under [Options](#options), where the reset button takes it back to the keyboard's height. The panel never covers more than most of the screen, so on a short screen or with the phone sideways it stops earlier, and with no room to grow at all the bar isn't drawn. The height changes only the history: a search, or a clip open in the editor, still shrinks the panel so the keys fit under it. A search has its own bar for that, the one every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares. With TalkBack on, the bar offers **Make the panel taller** and **Make the panel shorter** as actions. ### Clipboard and keys together The keyboard button at the end of the panel's header stacks the clipboard over the keys. The panel shrinks to its header, the tabs if they're on, and about two rows of clips, and the keys come back underneath it, typing into the app as usual. The strip of copied fragments steps aside to leave the clips that room; tap the button again to see it. Paste a clip, type a sentence, paste the next one, without opening and closing the panel in between. The height bar on top now sets how tall the stacked panel is, not the full one: drag it up for more clips over the keys. That height is the one every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares. Tap the button again to give the clipboard the whole keyboard back. Stacking lasts for that visit only: close the panel and it opens full again next time. This needs **Full-screen clipboard panel** under [Options](#options), which is on by default; the word suggestions don't show while the panel is stacked. ### Grid or list The square button beside the search box switches the history between the grid and a list. The list gives each clip a whole row: the text across the full width, up to three lines of it, with the pin and delete circles at the end, and a picture as a small thumbnail at the start of its row. It's the easier one to read when your clips are long text. The button shows the view a tap takes you to, and the choice sticks. It's the same setting as **History layout** under [Options](#options). The button is a cell of the [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) (**Grid or list switch** in the component picker), so you can move it or drop it. With the full-screen panel on, it sits at the right end of the header. ### Numbered clips Turn on **Number the clips** and every clip gets a small number: at the start of its row in the list, and next to the pin in the grid, so a card never gets taller for it. The number belongs to the clip, not to the spot it's drawn in. Search the history and the results keep the numbers they had, so the third clip is still number 3 when it's the only match. Numbers follow the panel's order from the top, pinned clips included. With [the tabs](#pinned-and-unpinned-tabs) on, each tab counts its own clips: the first pinned clip is 1, and so is the first unpinned one. ### Pinned and unpinned tabs Turn on **Pinned and unpinned tabs** and two tabs sit over the history: **Unpinned**, where the panel opens, and **Pinned**. Each shows how many clips it holds; tap one, or swipe sideways across the tabs, to change tabs. With swipes on the clips turned off, a swipe across the history changes tabs too; with them on, only the tabs take it, so a finger that slips into the gap between two clips never changes the tab by mistake. Pin a clip and it moves across to the Pinned tab, and unpin it to send it back, so the clips you keep for good are never in the way of a fresh copy. Since the clip leaves the tab you're looking at either way, a bar at the bottom says where it went, "Pinned. It is in the Pinned tab now", with an **Undo** that puts it back, so a swipe that pinned never looks like one that deleted. A search looks through both tabs at once, and the tabs step aside while it runs. With the tabs on, **Pinned entries last** has nothing left to do, so it's greyed out. **Keep pinned clips in place** turns the tabs off, since there is no pinned group left to give a tab. Or leave the history in one list and turn on **Outline pinned clips**: each pinned clip gets a thin border in the accent color, the same color as its lit pin, so it stands out from across the panel and not only from its pin circle. ### Columns, lines and times Three settings change how much each clip shows. **Grid columns** sets how many columns the grid has, one to four (two by default): more suits a history of short text, fewer gives long text and pictures room. The list is always one clip per row. **Lines per clip** sets how many lines of text a clip shows, up to 20, in both views. Its default, **Auto**, is six lines on a card and three in a row. Longer text ends in an ellipsis. **Time on each clip** puts a small time next to the number: **Time copied** reads "5 min. ago", and **Time left** counts down to when the [expiry](#expiry-and-the-entry-cap) deletes the clip. A pinned clip, or any clip while expiry is off, has no time left to show. The times update while the panel is open. **Line spacing** sets how far apart a clip's lines sit, from 100% (as tight as the font allows) to 200%, default 130%, so more short lines fit on a card. However many lines you ask for, a clip's preview is cut before it's laid out: at the line after the last one shown, and never past 4,000 characters. A clip that holds a whole document costs no more to draw than a short one. ### Editing a clip Press and hold a text clip and tap **Edit** in its popup. An editor opens where the history was, as tall as the panel, with the keys back underneath it, so the keyboard grows upward while you edit. On a phone held sideways it gets whatever room the keys leave. Your typing goes into the clip rather than into the app you're writing in. Enter starts a new line. Tap the text to move the cursor, or use the spacebar and arrow keys like anywhere else. Press and hold a word, or double-tap it, to select it, with Cut, Copy, Paste and Select all over it, or press and hold anywhere past the end of the text, the empty part of the box included, to put the cursor at the end and get **Paste** there. **Save** keeps the change and **Cancel** (or Back) leaves the clip as it was. The editor, Combine's boxes and the keyboard's other boxes of their own take the keys the way a text field does. Shift re-cases a selection in the box and otherwise capitalizes the next letter, and never reaches the app's field behind the panel. **Holding delete clears words** clears a word at a time here too. Select all, Copy, Cut, Paste, Select word and Undo act on the box, from their keys, toolbar buttons, long presses and flicks alike. A suggestion picked with the cursor in front of a word you didn't touch puts its word in front of that one rather than over it. An edited clip keeps its place in the history, its number, its pin and its age, so it doesn't jump to the top and push the others around. It does get a small pencil mark to the right of its pin button (or at the end of the card when the buttons are off). The mark is only a label and does nothing when tapped. **Clip shortcut button** under [Options](#options) can hide it, or put a button in its place on every clip: **Edit** opens the editor in one tap, and **Select** starts [picking clips](#picking-several-clips) with that one. The clip's details show when it was last edited, under when it was copied. A few things do change: - Rich text becomes plain text once you change it, since the old formatting was for the old words. The editor says so before you save. Saving without a change keeps the formatting. - A link's fetched preview is dropped, and whether the clip counts as a link at all is worked out again from the new text. - If the new text matches another clip, the two merge into the one you edited, the same way copying something twice never makes two entries. - If the clip expired while you had it open, saving adds your text back as a new clip instead of losing it. Images, files, folders and videos have no text to edit, so their popup has no **Edit**. Neither does a [hidden password or code](#passwords-are-handled-deliberately): the panel never shows its text, and an editor would. When the clip you edited is the one on the system clipboard, your edit goes there too, so the **Paste** tool, a long-pressed V and an app's own paste give the new text. Editing an older clip changes only the history. Every card carries two small circles along its bottom edge: a pin and a delete. Tap the pin to keep a clip out of reach of the expiry and the entry cap. Tap the bin to drop it. Turn off **Pin and delete buttons** under [Options](#options) and the circles go: the press-and-hold popup gets **Pin** (or **Unpin**) and a red **Delete** instead. A card also loses the row the circles sat on, unless that row still has something to show for that card: its number, its time, or its "rich text" tag. So a card with none of those shrinks to just its text, and more of them fit. Swiping a card left or right until it slides off deletes it too (with **Swipe to pin** set, one of the two directions pins it instead), unless you've turned off **Swipe to delete** under [Options](#options): then a sideways swipe does nothing, and the card's press-and-hold popup gets a red **Delete** button instead. Neither the bin nor the swipe asks first for an unpinned clip. A pinned one is different: the bin, the swipe or **Delete** in the popup puts up a question over the history, "Delete this pinned clip?", and only **Delete** there removes it; **Cancel** leaves it, and a swiped card slides back into place. Turn off **Ask before deleting a pinned clip** under [Options](#options) to have pinned clips go at once too. A deleted clip leaves its place empty for the rest of the visit, so the clips above it stay exactly where they were as you scroll back up; the clips below it close the gap. An empty place takes no height, so deleting clip after clip from the top never pushes the rest down. The places go when you close the panel, or change what it shows with a filter, a tag, a tab, the search or the order. Either way, once a clip is deleted a bar pops up at the bottom of the panel for five seconds saying **Clip deleted**, with an **Undo** button that puts the clip back in the spot it had, pin and all. Delete a few more while the bar is up and it counts them ("3 clips deleted"), and one Undo brings back the lot. If you've asked Android for more time under Accessibility, **Time to take action**, the bar stays up that long instead. Swipe the bar sideways to put it away sooner. Once it's gone, swiped or timed out, the delete is for good. Don't want the bar? Turn off **Undo a deleted clip** under [Options](#options). Deleting a clip also empties the system clipboard when that still holds the clip, so an app's own Paste can't bring it back. Deleting an older clip leaves the clipboard alone, since what's on it is something you copied later. **Undo** puts a text clip back on the system clipboard too, unless you copied something else while the bar was up. A picture or a file comes back to the history only. ### Clearing the history Turn on **Clear button** under [Options](#options) and a bin sits at the end of the search box, or on its own in that spot while the search bar is off. It's there while at least one clip is unpinned. Tap it and the panel asks first: "Delete 12 unpinned clips? Pinned clips stay." **Delete** takes every unpinned clip at once and leaves the pinned ones where they are. It empties the system clipboard as well, whatever it holds, unless that's one of the pinned clips. **Cancel**, or a second tap on the bin, takes the question away. With **Undo a deleted clip** on, the Undo bar then offers all of them back in one tap. On a hardware keyboard the bin is the search box's neighbour: Enter on it asks, and Enter again deletes. To see a clip's info popup instead, press and hold its card. The popup gives the time it was copied, its type and size, and (if [source tracking](#options) is on) the app it came from. Under the details sits a row of icons at the right end, the shapes every phone already uses, with **Paste** last, which does what a tap on the card does. Not sure what one of them is? Press and hold it and its name pops up over it; lifting your finger then doesn't run it. The same goes for the buttons in the search box and the header, like the filter icon and the list or grid switch. Every clip gets **Select** (a checklist) to start [picking clips](#picking-several-clips) and **Reorder** to [put clips in your own order](#putting-clips-in-your-own-order). While you search, every clip also gets **Show in history** (a clock): the search closes, the filters with it, and the history scrolls to that clip on its tab, so you see what you copied around it, the way a chat app's Go to message works. The clip is ringed for a moment so it's easy to spot. With the **Duplicates** [filter](#filtering-and-sorting) on, every clip gets **Keep only this one**, which deletes the clips grouped with it, with one **Undo**. Some clips get more: - A **text** clip opens straight on its whole text, however long, in a box you can scroll, with every web address in it underlined in your theme's accent colour (not with **Keep formatting** off, which shows everything as plain text). Under the text sits the same row of icons at its right end: ⓘ for the details described above (when it was copied, which app it came from, its type and length), select, reorder, edit, tags, QR code, open link, extract parts, pin and delete as the clip and your settings allow, and **Paste**. If a clip offers more icons than fit, the row scrolls sideways. **Order of a held clip's buttons** under [Options](#options) puts the icons in the order you want, and **Paste button on a held clip** can take **Paste** off the row, since a tap on the clip pastes it anyway. Tap anywhere outside to put it away. From the details, the page icon brings the text back. - A clip with codes, phone numbers, links or places inside it gets **Extract** while [entity detection](#pulling-codes-and-links-out-of-a-clip) is on. With one part found, it pastes that part. With more, it lists them in sections, **Links**, **Places**, **Phone numbers** and **One-time codes**, and a press on one pastes it. A [hidden password or code](#passwords-are-handled-deliberately) has no such button, since the panel never shows its text. - A **text** or **link** clip gets **QR code**, which opens the [QR code generator](https://wmkeyboard.pages.dev/tools/utilities/#making-a-qr-code) on the clip's text instead of the field's, to send as a picture or change first. A [hidden password or code](#passwords-are-handled-deliberately) never gets it, since the code would show what the panel hides. - A **text** or **link** clip gets **Search the web** while the [web search tool](https://wmkeyboard.pages.dev/tools/search/) is on, and **Search Wikipedia** while the Wikipedia tool is on. Either opens that tool on the clip's text (its first 200 characters, on one line) and runs the search straight away. A [hidden password or code](#passwords-are-handled-deliberately) never gets them. - A **link**, a clip that is one web address and nothing else, gets **Open link**, which opens it in your browser. A hidden clip never does, since the browser would show what the panel hides. - An **image** gets **View**, which shows the picture full screen: pinch or double-tap to zoom, drag to look around, tap or press Back to return to where you were typing. It also gets **Send as sticker**, which runs the picture through the sticker processor before it goes. - An **image** gets **Translate** while a server is set up under [Translate photos](https://wmkeyboard.pages.dev/tools/camera/#translating-a-photo). The camera opens on the picture and translates the text in it straight away, ready to send, copy or save. It sits just before **Extract text**. - An **image** also gets **Extract text** while the [text scanner](https://wmkeyboard.pages.dev/tools/scanner/#scanning-text-ocr) is on in your tools. The scanner opens on the picture instead of the camera and reads it straight away, in the language you type in. The words come back as chips: keep the ones you want, then **Copy** puts them on the clipboard, where they become a clip of their own, or **Insert** types them. **Scan again** shows the picture with its language chip, so you can read it again in another language. The back button returns to the clipboard. ### Tagging clips Press and hold a clip and tap **Tags** (the label icon) to give it labels of your own, such as `work`, `links` or `to read`. The editor opens on the clip's tags as a list with a comma between them, and the tags other clips already carry sit under it as chips, a tap away, so one group keeps one spelling. Enter or **Save** keeps the list; save it empty to take every tag off. Any clip can carry tags, pictures and files included, and they never change the clip's text or its place. A tagged clip shows its tags under its text, each with a `#`, in your accent colour. The search finds a clip by its tags, with or without the `#`, and every tag in the history gets a chip of its own in the [filter row](#filtering-and-sorting). ### Pinned folders Turn on **Pinned folders** under [Options](#options) and tags turn into folders for your pinned clips. A pinned clip with a tag leaves the pinned clips and goes into a folder named after the tag, so a set of clips you only paste together, like the questions for one survey, stays out of the way until you need it. The folders sit in a row over the pinned clips, on the Pinned tab when the [tabs](#pinned-and-unpinned-tabs) are on, each with a folder icon and how many clips it holds, in alphabetical order. Tap one to open it: its name sits over its clips, and the back arrow beside the name returns to the rest. Tap a clip in a folder to paste it, as anywhere else. To file a clip, pin it and [give it a tag](#tagging-clips); to take it out, remove the tag or unpin it. A clip with two tags is in both folders. Pinned clips without a tag stay where they always were, and unpinned clips are never filed, though with the tabs off an open folder shows the unpinned clips carrying its tag too. A search looks through every clip, filed or not. With **Keep pinned clips in place** on, and the tabs off, the folder row sits over the whole history, and a filed clip leaves its place in it for the folder. ### Picking several clips Press and hold a clip and tap **Select** (the checklist icon). The clip is picked, a tick appears in its corner, and every other clip shows an empty ring: tap a clip to pick it or drop it, instead of pasting it. A bar over the history counts the picked clips and offers what you can do with them. The select-all button beside the count picks every clip on show, and pressing it again clears them. **Delete** removes them all at once, with one **Undo** for the lot. **Combine** joins them into one new clip, below. **Tidy**, there while you have [copy rules](#tidying-copies-with-rules) or **Plain letters** on, runs them over the picked clips, so clips you copied before you wrote a rule get the same treatment; each changed clip keeps its place, as an edit does. **Zawgyi ⇄ Unicode**, there while a picked clip holds Burmese, converts each such clip between Zawgyi and Unicode, whichever way it needs (see [Burmese](https://wmkeyboard.pages.dev/languages/burmese/#converting-text)). The ✕ at the start of the bar stops picking. While you pick, the pin and delete circles and the swipes stand down, so nothing happens to a clip by accident. ### Combining clips Pick two or more text clips and tap **Combine**. A box opens where the history was, with the keys back underneath. The clips stay in view at the top, a line or two each, with the part each one gives marked in your accent colour and a clip that gives nothing dimmed, so you can write a pattern against them. Under them you set how the clips are joined: - **Between the clips**: what goes between one clip and the next. A space by default. The chips under it put in a **Space**, a **New line**, a **Comma**, a **Semicolon** or a **Bar** ( | ) in one tap, or type your own. In the box a space shows as ␣ and a new line as ⏎, so a single space is not an empty box. - **Prefix**: optional text that starts the new clip. It can name a group of the pattern as found in the first clip, like `$1`. - **Pattern**: optional. A [regular expression](#regular-expressions); each clip then gives only the first part of it that matches, and a clip with no match is left out. - **Combination**: optional, and only used with a pattern. What each clip's match becomes, with `$1`, `$2` and so on for the pattern's groups and `\$` for a dollar sign. Empty keeps the whole match. A long pattern wraps onto more lines in its box instead of scrolling sideways. The clips join in the order you picked them, and the **Result** under the boxes shows what you'll get as you type, scrolling when it's long. Press it to edit the result by hand before you save; changing a box after that goes back to what the rule makes. Turn on **Delete these clips after combining** to have the joined clips go once the new one is added, with one **Undo**; otherwise they stay as they were. **Combine** adds the result to the top of the history. The boxes, and the switch, are remembered for the next time. For example, to join two YouTube links into one line of video ids, pick `https://m.youtube.com/watch?v=Z7pkvfRl5S8` and `https://m.youtube.com/watch?v=A1bcdeFg2H3`, then set the joiner to `; `, the prefix to `youtube.com/watch?v` and the pattern to `=[a-zA-Z0-9_]{11}`. The new clip reads `youtube.com/watch?v=Z7pkvfRl5S8; =A1bcdeFg2H3`. With groups, two links to one owner's GitHub repositories, `https://github.com/wasi-master/wmkeyboard` and `https://github.com/wasi-master/wmkeyboard-addon-repository`, joined with `; `, the prefix `$1: github.com`, the pattern `github\.com/([^/]+)(/[^/]+)` and the combination `/$1$2` read `wasi-master: github.com/wasi-master/wmkeyboard; /wasi-master/wmkeyboard-addon-repository`. Tap a box to type into it. Enter in the first box and in the result adds a new line, in the prefix moves to the pattern, in the pattern moves to the combination, and in the combination combines. ### Putting clips in your own order Press and hold a clip and tap **Reorder**. The history turns into a list of the clips it was showing, in the same order: the open tab, the filter you set, newest or oldest first. Each row is two lines tall, with a thumbnail for a picture, and a handle at its start. Drag a clip by its handle, or press and hold it anywhere and then drag, to move it up or down, or tap the arrow at the end of its row to send it to the top. Pinned clips move among the pinned ones and unpinned among the unpinned ones, unless **Keep pinned clips in place** is on, where any clip goes anywhere. A clip you drop in a filtered list lands between the two clips it was dropped between. **Done** goes back to the history. A clip you moved stays where you put it. One you dragged to the top stays there even as you copy new things, which land just under it, so the clips you always want first stay first. Copying the same text again doesn't move a clip you placed. tools/clipboard-info-popup — Long-pressing a clip: the info popup shows when it was copied, its type and its size. ## What gets captured Turning [clipboard history](#options) off stops capture entirely. Even with it on, incognito being active and [set to pause the clipboard](https://wmkeyboard.pages.dev/privacy/incognito/) skips a copy, and that always wins. A copy made while a password field has focus goes into history exactly as you copied it, so you can paste it back into the box, but never as a chip on the strip. What happens to it there follows [Sensitive clips](#passwords-are-handled-deliberately): **Keep it like any other clip**, the default, leaves it readable in the panel, **Hide it and delete it quickly** masks it as dots and deletes it on the short timer, and **Never save it** leaves it out. Both go by the field that has focus when you copy. Once you leave a private-browsing field or a password box, a copy made somewhere else, such as a link copied off a browser's address bar or a new screenshot, is captured as usual. Clips are stored under seven kinds. The panel groups them like this: tools/clipboard-rich-media — A file, video, and folder clip side by side. Each keeps only a reference to the source, not the bytes. - **Text and rich text**: the plain content, shown as-is. A clip that is just one colour code, like `#1e88e5`, `#fff8`, `0xFF1E88E5`, `rgb(30, 136, 229)` or `hsl(210, 79%, 51%)`, gets a small square of that colour in front of it. A copied **link** with no preview is shown as the address itself, as many lines of it as **Lines per clip** allows, with no separate host line; with a preview, the title and description take its place and the address and host sit underneath. - **Links**: shown as the raw URL, or as a fetched title and description if [link previews](#options) are on. An address counts without `https://` or `www.` in front too, like `x.com`, `youtu.be/…` or `wmkeyboard.pages.dev`, when it ends in a common web ending. File names like `notes.txt` and words like `Node.js` stay text. - **Images**: copied into the app's own storage, so the source app can discard its copy without losing yours. Also captures device screenshots directly, if you turn that on (not in the Play Store build). - **Files and videos**: not copied. Only the file's URI, name, MIME type and size are kept, so copying a multi-gigabyte video costs nothing. A video card even shows a frame grabbed from the very start of the clip instead of a blank icon. Copying several files or a whole folder at once keeps up to 20 items from that one copy. - **Folders**: a reference, same as files. A folder can't be attached to anything, so tapping its card types the folder's name into the field and hands the folder's URI back to the system clipboard for a file manager to use. ### Tidying copies with rules Clipboard / Copy rules holds rules that change a copy before the history keeps it. Each rule is a pattern, a [regular expression](#regular-expressions) where case doesn't matter, and what to put in place of every match. Leave the replacement empty and the match is removed, which is what most rules are for: `https?://(www\.)?` takes the front off every address, so `https://www.github.com/wasi-master/wmkeyboard/` is kept as `github.com/wasi-master/wmkeyboard/`, and it works inside a longer copy too. A replacement can name a group of the pattern, like `$1`, to add to a match rather than only take from it. Rules run in the order listed, each on what the one before left. Tap a rule to change it, use its switch to turn it off without losing it, or the bin to delete it. **Change the order**, above the list once there are two rules, lets you drag them into another order. The **Try the rules** box at the bottom shows what a copy you paste into it would be kept as. A clip its app marked as a password is never changed. The rules apply to new copies; to run them over clips already in the history, [select those clips](#picking-several-clips) and press **Tidy**. **Plain letters**, on the same screen, keeps a copy written in decorated letters as the plain letters they stand for, before any rule runs: the bold, italic, script and double-struck alphabets chat apps and bio generators type with, full-width letters, circled and squared letters, small capitals, and the lines some text generators draw through or under each letter. Symbols like ™ and ℃ stay. It's off by default. By default the rules change only what the history keeps, and what the keyboard pastes from it. Turn on **Change the system clipboard too** on that screen and a copy a rule changed is put back on the system clipboard as changed, so the paste menu of every app gets the tidy text. **Remove trackers from copied links**, on the same screen and off by default, takes the tracking parameters off every link in a copy: `utm_source`, `fbclid`, `gclid`, `si` and the rest of the list the selection bar's [Remove trackers](https://wmkeyboard.pages.dev/smart/selection-actions/) uses. A link inside a sentence is cleaned where it stands, and the rest of the text is left alone. Since version 0.6.2 it also knows what the big sites add on their own: Amazon's `/ref=…` and `pd_rd_*`, YouTube's `feature`, X's `s` and `t`, TikTok's share ids, Google search's `ved` and `ei`, and more. Those names come off only on that site, so a `t=42` elsewhere stays. A redirect link is unwrapped to the page it leads to, as with Google's `/url?q=`, Facebook's `l.php?u=`, Outlook safe links and Steam's link filter. If a cleaned link stops working, press **Undo** straight after pasting it: the clean link is swapped for the one you copied, which also goes back on the clipboard. The clean copy always goes back on the system clipboard, whatever the setting above says, because a clean paste is the point. **Your own tracking parameters** adds names to that list, one on each line, for both this and the selection bar. End a name with `*` to take a whole family, so `abc_*` removes `abc_id` and `abc_src`. ### Pasting rich media Tapping an image, file, or video card negotiates a MIME type with the focused field. The keyboard offers the clip's own type, and sends it via `commitContent` when the field advertises that it takes one. If the field wants PNG but the clip is a static WebP, it converts on the fly. Animated WebP is left alone, so an animation is never silently flattened into a still. If nothing matches, a picture goes where the gallery's [When the app does not take pictures](https://wmkeyboard.pages.dev/tools/gallery/#when-the-app-does-not-take-pictures) sends it: by default Android's share screen opens for the app you were typing in, **Copy them to the clipboard** puts it back on the system clipboard to paste, and **Ask each time** opens Android's share sheet, which offers both. A file or video goes back onto the system clipboard and a toast tells you to paste it there. That happens when the field takes no rich content at all, or when the permission to read the file has lapsed. ### Expiry and the entry cap Unpinned clips fall off two ways. Clipboard sets an expiry in hours, where 0 turns expiry off. It also caps how many unpinned entries history keeps at once, and once that cap is hit the oldest unpinned clip drops with each new copy. Set the cap to **Unlimited** and only the expiry removes clips. Pinning a clip exempts it from both. A pinned clip never expires and never counts against the cap. A third limit is on the length of each clip, and it starts off. **Longest clip kept** keeps all of what you copy by default. Set it (1,000 to 100,000 characters; Gboard cuts at 20,000) and a longer copy is stored cut to that length, so a copied book doesn't fill the history and every backup of it. Rich text that gets cut is stored as plain text, since its formatting no longer matches. Only the history is cut: the system clipboard still holds the whole copy, so an app's own paste is unchanged. ## Finding a clip again Turn on the search bar and a search pill appears at the top of the panel, once you have at least one clip. Tap it and your keystrokes go to the search field instead of into your document, the same trick the emoji panel uses. The panel shrinks to just the field and its results while you type, and the bar on top drags it taller if a couple of rows of results isn't enough. The back button stays at the start of the row, where the header had it, and closes the search; the number at the end counts the clips the search and the filters leave. Results are listed newest first, pinned or not, so the first one is your latest matching copy. To see more results than fit over the keys, put the keys down: press Back, or tap the keyboard button at the right end of the search's suggestion strip. The query stays and the results fill the panel; tap the search field to type again. The clip editor and Combine's boxes work the same way. See [Putting the keys down](https://wmkeyboard.pages.dev/tools/overview/#putting-the-keys-down). Matching is a case-insensitive substring search, and it reaches past a clip's own text, to its [tags](#tagging-clips) among other things. A clip with no text of its own is still found by what it is: "image", "picture" or "screenshot" for a copied picture, "video", "movie" or "recording" for a clip, "folder" for a folder. The file format works too, because all of that is indexed alongside the file name, the source app and any link preview's title and description. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the clips whose text means what you typed come after these matches, best first, and with pictures on, so do the screenshots and images that show it. "plane ticket" finds "Flight AA102 departs from gate 23", which shares no word with it. Clips marked as passwords are never read for it. ### Filtering and sorting The filter icon at the end of the search pill opens a row of chips under it, without starting a search. The first chip flips the history between **Newest first** and **Oldest first**. The rest show only some clips: **All**, **Pinned**, **Unpinned**, **Text**, **Links**, **Images**, **Files** or **Duplicates**. **Duplicates** shows clips whose text is the same as another's but for capital letters and spacing, `GitHub` and `github` say, each group side by side; press and hold the one to keep and tap **Keep only this one**. Your [tags](#tagging-clips) have a row of their own under that one, one chip each, most used first: tap one to see only the clips that carry it, tap it again to see only the clips that don't (the chip then reads **Not #tag**), and a third time to let go. With a kind chip such as **Unpinned** set as well, that is every unpinned clip without the tag. A filter works with or without a search, and on top of the [tabs](#pinned-and-unpinned-tabs). While anything is set the row stays up, so a short history always shows why. The panel remembers the order and the kind for as long as the keyboard runs, so the next search opens filtered the way you left it. A tag lets go when you close the clipboard. ### Regular expressions The `.*` button beside the search pill, or **Regular expressions** under [Options](#options), turns on pattern search, and the button is lit while it's on. The search then reads what you type as a pattern instead: `^https` finds clips that start with a web address, `\d{6}` six digits in a row, `invoice|receipt` either word. Case still doesn't matter, and `^` and `$` hold on to the clip's own text, not the words it's indexed under. A pattern you haven't finished yet, such as an open bracket, searches as plain text until you have, so the results don't vanish halfway through typing it. ### Searching your notes too With **Search notes too** on, which it is by default, the search also looks inside your [notes](https://wmkeyboard.pages.dev/tools/notes/). Once you've typed two letters or more, the paragraphs that hold the match come first, under **From notes**, and the clips follow under **Clips**. Tap a passage to type it into the field as the note has it; press and hold it to open the note in the Notes panel. A passage is the paragraph around the match, cut down to the line or the sentences when the paragraph is long, three to a note and twelve in all. The number at the end of the search row counts them with the clips. The search's tools work on notes the same way. With the `.*` button lit, a pattern finds passages too, and `^` and `$` hold on to each line of the note. The tag chips include your notes' tags, so a note tagged **Quotes** shows up under the **#Quotes** chip next to any clip with that tag, and **Not #Quotes** leaves it out. **Text** keeps the passages, while **Pinned**, **Unpinned**, **Links**, **Images**, **Files** and **Duplicates** hide them, since none of those apply to a note. Locked notes and the trash are never searched. That lets you keep groups of related text together in one note, a set of replies or a list of addresses, and find any of it from the clipboard without opening Notes. ## Pulling codes and links out of a clip Turn on entity detection and the panel scans your recent clips for one-time codes, phone numbers, links and places. A link is found with or without `https://` in front, so `x.com` or `m.youtube.com/watch?v=…` in a message gets a chip too. A place is a pair of coordinates, a Plus code or a `geo:` link; a map link stays a link. The [Location tool](https://wmkeyboard.pages.dev/tools/location/#places-somebody-sent-you) reads both kinds and can turn them into an address, a different map's link, a saved place or a compass bearing. Each one becomes a dashed-outline chip above the history grid, so the six digits inside a verification text are one tap away rather than a copy-select-paste. A chip is one line: the kind's icon and tag, then the part itself. Tapping a chip pastes just that fragment and leaves the full clip in place. Press and hold a chip to see it highlighted inside the message it came from. The ✕ at the end of the chips folds the strip away, and it stays away. A scissors button beside the search, with a count of the parts found, brings it back. With **Show the found parts as icons on each clip** on, the chips give way to icons on the clips themselves: a link, a pin, a phone or a code icon at the end of each card's bottom row, for each kind found in it, with a small number when there's more than one. Press an icon to paste its part, or, when it counts more than one, to pick from the list. Every clip shows its own parts this way, not only the newest few the chip strip has room for. With **Copy picked parts to the clipboard** on, a part you pick, from a chip, an icon or **Extract**, is copied as a clip of its own instead of typed, ready to paste wherever you need it. With **Show the found parts on the suggestion strip** on, you don't have to open the panel at all. Copy something with a code, a number or a link in it, and the suggestion strip shows the whole copy as a chip followed by a dashed chip for each part, the way FlorisBoard and FUTO Keyboard do. Tap a part to paste just it. The chips hold the strip until you type something or press the ✕ at its end; after that the words come back, with the usual chip for the copy beside them. It's off by default. tools/clipboard-entity-chips — Entity chips above the history grid: an OTP and a phone number pulled out of a longer message. Detection is conservative on purpose. A 4-digit year is never mistaken for a code, and a run of digits after a currency symbol or a `#` is never mistaken for a phone number. Google's `G-123456` verification prefix is stripped, so only the code itself pastes. Unless **Show the found parts on the suggestion strip** is on, pulling a fragment out of a longer message is the panel's job. The suggestion strip only ever offers a copied code when the clip *is* the bare code, which is the **Suggest copied codes** rule under [Options](#options). Phone numbers are the one fragment with no shape of their own, so they get an extra setting. Without it, anything with 7 to 15 digits counts, which is how an invoice total or a tracking number ends up wearing a phone chip. The list starts with one format for your own country, worked out from your phone's region. Open **Phone number formats** from the clipboard settings to change it, or to add the second country you copy numbers from. Give it a number you copy often. The screen turns that into a format like `+880 1XXX-XXXXXX`: it keeps the dial code and makes the rest wildcards, and you can type digits back over a wildcard to pin it. After that, only numbers matching one of your formats become chips. The matching is forgiving about the things people vary and strict about the things they don't. Spaces, hyphens, brackets and dots are ignored on both sides. The same format accepts the number with its country code, without it, or with a leading 0 in its place. What it won't accept is the wrong length, the wrong leading digit, or an explicit `+` naming a different country. There's a test field on the screen, so you can paste a number in and see the answer before you trust it. Add as many formats as you like, one per country you deal with, and delete them all to go back to catching everything. [Phone number formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/) is the field reference for that screen: the mask characters, the length bounds, and every bit of leeway spelled out. This entity strip is a different mechanism from [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), which reacts to what you're currently typing rather than to what you've copied. The two look alike, both dashed outlines, but they share no code. ## Passwords are handled deliberately The clipboard tool can treat a copied password or code as sensitive in two independent ways: at copy time, and at paste time. Both are off out of the box, so a password you copy is kept, shown and pasted like any other clip. They're here for when you'd rather it wasn't. **At copy time**, Clipboard / Passwords and codes chooses what happens to a clip Android itself marks sensitive. That's the flag a password manager sets when you copy a login. You can keep it like any other clip (the default), hide it and forget it quickly, or never save it at all. Hiding and forgetting is the one to pick if you want this at all. Never saving would trade away the everyday "I pasted a password into the wrong field" recovery, and it closes a risk a short timer already closes. Turn on recognizing sensitive clips yourself and the same handling covers anything that merely *looks* like a password or a bare code. That's for managers older than the flag, or a code you copied by hand, and it's narrow enough that a sentence, a link or an email address never qualifies. A hidden clip shows as a row of dots and a character count in the panel, and it's never revealable there. It's also left out of the strip's paste chip (except in a password field with [Offer copied passwords in password fields](#paste-chip-and-codes) on, and then shown as dots), the entity strip and the settings backup. It expires on its own timer, independent of the ordinary history expiry above and not capped by it. Pinning a sensitive clip overrides all of that, the same as pinning anything else. Switch back to keeping them like any other clip and the clips that were hidden turn back into ordinary ones. **At paste time**, turn on forgetting after a password paste. The clip is then deleted from history the moment you paste into a password field, and from the system clipboard too if that still holds exactly what you pasted. Every paste route fires it: a tap on a card, the strip's paste chip, a pasted entity chip, a long-pressed V, the text-editing panel's Paste, and a hardware Ctrl+V. So the rest of an SMS can't sit on the clipboard after you've pasted only its code. **When the app wipes it**, the history lets it go as well, whatever you chose above. A password manager that marks its copies sensitive usually empties the clipboard a little later; [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/) does it after the time set in its own settings. If the clipboard is emptied while it still holds that copy, the clip leaves the history and the strip's paste chip goes with it. A clip the keyboard hid as sensitive itself goes the same way. A pinned clip stays. So does one you'd copied something else over before the wipe, because the manager had nothing of its own left to wipe; it then keeps to the rules above. The clipboard is already out of reach while the lock screen is showing, and no setting controls that. History reads as empty, the paste chip is cleared, and the panel refuses to open. Hiding the whole top strip there, suggestions and toolbar together, is a separate [Appearance](https://wmkeyboard.pages.dev/reference/settings/appearance/) setting and not part of this group. ## Options Clipboard Everything below lives on the Clipboard screen and its three pages, under Features on the settings home screen, rather than on the tool page. Your copies land in the history whether or not the panel's button is on the toolbar, so these aren't really the tool's settings. tools/clipboard-settings — The Clipboard screen before it became a hub: the History group and, below it, Passwords and codes. The Clipboard screen is a hub. Three rows open its pages, and the Panel group sits under them as a fold, closed until you open it. - **Clipboard history**: the master capture switch, on by default, on the same row as the door to the [Clipboard history](#clipboard-history) page below. The switch flips here; the rest of the row opens the page. - **Paste chip and codes**: opens the [page](#paste-chip-and-codes) of what the history puts on the suggestion strip. - **Rules and privacy**: opens the [page](#rules-and-privacy) with the copy rules, the phone-number masks and how passwords and codes are handled. **Panel**, in the order the rows appear: - **Panel layout**: opens the panel's [layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) in the layout editor. The search box, the grid or list switch, the strip of copied fragments and the history are cells you can move and resize, and any key can join them: an abc, space and backspace row along the bottom, say, which is what the old "Bottom control row" switch used to add. A layout made with that switch on carries over. - **Full-screen clipboard panel**: on by default, so this is what the panel does unless you say otherwise. The history takes the whole keyboard, and a slim back header stands in for the toolbar, the emoji row and the symbol row. Picking the right clip gets easier the more of them are on screen, which is why that trade is the default. Start a search and the panel drops back to its compact size so the keys fit underneath. Turn it off to keep the toolbar visible while the panel is open. - **Panel height**: how much taller than the keyboard the panel opens, up to 600 dp more, default **Same as keyboard**. The same setting as the [bar on top of the panel](#making-the-panel-taller). - **Height over the keys**: how much taller the panel opens while the keys are under it, stacked or searching, up to 400 dp more, default **Compact**. Every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares it, and dragging the bar on any of them sets it. - **ABC, space and enter keys**: off by default. Adds a row along the bottom of the panel: ABC back to the letters, a space bar, and enter. Paste a link and press enter to open it, or paste several clips with a space or a new line between them, without going back to the keys each time. Enter shows the field's own action, Go or Send, the way it does on the keyboard. The row steps aside while the clipboard is [stacked over the keys](#clipboard-and-keys-together), since the real keys are right there. It only changes the panel as it ships: if you've edited the **Panel layout**, add the keys there instead. - **History layout**: **Grid** (default) or **List**, the same choice as the [switch in the panel](#grid-or-list). - **Grid columns**: 1–4, default 2. Shown only while the history is a grid. - **Lines per clip**: **Auto** (default: six on a card, three in a row) or 1–20. - **Line spacing**: 100% to 200%, default 130%. How far apart a clip's lines sit. - **Time on each clip**: **None** (default), **Time copied** or **Time left**. See [Columns, lines and times](#columns-lines-and-times). - **Number the clips**: puts a [number](#numbered-clips) beside every clip; off by default. - **Pinned and unpinned tabs**: splits the history into [two tabs](#pinned-and-unpinned-tabs); off by default. - **Outline pinned clips**: a thin accent border around each pinned clip; off by default. - **Pin and delete buttons**: the pin and bin circles on every clip; on by default. Off, **Pin** and **Delete** move into the clip's press-and-hold popup, and a card with no number, time or rich text tag loses its bottom row. - **Clip shortcut button**: the small button at the end of a clip's bottom row. **Edited mark** (default) is the pencil on an [edited clip](#editing-a-clip), which does nothing when pressed; **Nothing** hides it; **Edit** puts an edit button on every clip you can edit; **Select** starts picking clips with that one; **QR code** puts a QR button on every text clip, which opens the [QR code generator](https://wmkeyboard.pages.dev/tools/utilities/#making-a-qr-code) on the clip's text, ready to send as a picture. - **Order of a held clip's buttons** (advanced): drag the icons under a held text clip into the order you want, first at the left. An icon the clip doesn't offer is skipped wherever it stands. - **Paste button on a held clip** (advanced, on by default): the **Paste** button at the end of that row. Turn it off for more room; a tap on the clip still pastes it. - **Type out pasted clips**: pastes a clip one character at a time, as if typed, the way a one-time code goes into a row of code boxes; off by default. A clip longer than 200 characters is pasted all at once. Bare codes are typed out either way, under the control of the One-time codes setting. - **Move a pasted clip to the top**: a clip you paste from the panel or the strip goes first in the history, as if you had copied it again, and back on the system clipboard so an app's own Paste gives it too; off by default. A pinned clip goes first among the pinned ones. Nothing is moved by a paste into a password field, and a hidden password or code, or anything pasted while the keyboard's clipboard is [kept private](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard), stays off the system clipboard. - **Rich text tag**: the small "Rich text" label under a clip that was copied with formatting; on by default. Off, the label goes, and a card with nothing else on that row loses the row. - **Keep formatting**: on by default, so a copy that came with bold, links or other formatting pastes it back into apps that take it. Off, every copy from then on is stored as plain text, pastes as plain text everywhere, and never wears the tag. The panel then draws every clip as plain text too: a link loses its colour, its underline and the icon in front of it. Clips already in the history keep what they have. - **Swipe to delete**: a sideways swipe across a clip deletes it; on by default. Off, the swipe does nothing and a **Delete** button joins the clip's press-and-hold popup. The bin works either way, while **Pin and delete buttons** is on. - **Swipe to pin**: shown while swipes are on. **Both ways delete** by default. **Right pins, left deletes** or **Left pins, right deletes** part the two directions: one pins or unpins the clip and lets it slide back into place, the other deletes. - **Undo a deleted clip**: shows the Undo bar for a few seconds after you delete a clip with the bin or a swipe; on by default. Off, a delete is immediate and final. - **Ask before deleting a pinned clip**: deleting a pinned clip, by the bin, a swipe or the press-and-hold popup, asks first; unpinned clips still go at once. On by default. - **Search bar**: enables the [search pill](#finding-a-clip-again) described above; off by default. - **Regular expressions**: shown while the search bar is on, off by default. On, the search reads what you type as a [regular expression](#regular-expressions). The same switch as the `.*` button beside the search. - **Search notes too**: shown while the search bar is on, on by default. The search also [lists passages from your notes](#searching-your-notes-too) above the clips. - **Clear button**: the [bin](#clearing-the-history) that deletes every unpinned clip after asking, and empties the system clipboard unless it holds a pinned clip; off by default. - **Link previews**: fetches a copied link's page title and description directly from the linked site; off by default, and the toggle's own description says so plainly. - **Pop-up when you copy**: off by default. It lives in the Panel fold but it isn't a panel setting, or a history one, at all, see [below](#details--edge-cases). ### Clipboard history Clipboard / Clipboard history Three groups: Limits, Pinned clips, and Screenshots and the source app. The last only shows while the history is on, since nothing is stored to carry a screenshot or a source without it. - **Maximum entries**: how many unpinned clips history keeps, from 5 to 5,000 in steps, default 100, or **Unlimited** at the end of the slider. Unlimited leaves the expiry as the only limit; with the expiry set to never as well, nothing is deleted, and a very long history makes the panel slower to open. - **Clipboard expiry**: hours before an unpinned clip is removed, 0–168, default 24 hours (0 means never). - **Longest clip kept**: **No limit** (default), or 1,000 to 100,000 characters in steps; a [longer copy is stored cut](#expiry-and-the-entry-cap). - **Pinned entries last**: moves pinned clips to the end of the panel instead of the top; off by default. Hidden while the tabs are on or pinned clips are kept in place. - **Keep pinned clips in place**: pinned clips stay where they were copied, among the others, wearing their pin; off by default. With it on, the tabs and **Pinned entries last** have no effect. - **Pinned folders**: files each tagged pinned clip in a [folder per tag](#pinned-folders); off by default. With pinned clips kept in place, the folders sit over the whole history. - **Your screenshots**: shows device screenshots in the panel alongside copied text and images; off by default. While it's on without the photos permission granted, a row appears underneath to open system settings. Screenshots start arriving the next time the keyboard opens after you grant it, without turning the row off and on. The Play Store build has no such row: Google Play only allows the photos permission to apps that can't use the photo picker for their main job, so that build doesn't declare it, and screenshots reach the clipboard only the usual way, by copying one. GitHub and F-Droid builds keep it. - **Show the source app**: records which app a clip was copied from, shown as that app's small icon at the start of the clip's bottom row and by name in the long-press info popup; off by default, and needs the Usage Access permission. It's a best-effort guess of the foreground app at copy time, not a precise log. It gets the same "grant this" row underneath until you do. ### Paste chip and codes Clipboard / Paste chip and codes Two groups, Paste chip and Codes, numbers and links. While the history is off the page shows a notice and a **Turn on** button in place of its rows, since nothing is kept to suggest from. - **Suggest the last copy**: offers your latest copy as a paste chip on the suggestion strip. On by default. The chip is as wide as what it holds, so a short copy is a short pill with its ✕ right beside it; a long one stops at about half the strip and ends in an ellipsis. While you type it keeps its narrow width for a moment after the words leave the strip, so it doesn't flicker between the two sizes as suggestions come and go. Press and hold the chip to delete that copy from the history, for something copied by mistake or needed only once. - **How long the paste chip stays**: 30 s to 30 min, default 5 minutes, or **Until dismissed** at the bottom of the slider. Shown only while "Suggest the last copy" is on. Typing never dismisses the chip, since it's meant to survive writing the message you'll paste into. Shorten it if you'd rather a copied password or address stopped being on screen. - **Recent copies on the strip**: 1 to 5, default 1, shown only while "Suggest the last copy" is on. At 1 the strip offers the single paste chip above. Set more and the chip gets a clock button beside its ✕. Tap it and the strip shows that many of your latest text clips, newest first whatever is pinned, as a row of chips you can scroll sideways, each a tap from pasted. The row never opens by itself, and it stays up until you type, tap its ✕, or move to another field, so you can paste several in a row. Press and hold a chip in it to delete that copy. Hidden (sensitive) clips never appear in it. The **Recent copies** action opens the same row from a gesture: bind it to the Copy tool's hold in that tool's settings, or to a flick or a key's long press, and the last few copies are one press away. It's under **Other** in those pickers, not on the toolbar. With this set to 1, the action shows five. - **Compact paste chip**: off by default, shown only while "Suggest the last copy" is on. With nothing else on the strip, a long copy stretches the chip to about half the row, which leaves no room for the tools beside it. On, the chip always keeps the narrow form it takes next to suggestions, so the rest of the row stays in reach while you write. - **Suggest copied codes**: **Any field** by default, and only shown while "Suggest the last copy" is on. A code on its own is otherwise treated as a secret and kept off the strip, since the chip sits in view while you type something else. A code you copied by hand is the case that rule was never aimed at: you copied it to paste it, and it sits one tap away in the clipboard panel either way. **Code boxes only** narrows it to fields that ask for a code (a number keypad, or a name like "OTP" or "verification code") if you'd rather a code never showed up over the keys of a message, and **Never** keeps the strip clear of codes entirely. Only a bare code qualifies at any setting. A copied password is never offered, unless you turn on the next option. Since 0.6.6 a copied code also shows in a password field, where the password manager chip used to cover it. - **Offer copied passwords in password fields**: off by default, shown only while "Suggest the last copy" is on. It changes a privacy rule, which is why you have to ask for it. With it on, a password field, and no other field, offers your newest copy that looks like a password or a code as a paste chip for five minutes after you copied it. The chip shows dots instead of the password, since the field hides what you type and the chip shouldn't undo that, and it disappears the moment you move to any other field. While it's up it takes the place of the password manager chip. It can only offer a copy the clipboard kept, so with **Passwords and codes** set to never save, there's nothing to offer. - **Detect codes, numbers and links**: enables the [entity chips](#pulling-codes-and-links-out-of-a-clip); on by default. Detection runs entirely on the device. - **Show the found parts as icons on each clip**: off by default, and only while detection is on. Icons with counts on each clip in place of the chip strip. - **Copy picked parts to the clipboard**: off by default, and only while detection is on. A picked code, number or link is copied rather than typed. ### Rules and privacy Clipboard / Rules and privacy **Rules**: - **Copy rules**: a nav row to the [rules that tidy every copy](#tidying-copies-with-rules). Its subtitle counts your rules. - **Phone number formats**: a nav row, shown only while detection is on, opening the mask editor covered in [Phone number formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/). Its subtitle counts your saved formats, and says every number becomes a chip once you have deleted them all. **Passwords and codes**: - **Keep the keyboard's copies private**: off by default. On, text you copy or cut with the keyboard's own keys, tools and panels never goes to the system clipboard, where any app can read it. It goes into the history instead, and the keyboard's paste keys and the panel paste it, until you copy something anywhere else or delete that clip from the history; then the paste keys paste from the system clipboard again. The copy and paste menu of other apps doesn't see it. Since Android's "Copied" bubble can't appear for a copy it never saw, the keyboard shows its own sign instead: the selection is let go with the cursor left at its end, and the copied text comes up as the paste chip on the suggestion strip, as a system copy does. That chip holds the whole strip until you type a key or move to another field, over any words and any smart chip, so it is always there to see after the copy. It's the always-on form of [Keep copies in the keyboard](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard), which holds only while incognito is on and records nothing in the history. - **Forget after you paste a password**: the paste-time purge described [above](#passwords-are-handled-deliberately); off by default. - **Sensitive clips**: keep (default), hide-and-forget, or never save. See [above](#passwords-are-handled-deliberately). - **Let the keyboard find them**: extends sensitive handling to clips that merely look like a password or code; on by default, shown only when sensitive handling isn't set to "keep." - **Forget sensitive clips after**: the hidden-clip timer, 1–120 minutes, default 5; shown only under the hide-and-forget option. Tools / Clipboard The tool page carries only what belongs to the button: - **All clipboard settings**: opens the screen above. - **Enabled**: whether Clipboard appears on the toolbar and in the toolbox. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color. - **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel. ## Backups and exports The whole-app [config backup](https://wmkeyboard.pages.dev/privacy/backup/) has its own Clipboard section, off by default when you export. It only ever includes pinned and recent *text* clips. Images and files are deliberately left out, since they live on the device they were copied on and wouldn't open anywhere else. Sensitive clips are excluded from an export regardless of that toggle. ## Details & edge cases - Every toggle and slider on this screen carries the usual [restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) once you've moved it off its shipped value, so the rows that grew one are the ones you've changed. - The panel stays open when the app changes the text of the field you're in by itself, such as a browser address bar's clear button, or the pencil that copies the page address into the bar for editing. Moving to a different field still closes it. - The clipboard tool needs the device to have been unlocked at least once since restart. Its history lives in credential-encrypted storage, so it sits out any lock-screen or pre-unlock typing until then. - Ships the same way in every build. On the F-Droid build **Extract text** reads with PaddleOCR, so it reads Chinese, Japanese and Latin-script text there, and other scripts only with the scanner's Online model reader. - Link previews make a direct, unauthenticated request to whichever site you copied a link from, not through any of the app's own network services. Turn that setting on and the site sees a request from your device the moment you open the panel with a fresh link in history. - A file or folder clip is only as good as the URI permission grant behind it. If that's lapsed by the time you tap it (the source app cleared its cache, for instance), pasting puts the URI back on the system clipboard with an explanatory toast rather than failing silently. - "Pop-up when you copy" appears in this same screen's History group but is actually a [feedback](https://wmkeyboard.pages.dev/typing/feedback/) setting. It's about the keyboard's own copy shortcuts (A/C/V/X and the text-editing panel's copy button), not clipboard history. It only fires in fields that give no copy feedback of their own, and it's off by default. - Pausing capture while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on is a separate Incognito setting, not part of this screen. So is [keeping the keyboard's copies off the system clipboard](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard) while incognito is on; **Keep the keyboard's copies private** on this screen does the same all the time. --- # Clock URL: https://wmkeyboard.pages.dev/tools/clock/ Section: Tools Summary: World clocks, alarms, timers, a stopwatch, a meeting planner, date maths and a history, in one keyboard tool that also types any time in the form you need. The **Clock** tool is a full clock app that lives in the keyboard. It keeps the time in the cities you care about, rings alarms and timers, runs a stopwatch, finds a meeting time that suits people in several time zones, counts days between dates, and keeps a history of all of it. And because it sits on a keyboard, every time it shows can go into the text you are writing, in the form you pick. Open **Clock** from the toolbox, or type `clock`, `alarm`, `timer`, `stopwatch`, `countdown`, `planner` or `date` on its own and tap the chip that appears. The panel has seven tabs along the top: **Clocks**, **Alarms**, **Timers**, **Stopwatch**, **Planner**, **Dates** and **History**. A dot on a tab means something is running there, and a number counts the alarms that are on or the timers that are running. The panel opens on the tab you used last. Swipe left or right across the panel to move to the next or previous tab; the highlight slides from one tab to the next, and the row scrolls so the tab you landed on stays in view. The gear at the end of the row opens the tool's settings. Every tab is laid out the same way: what matters most is large at the top, the rest is cards and tiles, and the one thing to do is a bar along the bottom with the main action as a wide button and the other ways beside it as round ones. Hold a round button to see its name. Cards and tiles pop in one after another when a tab opens; with **Reduce motion** on they appear at once. The settings app has a **Clock** screen too, under Tools / Clock / Open the clock. It works on the same alarms, timers, stopwatch, cities, countdowns and history, with a full-size time picker and a sound picker. A change made in one place shows in the other at once. **Add to home screen**, under **Open the clock**, puts an icon for that screen on your home screen, so it opens like a clock app. ## World clocks The **Clocks** tab starts with your own time, large, with the date, the city your phone's time zone is named after, and a small clock face. The face is light by day and dark by night, and under the time are today's sunrise and sunset. Under it, the cities you add ride a carousel of tiles that scrolls sideways. A tile is tinted by the daylight there: warm, in the keyboard's accent, where it is day, and dusky where it is night, with a sun or a moon in its corner and a stripe down its side in the same mood, so you can see at a glance who is awake. Each tile shows the city's name, its time, whether it is today, tomorrow or yesterday there, how many hours it is ahead of or behind you (`+5:30`, `−3`), and its sunrise and sunset. The last tile is **Add a city**. **Add a city**, the wide button at the bottom or the last tile, opens a search, and the keyboard types into it. Type a city (`tokyo`, `sao pa`), a country, a zone abbreviation (`ist`, `pst`) or an offset (`+5:30`, `utc-3`). Accents don't matter. The list comes from the time zone data already on your phone, about 420 places, in your language where Android has a name for them. That data names each zone after one city, so about 60 big cities that share a zone with another are added by name: Beijing, Mumbai, New Delhi, Bengaluru, Osaka, San Francisco, Washington, Mecca and the like, in English, with older spellings such as `bangalore` or `bombay` finding them too. Each one keeps its own name on its card, and its own sunrise and sunset. Nothing is looked up online. Enter adds the first match. Hold a city tile for its own row of chips under the carousel: type its time in the default format, move it left or right (unless **Sort cities by time** is on, which keeps the carousel in order of the time there, earliest first), rename it (to `Mum` or `Office`, say), or remove it. A removed city has an **Undo** for a few seconds. ### Typing a time Tap your own clock or any city tile, and a row of chips opens under it (under the carousel, for a city). Each chip shows exactly what it will type, so you pick by looking: | Format | Example | |---|---| | Time | `14:05` or `2:05 PM` | | Date and time | `Wed 14 Oct, 14:05` | | Full date | `Wednesday, 14 October 2026` | | Time and place (the default) | `14:05 Tokyo (UTC+9)` | | ISO 8601 | `2026-10-14T14:05:00+09:00` | | Relative | `in 3 hours`, `2 days ago` | | Unix time | `1791000000` | Under those is a row for Discord timestamps. Discord turns `` into a time in each reader's own time zone and language, so one message works for everyone in the server. Every style Discord has gets a chip, showing roughly what readers will see: | Discord style | Typed | Readers see | |---|---|---| | Short time | `` | `10:23 PM` | | Long time | `` | `10:23:18 PM` | | Short date | `` | `10/18/2026` | | Long date | `` | `October 18, 2026` | | Date and time | `` | `October 18, 2026 at 10:23 PM` | | Full date and time | `` | `Sunday, October 18, 2026 at 10:23 PM` | | Relative | `` | `in 2 hours`, kept current as the message ages | | Numeric | `` | `10/18/2026, 10:23 PM` | | Numeric with seconds | `` | `10/18/2026, 10:23:18 PM` | Every chip types the time now unless you pick another. **Another time** above the chips opens a time wheel set to the next whole hour, with arrows to step the day: tomorrow, next week, or a date you have to remember. The time is on the clock you tapped, so tapping the Kolkata card and picking 12:00 tomorrow gives 12:00 tomorrow in Kolkata, and a Discord chip then shows it to a reader in Karachi as 11:30. **Now** goes back to the current time. Hold a chip to make it the default. Holding a Discord chip makes Discord the default in that style. After that, holding your own clock types the default straight away, without opening the chips, and the default is the first chip when you hold a city. **Insert all clocks**, the round button beside **Add a city**, types one line with every clock: `14:05 London = 9:05 New York = 18:35 Kolkata`. A city whose date is different from yours gets its weekday, so nobody turns up a day early. ## Alarms The **Alarms** tab is a card per alarm: its time large, its label, the days it repeats as seven round pips (filled on the days it rings) and a switch. The alarm that rings next carries a line saying how long until it does ("Rings in 7 hr, 12 min"). An alarm with no days rings once, then turns itself off, like on any phone's clock. Tap a card to change it. Hold it for **Skip next**, which skips only the next ring of a repeating alarm, **Move to phone's clock**, and **Delete**, which has an **Undo**. **Move to phone's clock** hands the alarm to your phone's own clock app, with its time, days, label and vibration, and takes it off the keyboard's list (with an **Undo**, which only brings it back here). From then on it rings from the phone's clock app like any alarm you set there. **Phone's alarms**, beside **New alarm**, opens that app's list of alarms. The keyboard can't read the alarms in the phone's clock app, so they never show on this tab. Handing an alarm over uses an install-time permission Android grants without asking, and nothing reads or changes the phone's alarms otherwise. Along the bottom, **New alarm** is the wide button, and above it a row of quick alarms for 10, 20 and 30 minutes and for one and one and a half hours from now. A quick alarm is set with one tap. The editor puts the time on drums across the top that you spin with a finger, with a small tick at each step. Under them, a line says when the alarm will ring as you change it, then come the seven days, and one row with shortcuts for **Weekdays**, **Weekends** and **Every day**, a label, a vibration switch and the snooze length for this alarm. **Save** is the wide button at the bottom, with **Cancel** and **Delete** as round ones beside it. ### When an alarm rings The phone lights up with the ring screen, also over the lock screen. It shows the time, the alarm's label and a pulsing bell. **Snooze** is the big button; smaller chips under it snooze for 5, 10, 15 or 30 minutes instead. To dismiss, slide the thumb across the bar at the bottom. A slide rather than a tap means a sleepy hand can't turn an alarm off by accident. The volume keys snooze, unless you turn that off. The sound starts quietly and gets louder over 20 seconds, then plays until you answer it. If nobody answers, it stops by itself after 10 minutes, shows as missed in the history and leaves a notification saying so. With **Snooze when nobody answers** set, it snoozes instead, a few times, before it counts as missed, or every time until you answer it when the slider is at **Until answered**, so an important alarm is still ringing when you come back to the phone. The shade carries the same **Snooze** and **Dismiss** buttons. A snoozed alarm stays in the shade with the time it rings again, and its **Dismiss** ends the snooze: a one-time alarm turns off, a repeating one waits for its next day. With **Notice before an alarm** on, the next alarm appears there ahead of time too, and **Skip** skips that one ring, or turns off a one-time alarm. Several alarms or timers ringing at once share one ring screen: the slide dismisses them all, and each one after the first also has its own **Dismiss**. ### What a reliable alarm needs Android asks for three permissions before an app can ring an alarm the way a clock app does. The Alarms tab shows a line for any of them that is missing, and the tool's settings page lists them under **For alarms you can rely on**: - **Alarms and reminders** (Android 12 and later). With it, alarms ring at the exact minute. Without it they still ring, but Android may hold one back by some minutes while the phone sleeps. - **Full-screen notifications** (Android 14 and later, not in the Play Store build). With it, the ring screen takes over a locked phone. Without it, a ringing alarm shows as a notification instead. - **Notifications** (Android 13 and later). For the Snooze and Dismiss buttons in the shade, and for running timers. Each one opens with the keyboard's own explanation first, then Android's settings page for it. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). While something rings, the keyboard runs a short foreground service that plays the sound and keeps the phone awake. It is the only part of the app that runs that way, and it stops the moment you answer the ring or it gives up by itself. ### In the Play Store build Google Play allows that kind of service, and the ring screen's full-screen permission, only for apps whose main job is an alarm clock, so the Play Store build has neither. There, an alarm or a timer rings through Android's own alarm notification instead. Snooze, Dismiss and the history work the same, and the ring still stops by itself after **Stop ringing after**. What changes: - The ring shows as a notification at the top of the screen, not as the ring screen. Tap it to open the ring screen. - The sound and the vibration are the ones set for the **Alarms ringing (backup)** notification in Android's settings. **Sound and vibration** in the tool's settings opens that page. There is no sound or vibration per alarm. - There is no fade-in: the alarm starts at the volume Android gives alarm notifications. - Timers ring with the same sound as alarms. The GitHub and F-Droid builds have the service and every option above. After you restart the phone, Android forgets every alarm an app has set. The keyboard sets them again when it next starts, which happens straight after a restart when it is your default keyboard. The app has no permission to start itself at boot. ## Timers With no timer running, the **Timers** tab is a keypad. Digits fill from the right, the way a microwave's do: `1`, `3`, `0` is one minute thirty seconds. Under the reading are your presets; tap one to load it. Tap **Start**, the wide button at the bottom, and give the timer a label first with the round button beside it if you want to. The pill above the keypad switches between **Timer** and **Pomodoro**. You can run as many timers as you like. Each one is a tile with a large ring that empties as the time runs out and the time left in the middle; under the ring are its name, when it ends (or **Paused**), and round buttons for **Pause**, **+1 min** and **Stop**. One timer takes the whole width; two or more sit two to a row. A paused timer can be reset to its full length. Your presets are a row of chips above the tiles, each starting a timer with one tap, and **New timer** at the bottom goes back to the keypad. Presets start as 1, 3, 5, 10 and 25 minutes. With a time on the keypad, **Save as preset** adds it; hold a preset to delete it. Running timers also show in the shade, counting down by themselves, with **Pause**, **+1 min** and **Stop**. When a timer runs out it rings like an alarm, at full volume, and **Snooze** on its ring screen becomes **Add 1 minute**. ### Pomodoro Switch the keypad side to **Pomodoro** for focus sessions: 25 minutes of focus, a 5 minute break, again, and a 15 minute long break after every fourth focus session. **Start focus** begins the run. Each phase starts the next one by itself when it ends, so you only answer the ring. Dots under the timer show how far you are into the set of four. All four numbers are in the settings. ## Stopwatch The **Stopwatch** tab shows the time to the hundredth of a second in large digits across the top, with a thin line under them that fills once a minute. Along the bottom, **Start** (or **Stop** while it runs) is the wide button, and beside it **Lap**, **Reset** and a button that types the time with all the laps into the field. Laps are compact cards between the two, newest first, each with its own length, how much faster or slower it was than the lap before (`−0.31`, `+0.42`) and the total at that point. The fastest lap is green and the slowest is red. The stopwatch keeps running with the keyboard hidden, and the shade has its **Lap** and **Pause** buttons. **Reset** puts the run in the history with its laps. ## Meeting planner The **Planner** tab lays the same day out for your own city and every city on your list: one strip each, 24 hours across, in half hours. Working hours are the strong colour, waking hours the faint one, and night is nearly blank. The half hours where everyone is at work are brightest, and a soft band in the accent runs down every strip through them, so the good hours read as one column. Three tiles above the strips say how long that overlap lasts, the moment picked and how many of the places are at work then. Drag along any strip, or tap it, and a cursor follows your finger with a tick at each half hour. Every city's time at that moment shows beside its name, with `+1` or `−1` where it is already tomorrow or still yesterday. Along the bottom, **Insert** is the wide button and types the moment in every city at once; **Discord** types it as one timestamp that each reader sees in their own time zone, in your default Discord style; and **Best time** jumps to the first half hour that suits the most people with nobody asleep. The arrows at the top move to other days. Working hours are 9:00 to 17:00 unless you change them in the settings. They apply to every city. ## Dates The **Dates** tab does date maths with steppers, and shows each answer as tiles. **Between**, **Add** and **Countdowns** sit on one pill at the top. Each date has buttons for a month back, a day back, a day on and a month on; tap the date itself to go back to today. For a date far away, such as a birthday years back, press and hold the date and type it in the box that opens, such as `17/5/2004` or `2004-05-17`. The day comes first unless your phone is set to a country that writes the month first, like the US; a date that only makes sense the other way round is read that way, and a two-digit year up to 50 is this century. - **Between** shows three tiles: the days between the two dates (with the same span as years, months and days under it), the weeks, and the working days (Monday to Friday). The arrow between the steppers swaps the two dates. - **Add** moves a date by days, weeks, months, years or working days, and shows the result as a card with the date, beside tiles with its week number and its day of the year. A month added to 31 January gives the last day of February. - **Countdowns** is a grid of cards for the days you are counting towards (a trip, an exam), each with how many days are left, or how many have passed. Tap one to type it, such as `23 days until Exam`. Hold one for **Delete**. **New countdown**, the wide button at the bottom, asks for the date and a name; **Save** and **Cancel** take its place while you set it. **Insert**, the wide button at the bottom, types the result of the Between or Add view. ## History The **History** tab is a timeline of what rang and what was timed, newest first: a rail down the left names each day (**Today**, **Yesterday**, then dates) and the day's entries are compact cards beside it, each with its kind, its name, its length, its outcome and its time. It covers alarms (dismissed, snoozed or missed), finished and stopped timers, Pomodoro sessions, and stopwatch runs. A stopwatch run opens to show its laps. Above the timeline, two tiles hold today's focus time from Pomodoro and the total time you timed. Filter by kind with the chips; the bin clears what the filter shows, with **Undo**. The button on each card types it. A timer stopped within its first five seconds, or a stopwatch reset before five seconds, is not kept. History is kept for 90 days by default, and never more than 500 entries. ## Options Tools / Clock | Setting | What it does | Default | |---|---|---| | **Open the clock** | The Clock screen in the settings app | | | **Add to home screen** | A home screen icon that opens the Clock screen | | | **Hour format** | **Like the phone**, **12-hour** or **24-hour**, for the panel, the ring screen and what the tool types | Like the phone | | **Show seconds** | Seconds on the big clock and its face | On | | **Show local time first**, **Show UTC first** | Hold your phone's own time, UTC or both ahead of the cities | Off | | **Sort cities by time** | Lists the world clocks earliest time first, and the move buttons go | Off | | **Cities in a grid** | The city tiles two to a row under your own clock instead of on a sideways carousel. A city's chips and the ways to type its time open under its row | Off | | **Default format** | What a hold on your own clock types, and the first button when you hold a city | Time and place | | **Sound and vibration** | Play Store build only: opens Android's settings for the alarm notification, which plays the ring there | | | **Alarm sound** | Any sound from Android's picker, or silent (vibrate only). Not in the Play Store build | Default alarm sound | | **Timer sound** | The same, for timers. Not in the Play Store build | Same as alarms | | **Vibrate** | While an alarm or a timer rings. An alarm can turn it off for itself. Not in the Play Store build | On | | **Snooze length** | 1 to 30 minutes. An alarm can keep its own | 10 minutes | | **Fade in** | How long an alarm takes to reach full volume, up to 60 seconds. Timers always ring at full volume. Not in the Play Store build | 20 seconds | | **Stop ringing after** | When an alarm nobody answers stops, 1 to 30 minutes | 10 minutes | | **Snooze when nobody answers** | Each alarm can override this with a number of its own, or **Until answered**, in its dialog in the app. Off, 1 to 5 times, or **Until answered**: an alarm that stops by itself snoozes instead, until it has been snoozed that many times, and only then counts as missed. At **Until answered** it keeps snoozing and ringing again until someone answers it | Off | | **Notice before an alarm** | Off, or 15, 30, 60 or 120 minutes: the next alarm shows in the notifications that long before it rings, with **Skip** | Off | | **Notice while snoozed** | A snoozed alarm shows in the notifications with the time it rings again and **Dismiss**, which ends the snooze | On | | **Notice for a missed alarm** | An alarm that rang with nobody to answer it leaves a notification | On | | **Volume keys snooze** | On the ring screen. When off, the volume keys do nothing there | On | | **Focus**, **Short break**, **Long break** | The Pomodoro lengths | 25, 5 and 15 minutes | | **Long break after** | How many focus sessions come before a long break | 4 | | **Work starts**, **Work ends** | The planner's working hours, in half hours | 9:00 and 17:00 | | **Keep history for** | 7, 30, 90 or 365 days, or always | 90 days | The tool's settings live in the phone's device storage rather than with the keyboard's other settings, because the alarm has to read them before you unlock the phone after a restart. A [backup](https://wmkeyboard.pages.dev/reference/settings/backup/) carries them in its **Clock** section, together with the alarms, the presets and the countdowns. The clock and the calendar are the **Calendar and clock** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools and this screen leave the keyboard and the app until the bundle is switched back on; your clocks and countdowns stay. ## Details & edge cases - **Alarms ring before the first unlock.** Alarms, timers and the clock's settings are kept in the part of the phone's storage that is readable before you unlock it after a restart, so a 6:30 alarm rings even if the phone restarted overnight. The keyboard panel itself waits for the unlock, like the other tools that keep files. - **Daylight saving time.** An alarm set for a time that does not exist on the night the clocks go forward rings at the first minute that does, usually an hour later. On the night they go back, it rings the first time that hour comes round. - **Changing the clock or the time zone** moves every alarm with it: they ring at their time on the clock that is now showing. - **Timers follow the phone's clock.** A timer keeps running when the keyboard is closed and survives the keyboard being stopped, because its end time is stored. If you change the phone's clock by hand while one runs, it ends early or late by that much. - **A timer that ran out while the keyboard was not running** rings as soon as the keyboard starts again, rather than not at all. - **Two phones don't ring for one alarm.** [Sync](https://wmkeyboard.pages.dev/reference/settings/backup/) leaves the Clock section out unless you add it. The world clocks are part of the Clock section, not of the keyboard's settings. - **Nothing goes online.** The city search, the sunrise and sunset times and every calculation run on the phone. The sunrise and sunset for a city come from the city's coordinates in the time zone data, so they are for the city the zone is named after, except for the added cities like Beijing, which carry their own. - **The planner's working hours are the same for every city.** A city with a different working week or hours still uses yours. --- # File manager URL: https://wmkeyboard.pages.dev/tools/files/ Section: Tools Summary: Pick a file for a tool without leaving the app you are typing in, and rename, copy, move or delete files while you are there. When a tool wants a file, the keyboard can show its own file manager in a sheet over the keys. Android's picker takes over the screen and sends the keyboard away; this one leaves the app you are typing in where it is, and the tool is back as soon as you have picked. It isn't in the Google Play build, which cannot hold the permission it needs. There, every tool below opens Android's picker as before. ## Where it opens - **Files** and **Browse all** in the [Gallery](https://wmkeyboard.pages.dev/tools/gallery/). - **Files** in [Send files](https://wmkeyboard.pages.dev/tools/send/). - **An image** and **A file** in the **+** menu of the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) on the keyboard. The camera option still opens the camera. - **Files…** in [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/). - The picture button in the [QR scanner and the text scanner](https://wmkeyboard.pages.dev/tools/scanner/), and in the [camera](https://wmkeyboard.pages.dev/tools/camera/). Settings screens keep Android's picker, since you have already left the app you were in by then. So do backup folders, which need a grant only Android's picker can give. ## The first time The first pick asks for **All files access** (on Android 7 to 10, the storage permission). The keyboard explains what that covers before it sends you to Android's switch. The grant reaches every file in shared storage, because Android has no narrower one for browsing. The keyboard reads only the folders you open and the names you search for. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/#the-ones-that-arent-permissions). You don't have to allow it. **Android's picker** on the same screen does this pick the old way, and **Always use Android's picker** turns the file manager off. ## Picking The sheet opens at the keyboard's height. Its expand button makes it nearly as tall as the screen. The list starts at **Places**: Recent, each storage volume, and shortcuts such as Downloads and Pictures. The row above the list shows where you are, and any step in it is a tap away. Back goes up a folder, and out of the top back to the tool. Only files the tool can take are listed. The QR scanner shows pictures, for example. A tool that takes one file gets it at the first tap. For a tool that takes several, each tap adds a file, you can move between folders while you pick, and **Add** hands them over. The search button looks through file names in the open folder, or everywhere from **Places**. The ⋮ menu sorts by name, date or size (choose the same one again to reverse it) and shows hidden files. For anything not on the phone, such as Google Drive, the header's file button opens Android's picker for this pick. ## Changing files Press and hold a file or a folder to select it. Tap others to add them. The bar along the bottom then offers: - **Rename**, with one thing selected. - **Copy** and **Move**, which carry the selection until you open a folder and press **Copy here** or **Move here**. A name already taken there gets a number after it, so nothing is overwritten. - **Delete**, which asks first. It is permanent: Android has no recycle bin for files. A folder goes with everything in it. **New folder** is in the ⋮ menu. Copies, moves and deletes run in the background with a **Stop** button. A copy you stop takes back the part it had made. Android keeps `Android/data` and `Android/obb` closed even with this permission, so those folders look empty. ## Turning it off Tools / Built-in file manager switches it off, and every tool goes back to Android's picker. Taking away All files access in Android's settings brings back the first-time screen. Nothing opens over the lock screen, whatever the setting says. A pick made there goes to Android's picker. --- # Find & replace URL: https://wmkeyboard.pages.dev/tools/find-replace/ Section: Tools Summary: Search the text you are writing and replace what you find, with match case, whole words, regular expressions and your recent searches. Plenty of apps have no search of their own, or one that only finds. **Find & replace** works in any text field: it searches what you have written, steps through the matches, and replaces one or all of them. ## Opening it Tap **Find & replace** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. If you have a few words selected on one line, they become the search, the way Ctrl+F works on a computer. Otherwise it opens empty. The same panel opens from the [selection actions bar](https://wmkeyboard.pages.dev/smart/selection-actions/#find-and-replace)'s **Replace** chip, already holding what you selected. Password fields can't be searched; the tool says so and opens nothing. ## Searching The panel has two fields, **Find** and **Replace with**. Tap one and type into it; the keys go to the panel, not to the app. While one of them is focused, its word suggestions sit in the row above the keys. With the toolbar on a row of its own, the suggestion strip above the panel gives up its row while the panel is open, so there is only one strip, and the toolbar row stays open. The count beside the arrows says which match you are on and how many there are (**3 of 12**). **‹** and **›** step through them and wrap round at the ends. Enter in the Find field goes to the next match. The **×** at the end of a field empties it in one tap. The current match is selected in the field itself, and also shown in its line at the bottom of the panel with the match marked, so you can see it even when the keyboard covers that part of the text. The field is read again on every step. Some editors, code editors and notes apps in a web page among them, only draw the part of a long text that is on screen, so the text changes under the panel as it jumps from match to match. After selecting a match the keyboard also reads back what ended up selected. If it isn't the match, which happens in editors that count their text differently from the way they hand it over, the keyboard looks for the match close by and selects it there. Four toggles change how the search reads and what a replacement writes: - **Aa** matches case. - **[ab]** matches whole words only, in any script. - **.\*** reads the Find field as a regular expression. `$1` in the replacement stands for the first group. A pattern that isn't valid says so, and one that takes too long on the text is stopped rather than left to hang. In the replacement, `\n` is a line break and `\t` a tab. - **AB** preserves case: each replacement takes the case of the text it replaces, so replacing `cat` with `dog` turns `Cat` into `Dog` and `CAT` into `DOG`. Mixed case such as `iPhone` is replaced as you typed it. ## Replacing **Replace** swaps the current match and moves on to the next one. Enter in the Replace with field does the same. **All** replaces every match at once, and **Undo** takes it back in one go too. A field that doesn't report where its text is (a few web views) still lets you find, but not replace in place. ## Recent searches While the Find field is empty, the bottom of the panel offers your last ten searches. Tap one to search for it again, or **Clear** to forget them. A search is remembered once you use it, by stepping to a match or replacing, not while you type it. Nothing is remembered in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), in a field that asks not to be learned from, or in a password field. To stop remembering searches altogether, or to clear them from Settings: Tools / Find & replace / Remember searches Find and replace is the **Find and replace** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on. --- # Gallery URL: https://wmkeyboard.pages.dev/tools/gallery/ Section: Tools Summary: Your photos and videos inside the keyboard: albums, multi-select, a full-screen viewer, a photo editor, and a send that hands the app only a copy. The Gallery tool puts your photo library in a sheet over the keys, the way a chat app's attach button does. Pick a few photos or videos, caption them, edit one if you like, and send. The app you are typing in never sees your library: it gets a copy of what you picked and nothing else, with the location taken out by default. all. ## Opening it Open **Gallery** from the toolbar or the [toolbox](https://wmkeyboard.pages.dev/tools/overview/). Typing *gallery*, *photos* or *pics* also offers it as a [suggestion chip](https://wmkeyboard.pages.dev/smart/chips/). The first time, the panel asks for access to your photos and videos. The keyboard shows its own explanation before Android's dialog, and you can refuse. Without access, **Pick instead** opens Android's photo picker, which needs no permission. On Android 14 and later you can also allow only some photos; the panel then says so, and **Choose more** asks again. Photos and videos are two permissions from Android 13 on. If photos were already allowed for something else, such as screenshots in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/), the grid shows photos only and a banner says videos are not allowed yet. **Allow videos** asks for them. Turning off **Show videos** hides the banner too. The sheet opens at the keyboard's own height. Drag the bar on top of it, or press the expand button in its header, to make it nearly as tall as the screen. **Open at full height** under Tools / Gallery makes that the starting size. The viewer and the editor always use the full height. ### In the Google Play build The Play Store build cannot hold the photo library permission, so there the tool has no grid of its own. A tap on **Gallery** opens Android's photo picker straight away. Whatever you pick is prepared with the same size and privacy settings and sent the moment the keyboard is back in the text box. The rest of this page describes the grid in the GitHub and F-Droid builds. ## Albums and filters The chip at the start of the header names the album you are in. Tap it for the list of albums, each with its newest photo and a count: - **Recents**: everything, newest first: by the date a photo was taken, or, for a picture with no camera date (one saved from a browser, a sticker, an edited copy), the date it was saved. - **Sent from the keyboard**: what you sent through this tool before, the most recent send first. - **Favorites**: what your gallery app has starred, on Android 11 and later. - **Camera**, **Screenshots**, **Videos** and **GIFs**, when they have anything in them. - Then every folder on the phone, the biggest first. Next to it, **All**, **Photos**, **Videos** and **GIFs** narrow whatever album is open. **Open the last album** brings you back to the album you left; with it off, the panel always opens on Recents. ## Searching your photos With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on **Text and pictures** and its model downloaded, the header has a search button. Tap it and type what a photo shows, in any language: "beach at sunset", "receipt", "my dog in the snow". The keys come up under a short strip of the grid, and the photos that fit best fill it, best match first, a moment after you stop typing. Enter or **Done** gives the keys back and keeps the results. The query then stays in the header in place of the album chip; tap it to change the words, or its cross to go back to the album. The results are photos only, from every album. Picking, the viewer and sending work on them as on any album. Choosing an album or a filter ends the search. The search finds a photo once the keyboard has read it. That happens on the phone, in the background, while the gallery panel is open: a few dozen photos at a time, newest first, up to your 3,000 newest. Nothing is uploaded. The first time, a large library takes many openings to work through, and a photo not read yet is simply not found. Without the model, or with search by meaning on Text only, there is no search button and the panel is the grid it always was. The Play Store build has no grid and so no search. ## The grid The first square is a camera tile, which opens the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/). Every other square is a photo or a video. Videos show their length, GIFs say GIF, starred photos carry a star, and a photo you have sent before carries a small send mark. - **Pinch** the grid to change the number of columns, from two to six. The setting remembers it. - **Drag the handle** that appears on the right edge while the grid scrolls to move through years of photos fast. A bubble shows the month under your finger. When the handle is hidden, the right edge is free, so taps on the last column pick photos as usual. - New photos appear by themselves: a screenshot taken while the panel is open joins the top of the grid. ## Picking - **Tap the round check** in a photo's corner to pick it. Picked photos shrink a little, get an accent ring, and show their place in the send order. - **Tap the photo itself** to open it full size. Set **Tap on a photo** to **Pick it** if you would rather a tap picked, as Signal does; the corner check and the viewer still work. - **Hold a photo** to peek at it big. Lift, and it goes away. - **Hold, then slide** across the grid to pick every photo you pass, the way Google Photos does. Slide back and they drop out again. Near the top or bottom edge the grid scrolls under your finger. A slide that starts on a picked photo unpicks instead. You can pick up to 20 at once; **Pick up to** changes that, from 1 to 100. ## The send bar As soon as something is picked, a bar rises from the bottom: - **The picks in order.** Hold one and drag it sideways to move it. Tap a photo to edit it, a video to watch it. The cross takes one out. - **The caption.** Tap it and type. What you type goes into the message box after the pictures, or before them, as **Caption** says. Turn the box off there if you never use it. - **The size chip.** **Original**, **HD** (2560 px on the long side), **SD** (1280 px) or **Small** (800 px), with an estimate of the total under it. Tap to step through them. The starting size is **Picture size** in settings. Videos always go at their own size. - **Send.** Hold it for the other ways out: **Send at full size**, **Send without the caption**, **Share to this app**, **Share to another app**, **Copy to the clipboard**, **Save a copy to the gallery** and **Clear the selection**. Each item goes into the text box in turn, a moment apart, so the chat app has time to take each one. Some apps, WhatsApp and Signal among them, answer the first picture by opening a send screen of their own, which takes the text box away. The keyboard waits a moment after the first picture to see whether that happens, and if it does, all of them go to the same app together through its share screen, where you pick the chat again. ### When the app does not take pictures Some text boxes accept pictures from a keyboard and some do not, and fewer accept videos. What happens to the ones a box refuses is up to **When the app does not take pictures**: - **Share them to that app** (the default): Android's share screen opens for the app you were typing in, with the pictures and the caption. Most chat apps then ask which chat they go to. - **Copy them to the clipboard**, to paste yourself. - **Ask each time**, with Share and Copy on a card. A picture tapped in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) that the box refuses follows the same setting. ## The viewer The viewer pages through the album you opened it from, or through your picks when you open it from the send bar. - **Swipe** sideways for the next or the previous one, **pinch** or **double-tap** to zoom, and drag a zoomed photo to look around it. - **Drag down** to put the viewer away. - **Tap** the picture to hide the bars for a clear look, and again to bring them back. - Videos play while they are on screen and loop. Tap pause, or drag the line under them to seek. This is a preview: what is sent is the video file itself. - The **check** at the top right picks or unpicks what you are looking at. **Send** with nothing picked sends just this one. - **Details** shows when it was taken, its size in pixels and in bytes, its length and its folder. ## The editor Tap a picked photo in the send bar, or **Edit** in the viewer, to edit it. Editing leaves the photo in your library alone unless you save over it (see below). Every step can be undone and redone, **Remove every edit** starts over, and holding the compare button, or holding the picture in Filters and Adjust, shows the original. Every photo in the bar carries a pencil mark to show it opens the editor; on an edited photo the mark turns the accent colour, and the photo goes out with its edits at the chosen size. - **Crop.** Drag the corners or the sides, or drag inside the frame to move it. Shapes: **Free**, **Original**, **Square**, **4:3**, **3:4**, **16:9**, **9:16**, **3:2** and **2:3**. **Turn a quarter** rotates it, **Mirror** flips it, and the **Straighten** slider tilts it up to 45 degrees either way while it zooms just enough to keep the corners filled. - **Draw.** **Pen**, **Marker**, **Neon** and **Arrow** draw in any of twelve colours at the size you set. While you move the size slider, a dot of that size and colour sits in the middle of the photo, so you see how thick the line will be before you draw. **Eraser** takes back drawing, never the photo. **Blur** and **Pixelate** paint over a face, a number plate or an address so it cannot be read. - **Text.** Tap the photo where the text should go, or **Add text**, and type with the keyboard. **Plain**, **Outline**, **Box** and **Glass** styles, three alignments, and the colour row. Drag a text to move it, pinch to resize it and turn two fingers to rotate it. Tap it again to change the words. Drag it onto the bin that appears at the bottom to delete it. - **Stickers.** Thirty-two popular emoji, then your own [sticker packs](https://wmkeyboard.pages.dev/emoji/stickers/). They move, resize, rotate and delete the way text does. - **Filters.** Fourteen looks, each previewed on your photo: Original, Vivid, Warm, Cool, Mono, Noir, Fade, Sepia, Chrome, Dramatic, Matte, Retro, Lush and Dusk. Tap one, then set how strongly it applies. - **Adjust.** Brightness, contrast, saturation, warmth, tint, exposure, highlights, shadows, fade, vignette, grain and sharpen, each on its own slider. A dot marks the ones you changed. Double-tap a slider to put it back. - **Upscale.** Redraws a small photo two or four times larger, on the phone, sharper than stretching it would be. Pick **Photo**, or **Drawing or text** for a screenshot, a drawing or a meme (a PNG starts on that one), then **×2** or **×4**, each shown with the size it makes. The result goes up to 4096 pixels on its long side, so ×2 is offered up to 2048 pixels and ×4 up to 1024; a larger photo has nothing to offer. A bar shows the progress and **Cancel** stops it. Once it's done the upscale is a step like any other: Undo takes it back, and **Original size** removes it. Your crop, drawings and text stay where they were. The first time, the upscaler downloads: a 2.2 MB network from the project's data repository, and on the GitHub and Play builds the engine the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/) runs on, if that isn't on the phone yet. The photo still goes out at the size the send bar's chip allows, so choose **Original** to send all of an upscale; a save to the gallery keeps all of it. **Pinch** with two fingers in any tool to zoom in on the photo, and drag with two fingers to move around it, for drawing or blurring something small. Zooming only changes the view; the photo keeps its edits as they are. A chip at the top shows how far in you are, and a tap on it fits the whole photo again. A pinch that starts on a text or a sticker resizes that instead. ### Saving to the gallery The save button in the editor's top bar, next to Done, keeps the edited photo on the phone. It works once the photo has an edit. - **Save as a copy** adds a new photo next to the original, in the same folder, named after it with *_edited* on the end. A photo from a folder Android does not let apps add pictures to goes under Pictures/WM Keyboard instead. The original stays as it was. - **Replace the original** writes the edited photo over the one in your library. Android asks first whether the keyboard may change a photo another app made. This cannot be undone, and the editor closes afterwards, since the edits are now part of the photo. Either way the photo is saved at up to 4096 pixels on its long side, in its own format (JPEG, PNG or WebP; other formats are saved as JPEG), and keeps its date and camera details. It does not keep its location: Android hides that from the keyboard. **Replace the original** is only offered for JPEG, PNG and WebP photos from the library, not for HEIC photos or ones picked through Android's picker. The quality chip in the send bar does not apply to these saves. **Keep edited photos** in settings also saves each edited photo you send, under Pictures/WM Keyboard. ## Privacy What the app you type in receives is a copy in the keyboard's own storage, offered through Android's content system. It cannot see your library, other photos in the album, or the file the copy came from. - **Remove the location** (on) takes the GPS position out of every photo and video sent. On Android 10 and later the keyboard never reads the location in the first place, because Android hides it from apps without a separate permission that this one does not ask for. - **Remove the camera details** (on) also takes out the camera's make and model, the serial numbers, the owner's name and the time the photo was taken. A photo sent at a smaller size is re-encoded, which leaves all metadata behind anyway. A video is copied into a new container, which keeps its picture, sound and rotation and nothing else. - **Random file names** (on) sends copies under a random name such as IMG_3f9a01c2d4.jpg, not the photo's own. - **Remember what you sent** (on) keeps a list of the library ids you sent, for the Sent album and the send mark. It holds no pictures and no names, it stays on the phone, an incognito text box adds nothing to it, and **Clear the sent list** empties it. - The copies are deleted from the keyboard's storage a day after they were made. The library is read only while the panel is open. Nothing is uploaded: the tool works the same with no internet at all. With [photo search](#searching-your-photos) on, the numbers the keyboard makes from your photos stay on the phone, out of backups, and are deleted when you turn search by meaning off or take the photos permission away. ## Settings Everything above is under Tools / Gallery: access, **Tap on a photo**, **Columns**, **Camera tile**, **Show videos**, **Fast scroll by month**, **Open at full height**, **Open the last album**, **Vibrate on each pick**, **Folders**, **Picture size**, **Pick up to**, **Caption**, **When the app does not take pictures**, **Close after sending**, **Keep edited photos**, and the privacy switches. The Play Store build shows only the ones that apply to the picker. **Folders** lists the folders on the phone that hold photos or videos as a tree under DCIM, Pictures and the rest, with a search and a tick for each, so a whole branch can go at once. Untick one and its photos leave the gallery altogether: its own album goes from the list, and nothing from it shows in **Recents**, **Favorites** or any other collection, or in a search. A folder of saved receipts or a messaging app's downloads can stay out of the way. Tick it again to bring it back. The gallery and the camera are the **Camera and gallery** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on. --- # Handwriting URL: https://wmkeyboard.pages.dev/tools/handwriting/ Section: Tools Summary: Write with your finger or stylus, and on-device recognition turns the ink into text. Handwriting turns a full-width canvas into a keyboard. Write a letter or a word with a finger or a stylus, and the keyboard recognizes it on-device and drops it into your text. Three recognisers sit behind it. Google ML Kit's digital ink models read 126 languages google. [zinnia](#zinnia-for-chinese-and-japanese), an open-source recogniser, reads Chinese and Japanese in every build. [PaddleOCR's text recognisers](#paddleocr-for-other-scripts) read Latin, Cyrillic, Arabic, Devanagari, Tamil, Telugu, Thai, Greek and Korean writing wherever ML Kit has no model for the language, which in F-Droid's build is every language. ## Opening the panel Open **Handwriting** from the toolbar (or the toolbox if you haven't pinned it) to swap the keyboard for a full-width writing canvas. Under it is a row of four keys: abc, which closes the panel and returns you to normal typing, Space, Delete and Enter. They are ordinary keys, so the space bar's swipes and the delete key's repeat work as they do on the letters. You can move them, or add others, in the [panel's layout](#the-panel-is-a-layout). Write anywhere on the canvas. Strokes render live as you draw, and dim to partial opacity while recognition is running. A single tap still counts as a stroke (enough to register a period or the dot on an "i"), so short marks aren't lost. If two or more of your enabled languages have a handwriting model, a small chip in the top-left corner shows the active one. Tap it to cycle through the rest. With only one language available, no chip appears. ### Fixing a stroke Two different controls undo ink, and they don't do the same thing: - **Undo last stroke**, an icon that appears in the bottom-left corner once you've drawn something, removes only the most recent stroke and re-runs recognition on whatever ink remains. - The panel's Delete key is different: while ink is still waiting to be recognized, pressing it throws away all pending strokes at once rather than deleting already-committed text. It's the natural "no, not that" reaction mid-word. Once the strokes have already turned into text, Delete goes back to deleting committed characters normally, and it auto-repeats on a hold like it does on the main keyboard. ## The panel is a layout Tools / Handwriting / Panel layout The handwriting panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the emoji, clipboard, text editing and trackpad panels. Out of the box it is the writing area over an abc, Space, Delete and Enter row. Open the layout editor from **Panel layout** on the tool's page (or from **Panel layouts** in the layouts gallery) to rearrange that row, add keys beside or above the canvas, or give the canvas more or less height. The writing area is a component, and the one the layout cannot lose: a layout without it gets one added back. The language chip, the undo button and the full-screen button stay on the writing area wherever you put it. Each key layout also has a Handwriting tab of its own, so one language can carry a different handwriting panel from the rest. Before this panel became a layout, its four keys sat in a column down the right side of the canvas. The shipped layout puts them in a bottom row instead, so the canvas gets the full width. ## Writing over the whole screen Tools / Handwriting / Full-screen writing Tap the expand button in the top-right corner of the panel and the canvas leaves the keyboard: it covers the whole screen, see-through, so you write straight over the app and watch each word land in the text field. The keyboard shrinks to a small bar at the bottom with the recogniser's other readings of the last word (tap one to swap it in), and keys for undo stroke, Delete, Space, Enter, a shrink button back to the panel, and a keyboard button that closes handwriting. It works the same with the keyboard floating. While the canvas is up, the app keeps its full height and never jumps around under your ink. A see-through canvas has to decide which touches are writing and which are meant for the app. **Full-screen writing** picks how: - **Pen button** (the default): a pen button on the bar switches the canvas between writing and using the app. While it shows the pen, every touch on the screen is ink. Tap it and it turns into a hand: now every touch goes to the app underneath (scroll, tap a link, move the cursor) while the canvas stays out of the way. Works with a finger or any stylus. - **Stylus** (Android 14 and later): a stylus writes and a finger keeps using the app, at the same time, with no button. Start writing with the pen over a text field and Android hands the stroke to the keyboard, which then takes pen ink anywhere on the screen until you stop for ten seconds. This rides on Android's own stylus handwriting, so Android's **Write in text fields** setting for styluses has to be on. It sits with the phone's stylus settings, and phones that ship with a pen usually have it on already. It works in any app's text field even with the keyboard closed, since Android starts it from the pen. The row is not shown on Android 13 and older. The stylus mode only works with a stylus the screen reports as one (S Pen, a Wacom-type EMR pen, a USI pen, most Bluetooth styluses); a finger or a passive rubber-tip pen looks like a finger and goes to the app. ## Handwriting as a layout Chinese, Japanese and Korean have a **Handwriting** layout of their own, the way Gboard offers one, and so do 296 more languages, Burmese among them: every language a recogniser here reads in the script the keyboard writes it in. A language with a model only from ML Kit, Burmese again, says no recogniser is available on the F-Droid build. Turn it on with the language's other layouts and pick it with the globe key: the writing area takes the place of the letter keys, and a normal bottom row (?123, globe, punctuation, Space, Delete, Enter) stays under it. The [number row](https://wmkeyboard.pages.dev/typing/basics/#number-row-and-numerals) stays off on this layout even when it is on for the others, because there are no letter keys for it to sit above; the ?123 layer still has it. Ink is read in that layout's language, with everything below about models, delete and undo working the same. There's no language chip on it; the globe key changes language. On the F-Droid build these layouts write Chinese and Japanese through zinnia, and Korean through [PaddleOCR](#paddleocr-for-other-scripts). ## Handwriting without opening the panel Typing / Gestures / What a swipe does There's a second way to write by hand that doesn't need the panel at all. Turn on **Glide typing** and a **What a swipe does** choice appears underneath it. Set that choice to "Handwrite". A swipe across the letter keys then draws a stroke on the keyboard itself instead of gliding a word. Recognition and insertion work exactly like the panel, and a plain tap still types its key. The default is "Type words", so this is off until you change it. Picking "Handwrite" swaps one set of options for another: - **Extra time for a dot or a cross** appears: default **700 ms**, adjustable from 0–1500 ms. Letters like i, j, and t need a second mark after the main stroke. For this long after you finish a stroke, a tap over the letters is folded into that same character, as the dot or the cross, instead of typing the key underneath it. Set it to 0 to turn the leeway off. - The glide-only rows go away: **Glide across the spacebar**, **Ask when a glide is unclear** and the four rows under it, **Space after a glided word**, **Backspace removes a glided word** and **Delay after typing**. None of them means anything once a swipe draws ink rather than a word. If the language you're typing in has no downloaded model yet, a one-time notice points you at the Handwriting settings screen instead of silently failing. ## Downloading language models Tools / Handwriting / Recognition models tools/handwriting-model-manager — Recognition models: one row per enabled language, each with its own download status. The **Recognition models** section lists one row per enabled language that a recogniser can read, with its own status: not downloaded (with a download button, and the space that model would take), downloading (counting megabytes toward the total), downloaded and working offline (with a delete/trash icon), or download failed. Recognition runs entirely on-device once a model is on your phone, so after that one download the language works with no network connection at all. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else does and doesn't touch the network. Model sizes vary more than you might expect, so each row shows its own rather than one rounded figure for everything. Bangla installs 24.6 MB, English (US) 25.8 MB, French 26.1 MB, and Chinese only 7.9 MB. Roughly half of that crosses the network, because the files travel compressed and get unpacked on the phone. The numbers come out of ML Kit's own manifest rather than an estimate, so the progress readout counts real megabytes toward a real total. You can start a download from the panel too. Open Handwriting in a language whose model is missing and it offers a **Download** button instead of a canvas. The panel shows the same running total, and that button turns into **Cancel** while the download is going, so you are never stuck watching it. The recogniser itself is a separate file on most builds: ML Kit's handwriting engine, about 6.9 MB on the GitHub build (one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/offline/#feature-libraries)) and about 3 MB from Google Play on the Play Store build. Until it is on the phone, the offer to download says the language needs two downloads, the handwriting engine first and then the model, with both sizes where the build knows them. Play publishes no size before its download starts, so on Play only the model's size is shown. The same **Download** button covers both. While they come in, the panel says **Step 1 of 2** for the engine and **Step 2 of 2** for the model, the settings row and the notification say the same, and each step counts its own file's bytes. Once the engine is here, every other language's row drops it, since one engine reads them all. On the Play build, until the engine is here the rows cannot say how much a model takes, and the languages list still shows every language the recogniser knows, because that list is built into the app. The first time handwriting is used after the keyboard starts, the model check and any download wait about ten seconds before anything happens. ML Kit fetches the models through Google's Mobile Data Download service, and that service sets up its full list of models before it answers anything. On a fresh start WM Keyboard clears the service's list first, because re-listing models it already knew about could leave every check and every download waiting forever. A model you already downloaded stays downloaded through this, and nothing is fetched again. Downloads can also take longer than their size suggests, because the service can go quiet for a while before any bytes move. That is the real reason the readout counts megabytes: it tells you whether a download is slow or actually stuck. If nothing arrives for twenty seconds, both the panel and the settings row say so. After ninety seconds of silence the download gives up and clears out what it had already written, so trying again starts fresh instead of rejoining a transfer that already died. The list of ML Kit's models isn't hardcoded in WM Keyboard. It's read straight out of ML Kit's own digital ink library, then filtered down to the languages you have enabled. So enabling a new keyboard language surfaces its handwriting model on its own, with nothing WM-Keyboard-specific to configure. The flip side: the catalog of *which languages have a model at all* only grows when ML Kit itself grows. A phone that cannot download, or a build with no internet permission, can import a model instead. The **Import from a file** row under the models lists each language's files under **Get the files**: two or three `.zip` files from `dl.google.com`, the same ones ML Kit would fetch. Download them on any device, move them to the phone and pick them under **Import**. On the offline build the recogniser's own library has to be imported too; the import report says so, and the model finishes installing once it is in. See [Importing instead of downloading](https://wmkeyboard.pages.dev/privacy/offline/#importing-instead-of-downloading). If an enabled language has no ink model, it's called out separately underneath the list: writing in that language falls back to the nearest language that does have one, rather than failing outright. And if none of your enabled languages has a model, the list itself is replaced by a note asking you to add a language on the Languages screen first. A **Languages and layouts** row at the bottom of the section takes you straight there either way. ## zinnia for Chinese and Japanese Tools / Handwriting / Offline recogniser for Chinese and Japanese [zinnia](https://github.com/taku910/zinnia) is a small open-source recogniser (BSD licence) that runs on the phone with models from the [Tegaki project](https://github.com/tegaki/tegaki). It reads Chinese and Japanese, and it takes over handwriting for them in three cases: - In the F-Droid build, which has no ML Kit. zinnia is the only recogniser there, so handwriting works in Chinese and Japanese and in no other language, with no switch to set. - For Cantonese, in every build. ML Kit has no Cantonese model, so Cantonese writes with zinnia's Traditional Chinese one. - When you turn on Offline recogniser for Chinese and Japanese, a switch under Input that appears where ML Kit is in the build. It is off by default, so ML Kit stays the recogniser for both languages until then. Chinese has two zinnia models, one for Simplified and one for Traditional characters, and the Recognition models list shows both. Which one the panel writes with follows **Traditional characters** in the Chinese settings: off writes Simplified, on writes Traditional. | Model | Characters | Download | |---|---|---| | Japanese | 6,420 (kana and the common kanji) | 26.1 MB | | Simplified Chinese | 6,763 (all of GB 2312) | 26.8 MB | | Traditional Chinese | 11,853 (Big5) | 52.2 MB | The download is the model and, the first time, zinnia's own library (about 450 KB). One press fetches both; the offer says so first, and the panel and the settings row name the step that is running (**Step 1 of 2** for the library, **Step 2 of 2** for the model). On the F-Droid build the library is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/), so it needs **Download feature libraries** turned on under Privacy first, and the panel says so if it is off. The models come from the project's data repository and are checked against the checksum the app ships with. A phone that cannot download can import a model file: the model's row under **Import from a file** links to it. zinnia reads one character at a time. Write a line of characters with a gap between each, and the keyboard cuts the ink at the gaps and reads every way of joining the pieces back up, so a character whose halves stand apart (石 and 艮 in 硍) is not read as two. The reading zinnia is surest of commits, and the next ones wait in the suggestion strip. It is less accurate than ML Kit, especially for hurried or cursive writing, and it does not use the text before the cursor. ## PaddleOCR for other scripts Where ML Kit has no model for a language, handwriting reads with [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)'s PP-OCRv5 text recognisers (Apache licence), one for each script. In F-Droid's build, which has no ML Kit, that means every language below. No open recogniser exists that reads pen strokes in these languages, so the keyboard draws your ink as a line of printed text and reads that line the way the [text scanner](https://wmkeyboard.pages.dev/tools/scanner/) reads a photo. | Model | Languages | Download | |---|---|---| | Latin script | English and about 45 more, among them French, German, Spanish, Portuguese, Italian, Dutch, Polish, Turkish, Indonesian, Swahili and Vietnamese | 8.0 MB | | East Slavic | Russian, Ukrainian, Belarusian | 7.9 MB | | Cyrillic script | Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, Tajik, Tatar, Bashkir and 19 more | 8.0 MB | | Arabic script | Arabic, Persian, Urdu, Pashto, Sindhi, Uyghur, Sorani and Balochi | 8.0 MB | | Devanagari | Hindi, Marathi, Nepali, Sanskrit, Konkani, Maithili, Bhojpuri and more | 7.9 MB | | Tamil, Telugu, Thai, Greek | one language each | 7.8 to 7.9 MB each | | Korean | Korean | 13.4 MB | Every language of a script shares its model, so the Recognition models list shows one row per script for the languages you have enabled, and one download covers them all. The first download also fetches MNN, the open-source library that runs the model, which the sticker generator and the F-Droid text scanner use too. The offer to download says so before you press it, and the readout names the step that is running. On the F-Droid build MNN is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/), so **Download feature libraries** has to be on under Privacy first, and the panel says so if it is off. The models come from the project's data repository and are checked against the checksums the app ships with. A phone that cannot download imports the model's two files instead: its row under **Import from a file** links to both. A text recogniser reading handwriting gets a lot of letters wrong, so each word it reads is weighed against the words the keyboard would suggest if you had typed what it read. The recogniser gives every candidate a score for how well that exact spelling fits your ink, and the best fit wins. What the recogniser read on its own still competes, so a name or a word missing from the dictionary can come through. On 192 English words of real handwriting traced into strokes, it read 43% right on its own and 52% with the dictionary's help. ML Kit does much better, which is why PaddleOCR never takes over a language ML Kit has a model for. Thai is written without spaces, so it skips the dictionary step. Write a word or a few words on one line. Several words written in one go are read separately, each with the word before it as context, and the strip offers other readings of the last one. ## Options Tools / Handwriting / Input The panel is also the **Handwriting** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list, and the models you downloaded stay on the phone. - **Stylus only**: off by default. With it on, only a stylus (S Pen or similar) draws on the canvas, and finger touches are ignored completely. Even with it off, a finger touch is briefly ignored right after a stylus stroke, so a palm resting on the screen while you write doesn't add stray ink. - **Automatic space**: on by default. Inserts a space between words you write one after another, but never before punctuation, and never next to Chinese, Japanese, Thai or another script written without spaces. - **Offline recogniser for Chinese and Japanese**: off by default, and shown only where ML Kit is in the build. With it on, Chinese and Japanese read through zinnia; see [zinnia for Chinese and Japanese](#zinnia-for-chinese-and-japanese). - **Panel layout**: opens the layout editor described in [The panel is a layout](#the-panel-is-a-layout). - **Pen thickness**, **Pen color** and **When ink turns into text**: the pen's width from 1 to 12 dp (default 4), its color (the theme's accent until you pick one), and how strokes leave the canvas: Dissolve (default), Fade, Shrink, Rise or Disappear at once. - **Panel height**: opens the canvas taller than the keyboard, without changing the keys. The emoji panel has its own, under Emoji. - **Recognition pause**: default **700 ms**, adjustable from 300–2000 ms. How long the canvas waits after your last stroke before it sends the word off for recognition. Shorter feels snappier. It can also cut a multi-stroke letter or a Bengali conjunct in half if you pause mid-letter, where a longer wait gives you room to breathe between strokes. Whatever the Recognition pause slider is set to, handwriting in these languages never commits sooner than 1200 ms after the last stroke. Bengali glyphs are frequently built from several separate strokes (conjuncts, the matra, vowel signs), and a kanji, hanzi or Hangul block can take ten strokes or more. A natural pause between them would otherwise trigger an early, wrong commit. ## Details & edge cases - **Palm rejection** happens automatically and isn't a setting: once the canvas sees a stylus point, it ignores finger touches for 800 ms afterward. - There's no hover preview, no air-gesture support, and no S Pen side-button handling. The stylus-specific behaviors are palm rejection and the **Stylus** full-screen mode. - Switching between the panel and full screen throws away ink that hasn't been recognised yet: it was drawn on the other canvas, at another size. - Auto-capitalization at the start of a sentence applies when you're writing in American English through ML Kit, or in a Latin, Cyrillic or Greek script through PaddleOCR, with Auto-capitalize on, and never in a password or other secure field. Bangla and other languages without letter case are skipped entirely. - ML Kit's recognizer only sees the last 20 characters of text before your cursor as context (a limit set by ML Kit itself, not WM Keyboard). PaddleOCR's reader sees the word just before the cursor, for its dictionary step. zinnia sees none. - Handwriting has no direct-boot support: neither the panel nor handwriting-by-swipe works before you've unlocked your device once after a restart, unlike some other tools. If [Power Saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) is set to drop on-device models to save battery, handwriting-by-swipe is switched back to plain gesture typing until you turn it back on. ## On F-Droid ML Kit's digital ink recognition depends on `com.google.mlkit:digital-ink-recognition`. That library is Google's and not open source, so the F-Droid build leaves it out entirely. The Handwriting tool, the handwriting layouts and the "Handwrite" option of **What a swipe does** are all still there. Chinese and Japanese read through zinnia, and the nine scripts in the [PaddleOCR table](#paddleocr-for-other-scripts) through PaddleOCR on MNN; F-Droid compiles both libraries from source. Languages in other scripts, Bangla among them, get a note in the panel instead of a canvas. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're deciding between builds. --- # Smart home URL: https://wmkeyboard.pages.dev/tools/home-assistant/ Section: Tools Summary: Your home from the keyboard: Home Assistant, Google Home, SmartThings, a Philips Hue bridge, openHAB, Smart Life (Tuya) or SwitchBot as rooms of live tiles for lights, blinds, thermostats, media, locks and sensors, with a typed command bar for Home Assistant's Assist. The **Smart home** tool puts your house inside the keyboard. It connects to the homes you list, shows every room as a tab of tiles, and keeps them live: when somebody flips a wall switch, the tile rings and changes while you watch. A tap switches a light, runs a scene or types a sensor's reading into your message. A hold opens the thing's own card, with a brightness slider, a thermostat dial, blinds buttons, the media transport, or a lock that takes a hold to open. With a Home Assistant on the list, a bar under the tiles takes a sentence for Assist, so `turn off the kitchen lights` typed with the keyboard does exactly that. Seven kinds of home can be on the list, and more than one at once: [Home Assistant](#home-assistant), [Google Home](#google-home), [SmartThings](#smartthings), a [Philips Hue bridge](#philips-hue-bridge), [openHAB](#openhab), [Smart Life and Tuya](#smart-life-and-tuya) and [SwitchBot](#switchbot). Each is a row of the tool's [provider list](https://wmkeyboard.pages.dev/tools/providers/): add it once, sign in or pair, and its rooms join the panel. The panel talks only to the homes you listed; nothing about your home passes through any other service. Open **Smart home** from the toolbox, or type `hass`, `smarthome`, `googlehome`, `smartthings`, `hue`, `openhab`, `tuya`, `smartlife` or `switchbot` on its own and tap the chip. ## Adding a home The tool's page is at Tools / Smart home. **Homes** is the list: **Add a provider** opens the kinds this build can use, and each kind's setup sheet asks for what it needs. A home that is set up but not yet signed in, paired or granted says so in red in the list; **Edit** opens the sheet again. Drag rows to change the order the rooms appear in, switch a home off to keep its setup without connecting, or remove it, which forgets its key with it. ### Home Assistant Needs the server's address and a sign-in. **The address.** Type it the way you reach it at home: `homeassistant.local:8123`, `192.168.1.5`, or whatever your browser shows. The sheet also lists any Home Assistant that announces itself on your Wi-Fi; tap one to fill the address in. A remote address is optional: a Nabu Casa or reverse-proxy `https://` address that works from anywhere. With both set, the keyboard tries the home address first and falls back to the remote one when the home one does not answer. **Signing in**, two ways: - **Sign in** opens your browser on your own server's login page. You sign in there, with your password and whatever second factor you use, and Home Assistant hands the keyboard a key. The keyboard never sees your password, and the key renews itself. Home Assistant checks `wmkeyboard.pages.dev/ha/` once during this, because that page is the keyboard's name as a sign-in client; your server, account and key never go there. - **Use token.** In Home Assistant, open your profile, the **Security** tab, and create a **long-lived access token**. Paste it into the sheet. This is the way in when the browser cannot reach your server, or when you prefer a key you can revoke from Home Assistant yourself. **Test connection** asks the server its name and version with the key you have, so a wrong address or a pasted token with a character missing shows up here rather than in the panel. **Sign out** revokes the browser key on the server and keeps only the addresses. Home Assistant is the one kind with an assistant, so the [Assist bar](#assist) and the **Ask home** chip appear only while a Home Assistant is on the list. Every kind of entity is shown: lights, switches, fans, covers, valves, climate, humidifiers, water heaters, media players, locks, alarm panels, vacuums, sirens, numbers, selects, text, buttons, scenes, scripts, automations, cameras, sensors, weather and people. ### Google Home The devices of one Google Home, through Google's own Home APIs on the phone. Needs Google Play services and the Google Home app, so it is offered only in the edition from Google Play, and only on a phone that has them. The Home APIs are a separate part of the keyboard, which Play downloads the first time you tap **Choose a home**. When the download finishes, the screen reopens, and you tap **Choose a home** again. **Choose a home** opens Google's own screen: you pick the account and the home, and which kinds of device the keyboard may see. Locks, cameras and doorbells are granted one by one. One home at a time; **Change home** runs the screen again. To take the access back on Google's side, remove WM Keyboard under your Google account's third-party apps, or in the Google Home app's linked apps. What arrives are Matter and Google device types: lights (on/off, brightness, colour temperature, colour), plugs and switches, thermostats, air conditioners, heat pumps and air purifiers, locks, blinds and shutters, fans (by the steps a fan has, so one with five speeds moves in 20 % steps, and its named speeds as presets), speakers and players, and contact, occupancy, temperature, humidity and light sensors. The keyboard reads and controls them through Google's service, which keeps its own connection; away from home, Google needs a hub in the house to reach the devices. While Google's developer console stays closed to registration, Google caps an app's Home APIs access at a hundred test users and shows an unverified-app warning during consent. That is Google's limit on every app at this stage, not a setting. ### SmartThings Your SmartThings devices, rooms and scenes, through Samsung's cloud. Add one row per account, so a second account (a parent's house, an office) sits beside the first with a nickname of its own. Two ways in: - **Sign in** opens Samsung's login in your browser, and SmartThings hands the keyboard a key that renews itself. The browser comes back through `wmkeyboard.pages.dev/st/`, which only forwards the sign-in result to the app. Offered when the build carries a SmartThings sign-in client. - **Use token.** A personal access token from `account.smartthings.com/tokens`, with the devices, locations and scenes scopes. SmartThings expires these after a day, so this is for trying things out. SmartThings has no event stream for an app like this one, so the panel asks for the device list every ten seconds while it is open; the dot by the home's name stays amber for that. A tap still acts at once. What arrives: lights with level and colour, switches and plugs, fans, shades and doors, locks, valves, thermostats (heating and cooling setpoints, modes, fan modes), media players, and contact, motion, water, presence, smoke, temperature, humidity, battery, light, power and energy readings. Scenes run from their tiles. ### Philips Hue bridge A Hue bridge on your Wi-Fi, directly, with nothing leaving the house. The sheet finds the bridge on the network (or you type its address), then: press the round button on the bridge and tap **Pair** within thirty seconds. The bridge mints a key of its own for the keyboard, and the bridge's certificate is pinned with it: a different device answering at that address later is refused. What arrives, live over the bridge's event stream: every light with brightness, colour temperature, colour and effects; one group light per room; scenes, which run from their tiles; and the motion, contact, temperature, light level and battery readings of Hue sensors. Rooms come from the bridge. ### openHAB Your openHAB server, at its address, with an API token from your profile (the user menu, **Create new API token**). Leave the token blank when your server lets devices on your network in without one. Items map by type and semantic tag: Switch, Dimmer and Color items are lights or switches (a `Lock` tag makes a lock), Rollershutter is a cover, Contact a contact sensor, Number a sensor or, with a `Setpoint` tag, a slider, String a selection or a text, Player a media player. Rooms and floors come from the semantic model: groups tagged as locations. States arrive live over the server's event stream. ### Smart Life and Tuya Devices in the Smart Life or Tuya Smart app, through Tuya's cloud. Tuya lets another app in only through a developer project of your own, so this takes a one-time setup on Tuya's site, free of charge: 1. Sign up at `platform.tuya.com` and create a cloud project. Pick **Smart Home** as the industry and development method, and the data centre that serves the country your app account was registered in; Tuya's own data centre table lists them. 2. In the project, open **Devices**, then **Link App Account**, and add your account: the page shows a QR code that you scan from the Smart Life or Tuya Smart app (**Me** tab, the scan icon). Your devices appear in the project. 3. Make sure the project's **Service API** list has **IoT Core** and **Smart Home Scene Linkage**; the first is added for you, the second gives the scenes. 4. Copy the **Access ID** and **Access Secret** from the project's **Overview** into the keyboard's setup sheet, and pick the same data centre there. The Access Secret stays on the phone: it signs each request and is never sent. Tuya's free trial of the cloud services has to be extended on its site from time to time; when it lapses, the home shows as refused until you extend it. One row per project, so two app accounts can sit side by side. Tuya has no push channel a phone can hold, so the panel asks for the device list every 30 seconds while it is open, and the dot stays amber. What arrives: lights with brightness, colour temperature and colour; plugs, power strips (one switch per socket) and wall switches; fans; curtains and garage door openers; thermostats, heaters and air conditioners; and temperature, humidity, battery, power, voltage, energy, CO2, PM2.5, door, motion, presence, leak, smoke and gas readings. Rooms and tap-to-run scenes come from the app's homes. Locks and cameras are left out: Tuya's cloud opens a lock only through a separate password flow. ### SwitchBot Your SwitchBot devices through SwitchBot's cloud, with the token and secret key from the SwitchBot app: **Profile**, **Preferences**, **About**, then tap **App Version** ten times; **Developer Options** appears, and **Get Token** shows both. Paste them into the setup sheet. The secret key stays on the phone and only signs requests. Bluetooth devices answer through a hub, and only once **Cloud Services** is on in each device's settings. SwitchBot allows 10,000 requests a day per account, and a device's state is one request, so the panel asks each device that reports anything once a minute while it is open; a tap acts at once and checks the device two seconds later. What arrives: bulbs, strips and ceiling lights; plugs, relays (two switches for a 2PM) and Bots; Curtain, Roller Shade and Blind Tilt, with position; the garage door opener; locks, with the door sensor where there is one; circulator fans and air purifiers; the radiator thermostat; and meter, CO2, motion, contact, presence and leak readings. The infrared remotes a hub has learnt are switches that remember what the keyboard last sent them, since a remote cannot report. Manual scenes run from their tiles. SwitchBot has no rooms, so its things sit under **Elsewhere**. ## The panel Along the top: the tabs, then search and settings. Under the tiles, in small type, the home's name with a dot beside it. The dot breathes while the keyboard connects, turns green when the live stream is up, amber when only a polled or plain web API answers, and red when a home cannot be reached or its key was refused. With several homes, the names join and the dot shows the best of them. **Tabs.** **Favourites** first, then one tab per room of each home in the list's order, then **Elsewhere** for things with no room, then **All**. With more than one home, a room tab carries the home's name first. Swipe across the tiles to change tab. The panel opens on Favourites, or on the tab you used last if you prefer. **Tiles.** Each tile is the thing's icon in a soft circle, its name, its state, and the one control its kind has. The circle fills with the theme's accent as the thing comes on, and for a light, as bright as the light is. Tiles arrive with a short stagger when a tab opens. | Kind | On the tile | A tap | |---|---|---| | Light, switch, fan, input boolean, automation | A switch | Toggles | | Scene, script, button | A play arrow | Runs or presses it; the tile flashes | | Cover, valve, climate, media player, lock, alarm, vacuum, humidifier, water heater, number, select, text, camera | A chevron | Opens the card | | Sensor, binary sensor, weather, person | The reading, large | Types it into your text | A tap acts straight away: the tile shows the new state and the home's next event confirms it. If the home disagrees, or refuses, the tile shakes and shows the truth. Hold any tile to open its card. **The card** grows out of the middle of the panel over the dimmed tiles. At the top are the name, the room and the live state; at the bottom, **Insert**, **Hide** and **Open in its app**, plus a star to make it a favourite. In between, the controls for its kind: - **Light**: the switch, a brightness slider, a warmth slider from candle to daylight, a hue strip and a row of colours, and the light's effects. Dragging a slider ticks every five per cent and sends as you go. - **Fan**: speed, oscillation, direction, presets. - **Cover, valve**: open, stop and close, a position slider, a tilt slider. A garage door, a gate or a door takes a hold instead of a tap. - **Climate**: a dial that fills to the target in orange while heating and blue while cooling, the current temperature as a dot on it, minus and plus by the step your thermostat uses, then the modes, presets and fan modes. - **Media player**: what is playing, previous, play or pause, next, the volume with mute, and the sources. - **Lock**: Unlock and Lock, each a hold; Open where the lock has a latch. - **Alarm panel**: Disarm, and the arm modes your panel offers, each a hold. A panel that wants a code asks for one; the keys type it. - **Vacuum**: start or pause, stop, dock, find, and suction levels, with the battery in the state line. - **Humidifier, water heater**: the target slider and the modes. - **Number, select, text**: a slider, a row of options, or the text with an Edit that hands the keys over. - **Camera**: a still from the camera, **Refresh**, and **Send picture**, which puts the picture into the chat the way the Gallery does. Home Assistant only. - **Sensors**: the reading large with its unit and when it last changed, and three chips for how to type it: the value, the value with its unit, or the name and the value. Hold a chip to make it the default for every sensor tile's tap. A kind a home does not have never shows; a control a home cannot do (a colour on a Hue scene, say) is left off the card. **Search** filters every tab by name, entity id or room as you type; Enter opens the first match. **Hide** takes a tile out of every tab; the Favourites and rooms screen brings it back. ### Risky things Locks, alarm panels, garage doors, gates, sirens and valves are never one tap. Their buttons fill a ring while your finger stays down and act when it is full, about a second; lifting early does nothing. **Hold to confirm risky actions** on the tool's page turns this off. ### Assist With a Home Assistant on the list, the bar at the bottom reads **Ask your home…**. Tap it and the keys type into it, glide and voice included. Enter sends the sentence to Home Assistant's Assist, the same one that answers in its own app: `turn on the hall light`, `set the bedroom to 20 degrees`, `is the garage closed`. The reply slides up over the bar. Things it changed ring their tiles; things it could not find shake. **Insert reply** types what it said into your message. A follow-up question stays in the same conversation until you close the panel. Assist speaks the language you are typing in, when **Send the keyboard's language** is on, and you can pick which of your conversation agents answers. The first Home Assistant on the list is the one asked. ### Favourites and rooms Tools / Smart home / Favourites and rooms lists everything every home has, by home and room. Star what the Favourites tab should show, drag the favourites into order, hide single things, and switch off whole kinds (every `automation`, say) you never want to see from the keyboard. Entities a home marks as diagnostic or configuration, and ones it hides itself, never show. ## Elsewhere in the keyboard - **A chip while you type.** With a Home Assistant connected, a sentence that reads like a command for the house (`turn off the lights`, `dim the bedroom to 30%`, `lock the front door`, `open the blinds`) gets a strip chip, **Ask home**. Tapping it opens the panel and sends the sentence to Assist instead of leaving it in your message. **Suggest commands as you type** on the tool's page switches it off; it is part of the [smart chips](https://wmkeyboard.pages.dev/smart/chips/). - **The AI chat's tools.** The [AI chat](https://wmkeyboard.pages.dev/tools/ai/) gets two tools: **Control your home**, which works a device on any home on the list (Google Home, SmartThings and the rest included) or sends one plain sentence to Home Assistant's Assist, and **Read your home**, which lists what is on across every home on the list, or whatever matches a name. Both ask before they run, like every tool that touches your things. A fan with a few fixed speeds reads as `speed 2 of 5`, so asking for speed 1 sets the first speed rather than one percent. They are listed only while a home is set up. - **Snippet variables.** `{ha:sensor.outside_temperature}` expands to the entity's state with its unit, and `{ha:light.kitchen:brightness}` to one attribute; see [snippets](https://wmkeyboard.pages.dev/tools/snippets/). The entity is looked for in every home; `{ha:home_key|entity_id}` names one. They read what the keyboard last saw and ask the homes for a fresh state when they are connected. ## Options Tools / Smart home Smart home is the **Smart home** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, its panel and the devices screen leave the keyboard and the app; your connection and devices stay until you switch it back on. | Setting | What it does | Default | |---|---|---| | **Smart home tool** | The switch; adding a home turns it on | Off | | **Homes** | The provider list: each home's kind, address and sign-in, in the order their rooms appear | Empty | | **Favourites and rooms** | The Favourites tab's tiles, hidden things, kinds to leave out | | | **Open on** | **Favourites** or **Last tab** | Favourites | | **A tab per room** | Each home's rooms as tabs; off, just Favourites and All | On | | **Tile size** | **Comfortable** (two across) or **Compact** (four) | Comfortable | | **Hold to confirm risky actions** | Locks, alarms, garage doors, gates, sirens and valves take a hold | On | | **What a sensor tile types** | **Value**, **Value and unit** or **Name and value** | Value and unit | | **Command bar** | The Assist bar under the tiles (with a Home Assistant listed) | On | | **Conversation agent** | Which of your Home Assistant's assistants answers | Home Assistant's default | | **Send the keyboard's language** | Assist answers in the language you type in | On | | **Stay connected** | **While the panel is open**, or **While the keyboard is shown** so the panel is live the instant it opens; either way the connections drop a minute after | While the keyboard is shown | | **Keep the last states on the phone** | Draw the tiles from the last session at once, dimmed until each home confirms them | On | | **Suggest commands as you type** | The **Ask home** chip (with a Home Assistant listed) | On | ## Details & edge cases - **What is sent, and where.** Only to the homes you listed: each home's key in a header, the commands you give, the states it sends back, and a camera still when you ask for one. Home Assistant is a WebSocket to `/api/websocket`; a Hue bridge and openHAB hold an event stream open; SmartThings is asked every ten seconds, Smart Life every 30 and SwitchBot once a minute; Google Home goes through Play services. All of it only while the panel is open or the keyboard is shown, and a minute after. Nothing goes out when the tool is off or no home is signed in. The [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) shows every request under the home's platform. - **Plain HTTP to your own server.** A Home Assistant or openHAB address without `https://` is plain HTTP on your own network, which is how most installs answer. A remote address should be `https://`; a bare public domain is taken as such. A Hue bridge is always HTTPS, with its own certificate pinned at pairing. - **When a stream cannot connect**, Home Assistant falls back to its plain web API: taps still work, states refresh when you act, and the dot turns amber. When nothing answers, that home's tiles stay as last seen, dimmed, under a banner with **Retry**; the other homes carry on. - **A key that stops working** (you revoked it, the server was reset, a SmartThings token expired, a Tuya trial lapsed) shows as a red dot and a banner on the tool's page. Edit the home and sign in again; nothing else is lost. - **Several homes.** Rooms with the same name in two homes are two tabs, each with its home's name first. Favourites and hidden things remember which home they belong to. - **Before the first unlock** after a restart, the tool is not available: the keys live in the part of the phone that opens with your unlock. - **Backups** leave every home's key out unless you turn on **Include API keys**; the addresses and the list go with them. - **Camera stills** are fetched over Home Assistant's web API with the key, never through any image service, and are not kept on the phone. --- # Instruments URL: https://wmkeyboard.pages.dev/tools/instruments/ Section: Tools Summary: Flashlight, compass, bubble level, moon phase, and weather: glanceable tools between sentences. "Instruments" is this site's name for five small utility tools that Settings groups together as "Utilities," alongside [Calendar](https://wmkeyboard.pages.dev/tools/calendar/) and [Plugins](https://wmkeyboard.pages.dev/plugins/overview/): **Flashlight**, **Compass**, **Bubble level**, **Moon phase** and **Weather**. None of them makes you leave the keyboard to check something. Four open as a panel that replaces the key rows. Flashlight is the odd one out, a single tap with no panel at all. The same group also holds [Screenshot](#screenshot), another single tap. tools/instruments-settings-utilities-group — The Utilities group under Settings → Tools: Flashlight, Compass, Bubble level, Calendar, Weather and Moon phase. Open any of the five from the toolbar if you've pinned it, or from the toolbox. None ships pinned by default, and none is in the onboarding wizard's Recommended set, so the first time you want one you'll probably find it in the toolbox. Weather is the one the "show me everything" answer does turn on, next to Calendar. The toolbox doesn't label a "Utilities" section the way Settings does, and it doesn't keep the five together either. Flashlight, Compass, Bubble level and Moon phase sit as a block near the end of the default ranking, after the scanners and the camera and just before the one-tap cursor moves. Weather is much earlier, right after Calendar in the everyday run. None of the five needs an Android runtime permission. The torch doesn't need the `CAMERA` permission (that's only for the separate [camera tool](https://wmkeyboard.pages.dev/tools/camera/)). Weather's location is typed or searched, or read once from the phone when you tap **Use my location**, which asks for location access the first time; see [Location](https://wmkeyboard.pages.dev/tools/location/#weather-and-compass). No build is short-changed either: every build behaves identically across all five. ## Flashlight: a one-tap torch Tapping **Flashlight** turns the device's torch on or off immediately. There's no panel to open or close. If the device has no flash, a toast says "Your device has no flashlight" and nothing else happens. The toolbox tile mirrors the torch's real state. Flip the torch on from the system quick-settings tile instead of the keyboard and the Flashlight tile lights up to match. Turning it off from either place turns it off in both. tools/flashlight-toggled-on — Flashlight is a direct toggle: no panel opens, and the toolbox tile lights up while the torch is on. ### Options Tools / Flashlight - **Turn off with the keyboard**: on by default. On, closing the keyboard or switching apps switches off a torch you turned on from the keyboard, so it's never left burning in your pocket. A torch you turned on from the quick-settings tile or another app is left alone. Off, the torch stays on until you toggle it again from the tool or the system quick-settings tile. ## Compass, with an optional qibla bearing **Compass** draws a live rose from the device's rotation-vector sensor, with a fixed needle at the top marking the way you're actually facing. The heading glides smoothly rather than jumping with every sensor sample. On a device with no orientation sensor at all, the panel says so instead of drawing an empty rose. Turn on **Show the qibla** and a 🕋 marker rides the rose, pointing toward the Kaaba. The compass has no location field of its own. It reuses whatever location you've already saved in the [Weather](#weather) tool's settings, and without one saved the panel prints "Qibla needs a saved location. Save one in the settings of the weather tool." instead of a bearing, with a **Use my location** button under it that reads your position once and saves it as that location. Everything is calculated on-device from that saved latitude and longitude. The compass itself never touches the network. The [Location tool](https://wmkeyboard.pages.dev/tools/location/#places-somebody-sent-you) can also point the compass at any place, a friend's pin, say. A 📍 marker rides the rose next to the 🕋 one, and a line under the rose gives its distance and direction, with **Stop pointing** beside it. Once a location is saved, the rose turns to true north rather than magnetic north. The phone's sensor finds magnetic north, which is a few degrees off true north in most places and up to twenty in some; Android's own model of the Earth's field gives the difference for the saved place, so the heading and the qibla marker are both measured from true north. tools/compass-panel-qibla — The compass panel: rotating rose, degree readout, and the qibla marker pointing toward Mecca. ### Options Tools / Compass - **Degree readout**: on by default. It shows the numeric heading and a 16-point cardinal (N, NNE, NE, … NNW) under the rose. Before the sensor has reported it reads "Calibrating…" instead. - **Show the qibla**: off by default. Turn it on before saving a location and the settings screen shows a caution of its own: "No location is saved yet. Set one under Tools → Weather." ## Bubble level **Bubble level** turns the accelerometer, or the gravity sensor on a device that has one, into three gauges at once. A circular bullseye bubble combines pitch and roll, and a vertical and a horizontal tube vial each track one axis alone. The bullseye turns accent-colored once both pitch and roll are within about a degree of flat. Each tube vial turns accent-colored independently on its own single-axis threshold, so a vial can read level a moment before or after the bullseye agrees. tools/level-panel — Bubble level: a bullseye bubble plus a vertical and horizontal tube vial, with an angle readout below. ### Options Tools / Bubble level - **Angle readout**: on by default. It shows pitch and roll in degrees below the gauges, colored to match the bullseye's flat/not-flat state. ## Moon phase **Moon phase** is arithmetic rather than a lookup. The panel computes the current phase from a fixed synodic-month constant and a reference new moon, with nothing fetched over the network. It's accurate to within a few hours, which is plenty for "is it worth looking up tonight" and not built for precise astronomical prediction. The panel shows a painted lit/dark disc, one of eight phase names (New moon, Waxing crescent, First quarter, Waxing gibbous, Full moon, Waning gibbous, Last quarter, Waning crescent), the percentage illuminated, and how many days into the roughly 30-day cycle you are. Under that is a short timetable of the four main phases (new moon, first quarter, full moon and last quarter), each with its day and time: the one that has just passed, in a dimmer color, then the next three. tools/moon-phase-panel — Moon phase: the current lit fraction, phase name, and next full/new moon dates, computed on-device. ### Options Tools / Moon phase - **Arabic names too** (off): shows each phase's Arabic name as well, under the name in your language and beside each phase in the timetable: محاق, هلال, تربيع أول, أحدب متزايد, بدر, أحدب متناقص, تربيع ثانٍ and هلال متناقص. - **Southern hemisphere**: read off your phone's region, so it starts on in Australia, Brazil or South Africa and off in Bangladesh or the UK. It mirrors the lit disc the way the moon actually looks south of the equator. Only the drawing changes, never the phase name, dates or illumination percentage. ## Weather **Weather** shows current conditions and a same-day high/low for one saved location, sourced from [Open-Meteo](https://open-meteo.com). No account and no API key are needed. You set the location yourself: search a city or place name (which hits Open-Meteo's geocoding endpoint), type latitude and longitude directly, or tap **Use my location** to read the phone's position once. Only that last one asks for location access, and only the first time you use it. The saved place doesn't follow you afterwards; it stays put until you change it. A fetched result is reused for as long as **How often to check** says, 15 minutes out of the box, so reopening the panel is usually instant. A refresh button in the panel forces a new request. Until you save a location the panel shows a prompt with an **Open settings** shortcut and a **Use my location** button instead of any weather data. If a fetch fails it shows a retry button rather than stale numbers. tools/weather-panel-ready — Weather: current conditions plus a stat grid: feels-like, humidity, wind, and whatever else Open-Meteo returns for that location. Once a location is set, the panel shows an emoji and short description for the current conditions, the place name with today's high and low, and a wrapping grid of stats. Feels-like temperature, humidity, and wind speed with an 8-point cardinal direction always appear. Rain chance, cloud cover, pressure, UV index, sunrise and sunset each appear only when Open-Meteo's response actually includes them. ### Options Tools / Weather tools/weather-options-location — Weather's settings: a location search or coordinate entry, and the Fahrenheit toggle. - **Location**: unset by default. It opens a dialog with **Use my location** at the top, a search field backed by Open-Meteo's geocoding API, and manual latitude and longitude fields if you'd rather skip both. The same dialog adds places in the [Location](https://wmkeyboard.pages.dev/tools/location/#saved-places) tool. - **Fahrenheit**: off by default, so temperatures show in Celsius until you turn this on. It moves the current, feels-like, and day's high and low temperatures. **Wind speed follows this switch too**: km/h with Celsius, mph with Fahrenheit. Pressure stays in hPa, which has no second unit here. - **Fetch the forecast as you type**: on by default, off in the F-Droid edition. On, a weather chip fetches the forecast it needs as soon as you type a weather question. Off, the chip reads **Get the forecast** and fetches nothing until you tap it. The panel fetches when you open it either way. - **How often to check**: how long a reading is reused before the tool fetches again. 1 to 180 minutes, default 15. The panel's refresh button still forces a fetch whatever this says. Those four rows are the whole screen. The settings screen carries its own privacy note underneath: "The weather and the place search both use Open-Meteo, only when you use them. The keyboard makes no other network requests." Weather is the only one of the five instruments that reaches the network at all. The one exception to that note is **Use my location** with the Location tool's address lookup on, which asks the address server for the city's name once. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what every network-touching feature sends and when. The flashlight, the compass, the bubble level and the moon phase are the **Instruments** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), and weather is part of **Weather and location**. A bundle that is off takes its tools off the toolbar, the toolbox and the Tools list until it is switched back on. ## Screenshot **Screenshot** takes a screenshot of the app you're in, the same one the power and volume keys take. The keyboard goes down first, so it isn't in the picture, and stays down afterwards. Android saves the screenshot where every screenshot goes and shows its usual preview, with its own editing and sharing. Bind it to the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures) and the button steps out of the picture too, coming back a moment later. Android lets another app take a screenshot only through an accessibility service, so the tool needs one of its own, **WM Keyboard system buttons**, turned on in Android's accessibility settings. It presses Android's own buttons when you ask: the screenshot for this tool, and Back, recent apps and the like for the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#system-buttons). It reads nothing on screen. The first press while it's off explains this and then opens Android's accessibility settings; the tool's page under Tools / Screenshot has a **System buttons service** row that says whether it's on and takes you there too. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). It needs Android 9 or later, and it isn't in the Play Store build, which ships without accessibility services that aren't its main purpose. It doesn't work before your first unlock after a restart. ## Details & edge cases - **None have a default hardware-keyboard letter.** Tools such as web search (W) and dictionary (D) ship with a leader-key letter, and none of these five do. Reach them through the toolbox shortcut (double-tap Ctrl, then T) or [rebind a letter to one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/). - **Four keep working before your first unlock. Weather doesn't.** Flashlight, Compass, Bubble level and Moon phase are all direct-boot safe. They're pure sensor readings or arithmetic, so they work right after a restart, before you've entered your PIN or pattern for the first time. Weather is the exception: it needs network access and reads a location saved in credential-encrypted storage, and neither is available until you unlock. - **Every tool here has more than a bare switch.** Each of the five gets a "Tune" icon in Tools, meaning it has real options beyond the shared Enabled toggle. None of them is one of the toggle-only tools on that screen. - **Colored tool icons** under Tools is on out of the box, so each of these five already wears its own accent color in the toolbox and in the Tools list itself. The pinned toolbar row keeps the theme's plain icon color regardless, same as every other tool. For the toolbar and toolbox mechanics these five share with every other tool (pinning, the toolbox drag, and what "direct-boot safe" means more broadly), see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/). --- # KDE Connect URL: https://wmkeyboard.pages.dev/tools/kde-connect/ Section: Tools Summary: Pair the keyboard with a computer: use its mouse and keys from your phone, control its music, share the clipboard and files, and let the computer type into your phone. [KDE Connect](https://kdeconnect.kde.org/) links a phone to a computer over your home network. The **KDE Connect** tool speaks that protocol from inside the keyboard, so you can reach your computer from whatever app you are typing in, and the computer can type back. It works with KDE Connect on Linux, Windows and macOS, with GSConnect on GNOME, and with Valent. You do not need the KDE Connect app on the phone. The keyboard shows up on the computer as a device of its own, named after the phone's model followed by **WM Keyboard**, for example *Pixel 8 - WM Keyboard*. If you do have the app, both keep working side by side, as two devices, and the name tells you which is which. To move a file to another phone, or to anyone as a link, see the [Send files](https://wmkeyboard.pages.dev/tools/send/) tool: its Nearby mode runs on this same engine, and its direct link joins two phones with no Wi-Fi between them. ## Turning it on **Use KDE Connect** is on from the start, but being on sends nothing by itself. The keyboard only goes on your network once a computer is paired, or while a device list is open in the panel or in settings. Turn the switch off under Tools / KDE Connect and it does nothing at all on your network: it sends nothing and listens for nothing. After that the panel lists the computers it can see. On the computer, KDE Connect (or GSConnect, or Valent) has to be running. ## Pairing 1. Press **Pair** next to the computer's name. 2. An eight character code appears on the phone. The same code appears on the computer. 3. If the two match, accept on the computer. You have 30 seconds. You can also start from the computer: pick the phone there, press pair, and accept on the phone. That only works while the keyboard's device list is open, because that is the only time the keyboard talks to computers it has not paired with. The code is what makes pairing safe. It is calculated from both devices' keys, so a match means the computer on your screen is the one in front of you and not something else on the Wi-Fi. After pairing, the computer's certificate is remembered and nothing else is accepted under its name. ### Pairing with WM Keyboard on a computer WMK Desktop, WM Keyboard for the Mac, shows a pairing code under **Backup & restore › Sync devices › Pair a phone**, and on its setup page. Point the phone's camera, or any QR scanner, at the code and open the link it shows, and WM Keyboard opens on the devices screen. Or press **Scan a code** in this panel, next to **Add by address**, which opens the keyboard's own [QR scanner](https://wmkeyboard.pages.dev/tools/scanner/) (not in the F-Droid build). A link opened from the camera or another app first asks whether to pair, naming the computer and its address, because any web page or app can open such a link; press **Pair** only for a code you scanned from your own computer. The keyboard's scanner pairs at once. Either way there is no code to compare. The link holds the computer's address and its certificate fingerprint, so the phone pairs only with the computer that showed it, and its one-time token works once. Once paired, the two sync through each other: see [Sync between devices](https://wmkeyboard.pages.dev/privacy/backup/#sync-between-devices). ### If the computer does not show up - Both devices have to be on the same network. Mobile data does not work, and many guest networks keep devices apart. - The computer's firewall has to allow ports **1714 to 1764**, for both TCP and UDP. - Press **Add by address** and type the computer's IP address. The phone then announces itself to that address directly, which gets through on networks that block discovery. - If another KDE Connect app on the phone is already using the discovery port, the keyboard can still be found. Press **Refresh** on the phone rather than waiting for the computer. ## The tabs Tap the device name in the panel's header at any time to switch computers, pair another one, or unpair. Swipe sideways across the panel to move between tabs. On the touchpads a swipe moves the pointer instead, so start it off the pad. Not every computer can do everything. A tab or button for something the computer does not support is left out, or says so when you open it. ### Input The big area is a **touchpad** for the computer. | Gesture | What it does | |---|---| | One finger drag | Move the pointer | | Tap | Left click. Tap twice for a double click | | Two finger tap | Right click | | Three finger tap | Middle click | | Two finger drag | Scroll | | Hold still, then drag | Drag with the button held. Lift to drop | | Tap, then drag straight away | The same drag, without the wait | Slow, careful movement moves the pointer by single pixels rather than not at all, and a steady drag keeps a steady pointer speed, so the pointer glides rather than stepping. The L M R buttons beside it are the mouse buttons, for when a tap is not precise enough. Press **Type on computer** and the keys come back under a smaller panel. Now everything you type goes to the computer instead of the app: letters, glide typing, suggestions you pick, autocorrections, emoji, and any language the keyboard has. A strip of keys a phone layout does not have sits on top: Esc, Tab, the arrows, Del, Home, End, page keys and F1 to F12. Ctrl, Alt, Super and Shift stay lit until the next key, so Ctrl then c copies on the computer. **Compose** changes how typing is sent. Normally each keystroke goes out as you make it. With Compose on you write a whole line on the phone, fix it, and send it with Enter. The **Send** button sends the line without pressing Enter on the computer. ### Media Whatever is playing on the computer: artwork, title, a seek bar, previous, play or pause, next. Shuffle and repeat appear for players that have them. If several players are open you can switch between them. The slider at the bottom is the computer's own volume, with a mute button. KDE Connect on macOS does not let a phone control its players, so on a Mac this tab has only the volume. The official KDE Connect app has the same limit there. A [Run](#run) command such as `osascript -e 'tell application "Music" to playpause'` is a way around it. ### Send Until something has been sent or received, the three buttons fill the tab as large tiles. Once there are transfers they shrink to a row above the list. - **Clipboard** sends what is on the phone's clipboard right now. - **Text in the field** sends your selection, or the whole field if nothing is selected. - **Files…** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) (Android's picker in the Play Store build) and sends what you choose. - A place goes from the [Location tool](https://wmkeyboard.pages.dev/tools/location/#sending-a-place-to-your-computer) instead: its **Send to** button sends it as a map link, which the computer opens in its browser. Transfers are listed with their progress and can be cancelled. Files the computer sends to the phone are listed here too, with an **Open** button. ### Run The commands you have set up on the computer, as buttons. Output shows up live while a command runs, and **Stop** ends it. Commands are created on the computer, in its KDE Connect settings. **Set up on computer** opens that screen there. ### Slides Big **previous** and **next** buttons for a presentation, **Start** (F5) and **End** (Esc). Hold and drag on the pad between them to show the computer's pointer dot and move it around. Let go and it disappears. ### Device **Ping** the computer, **Ring it** to find a laptop in a bag, and **Lock** or **Unlock** its session where the desktop supports it. The device's address, battery and certificate fingerprint are here, along with **Unpair**, and **Pause clipboard**: see [Pausing clipboard sharing](#pausing-clipboard-sharing). ## What the computer can do - **Type into the phone.** On the computer, open the phone in KDE Connect and use its remote keyboard box. What you type lands in the field on the phone. Arrows, Home, End, selection with Shift, word moves with Ctrl, Ctrl+A C X V Z, and Enter all do what you expect. This works only while the keyboard is on screen and the phone is unlocked. - **Share its clipboard.** Text copied on the computer is ready to paste on the phone, and shows up as the paste suggestion. - **Send text, links and files.** Text and links arrive on the clipboard. Files are saved to **Downloads/WM Keyboard** and added to the [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/), so you can paste a picture from the computer straight into a chat. - **See the phone's battery**, and **control the phone's media** if you allow it. ## Pausing clipboard sharing Sometimes you want the clipboard to stay on one side for a while, say while you copy passwords on the computer or show your screen to someone, without unpairing and without flipping the clipboard switches one by one. In the panel, open the **Device** tab and press **Pause clipboard**. Pick how long: **15 min**, **1 hour**, or **Until I resume**. Tick **All computers** first to pause every paired computer at once instead of only the one you are on. The same choice is on the **Devices** screen under Tools / KDE Connect / Devices: a **Pause clipboard** button under each computer, and an **All computers** row at the top when more than one is paired. While a computer is paused, nothing you copy on the phone goes to it, and nothing it copies lands on the phone. Everything else keeps working: typing, the touchpad, media, files. A timed pause ends by itself, and a pause outlasts a restart of the phone or the keyboard. To end it sooner, press **Resume clipboard** in the panel, **Resume** on the Devices screen, or **Resume** on the notification that shows while a pause is on. A computer held by a pause on all of them is resumed together with the rest. **Send › Clipboard** still sends while paused, because pressing it is asking for that one clip to go. Pauses are kept on this phone only and never sync to another device. ## Options Tools / KDE Connect KDE Connect is the **KDE Connect** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, the devices screen and the pairing leave the keyboard and the app; the pairing itself is kept until you switch it back on. **Use KDE Connect**: on by default. The master switch. Nothing goes on the network until a computer is paired or a device list is open. **Devices**: paired computers and the ones nearby, with a switch per computer for clipboard, typing, files, phone media, battery and sync, and a [pause for the clipboard](#pausing-clipboard-sharing). Also the list of addresses added by hand, and this phone's certificate fingerprint. **Name shown on the computer**: blank uses the phone's model followed by *WM Keyboard*, shortened to *WMK* when the model name is long. Up to 32 characters, and the protocol does not allow punctuation. **Stay connected**: default **Keyboard showing**. - *Panel open*: only while the KDE Connect panel is open. - *Keyboard showing*: while the keyboard is on screen and for a minute after, so switching apps does not reconnect every time. - *In the background too* (called *Always* before 0.6.4): the link also stays up after the keyboard hides, so the clipboard can keep syncing while you are not typing. There is no background service behind it. The link lives inside the keyboard, and once the keyboard is off screen Android is free to pause or close it, which many phones do within minutes and some at once; the link drops then, and comes back the next time the keyboard shows. Setting WM Keyboard's battery use to *Unrestricted* in Android's app settings keeps it up longer on most phones, but nothing guarantees it. Power saving, with its *Background network* switch on, puts it back to *Keyboard showing*. **Connect automatically**: default on. Reconnect to paired computers when they are found. **Receive the computer's clipboard** and **Send this phone's clipboard**: both default on. **Clear received passwords**: default **1 minute**, up to 30, or *Never*. Text from the computer that looks like a password or a one-time code is taken off the phone's clipboard after this long, along with its unpinned copy in clipboard history. It is only cleared if it is still the latest clip, so something you copied since stays. Ordinary text from the computer is never cleared. **Let the computer type here**: default on. **Use the keyboard language**: default off. Off, what the computer sends is typed exactly. On, its keys go through the keyboard's layout the way a [hardware keyboard's](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) do, so a Latin keyboard on your desk can write Bengali through Avro, with suggestions and autocorrect. **Pointer speed**, **Acceleration**, **Scroll speed**, **Natural scrolling**, **Tap to click**, **Vibrate on clicks**: how the touchpad feels. Speeds go from 0.3× to 3×. **Receive files**: default on. **Show in the share menu**: default on. Adds **Send to computer** to other apps' share sheets. The entry only exists once a computer is paired, so it never appears for someone who does not use the tool. With more than one computer paired, **Send to computer** asks which one. Computers that are still connecting are listed greyed out and become selectable as they connect. Only files another app shares from its own storage go through. A share that names a file inside WM Keyboard's own storage is ignored, so an app cannot use **Send to computer** to send out the keyboard's settings or clipboard history. **Report battery level** and **Offer this phone's media**: both default on. Media needs the same notification access as the [media controls](https://wmkeyboard.pages.dev/tools/media/) tool, and does nothing without it. **Send one-time codes**: default off. A verification code that arrives in a notification goes to the paired computer, the code alone and never the message around it. WM Keyboard for the desktop offers it above the cursor; a KDE Connect desktop shows it as a notification. It needs **Codes from notifications** on under Typing, which is what reads them. Notifications for a pair request, a file or a ping that arrives while the keyboard is hidden, and the one shown while clipboard sharing is paused, have their own switch under Notifications. ## Details and edge cases - Everything stays on your local network. There is no account and no server. Both ends encrypt the connection with TLS, and each side pins the other's certificate at pairing. - Passwords and one-time codes are never sent on their own, even with clipboard sending on. Nothing is sent from a password field, or while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) pauses the clipboard. **Send › Clipboard** still sends whatever is there, because you asked. - A password or code from the computer is marked sensitive on the phone, the way a password manager marks one, so Android 13 and later hide it in the copy preview and clipboard history treats it as a secret. - Clipboard sync is separate from clipboard history. Turning history off does not stop the sync, and the other way round. - Computers you have not paired with are only contacted while a device list is open, in the panel or in settings. The rest of the time the keyboard only talks to computers it already knows. - The tool is hidden on the lock screen and before the first unlock after a restart. A computer cannot type into a locked phone. - The phone's key and the paired computers' certificates are left out of backups on purpose. Restored onto another phone they would put two devices on your network under one identity. Pair again on a new phone. - The scroll direction and speed can differ between desktops, because the protocol does not define a scale. Both are settings for that reason. - Text, links and clipboard only. Images on the clipboard are not synced. Send them as files. - Not supported: SMS and calls, notification mirroring, browsing the phone's files from the computer, and ringing the phone. Those need permissions a keyboard should not hold. The KDE Connect app does them well, and the two can run together. --- # Learn from text URL: https://wmkeyboard.pages.dev/tools/learn-from-text/ Section: Tools Summary: Teach the keyboard your words from something you already wrote: it lists the words it doesn't know, and you add them in one go. A new keyboard doesn't know your names, your slang or the jargon of your job, and normally it only picks them up one sighting at a time as you type. **Learn from text** skips the wait. Open it in a note, an email draft or anything else you wrote, and it reads the text, lists every word the keyboard doesn't know yet, and lets you add the ones you want to your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) at once. tools/learn-from-text — Learn from text listing the new words in a note, all checked, with Add 5 in the header. ## What it reads It starts switched off, whatever you picked during setup, so turn it on first in Tools. Then open **Learn from text** from the toolbar, or from the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). It reads the text you have selected. With nothing selected, it reads the whole field. It never moves your cursor and never changes the text. A few things are left out on purpose: - **Password fields.** The panel says so and reads nothing. - **Words the keyboard already knows.** That means words in the word lists for your current language and its [secondary languages](https://wmkeyboard.pages.dev/smart/mixed-language/), your imported lists, your personal dictionary, Android's personal dictionary, your contacts and your app names. - **Words on your [blacklist](https://wmkeyboard.pages.dev/smart/blacklist/).** - **Things that aren't words:** numbers, single letters, links, email addresses and @handles. - **Chinese, Japanese and the other scripts written without spaces**, where the run of letters between two spaces is a phrase rather than a word. Thai, Lao, Khmer and Burmese are the exception: the keyboard finds the words inside their runs and lists those. Burmese written in Zawgyi is still left out, since the words can only be found in Unicode Burmese. Some apps only hand a keyboard part of a long field. When that happens a line at the top says only part of it could be read. Select the part you care about and open the tool again. The list stops at 2,000 words, and a note says so when a text has more. A word only in a language that isn't in your current mix shows up as new. Switch to that language first, or add it as a secondary language, if you're reading text in it. ## The list If the text holds nothing the keyboard doesn't already know, the panel says so, and under that it points the other way: the words already learned are not listed here but in the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) in Settings, which **Open the dictionary** takes you to. This tool is for adding words, not for reviewing them. Every word gets a row, and every row starts checked. Each row shows the word, how many times it appears in the text (**×4**), and **seen 2** when the keyboard has already seen it while you typed but hasn't learned it yet (see [Words wait their turn](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn)). Tap a row to check or uncheck it. The header holds the rest: - **Select all** / **Select none** flips every row at once. - The sort chip cycles through **Most used**, **Text order** and **A to Z**. The panel remembers your choice for next time. - **Word pairs** decides whether adding words also teaches the word pairs in the text (below). - **Add N** adds the checked words. The word's spelling comes from how the text writes it away from the start of a sentence. "Zelda" written mid-sentence is added with its capital, and the capital is kept for good. A word you only ever wrote at the start of a sentence is added in lowercase, because that capital says nothing about the word. ## Row actions The ⋮ on a row opens four actions under it: - **Edit** fixes the spelling before you add it: `teh` becomes `the`. The keys come up while you type. Press Enter or **Done** to keep the new spelling, or **Cancel** (or Back) to leave the word as it was. The row then reads **teh → the**. Only what gets added changes, never your text. - **Show in text** selects the word's first appearance in the field, so you can see how it was used. Some apps don't tell the keyboard where text sits, and the action is greyed out there. - **Never suggest** puts the word on the [blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) and takes the row away. - **Ignore** hides the row for this list only. Open the tool again and the word is back, and typing it still counts toward learning it the usual way. ## Adding **Add N** (or **Learn text** when no new unknown words remain) puts every checked word in your personal dictionary and compiles the document's word and n-gram usage statistics. Each new word is marked as added by hand, so it's safe from autocorrect straight away and reads "You added this word" on the dictionary screen. It leaves the waiting room, comes off the blacklist if it was on it, and is copied to Android's dictionary too when **Add words to the system dictionary** is on. Known words and n-grams in the text also have their usage counts learned into your typing history, building your personal language model. The added rows disappear, and a line at the top says what happened: "Added 5 words · 12 word pairs". With **Word pairs** on (the default), the same tap teaches the pairs the text holds, the way typing the text would have: two-word pairs, three-word contexts and the word two back. So "see you soon" helps predict "soon" after "see you". The same rules as typing apply: - Both ends of a pair have to be words the keyboard knows once your words are in. A word you left unchecked never becomes a next-word suggestion. - A pair never crosses a full stop, a question mark, an exclamation mark, a danda or a line break. - Each pair counts as many times as the text repeats it, the same as typing the text would have, so a phrase you use often in your writing is predicted sooner. - Pairs follow **Learn from typing**. With learning off, or paused in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), words are still added (you asked for them) but no pairs are learned. The panel closes when you move to another field, since what it read belonged to the old one. ## Options Tools / Learn from text The tool has no settings page of its own beyond its **Enabled** switch and the toolbar hold action every tool has. Its two choices, the sort order and **Word pairs**, live on the panel and are remembered from one use to the next. | Choice | Default | Where | | --- | --- | --- | | Sort order | Most used | Panel header | | Word pairs | On | Panel header | To move words between your personal dictionary and Android's own in bulk, use **Import from Android's dictionary** and **Export to Android's dictionary** on the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#androids-dictionary) screen. Learn from text is the **Learn from text** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on; what it taught the dictionary stays. --- # Location URL: https://wmkeyboard.pages.dev/tools/location/ Section: Tools Summary: Share where you are, or a place you saved, as a map link, an address, coordinates or a Plus code, without leaving the chat. Most chat apps can send a location, each in its own way, and some can't at all. The **Location** tool does it from the keyboard, so it works the same in every app: a tap finds you, you pick how the place should read, and the text goes into the box you're typing in. It also keeps a list of places you name yourself, like Home or Work, which go in with one tap and never need location access at all. Open **Location** from the toolbox, or type `location` or `gps` and tap the chip that appears. Typing `I'm at` or `my location` offers it too, and keeps your words, so the place goes in right after them. The panel has two tabs, **Here** and **Places**, a precision chip, and a shortcut to the tool's settings. Swipe sideways across the panel to change tabs. ## Sharing where you are The first time, **Here** shows a **Locate me** button and nothing else. Tap it and the keyboard explains what it is about to ask for, then Android shows its own location dialog. - **Only this time** is a good answer. The keyboard reads your position once and is done with it, so the grant has nothing else to do. Next time you tap Locate me, Android asks again. - **Approximate** works too. Android then hands over a point snapped to a grid of a few kilometres, and the panel says **Approximate** with an **Allow precise** link beside it for the day you want the street. - **Don't allow** leaves the tool exactly as it was. Saved places still work. Once Android allows it, the panel finds you the moment it opens, unless you turn **Locate when the tool opens** off. A position Android already holds from the last minute shows up at once, then the keyboard asks every location source the phone has switched on (the satellite receiver, the network one, and Android's own combined source on Android 12 and later) and keeps the best answer. It stops as soon as one is within about 25 metres, or after 20 seconds with whatever it has. Nothing keeps listening after that, and nothing runs in the background. If location is switched off for the whole phone, the panel says so and offers **Turn it on**, which opens Android's location settings. ## Reading the panel Instead of a map, **Here** shows a dial. A map would need pictures of the streets from a map server, and that server would learn roughly where you are every time the panel opened. The dial shows the two things you would check a map for, drawn to scale with north at the top: - **The ring** is how sure the reading is. A tight ring is a good fix; a wide one means the phone is guessing. - **The square** appears when you share at a coarser precision. It is the area that goes out, with a hollow dot at the point that is actually shared and the filled dot where you really are. If you'd like streets behind the dial anyway, name a tile server under **Map behind the dial** in settings. The keyboard then fetches the few tiles around you, and only from that server. None is built in, because the public OpenStreetMap servers don't allow apps to use them this way, and because every tile request says roughly where you are. Next to the dial: the address (when **Look up the address** is on), the coordinates, how accurate the reading is, how old it is, and its Plus code. **Open in maps** hands the point to your own maps app, which is the way to see it on a real map. Under that is a row of formats. Tap one and the box below shows exactly what will be typed, then **Insert** types it. Hold a format to make it the default; the default carries a small dot. | Format | What it types | |---|---| | **Message** (the default) | 📍 and the address on one line, the map link on the next. Without an address, the coordinates take its place. | | **Link** | Just the map link. | | **Coordinates** | `23.780600, 90.419300` | | **Degrees** | `23°46'50.2"N 90°25'09.5"E`, the way Google Maps writes them. | | **Address** | The short address, or the place's name. | | **Plus code** | `7MMGQCJ9+6P`, a grid reference any maps app can search for. | | **Custom** | Your own template, see [Options](#options). | Numbers always use a dot and Latin digits, whatever language the keyboard is in, because a map app reads `23,78` as two numbers. The buttons beside **Insert** save the place, open it in your maps app, make it the location the [weather and compass](https://wmkeyboard.pages.dev/tools/instruments/) use, and find you again. ## Precision The precision chip in the panel's header cycles through four settings, and the setting is remembered: | Precision | What a share gives away | |---|---| | **Exact** | The reading as it is. | | **About 100 m** | A point in a 100 metre square: the street, not the house. | | **About 1 km** | A point in a 1 kilometre square: the neighbourhood. | | **City** | A point in a 10 kilometre square, and only the city's name. | A coarser setting doesn't blur the point or add noise. It moves the point to the centre of a fixed square, and the squares are the same every time. That matters: ten noisy shares from one sofa can be averaged back to the sofa, but ten shares that all name the same square centre can't. Everything in a share uses the moved point, including the link, the Plus code and the address, and the address is looked up at a matching level, so a city share is answered with a city. ## Saved places **Places** lists the places you saved. Tap one to insert it in the format that's selected; it needs no location access and no network. The distance beside each one appears once the panel has a reading. Its menu opens it in your maps app, makes it the weather location, or deletes it. Save a place from **Here** with the bookmark button. It takes the first part of its address as its name, which you can change in settings. In Tools / Location you can also add a place by searching for it, by using where you are, or by typing its coordinates. Saved places are ordinary settings: a backup carries them, and [sync](https://wmkeyboard.pages.dev/reference/settings/backup/) brings them to your other phone. ## Places somebody sent you The **Found** tab reads the places in the text box and in your ten most recent clips: map links from Google Maps, Apple Maps, OpenStreetMap, OsmAnd, Waze and Bing, `geo:` links, coordinates in decimals or in degrees, minutes and seconds, and full Plus codes. Copy the link a friend sent and it's there when you open the panel. Each place shows where it was found, and its distance and direction from you once the panel has a reading. Tap a place for what you can do with it: - **Insert** types it in the format that's selected, so a long Google link becomes an address, a Plus code, or an OpenStreetMap link for someone who doesn't use Google. - **Open in maps**, **Save this place**, and **Use for weather and compass**, as on the other tabs. - **Point the compass** opens the [compass](https://wmkeyboard.pages.dev/tools/instruments/#compass-with-an-optional-qibla-bearing) with a 📍 marker on the rose pointing at the place, and the distance and direction under it. It measures from your current reading, or from the weather tool's saved place when there's none. **Stop pointing** under the rose takes the marker away. Reading is done on the phone, and nothing is looked up. A shortened link such as `maps.app.goo.gl/…` names no place until a server is asked, so it isn't listed. The text box is never read while it is a password field, and clips marked sensitive are skipped. Numbers that only look like coordinates are left alone: a pair needs at least three decimals on each side, and coordinates inside a link that isn't a map link don't count. ## A link anyone can open **Map link** in settings also offers **A WM Keyboard page**. The link goes to a page on this website with the place after the `#`, which a browser never sends to the site, so nothing about the place reaches it. The page shows the name, the coordinates and the Plus code, with a button for each map: Google Maps, OpenStreetMap, Apple Maps, OsmAnd and Waze, plus the phone's own maps app on Android. A map view is one more press away, and it loads from OpenStreetMap only then. Use it when you don't know which maps app the reader has. Another WM Keyboard reads the link back, name and all, in its Found tab. ## Sending a place to your computer With a computer paired through [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/) and in reach, the **Here** tab has a computer button, and every place on **Places** and **Found** has **Send to** with the computer's name. The place goes as a map link, in the map you picked, which the computer opens in its browser. A `geo:` link has nothing to open it on most desktops, so with that choice the computer gets an OpenStreetMap link instead. ## In snippets [Snippets](https://wmkeyboard.pages.dev/tools/snippets/) can carry a place. `{location}` is where you are, in the default format, and `{location:link}` or any other format name picks one: `message`, `link`, `coordinates`, `dms`, `address`, `plus_code`, `custom`. `{place:Home}` is a saved place by name, and takes a format the same way, `{place:Home:address}`. A snippet never waits on the satellites. `{location}` uses the reading the panel has from the last 15 minutes, or the one Android already holds from the last 30, and the address only when it was already looked up for that point. With no reading at all it expands to nothing, says so, and starts one for next time. The precision you picked applies here too. ## Photos with the place on them The [camera tool](https://wmkeyboard.pages.dev/tools/camera/#putting-the-place-on-a-photo) can stamp the place onto a photo, and type it into the box as the photo's caption. The camera button on **Here** opens the camera with the stamp on for one photo. Both use this tool's precision, address lookup and default format. ## Weather and compass The [weather and compass tools](https://wmkeyboard.pages.dev/tools/instruments/) share one saved place. Both now offer **Use my location**: on the weather panel when no place is saved, on the compass when the qibla needs one, and in the place dialog under Tools / Weather. It reads your position once, names it after the city when the address lookup is on, and saves it. Nothing updates it afterwards, so a forecast for home stays a forecast for home while you travel. ## What leaves the phone The position itself never leaves the phone by the tool's hand. It goes where you send the text, and nowhere else, with one exception you control: - **The address lookup.** With **Look up the address** on, the keyboard sends the shared point, at the precision you chose, to an address server once per share. The default is [Nominatim](https://nominatim.org), the geocoder OpenStreetMap runs. The request names the keyboard, as Nominatim asks, and is limited to one a second. Answers are kept in memory, so changing the format doesn't ask again. In the F-Droid edition you can point it at your own Nominatim server under [Servers](https://wmkeyboard.pages.dev/reference/settings/servers/). Turn the lookup off and nothing about your position goes anywhere until you send it yourself. The reading stays in memory for 15 minutes, so reopening the panel doesn't ask again, and is then forgotten. It is never written to storage unless you save a place. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) and [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). ## Options Tools / Location - **Default format**: Message. What **Insert** types. Holding a format in the panel sets this too. - **Map link**: Google Maps. Which map a link opens: Google Maps, OpenStreetMap, Apple Maps, OsmAnd, Waze, a `geo:` link, which opens the reader's own maps app but isn't a web address, or [a WM Keyboard page](#a-link-anyone-can-open) with a button for each. - **Precision**: Exact. See [Precision](#precision). The chip in the panel changes this same setting. - **Look up the address**: on. See [What leaves the phone](#what-leaves-the-phone). - **Locate when the tool opens**: on. Off, the panel waits for **Locate me** every time, even when Android already allows it. - **Custom format**: a template for the **Custom** chip. Words in braces are replaced: `{name}`, `{address}`, `{coords}`, `{lat}`, `{lon}`, `{dms}`, `{plus}`, `{link}` (in the map you picked), `{google}`, `{osm}`, `{apple}`, `{osmand}`, `{waze}`, `{geo}`, `{card}`, `{accuracy}` and `{time}`. A word with nothing to put in its place disappears, and a word in braces the tool doesn't know is left as you typed it. An example under the field shows the template filled in for the Eiffel Tower. - **Tile server**: blank. A `{z}/{x}/{y}` address, such as `https://tile.example.org/{z}/{x}/{y}.png`, for a map behind the dial. Use a server whose terms allow it; tiles are kept in memory only. - **Saved places**: each place, to rename, move or delete, and **Add a place**. - **Location access**: Precise, Approximate or Not granted. Tapping it asks Android, or, once precise is granted, opens the app's page in Android's settings, where it can be taken back. Location is part of the **Weather and location** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on. ## Details & edge cases - **A build can leave location out entirely.** Building with `-Pwmkb.noLocation=true` removes both location permissions. The tool then keeps its saved places, and **Locate me** and **Use my location** disappear. See [Building](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-location-permission). - **It isn't available before your first unlock.** Like the other tools that read your settings and saved data, Location waits until you've unlocked the phone once after a restart. - **The compass now points at true north once a place is saved.** The phone's sensor finds magnetic north, which is up to twenty degrees off in parts of the world. With a saved place the compass knows the difference and corrects for it, so the qibla marker lands where it should. - **No live location.** A shared position is a snapshot. Following someone as they move needs a server between the two phones, and this keyboard has none. - **No hardware-keyboard letter by default.** Bind one under [shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/) if you use it often, or reach it through the toolbox shortcut, double-tap Ctrl then T. --- # Look up URL: https://wmkeyboard.pages.dev/tools/look-up/ Section: Tools Summary: Look up a word in any app on a card over that app, from the text selection menu or by tapping it, with readings, pitch accent and Anki cards. Look up defines a word in any app without leaving it, the way Yomichan and Yomitan do in a browser. A card opens over the app with the word's meaning, its reading and pronunciation, and its translations. From the card you can hear the word, keep it in your vocabulary list, or make an Anki flashcard of it. The card answers from the [Dictionary tool's sources](https://wmkeyboard.pages.dev/tools/search/#dictionary-sources), in the order you set there. Add [imported dictionaries](https://wmkeyboard.pages.dev/tools/search/#imported-dictionaries) for Japanese, Chinese or any other language you are learning, and the card shows their readings, pitch accent and frequency too. ## Opening the card There are two ways to reach a word. **From the selection menu.** Select the word in any app and pick **Look up** from the menu that appears over the selection. On some phones it is behind the menu's three dots, and some apps leave other apps' entries out of their menu entirely ([why](https://wmkeyboard.pages.dev/smart/selection-actions/#when-the-entry-is-missing)). Select more than one word and the card shows a chip for each, with the first one looked up; tap another chip to look that one up. This works in every build, Play Store included, and needs no permission. **By tapping it.** Give the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/) the **Look up a word** action, on a gesture or in its quick menu. Use it, and a hint at the top of the screen says "Tap a word to look it up". Tap the word and the card opens for it. A swipe instead of a tap, or 10 seconds with no tap, cancels it. This needs the floating button's screen reading service, so it is not in the Play Store build. See [How a tap finds the word](#how-a-tap-finds-the-word). ## The card - The word, with its reading over it in small kana when it is written in kanji, and its IPA when the source has it. - A speaker button. It plays a recording of a real person saying the word, from Wiktionary or, with your own key, Forvo, and shows a chip for every voice it found, so you can pick another. See [Recorded pronunciations](https://wmkeyboard.pages.dev/tools/search/#recorded-pronunciations). When nobody has recorded the word it plays the source's recording if there is one, and otherwise, or when the recording can't be fetched, says the word in the dictionary's language: with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for that language, or else the phone's text-to-speech voice. - The pitch accent and frequency line from an imported dictionary, as in "たべꜜる [2] · Frequency 512". The ꜜ marks where the pitch falls; [0] is a flat word. - The meanings, grouped by part of speech, with examples where the source has them. - [Translations](https://wmkeyboard.pages.dev/tools/search/#translations) into your languages, as the Dictionary tool shows them. - A line naming the source or the imported dictionary that answered. Under it, a row of actions: - **Add to Anki** makes a flashcard of the word. See [Anki](#anki). - **Add to My words** adds the word to the [vocabulary tool's](https://wmkeyboard.pages.dev/tools/vocabulary/) My words list. Shown while the Vocabulary tool is on. - **Copy** copies everything on the card as text. - **Ask AI** opens an [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) about the entry. Shown while the AI tool is on. Tap outside the card, or press Back, to close it. ### Sheet or popup Tools / Dictionary / Look up in other apps / Card for a tapped word By default the card is a short sheet at the bottom of the screen, with the app showing above it. For a tapped word you can have it **Beside the word** instead: a small card just below the word, or above it near the bottom of the screen, with the word itself marked in your accent colour. A word from the selection menu always gets the sheet, because the menu does not say where on the screen the word is. In an app that does not report where its letters are (see below), the card sits by the piece of text you tapped, with nothing marked. ## How a tap finds the word The screen reading service finds the smallest piece of text under your finger and asks the app where each of its letters is. Ordinary apps answer, so the card knows the exact letter you tapped and can mark the word. - **Words with spaces.** The word is the one the tapped letter belongs to, as Android splits words for that language. - **Japanese, Chinese, Thai and other text without spaces.** Look up does what Yomichan does: it tries the longest stretch from the tapped letter that an imported dictionary knows, inflected or not, down to one character. Tap the start of 食べました and the card opens on 食べる. Without an imported dictionary, Android's own word splitting decides. - **Apps that do not report their letters.** Some apps built with Jetpack Compose, and many web pages, give the text but not where each letter is. Then the card opens with a chip for each word in the text you tapped, so you pick the word yourself. Only the text under your finger is read, once, when you tap. Nothing is read at any other time, password fields are skipped, and nothing is kept. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). ## Anki Tools / Dictionary / Look up in other apps / Add to Anki **Add to Anki** sends the word to [AnkiDroid](https://github.com/ankidroid/Anki-Android). Without AnkiDroid installed, the button says so. There are three ways it can go: - **Automatic** (the default): straight to the deck when AnkiDroid has let WM Keyboard add to it, and otherwise AnkiDroid's note editor, filled in, for you to save. - **Straight to the deck**: the first time, WM Keyboard explains and asks for AnkiDroid's permission. After that, a tap adds the note and the button says **Added to Anki**, or **Already in Anki** when the deck has a note for that word. - **AnkiDroid's editor**: always the editor, filled in. This needs no permission. Notes added straight to the deck go to the deck named under **AnkiDroid deck**, "WM Keyboard" by default, which AnkiDroid makes the first time. They use a note type named **WM Keyboard Look-up**, also made on first use, with six fields: Word, Reading, Pitch, Meaning, Sentence and Source. The sentence is the one the word was in, when it came from a tap or a longer selection. The front of the card shows the word and its sentence, and the back adds the reading, pitch, meaning and source. You can change the note type's look in AnkiDroid like any other. Each note is tagged `wm_keyboard`. Through the editor, the word goes in the note's first field and the rest, one item a line, in the second, in whichever note type AnkiDroid's editor opens with. ## Options Tools / Dictionary / Look up in other apps - **Look up in the selection menu**: on by default. Off, Look up leaves every app's selection menu. - **Tap a word on the screen**: opens the floating button's settings, to give it the Look up action. Not in the Play Store build. - **Card for a tapped word**: **Sheet at the bottom** (default) or **Beside the word**. Not in the Play Store build. - **Add to Anki**: **Automatic** (default), **Straight to the deck** or **AnkiDroid's editor**. - **AnkiDroid deck**: the deck notes go to, "WM Keyboard" by default. - **Takoboto**, **Shinjikai**, **Jisho** and **Wiktionary**: off by default. Each one turned on is offered in the Dictionary's **Open in** menu, and opens that site on the word, in the browser or in the site's own app when the app claims its links. They're sites with no way for the keyboard to ask them directly, so the answer is the site's own page. - **Your own website**: any other dictionary site, as its address with `%s` where the word goes, such as `https://example.com/search?q=%s`. It shows in the menu under its host name. ## Details & edge cases - **What leaves the phone.** The same as for the Dictionary tool: the word goes to an online source only when the sources before it did not know it, in the order you set. Imported and offline dictionaries answer on the phone. The surrounding text never leaves it. - **Long selections.** The card reads at most 2,000 characters of a selection and offers chips for its first 40 words. - **Japanese inflections.** The dictionary form is found for polite, negative, past, て and conditional forms, the potential, passive and causative, たい, ている and its kin, and い-adjectives, including chains of them such as 食べられなかった. Irregular readings outside those rules may not be found. --- # Media controls URL: https://wmkeyboard.pages.dev/tools/media/ Section: Tools Summary: Play, pause and skip whatever is playing (with album art) from the keyboard. Media controls turns the keyboard into a transport for whatever is playing elsewhere on your phone: album art, track title, a seek bar, and play/pause/skip. It's the same session your lock screen and quick-settings tile already show, so it saves you from switching apps just to pause a song. tools/media-now-playing — Media controls showing album art, track info, a seek bar, and the play/pause/skip transport. ## Opening the panel Open **Media controls** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. It isn't pinned out of the box, so the first time you'll likely reach it through the toolbox. Once notification access is granted it pins itself while music is playing, which is how you'll usually reach it after that: see [Pinning while music plays](#pinning-while-music-plays). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press M. Unlike many other tools it has no suggestion-strip keyword shortcut, so there's no word you can type to summon it. The panel shows one of three things: - **Now playing**: album art (or a music-note placeholder if the app didn't publish any), title, artist and album, with a seek bar and Previous / Play-Pause / Next buttons. - **"Nothing plays now"**: shown when no app currently holds an active media session, with a line underneath telling you to start a song, video or podcast in any app. Do that and its controls appear here automatically. - **A one-time access prompt**: shown until you grant notification access. See below. ### The transport - **Play/Pause** toggles playback. **Previous** and **Next** skip a track. Both go dim and unresponsive if the current app's session doesn't advertise that action. Some players don't expose a "next track" action for the current content, during a live stream or a single long file for instance. - The **seek bar** only appears once the session reports a track duration. Drag it and release to jump playback to that point. It's disabled the same way the skip buttons are, whenever the session doesn't support seeking. - Controls act on whichever app currently owns the active media session, usually the one you left playing. If more than one app is publishing a session at once, it's the one actively playing. There's no in-panel app switcher, so open the source app directly to change what's playing. ## Options Tools / Media controls - **Enabled**: which way this starts depends on how you answered the setup wizard. Media controls is only in the "show me everything" starter set. Pick either of the two simpler answers and it arrives switched off, so you turn it on here. Turning it off removes Media controls from both the toolbar and the toolbox. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are turned on globally (on by default). It overrides this tool's icon colour, purple out of the box. - **Pin while music plays**: on by default. See below. - **Music players**: which apps count as music. Only shown while the pin is on. - **Notification access**: reports whether the grant is in place, and opens the screen that gives it. Same grant as the one the panel asks for, in a row you can check without opening the keyboard. ## Pinning while music plays With **Pin while music plays** on, starting a song puts Media controls on the toolbar by itself, so pause and skip are one tap away instead of a trip through the toolbox. Stop the player and the pin goes with it. It is deliberately unobtrusive: - The tool goes on the end of the toolbar. Nothing you pinned yourself moves, and their [hardware digit shortcuts](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) keep the numbers you already know. - If you have already pinned Media controls by hand, nothing happens. There is one slot for one tool, and your own pin wins. - The pin is never written to your pinned-tools list. It is drawn, not saved, so it cannot survive a reboot or turn up in a backup. - Pausing does not unpin. The pin arms the first time an app on your Music players list actually plays, then stays through a pause so you can still hit play again, and clears when that player gives up its session. - Dragging the pinned icon around the toolbar pins it properly, the same as dragging any tool out of the toolbox. That is the way to make it permanent. The pin needs [notification access](#granting-notification-access), same as the panel. Without it, the keyboard cannot see that anything is playing, so nothing is ever pinned. [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/) turns the pin off. Keeping it accurate means watching the media session for as long as the keyboard is on screen, which is the sort of background work a low battery should not pay for. The tool stays in the toolbox. ### Which apps count as music Tools / Media controls / Music players Any app can hold a media session: a video, a game, a browser tab with an autoplaying clip. Pinning a music transport because of one of those would be noise, so the pin only fires for apps on this list. WM Keyboard ships it ticked for the players most people use, streaming services, offline players, podcast and audiobook apps. No such list can name every music app, so the picker is there to fix the gaps: - The **Players** group leads with the installed apps that look like players, which means anything declaring a media browser service (the one Android Auto and Assistant use to find music apps), plus whatever you have already ticked. - The search box below finds anything else on your phone, by app name or by package name. - **Reset to the shipped list** puts back the ticks a fresh install starts with. - An app you ticked and later uninstalled stays in the list, marked as not installed, so reinstalling it does not mean finding it again. Untick everything and nothing ever pins, which is a perfectly good way to keep the pin switched on but silent for now. ## Granting notification access The Android API that hands over another app's media session is `MediaSessionManager`, and it's gated behind notification-listener access, the same permission class notification-reading apps use. This tool never reads a single notification anyway. One other feature shares that grant: the [one-time code chip](https://wmkeyboard.pages.dev/smart/one-time-codes/), which does read notification text, and only while you have it switched on. See [privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for how this fits the keyboard's per-tool, opt-in approach to anything that touches data outside the keyboard itself. The panel explains the grant up front: > WM Keyboard needs notification access to show and control the media. This tool reads no notification content, only the media session. The same access powers the optional one-time code chip, which reads notification text only while you have that turned on. tools/media-access-prompt — The access prompt shown before notification-listener access is granted. Tap **Allow access**. A short explanation appears first, restating what the access is for. Tap **Open system settings** on it. Your device's Notification access screen then opens on WM Keyboard's row. On Android 10 and older it opens the full list instead, so find WM Keyboard yourself. Look for **"WM Keyboard media controls and one-time codes."** That's the label the system lists it under, and it names both features because both ride on the one grant. An IME cannot show its own permission dialog, which is why this detour through system Settings is needed. You do it once, and you can revoke it at any time from the same Settings screen. The keyboard re-checks the grant every time it returns to the foreground, so grant it in Settings and come back: the panel picks it up without being reopened. Media controls are the **Media controls** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, the pin and the music apps screen leave the keyboard and the app until the bundle is switched back on. ## Details & edge cases - **Works with anything that publishes an Android media session**: music players, YouTube, podcast apps and the like. That isn't every app that makes sound. An app has to implement the platform's Media Session API for its controls to show up here. One that plays audio without publishing a session won't appear. - The **listener service** that unlocks this (`MediaNotificationListener`) is one component worn by two features. For media controls it is an empty stub: the tool needs the grant, not the notifications, and nothing on that path reads a notification. The service's one working method belongs to the [one-time code chip](https://wmkeyboard.pages.dev/smart/one-time-codes/), and it returns immediately unless that chip is switched on. Never renamed, because Android stores the grant against the component name. - The seek bar is touch-only: it doesn't participate in hardware-keyboard focus-ring navigation, so Tab/arrow-key cycling skips over it. The three transport buttons do take the ring, and Enter on a dimmed Previous or Next does nothing, exactly like a tap. - Media controls has no direct-boot support. It isn't available on the lock screen before you've unlocked your device once after a restart, since the notification-listener grant and session data aren't available that early. - This tool ships in every build. There's no build gating to worry about here. --- # Offline voice (Moonshine) URL: https://wmkeyboard.pages.dev/tools/moonshine/ Section: Tools Summary: Quick English dictation on your phone with Moonshine Tiny, a model made for short phrases. Offline Moonshine is an English dictation engine that runs entirely on your phone, with [Moonshine Tiny](https://github.com/moonshine-ai/moonshine), a 27-million-parameter model made for short phrases on small devices. Once the model is downloaded, no audio leaves the phone. While a layout in another language is in use, dictation uses the system recognizer instead. ## Setting it up Voice typing Add **Moonshine** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download the model from the **Moonshine model** group there: 151 MB in three files, from Hugging Face at a fixed revision, each checked against its published SHA-256. ## How it works Moonshine transcribes a phrase once you stop, and a phrase can run to 30 seconds. Where Whisper always pads what you said to a 30-second window, Moonshine reads only as much audio as there is, so a three-second phrase costs a three-second encode, which is what makes it quick on a phone. The text arrives punctuated and capitalised, then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). ## Moonshine, Asr4All or Whisper For English, [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) shows the words while you speak, and the [whisper.cpp Whisper models](https://wmkeyboard.pages.dev/tools/whisper/) take your own words as hints and run to two minutes. Moonshine is the small, quick one: the least to download among them for its accuracy, and the fastest to answer a short phrase. ## Details & edge cases **No words to listen for.** Moonshine has no way to take a list of names or terms, so **Words to listen for** does not show while it is the engine. **Every build.** The runtime, [ExecuTorch](https://github.com/pytorch/executorch) built for the keyboard's models alone, is in every build; F-Droid compiles it from source. A build made without it does not offer the engine. **The licence.** Moonshine is Useful Sensors' model, under the MIT licence; it is credited in the app's About screen. --- # Music ID URL: https://wmkeyboard.pages.dev/tools/music-id/ Section: Tools Summary: Name the song that is playing from the keyboard through a recognition service of your choice, then type it into the chat or send it as a picture. Somebody asks what is playing, or you want to tell them. **Music ID** listens for ten seconds, names the song, and puts it into the box you're typing in as text, or as a picture with the cover on it. It works the way Shazam does, without leaving the chat. The keyboard carries no recognition service of its own: you add one with a key of yours, or point it at a server you run, and the clip goes there. Open **Music ID** from the toolbox, or type `shazam` and tap the chip that appears. Typing `what song is this` or `what's this song` offers it too, and keeps your words, so the answer goes in right after them. The panel has two tabs, **Listen** and **History**, and a shortcut to the tool's settings. ## Listening Tap the big button and hold the phone towards the music. The ring around the button follows what the microphone hears, and the count under it says how long is left; ten seconds by default, which is what the services work best with. Tap again to ask early once four seconds are in. The keyboard records only while the panel listens, and never keeps the recording. Before it listens, the tool looks at what the phone itself is playing. With [notification access](https://wmkeyboard.pages.dev/tools/media/#granting-notification-access) granted and a player running, the song comes straight from the player, with its cover, and no recording is made at all; **Listen anyway** records regardless, for the song playing in the room rather than in your headphones. Turn **Check what the phone plays first** off to skip this. Then the clip is asked about: the recognisers on the tool's list, one after the other, until one names the song. The clip goes up compressed as Opus (about 25 KB) where the keyboard has that library, as a 16 kHz WAV (about 320 KB) otherwise. With no recogniser on, the panel says so and opens the settings. ## What you get The song's cover, title, artist, album and year, and who named it. Under it: - **Insert** types the song in the format picked in settings, **Title – Artist** out of the box. The chips beside it type the other shapes, each shown as it would read: artist first, with the album and year, the link alone, the title with the link under it, or `🎵 Now playing: …`. - **Picture** opens a row of styles and sends the song as a picture: the **Cover** alone, a square **Card** with the words under the cover, a wide **Banner** with the words beside it, or a tall **Story**. The ground is coloured after the cover. The picture goes into the field the way every picture from the keyboard does: as a PNG, WebP or JPEG, whichever the app takes, and to the clipboard or the share sheet where it takes none. Without a cover the picture carries a note glyph instead. - **Open in** opens the song in a store the recogniser linked: Spotify, Apple Music, Deezer, YouTube, a song.link page that lists every store, MusicBrainz, or Shazam. Which one comes first is **Open in** in settings. - **Copy** puts the default text on the clipboard. - The round button listens again. A song that came without a cover or links (ACRCloud and AudioTag answer with the names alone) is looked up by its title and artist on the iTunes Search API and, failing that, Deezer, both without a key, to fetch the cover and a store link. The lookup sends the title and artist and nothing else, and only when data saving allows online recognition outright; see below. ## History Every song recognised is kept in the **History** tab, newest first, on this phone only, up to 200. Tap one to have it back on the Listen tab with its inserts; the bin on a row forgets it. The same list is under Tools / Music ID / Recognised songs, where each row shows what it would type. **Keep a history** off keeps nothing new. ## Recognisers Tools / Music ID / Song recognisers A [provider list](https://wmkeyboard.pages.dev/tools/providers/). Every kind is listed but off until you give it a key and switch it on, since each is sent the clip; the tool stays off the toolbar until one is. | Recogniser | Needs | Cost | What it is sent | |---|---|---|---| | **AudD** | a token from [dashboard.audd.io](https://dashboard.audd.io) | a few hundred recognitions free, then paid | the clip and the token | | **ACRCloud** | a project's host, access key and access secret from [console.acrcloud.com](https://console.acrcloud.com) | a two-week trial, then paid plans | the clip, signed with the key and secret | | **AudioTag** | a personal key from [audiotag.info](https://audiotag.info) | free for personal use, a handful a day | the clip; the answer takes a few seconds | | **Shazam (unofficial, via RapidAPI)** | a RapidAPI key, subscribed to the Shazam listing | a free quota, then paid | the last five seconds of the clip, raw | | **Your own recognition server** | its address, and a key if it wants one | yours | the clip, as AudD takes it | Shazam's row is unofficial: Apple offers no API an open source keyboard could ship, so this one goes through a third party's listing, which can change or stop without notice. Its description says so. Every row is skipped in a password field and while typing in incognito, and held back under [data saving](https://wmkeyboard.pages.dev/reference/settings/data-saver/): the **Song recognition online** row there asks before each clip on mobile data by default. ## Your own recognition server A server you run that takes the clip and answers with the song. It speaks AudD's shape, so anything that does, or a thin wrapper around a fingerprinter such as [Panako](https://github.com/JorenSix/Panako), [Olaf](https://github.com/JorenSix/Olaf) or [audfprint](https://github.com/dpwe/audfprint), fits: - The keyboard posts `multipart/form-data` to the address you give, with the clip in the `file` field (`clip.ogg`, Ogg Opus, or `clip.wav`, 16 kHz 16-bit mono WAV; a server that answers 400, 415 or 422 to the Opus upload is sent WAV from then on) and, when you set a key, `api_token`. - A match answers `{"status":"success","result":{"artist":"…","title":"…","album":"…","release_date":"2019-05-17","label":"…","timecode":"00:36","song_link":"…"}}`; any of the fields may be left out. No match is `{"status":"success","result":null}`. A refusal is `{"status":"error","error":{"error_code":900,"error_message":"…"}}`, and the message is what the panel shows. - AudD's optional `apple_music`, `spotify` and `deezer` objects are read for the cover and the links when present. This is what the F-Droid edition's no-proprietary-services rule asks for: every online step has a self-hosted alternative. The iTunes and Deezer lookups can be pointed elsewhere under [Servers](https://wmkeyboard.pages.dev/reference/settings/servers/), under **Song recognition**. ## What leaves the phone - **The clip** goes to each recogniser that is asked, with your key for it, until one answers. Each is its own row in the [network activity log](https://wmkeyboard.pages.dev/privacy/network/), so the log says which service heard it. The recording is never kept and never sent anywhere else. - **The title and artist** of a song already named go to the iTunes Search API or Deezer for its cover, when the recogniser gave none. - **The microphone** is used only while the panel listens, and released when the panel closes, the keyboard hides or the field goes. - **Nothing at all** when the song came from the player on the phone, unless you ask for a cover it did not have. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) and [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). ## Options Tools / Music ID - **Song recognisers**: the list above. - **Insert as**: Title – Artist. What **Insert** types. - **Picture style**: Card. What the **Picture** chip makes first. - **Open in**: the first the song has. The store a link points to, when the song is on it. - **Listen for**: 10 s, between 6 and 12. Longer clips match more reliably and cost the same. - **Check what the phone plays first**: on. - **Keep a history**: on. **Recognised songs** opens the list. Music recognition is the **Music recognition** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool and its history leave the keyboard and the app until the bundle is switched back on. ## Details & edge cases - **Four seconds is the least it asks about.** Stopping earlier shows a notice and listens on. - **A quiet room is reported as nothing heard**, not sent to a service: a clip below the keyboard's own silence floor would cost a request to be told "no match". - **Voice typing and Music ID share the microphone.** While a dictation runs, Listen waits and says so. - **The Quick Settings microphone tile** blocks the microphone for every app. The panel notices and says so rather than listening to silence. - **No hardware-keyboard letter by default.** Bind one under [shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/), or reach it through the toolbox shortcut, double-tap Ctrl then T. - **It isn't available before your first unlock.** The history lives in the keyboard's files, which are locked until then. - **Humming is not recognised.** Every service here matches the recording itself; a cover version or a live take usually goes unmatched. - **Nothing runs on the phone.** There is no built-in catalogue and no fingerprinting of your own files: a song you have downloaded is one you already know. --- # Notes URL: https://wmkeyboard.pages.dev/tools/notes/ Section: Tools Summary: A notebook inside the keyboard: jot while you type, tag and pin, tick off checklists, lock the private ones, and keep every note in your backup. The **Notes** tool is a notebook that lives in the keyboard. Ideas, a diary, a shopping list, the thing someone just said in a call: write it down without leaving the app you are in, and find it again from any other app. Notes are plain text that the keyboard draws as Markdown, so a heading, a list or a `- [ ]` task box looks like one, and a task box can be ticked with a tap. Tags are buttons along the top of the list, so a tap on **Work** shows only the notes you filed under it. The same notes are in the settings app, where a full-screen editor with a real text field is the better place to write anything longer than a few lines. Both sides write the same file, so a note made on the keyboard is in the app when you come back to it, and the other way round. ## Opening it Notes is also a bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), as **Notes**. Off there, the tool, the quick note action and the notebook screens leave the keyboard and the app together; the notes stay on the phone. Tap **Notes** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. Typing `notes` into a text field offers the tool as a chip on the suggestion strip, like every other tool keyword. In the settings app the notebook is one tap from the tool's own page: Tools / Notes / Your notes **Add to home screen**, under it, puts an icon for the notebook on your home screen, which opens it straight away without going through the settings. The panel opens on the list by default. Tools / Notes / The keyboard panel opens on can make it open on the note you edited last instead, which suits a running diary or a list you add to all day. ### Quick note The **Quick note** action opens the panel straight on a new note, with the keys already typing into its body, so a thought goes down in one press. Bind it to a flick, a key's long press, a toolbar button's hold or the floating button. It's an action rather than a tool, so it never shows on the toolbar or in the toolbox: you'll find it under **Other** in those pickers. Fire it again while the note is up to close the panel. Under the editor's Markdown chips, a quick note adds a row of your latest notes, the one you touched last first, which you can scroll sideways. Tap one to switch to it with the caret at its end, ready to add another line to a list you keep going, like the bugs you run into in an app. **New note** at the start of the row goes back to a fresh one. The note you leave is saved as it is; a new one you never typed into is deleted rather than left behind as an empty note, and the same happens when you close the panel or the keyboard. Locked notes, and notes too long for the keyboard's editor, stay out of the row. ## The shelf and the tags The list is a shelf. Pinned notes sit on their own row at the top, as wide cards you scroll sideways, each washed with the colour of its first tag and striped along its edge; a note with no tag takes the theme's accent. The rest fill a two-column grid under them. A card shows the note's title, a line or two of its text, when it was last edited, and a dot for each tag; a checklist shows a small ring of how much is ticked, and a locked note a padlock. Cards arrive one after another as the panel opens, and squash a little under your finger. Along the top, next to Back, the filter chips scroll: **All**, **Pinned**, one chip per tag, **Archived** and **Trash**, then **Manage tags**. The tag chips are your tags, in your colours; there are none until you make one. The round search button at the end turns the header into a search pill, which searches titles, tags and text as you type into it, the way the clipboard's search works; the keys type into the search rather than into the app behind the keyboard, and the chips move down under the pill so a filter stays a tap away. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, notes that are about what you typed follow the matches, from the same chip; locked notes are left out of it. Once the search holds two letters or more, the paragraphs that contain them come first, under **Passages**, above the notes. Each shows its paragraph with the match picked out and, under it, the note it comes from. Tap one to type that paragraph into the field as the note has it, without opening the note; hold it to open the note instead. A long paragraph is cut down to the line around the match, and a long line to the sentences around it. One note gives three passages at most, a search lists 24, and locked notes give none. Tap a card to open it. Hold a card for its actions right there on the card: **Insert**, **Copy**, **Edit**, **Share**, **Pin**, **Archive**, **Lock** and **Delete**. Swipe a card to the left instead for **Pin**, **Archive** and **Delete** behind it; a long swipe past about half the card moves it to the trash at once, with the Undo bar to bring it back. In the trash, the swipe shows **Restore**. Holding a tag chip opens the tag manager in the settings app. A **Jot something…** pill floats along the bottom of the shelf. Tap it, or its **+**, to start a new note; it steps aside while the Undo bar is up. On an empty tag, the empty list offers **New note tagged** with that tag already on it. ### The timeline Tools / Notes / Keyboard panel layout swaps the shelf for a timeline: a rail of days down the left, **Today** and **Yesterday** first and dates after them, with one compact row per note beside it, newest first. A row leads with the checklist's ring, a padlock, or a text glyph in the tag's colour, then the title with a pin when the note is pinned, a line of its text with the checklist's count in front, its tag pills, and the time for today's notes or the weekday for the rest of the week. The chips, the search, the swipes and the holds work the same, and a round **+** button in the corner starts a new note. The shelf is the default. ## Writing a note A new note opens its editor with the title line focused: type a title, press Enter, and you are in the body. A title is optional. A note with none is named after its first line, with the Markdown taken off, so `# Shopping` on the first line is a note called Shopping. The editor collapses the panel so the key rows come back underneath it, the way the clipboard's clip editor does. Enter is a line break. Inside a list it carries the list on: `- ` after a bullet, `3. ` after `2. `, a fresh `- [ ] ` after a task box, and Enter on an empty item ends the list. A row of chips above the keys inserts the Markdown you reach for most: bold, italic, a bullet or a numbered list, a task box, a heading, a quote and code. Each one wraps whatever is selected, or toggles its prefix on the lines you selected. The row's end counts your words and characters. Everything you type is saved as you go. There is no Save button and nothing to lose when the keyboard closes or another app takes the field: the draft is written first. **Done** keeps the note and goes back to reading it; **Cancel** puts the note back the way it was when you started, and throws away a new note altogether. A new note left blank is not kept. Tags sit above the body. Tap one to take it off the note, or **+ Tag** for the picker: your other tags first, then a few starters (Important, Work, School, Study, Personal, Ideas) until you have tags of your own, and **New…** to type a name. A tag is made the first time it goes on a note. ## Reading a note An open note is its card grown to fill the panel: its tag pills along the top with when it was edited at the right, the title large, and the note under it. Tapping a tag pill filters the list by that tag. Along the bottom sit **Insert**, **Edit**, **Copy** and **Share**; in the header, **Pin**, **Lock**, the arrows that make the note taller, and **More**, which unfolds a row with **Pin**, **Archive**, **Lock** and **Delete**. Back shrinks the card into the shelf again. The reading view draws the note as Markdown: headings (including the `===` and `---` underline kind), lists, nested quotes, tables, footnotes, strikethrough, links, code blocks with their keywords, strings and comments coloured, LaTeX maths between `$` or `$$` signs, and task boxes you can tick and untick. A link opens in your browser; a bare address like `https://example.com` is a link too. An image shows its description until you tap it, and only then is the picture downloaded, so reading a note never contacts a server on its own. Ticking a box rewrites that one line of the note, so the text behind the view always says what the view shows. Tap anywhere in the text to edit from that line. Hold a word to select it, as in the [Wikipedia](https://wmkeyboard.pages.dev/tools/search/) reader: drag the handles to take more, then **Copy**, **Insert**, **Select all**, or **Ask AI** about the passage when the AI tool is on. The settings app's Preview selects text the same way, with the phone's own Copy bar. The bar under the note holds the same actions the list offers: **Insert** types the note into the field behind the keyboard, as it was written; **Copy** puts it on the clipboard; **Share** hands it to another app through the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/), whose **Insert** types it into the field too. The arrows in the header make the note taller, up to most of the screen, for reading something long. A locked note, or one in the trash, keeps a row of chips instead, with only what it may do. A long note shows its first part and a **Show more** chip. A very long one, past twenty thousand characters, still reads here but edits in the settings app, where the Edit chip takes you. Tools / Notes / Format Markdown turns the formatting off, for anyone who would rather see exactly what they typed. ## Pin, archive, trash **Pin** keeps a note at the top of every list, whatever the sort order. **Archive** takes a note out of the main list without deleting it: it waits under the **Archived** chip and still turns up in a search of that chip. **Delete** moves a note to the **Trash**, and an Undo bar gives you a few seconds to change your mind; the trash itself is the longer undo, with **Restore** on every note in it. Notes in the trash are deleted for good after thirty days, or after seven or ninety, or never, under: Tools / Notes / Keep trashed notes for **Delete forever** and **Empty trash** ask for a second tap. In the settings app the same actions are on each note's menu, and the list there has a select mode: hold a note, tick the others, and pin, archive, tag, lock or trash them together. Swiping a note to the left shows **Pin**, **Archive** and **Delete** behind it for that one note, and a long swipe past about half the row moves it to the trash straight away, with the same Undo bar. In the trash, the same swipe shows **Restore** and **Delete forever**. The sort order, last edited by default, is Tools / Notes / Sort notes by. ## Locked notes A note can be locked, from its actions on the keyboard or from the menu in the app. A locked note shows its title and nothing else in every list, and the keyboard never shows, inserts, copies or shares its text. In the settings app it opens only after the same check the [app lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/) uses: your fingerprint or screen lock, or the PIN, password or pattern you set there, and taking the lock off asks too, whether from the note's menu or for several notes at once in select mode. Opening a locked note asks once; if the whole Notes screen is already behind the app lock, it does not ask again. This is a lock on the screen, not on the data. The note is stored like any other note, so a backup file, Android's own backup, or a computer with USB debugging still reach it. To keep a locked note out of the backup file, see [Backups](#backups) below. On a phone with no fingerprint and no screen lock set up, there is nothing to ask for, and locked notes open with a caption that says so. ## Save a selection as a note With [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, selecting text in any app offers **Save as note** on the bar. It saves the selection as a new note and says so with a toast, leaving the field as it was. Tools / Notes / Save as note from a selection can make it add the selection to the end of the note you edited last instead, which turns a day of selections into one running note. Nothing is saved from a password field, and nothing can be saved before the phone's first unlock after a restart, since the notes file is behind the lock screen until then. ## Save from another app's share sheet Tools / Notes / Save to notes from other apps Share text from any app and pick **Save to notes**: it becomes a new note, with the share's subject, when there is one, as the title. This works with the keyboard closed, and with no selection actions needed. Share a link instead, a page from the browser's Share menu for instance, and it becomes a read-later note. The keyboard reads the page once, keeps its title and its text with the layout taken off, puts the link at the top, and tags the note **Read later**, so the tag's filter gathers everything you saved for later. The [Read back](https://wmkeyboard.pages.dev/tools/read-back/) tool can then read it to you. There is no JavaScript, so a page that draws itself with scripts comes back short; a page that cannot be read at all, or the build with no internet, saves the link alone, and the toast says which. The page request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Notes: a shared page**. Data saver leaves it alone, since it is one page you asked for. The switch above takes **Save to notes** out of the share sheet. It is on by default. ## Export, share and import In the settings app, a note's menu shares it as a Markdown or a plain text file, named after its title. **Export all as a zip** under the list writes one Markdown file per note, archived notes included and locked notes left out, with the note's tags on a `Tags:` line at the end. **Import text files** reads `.txt` and `.md` files back as notes, one per file, skipping any that already exist word for word, and reads a `Tags:` last line back as tags. A note holds up to 100,000 characters, and a longer file is cut there. Importing is also the way to keep texts you quote often within reach: a speech, a book's chapter, a set of answers you give again and again. Give them a tag such as **Quotes**, and the search's [passages](#the-shelf-and-the-tags) type a quote from them straight into a chat. Pick the tag's chip while you search to look in those notes only. The [clipboard's search](https://wmkeyboard.pages.dev/tools/clipboard/#searching-your-notes-too) lists the same passages above your clips, so you can reach a quote without leaving the clipboard. **Export to an Obsidian vault** asks for the vault's folder and writes each note to a `WM Keyboard` folder inside it, with its tags and dates in front matter so Obsidian sees them, and any space in a tag turned into a dash. It leaves out locked notes the same way. Export again to update the files: a note whose file is there overwrites it, so edits made to that file in Obsidian are lost, and a note you renamed gets a new file beside the old one. It runs when you tap it, not in the background. Import reads those files back, tags and dates included, and leaves front matter with any other key in the note's text. ## Backups Notes are a section of the [backup file](https://wmkeyboard.pages.dev/privacy/backup/), on by default for the automatic backup and for sync between phones. Every note, its tags and the trash travel in it, except locked notes, which stay out unless **Include API keys** is on: a backup file is easy to send yourself or drop in a shared folder, and a locked note is one you asked to keep from view. Restoring a backup that left locked notes out keeps the ones already on the phone. ## Privacy Notes stay on the phone. Nothing is sent anywhere unless you tap an image in a note to load it, share a link to **Save to notes** (which fetches that page), share a note, export the zip or to a vault, or back the file up to a location you chose. The keyboard never learns from a note: what you type into it does not reach your personal dictionary, your word pairs or your typing statistics. The tool is not available before the first unlock after a restart, when the keyboard has no access to its files. --- # Offline voice (Omnilingual) URL: https://wmkeyboard.pages.dev/tools/omnilingual/ Section: Tools Summary: One offline model for about 380 keyboard languages, many with no other voice typing: Meta's Omnilingual ASR on your phone. Offline Omnilingual runs [Omnilingual ASR](https://github.com/facebookresearch/omnilingual-asr), Meta's speech recognition model for more than 1,600 languages, on your phone with ONNX Runtime, the engine [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) runs on. One model covers every language it knows, and it works out which one you speak by itself, while the keyboard keeps what it writes to your language's script. About 380 of the keyboard's languages are among them: Hindi, Tamil, Swahili, Yoruba and Persian, but also hundreds that no other engine here can take, such as Nuer, Nande or Tarifit. Once the model is downloaded, no audio leaves the phone. ## Setting it up Voice typing 1. Add **Omnilingual** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**. Picking it per language is the better fit when another engine already serves your main language well. 2. Press **Omnilingual** on the list to open its screen, which holds the **Omnilingual model** group. The first time, a banner offers ONNX Runtime, the engine it runs on, shared with Sherpa: a one-time download of about 33 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub and F-Droid builds; the offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first. 3. Download **Omnilingual ASR 300M**: 367 MB from Hugging Face at a fixed revision, every file checked against its published SHA-256. On mobile data it asks first. | Model | Languages | Download | Licence | |---|---|---|---| | Omnilingual ASR 300M | about 380 keyboard languages (1,600 in all) | 367 MB | Apache-2.0 | The download includes Silero VAD (2 MB), the voice detector that cuts your speech into phrases. ## How it works Omnilingual is not a streaming model: it reads a whole phrase at once. While you talk, Silero listens for the short pause at the end of each phrase (half a second) and hands that phrase to the model while you go on to the next one. **Phrase end detector** on the Voice typing screen can put FireRedVAD, plain loudness or a model of your own in Silero's place; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase). The words therefore arrive a phrase at a time, as underlined composing text in the field and on the line under the microphone, and not word by word as they do with Sherpa. When the dictation ends, the text goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). **When dictation ends.** With **Stop after a pause** set, a longer pause of that length ends the dictation; with **Keep listening** on, the next one starts at once. With the setting off it runs until you press the microphone or two minutes pass. A phrase that runs past 20 seconds without a pause is cut at the next short breath, because the model reads at most 40 seconds at a time. **Plain words.** The model writes words without punctuation or capitals. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a punctuation model for 88 languages puts in full stops, commas, question marks and sentence capitals once it is downloaded from that setting's row on the Voice typing screen. Without it the keyboard capitalises the start of a dictation, and spoken punctuation ("comma", "full stop") works as usual. **Memory and speed.** The model takes about 1 GB of memory while it is open, so it suits a recent phone. The keyboard frees it when Android asks for memory back, and opening it closes Sherpa's model first. Measured on a Mac, it needs about 2.1 billion processor cycles per second of speech on one core; with four threads that comes to an estimated 0.3 seconds of waiting per second of speech on a flagship phone and about 0.5 on a mid-range one. ## Omnilingual, Whisper or the others Use Omnilingual for a language that has no other offline engine here. Where one exists, it is usually the better choice: [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) is more accurate on the languages it was trained hardest on, [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) streams its 13 languages word by word, and [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) covers 25 European ones. On 12 FLEURS test clips per language, Omnilingual's character error rate was 14.3% for Hindi, 19.4% for Tamil, 5.3% for Swahili and 21.0% for Yoruba. Meta's larger MMS-1B model was measured too and was more accurate (6.4% for Hindi), but at 1.9 GB of memory and about two and a half times the work per second of speech it is too heavy for a keyboard, so it is not offered. ## Details & edge cases **No language to choose.** The model has no way to be told the language, so it guesses from the sound. A dictation that listens for several languages at once works the same way, as long as Omnilingual knows all of them. **Kept to your script.** Left to itself the model spells by sound in whichever script it thinks it hears, so Hindi often comes out in Urdu letters and an English phrase now and then in Arabic ones. The keyboard therefore lets it write only the letters of the script of the language you dictate in, plus digits, spaces and punctuation. Where the model reaches for another script's letter, the letter of your script that it rates best for that sound goes in instead: Urdu ب becomes ब, Arabic ل becomes l. A dictation in several languages may use the scripts of all of them, so Hindi with English keeps both Devanagari and Latin. A phrase the model heard as the neighbouring language can still be spelled the way that language writes it. Urdu leaves out most short vowels, so a Hindi word may now and then miss a vowel sign, and English heard as Arabic loses some vowels the same way. For a language whose script the keyboard has no Unicode name for, or a notation such as music or Braille, the model writes as it likes. **Scripts.** It writes each language in the script it was trained on. Languages it only writes in a different script than the keyboard's are left out of its list, so they go to another engine: Crimean Tatar (Cyrillic, not Latin), Manipuri (Bengali, not Meetei Mayek), Tulu (Malayalam, not Kannada) and a few others. **No words to listen for.** The model takes no list of names or terms, so **Words to listen for** does not show while it is the engine. **The licences.** Omnilingual ASR is Meta's under Apache-2.0, converted for sherpa-onnx by k2-fsa. sherpa-onnx is Apache-2.0, ONNX Runtime is Microsoft's under MIT, and Silero VAD is MIT. All of them are credited in the app's About screen. --- # Offline voice (Parakeet) URL: https://wmkeyboard.pages.dev/tools/parakeet/ Section: Tools Summary: Dictation in 25 European languages on your phone, with NVIDIA's Parakeet TDT model telling them apart. Offline Parakeet is a dictation engine that runs entirely on your phone, with NVIDIA's [Parakeet TDT 0.6B v3](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3), one of the most accurate open speech recognition models of its size. It transcribes 25 European languages and works out for itself which one you're speaking, so a layout in any of them can dictate without choosing a model per language. Once the model is downloaded, no audio leaves the phone. The languages: Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish and Ukrainian. While a layout in another language is in use, dictation uses the system recognizer instead, the same way [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/) hands over its other languages. ## Setting it up Voice typing Add **Parakeet** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download a model from the **Parakeet model** group there. Each is large: 719 MB, the 4-bit build made for phones, and suits a recent phone with plenty of memory. On mobile data the download asks first. It comes from Hugging Face at a fixed revision, and both of its files are checked against their published SHA-256 before they count. There are two models, the same size and speed: - **Parakeet Ultra**, [Moondream's](https://huggingface.co/moondream/parakeet-ultra) further-trained copy of v3, with the same 25 languages. On Moondream's own tests it gets about a sixth fewer words wrong across the 25 languages (FLEURS: 9.55% word error rate against v3's 11.62%), and does better in background noise. The keyboard downloads its own [ExecuTorch export](https://huggingface.co/wasi-master/parakeet-ultra-executorch) of it. - **Parakeet TDT 0.6B v3**, NVIDIA's original. With both downloaded, dictation uses Ultra, and v3 can be deleted to free its space. [Engine for each language](https://wmkeyboard.pages.dev/tools/voice/#an-engine-for-each-language) can still give a language v3. ## How it works Parakeet doesn't write its answer a word at a time: an encoder reads a whole clip, and a small transducer then walks through it, writing a word piece where it hears one and skipping ahead where the model's own duration head says nothing more is coming. That is most of why it's quick for its size. A phrase can run to 50 seconds. The first phrase after the keyboard starts takes a few seconds longer while the model's weights are prepared. The words show while you speak, as they do with [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) and [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/): under the microphone, and in the field as underlined text in the default voice typing mode. Parakeet isn't a streaming model, so the keyboard gets them by transcribing the phrase as it stands, again and again while it grows. A new pass starts once there is more than half a second of new audio, and never sooner than three times as long as the last pass took, so a slower phone shows the words less often rather than falling behind. The words can still change as the rest of the sentence arrives. When the phrase ends, one last pass over all of it gives the text that goes in, the same text Parakeet gave before it showed words as you spoke. Each pass covers the whole phrase, so the phone works harder during a long one; a pause that ends the phrase (see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase)) keeps phrases short. The text arrives punctuated and capitalised by the model itself, and then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). ## Ending a phrase Voice typing / Stop after a pause A phrase ends when you stop talking for as long as **Stop after a pause** says, heard by the **Phrase end detector** you picked (see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase)). Both are also in the **End of a phrase** group on Parakeet's own page, under its models. **Stop after a pause** is off by default; while it is off, a phrase only ends when you press the microphone or it reaches 50 seconds. Set it to a second or so and each phrase goes in at the pause, and with **Keep listening** on the next one starts by itself. Parakeet has no detector of its own, so **Auto** gives it Silero once ONNX Runtime and a Silero copy are on the phone, and loudness until then. Silero is the better choice in a noisy room, where loudness can keep a phrase open. Moondream's Parakeet Ultra carries a small voice activity head of its own, which their Photon runtime uses to cut long recordings at pauses. The keyboard's ExecuTorch export of Ultra was made with the four parts the Parakeet runner uses and left that head out, so it is not used yet; the phrase end detector does that job. Using it would take a new export of Ultra with the head as a fifth part, and a new download. ## Details & edge cases **Several languages at once.** A dictation that listens for several languages (see [Language and engine](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine)) is Parakeet's when every one of them is among its 25; it tells them apart on its own. **No words to listen for.** Parakeet has no way to take a list of names or terms, so **Words to listen for** does not show while it is the engine. **Every build.** The runtime, [ExecuTorch](https://github.com/pytorch/executorch) built for the keyboard's models alone, is in every build; F-Droid compiles it from source. A build made without it does not offer the engine. **The licence.** Both models are under CC-BY-4.0: v3 is NVIDIA's, and Ultra is Moondream's, built on it. Both are credited in the app's About screen. --- # Providers URL: https://wmkeyboard.pages.dev/tools/providers/ Section: Tools Summary: How tools that can ask more than one service keep a list of providers you add, order, switch off and set up, with fallback between them. Some tools can get the same thing from more than one service. GIFs can come from KLIPY, GIPHY, Wikimedia Commons, GifCities or packs on your phone. Each of these tools keeps a **Providers** list near the top of its settings page, and every list works the same way, much like the locations of [backups](https://wmkeyboard.pages.dev/privacy/backup/). ## The list The providers that are **on** come first, in the order the tool asks them. Each row shows its place in the order, its logo, its name, one line saying what it is set up with, a chevron, and its switch behind a thin rule. The line comes from the provider's own settings, so it changes as soon as you change them on the provider's screen: | Provider | The line says, for example | |---|---| | A voice engine or the on-device AI | *Model:* and the model it uses, then its size. The engines that pick their own model name the biggest one on the phone and count the rest. While a model downloads, how far it is; with no model, *No model downloaded*. | | The phone's speech recognizer | *Model on the phone first* or *Always the recognizer service* | | An on-device translator | How many languages are on the phone, English not counted | | Your own server, or a front end | *Server:* and its address, then its engine or model | | An AI service | Its model, or *Default model*, and its key | | A service with a key | *Key set*, *Key ending* and the last four characters, *Key in this build*, *No key*, or *No key needed* | | DeepL, Tavily | Their key, then *DeepL Write on*, or Tavily's search depth when it isn't Basic | | Harper | The kind of English it checks | | Offline GIF packs, vocabulary packs, imported or offline dictionaries | How many are on the phone, or whether the dictionary is downloaded | - **Drag the number** at the start of a row (the grip under it) to change the order. Only the providers that are on move, since the tool asks no other. With TalkBack, the row's actions include **Move up** and **Move down**. - **Press a row** to open that provider's own screen. Everything the provider has is there, see below. - **Switch a row off** to skip it without losing its setup. It moves down to the providers that are off. - **Add a provider**, under the list, opens a sheet of every kind this build can use, in the same order on every build. A kind that needs nothing typed in is added at once. The rest open the setup first. The providers that are **off** sit under the list, each folded to its name, a short description and a switch. When they run in more than one kind of place they are sorted under **On the phone**, **Online services** and **Your own servers**. Press one to set it up before you switch it on. **List settings**, a fold at the end, holds how the tool uses its list (see below), **Two of the same provider** when a kind is on the list twice, and **Reset to default** once you have changed the list. A row with a problem says what it needs in red: an API key, a server address, a model name, an imported GIF pack, or a model to download. Such a row is skipped until it is fixed. ## A provider's own screen Pressing a row opens a screen for that one provider, with its description and tags at the top: - **Use this provider**: the same switch as on the row. - **Setup**: its key, its server address, a model, an engine, a nickname, or a sign-in. The line under it says what is set now, and a press opens the setup sheet with its **Test** button. A provider with nothing to set up has no such row. - The provider's **own settings**, where it has any: a voice engine's models, the on-device AI model, the translation models, the offline GIF packs, the offline dictionaries, DeepL Write, Tavily's search depth. On the F-Droid build the provider's server address is here too. - **Usage**, for the services that charge: Brave Search, Tavily and the AI services. See [Usage and credit](#usage-and-credit). - **Shared with**: the other tools that list the same provider and use its setup. - **Remove** takes the provider out of this tool's list. If no other tool uses its key or server, the key and address are deleted too, and the dialog says so first. A problem shows as a banner at the top with a **Set up** button. A search result or a link can open a provider's screen before it is on the list; the banner then offers to **Add** it. ## Usage and credit The screen of Brave Search, Tavily, or any AI service except the model on your phone has a **Usage** group. It keeps two kinds of number apart, because only one of them comes from the service: - **This month on this phone** is what the keyboard counted itself: the requests it sent, Tavily's credits (one per search, two at **Advanced** depth), and for an AI service the tokens it read and wrote, as the service reported them for each answer. Anthropic, Gemini, Ollama, OpenAI, xAI and DeepSeek report tokens; another OpenAI-compatible service may not. The count starts again on the first of each month. **Start the count again** clears it sooner and doesn't touch your account. - **What the service reports** is the service's own number, and when the keyboard last saw it: - **Brave Search** sends the month's remaining searches with every answer, so this keeps itself up to date. With the key built into the app, it's the shared key's quota, not yours, and the row says so. - **Tavily**, **DeepSeek** and **OpenRouter** (as an OpenAI-compatible service at `openrouter.ai`) answer **Check what is left**: Tavily with your plan's credits left, DeepSeek and OpenRouter with your balance. The keyboard asks only when you press it. - **Anthropic, OpenAI, xAI and Groq** report a per-minute token allowance with each answer, shown as such. None of them tells an app your account's balance. - The others report nothing, and the phone's own count is all there is. The web search panel shows what's left beside the service's name, as in "via Tavily · 812 left", once Tavily or your own Brave key has reported it. To keep one request from running long, set **Max response length** in the [AI tool's options](https://wmkeyboard.pages.dev/tools/ai/#options); **Stop** on the panel or in the chat ends an answer and its billing at once. ## Finding a provider in search Every provider is a result in the settings search under its own name: search for *DeepL*, *Whisper* or *Ollama* and the result opens that provider's screen, with the path of the list it is on (*Tools › Translate*) under it. A provider that can join several lists, such as Wiktionary, has a result for each. A second server or key, and any provider you gave a nickname, is a result of its own, found by that nickname and showing its server or key under it. Providers this phone cannot run are left out, unless one of your lists has them. Rows show the service's own logo where the app carries one. The logos are small vector drawings from [Simple Icons](https://simpleicons.org/) or from each project's own repository, and are in the app itself, so drawing them never reaches the network. A one-colour logo is lightened on a dark theme when the brand's colour is close to black, and a logo whose colours would vanish on one theme (KLIPY, Lingva, Tavily) sits on a small plate of its own. ## Shared keys and servers A provider is set up once, and every tool that lists it uses the same setup. A KLIPY key added on the GIF page also works for stickers. The provider's screen says which other tools use it, under **Shared with**. Edit it on either page and both change. Server kinds can be added more than once, each with its own address and nickname: two Wikimedia wikis, say. When you add one on another tool's page, the add sheet offers the servers you already set up, under **Use**, so you don't type the address twice. ## Two of the same provider A service you set up with a key can also be added a second time, with another key or another account: two KLIPY keys, two DeepL accounts, a Brave key of your own beside the one in the build. Tap the kind again in **Add a provider**. The second one always needs its own key, since the key in the build already belongs to the first. Give each a **Nickname** so the list and the keyboard can tell them apart; a row without one shows the last four characters of its key. This works on the GIF, sticker, translate, web and image search, online text scanning, photo translation, AI and sticker generator lists. Background photos, currency rates and the word sources ask a service by its name, so they keep one of each. When a list asks **One at a time**, the second one is simply the next provider down. On the GIF and sticker lists in **Tabs** or **Mixed**, and on any list set to **Choose in the panel**, a setting called **Two of the same provider** appears in **List settings** as soon as one kind is on the list twice: - **As backups** (the default for a second key): they act as one provider, with one chip and one entry in the panel. The first is asked, and the next only when it fails, for example when a key runs out of quota. - **Separately**: each one is its own provider, with its own chip, its own entry in the panel, and its own share of a mixed grid. Two servers of one kind, like two Wikimedia wikis, start out this way. ## How a tool uses its list Most tools ask **One at a time**: the first provider that is on, and the next one only if the first fails or has nothing. The GIF and sticker panels can also show their providers as **Tabs** (a chip for each) or **Mixed** (all of them at once, in one grid). The smart home list is always mixed: every home on it is connected, and the order is the order their rooms appear in. Packs on your phone always get a chip of their own, and GIPHY keeps its own chip in Mixed mode, because its terms do not allow mixing its results with another service's. The AI list is always **Choose in the panel**: one service answers at a time, the one picked under **In use** on the AI page or with the AI panel's model picker, and that pick is kept. The voice typing list is always one at a time: dictation uses the first engine that is on and can run, and a language can be given another engine of its own. Translate, the dictionary, web and image search, online text scanning and photo translation can instead **Choose in the panel**. The panel then shows a chip with the provider in use, and tapping it lists the providers that are on. Only the one you pick is asked, with no fallback to the next. Picking another one runs the same request again with it. The choice lasts until the keyboard restarts, and then the first provider in the list is the one in use again. With only one provider on, there is nothing to choose and the chip stays hidden. The AI's own tools always use the list in order, whatever the mode. A provider that can't be reached (no answer, a server error, too many requests) is skipped for a minute, so the next one answers at once instead of after a timeout. If every provider is in that state, they are all asked anyway. Editing a provider clears this, so a fixed key is tried straight away. ## What each build offers Every build offers every kind it can run, your own servers included, in the same order and with none put first. The tags under each kind (free software, on the phone, your own server, needs your key) say what it is. What changes between builds is the list a fresh install starts with. | Build | Starts with | |---|---| | Google Play and GitHub | The services this build has keys for | | F-Droid | Services that need no proprietary key | | No internet | Only what runs on the phone; nothing else can be added | Updating the app does not change your lists. Until you edit a list, it is worked out from your earlier settings, so the same providers are asked in the same order with the same keys. When an update adds a word source that needs no setup (a new dictionary, say), it joins the end of the dictionary, synonym or vocabulary list, switched on. One you removed stays removed. ## Tools with a provider list | Tool | Kinds | Settings | |---|---|---| | [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) | KLIPY, GIPHY, Wikimedia Commons or another MediaWiki, GifCities, offline GIF packs | Tools / GIFs / Providers | | [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) | KLIPY, GIPHY, your sticker packs | Tools / Stickers / Providers | | [Translate](https://wmkeyboard.pages.dev/tools/translate/#providers) | On device (ML Kit not on F-Droid), Google Translate, DeepL, LibreTranslate, Mozhi, SimplyTranslate, Lingva, Translite, your own translation server | Tools / Translate / Providers | | [Dictionary](https://wmkeyboard.pages.dev/tools/search/#dictionary-sources) | Vocabulary packs, Wiktionary, Wiktionary definitions, Free Dictionary API, the offline dictionaries, imported dictionaries, Urban Dictionary | Tools / Dictionary / Dictionary sources | | [Synonyms](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip) | Vocabulary packs, Datamuse, Wiktionary, Free Dictionary API, Similar words | Typing / Suggestions / Synonym sources | | [Web and image search](https://wmkeyboard.pages.dev/tools/search/) | SearXNG, Tavily, Brave Search | Tools / Web search / Providers | | [Background photos](https://wmkeyboard.pages.dev/themes/online-photos/) | Unsplash, Pexels, Wikimedia Commons or another MediaWiki | Themes / Photo services | | [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) | ExchangeRate-API, Frankfurter, AllRatesToday, Coinbase, currency-api; for coins Coinbase, currency-api, CoinGecko | Tools / Currency converter / Currency rates from | | [Text scan, online](https://wmkeyboard.pages.dev/tools/scanner/#reading-text-online) | Your vision model, Rens | Tools / Text scan (OCR) / Online text reader | | [Camera, Translate](https://wmkeyboard.pages.dev/tools/camera/#translating-a-photo) | Rens | Tools / Camera / Translate photos | | [Vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) | Wiktionary, Wiktionary definitions, Free Dictionary API, for words no pack knows | Tools / Vocabulary / Providers | | [AI](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) | Anthropic (Claude), OpenAI, Google Gemini, xAI (Grok), DeepSeek, Brave Answers, Ollama, LM Studio, an OpenAI-compatible service, On your device (not on F-Droid) | Tools / AI writing tools / AI providers | | [Sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/#engines) | On this phone, Pollinations, AI Horde, OpenAI Images, Gemini Images, Grok Imagine, Stability AI, Black Forest Labs (FLUX), Recraft, Replicate, fal.ai, Together AI, Hugging Face, Cloudflare Workers AI, Automatic1111 / Forge / SD.Next, ComfyUI, an OpenAI-compatible image service; Pollinations and AI Horde start switched off | Tools / Stickers / Sticker packs / Create stickers / Engines | | [Internet speed test](https://wmkeyboard.pages.dev/tools/speed-test/#servers) | Cloudflare, LibreSpeed servers (your own, or picked from the public list) | Tools / Internet speed test / Servers | | [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/#checkers-harper-and-languagetool) | Harper (on the phone, English), LanguageTool (the public server, a Premium account, or your own server); LanguageTool starts switched off | Tools / Grammar check / Checkers | | [Voice typing](https://wmkeyboard.pages.dev/tools/voice/#options) | Phone's recognizer, Whistle, Whisper, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual, Vosk, transcription servers | Voice typing / Recognition engine | | [Smart home](https://wmkeyboard.pages.dev/tools/home-assistant/) | Home Assistant, Google Home, SmartThings, a Philips Hue bridge, openHAB, Smart Life (Tuya), SwitchBot; every home on the list is connected at once, and its sign-in or pairing lives in the row's setup | Tools / Smart home / Homes | | [Music ID](https://wmkeyboard.pages.dev/tools/music-id/#recognisers) | AudD, ACRCloud, AudioTag, Shazam via an unofficial RapidAPI listing, your own recognition server; every kind starts switched off until it has your key | Tools / Music ID / Song recognisers | --- # Read back and offline voices URL: https://wmkeyboard.pages.dev/tools/read-back/ Section: Tools Summary: Hear what you typed, read aloud in the keyboard's language, with Kokoro's natural voices for English and Mandarin and Meta's MMS voices in more than a thousand languages, offline. **Read back** reads your text aloud. With something selected it reads the selection; with nothing selected it reads everything in the field, up to about 4,000 characters. It reads in the keyboard's current language, so switching to Bangla and tapping it reads Bangla. Tap it again while it reads to stop. The tool stays lit while it is reading. It never reads a password field. Add it from the toolbox like any other tool. It sits in the **Create and convert** group on the Tools screen, and its page there is Tools / Read back. ## Kokoro voices Tools / Read back / Kokoro offers [Kokoro](https://huggingface.co/hexgrad/Kokoro-82M), a small open speech model (Apache-2.0) that reads English and Mandarin in voices close to a person's. It is one download of 132 MB, about 160 MB once unpacked, that holds every voice: 20 American English, 8 British English and 8 Mandarin. With it downloaded, it reads those languages ahead of the MMS voices below and the phone's own voice; other languages carry on as before. Once it is here, the group shows a row of voices for each language. Tap a name to pick it; each is marked female or male. British English reads keyboards set to the UK, Ireland, Australia, New Zealand, South Africa or India, and American English the rest. The download is sherpa-onnx's packaging of Kokoro v1.0, checked against its SHA-256 and resumed if it stops. It runs on the same ONNX Runtime as the MMS voices, so the GitHub and Play builds fetch that first if it is not on the phone yet. Swipe or tap the bin to delete it; it also shows under Storage / Voice models. Kokoro reads from pronunciation dictionaries, and a word missing from them, such as an unusual name, is skipped. Before it reads English, the keyboard spells out numbers ("$12.50" becomes "twelve dollars fifty cents", "3rd" becomes "third") and splits words run together in camel case. Making a sentence takes about as long as saying it on a recent phone, and longer on an older one. The model holds a few hundred megabytes of memory while it reads, and the keyboard unloads it with its other models when it has been hidden for 2 minutes. ## Offline voices The phone's own text-to-speech voice reads by default. Many phones have no voice for smaller languages at all, and that is what the offline voices are for: Meta's [MMS-TTS](https://huggingface.co/facebook/mms-tts) voices, one per language, for more than 1,100 languages. A downloaded voice reads its language on the phone, with no connection. Tools / Read back / Offline voices lists a voice for each of your keyboard languages that has one, with a Download button. Each voice is about 114 MB. Languages with no MMS voice are named under the list; the phone's voice keeps reading those. 1. The first download asks you to accept the voices' licence, Creative Commons Attribution-NonCommercial 4.0: free to use, not for commercial purposes. The voices come from Hugging Face under that licence, not from the keyboard, so you agree to it once, before anything downloads. 2. On the GitHub and Play builds a banner offers ONNX Runtime first, the engine the voices run on, a one-time download of about 33 MB that [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) uses too. The offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first. 3. Download the voices you want. Each file comes from a fixed revision of the ONNX conversion of Meta's models and is checked against its SHA-256 before it is used. A download that stops resumes where it left off. Swipe a downloaded voice's row, or tap its bin, to delete it. The voices also show under Storage / Voice models. ### Where the voices read Once a language's voice is downloaded, it reads that language everywhere the keyboard speaks: - **Read back**. - The speaker button on [vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) cards, when there is no recording to play. - The speaker button on the [Look up](https://wmkeyboard.pages.dev/tools/look-up/) card, in the dictionary's language. - **Speak** in [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/), for Bengali and Devanagari text and for text in the phone's own language. A recording, when a card has one, still plays first. The speech rate is the one under Tools / Vocabulary; MMS voices have no pitch setting, so the pitch slider only changes the phone's voice. ### What to expect MMS voices read letters, not phonemes, and they were trained on Bible readings, so they sound clear but plain, a little like someone reading a passage aloud. They read words as written: digits and symbols the voice has no letter for are skipped, so in most languages "3 kg" comes out as "kg". Each sentence is made while the one before it plays, so a long text starts once the first sentence is ready. Making speech is heavy work: a second of speech takes about a second of one fast processor core, so on a phone expect a pause of a second or so before the first sentence, and on a slower phone short gaps between sentences. Voices that need Meta's romaniser, which spells a language in Latin letters before it is read, are not offered; for those languages the phone's voice reads. ## Privacy Reading happens on the phone. A downloaded voice sends nothing anywhere. The phone's own voice is whatever text-to-speech engine you picked in Android's settings, and some of those engines use the network. Read back is the **Read back** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on; downloaded voices stay. --- # Resize the keyboard URL: https://wmkeyboard.pages.dev/tools/resize/ Section: Tools Summary: Drag the keyboard's height, position and side margins in place, over the keys, instead of hunting for sliders in Settings. **Resize** is the tool for the moment you think "this keyboard is a bit too short," without wanting to go digging through Settings for a slider. Tap it and the keyboard dims, an outline appears around it with a handle on each edge, and you drag until it looks right. Nothing is saved until you press **Done**. tools/resize-overlay — Resize mode: the keyboard dimmed behind its outline, with drag handles on all four edges and the Reset / move / Done row in the middle. ## Turning it on Tools / Resize Resize is one of the 72 toolbar tools, filed under **Keyboard modes** on the Tools screen next to Modes, One-handed, Split and Floating. Its row is just a switch: everything it changes already has a slider on the Layout & size screen, and the tool is only the in-place way to drag them. You don't need the tool switched on to use resize mode once: **Resize by dragging** at the top of Layout & size / Size and position opens the test field with the keyboard already in resize mode. Once it's on, it waits in the toolbox like any other tool, and you can drag it up onto the toolbar if you resize often enough to want it one tap away. See [The toolbar](https://wmkeyboard.pages.dev/tools/overview/) for how pinning works. ## Dragging Five things move: - **The top handle** scales the keys. Both the key height and the number row height change together, keeping their ratio, so a taller keyboard doesn't leave the number row looking stranded. The bottom edge stays put. - **The bottom handle** moves the bottom edge and leaves the top edge where it is. Drag it down and the keys grow into the gap below the keyboard; drag it up and the gap grows while the keys shrink. It stops when the gap runs out either way, so it can never lift the top edge. - **The left and right handles** set the margin on that side of the keyboard, the same **Left padding** and **Right padding** values the Layout screen has as sliders. Each side is independent, so a right-handed thumb can shave the left edge alone. - **The round move button** in the middle slides the whole keyboard up and down without changing its height, so you can lift it off the navigation bar with a thumb in the middle of the board rather than reaching for its edge. While the tool is open the keyboard area on screen is held at the tallest the keyboard could reach, or the tallest window your phone will actually give the keyboard, so nothing moves under your finger mid-drag. Where that window is short, the held space shrinks to fit it rather than pushing the keys off the bottom of the screen. The app above doesn't move at all: it stays laid out where the keyboard's top edge was when you entered, dimmed behind that held space, and a keyboard you drag taller grows over it. The app catches up once, when you press Done. Either side of the move button: - **Reset** puts everything back to the values the tool opened with. It doesn't reset to the app's defaults, just to where you started this session, so a drag you regret costs you nothing. - **Done** saves and leaves. Below those, once the keys are narrower than the screen, sits a **Full-width toolbar** chip. Tap it and the toolbar and suggestion strip stretch back across the whole screen while the keys keep the width the side handles gave them, which is the tablet arrangement Samsung Keyboard uses. Tap it again to narrow them with the keys. Like the drags, it only previews until **Done**, and it saves to the same **Full-width toolbar** switch as the Layout screen, for the screen shape you're in. Everything behind the outline is covered by a scrim that swallows every touch, so you cannot type by accident while you're dragging. Push a handle as far as it goes and it turns red and buzzes once, which is how you know you've hit the limit rather than lost the drag. The limits are the same ones the sliders in Settings use: key height and number row height run from **32dp to 100dp**, the gap under the keyboard runs from **0dp to 160dp**, and each side margin runs from **0% to 30%** of the screen width. ## What gets saved, and where Done writes to whichever screen shape you're currently in. In portrait, that's the plain **Key height**, **Bottom padding**, **Left padding** and **Right padding** values under Layout & size / Size and position, plus the **Number row height** slider that sits one group higher under Layout & size / Number row. In landscape, or unfolded, it writes that shape's own override instead, exactly as if you'd opened the per-screen group in Settings and moved the sliders there yourself. Resizing in landscape therefore never disturbs your portrait sizing. See [Size, position & one-handed use](https://wmkeyboard.pages.dev/typing/size-position/) for how the per-screen overrides work. Only the values you actually moved get written, and the **Full-width toolbar** chip counts as one of them only if you left it changed. Nudge the keyboard up without touching the top handle and no key height is saved at all, so a tablet still keeps its larger shipped default rather than being frozen at whatever number the drag happened to start from. ## Details & edge cases - **Tapping the tool again cancels.** A second tap while resize mode is open closes it without saving, which is also what happens if you reach it twice from a hardware shortcut. - **Done is the only thing that saves.** Anything else that takes the keyboard down drops the session unchanged: switching apps, hiding the keyboard, or the system **Back** gesture, which just dismisses the keyboard here. So does rotating, folding, unfolding, or moving to another display, since that swaps the screen shape out from under a preview that belongs to the old one. - **It only works docked.** With the [floating keyboard](https://wmkeyboard.pages.dev/typing/size-position/) on, the tool refuses and says "Dock the keyboard to resize it": floating has its own drag grip on the frame, and these handles are laid out against the docked shape. - **One-handed mode switches off when you enter.** So does any open panel, which closes through the normal path rather than just vanishing, so nothing is left behind it. - **It does nothing while the voice bar is up.** Finish or dismiss the dictation strip first. - **It works before you unlock.** Resize is one of the 45 tools available during [direct boot](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone), since it only touches the keyboard's own geometry. - **A theme that pins its own key height wins afterwards.** Some themes ship a key height as part of their look. The drag previews fine and the value is saved, but the theme's number is what gets drawn once you're out of resize mode. A per-screen override beats the theme, so the same drag does stick in landscape or unfolded. - **No hardware shortcut letter by default.** About two dozen tools have a default leader-key letter and Resize isn't one of them, but you can bind one under Typing / Physical keyboard. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/). - **The pill toolbox draws no arrow on it.** Resize doesn't open a panel or an activity, so it reads as an action rather than a destination, alongside Undo and Flashlight. ## Related pages - [Size, position & one-handed use](https://wmkeyboard.pages.dev/typing/size-position/): every sizing slider the tool drags, plus per-screen overrides, split and floating. - [The toolbar](https://wmkeyboard.pages.dev/tools/overview/): pinning, the toolbox, and direct boot. - [Tools](https://wmkeyboard.pages.dev/reference/settings/tools/): the master list of all 77 tools and what each row on the Tools screen does. --- # Scanner (OCR, QR, documents) URL: https://wmkeyboard.pages.dev/tools/scanner/ Section: Tools Summary: Point the camera at text, codes, or paper and get the result in your field. Three camera-driven tools live under one Scanners group. **Text scan (OCR)** reads printed words into your field. **QR and barcode scanner** decodes a code and inserts or opens its value. **Document scanner** captures a paper document as a cropped, cleaned-up image. All three are in every build. On F-Droid's, text scan reads with PaddleOCR alone, the QR scanner with zxing-cpp, and the document scanner is the built-in one. The QR scanner reads with Google ML Kit or zxing-cpp; text scan reads with ML Kit, PaddleOCR or Tesseract, depending on the language. The document scanner uses Google's scanner where Google Play services can run it and the keyboard's own everywhere else. The three scanners are also modes of the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/#scanner-modes). A rail down the right edge of the camera, the QR scanner and Text scan switches between Photo, QR code, Text and Document, so a single Camera button on the toolbar reaches every one of them. Each scanner's own tool opens the camera straight in its mode. ## Scanning text (OCR) Open **Text scan (OCR)** from the toolbar or toolbox to replace the keyboard with a full-bleed camera view. It covers the toolbar row too, not only the key area. Point it at printed text and it recognizes words on-device. A torch toggle appears if your device has a flash. ### Which languages it reads Three engines read the text on your device, and a fourth, which you set up yourself, reads it online: - **ML Kit** reads Latin-script text: English, French, Spanish and the like. It needs no download. On a phone with Google Play services it also reads Chinese (simplified and traditional), Japanese, Korean and Devanagari (Hindi, Marathi, Nepali and the like), and those recognizers read Latin words mixed into the text as well. Google Play services downloads each of them the first time you scan in that script; until it arrives, the panel asks you to try again in a minute. - **PaddleOCR** reads Chinese (simplified and traditional) and Japanese, including vertical columns, with the English words mixed into them. It uses the PP-OCRv5 mobile models from Baidu's PaddleOCR project, which read these scripts far better than Tesseract does. Both languages share one download of 11 MB. It has no setting of its own: **Automatic** uses it. On the F-Droid build, which has neither ML Kit nor Tesseract, it reads Latin-script text too (English, French, Spanish and the like), and its download there is 21 MB; see [On the F-Droid build](#on-the-f-droid-build). - **Tesseract** reads more than 100 languages in most scripts, for example Bangla, Hindi, Arabic, Russian, Greek, Thai, Chinese, Japanese and Korean. Each language needs a one-time download of its data, from 0.4 MB to 12 MB. - **Online model** sends the photo to an online reader and shows the text it sends back. It reads any script, mixed scripts and handwriting, with no language data to download. The reader is one you add: a model that can read images, through any service with an OpenAI-compatible API (OpenAI, OpenRouter, Gemini, Mistral, or Ollama or LM Studio on your own computer), or a [Rens](https://git.maid.zone/stuff/rens) server, a free front end to Google Lens. See [Reading text online](#reading-text-online). With **Text recognition** on **Automatic** (the default), every script ML Kit reads goes to ML Kit, Chinese and Japanese go to PaddleOCR where ML Kit can't read them, and every other script reads with Tesseract. So on a phone with Google Play services, Chinese, Japanese, Korean and Devanagari text reads with ML Kit. On a phone without it, Chinese and Japanese read with PaddleOCR and Korean and Devanagari with Tesseract. If you downloaded Tesseract's Chinese or Japanese data before PaddleOCR came, the scanner keeps reading with it until PaddleOCR's download is on your device too. The scanner reads in the language you're typing in when you open it. If you've turned on more than one language, a chip at the bottom left of the viewfinder names the text's language, and tapping it steps to the next one without switching your keyboard. All your languages that read the same way share one stop on the chip: on Automatic, every Latin-script language is one stop. On the F-Droid build, **Text recognition** offers only **Automatic** and **Online model**. Automatic reads Chinese, Japanese and every Latin-script language with PaddleOCR, so they all share one stop on the chip. A language in any other script, Bangla or Russian for example, shows a message in place of the viewfinder saying that nothing on the phone reads it, and reads only with Online model. A language without Tesseract data of its own reads with the main one for its script: Maithili and other Devanagari languages read with Hindi's, and a Latin-script language with no data of its own reads with English's. The first time you scan in a language that needs Tesseract or PaddleOCR data, the viewfinder is replaced by a **Download** button that says how big the data is. The download shows its progress in the panel and can be cancelled there. With [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) set to ask, the first press explains that it uses mobile data and a second press starts it. Set to block, the button says why it can't. Tesseract's data comes from the `tessdata_fast` 4.1.0 set of the Tesseract project on GitHub. PaddleOCR's is three files, a text detector, a line recognizer and its list of characters, converted to run on the same LiteRT engine as [offline Whisper voice typing](https://wmkeyboard.pages.dev/tools/whisper/) and published on GitHub by the PPOCRv5-Android project; the download is pinned to one version and each file is checked against its checksum. The F-Droid build fetches only the list of characters from there. Its detector and recognizer are PaddlePaddle's own models exported for MNN and published in WM Keyboard's data repository, checked the same way; on a set of test pages they read exactly the text the LiteRT ones do. Both stay in the app's private storage. You can also download or delete each language on the tool's settings page, and delete it under [Storage](https://wmkeyboard.pages.dev/privacy/data/). On a phone that cannot download, **Get the files** on that page links each language's `.traineddata` file, or PaddleOCR's three files, and **Import** installs ones fetched elsewhere ([Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/)). After a capture, the recognized words appear as tappable chips grouped by line: - With **Start with everything selected** on (the default), every word starts selected and tapping a chip deselects it, so you can trim the capture down to what you need. Turn it off and chips start empty, so you tap to pick words one at a time instead. - **Copy** and **Insert** act on whatever's currently selected. - **Translate** opens the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool on the selected words, translated straight away into its language. Pick only the line you need first, and only that line is translated. - **Scan again** discards the capture and returns to the viewfinder. A select-all/deselect-all toggle flips every chip at once. PaddleOCR finds each line of text first and then reads the lines one by one, so a line that's tilted, or a column of vertical Japanese, comes out in reading order: rows top to bottom, then columns right to left. Each line becomes one chip, since Chinese and Japanese put no spaces between words. Tesseract reads each photo twice, once with a threshold set from each small area of the photo and once with one threshold for the whole photo, and keeps whichever read found more text. The first copes with uneven light: a shadow across the page, the dark corners of a phone photo, or the torch's bright spot. The second copes with light text on a dark screen. "No text found" means both reads came back empty. If recognition itself fails, the panel says **Could not read** instead. If a downloaded language won't load, with Tesseract or PaddleOCR, the scanner deletes that language's data and offers the download again. If your device has no camera, the panel shows a message instead of a blank viewfinder. To read a photo that's already on your phone, such as a screenshot, tap the picture button in the top-right corner of the camera view. The keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) opens, or Android's photo picker in the Play Store build, and the photo you choose is read in the language on the chip, then lands on the same chips as a capture. It is shown whole rather than cropped to the viewfinder. A picture you copied can be read the same way, with no camera at all: press and hold it in the [clipboard panel](https://wmkeyboard.pages.dev/tools/clipboard/) and tap **Extract text**. The scanner opens on the picture and reads it at once, and its back button returns to the clipboard. **Scan again** shows the picture with the language chip and the shutter, so you can read it again in another language. The button is only there while Text scan is on in your tools. ## Scanning QR codes and barcodes **QR and barcode scanner** is also full-bleed and decodes continuously as you point the camera, with no shutter tap needed. It reads QR codes plus the common barcode formats: Aztec, Codabar, Code 39/93/128, Data Matrix, EAN-8/13, ITF, PDF417, and UPC-A/E, labeled by format in the result. The first code it decodes freezes into a result card with **Scan again**, **Copy**, **Share** and **Insert**. If the decoded value is a plain URL (`https://…`, `http://…`, or `www.…`), an **Open** button appears above that row. Anything else is text only, so a scanned value like a Wi-Fi network payload inserts as raw text rather than turning into a connect action. Pinch to zoom while scanning, and a small pill reads out the zoom ("1.2x" style) above roughly 1.05x. One particular link gets a little extra recognition on the result card, over on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#scan-the-inevitable-qr-code). To read a code that's already on your phone, such as a screenshot of a QR code, tap the picture button in the bottom-left corner of the camera view. The keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) opens, or Android's photo picker in the Play Store build, and the photo you choose is read the same way, on-device, landing on the same result card. When the picture holds no code, a short message says so and the camera view stays up. ### Which reader decodes codes Two readers can decode what the camera sees, and the result card is the same with either. - **ML Kit** is Google's barcode reader. It is the default wherever it can run: the GitHub builds, which carry its model, and the Google Play copy on a phone with Google Play services, which supplies the model. - **zxing-cpp** is an open-source reader (Apache-2.0). The F-Droid build reads with it, since ML Kit is a Google library, and so does the Google Play copy on a phone without Google Play services. Anywhere else, turn on **Read codes with zxing-cpp** in the options below to use it instead of ML Kit. Besides the formats above it reads Micro QR, rMQR, MicroPDF417, MaxiCode, GS1 DataBar, Telepen and the Code 39 variants, which the card labels "Barcode" when it has no name for them. It also tries light codes on a dark background, such as a QR code on a phone in dark mode. zxing-cpp is a separate download of about 1 MB, fetched the first time the scanner opens with it: from Google Play on the Play build, and from GitHub elsewhere, which on F-Droid's build waits until you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). Until it's on your phone, the scanner shows its size and a **Download** button in place of the camera. A UPC code reads as its own 12 or 8 digits with either reader. ## Options All three are one bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), **Scanners**. Off there, the three tools and the Scanners group of the Tools list are gone until it is switched back on. All three scanners have their own settings screen, and all three start with the same header rows before anything tool-specific: - **Turn on**: Text scan is in the "recommended" and "show me everything" starter sets that setup lands, so it arrives on unless you picked the simplest answer. Neither QR and barcode scanner nor Document scanner is in any starter set, so setup leaves both switched off unless you pressed **Everything** on its tools page. Turn them on here. - **Icon colour**: only shown while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) is on globally, which it is by default. Text scan is green, QR and barcode teal, Document scanner a light blue. - **Trigger words**: under a **Keyword shortcut** heading. It's `ocr` for Text scan, `scan` for QR and barcode, and `docscan` for Document scanner. Type one of them on its own and the suggestion strip offers to open that tool. Commas separate several words, and an empty field means the tool never offers itself. - **Match capitals**: off by default on all three, so a trigger word matches however you capitalise it. Turn it on and only the exact spelling counts. - **Reset to defaults**: joins the group once you've edited that tool's trigger words, and puts them back. Tools / QR and barcode scanner / Options - **Read codes with zxing-cpp**: off by default, and shown only where ML Kit's reader can run. Decodes with the open-source reader instead; see [which reader decodes codes](#which-reader-decodes-codes). The caption above the rows names the reader in use. - **Insert automatically**: off by default. With it on, the scanned code's text types into your field the instant it's spotted, with no confirm tap. - **Vibrate on detection**: on by default. A short buzz when a code is spotted. - **Load link details**: off by default. When a scanned value is a web link, it fetches the page's title and description to show above the result. That needs a network connection. [Power Saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) force-disables this row while its background-network toggle is on. Recognition for both OCR and the QR/barcode scanner runs entirely on-device, so no photo is uploaded and both work offline once any Tesseract or PaddleOCR data they need is downloaded. The one exception is **Text recognition** set to **Online model**, which sends each photo you scan to the service you named. Link previews are the one exception, since fetching a page's title needs the network. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else does and doesn't touch the network. Tools / Text scan (OCR) / Options - **Start with everything selected**: on by default, described above. - **Text recognition**: **Automatic** (the default), **ML Kit**, **Tesseract** or **Online model**. See [which languages it reads](#which-languages-it-reads). The online model ignores the text's language, so the viewfinder shows no language chip with it. With ML Kit the chip picks its recognizer, and a language in a script ML Kit has no recognizer for reads with the Latin one. Tesseract reads Latin script too, usually slower than ML Kit. ### Reading text online With **Text recognition** on **Online model**, an **Online text reader** group appears under it. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): add readers with **Add a provider**, drag them into order, and switch them off. The first reader that reads the photo is used, and the next one is asked only if it fails. With **How to use them** set to **Choose in the panel**, a chip at the bottom right of the viewfinder names the reader, and tapping it picks another. Two kinds of reader can be added, each more than once: - **Your vision model**, a model that reads images through an OpenAI-compatible API: - **Server address**: such as `https://api.openai.com/v1`, `https://openrouter.ai/api/v1`, or `http://192.168.0.10:11434/v1` for Ollama on your computer. The full address ending in `/chat/completions` works too. - **Model**: required. The name of a model that reads images, as the service spells it, for example `gpt-4o-mini`. - **API key**: the service's key. Leave it blank for a server of your own that asks for none. It's kept with your other API keys, and leaves the phone in a backup only with **Include API keys** on. - **Rens**, the address of a Rens server, your own or [a public one](https://git.maid.zone/stuff/rens#instances). Rens passes the photo to Google Lens and sends back the text Lens found. It strips the photo's metadata and gives it a random name first, and Google sees the server, not your phone. **Test** checks that the address is a Rens server without sending a photo. A reader that was set up before the list existed is in it already, as **Your vision model**. Each scan sends the photo, scaled down to 1600 pixels on its longer side. A vision model gets a request to write out exactly the text in it and nothing else; Rens gets the photo the way its own web page sends it. The text comes back as the same word chips the other engines give. A paid service may charge for each scan. With no reader on, the panel says so after the shutter, and an error from the reader, such as a wrong key or a model that can't read images, shows as a message rather than as "No text found". Every scan shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Scan text, online**. Tools / Text scan (OCR) / Language data One row for each download the languages you type in need, with its size, a **Download** button, its progress, or a delete button once it's on your device. Two languages that read with the same data share one row, for example Norwegian Bokmål and Nynorsk, or Chinese and Japanese, which on **Automatic** share PaddleOCR's row (named "with PaddleOCR") wherever ML Kit can't read them. On **Tesseract** the list shows Tesseract's own Chinese and Japanese data instead. The list is hidden while **Text recognition** is on ML Kit or Online model. On the F-Droid build it has one row, PaddleOCR's, for every language PaddleOCR reads. ## Scanning a document **Document scanner** has two scanners behind it, and both work the same way from the keyboard's side: the scan runs full screen, outside the keyboard (an IME can't host that kind of flow itself), and the pages come back as JPEGs. They go into the field you scanned for, one after another. Some chat apps take only the first: WhatsApp opens its own send screen on it. Whatever pages the field didn't take then open together in that app's share screen, so WhatsApp sends them all as one album, and a toast says how many went that way. - **Google's scanner** is the default on a phone with Google Play services. Edge detection, cropping and cleanup happen in Google's own UI, and gallery import is allowed alongside the camera. - **The built-in scanner** is the keyboard's own, built on [OpenCV](https://opencv.org/). It is what the F-Droid build and phones without Google Play services use, and on any other phone you can pick it with **Use the built-in scanner**. If Google's scanner fails to start, the built-in one opens in its place. The built-in scanner goes through three steps: 1. **Capture.** The camera outlines the page it sees as you aim. Press the shutter, or the photo button to pick a picture you already have. The light button turns on the flash as a torch. 2. **Corners.** The scanner places the four corners on the page it found. Drag any that missed; a magnifier shows the spot under your finger while you drag. When it finds no page, the corners start just inside the edges of the picture. The button beside the tick swaps between the corners it found and the whole picture. A red outline means the corners cross over each other, and the tick waits until they don't. 3. **Page.** The flattened page, with four looks: **Original**, **Colour** (the default: paper turned white and shadows lifted, ink and coloured areas kept), **Grey**, and **Black and white**. The turn button rotates it a quarter turn at a time. **Add page** keeps it and goes back to the camera for the next one; **Done** keeps it and sends every page. Back steps back through those three. Leaving the camera with pages kept asks whether to send or discard them. The camera's permission is asked for on the scanner's own screen; without it, the photo button still works. The built-in scanner needs its library, about 2.5 MB. Builds that don't carry it fetch it the first time you scan (or from the banner on the tool's settings page), the same way as the keyboard's other feature libraries: from Google Play on the Play build, and from GitHub elsewhere, which on F-Droid's build waits until you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). Everything runs on your device. Once you're back in your app, each captured page is inserted exactly like a Camera-tool photo, sent to the field one page at a time. Tools / Document scanner / Options - **Use the built-in scanner**: off by default, and shown only where Google's scanner can run. Scans with the keyboard's own scanner instead. - **Save to the gallery**: off by default. Also keeps scanned pages in the **WM Keyboard** album under Pictures. ## Details & edge cases - **Camera permission** works differently across the three tools. OCR and QR/barcode check for it themselves, and when it isn't granted they offer an **Allow the camera** button. That opens the same disclosure-then-system-prompt trampoline the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/#granting-camera-access) uses, and returns you to the panel. The document scanner skips that prompt: Google's scanner manages its own camera permission, and the built-in one asks on its own screen. - None of the three scanners work before you've unlocked your device once after a restart (no direct-boot support), unlike some other tools that do work at the lock screen. - The trigger words in [Options](#options) are what make a standalone `ocr`, `scan` or `docscan` surface a chip into the matching tool. [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) covers that mechanism in full. - Open any of the three from Settings → **Tools** → **Scanners**, or by typing its keyword as described above. ## On the F-Droid build Elsewhere text scan leans on Google: ML Kit's text-recognition library (and its Chinese, Japanese, Korean and Devanagari ones, whose models Google Play services fetches), and the LiteRT engine that offline voice typing also uses, for PaddleOCR. Beside them sits a Tesseract library built into the app, about 1.7 MB on a 64-bit phone. None of the three is in the F-Droid build. ML Kit and LiteRT are Google's, and Tesseract's library is one F-Droid can't build from source. So the F-Droid build runs PaddleOCR on [MNN](https://github.com/alibaba/MNN) instead, an open-source engine from Alibaba that the build compiles from source for the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/). Both features share that one library, which arrives with the first download after you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). It reads Chinese, Japanese and Latin script. Anything else needs the Online model reader. Google's document scanner depends on Google Play services' document scanner module, so F-Droid's document scanner is the built-in one, and its QR scanner reads with zxing-cpp; neither has any Google code in it. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're deciding between builds. --- # Search, Wikipedia & dictionary URL: https://wmkeyboard.pages.dev/tools/search/ Section: Tools Summary: Web and image search, a three-tab Wikipedia reader, and instant word definitions: four lookup tools built into the keyboard. This page covers three tools, each with its own panel and its own settings screen: **Web search**, with its **All** and **Images** tabs (backed by Brave, by Tavily with a key of your own, or by a SearXNG server you name on the F-Droid build), a three-tab **Wikipedia** reader, and an **English dictionary**. Web search and Wikipedia never read what's already in your field. You type a query into the panel's own search bar, and nothing out of the field you're typing in is sent anywhere. Dictionary is the one exception. With its auto-lookup setting on, which is the default, opening the tool reads your current selection or the word at the cursor and looks that up. See [Looking up definitions](#looking-up-definitions) below. tools/web-search-results — Web search results. Tap a row for its menu: open it in your browser, insert its link, or copy the link. ## Web and image search Open **Web search** from the toolbar, or from the toolbox if you haven't pinned it. It isn't pinned by default. On a hardware keyboard, double-tap Ctrl for the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/), then press W. Results come in two tabs under the search bar: **All**, the web results, and **Images**. Switch tabs after a search and the same search runs on the other tab, once; switching back shows what was already there. Image search used to be a tool of its own. Its **Image search** button is still in the toolbox and opens this panel on the Images tab, and so do the I leader key, a key or gesture bound to it, and a smart chip for "images". Its settings are all on web search's page now. Both tabs ask the providers in their **Providers** list, in order: the Brave Search API by default, [Tavily](#searching-with-tavily) with a key of your own, or a SearXNG instance you name, on any build. If the first one fails, the next one that is on answers. Typing routes into the panel's own header search bar rather than your field, the same key-rerouting trick the GIF, sticker, and Wikipedia panels use. Unlike GIFs and stickers, the search doesn't run live as you type. It waits for you to press the search action before firing a request. To search in your browser instead, tap the open-in-browser icon at the end of the row of tabs: it opens the typed query as a search page on your SearXNG instance when that is your first provider, on Brave Search when Brave is, and on DuckDuckGo otherwise. Nothing is fetched by the keyboard for it. tools/image-search-grid — The Images tab. Tap a thumbnail for its menu: insert the picture, save it, or open, insert or copy its link. - **All**: each row shows the title, host, and an HTML-stripped snippet. From a SearXNG instance the host is followed by the engines that found the result, such as `en.wikipedia.org · google, duckduckgo`. Tap a result for its menu: **Open in the browser** (which leaves the keyboard), **Insert the link** at the cursor, or **Copy the link**. **Tap a result** in the options can skip the menu: set to **Insert**, a tap inserts the address and an open icon on each row opens the page; set to **Open in the browser**, it's the other way round. Press and hold a result for its menu whatever that says. - **The search engine's answer**: when the backend writes a short answer to your query, it sits in a box above the results. Tavily writes one from the pages it found, and some SearXNG instances give instant answers; Brave doesn't with a normal search key. It's drawn the way the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) draws an answer: LaTeX formulas are typeset, Markdown shows as headings, lists, bold and links, and a code block sits in its own box with **Copy** and **Insert** for just that code. The box shows about four lines and a tap shows the rest. Two icons beside its label act on the whole answer: the copy icon puts it on the clipboard as written, and the insert icon puts it in your text without the Markdown marks. Press and hold either to see its name. Under the box, a row of up to five pictures for your query: Tavily sends them with its answer at no extra cost, and with another provider the row shows the Images tab's pictures when that tab already searched the same words. A picture there works as it does on the Images tab. Turn **Show the answer from the search engine** off to hide it. - **Images**: tap a thumbnail for its menu. **Insert the picture** downloads the full image and inserts it directly, on the same `commitContent` path the clipboard and GIF tools use. **Save to the gallery** downloads it into Pictures/WM Keyboard. **Open in the browser** opens the page the picture is on, or the picture itself when the search didn't say which page that is. **Insert the link** and **Copy the link** give the picture's own address. With **Tap a result** set to **Insert**, a tap inserts the picture, and set to **Open in the browser** it opens its page; press and hold for the menu either way. The grid dedupes results by image URL before drawing them, since Brave can return the same full-size image for more than one widely-reposted result. - **More results**: at the end of either tab, it runs the same search again for the next page and adds what it finds under what you have. Press it again for the page after, as often as you like; when a page brings nothing new it reads "No more results" instead. Each press is one more request, which counts against your quota like a search. Tavily has no pages, so the keyboard asks it again without the sites already shown, and without the answer, which came with the first page. Brave gives ten pages of web results and up to 200 pictures, and a SearXNG instance pages as far as its engines go. With [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) holding searches back on mobile data, the button says so and fetches nothing. To search with a picture instead of words, tap the **camera button** at the right of the search bar. It opens the [camera tool](https://wmkeyboard.pages.dev/tools/camera/#searching-with-a-photo), and after the shutter the photo goes to Google Lens, a search site, or the share sheet, whichever **Search with** names on the camera tool's settings page. Back from the camera returns you here. Results stay put after you leave. Open one link in your browser, come back, and open the tool again: the same list is there under the same search, so reading several results costs one request, not one per visit, and the list can't change under you. Tap the **✕** at the end of the search bar to clear it and start a new search, or tap the bar itself to edit the search you ran. The two tabs each keep their own results. They're held in memory only, so they're gone once Android closes the keyboard. tools/search-needs-key — The web search panel before a Brave API key is configured. Without something to search with, the tool doesn't appear on the keyboard at all. There's no button that opens a panel with nothing behind it. Until then Web search and its Image search button are off the toolbar, out of the toolbox, and out of the smart chips, and a hardware shortcut bound to either one does nothing. On the **Tools** screen they're still listed, with their switch greyed out and a reason in place of the usual description. Everywhere except the F-Droid build, that reads "Needs an API key. Press this row to add one." The F-Droid build reads "Needs a SearXNG instance" instead, since it offers a self-hosted server as another way in. Add a provider to the list and both come straight back. ### Options Tools / Web search Every setting for both tabs is on **Tools / Web search**. **Tools / Image search** only has the switch and icon colour for its button, and a row that leads here. - **Turn on**: already on, like every tool. Switching it off removes that tool from both the toolbar and the toolbox. The switch can't be pressed while there's nothing to search with, since there's nothing to turn on. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are turned on globally. - **Providers**: who answers, in order. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), shared by both tabs: drag to reorder, switch a row off, or add **SearXNG**, **Tavily** or **Brave Search**. A fresh install starts with Brave when the build has a Brave key, and empty otherwise. Updating keeps the order your earlier settings gave: a SearXNG instance first, then Tavily, then Brave. - **SearXNG**: the address of a SearXNG server, your own or one you trust, on any build. Add more than one, and the next is asked when one refuses. The instance has to allow its JSON API (`json` under `search.formats` in its `settings.yml`), which most public instances don't, and public instances also rate-limit anything that isn't a browser. The keyboard sends the headers a browser would, so SearXNG's own bot check doesn't refuse it outright, but a busy public instance can still answer "too many requests" (HTTP 429). Some public instances also sit behind a challenge page (Anubis or similar) that only a real browser can pass; the panel then says the instance answered with a web page, and the fix is another instance. One you run yourself, with its limiter off, never refuses. - **Brave Search**: its setup takes a key from `api-dashboard.search.brave.com` to use your own quota. Leave it blank and the app uses a key baked into the build, if this build ships one; the row then says "Uses the key in this build". - **Tavily**: its setup takes your key from `app.tavily.com`. No build ships a Tavily key. - **SafeSearch**: on by default. It filters explicit results. On the All tab it maps to Brave's "moderate" filter. Brave's image search only offers strict or off, so the same toggle maps to "strict" on the Images tab. Tavily has one level, on or off. - **Results for each search**: a slider from 1 to 20, defaulting to **8**. ("Each search uses one API request either way," the app notes.) 20 is the most the Brave and Tavily APIs give for one request. - **Show the answer from the search engine**: on by default. Shows the box described above when the engine sends an answer. On Tavily the answer costs no extra credits. - **Tap a result**: **Show the menu** by default. **Insert** puts the link in your text (on the Images tab, the picture itself), and **Open in the browser** opens the page. Press and hold a result for its menu whichever you pick. If you had turned on **Open results in the browser** before this replaced it, it starts on **Open in the browser**. - **Search depth**: on Tavily's own screen (press its row on the list), **Basic** by default. Tavily's API has four depths: - **Ultra fast**: the quickest answer, with one short summary of each page. 1 credit. - **Fast**: quicker than Basic, with results nearly as good. 1 credit. - **Basic**: the balance of speed and good results. 1 credit. - **Advanced**: the deeper search, which picks the parts of each page that match your query more closely. 2 credits. Tavily takes no SafeSearch at the two fast depths, so results aren't filtered there whatever **SafeSearch** says. - **Images per row**: 3 by default. How many images sit side by side on the Images tab, from 2 to 5. Fewer columns means bigger previews, which is easier to judge before you send one. The settings page's own caption: "Web and image search share everything here. They search the whole web. The Brave plan includes a free monthly credit of about a thousand searches. Brave asks for attribution, so the panel shows 'via Brave'." That attribution is a small **"via Brave"** label inside the search bar, at its right end (just before the **✕** when there's a search to clear). It reads **"via Tavily"** while Tavily answers, and a SearXNG instance, being your own server, gets no label. ### SearXNG categories Tools / Web search / SearXNG / Search categories SearXNG sorts its engines into categories: general, images, videos, news, science, IT, music, files and more, depending on the instance. **Search categories**, on the SearXNG row's own screen, lists the ones your instance has and lets you pick any of them. The list comes from the instance's `/config` page, which answers even where the JSON search API is off; when the instance can't be reached, the screen offers the usual SearXNG categories and says so. General and images aren't in it, since they're the **All** and **Images** tabs already. None are picked by default. Each category you pick becomes a chip in the panel's row of tabs, between **All** and **Images**, in the order the instance lists them; the row scrolls sideways when there are more than fit. Tap one to search that category only. Its results look like the **All** tab's, with a title, the site, the engines and a snippet, and a video result shows its thumbnail at the start of the row. **More results** pages through the category, and a tap on **All** goes back to the general search. The search engine's answer only shows on **All**. The chips only show while a SearXNG instance is the one that searches: first in the **Providers** list, or picked in the panel's provider chooser. With Brave or Tavily first, the row is **All** and **Images** as before. A category search only asks the SearXNG instances in the list, never Brave or Tavily, whose general results would pass for the category's. ### Searching with Tavily [Tavily](https://tavily.com) is a search API built for AI tools, and it works as a third backend for both tabs. Sign up at `app.tavily.com` for a key. The free plan gives 1,000 credits a month, and each search the keyboard makes uses one: it asks for Tavily's `basic` search unless you pick another **Search depth**, and only **Advanced** uses two. The answer Tavily writes for the query is free, and shows above the results while **Show the answer from the search engine** is on, with up to five of the pictures Tavily found under it, also free. - **All** looks the same as with Brave: a title, the site, and a snippet. The snippet is the passages Tavily picked from the page, joined with "…". - **Images** has no image index of its own behind it. Tavily sends back the pictures it found on the pages of its results, so a search usually finds fewer pictures than Brave, with no title under them. Tapping and long-pressing work as they do with Brave. - With both Brave and Tavily in the list, the one higher up answers, and the other one only when it fails. Drag them to change which comes first. - When your credits run out, or the key is wrong, the panel shows Tavily's own message for it. - Tavily's own screen in the search list counts the credits this phone spent this month, and **Check what is left** asks Tavily for your plan's balance. After that the search bar reads, for example, "via Tavily · 812 left". With your own Brave key the same appears for Brave, which reports it on every search. See [Usage and credit](https://wmkeyboard.pages.dev/tools/providers/#usage-and-credit). ## Reading Wikipedia Open **Wikipedia** from the toolbar, or the toolbox if you haven't pinned it. It isn't pinned by default either. Unlike the other tools on this page, Wikipedia has no [leader-key](https://wmkeyboard.pages.dev/reference/shortcuts/) letter of its own out of the box. From a hardware keyboard the toolbox shortcut is the way in: double-tap Ctrl, then press T. You can also rebind a letter to it yourself. The panel takes the whole keyboard, the toolbar row included, with a back button and the search bar in its header. While you type a search it shrinks to that header so the keys fit underneath. Results list article titles with short descriptions, and tapping one opens the article. tools/wikipedia-article — An open Wikipedia article, with its Summary, Links and Full article tabs. An open article has three tabs: - **Summary**: the article's short description plus its extract, with **Insert summary**, **Insert link**, and **Insert title** chips. - **Links**: the article's outgoing links, namespace 0 only, so no Talk: or File: noise. The list is capped by **Links per article** in the tool's options: 50 to 500, defaulting to 200. 500 is as many as Wikipedia returns at once. The tab loads the first time you open it, and each link gets its own **Insert** chip. - **Full article**: the complete plain-text extract, split into paragraphs. Tap any paragraph to insert just that paragraph. Also loaded on first visit rather than up front. Formulas are typeset in place, the way the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) draws maths, rather than spread over the screen one symbol per line. What you insert or copy has them written out in plain characters (`x²`, `√(x+1)`), since a text box can't show a typeset formula. **Ask AI** gets the formula's LaTeX. Two buttons beside the article's title are for reading it in more room. The expand button grows the panel up the screen, to as much as 80% of its height, and tapping it again brings it back to the keyboard's height; search results always stay at the normal height. The other opens the article's page on wikipedia.org in your browser. Under the title, next to the tabs, **Copy** copies the article's text (the full article once you've opened that tab, otherwise the summary) with its address after it. When the phone has an app that looks words up, such as Kiwix with a Wikipedia file in it or Aard 2, an **Open in** chip sits after **Copy** and hands it the article's title. A search that finds nothing offers the same chip with your query. With one such app the chip names it; with several, tapping it lists them. Apps that offer themselves in the text selection menu count too, so a translator may be on the list. The chip doesn't show on a phone with none. Once you've opened an article from a results list, a back arrow appears and returns you to those results instead of a blank search. The idle screen puts it simply, with `en` swapped for whichever language you've set below: "Search for a subject on en.wikipedia.org. Read the summary, open the links, and insert text or the address of the article." ### Asking the AI about an article With the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, an **Ask AI** chip sits next to the tabs, beside **Copy**. It opens the AI panel's [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) on a new conversation with the article attached: the full article when you've opened that tab, otherwise the summary, along with its title, description and address. The keys come up under the message box so you can type your question straight away, or pick **Explain simply**, **Summarise** or **Hard words** to start one for you. Nothing is sent until you send it. To ask about one part, long-press a word in the summary or in a paragraph of the full article. The word is selected, with a handle at each end to drag it wider, and a bar over it offers **Ask AI**, **Copy**, **Insert**, **Quote** and **Select all**. **Ask AI** sends the passage along with the paragraph it came from, so the model reads it in context. **Quote** inserts the passage in quotation marks followed by the article it came from: `"…" (from [Wikipedia: Cat](https://en.wikipedia.org/wiki/Cat))` when **Markdown links** is on in the tool's options, `"…" (Wikipedia: Cat, https://en.wikipedia.org/wiki/Cat)` when it's off. Tap anywhere else to let the selection go. A selection stays inside one paragraph. **Copy** and **Quote** work without the AI tool. ### Options Tools / Wikipedia - **Turn on**: already on. - **Icon colour**: only shown if colorful tool icons are on globally. - **Wikipedia language**: a free-text subdomain code (`en`, `bn`, `de`, `fr`, `es`…), defaulting to **`en`**. It changes which language's Wikipedia every request in the tool reads from. Search, summaries, links, and the full article all follow it. - **Markdown links**: off by default. Turn it on to insert links as `[Title](url)` instead of a bare URL, in both the Summary tab's "Insert link" chip and every Links-tab chip. - **Links per article**: how many outgoing links the Links tab lists, from 50 to 500 and defaulting to **200**. A long article can have hundreds, and anything past your number is cut. The settings page's caption: "The searches and the article text come from the free APIs of wikipedia.org, only while you use the tool." No API key is needed anywhere in this tool. It calls Wikipedia's free public REST and MediaWiki APIs directly. ## Looking up definitions The keyboard has two things it calls a dictionary, and they don't share anything. - **Word lists**, under **Languages & layouts**, are for typing: suggestions, glide and autocorrect, in any language. They hold spellings and how common each is, never a meaning. - **The Dictionary tool**, under **Tools › Dictionary**, shows definitions. Downloading a word list does nothing for it. You don't need vocabulary packs or Anki either. Out of the box the tool defines English words, online or from an [offline dictionary](#offline-dictionaries). For any other language, [import a dictionary](#imported-dictionaries) in Yomitan or StarDict form, or send the word to a [dictionary app on the phone](#dictionary-apps-on-the-phone). [Translations](#translations) go from English into other languages, not the reverse. The dictionary looks up English words in a chain of sources you put in order (see [Dictionary sources](#dictionary-sources) below). None of them needs a key or an account. Open **Dictionary** with auto-lookup on, which is the default (see Options below), and it immediately looks up your current selection, or the word touching the cursor if nothing is selected. In the search bar the entry follows what you type, about 400 milliseconds after you stop, and a short result shows between the bar and the keys. Pressing return or the arrow puts the keys away so the whole entry shows. Each pause asks your sources in order, so with online sources on, a half-typed word can reach them too. tools/dictionary-entry — A dictionary entry: serif headword, pronunciation audio, numbered definitions and synonym chips. - Auto-lookup only picks up Latin-script words. A word holding any character outside that range, Bengali script for instance, is skipped quietly rather than looked up and shown "not found." The word also has to be 40 characters or fewer and hold at least one letter. Surrounding quotes and hyphens are trimmed off first. - If there's nothing to look up and no earlier result is still on screen, the tool drops straight into its own search field instead. - Headwords render in a serif display font (Lora), distinct from the rest of the keyboard's typography, falling back to the system serif if the font hasn't downloaded yet. - Each entry shows the word and, when the source has them, its IPA phonetic transcription. Every entry has a speaker button, which plays a [recording of a real person](#recorded-pronunciations) saying the word when one exists. Otherwise it plays the source's own recording, and when there's none, or it can't be fetched, it says the word with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) or the phone's text-to-speech voice. Set the voice's speed and pitch under the Vocabulary tool. The **Wiktionary definitions** source has no phonetics and no recording. An **Insert** chip types the headword itself at the cursor, not a definition. - When an installed [vocabulary pack](https://wmkeyboard.pages.dev/tools/vocabulary/) has the word and a translation for it, a line under the headword gives its meaning in your language: the one you picked under **Translation languages**, or else your keyboard's. It needs the Vocabulary tool on and translations shown in the keyboard under its **Card sections**; a word outside the packs has no such line. When the entry has [translations](#translations), that line is left out, because the translations already say it. - Each meaning lists its part of speech, numbered definitions with optional italic example sentences, and up to 12 tappable synonym/antonym chips per row. Tapping a chip looks that word up in turn. - An entry from an [imported dictionary](#imported-dictionaries) can carry more: the reading in small kana over a Japanese headword, and a line under it with the pitch accent and the dictionary's frequency figure, as in "たべꜜる [2] · Frequency 512". The ꜜ marks the mora the pitch falls after; [0] is a flat word. - A line under the last entry names the source that answered, for example "From Wiktionary". An imported dictionary is named by its own title, as in "From Jitendex". - A **Copy** chip beside the search bar copies everything the look-up found as text. With the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, an **Ask AI** chip beside it opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) about the same text, and a long press on a definition or an example selects it, with **Ask AI**, **Copy** and **Insert** on a bar over it. See [Asking the AI about an article](#asking-the-ai-about-an-article), which works the same way. - "The app cannot reach the dictionary" means no source could be reached: you're offline, or every online source you left on is down. A word that every reachable source answered for, without an entry, shows "No entry for" instead. - When only the sources on the device answered and no online source could be reached, the panel says the word is not in the dictionaries on this device, rather than "No entry for". The vocabulary packs hold only the words of their own study lists, a few thousand at most. To define ordinary words with no connection, download one of the [offline dictionaries](#offline-dictionaries). The same look-up works over any other app: select a word and pick **Look up**, or tap it after the floating button's Look up action. See [Look up](https://wmkeyboard.pages.dev/tools/look-up/). ### Recorded pronunciations The first tap on an entry's speaker looks for recordings of people saying the word, in the word's language. Wiktionary is always asked. It needs no key, and its pages play audio files from Wikimedia Commons, many of them recorded through Lingua Libre, in a lot of languages the phone's voice reads badly or not at all. With your own [Forvo](https://forvo.com) API key added under the options below, Forvo's voices come first, best rated first. The speaker plays the first voice. Every voice found also gets a chip under the headword, named by its accent (US, UK, AU) or by the person who recorded it, and a Forvo voice is marked "Forvo:". Tap a chip to hear that voice instead; the speaker then plays the one you picked. When nobody has recorded the word, nothing changes: the speaker reads it with the phone's voice as before. With **Keep recordings for offline use** on (the default), a recording you've played stays on the phone, along with the list of voices the word had, so it plays again with no connection. They take at most 24 MB together, and the ones played longest ago go first. Turn the setting off and they're kept in the app's cache instead, which Android may clear. The word goes to Wiktionary, and to Forvo when you've set a key, only when you tap the speaker. Turn **Recorded pronunciations** off to keep the speaker on the phone's own voice. ### Translations Tools / Dictionary / Translations Under the definitions, a **Translations** section gives the word in other languages: one row per language, with the language's name and a chip for each translation. Tap a chip to type that translation at the cursor. A word in a script other than Latin shows its romanisation in the chip after a dot, as in "বাড়ি · baṛi". - The translations come from Wiktionary, the same kaikki.org page as the **Wiktionary** source. When that source answered, the page already had them, so nothing more is sent. - When another source answered (a vocabulary pack, an offline dictionary, the Free Dictionary API or **Wiktionary definitions**), the entry shows at once and the tool asks Wiktionary for the translations after it, with "Looking for translations…" in the meantime. It does this only if **Wiktionary** is on in your [dictionary sources](#dictionary-sources). With it off, the word goes nowhere else, and an entry from another source has no translations. - They're for the first entry, and they're English to other languages, like the rest of the tool. - A language Wiktionary has no translation for gets no row. With no translation in any of your languages, the section isn't shown. **Show translations** is on by default. **Translate into** picks the languages and their order. With nothing picked, it follows your keyboard languages other than English, so a keyboard with English and Bangla shows Bangla. The list offers the languages of the vocabulary packs' translations, your keyboard's languages, and the languages Wiktionary most often has. This list is separate from the Vocabulary tool's **Translation languages**. ### Dictionary sources The tool asks the first source that is on. When that source doesn't know the word, or can't be reached, it asks the next one, so one service being down doesn't stop the tool. The default order, all on except the last: 1. **Vocabulary packs**: the [vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) packs you downloaded, read on the device. Only knows the packs' words, but sends nothing and works offline. The **Vocabulary packs** row under the list opens the screen where you get them. 2. **Wiktionary**: Wiktionary's full entry through kaikki.org, with definitions, examples, IPA and recordings. 3. **Wiktionary definitions**: Wiktionary's own definition endpoint on wiktionary.org. Definitions and examples only, but small and fast. 4. **Free Dictionary API**: dictionaryapi.dev. Last of the online sources because it's often down for days at a time; while it's down, every look-up waits about 8 seconds on it before giving up. 5. **Wiktionary, offline**: the [offline dictionary](#offline-dictionaries) made from Wiktionary, once downloaded. 6. **Open English WordNet**: the [offline dictionary](#offline-dictionaries) made from WordNet, once downloaded. 7. **Bengali–English Wiktionary**: the [offline dictionary](#offline-dictionaries) of Bengali words with their English meanings, once downloaded. Only a Bengali word finds anything in it. 8. **Other languages, offline**: the [offline dictionaries](#other-languages-offline) of other languages' words with their English meanings, whichever you downloaded. 9. **Imported dictionaries**: the [Yomichan and Yomitan dictionaries](#imported-dictionaries) you imported, in any language. Every one that is on and knows the word answers, in their own order. 10. **Urban Dictionary**: slang from urbandictionary.com, up to five definitions a word. Off until you turn it on, because anyone can write a definition and many are crude. At the bottom of the list it only answers for words the other sources don't know, so drag it higher if you want its reading first. The two offline dictionaries come after the online sources, so with a connection you get Wiktionary's fuller entries and recordings, and without one the tool still answers from the phone. Drag either one higher to have it answer first and send nothing. An update that adds a source puts it at the end of your list, switched on. Drag a source to change the order, and switch one off to never ask it. With every source off, the panel says so rather than looking anything up. Before version 0.5.12 the Free Dictionary API was the only source, so an outage there broke the tool on every device. ### Offline dictionaries Tools / Dictionary / Dictionary sources Press **Wiktionary (offline)** or **Open English WordNet** on the source list to open its screen, where it is downloaded. Two whole English dictionaries you can keep on the phone, so the tool defines ordinary words like "house" with no connection at all, and in the [build with no internet permission](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-internet-permission). Each row has a download button; once the dictionary is in, the row shows its size on the phone and a delete button. | Dictionary | What it holds | Download | License | |---|---|---|---| | **Wiktionary (offline)** | about 65,000 common English words from Wiktionary, slang like "selfie" and "lol" included: three senses per part of speech and six in all, a short example, synonyms, antonyms and IPA; no proper nouns | 5.1 MB | CC BY-SA 4.0 and GFDL | | **Open English WordNet** | every single English word in Open English WordNet 2024 (about 79,000), every sense, one example, synonyms, antonyms and, where it has one, IPA | 5.0 MB | CC BY 4.0 | | **Bengali–English Wiktionary** | about 7,500 Bengali words from English Wiktionary with their English meanings, an example with its translation where Wiktionary has one, and the romanized spelling in place of IPA. Each inflected form Wiktionary lists (কলকাতার, বাড়িতে…) opens its word | 0.5 MB | CC BY-SA 4.0 and GFDL | Both hold single words only. Phrases such as "ice cream" or "look after" are not in them, and neither are hyphenated compounds. A word form finds its headword: "mice" opens *mouse*, "houses" *house*, "running" *run*. Neither has recordings, so their entries have no play button. Each takes about 14 MB on the phone once installed. A look-up reads a few kilobytes of that file; it does not load the dictionary into memory. Neither file is read before the phone is unlocked for the first time after a restart. ### Other languages, offline Tools / Dictionary / Dictionary sources / Other languages, offline One download per language, each made from English Wiktionary's entries for that language: Albanian, Arabic, Basque, Bulgarian, Catalan, Czech, Danish, Dutch, Estonian, Finnish, French, Galician, German, Greek, Hebrew, Hungarian, Icelandic, Italian, Latvian, Lithuanian, Persian, Polish, Portuguese, Romanian, Russian, Slovak, Slovene, Spanish, Swedish, Turkish and Ukrainian. They keep what the Bengali one keeps: English meanings, not definitions in the language itself, up to six senses a word, a short example with its translation, and IPA or a romanized spelling. They hold single words, and a form Wiktionary gives an entry of its own opens its word: German *Häuser* opens *Haus*, *gehst* opens *gehen*. A form with no entry of its own finds nothing. The packs run from about 0.1 MB (Basque, Slovene) to 5.4 MB (Finnish) to download, and about three times that on the phone. When a word is in two packs you have, as *hand* is in German and Dutch, the tool shows both and each part of speech says its language. With a connection, the online Wiktionary sources above them answer first; drag this one higher to have it answer first and send nothing. ### Dictionary apps on the phone A dictionary app you already use can answer too. When the phone has one, an **Open in** chip after the "From" line at the bottom of an entry hands the word to it, and the "No entry" and "not in the dictionaries on this device" messages offer the same chip. It finds apps three ways: Aard 2's own lookup, the ColorDict intent that ColorDict, GoldenDict and most other offline dictionaries answer, and the text selection menu, which is how Kiwix and many others offer themselves. With one app the chip names it, and with several a tap lists them, each app once. Websites you turned on under [Look up in other apps](https://wmkeyboard.pages.dev/tools/look-up/#options) (Takoboto, Shinjikai, Jisho, Wiktionary, or one of your own) are listed after the apps and open on the word in the browser. Nothing is sent anywhere but to the app or site you pick. The **Import a dictionary** row on each screen takes a `.wmdict.gz` file fetched elsewhere: **Get the files** lists both download links, to open in a browser on any device, and **Import** installs the file. See [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/). ### Imported dictionaries Tools / Dictionary / Dictionary sources Press **Imported dictionaries** on the source list to open its screen. The dictionaries made for Yomichan and its successor Yomitan, the pop-up dictionaries that language learners use in their browser. There are hundreds of them: Japanese ones such as JMdict and Jitendex with readings, pitch accent and frequency lists, and Chinese, Korean and Wiktionary-based dictionaries for many other languages. **Get the files** opens the Yomitan wiki's list of dictionaries. Download a dictionary's `.zip` there, then tap **Import** and pick it. Any of the app's other **Import** rows takes it too; see [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/). **StarDict** dictionaries work here too, in any language pair: pick the `.tar.bz2`, `.tar.gz`, `.tar` or `.zip` a book came in, with its `.ifo`, `.idx` and `.dict` files together inside. If you have the loose files, zip them first. Definitions show as plain text, so a book's HTML styling is dropped along with any pictures and sound. Other spellings in a `.syn` file find the entry too. - A large dictionary takes some minutes to import, and the import line shows how far it has got. On the phone it takes about as much space as the dictionary's text unpacked, so a big Japanese dictionary needs a few hundred megabytes free while it imports. - Each imported dictionary gets a row with its number of entries, its size, its languages, its version and its attribution. The switch turns it off without deleting it. The arrows set the order its entries are shown in, and the bin deletes it. - Importing a dictionary with the same title again replaces it, so a newer version simply goes over the old one. - In the [source list](#dictionary-sources) they are one source, **Imported dictionaries**, which you can drag above the English sources so that it answers first. - An inflected Japanese word finds its dictionary form: 食べました opens 食べる, 行かなかった opens 行く, 高くない opens 高い. A word typed in kana finds the entry written in kanji. Other languages are matched in any case. - They work with no connection, and nothing about a look-up in them leaves the phone. - Kanji dictionaries, which list one character's readings and strokes rather than words, are not supported. A dictionary with both keeps its words. - Some dictionaries put pictures in their definitions; those are left out. ### Options Tools / Dictionary - **Turn on**: already on. - **Icon colour**: only shown if colorful tool icons are on globally. - **Look up the word at the cursor**: on by default. Its description on the settings screen reads "The tool searches the selected word or the current word when it opens". Turn it off and Dictionary always opens on its own empty search field. - **Show translations**: on by default. Shows the word in other languages under its definitions. See [Translations](#translations) above. - **Translate into**: the languages of those translations, in order. With none picked, your keyboard languages other than English. - **Dictionary sources**: a [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a source off, remove one, or add it back with **Add a provider**. See [Dictionary sources](#dictionary-sources) above. - Each source's own screen, opened by pressing its row: **Vocabulary packs** links to the packs, the two offline dictionaries download, delete or import there (see [Offline dictionaries](#offline-dictionaries) above), and **Imported dictionaries** imports, orders and deletes Yomichan and Yomitan dictionaries (see [Imported dictionaries](#imported-dictionaries) above). - **Look up in other apps**: the [Look up](https://wmkeyboard.pages.dev/tools/look-up/#options) card's selection menu entry, its style for a tapped word, and how it sends words to AnkiDroid. - **Recorded pronunciations**: on by default. The speaker plays people's recordings of the word before the phone's voice. See [Recorded pronunciations](#recorded-pronunciations) above. With it on, two more rows show at the Advanced level: **Keep recordings for offline use** (on by default) and **Forvo API key** (empty by default). The key is never put in a backup. - **Servers** (F-Droid build only): on the screens of Wiktionary, Wiktionary definitions and the Free Dictionary API, the address each is called at, for a mirror or a copy you host yourself. The settings page's caption: "Definitions come from the sources below, in their order. The keyboard sends the word that you look up to an online source only when you use the tool." [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/)'s "Background network" toggle overrides this row. While that toggle is on, auto-lookup is forced off whatever you've set here. A look-up nobody explicitly asked for is exactly what it exists to stop. Web search, image search and Wikipedia are the **Web search** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the three tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on; your search services stay set. ## Details & edge cases - **Only web search needs to be unlocked.** It needs one usable provider: a Brave key, yours or the one baked into this build, a Tavily key of your own, or a SearXNG instance. Wikipedia and Dictionary work out of the box against free, keyless public APIs. - **Default hardware letters:** W for web search, I for its Images tab, D for dictionary. Wikipedia has no letter of its own out of the box, so reach it through the toolbox (T), or [rebind a letter to it yourself](https://wmkeyboard.pages.dev/reference/shortcuts/). - **Discoverable by typing.** Typing "search", "images", "wiki"/"wikipedia", or "define"/"dict" as the last word before your cursor offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that opens the matching tool ("images" opens the Images tab). The keyword list is editable per tool, and only shows while that tool is enabled. - **None of these are build-gated.** Every build behaves identically across all the tools here. - **Attribution.** Brave Search and Tavily results, Wikipedia's article text, Wiktionary's entries, and the Free Dictionary API's definitions are all credited under "Online services" on the app's About → Open-source licences screen, alongside the terms each service is used under. Wikipedia's and Wiktionary's text is CC BY-SA, which keeps its own license independent of the app's. The offline dictionaries carry their sources' licenses with them: Wiktionary's CC BY-SA 4.0 and GFDL, and Open English WordNet's CC BY 4.0. An imported dictionary's own attribution line is shown on its row. The [AI tool](https://wmkeyboard.pages.dev/tools/ai/#letting-the-model-look-things-up) can run searches of its own through this same backend, when you let it: it writes the query, reads the results and answers from them, and each one counts against your quota like a search you ran yourself. For what leaves your device and when, across every network tool, see [network policy](https://wmkeyboard.pages.dev/privacy/network/). For the GIF and sticker tools' different, search-as-you-type behavior, see [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/). --- # Send files URL: https://wmkeyboard.pages.dev/tools/send/ Section: Tools Summary: Send a file to anyone as an encrypted link that expires, or straight to a phone next to you, and receive both kinds without leaving the app you are typing in. The **Send files** tool moves a file out of the keyboard in two ways. **Link** encrypts the file on your phone, uploads it to a [Send](https://github.com/timvisee/send) server (the maintained fork of Firefox Send) and gives you a link with an expiry, a download limit and an optional password. **Nearby** hands the file to a phone or computer next to you over Wi-Fi, with no server at all. It receives both kinds too: a Send link in a chat gets a chip that downloads and decrypts the file, and a device nearby can send a file to you. ## The panel The panel is a tray. Along the top, under **TO**, one row of cards names every place a file can go: **Link** (the Send server, with its host and the largest upload it takes), each phone or computer the Nearby engine knows, **Browser** (a page any browser on this Wi-Fi can open), **Direct** (a Wi-Fi this phone opens for another phone), **Scan code** where the build has a scanner, and a last **Nearby** card that opens the radar. The card you tap is filled and ringed; it is where the Send circle sends. Under the cards sit the four sources, **Files**, **Gallery**, **Clipboard** and **Field**, and the rest of the panel is the trough the files land in. The header has **History**, which lists every link this phone made and every file it received, **Receive**, which takes a pasted Send link, and the tool's settings. ## Sending a link 1. Pick what to send: **Files** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) (Android's document picker in the Play Store build), **Gallery** opens the [Gallery](https://wmkeyboard.pages.dev/tools/gallery/) tool to pick photos and videos, **Clipboard** takes what you copied (a file, a picture, or text saved as a file), and **Field** saves the text in the field you are typing in. Each file drops into the trough as a tile; the tiles fan into a pile, and a tap on the pile spreads them out so one can be removed. 2. With **Link** chosen, the trough shows how much of the server's cap the pile takes and three chips for the link's limits: how long it lives, how many downloads it allows and a password. Each chip picks in place; the choices are what the chosen server allows. **Generate**, in the password box, makes a twelve-character one you can read out. 3. Press the **Send** circle. The panel turns into the sending face: the Link card sits in the middle of a ring that fills as the file is encrypted and uploaded in one pass, the pile flies into it, and the percent, the bytes sent, the speed and the time left are written under it. **Cancel** is in the header. When it is done the link is typed into the field, or copied, or shown with its QR code, whichever you chose under **After the upload**. Hold the circle instead to pick for this one upload. The file is encrypted before it leaves the phone, with a key that travels only in the link, after the `#`. The server stores the ciphertext, its size and when it arrived. It never sees the key, the file or even its name, which is encrypted too. Anyone with the link can download the file until it expires or the downloads run out; a password is a second lock the server checks on top. A finished link is a receipt: the QR code at the left (tap it for a big one), the host over the link's hash, and chips for when it expires, how many downloads are used and the password, which shows its letters when you tap the lock. Under it one bar holds **Insert link** (or **Copy** when no field is open) with **Copy**, **Share** and the QR code as round buttons, and **Manage** opens the download count and **Delete from the server**. **RECENT** shows the link before this one, as a door into History, and **New** in the header starts over. ## Receiving a link Paste or type a Send link in any field and a chip appears on the suggestion strip: **Receive the file**. Copy a link in another app and the same chip is waiting when you come back to the keyboard. Tap it and the panel asks the server about the file, shows its name, size and expiry, warns you when yours is the last download allowed, asks for the password if there is one, and downloads and decrypts it into **Downloads › WM Keyboard**. A picture can go straight into the chat field from there; anything else opens or shares from the panel. You can also press **Receive** in the panel's header and paste the link into its box. ## Nearby Two other apps' devices show up here as well, each as a card in the TO row. **LocalSend** apps on the same Wi-Fi appear by themselves, with no server and no account, while the panel is on Nearby. **PairDrop** pages on the same network appear once you turn on **PairDrop** in the tool's settings: it is off by default because it meets other devices through a PairDrop server (`pairdrop.net` unless you enter your own), which sees this phone's address while the panel is on Nearby. Sending is the same as to any other card. A file coming to you asks first, with **Accept** and **Reject** in the tray, and is refused after a minute with no answer. Neither runs with the panel closed, except to finish a transfer already under way. PairDrop's files go directly between the devices through WebRTC, a library of about 12 MB that the tray offers to download the first time. The F-Droid build has no such library, so there PairDrop works only with a server that relays files itself. PairDrop's paired devices and public rooms aren't supported, only devices on one network. **Nearby** uses the same engine as the [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/) tool, so it reaches phones and computers running WM Keyboard, KDE Connect, GSConnect or Valent on the same Wi-Fi. Every device it knows is a card in the TO row, with **Connected** in green, **Not in reach**, or **Pair** for one it has not met; tap the card and the Send circle sends the pile to that device. Pairing happens once, by comparing the eight-character code the trough shows with the one on the other screen, with **Accept** and **Reject** for a device that asks first. The **Nearby** card at the end of the row opens the radar: a sweep turning over three rings, the pile in the middle, and the devices placed around it, the ones in reach on the near ring and the rest dim on the far one. The browser page and the direct Wi-Fi join the radar as nodes while they are up. Tap a device to send the pile to it (or to pair, when it is new), tap the middle to go back to the tray, and use **Refresh** in the footer to look again. A send wears the same sending face as a link, with the device's card in the ring; **Hide** in its header puts it away so the tray can be used meanwhile. Files sent to you land in **Downloads › WM Keyboard**; the transfers either way, under way and finished, are a chip in the trough and in the radar's footer that opens the list, with **Clear finished**. ### In a browser The other device does not need WM Keyboard, or any app. Pick the **Browser** card and press **Start the page** (or the Send circle): the phone starts a small web page on its Wi-Fi address, then shows a QR code. Whoever scans it on the same Wi-Fi gets a page in their browser with each file you picked and a **Download** button beside it. With **Accept files** on, the page also takes files back: choose or drop them there and they land in **Downloads › WM Keyboard** on your phone. Files you add or remove in the panel show up on the page within a few seconds. While the page is up, the trough holds its controls: **Show the code**, **Accept files**, **Copy** for the address, and **Stop**. When the other device is on a different network, or the Wi-Fi keeps its devices apart (common on guest and office networks), press **Use a hotspot**. The phone opens a Wi-Fi of its own, the same way the direct link below does, and the code sheet shows two codes: the first joins that Wi-Fi from a phone camera, the second opens the page. A laptop joins by typing the Wi-Fi name and password shown beside the code. Nothing goes through the internet and no outside server is involved; the page comes straight from your phone. The address carries a random 128-bit token, so a device on the same Wi-Fi that did not get the code finds nothing. The page is plain HTTP, since browsers refuse a certificate a phone made for itself, so use it on networks you trust. It stops when you press **Stop** or after 10 minutes with nobody on it. ### With no Wi-Fi between you Two phones with WM Keyboard can link directly. On the receiving phone pick the **Direct** card and press **Receive here**: the phone opens a Wi-Fi of its own (a Wi-Fi Direct group, or a local hotspot where the phone cannot do that) and shows a QR code. On the sending phone tap the **Scan code** card google and point the camera at it. The code carries the Wi-Fi's name and password, the receiver's address and a one-time pairing token, so the sender joins the Wi-Fi, finds the receiver and pairs with it in one go; then the files go. **Disconnect** takes the Wi-Fi down again, unless **Keep a direct link up** is on. Android asks for the **Nearby devices** permission before an app may open or join a Wi-Fi this way (on Android 12 and older, the same thing sits behind the location permission). The tool asks only when you press one of those two buttons, reads no position, and needs nothing at all for devices that are already on the same Wi-Fi. ## From other apps With **Show in other apps' share sheets** on, **Send with WM Keyboard** appears beside the apps you share to. Share a file from anywhere, pick the limits and a password, and the link is copied for you, with its QR code on the card. Nearby works from there too, to a paired device in reach. A share that names a file inside WM Keyboard's own storage is ignored, so another app cannot use it to upload the keyboard's private files. ## Choosing a server The **Link** card names the instance uploads go to and the largest upload it takes. Tap it again while it is chosen, or hold it, for the list: the servers from the [community list](https://github.com/timvisee/send-instances) with their size, time and download limits, each with a dot that says whether it is answering, and **Add your own** for a server you run or trust. The default is the one the maintainer of Send runs. Every public instance is run by volunteers with no promises; what they see of your file is its size and its arrival time, nothing else. If the pile is too big for the chosen server, the trough says so in red and offers the instances with room for it. ## Settings Tools / Send files Send files is the **Send files** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Switch the bundle off and the tool, the link detection and the history screen leave together; the history stays on the phone. | Setting | What it does | |---|---| | Instance | The server uploads go to, and the list of your own. | | Link expires after | The default life of a new link: 5 minutes to 7 days, capped by the server. | | Downloads allowed | The default download count of a new link. | | Password | Off, ask on every upload, or generate one every time. | | After the upload | Insert the link, copy it, show it, or ask. | | Offer to receive links | The strip chip for a Send link typed, pasted or copied. | | Open after download | A single received file opens as soon as it is saved. | | Send to devices nearby | The device cards, the radar, the direct link and the browser page; off, one card in the TO row leads here. | | Keep a direct link up | Stay on the other phone's Wi-Fi after a transfer. | | Show in other apps' share sheets | The **Send with WM Keyboard** entry. | | Keep a history | The links you made and the files you received, for the History sheet and screen. | Data saving has a row for this tool: a whole file over mobile data asks first, like a download. ## Privacy The link is the key. The server you upload to receives only ciphertext; the server a link points at learns that you fetched it, which is unavoidable, and nothing about you beyond your address. Links you made are kept on the phone, with the owner token that can delete them, in a folder that is left out of every backup. Nothing about any of this goes to the developer. The network activity log lists every request the tool makes under **Send files**. --- # Offline voice (Sherpa) URL: https://wmkeyboard.pages.dev/tools/sherpa/ Section: Tools Summary: Streaming dictation on your phone in 13 languages, Bangla among them, with words that appear while you speak. Offline Sherpa runs [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx), k2-fsa's speech toolkit, on your phone. Its models are streaming Zipformer transducers: they decode while you speak, so the words go into the field as you say them, the way the phone's own recognizer does it, and nothing waits for the end of the phrase. Each language has its own model, and one covers eight at once. Once a model is downloaded, no audio leaves the phone. The same runtime also reads text aloud: see [Read back and offline voices](https://wmkeyboard.pages.dev/tools/read-back/). A language with no model on the phone uses the system recognizer instead. ## Setting it up Voice typing 1. Add **Sherpa** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**. 2. Press **Sherpa** on the list to open its screen, which holds the **Sherpa models** group. The first time, a banner offers ONNX Runtime, the engine Sherpa runs on: a one-time download of about 33 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub and F-Droid builds; the offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first. 3. Download the model for each language you dictate in. Each one comes from Hugging Face at a fixed revision, every file checked against its published SHA-256. | Model | Languages | Download | Licence | |---|---|---|---| | Zipformer English Small | English | 46 MB | Apache-2.0 | | Zipformer English | English | 75 MB | Apache-2.0 | | Vosk Bengali | Bangla | 96 MB | Apache-2.0 | | Vosk Russian Small | Russian | 30 MB | Apache-2.0 | | Zipformer Korean | Korean | 134 MB | Apache-2.0 | | Zipformer French | French | 130 MB | Apache-2.0 | | Kroko German | German | 73 MB | CC-BY-SA-4.0 | | Kroko Spanish | Spanish | 158 MB | CC-BY-SA-4.0 | | Zipformer Chinese | Mandarin | 169 MB | Apache-2.0 | | Zipformer Chinese Small | Mandarin | 27 MB | Apache-2.0 | | Zipformer Chinese + English | Mandarin and English in one model | 200 MB | Apache-2.0 | | Paraformer Chinese + English | Mandarin and English in one model; can't listen for your words | 239 MB | Apache-2.0 | | Zipformer ar en id ja ru th vi zh | Arabic, English, Indonesian, Japanese, Russian, Thai, Vietnamese, Mandarin | 341 MB | Apache-2.0 | | Punctuation (English) | punctuation and capitals for English; see below | 8 MB | | The sizes include Silero VAD (2 MB), a small voice detector every model carries so that deleting one never breaks another. ## How it works While you speak, the words go into the field as underlined composing text, rewritten as they grow, and the line under the microphone shows them too. When the phrase ends, the finished text replaces them and goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). **Where a phrase ends.** With **Stop after a pause** set, Silero listens for you to stop talking and ends the phrase after that long a pause; with **Keep listening** on, the next phrase starts at once. Silero hears speech rather than loudness, so a fan or traffic does not keep the phrase open the way it can for a loudness threshold. With the setting off, the phrase runs until you press the microphone or two minutes pass. **Phrase end detector** on the Voice typing screen swaps Silero for FireRedVAD, plain loudness or a model of your own, and **Smart Turn** ends the phrase sooner when you sound finished; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase). **Which model.** When two downloaded models know the language, the larger one dictates. The model list marks each one in use, and **Engine for each language** can pin a language to a particular model. **Punctuation.** Most of these models write plain words with no punctuation, and the English ones trained on audiobooks spell in capitals. Sherpa turns capitals into ordinary lower case. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a finished phrase gets full stops, commas, question marks and sentence capitals from a punctuation model for 88 languages, downloaded from that setting's row on the Voice typing screen. **Punctuation (English)** here does the same for English on its own, and with both on the phone an English phrase takes the multilingual model's marks and this one's capitals for names. The German, Spanish, Korean and eight-language models punctuate by themselves and are left as they are. Spoken punctuation ("comma", "full stop") works either way. ## Words to listen for Voice typing / Words to listen for Sherpa listens for the same [words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for) the system recognizer gets: the names and terms you typed into **More words to listen for**, then your own words from the dictionary when **Listen for my words** is on. They go to the model as hotwords. While it decodes, a guess that spells one of your words gets a small bonus for each piece of the word it has spelled so far, and the bonus is taken back if the guess turns away before the word is complete. A name the model was close to hearing then comes out the way you spell it. The model can only write a word in the pieces it was trained on, so each word is first spelled in that model's pieces. Words it has no pieces for are left out: German umlauts for the English models, Bangla letters for any model but the Bengali one, and characters the Chinese model has never seen, which is most rare characters in Chinese names. The eight-language model takes Chinese characters and plain Latin words, but not words in Arabic, Cyrillic, Thai or Vietnamese letters. Words under three letters are left out too, so a stray initial does not turn up wherever a word starts with it. **What it costs.** Hotwords need a slower search: Sherpa keeps four guesses open instead of one. The search only runs when some of your words can be spelled by the model, so with an empty list, or **Listen for my words** off and nothing typed in, Sherpa decodes the quicker way. Switching between the two loads the model again, once. **What to expect.** Hotwords help a name the model nearly heard; they cannot teach it a sound it has never heard. On synthetic test sentences with 15 rare names (Siobhan, Okonkwo, Ljubljana, Kubernetes and the like) read by five voices, the English model got the name right 1 time in 75 without the list and 9 to 11 times with it, whether the list held just those names or 195 words. Ordinary sentences came out as before: no word from the list was ever typed into a sentence that did not have it. ## Reduce background noise Voice typing / Reduce background noise Not Sherpa's own, but worth knowing about here: **Reduce background noise** cleans the microphone's audio before any offline engine hears it, Sherpa included. **Light** runs [RNNoise](https://github.com/xiph/rnnoise), a tiny network that costs about 10 ms; it comes with the offline voice engines' shared libraries, so on a phone that has only Sherpa so far it fetches those (about 12 MB) at the next phrase. **Strong** runs [DPDFNet](https://github.com/ceva-ip/DPDFNet), a DeepFilterNet model, on the ONNX Runtime Sherpa already has, after a 9 MB model download. Strong is the one that helps recognition: on noisy test recordings Sherpa's English model went from 33% of words wrong to 31% with it, and to 40% with Light. The RNNoise 0.2 model Light runs now does a little better than that but still costs words: on a second set made the same way it took Sherpa's English model from 29% to 35%, where 0.1.1 gave 37%. Off by default; see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/#continuous-mode) for the details. ## Your own models Voice typing / Recognition engine / Sherpa / Your own models The list above covers a few languages. To use another [streaming transducer](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-transducer/index.html) or [streaming Paraformer](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-paraformer/index.html) model, download its files and press **Import a model**. Pick the encoder, the decoder, `tokens.txt` and a transducer's joiner together, then give it a name and its language codes, such as `tr` or `en, de`. Where a repository has both a full and an `int8` copy of a part, the `int8` one is kept. - It's used before the listed models for its languages, and a language it adds becomes one Sherpa can dictate. Delete it to go back. - It also needs `silero_vad.onnx`, which hears where a phrase ends. Pick it with the rest, or have any listed model downloaded and its copy is used. - Only streaming transducers and streaming Paraformers work. Files for CTC or a whole-file (offline) model are refused with a note, because the engine can't run them this way. A Paraformer can't follow the words to listen for. ## Sherpa, Whisper or the others For Bangla, Sherpa is the only offline engine that shows the words while you speak; [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) understands more accents and rarer words but types the phrase when you stop. For English, [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) streams too and punctuates on its own, [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/) is the smallest, and Whisper (on whisper.cpp) takes your own words as a prompt, which it reads more freely than Sherpa's hotwords. Sherpa is the one to pick for live text in a language the others do not stream in. For a language none of them knows, [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) runs on the same runtime and covers about 380 keyboard languages, a phrase at a time. ## Details & edge cases **Several languages at once.** A dictation that listens for more than one language at a time needs one Sherpa model that knows all of them, which only the eight-language model can be. Otherwise the panel asks for a model. **The licences.** sherpa-onnx is Apache-2.0, ONNX Runtime is Microsoft's under MIT, Silero VAD is MIT, FireRedVAD is FireRedTeam's under Apache-2.0 and Smart Turn is pipecat's under BSD-2-Clause. The Kroko models are Banafo's community models under CC-BY-SA-4.0, the Vosk models Alpha Cephei's under Apache-2.0. All of them are credited in the app's About screen. **Where the runtime comes from.** The app never carries a copy of ONNX Runtime rebuilt by anyone but Microsoft: the Play build gets it from Play, the offline build carries Microsoft's own file inside the APK, and the GitHub and F-Droid builds read Microsoft's file straight out of its package on Maven Central, checked against a SHA-256 built into the app. See [Network activity](https://wmkeyboard.pages.dev/privacy/network/). --- # Snippets & text expansion URL: https://wmkeyboard.pages.dev/tools/snippets/ Section: Tools Summary: Reusable text blocks with variables, inserted from a panel or expanded by keyword. Snippets are reusable pieces of text (a mailing address, a canned reply, a sign-off) that you save once and reuse everywhere you type. Insert one with a tap from the panel, or type its trigger word and let it expand on its own as you finish typing. tools/snippets-panel — The snippets panel: saved snippets as two-column cards, ready to insert with a tap. ## Opening the panel Open **Snippets** from the toolbar if you've pinned it there, or from the toolbox. Snippets isn't pinned by default on any device. A phone starts with Emoji, Clipboard and Settings, and a tablet starts with five or seven pins, none of them Snippets. So the first few times you'll likely reach it through the toolbox, or by typing its [keyword](#options). On a hardware keyboard, double-tap Ctrl to arm the leader key, then press S (see [hardware shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/) for the full letter-to-tool map). Each snippet shows as a card in a two-column grid: its label in bold on top, the snippet text underneath. That text is the raw template rather than an expanded preview, so a card containing `{date}` shows the literal token instead of today's date. Tap a card to insert it. A card with more than one thing to insert shows a count in a filled circle beside its name, so it can't be mistaken for part of a name like `2`: press and hold it and the panel lists everything it offers, its own expansions first and then the snippets it links to, with a **›** on any of those that has more behind it. Anything you were mid-typing gets committed first, so a half-finished word can't get swallowed into the snippet. With nothing saved yet, the panel shows the same variable hint plus a shortcut straight to Snippets settings. If you've made any [folders](#folders), the panel opens on them instead: folder tiles first, then whatever you've filed in no folder at all, in the same grid. Tap a folder to go inside it, and the header takes the folder's name while you're there. Back (the header arrow, the system Back gesture, or Esc on a hardware keyboard) climbs out of the folder before it closes the panel. Press and hold a folder tile to switch its automatic expansion on or off without leaving the app. With no folders anywhere the panel is exactly the flat grid it always was. The panel re-reads the snippet file every time you open it. Settings and the keyboard share that file, so an edit you make in Settings shows up the next time you switch to this panel, not while it's already open. The magnifier in the panel's header opens a search. The keys come back under a shorter panel that keeps a couple of rows of matches in view, with a bar on top that [drags it taller](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) so more matches show, and as you type the grid narrows to the snippets whose label, text or trigger contains what you typed, from every folder at once. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the snippets that mean what you typed follow them. The × in the search, or Back, closes it and brings the full list and the folders back. **Panel columns** under [Options](#options) sets how many tiles sit side by side, two unless you change it. ## Auto-expanding as you type Give a snippet a trigger and it expands on its own. Type the trigger by itself, then finish it with a space, punctuation or Enter. What you typed is replaced with the expanded snippet instead of being committed literally. Matching is case-insensitive and exact: `omw` matches `OMW` but not `omww`, with no partial or prefix matching. You can also glide a trigger: swipe `omw` and the snippet is typed as soon as you lift your finger, and one backspace puts the trigger back. While you swipe, the floating word, the suggestion strip and the words offered when you hold still all show the trigger with its expansion beside it, like `omw = on my way`. That works for a one-word trigger that doesn't [ask first](#ask-before-it-expands); a trigger with punctuation or a space in it still has to be typed. To match something that varies, like a name or a time or a phrase, use a [pattern trigger](#pattern-triggers) instead. To skip the space altogether, set the snippet to [expand immediately](#expanding-immediately). ### Triggers with punctuation or spaces in them A trigger doesn't have to be a single bare word. It can lead with punctuation, as in `:shrug`, `;ty` or `//date`. It can hold spaces, as in `gr db` or `on my way`. It can do both, as in `:gr db`. Type the whole thing, finish it the usual way, and it expands like any other trigger. The punctuation form is the convention almost every [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/) package uses. The spaces form is what makes a trigger a phrase rather than a code you have to memorise: `gr db` for `./gradlew assembledebug` reads like the command it stands for. Both work the same way under the hood, and that explains the one rule below. The keyboard's word buffer only ever holds letters, digits, apostrophes and a hyphen inside a word (as in `e-mail`), so by the time you finish typing `:shrug` the colon has already gone into the field, and by the time you finish `gr db` so has "gr ". Only the last word is in the buffer. So the keyboard looks that word up, and only when it could finish some trigger does it read the short run of characters in front of it to check the rest. A snippet list with no trigger of this kind in it costs nothing at all. A trigger can also **end in a symbol**: `js:`, `->`, `gr db:`. Those fire the moment the symbol is typed, with no space or Enter after it, because the symbol itself is the cue: the keyboard checks whether a trigger ends in that symbol at all, and only then reads the short run in front of it off the field to confirm the rest. `js:` typed as `JS:` still matches, and a trigger that starts with a letter needs a word boundary in front of it, as every trigger does, so `xjs:` leaves `js:` alone. Punctuation in the middle is fine too, so `a-b`, `m:f-u` and `:a.b` all work, whether or not the hyphen joined the word you were typing. The one shape that can't be watched for is a trigger ending in a space. Two smaller ones: - The longest match wins. `::x` beats `:x` if you have both, and `gr db` beats a plain `db`. - A trigger that starts with a letter or a digit needs a word boundary in front of it, the same as a plain trigger does. `gr db` fires after a space or a punctuation mark, but not in the middle of "xgr db". A trigger that starts with punctuation doesn't need one, because the punctuation is the boundary. Everything in front of the last word can run to 32 characters. That's room for a couple of words, not a sentence. ### Expanding immediately A word trigger can also fire the moment you type its last character, with no space, punctuation or Enter after it. Turn on **Expand immediately** on the snippet's editor screen and `btw` becomes "by the way" as the `w` lands. It works for every shape of word trigger: `:sig`, `;;addr` and `gr db` fire on their last letter too. This is how [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/#when-a-trigger-fires) fires a match unless told otherwise, and an imported package keeps that. By default the trigger still needs a word boundary in front of it, so `btw` leaves "subtweet" alone. Turn on **Also inside words**, under it, and it fires wherever the text ends with the trigger, even partway through a word. A trigger that starts with punctuation never needed the boundary. Two ways to keep the trigger as you typed it: - **Backspace once** straight after it expands. The trigger comes back, and it stays: a trigger set to expand immediately isn't looked at again when the word ends, so the space after it leaves it alone. - **Type the escape character first.** A backslash in front of the trigger, as in `\btw`, stops it firing, and the backslash itself is taken out, leaving `btw`. Choose a different character, or turn the escape off, with **Escape character** under [Options](#options). The rest works as it does for any trigger: capitals carry over with **Match the trigger's capitals**, a [glide](#auto-expanding-as-you-type) of a one-word trigger expands when you lift, and [several expansions](#several-expansions) put their chips on the bar. Nothing fires while a transliterating or converting layout is spelling the word (Pinyin, Avro, Vietnamese and the like), on a keypad layout that guesses words from key presses, or in a password field unless you've turned on **Expand in password fields**. A snippet set to [ask first](#ask-before-it-expands) has nothing to fire, so the switch is hidden for it, and [patterns](#pattern-triggers) always wait for the space. A trigger that [ends in a symbol](#triggers-with-punctuation-or-spaces-in-them), like `js:`, already fires the moment that symbol is typed, with or without this switch. ### Several triggers One snippet can answer to as many triggers as you like. The **Triggers** field on the editor screen takes one at a time: type a trigger, press the **+** button at the end of the box, and it becomes a small chip you can tap to edit or press the ✕ to remove. Enter does the same thing, and so does a comma, so pasting "brb, omw, ttyl" gives you three chips. You never have to save and reopen the snippet between triggers: add all four, then press Save once. Spaces don't split any more, since a trigger is allowed to contain them, and any run of whitespace inside one is saved as a single space. Each trigger fires the same snippet in exactly the same way. A spelling that repeats another is quietly dropped, and the app keeps the first 256. ### Matching capitals **Match the trigger's capitals** carries how you typed the trigger into what comes out. Off (the default), `omw` and `OMW` both expand to whatever the snippet says. On: - typing it in **capitals** gives you the expansion in capitals: `ALH` gives "ALTHOUGH" - typing it with **one leading capital** gives you the style you picked: **First letter** (`Alh` gives "Although"), **Every word**, or **All capitals** - typing it in **lower case** changes nothing It matters for snippets that fix ordinary words rather than expand codes. A pack that turns "a bas" into "à bas" would otherwise mangle "A Bas" at the start of a sentence. Espanso calls this `propagate_case`, and packages that correct spellings lean on it heavily. Trigger expansion is skipped while you're mid-transliteration or mid-conversion. For phonetic composers like Bangla-style transliteration, or Pinyin/Japanese-style conversion, the composing buffer holds an input spelling rather than the literal word you meant as a trigger, so expansion would misfire. On Pinyin and the other conversion layouts a reading that spells a trigger offers the snippet as a chip at the end of the strip instead, and a tap on it drops the reading. **Shortcuts while converting** turns that off. This is a different mechanism from the **Keyword shortcut** field on the Snippets settings screen (see [Options](#options)), which is a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that offers to *open the Snippets panel* when you type "snippet" or "snippets." It has nothing to do with any individual snippet's own trigger word. ### Pattern triggers A word trigger matches one fixed word. A **pattern** trigger matches the last few words before the cursor with a regular expression, and hands whatever it captured to the snippet's text. One snippet then covers every name, every time, every phrase: | Pattern | Snippet text | You type | You get | |---|---|---|---| | `^hello (.+)$` | `Hello, $1! Nice to meet you.` | `hello John` + space | Hello, John! Nice to meet you. | | `^thanks (.+)$` | `Dear $1,`
`Thank you for your message…` | `thanks Sarah` + space | The whole letter, addressed to Sarah | | `^/gh (\S+)$` | `https://github.com/$1` | `/gh wasi-master` + space | https://github.com/wasi-master | | `\s+([.,?!:;])` | `$1` | `hi` + space + `.` | hi. | Each pair of brackets in the pattern is a capture, and `$1` to `$9` put those captures into the text. `$0` is everything the pattern matched. Four transforms are available when you want the capture reshaped: `${1:upper}`, `${1:lower}`, `${1:title}` and `${1:trim}`. A literal dollar sign is `$$`. Some details: - **Matching is case-insensitive** by default, so `HELLO John` works too. Start the pattern with `(?-i)` if you want it to care. - **`^` and `$` are optional.** The pattern always has to fill the whole span it's matched against, so `hello (.+)` behaves exactly like `^hello (.+)$`. Most people write the anchors anyway, and the examples here follow suit. - **Words to match** caps how far back a pattern can reach: three words by default, up to eight. It's there because names are short. With a cap of three, `^hello (.+)$` can turn "hello John Smith" into a greeting but will leave a whole sentence alone. - **A pattern never reaches across a line break**, so a snippet can't swallow the paragraph above the one you're writing. - **The shortest match wins.** If two patterns both fit, the one that eats fewer of your words is the one that runs. A plain word trigger always beats a pattern. - **A pattern can start on the space in front of a word.** `\s+([.,?!:;])` with the text `$1` pulls a stray space back out from in front of a full stop or a comma. The space and the word after it count as one word for the words-to-match cap. - **Backspace takes it back.** One press right after an expansion restores exactly what you typed, spacing and capitals included, and the pattern won't fire again on that same text. Patterns run on every space you type, so the keyboard is careful about what they cost. Start a pattern with a plain word (`^hello `, `^/gh `) and it can be skipped entirely unless that word is actually behind your cursor, which is nearly always. A pattern that starts with something variable instead, like `^(.+)$` or `^\d+`, has to be tried after every word. The editor says so when you write one. A pattern that turns out to be pathologically slow is stopped rather than allowed to stall your typing. Everything else about word triggers applies here too: patterns are skipped mid-transliteration and mid-conversion, and they never run in password fields or in fields that ask for no suggestions. A pattern either expands on its own or asks first, never both. Which one it is decides when it's looked for at all: the expanding kind is checked on the space, Enter or punctuation that ends a word, the asking kind on every keystroke (see below). Punctuation gets a second look once the mark itself is in the field, so a pattern about the mark (the space fixer above) sees it. That second look is skipped when the first one already expanded something. ### Ask before it expands Every snippet has an **Ask before it expands** switch on its editor screen. Turn it on and the trigger stops rewriting your text. Instead, the moment it matches, the snippet appears as a chip on the suggestion bar showing the text it would insert, and nothing goes into the field until you tap it. Keep typing and the chip goes away, leaving what you typed exactly as you typed it. The chip arrives as soon as the trigger is typed. There's no need to press space first, and that goes for both kinds of trigger: - A **word** trigger offers itself while you're still typing the word. Type `brb` and the chip is there before you reach the space bar. Press space instead of tapping and you get a literal "brb." - A **pattern** offers itself mid-phrase, and keeps up as you type. With `^hello (.+)$`, typing "hello Jo" already shows a chip reading "Hello, Jo!". The next letter re-reads it as "Hello, Jon!", and so on. The offer survives the space that ends the word, so you can finish the phrase and then decide. Tapping the chip swaps the words it matched for the expansion and keeps any trailing space. What the chip shows is exactly what goes in. The chip shares the suggestion bar with your word candidates, so turning an offer down never costs you the predictions you were about to use. Backspace takes an accepted expansion back the same way it does an automatic one. The offer isn't remembered anywhere. It's worked out fresh from the text in front of the cursor, so moving the cursor, editing from elsewhere, or carrying on typing all stop it matching, and the chip goes. One cost: an asking *pattern* is checked on every keystroke rather than once a word, so it reads a little of the text in front of your cursor each time you type. The check is debounced and runs off the typing path, and it doesn't happen at all unless you have an asking pattern installed. Plain word triggers are free either way, because matching one is a dictionary lookup on the word you're typing, with nothing to read. This is what the **Pattern Replies** pack in the [addon repository](https://wmkeyboard.pages.dev/addons/overview/) uses. Canned replies written by somebody else are exactly the case where silent rewriting is unwelcome: "hello John" turning itself into a paragraph you didn't ask for is a worse outcome than one extra tap. Every pack's preview says which way each of its snippets goes, so you can see it before installing. ## Several expansions A snippet doesn't have to say one thing. Add a second expansion and typing its trigger offers you both, as chips on the suggestion bar, and you pick the one you meant. The **Expansions** section on the editor screen is a list. The first row is the default, marked with a star: it's what a tap in the snippets panel inserts, and what any export that has no way to say "several" carries. The arrows move a row up or down, so promoting an expansion to the top is how you change the default. There's no separate button for it. What happens when the trigger fires is up to you, in **Settings → Text expansion → When a snippet has several expansions**: - **Chips only** (the default) inserts nothing. Every expansion sits on the suggestion bar and the text stays exactly as you typed it until you press one. Your word candidates keep working beside them, so ignoring the offer costs nothing. - **Insert first** puts the default in straight away, the way a one-expansion snippet always has, and offers the rest as chips. Pressing one swaps what was just inserted for it, and you can keep swapping. Backspace still takes the whole thing back to the trigger you typed. Any one snippet can overrule that. Once it has more than one expansion, its editor grows a row of its own with the same two choices plus **App setting**, which is what it starts on. Each expansion is a row of its own in the editor, under **Expansions**. Press the row, or its pencil, to open the text for editing; the arrows move it up and down the list, where the top one is the default, and the bin removes it (a snippet keeps at least one). **Add expansion** opens a fresh row. The bar scrolls sideways when there are more chips than fit. Nothing is hidden behind a "more" button, because reaching for one would mean leaving the bar and losing the text you were in the middle of. ## Linked snippets A snippet can also point at other snippets, and typing its trigger offers those too. This is the part that turns a snippet list into something closer to a map of the words you use. Give "continent" the links Asia, Africa and Europe, and typing "continent" offers all three. Give "address" the links Home, Work and Email, and one trigger covers all the addresses you own. Add them under **Linked snippets** in the editor. The picker searches by name, trigger or tag, and you can pick several at once. The editor also tells you which snippets point *at* the one you're editing, under "Linked from", because a snippet can sit under more than one parent and there's otherwise no way to see it: Tehran belongs under Iran, under Capitals and under Cities all at once. On the suggestion bar, a linked snippet shows as one chip carrying its default expansion. If that snippet has more of its own, whether other expansions or links, the chip grows a **›** on the end. Press the chip to insert what it says; press the **›** (or hold the chip) to go one level in, and the bar fills with what *that* snippet offers, with a back arrow to climb out. So "continent" → Asia → Iran → Tehran walks down as far as you've built it. Links are a graph rather than a tree, so two snippets can share a child and a pair can point at each other without any harm: a walk never re-enters somewhere it has already been, it just stops offering the way back. ## Tags Tags are free words you file a snippet under so you can find it again. Add them in the editor the same way you add triggers: type one, press enter, it becomes a chip. They do nothing while you type; they're for searching in the snippet picker when a list has grown past the point of scrolling it. ## Template variables A snippet's text can include any of these tokens. They're substituted the moment the snippet is inserted, not when you save it, so `{date}` always reflects the day you actually type it: | Token | Expands to | |---|---| | `{date}` | Today's date, e.g. 19 Jul 2026 | | `{time}` | Current time, 24-hour, e.g. 16:45 | | `{time12}` | Current time, 12-hour, e.g. 4:45 PM | | `{datetime}` | Date and time together | | `{isodate}` | ISO date, e.g. 2026-07-19 | | `{isotime}` | ISO timestamp with a UTC offset | | `{weekday}` | Day name, e.g. Sunday | | `{day}` | Day of month | | `{month}` | Month name | | `{year}` | Four-digit year | | `{timezone}` | Your time zone's full name, e.g. Bangladesh Standard Time | | `{timestamp}` | Unix time in seconds | | `{clip}` | Your latest clipboard entry | | `{selection}` | Text currently selected in the field, which the snippet then replaces | | `{app}` | Name of the app you're typing into | | `{package}` | That app's package name | | `{uuid}` | A fresh random UUID, different every time it's used | | `{cursor}` | Not text; marks where the cursor lands after insertion | | `{random:a\|b\|c}` | One of the alternatives, picked fresh each insertion | | `{location}` | Where you are, from the [Location tool](https://wmkeyboard.pages.dev/tools/location/#in-snippets), in its default format. `{location:link}`, `{location:address}`, `{location:coordinates}`, `{location:dms}`, `{location:plus_code}`, `{location:message}` or `{location:custom}` pick one | | `{place:Home}` | A place you saved in the Location tool, by its name in any case. A format can follow: `{place:Home:address}` | | `{ha:sensor.outside_temperature}` | A [smart home](https://wmkeyboard.pages.dev/tools/home-assistant/) entity's state with its unit, as the keyboard last saw it, looked for in every home on the list; `{ha:home_key\|sensor.x}` names one. An attribute can follow: `{ha:light.kitchen:brightness}`. Empty when the tool is off | `{timezone}` is the one worth a second look. It writes the zone's long name, not an offset, so you get "Bangladesh Standard Time" or "Central European Summer Time" rather than GMT+06:00. An offset only shows up for a zone with no name of its own. The settings screen's reference card gives GMT+06:00 as its example, which is the fallback rather than the usual case, but the live preview next to it always shows what you'd actually get. If you want the offset itself, `{date:XXX}` or `{date:ZZZZ}` will give you one. `{date:pattern}` takes any date pattern you like, for example `` {date:EEEE 'week' w} `` or `` {date:dd/MM/yy} ``. A `+` or `-` and a number of seconds before the colon shifts it: `{date+86400:dd/MM/yy}` is tomorrow, `{date-86400}` was yesterday, and a bare `{date+3600}` is an hour from now in the usual format. It's checked before the plain `{date}` token, so a literal pattern can't be partly swallowed by it. A malformed pattern quietly expands to nothing instead of breaking the rest of the snippet. Tokens the expander doesn't recognize, like `{nope}`, are left untouched. A variable with nothing to draw on expands to an empty string rather than leaving the raw token behind, which is what you get from `{app}` outside the keyboard or `{clip}` with an empty clipboard. ## Managing snippets Find the list under Settings → Text expansion (see [Options](#options) below for the exact path). The Snippets tool page has one row that opens the same screen. tools/snippets-settings — Snippets settings: the variable reference card, saved snippets with live insert previews, and Add / Import / Export. The settings screen opens with a reference card listing all 18 variables plus `{date:pattern}`, each with a live example. Below it: tools/snippets-add-dialog — Adding a snippet: label, text with variables, and a Word or Pattern trigger. - **Add snippet** opens the editor screen: a label, an optional folder, the [expansions](#several-expansions) it can insert, and a Word / Pattern switch for how it expands as you type. - The section that holds both is called **Trigger**. Choosing **Word** gives you the [Triggers](#several-triggers) chip field and a **Match the trigger's capitals** switch that reveals its style choice when you turn it on. Choosing **Pattern** gives you the pattern itself, chips that write `(.+)`, `$1`, `^` and `$` at the cursor, a words-to-match picker, and a **Try it** box. Type into **Try it** what you'd type on the keyboard, and the result appears underneath. A pattern that won't compile is flagged as you type, with the regular-expression engine's own complaint under it. Save stays disabled until you fix it. - Under both is **Ask before it expands**, which turns the trigger into a suggestion-bar offer instead of an automatic rewrite (see [Ask before it expands](#ask-before-it-expands)). With it off, a word trigger also gets **Expand immediately**, and once that's on, **Also inside words** (see [Expanding immediately](#expanding-immediately)). - Below that come [Linked snippets](#linked-snippets) and [Tags](#tags). - Pressing a saved snippet reopens the same screen, filled in, to edit it. Nothing is saved until you press **Save**, the button that floats at the bottom corner of the screen so it's always in reach. It's greyed out until the snippet has a label and some text. Leaving with changes you haven't saved, by the back arrow or the back gesture, asks first: save them, discard them, or go back to editing. - The trash icon at the end of the row deletes it immediately. Swiping the row to the left shows the same delete button behind it, and a long swipe past about half the row deletes it outright. There is no confirmation prompt. Instead a bar appears at the foot of the screen naming what went, with **Undo** on it for a few seconds; undo puts the snippet back with its own id, its place in the list, and the links any other snippet had to it. Once that bar goes, the delete stands, so keep an [export](#import-and-export) around for anything that took a while to write. Deleting a second snippet replaces the bar rather than queueing behind it, so only the last delete can be undone. - Every row also shows a live "Inserts as: …" preview whenever the expanded text would differ from what you typed, and "Expands by itself from: …" if you've set a trigger (or the pattern itself, if you set one of those). A snippet set to ask first says "Offers itself after: …" instead. A snippet with more than one expansion, or with links, counts them on a line of its own. Snippets have a stored order. New ones append to the end, and the pencil button on the heading above a list of them turns that list into one you drag into order, so the ones you use daily can go back to the top. Press the tick to leave. That matters because the snippets panel draws in the same order, so the top of the list is what you see without searching. Each list is ordered on its own: the snippets inside a folder, and the ones in no folder at all. ### Folders Folders group snippets that belong together: a set of work replies, a pack you installed, the handful of things you only use for one project. They're one level deep, and a snippet is in at most one of them. The **Folders** section on the Text expansion screen is where you make them, and **New folder** asks for a name. Pressing a folder opens it, the same way the keyboard's snippet panel does, and inside you get the folder's own page: its name, its icon, its switch, and the snippets filed in it. So the main screen lists folders and then whatever is in no folder at all under "Not in a folder", instead of repeating every folder's contents down the page. A folder can wear an icon of its own, picked from the same set a [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/) picks from. It's on the folder's page under **Icon**, and it shows up on the folder row in Settings and on the folder tile in the snippets panel. **Plain folder** in that picker puts the ordinary folder drawing back. The pencil button on the **Folders** heading is where the rest lives. Press it and each folder's switch is replaced by a drag handle and a trash icon: drag to set the order they're drawn in everywhere, or delete one. Deleting asks first. By default the snippets inside survive and become ungrouped, and a switch in that same question deletes them along with the folder if that's what you meant. An empty folder goes without being asked about. Press the tick on the heading to go back to the normal view. The editor grows a **Folder** picker once at least one folder exists, and the **+** button on a folder's own page writes a new snippet straight into that folder. Each folder row carries a switch, and that switch is the point of the whole feature. (It's also on the folder's own page, as **Expand while you type**.) - **On** is the normal state. Everything about the folder's snippets works the way it always has. - **Off** stops the folder's snippets expanding while you type. Their word triggers and pattern triggers don't fire, and their ask-first chips don't appear. Nothing else changes: the snippets are still there, still listed in the panel, and still insert when you tap them. It's the difference between a set of canned work replies that must not fire in the middle of a personal message, and having to delete them. Because switching a folder off is exactly the kind of thing you realise mid-message, you can also do it from the keyboard: press and hold a folder tile in the snippets panel. The tile dims and says "Off" while it's disarmed. The change is written straight to the shared file, so Settings shows it too. A folder switched off costs nothing at all while you type. Its snippets are left out of the trigger index entirely, so the keyboard's "could anything match here?" checks answer no without doing any work. **Change all snippets here**, on a folder's page, sets options on every snippet in the folder at once, so a folder of forty corrections doesn't need forty trips to the editor. For each of **Match the trigger's capitals**, **Which letters to make capitals**, **Ask before it expands** and **Expand immediately**, pick **On**, **Off** or a style, or keep **Leave as is**, which is where each one starts. **Apply** changes only what you picked. The editor's own rules still hold: a snippet with a pattern trigger keeps its own capitals and expansion, and a snippet that asks before it expands never expands immediately. A message at the bottom of the screen offers **Undo** for a few seconds. ### Import and export **Import** and **Export** each start by asking which format you mean. This app's own is one of three; the other two are [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/)'s, which is what lets a package from the [Espanso Hub](https://hub.espanso.org/search) work here and your own snippets work on a desktop. Importing from Espanso can also fetch straight from a pasted web address, and always shows what conversion costs before it writes anything. Import takes two more formats that nothing exports to: - **A table (CSV).** A `.csv` or `.tsv` file, with commas, tabs or semicolons between the columns. This is what most other text expanders and every spreadsheet can write. With a header row, the columns are found by name in any order: `trigger`, `shortcut` or `abbreviation` for the trigger; `text`, `expansion`, `phrase` or `content` for what it types; and, if you want them, `label` or `name`, `folder` or `group`, and `tags`. Without a header the columns are read in order: trigger, text, then an optional label, which is how TextExpander, aText and Texpand export. A cell in double quotes can hold commas and line breaks. A row with no text is dropped, and a trigger the keyboard cannot watch for is taken off while the snippet stays. - **Gboard's dictionary.** The `PersonalDictionary.zip` that Gboard exports. Its shortcuts become snippets that ask first, the way Gboard shows them as suggestions. Its plain words are left out here; import the same file from the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#androids-dictionary) to add both at once. See [Gboard's personal dictionary](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#gboards-personal-dictionary). Both show the same confirm dialog as an Espanso file, with a folder name to put the snippets in. **Export** writes every current snippet to a `.wmsnippets.json` file (see [file formats](https://wmkeyboard.pages.dev/reference/file-formats/)), along with the folders they're filed in. A folder with nothing in it isn't written, so exporting doesn't carry empty shelves along. **Import** always adds alongside what's already there rather than replacing it, so importing the same file twice leaves you with two copies. The file's folders come back as new folders of the same name, and a snippet that was in none stays in none. The fourth choice under **Export**, **Share with everyone**, sends one folder of snippets, or all of them, to the [community repository](https://wmkeyboard.pages.dev/addons/community/) for anyone to install. It asks which folder, and reminds you to check there's nothing personal in them first. Rows are repaired rather than rejected when something's off. A snippet with no text is dropped. Text over 20,000 characters is trimmed. A blank label is filled in from the snippet's own first line. A trigger pattern that won't compile, or that runs past 200 characters, is removed while the snippet itself is kept. Only the first 500 snippets of an oversized file are kept, and the first 100 folders. A folder with no name, or one repeating another's ID, is dropped and its snippets arrive ungrouped. Once the import finishes you get a summary of everything that changed. ## Options Text expansion Everything about the snippets themselves lives here, under Features on the settings home screen, rather than on the tool page: the list, the two reference cards, folders, Add, Import and Export. Snippets expand while you type whether or not the tool sits on the toolbar, so they aren't really the tool's settings. Tools / Snippets The tool page carries only what belongs to the button: - **All Text Expander settings**: opens the screen above. - **Panel columns** (1 to 6, default 2): how many snippet tiles the keyboard's panel draws side by side. Folders use the same grid. The gear in the panel's header opens this page, so the setting is one tap away from the panel. - **Names only** (off): each tile shows the snippet's name alone, without its text, so a tile is one line tall and a long list fits more to a screen. - **Stay open after inserting** (off): tapping a snippet inserts it and leaves the panel open, for several in a row. Off, the panel closes after each one. - **Enabled**: which way this starts depends on how you answered the setup wizard. Snippets is in the "recommended" and "show me everything" starter sets, so only the simplest answer leaves it switched off. Turning it off removes Snippets from both the toolbar and the toolbox. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color. - **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel. "Snippet" and "snippets" out of the box, editable or clearable here. This only controls opening the panel, and it's unrelated to a snippet's own trigger word (above). The same **Panel columns**, **Names only** and **Stay open after inserting** settings also sit on the Text expansion screen, under **Behaviour**. It only changes the panel's grid, not the chips a trigger offers in the suggestion bar. **Expand in password fields** (default **off**), in that **Behaviour** group, lets a word trigger or a trigger ending in a symbol expand inside a password box, for a Wi-Fi key or a long passphrase you keep as a snippet. With it on, pressing space or Enter there, or typing the symbol a trigger ends in, makes the keyboard read back the few characters before the cursor and check them against your triggers. Nothing it reads is learned, kept or sent. The space that ends a trigger is spent on the expansion rather than typed into the password, and Enter still does what the field asks, so `wifi` then Enter fills the box and submits it. Patterns, chips that ask first, and apps typing snippets through [automation](https://wmkeyboard.pages.dev/reference/automation/) stay off in password fields either way. Number-only PIN fields aren't covered; tap the snippet in the panel there instead. **Escape character** (default **backslash**), in the same group, is what you type just before a trigger set to [expand immediately](#expanding-immediately) to keep the trigger as text. The character is removed when it does its job, so `\btw` leaves `btw`. The choices are a backslash, a backtick, or **Off**, for when you write that character in front of words for its own sake. Beyond that, there's nothing global to configure about expansion. There's no on/off switch separate from the tool. How matching works is set per snippet instead, on the editor screen: word or pattern, how far back a pattern may reach, whether it expands on its own or asks first, and whether it waits for the space or expands immediately. The nearest thing to a group switch is a [folder](#folders), which arms and disarms everything filed in it together. Having no pattern snippets at all is what keeps the matching machinery out of the typing path entirely. ## Asking the AI about them The [AI chat](https://wmkeyboard.pages.dev/tools/ai/) can look through your snippets with **Read your snippets**, one of the [keyboard's own tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools): ask it for "my bank details snippet" or "what do I type for my address?" and it reads back the matching snippets' names, triggers and text. A template is read as you wrote it, so the model sees `{date}` rather than today's date, and `{clip}` never hands it your clipboard. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, it also finds snippets that say the same thing in other words. Like the other tools that read your things, it asks you first each time unless you set it to **Run**. ## Snippet packs and backups Someone can package a set of snippets as an installable addon: a `.wmsnippets.json` file up to 4 MB. You can preview it before installing, so you see the actual snippets it contains rather than installing blind. Installing a pack adds its snippets alongside anything you already have, in a [folder](#folders) of its own named after the pack. That's what lets you silence a whole pack with one switch, find it again in six months, and see at a glance which snippets are yours. If the pack file declares folders of its own they're flattened into the pack's folder, since folders are one level deep. Uninstalling a pack removes exactly the snippets that pack added and leaves your own snippets and any other installed pack untouched. Its folder goes with it, unless you've moved something else into that folder, in which case the folder is yours now and stays. Packs you installed before folders existed are filed into one the next time you open the Addons screen, using the name the pack was installed under. If you've already sorted a pack's snippets by hand, that arrangement is left exactly as you made it. See [installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general install/preview/uninstall flow. That's a different mechanism from the whole-app [config backup](https://wmkeyboard.pages.dev/privacy/backup/) (`.wmconfig.json`). That backup has its own optional Snippets section you can include or leave out when backing up everything at once, alongside themes, dictionary, clipboard, stickers and your settings. The plain settings backup (`.wmsettings.json`) is different again: it deliberately leaves snippets out, since these two flows already cover them. ## Details & edge cases - Snippets aren't available before you've unlocked your device once after a restart. The snippet file lives in credential-encrypted storage, so this tool sits out any lock-screen or pre-unlock typing until then. - Ships the same way in every build: there's no build gating on this tool. - The 500-snippet and 20,000-character limits above are enforced only on import. Adding snippets one at a time from the editor screen has no equivalent cap. - Because the panel re-reads the snippet file only when you open it, a snippet you just added in Settings won't appear if you already had the panel open. Close and reopen the panel (or switch to another panel and back) to pick it up. Triggers themselves are refreshed whenever you tap into a text field, so a snippet you just saved starts expanding straight away even if you never open the panel. - `{uuid}` generates a new value on every insertion, including inserting the same snippet twice in a row. It isn't fixed per snippet. - A tap in the snippets panel always inserts the default expansion, never a picker: one tile, one insertion. Hold it if you want the choice. - Chips on the suggestion bar go the moment the text they were talking about changes: a keystroke, a backspace, moving the cursor, opening a panel. A swap chip verifies the text is still there before it replaces anything, so a press that lands late does nothing rather than eating the wrong words. - Every expansion of a pattern snippet sees the same captures, so `$1` means the same thing in the second one as in the first. - Tapping a **pattern** snippet in the panel inserts its text with the captures left blank and puts the cursor in the first gap, since a tap has no words to capture. `Hello, $1! Nice to meet you.` goes in as "Hello, ! Nice to meet you." with the cursor after "Hello, ". A plain snippet is unaffected: a `$1` in its text is a dollar sign and a one. - Capture text is spliced in exactly as you typed it. A template variable that lands inside a capture stays literal rather than expanding. Type "note remember `{clip}`" into a `^note (.+)$` pattern and the token goes in as written, so text in the field you're typing into can never pull your clipboard in behind you. - Put `{cursor}` in a snippet's text where you want to keep typing after it goes in. `Dear {cursor},` with the trigger `dear` inserts "Dear ," and leaves the cursor before the comma, ready for the name. The space you pressed to expand the trigger is used up by the expansion rather than typed, so it doesn't land in front of the name. With `{cursor}` at the very end, or with no `{cursor}` at all, the cursor ends up after the snippet and the space goes in as usual. Some editors, mostly code editors and a few note apps, ignore where the keyboard asks the cursor to go and leave it at the end of the inserted text; the keyboard checks for that and moves the cursor back to the marker itself. - Word triggers work in every field except password fields (unless you turn on **Expand in password fields** under [Options](#options)), including a browser's address bar, which asks the keyboard for no suggestions. There the trigger is read back from the box when you press space or Enter, and it replaces the browser's own inline completion. Chrome, Brave and the other Chromium browsers treat a block of text landing at the start of an empty address bar as a paste and strip `javascript:` out of it, so the keyboard puts an expansion there the way typing would, and a snippet that expands to a `javascript:` bookmarklet arrives whole. A trigger set to [ask first](#ask-before-it-expands) offers its chip in the address bar too, as long as the suggestion bar is showing there (**Suggestions in every field**, on by default). A trigger that starts with a digit, like `123`, is read back the same way in every field, since a number at the start of a word goes straight into the field instead of the word buffer. Patterns are stricter: they stay out of fields that ask for no suggestions, and out of every field when you've turned suggestions off entirely. - Swiping a word with gesture typing, or tapping one from the suggestion strip, doesn't fire a pattern. The next word you type by hand does. --- # Internet speed test URL: https://wmkeyboard.pages.dev/tools/speed-test/ Section: Tools Summary: Measure download, upload, ping and jitter from the keyboard, then send the result as a picture: a card in your choice of style, size and layout. Someone asks how the Wi‑Fi is, and the honest answer is a number. The **Internet speed test** tool measures the connection you are on and drops the result into the chat as a picture, without leaving the keyboard. A run takes about twenty seconds. It pings the server, pulls random bytes for ten seconds, pushes them back for ten more, and then shows the card it would send, with chips to change how that card looks before it goes. ## Running a test Open the tool from the toolbox. The panel is a dial, with the server a run will use in its header and roughly how much data the run will move written inside it. The dial is the button: tap it and it fills with the live speed while the run goes, the phases (Ping, Download, Upload) as a rail across the header with the ping's answer written on its step, and the last seconds as a skyline along the bottom. **Cancel** sits in the header and stops it at once, and so does closing the panel or hiding the keyboard: a run nobody is watching is only costing data. A finished run shows four numbers. - **Download** and **Upload** are the mean speed over each transfer with its first fifth left out. That is where the connections are still opening and TCP is still finding its pace, and every browser test discards it too. The unit is megabits per second unless you switch it to megabytes. - **Ping** is the median of ten round trips to an empty page, the first one dropped because it also opens the connection. - **Jitter** is how much those ten varied, as the mean difference between one and the next. The panel remembers the last run. Open the tool again later and the result is already there with its card, and the next run's card notes how it compares. ## The card The result is sent as a PNG, and the panel shows it as it will go: the card itself, as large as the panel allows, with the next and previous looks peeking at its sides. A swipe across the card changes its look, the dots under it say which of the five is on, and one row of chips under those sets its size, layout and unit; every change redraws the card on the spot. - **Style** (the swipe): **Theme** paints the card in the colours of the keyboard theme you have on, so it matches your screenshots. **Dark**, **Light**, **Gradient** and **Mono** are fixed designs that look the same whatever theme is on. Mono is black on white, for printing or a plain thread. - **Size**: **Square** (1080 × 1080) and **Portrait** (1080 × 1350, 4:5) suit most chats; **Story** is 9:16 for a status or story; **Wide** is 16:9 for a post or a forum; **Sticker** is 512 × 512, small enough to go as a sticker where the app takes one. - **Layout**: **Full** has every number, the download line as a graph, and a footer naming your network, the server and the change since the last run. **Gauge** is one dial with the download speed in it and the other numbers underneath. **Compact** is the four numbers in a grid, which suits the Sticker size. **Minimal** is the two speeds and nothing else, in very large type. Along the bottom sits one bar of the ways to send it: **Send** as the wide button, the other three as round buttons that name themselves on a hold. - **Send** puts the card into the field, the same way a GIF or a sticker goes. An app that takes no pictures gets the card on its share screen instead, or on the clipboard. - **Save** writes it to `Pictures/WM Keyboard`. - **Share** opens the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/) with the card. - **As text** types one line instead of a picture, `↓ 94 Mbps · ↑ 31 Mbps · 18 ms ping (Cloudflare Dhaka)`, with your own numbers. - **Run again**, in the header beside where and when the result came from, starts over. The footer never shows your IP address unless you turn **Show my IP address** on. It does name your network provider and the city of the server that answered, which is what a result is usually shared to say. ## Servers A run talks to one server, and the chip under the Start button picks which. Tools / Internet speed test / Servers is the list, in the same shape as every other [provider list](https://wmkeyboard.pages.dev/tools/providers/). - **Cloudflare** is on by default and needs nothing set up. The test reaches the nearest of Cloudflare's data centres, which is why the numbers are about your line rather than about some distant server, and Cloudflare tells the keyboard which network you are on and which city answered, which the card shows. - **LibreSpeed** is a server running the free LibreSpeed software. Add the row, then either type the address of one you run yourself under **Setup**, or open the row and **Pick from the public list**: about a hundred volunteers' servers, listed by city with their sponsor, filtered as you type. Picking one fills in the row. You can add several LibreSpeed rows and switch between them on the panel. In the F-Droid edition both addresses, Cloudflare's and the list at librespeed.org, are settings on the row's screen and under Advanced / Servers, as every service's address there is. ## Data use This is the one tool in the keyboard that moves a lot of data on purpose: on a fast line a run can pass a few hundred megabytes. The panel says what a run is likely to cost before you start, worked out from the last run's speeds (a guess of 50 Mbps each way before there is one). On a connection Android counts as metered, [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) asks first by default and **Run anyway** is the yes. Every request of a run shows up in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) under Speed test, with the bytes it moved. ### Options Tools / Internet speed test - **Seconds per direction**: 5 to 30, default **10**. How long the download and then the upload each run. Longer is steadier and costs more data. - **Connections at once**: 1 to 8, default **4**. Requests in flight together. One connection rarely fills a fast line, and four is what the browser tests use. - **Measure upload**: on by default. Off, a run is pings and download only, in half the time and data. - **Unit**: **Mbps** (default) or **MB/s**, on the panel and the card. - **Result card**: **Style**, **Size** and **Layout**, the same choices as the panel's chips, so either place changes both. **Send a sticker-sized card as** picks Picture (default) or Sticker for the Sticker size, which is the only size that can go as a sticker, and only into apps that take one. **Save sent cards to the gallery** (off) keeps a copy of every card you send; the panel's Save button saves one regardless. **Show my IP address** (off) puts the connection's public address in the card's footer. - **Servers**: the provider list described above. The speed test is the **Speed test** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on. ## Details & edge cases - **Discoverable by typing.** Typing `speedtest` or `bandwidth` as a word offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that opens the tool. The list is editable under the tool's own **Keyword shortcut** field. - **Not before you unlock.** The tool needs the network and the settings, so it waits for the first unlock like the other online tools; see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone). - **A VPN or a hotspot counts as metered** to Android, so Data saver may ask on Wi‑Fi too. The question names the kind of link it saw. - **What the headline leaves out.** The first fifth of each transfer is dropped from the mean. The dial during a run shows the same running mean, so it climbs while the connections open and then settles on the number the card will carry; the thin line beside it is the raw 200 ms samples, lightly smoothed and drawn as a curve on a fixed time axis, so it grows to the right rather than squeezing up as the run goes on. A run shorter than a second has too few samples to trim and reports them all. - **Why the upload line is spikier than the download line.** A write lands in the phone's send buffer at once and counts the moment it does; the next waits until that buffer drains. The raw samples therefore come in bursts, which the running mean on the dial evens out. - **Cloudflare limits repeated runs.** After several large runs from one address in a short time, Cloudflare refuses big downloads with HTTP 429 while still serving small ones. The test then asks for smaller pieces, 25 MB down to 1 MB, and the run goes on; only when even those are refused does it stop, saying that Cloudflare is limiting speed tests from this connection for now. Other refusals name the status and the phase. A LibreSpeed server has no such limit. - **Uploads on a slow line.** The upload body grows while requests finish quickly and shrinks when they take seconds, so a slow line is not stuck finishing one large post after the clock has run out; at the deadline every open connection is cut. - **Hardware shortcut.** There is no default letter. Reach it through the toolbox (T) or [assign one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/). --- # Text editing & cursor tools URL: https://wmkeyboard.pages.dev/tools/text-editing/ Section: Tools Summary: A cursor pad, selection helpers, and undo/redo: precision editing from the keyboard. Fixing a typo three lines up, selecting a whole paragraph, or nudging the cursor one character at a time is fiddly on a touchscreen with no physical arrow keys. WM Keyboard adds a dedicated text-editing panel, a set of one-tap cursor tools for the toolbar, and one-tap undo/redo. Together they stand in for the arrow keys and Ctrl-shortcuts a hardware keyboard would give you. tools/text-edit-panel — The text-editing panel: a d-pad for moving the cursor, Select/Select all/Copy/Paste on the right, and Home/End/Backspace along the bottom. ## Moving and selecting text Open **Text editing** from the toolbar, or from the toolbox if you haven't pinned it there (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/) for pinning and the toolbox in general). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press X. The panel is a d-pad cluster: ◀ and ▶ span the full height on either side, with ▲, a Select toggle, and ▼ stacked in the middle. A bottom row adds Home, End, and Backspace. Each of those keys sends the field a real key event, the same one a hardware arrow key would send, rather than a movement the keyboard computed itself. So the cursor behaves exactly as it would with a physical keyboard plugged in. Hold Home or End for the start or the end of the whole text. Those two are the exception: Ctrl+Home and Ctrl+End mean nothing to many apps, so the keyboard puts the cursor there itself, and falls back to the key press only in a field that hasn't said where its cursor is. With Select on, they select from the cursor to that end. Tap Select to turn selection mode on. The key lights up while it's active. With Select on, every arrow, Home, or End move extends the selection instead of just moving the caret. The panel wraps each move in a real Shift press rather than a background modifier flag, since some editors only pick up Shift state from the key events themselves. Tap Select again to turn it off, or see below for the two actions that flip it automatically. The toolbar's [Selection mode](#selection-mode) tool is the same mode from outside the panel: either one turns it on, either one turns it off, and both light up while it's on. The difference is how long it lasts. The panel's toggle ends when the panel does, and the tool's mode stays on until you press it again. Select answers the same three gestures the tool does. One tap toggles, two quick taps select the word at the cursor, three select the line, and a press and hold turns selection mode on only for as long as your finger stays down. Both extra gestures read the same two switches under Tools / Selection mode / Options the tool does, so turning either off turns it off on both surfaces. The one thing that overrides the hold is a long press you assigned yourself: give a Select key alternates in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and the hold opens those instead, which the editor warns you about as you bind them. Tapping is never affected. Once something is selected, the keyboard can offer what to do with it: [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) puts the edits, a find and replace, the case options, Call, Open, QR and a good deal more above the keys, depending on what you selected. That bar is off by default and lives under Advanced. Holding ◀, ▶, ▲, ▼, or Backspace auto-repeats after the keyboard's own key repeat delay, then keeps repeating at an interval you can tune (see [Options](#options) below). That start delay defaults to 300 ms and is adjustable from 150 to 3000 ms under Key press. ## Copying, pasting, and cutting The panel's second column holds Select all, Copy, and Paste. There's no Cut button in the panel: cutting lives elsewhere, as a press-and-hold letter shortcut (see [below](#press-and-hold-shortcuts)). - **Select all** selects the whole field and doesn't touch selection mode either way, so a follow-up arrow key moves the caret unless selection mode was already on for some other reason. It's also a toolbar tool of its own (see [below](#one-tap-cursor-tools-for-the-toolbar)), so it needn't cost a panel. - **Copy** copies the current selection and turns selection mode back off. With nothing selected it copies the whole text, the same as holding C. - **Paste** inserts the clipboard content at the cursor (replacing any selection) and doesn't touch selection mode either way. All three also exist as their own toolbar tools, **Copy**, **Cut** and **Paste**, under the **Quick actions** group in Tools. Pin any of them and the operation costs one tap with no panel to open first. They do exactly what the panel's keys do, including copy and cut ending selection mode. Like Copy, Cut takes the whole text when nothing is selected, the same as holding X. That goes for a layout's Cut key too, and for the keyboard's own boxes, such as a note or a search field. Copy and cut work [before you unlock the phone](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone); paste doesn't, because the clipboard isn't readable yet. Copy and paste run through the target field's own handling, the same as if you'd triggered them from that app's text-selection menu. What exactly gets copied, and whether a "Copied" system toast appears, therefore follows the app you're typing into. WM Keyboard can add its own **"Copied"** confirmation on top, off by default, under Clipboard / Panel (see [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/)). Pasting into a password field clears the system clipboard afterward by default, so a copied password doesn't linger (also configured on the [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/) page, not here). ## One-tap cursor tools for the toolbar The panel above costs one tap to open and another to act. If you reach for the same cursor move often, pin its toolbar tool instead and skip the panel entirely. They call the same underlying handler, so the move itself is identical either way. The toolbar adds two things the panel has no room for: Shift extends the selection, and a hold repeats the move. | Tool | What it does | |---|---| | Cursor left / Cursor right | Move the cursor one character | | Word left / Word right | Jump a whole word (Ctrl+Arrow, or where you set a word to end) | | Cursor up / Cursor down | Move the cursor a line | | Line start / Line end | Jump to the start or end of the line | | Page up / Page down | Scroll the cursor a page | | Select word | Select the word at the cursor; press again to take the words beside it too | | Select line | Select the entire line at the cursor | | Select all | Select the whole field | | Selection mode | Every cursor move selects text until you turn it back off | **Select all** is the panel's Select all key on the toolbar: it selects the whole field and leaves selection mode where it found it. **Select word** and **Select line** don't send Ctrl+Shift+Arrow. The keyboard works out the word or line boundaries around the cursor itself and sets the selection directly. Both turn selection mode on afterward. Both do nothing if the cursor sits between spaces or on an empty line. A cursor against marks with no word in reach, as in `example@|`, gets **Select word**'s next best thing: the whole run between the spaces around it, `example@`. Press **Select word** again while its selection is still there and the selection grows, the parts touching it first and anything past a space last. Each press takes the next ring out: across `-` and `_`, then across `.`, then across a single `/` (never the `//` of `https://`), then the whole run between two spaces, then a word past the space on each side, and on from there. With the cursor in `git|hub.com` inside `https://github.com/wasi-master/wmkeyboard`, the presses select `github`, `github.com`, `github.com/wasi-master/wmkeyboard` and then the whole link. With Shift up, a press goes back one step instead, down to the first word. While **Shift changes the case of the selection** is on, pressing Shift with text selected changes its case, so turn that off to use Shift this way. ### Where a word ends Out of the box, **Word left** and **Word right** send Ctrl and an arrow key, and the app decides what a word is. Most apps treat `wmkeyboard.pages.dev` as one word and `GrapheneOS` as one word, so the cursor cannot stop at `wmkeyboard.|`. **Word steps**, on the settings page of each of the three word tools, lets the keyboard measure the steps itself: - **As the app decides**: Ctrl and an arrow key, as before. - **At spaces**: the cursor stops only at spaces. - **At spaces and punctuation**: the cursor also stops between letters and marks, so `wmkeyboard.pages.dev` is five steps. An underscore is a mark too, so `wordtwo_wordthree` is three. With either of the last two, **Stop at capitals** also splits a word at a capital inside it: `GrapheneOS` is `Graphene` and `OS`, and `HTMLParser` is `HTML` and `Parser`. **Stop at numbers** splits letters from digits, so `word1` is `word` and `1`. **Select word** takes its word by the same rules. A field that does not say where its cursor is gets the Ctrl and arrow key anyway. The delete keys have their own switches for the same thing, on the [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/#timing) screen. ### Hold Shift to select Tap Shift before a cursor tool, or before an arrow key on the keys themselves, and the move extends the selection instead of collapsing it, the way Shift+Arrow does on a physical keyboard. Shift lock works too, which is the easier one for dragging a selection out over several taps. Shift isn't spent by the move, so it stays on and every following tap keeps extending. To stop, tap Shift again: that puts it back down and leaves the selection where it is. A cut or a paste puts Shift down for you, lock included, the way you let go of Shift on a physical keyboard once the shortcut is done, and so does leaving the letters for the symbols or the number pad. The cursor keys after it move the cursor again instead of selecting. One more Shift tap after that, with the selection still standing, re-cases it (see [Details & edge cases](#details--edge-cases)). Only a Shift you pressed yourself counts. The keyboard arms Shift by itself at the start of a sentence, and an ALL CAPS field arms the lock, so reading those would turn an ordinary cursor tap into a selection you never asked for and your next keystroke would type over it. Auto-capitalize leaves your Shift alone while a selection is live, so the second tap extends the selection instead of dropping it. A held or locked Shift reaches every cursor move you make from the keyboard itself, not only the toolbar's tools: the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/) on both axes, the [volume keys](https://wmkeyboard.pages.dev/typing/gestures/) when they're set to move the cursor, and a layout's own arrow keys. Lock Shift and scrub the spacebar and you're dragging a selection out with one thumb each. In a one-line field such as a search box, Shift with up or down selects to the start or the end of the text, the way a desktop text box does, rather than stepping into the box's suggestions or out of the field. A box the app marks as holding several lines, or one that already has a line break in it, keeps its real Shift with up or down, so the selection moves a line at a time there, Firefox's text boxes included. A selection grown up or to the left keeps growing that way: the end you started from stays put. The text-editing panel as shipped has no Shift key, so its own Select toggle is the answer there, unless you put a Shift key on the panel in its [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts), where it works exactly like this. ### Selection mode **Selection mode** is the toolbar's version of holding Shift down, without a hand tied up doing it. Press it once and it stays on: from then on every cursor move extends the selection instead of collapsing it. Press it again to turn it off. The tool lights up while it's on, and so does the panel's own Select key, because they're the same mode seen from two places. *Every* cursor move obeys it, not just the toolbar's: - the arrows, Home and End in the text-editing panel - the one-tap cursor tools above - the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/), including its 2-D mode - the [volume keys](https://wmkeyboard.pages.dev/typing/gestures/), when you've set them to move the cursor - arrow keys on the keyboard itself, on a layout that has them That's the point of a mode over a held Shift: whichever hand you'd normally move the cursor with is free to do it, and navigating never cancels the selection you're building. Turning it off keeps the selection. Only the *extending* stops, so what you've selected is still there to copy, cut, or type over. Two more gestures on the same button: - **Press and hold** for a temporary mode. Selection mode is on for as long as your finger stays down and off the moment you let go, again leaving the selection intact. Good for one quick selection when you don't want to remember to switch the mode back off. It's on by default and can be turned off under [Options](#options). - **Press twice quickly** to select the word at the cursor, or three times to select the line. Both leave the mode on, so you can widen the selection from there with any cursor move. The first press of a pair still toggles the mode, exactly the way the first tap of a Shift double-tap still arms Shift. If that trips you up, the shortcut is switchable off. Selection mode ends by itself when you move to a different text field, and a press and hold ends when the keyboard closes. It survives everything else: panels opening and closing, layout switches, and any amount of cursor movement. ### Hold to repeat Holding a cursor tool on the toolbar keeps the move going for as long as your finger is down, at the same repeat interval the panel uses. It saves a tap per character when you're crossing a paragraph. Eight tools repeat: **Cursor left**, **Cursor right**, **Word left**, **Word right**, **Cursor up**, **Cursor down**, **Page up** and **Page down**. **Line start**, **Line end**, **Select word**, **Select line** and **Select all** don't, because a second press of any of those lands exactly where the first one did. **Selection mode** doesn't either: its hold is the temporary mode described above. The toolbox grid can do it too, but that one is off until you ask for it, one tool at a time. See [Options](#options) for why. Two things a hold does on every *other* tool are worth knowing about here: - **Hold and drag still moves the tool.** Once your finger travels far enough to read as a drag, the repeat stops and the tool picks itself up as usual. The moves it already made stay, so a deliberate drag costs you a caret move or two. Move the caret back afterward. - **Hold opens a tool's settings page.** Repeat spends that gesture on whichever surface it's turned on for. With the toolbar switch on and the toolbox one off, which is how it ships, a toolbox hold still opens any tool's page. Turn a tool's toolbox switch on as well and that tool's page is only reachable under Tools. A **Hide keyboard** tool (one tap dismisses the keyboard) shares the **Cursor** section with these on the Tools screen, but it isn't a cursor move itself. None of the cursor tools is pinned to the toolbar out of the box, and Text editing is pinned only on a large tablet. A phone's default row is Emoji, Clipboard and Settings, a small tablet adds GIF and Voice, and a large tablet adds Translate and Text edit on top of those. A pinned tool that setup left switched off is dropped from the row rather than drawn dead, so the "keep it simple" answer on a large tablet gets five of the seven. Everything else waits in the toolbox. The onboarding answer matters too. "Recommended" turns on Text editing, Undo and Redo, but leaves out the numpad, Hide keyboard and every cursor tool above. If you don't see one, open Tools and turn it on. ## Undo and redo The toolbar's **Undo** and **Redo** tools fire immediately, with no panel: one tap sends the field's own undo or redo keyboard shortcut. Undo always sends Ctrl+Z. Redo sends Ctrl+Shift+Z by default, or Ctrl+Y if you flip the [redo shortcut setting](#options). This isn't a typing history WM Keyboard keeps of its own. It has no way to reach into another app's private undo stack. It sends the same shortcut a hardware keyboard would, and relies entirely on the app you're typing in supporting it: an editor with no undo shortcut just ignores the tap. ## The numeric keypad **Numpad** opens a dedicated 4×4 number pad over whatever field you're in. Don't confuse it with the keyboard switching to a numeric layout on its own, which happens in a field already typed as a phone or number field. Numpad is a panel you open by hand, and it works over any field, numeric or not. There are several ways to open it: the Numpad tool, a press and hold on ?123, a spacebar swipe set to Numpad, a 1234 key on the symbol pages (**1234 key on the symbol pages** under Layout & size / Bottom row keys, off by default), or a Numpad key you add to your own layout in the editor. tools/numpad-panel — The numpad panel: a 4×4 grid in phone or calculator digit order, with Backspace repeating on hold. It comes in two layouts, switched under [Options](#options): - **Phone-style** (default): `1 2 3 ⌫` / `4 5 6 +` / `7 8 9 −` / `. 0 , ⏎` - **Calculator-style**: `7 8 9 ⌫` / `4 5 6 +` / `1 2 3 −` / `. 0 , ⏎` Backspace repeats on hold like the rest of the keyboard, and Enter sends a normal Enter key. ## The quick alternative: spacebar cursor swipe You don't have to open a panel at all for the most common move: sliding the cursor a short distance. By default, a long swipe on the spacebar moves the cursor (a short swipe switches language instead), and there's an optional 2-D touchpad mode that adds vertical line movement to the same gesture. Full mechanics, including how to reassign what each swipe length does, live on [Gestures](https://wmkeyboard.pages.dev/typing/gestures/). ## Options ### Text-edit panel repeat speed Tools / Text editing / Options **Key repeat interval**: default **60 ms**, adjustable from 30–200 ms. It sets the pause between repeats while you hold an arrow key in the panel, and while you hold one of the toolbar's cursor tools. The panel's two delete keys are the exception: they repeat at the keyboard's own **Key repeat interval for delete** under Key press, because they *are* the delete keys (see below). Lower is faster. The delay before repeating starts is the keyboard's own key repeat delay in the panel, and the system's long-press timeout on the toolbar. ### Panel layout Tools / Text editing / Panel layout The panel's grid is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts): the same editor, the same key sheet and the same undo as a key layout. The row reads **Standard** until you change something, then **Your own**. Every key on the panel is a key whose action is a **text editing action**, one of twenty-five: the four arrows, word left and right, page up and down, start and end of the line, start and end of the text, Select, Select all, Select word, Select line, Cut, Copy, Paste, Delete, Forward delete, Delete word, Delete word forward, [Retype word](https://wmkeyboard.pages.dev/smart/suggestions/#retyping-the-last-word) and [No space](https://wmkeyboard.pages.dev/typing/glide-typing/#the-space-after-a-glided-word). Holding a No space key arms it for the next key, so a key needs no second action for that. Delete word and Delete word forward take the word before or after the cursor in one press, the way Ctrl+Backspace and Ctrl+Delete do on a computer, and find the end of a word the same way a [word swipe on the delete key](https://wmkeyboard.pages.dev/typing/gestures/#deleting-more-than-a-character-with-backspace) does. Pick a key in the preview and its settings open under it, where you can change its action, its width in grid units, how many rows it covers, and its alternates for press and hold. You can also put ordinary keys on the pad: Enter, a tool key, or anything else the Action picker offers. **Press and hold only works on keys that don't repeat.** The four arrows, word left and right, page up and down, the two deletes and the two word deletes all repeat while you hold them, at the repeat interval above, and that's the point of holding them, so the sheet doesn't offer alternates on those. Everything else (Home, End, start and end of the text, Select, the selects, Cut, Copy, Paste) has a hold going spare, and that's where an alternate goes. Holding such a key opens the alternates popup, the way it does on any key of the keyboard, and sliding onto the entry and letting go runs it. **The pad's Delete and Forward delete are the keyboard's own delete keys.** They carry every gesture the key on the letters row has: a swipe takes words or characters with the same select-first preview, **Holding delete clears words** applies, and each stops at the end of the text it eats into. They use the delete key's repeat interval rather than the panel's for the same reason. Delete takes what is behind the cursor; Forward delete takes what is ahead of it, and its swipe runs rightward. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/#deleting-more-than-a-character-with-backspace) for the settings that shape both. Two of those pairings ship: **Home** and **End** hold to the start and end of the *text* rather than the line. Widths and spans mean exactly what they mean in a key layout, and the panel places them with the same geometry the keyboard draws its keys with, so the tall left and right arrows in the standard arrangement are simply a key three rows deep. The pad has no component cell, so its rows share the height of the key area by their row heights. **Use the standard layout** at the bottom of the editor puts the shipped layout back. A layout you made on an earlier version, when the pad had an editor of its own, carries over the first time this version reads it, key for key. ### Hold a cursor tool to repeat Tools / Cursor left / Options **Hold to repeat on the toolbar**: on by default. Holding one of the eight repeating cursor tools on the toolbar keeps moving the cursor. Turn it off and a hold there does the tool's [press and hold action](https://wmkeyboard.pages.dev/tools/overview/#hold-a-tool-for-a-second-action) instead: out of the box a word left or right for the arrows, page up or down for up and down, and the start or end of the line for the word moves. Page up and down hold to their settings page. It's one switch shown on all eight of their settings screens, so flipping it on any one of them flips it everywhere. **Hold to repeat in the toolbox**: off by default, and this one is per tool. Turn it on and holding that tool in the toolbox grid repeats the move as well. It's per tool, and off to start with, because of what it costs. A hold in the toolbox is how you reach any tool's settings page from the keyboard. A tool that repeats there has spent that gesture, so its own page is then only reachable under Tools. That's a fair trade for the one or two moves you actually hold down and a bad one across the whole grid, which is why it's answered a tool at a time rather than all at once. The **Key repeat interval** slider sits under both, the same slider the text-editing panel uses, and it shows as soon as either surface repeats. Dragging a tool to move it survives both switches on both surfaces. ### Selection mode gestures Tools / Selection mode / Options **Press and hold to select**: on by default. Holding the tool on the toolbar turns selection mode on until you let go. Turn it off and a hold there opens the tool's settings page instead, the way a hold on any other toolbar tool does. **Two presses select a word**: on by default. Two quick presses select the word at the cursor and three select the line, on the same 350 ms window the Shift key uses for caps lock. Turn it off if you flip the mode on and off faster than that and don't want a word selected on the way past. Both switches cover the panel's Select key as well as the toolbar tool. The two buttons count their taps separately, so alternating between them never reads as a double tap. Dragging the tool to move it works with either switch on: once your finger travels far enough to read as a drag, the mode ends and the tool picks itself up as usual. ### Numpad layout Tools / Numpad / Options **Calculator-style layout**: off by default. It puts 7 8 9 on the top row like a desk keypad instead of 1 2 3 like a dialer. It only reaches the shipped pad: a pad you laid out yourself is drawn as you laid it out. **Panel layout**: the pad is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the text editing pad, so it has a shared grid under Key layouts → Panel layouts, a tab in every key layout's editor for a grid of that layout's own, and with them a theme, a "keep this layer open" switch, press-and-hold alternates on its keys, and any key the Action picker offers. What the keyboard draws, in order: the typing layout's own Numpad tab, the shared Numpad layout if you changed it, the layout's own **Number** layer if it authored one (key for key, in the order it drew them), and last the shipped pad in the digit order the switch above asks for. ### Undo/redo shortcut Tools / Undo / Options **Redo sends Ctrl+Y**: off by default, so Redo sends Ctrl+Shift+Z. The same toggle appears on both the Undo and Redo tool's own settings screen. Both are standard redo shortcuts, and which one an app responds to varies. If redo does nothing, flip this. ### Press and hold shortcuts Key press / Press and hold shortcuts One **Hold shortcuts** row with six chips, all on by default. Each one puts its command at the end of that letter key's press-and-hold popup, after the accents: - **Select all** - **Copy**: copies the current selection. If nothing is selected, it selects everything first. - **Cut**: the same rule as copy, but it cuts. - **Paste**: pastes the clipboard at the cursor, replacing any selection. - **Undo**: the same shortcut as the toolbar's Undo tool. - **Redo**: the same shortcut as the toolbar's Redo tool. A **Shortcut keys** row sits under them. A, C, V, X, Z and Y are the QWERTY answer, so on a layout with no Latin letters that row is where you move the six onto keys you actually have. This is the only place cut lives: there's no Cut button in the text-editing panel itself, and no Cut toolbar tool. ## Details & edge cases - **Shift on a live selection re-cases it, and the keys follow.** Lower case puts Shift down, Title case arms it, UPPER turns the lock on, so the case you just gave the selection is the case the next thing you type comes out in. There is no need to unselect, press Shift again and reselect. It re-cases rather than switching Shift off, with one exception: a Shift that's currently holding the selection open. There the first tap is read as putting the modifier down, so a selection you dragged out with Shift can always be ended with the same key; the tap after that re-cases. That's the "Tapping Shift with text selected cycles its case" toggle in the next bullet, on by default; turn it off and Shift goes back to being a plain Shift. - **Shift can change the case of a word with nothing selected.** Two switches sit under the selection one on the Typing screen, both off by default. With **Shift changes the case of the word before the cursor** on, Shift with the cursor right after a word steps that word through lowercase, Title Case and UPPERCASE, the way SwiftKey does: `cursor| touching`. **Shift changes the case of the word around the cursor** does the same with the cursor inside a word: `cur|sor`. A word that only starts at the cursor is left alone. The cursor stays where it was, and the Shift key follows the word's case, so UPPERCASE leaves caps lock on for what you type next. While you type a word, Shift is still for the next letter. - **A case change never changes the spelling.** UPPER CASE turns `ß` into the capital `ẞ`, not `SS`, so `Spaß` goes to `SPAẞ` and back down to `Spaß` again. The same holds for a word glided with caps lock on or shouted through the Shift key. A letter with no single capital form, such as the `ff` ligature, stays as it is. - **A selection the keyboard sets is brought into view.** Find on the selection bar, and the Find & replace panel, select the match in the field. Many editors leave a selection set from outside where it is, under the keyboard or off the screen, so the keyboard nudges the selection a step and back, which makes the editor scroll to it. The same nudge puts the cursor back on screen when the keyboard grows under it, for example when the suggestion strip opens for a word you tapped. Only a plain cursor gets it, and only once for the same spot: a selection you made yourself, Select all included, is left exactly as it is when the selection bar opens under it, and in a web page the selection bar doesn't try to scroll it either. - **Two Typing-screen toggles change what Select mode does elsewhere on the keyboard**, even though neither is on this page's own settings screens. Typing a bracket or quote with text selected wraps the selection in the pair instead of replacing it (on by default). Either half works: `(` and `)` both turn a selected "abc" into "(abc)". Tapping Shift with text selected cycles its case instead of arming Shift for the next letter (also on by default, and a no-op in caseless scripts like Bengali). Both live under Typing / Automatic corrections. - **Every tool on this page works before you've unlocked your device for the first time after a restart** (direct boot). The panel, the cursor tools, undo/redo and the numpad only touch the input connection, never stored data, so none of them wait on your device being unlocked. Compare [Media controls](https://wmkeyboard.pages.dev/tools/media/), which can't. - **Nothing here is build-gated.** The panel, every cursor tool, undo/redo, and the numpad all work identically in every build. - The hardware [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/) reaches Text editing (X) and Numpad (N) as panels, and fires Undo (Z) directly. There's no leader-key letter for Redo or for any individual cursor tool. - All copy and paste actions here go through the target app's own text-selection handling, not a WM-Keyboard-side clipboard bypass (see [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/) for the separate clipboard history panel these actions feed into). --- # To-do list URL: https://wmkeyboard.pages.dev/tools/todo/ Section: Tools Summary: A to-do list inside the keyboard: add and tick tasks, open a task to its steps, edit with a long press, and sync with a CalDAV task list through DAVx5 if you want to. The **To-do list** tool keeps your tasks in the keyboard, a tap away from whatever app you are writing in. Each task has a box to tick and can hold steps under it: "Plan the trip" with "Book the train" and "Pack" below, each ticked on its own. The same list is in the settings app, where there is room to pick any date. Both write one file, so a task added on the keyboard is in the app when you get there, and the other way round. If you already keep your tasks on a CalDAV server, such as Nextcloud, the list can sync with one of its task lists through DAVx5. That is off until you set it up; see [Sync with a task app](#sync-with-a-task-app). ## Opening it Tap **To-do list** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. Typing `todo` or `tasks` into a text field offers the tool as a chip on the suggestion strip, like every other tool keyword. In the settings app the list is one tap from the tool's own page: Tools / To-do list / Your tasks **Add to home screen**, under it, puts an icon for the list on your home screen, which opens it straight away. ## The panel Each task is a card: a round box to tick at the left, the task's words, its date under them when it has one, and at the right a small ring with "2 of 5" for a task with steps, then an arrow. Cards arrive one after another as the panel opens and squash a little under your finger. A ticked task fades to struck-through and slides down below the open ones, where it sits dimmed. Anything overdue or due today leaves the column for a **Due** row at the top, where the tasks wait as wide cards you scroll sideways. A card due today is washed with the theme's accent, an overdue one with a warmer amber, each with a stripe of that colour along its edge and the date written in it. The row is there only while something is due. An **Add a task…** pill floats along the bottom. Tap it, or its **+**, to start a new task; it steps aside while the Undo bar is up. The header says what is left, **3 to do** or **All done**, and holds **Hide done** and **Clear done** once anything is ticked, the sync button when a task app is set up, and the tool's settings. ## Adding, ticking and deleting The **Add a task…** pill opens a box in the header, and the key rows come back underneath so you can type into it. Enter adds the task and leaves the box open and empty for the next one, so a whole shopping list goes in without a tap between items. **Done** adds what is in the box and gives the keys back to the app. Tap a task's box to tick it. The box springs shut, the words fade to struck-through, and the card sinks below the open ones. Ticking a task ticks the steps still open under it, and clearing a step's tick clears its task's tick too, since the task isn't finished after all. Press and hold a task to edit it. Its words go into the box in the header, and the card stays under it with a row of chips inside: the quick dates, then **Repeat**, **Delete**, **Type it** (which types the task into the field behind the keyboard), **Add a step** and **Cancel**. A tap on the card puts the edit away. The quick dates are **Later today** (at least three hours on, at the next full hour, while that is still today), **This evening** (18:00, until then), **Tomorrow** and **Next week** (09:00 tomorrow, and 09:00 next Monday), and **No date** on a task that has one. A tap keeps any change to the words too. A delete shows an Undo bar for a few seconds. **Clear done** in the header deletes every ticked task and step at once, with the same Undo. **Hide done** in the header leaves the ticked tasks out for this visit; Tools / To-do list / Hide finished tasks in the keyboard makes the panel always open that way. ## Steps Tap a task's card to open it. Its steps unfold inside the card, each with its own smaller box, and **Add a step** at the end opens the box for a new one, which Enter adds like a task. A task with steps shows a ring of how many are ticked, "2 of 5", and the arrow turns while it is open. Tap the card again to fold it shut. A card in the Due row opens the same way. Steps go one level deep: a step can't hold steps of its own. ## The list order Tasks overdue or due today sit in the Due row. Below it, open tasks come first, the soonest due at the top and the ones with no date after them, newest first. Ticked tasks follow, the one ticked last at the top. Steps stay in the order they were added. ## In the settings app The list in the app works the same way with a phone-sized touch. The field at the top adds a task. Tap a task to open its steps and the **Add a step** row; tap its box to tick it. Hold a task, or a step, to edit its words, give it a due date or take the date off, or delete it. Swipe a row to the left to delete it; a long swipe deletes it at once, with an Undo. **Clear finished tasks** under the list does what the panel's Clear done does. A date picked in the app is a day; one from the keyboard's quick dates, or from a task app, has a time as well. The keyboard shows it under the task, in the accent colour on the day and in amber once it has passed. ## Reminders An open task with a date reminds you when it falls due: a notification with the task's words and a **Done** button that ticks it. A task with a time reminds at that time; a day-only one at 09:00 that day. Tapping the notification opens the tool's page in the settings app. A date moved later reminds again, a ticked task never does, and a date already past when it was set (an old one from a task app) stays quiet. Reminders need the keyboard's notifications allowed. To turn them off, or change how they sound, use the **To-do reminders** channel in the system's notification settings for WM Keyboard. Android 12 and later may deliver one a few minutes late unless the app is allowed to set exact alarms, the same grant the [clock's alarms](https://wmkeyboard.pages.dev/tools/clock/) ask for. After a restart they come back once the phone is unlocked, and one that fell due while it was off shows then. **On the suggestion strip.** A task that has fallen due also waits on the suggestion strip, once the keyboard next comes up. Tap its words to open the list. The tick marks it done, the clock puts it off for an hour, and the cross closes the chip until the task has a new date. Tools / To-do list / Show due tasks on the suggestion strip turns it off. ## Repeating tasks Hold a task and tap **Repeat** to step through every 30 minutes, every hour, day, week, month and year, then off. A task with no date yet is first due one step from now. Ticking a repeating task doesn't finish it: its date moves to the next time and its steps open again. Until you tick it, it reminds you each time it comes round. Repeats stay on this phone's list. A task app you sync with sees only the task's current date. ## Sync with a task app Tools / To-do list / Sync with a task app Off by default. On, the list stays the same as one task list in **OpenTasks** or **Tasks.org** on the phone, and DAVx5 syncs that task list with your CalDAV server. WM Keyboard never connects to the server itself. It reads and writes the task app's list on the phone, and DAVx5 carries the changes on its own schedule. To set it up: 1. Install DAVx5 and either OpenTasks or Tasks.org, and add your CalDAV account in DAVx5 with its task lists ticked. 2. Turn on **Sync with a task app**. A dialog lists the task apps on the phone. 3. Tap **Allow access** for the app you use. The keyboard says what the permission covers before Android asks: the permission reaches every task list in that app, and the keyboard opens only the one you pick. 4. Pick the task list. The first sync runs straight away. After that the keyboard syncs when the panel opens and a moment after each change you make, and the sync button in the panel's header runs one on demand. **Task list** on the tool's page changes the list, and **Sync now** runs a sync and says how many tasks came in, went out and were removed. What travels, both ways: each task's words, its tick, its due date, and which task a step belongs to. A delete on either side deletes on the other. When the same task changed on both sides since the last sync, the later change wins. A reminder set in the task app stays in the task app. Turning the sync off leaves both lists as they are. **Delete all tasks** on the tool's page empties only the keyboard's list: the task app keeps its tasks, and the next sync brings them back. **Remove copied tasks**, under **Sync now**, deletes every task that repeats an older one word for word, with the same tick and due date, and keeps the oldest of each. The copies go from the task app too, and DAVx5 deletes them from your server on its next run. Up to 0.6.5, a task added in the keyboard could be sent to the task app again on every sync, so a list could fill up with copies of the same tasks; this cleans that up, and the sync no longer makes them. jtx Board, the third task app DAVx5 works with, isn't supported. Its provider only answers apps that say they are a sync adapter, and a change written that way is marked as already synced, so DAVx5 would never send it to the server. ## Coming from the Todo List plugin The tool replaces the Todo List plugin from the add-on repository. If the plugin is installed and holds tasks, the tool's page shows: Tools / To-do list / Import from the Todo List plugin It copies the plugin's tasks, their ticks and their due dates into the list, and skips any task already there with the same words, so running it twice doesn't double anything. The plugin keeps its own copy and its reminders, and keeps working until you remove it. ## Backups The to-do list is a section of the [backup file](https://wmkeyboard.pages.dev/privacy/backup/), on by default for the automatic backup and for sync between your phones. Every task and step travels in it, ticked or not. A restore replaces the list, and a keyboard already running reloads it rather than saving its own copy back over it. The task-app sync keeps a small record of its own on the phone, which never goes into a backup: it names rows of this phone's task app, which mean nothing on another one. ## Privacy The list stays on the phone unless you back it up or turn on the task-app sync, and then it goes only where those send it. The keyboard never learns from a task: what you type into the box does not reach your personal dictionary, your word pairs or your typing statistics. The tool is not available before the first unlock after a restart, when the keyboard has no access to its files. --- # Trackpad URL: https://wmkeyboard.pages.dev/tools/trackpad/ Section: Tools Summary: Turn the key area into a trackpad: drag to move the cursor, hold and drag to select, two fingers for words. Placing the cursor by tapping the text is the least precise thing a touchscreen asks of you. The **Trackpad** tool takes the key area over and turns it into a laptop-style pad: put a finger down anywhere and the cursor moves by how far you drag, a character at a time sideways and a line at a time up or down. Hold first and the same drag selects. It is the same idea as the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/), with the whole keyboard to move across instead of one key. tools/trackpad-panel — The trackpad panel: the pointing surface over an abc / space / backspace row. The trail and crosshair follow your finger. ## Opening it Tap **Trackpad** on the toolbar, or in the toolbox if you haven't pinned it (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). The tool lights up while the panel is open, and tapping it again, or the abc key on the panel's bottom row, puts the keys back. **Press and hold** the tool instead and the trackpad is open only for as long as your finger stays down. Drag on it with your other thumb, let go of the tool, and you are back on the keys. This is the one for a quick nudge in the middle of typing. A hold over a trackpad you already opened with a tap does nothing, so that panel stays put when you let go. ## Gestures | Gesture | What it does | |---|---| | One finger drag | Move the cursor. Sideways is characters, up and down is lines | | Hold, then drag | Select. Selection mode is on until you lift the finger | | Two taps | Select the word at the cursor | | Three taps | Select the whole line at the cursor | | Two finger drag | Move the cursor a word at a time | | Two finger tap | Type a space | The pad is relative. Where you put your finger down doesn't matter, only how far it travels from there, so you can start a drag anywhere on the surface, lift, and start again. A slow drag that covers less than a step per frame still adds up: the surface keeps the fraction and fires the step once the travel reaches it, and dragging back the way you came unwinds that fraction rather than firing a move. Every move is a real arrow key sent to the field, the same event a hardware keyboard would send, through the same handler the [text-editing panel](https://wmkeyboard.pages.dev/tools/text-editing/) uses. That has two consequences. The cursor behaves exactly as the app you're typing in wants it to, and the trackpad obeys whatever selection mode is armed, from wherever it was armed. Turn on the toolbar's [Selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) tool, or the panel's Select key, and every trackpad drag extends the selection until you turn it off again. **Hold, then drag** is the short form of that. The hold arms selection mode, the drag extends the selection, and lifting the finger disarms the mode. What you selected stays selected: the selection lives in the editor and nothing on the keyboard collapses it. Move the cursor again with a plain drag and it collapses, the way an arrow key collapses a selection anywhere else. A word or line picked up by two or three taps also stays until the next plain move, so tap twice and then hold-drag to grow the selection from the word outward. **Two fingers** move by words: Ctrl+Arrow to the field, in the direction you drag. Both fingers down and up together with no travel is a space, so you can separate words without leaving the pad. A third finger counts as two. ## Visual feedback The tool's button is lit on the toolbar while the panel is open, however it was opened. On the surface, a trail follows your finger and a crosshair marks where it is now; both go the moment you lift. The trail can be turned off under [Options](#options). A faint hint in the middle of the surface fades while you are touching it. While you drag, a **magnifier** shows the line of text around the cursor, enlarged, in a bubble over the text, with the cursor in its middle. It comes up when a drag starts or a hold arms selection, and it follows the cursor from line to line. Any selection shows highlighted in it, and the cursor in it is the end of the selection that is moving. A plain tap never shows it. The bubble is the keyboard's own drawing of the text the app hands it, not a zoomed picture of the screen, so it uses the keyboard's colours and font. It sits above the cursor, or below it when the cursor is too close to the top of the screen, for apps that tell the keyboard where their cursor is, which most do. For an app that doesn't, or when the cursor is scrolled out of sight or hidden behind the keyboard, it sits just above the keyboard instead. In a password field it shows dots, as the field does, never the password itself. With a long selection (more than about a thousand characters, such as after Select all) it shows the text past the moving end, with the selected side tinted, rather than reading the whole selection back on every step. It never takes a touch and it goes the moment you lift your finger. ## The panel is a layout The trackpad is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the emoji, clipboard and text-editing panels. Out of the box it is the pointing surface over an abc, space and ⌫ row. Open the layout editor from the tool's page (or from **Panel layouts** in the layouts gallery) to change the row, add keys beside the surface, give the surface more or less height, or put a text-editing key on it. The surface itself is the one component the layout cannot lose: a layout without it gets one added back. ## Options Tools / Trackpad / Options **Horizontal sensitivity**: default **12 dp**, adjustable from 4 to 48. How far your finger travels for each character. Lower is faster. **Vertical sensitivity**: default **28 dp**, adjustable from 8 to 96. How far your finger travels for each line. It starts coarser than the horizontal step on purpose: a line is a bigger jump than a character, and a finger dragging sideways drifts up and down more than it means to. Two fingers move a word for every two horizontal steps of travel. **Press and hold to open**: on by default. Holding the tool on the toolbar opens the trackpad until you let go. Turn it off and a hold there opens the tool's settings page instead, the way a hold on any other toolbar tool does, or runs whatever second action you bound to it. Dragging the tool to move it works either way: once your finger travels far enough to read as a drag, the panel closes and the tool picks itself up as usual. While the toolbox is open the hold does not open the trackpad at all, so the toolbox stays on screen and you can drag the tool off the toolbar and back into the grid. A still hold there opens the tool's settings page. **Two taps select a word**: on by default. Two quick taps on the surface select the word at the cursor and three select the line, on the system's double-tap window and within a thumb's width of each other. Turn it off if your drags tend to start with a tap you didn't mean. **Vibrate on each move**: on by default. A short tick for every character or line the cursor moves. **Show the trail**: on by default. Draws your finger's path and a crosshair while you drag. **Magnifier**: on by default. Shows the text at the cursor, enlarged, while you drag (see [Visual feedback](#visual-feedback)). Turn off **Follow the text** under the spacebar's **Cursor magnifier** and it stays still above the keyboard instead of moving with the cursor. The [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/#moving-the-cursor-with-a-spacebar-swipe) has a separate switch for the same bubble. **Panel layout** opens the layout editor described above. ## Details and edge cases - The trackpad is a panel, so opening it closes whatever panel was open and puts down any pending shift, Ctrl or Alt. - The bottom row's space is an ordinary spacebar, swipes included, so the language switch and the spacebar cursor swipe still work from inside the panel. - The panel never goes full-bleed, because the toolbar has to stay for the hold gesture that may have opened it. - With a hardware keyboard, the panel has no focus ring: it is a pointing surface with nothing to arrow between, like the handwriting canvas. Bind a leader letter to open it under Typing / Physical keyboard; the tool ships without one because every letter is taken. - The trackpad works before the first unlock after a restart, since it only touches the field it is typing into. - If the keyboard closes while you are holding the tool, the hold ends with it; there is no release to wait for. --- # Transcription server URL: https://wmkeyboard.pages.dev/tools/transcription-server/ Section: Tools Summary: Dictate through a speech-to-text server you run: speaches, whisper.cpp, or any OpenAI-compatible service. The transcription server is another dictation engine, next to the system recognizer, [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/), [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/), [offline Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), [offline Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [offline Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/). The keyboard records what you say and sends the recording to a server you choose. The server sends back the text. The heavy work runs on a computer on your desk or in your rack, not on your phone's battery, and you choose the model. It works in every build, F-Droid's included. The phone needs only a small library (about 300 KB) that compresses each recording to about a twelfth of its size before it is sent, fetched the first time you dictate with this engine. Until it is there, recordings go uncompressed, and dictation works the same. See [How much it sends](#how-much-it-sends). ## Setting it up Voice typing 1. Under **Recognition engine**, press **Add a provider** and pick **Transcription server**. 2. In the sheet that opens, fill in **Server address**. For a server on your own network it looks like `http://192.168.1.10:8000/v1`. An address has no spaces, so if the field turns red and says so, something was pasted along with the address, such as the line of a provider's docs that follows it. Remove everything after the address. 3. Fill in **Model** if your server needs one (see the recipes below). Fill in **API key** if it asks for one, and **Endpoint path** for a server on a route of its own. 4. Press **Test**. The keyboard sends one second of silence and says whether the server answered. If it fails, it shows the same message a failed dictation would show. Then press **Add**. 5. Drag the server to the top of the list to make it the engine for every language, or give it only some languages under **Engine for each language**. You can add more than one server, each with its own address, key and model, and switch one off without losing it. Dictation sends to the first server on the list that is on. Dictation then works the way it does with offline Whisper. Speak, then press the microphone when you are done (or let the 30-second clip fill, or set **Stop after a pause** under Voice typing and let a pause end it). The phrase lands a moment later, with [spoken punctuation](https://wmkeyboard.pages.dev/tools/voice/), spacing and undo applied as usual. **Keep listening** chains the next phrase the same way. Press a server's row to open its screen: **Setup** reopens the sheet, and **Send the keyboard language**, the prompt and the server languages are there too. **Send the keyboard language** is on by default. It tells the server the language of your active layout, which is faster and more accurate than letting it guess from a short clip. Turn it off if you dictate in two languages on one layout and want the server to detect which one you are speaking. With a [Voice typing language](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) picked, the server is told that language instead, and with several picked it's told none and detects it. ## The address Type the server's API root, up to and including the version segment, the same way the AI tool's [Other service](https://wmkeyboard.pages.dev/tools/ai/#any-other-openai-compatible-service) asks for it. The keyboard adds `/audio/transcriptions` itself. | You type | The keyboard sends to | |---|---| | `http://192.168.1.10:8000/v1` | `http://192.168.1.10:8000/v1/audio/transcriptions` | | `http://192.168.1.10:8000` | `http://192.168.1.10:8000/v1/audio/transcriptions` (a bare address gets `/v1`) | | `https://api.groq.com/openai/v1` | `https://api.groq.com/openai/v1/audio/transcriptions` | | `http://192.168.1.10:8080/inference` | used as it is (whisper.cpp's own endpoint) | An address that already ends in `/transcriptions` or `/inference` is used as typed. ### A server with its own route Some servers take the same request on a route of their own, such as `POST /v1/transcribe`. Put that route in **Endpoint path**, under **Server address**. When it is filled in, the keyboard adds it to the address exactly as typed, with no guessing and no `/v1`: | Server address | Endpoint path | The keyboard sends to | |---|---|---| | `http://192.168.1.10:8000` | `/v1/transcribe` | `http://192.168.1.10:8000/v1/transcribe` | | `http://192.168.1.10:8000/v1` | `transcribe` | `http://192.168.1.10:8000/v1/transcribe` | | `http://192.168.1.10:8000` | `https://asr.example.com/api/stt` | `https://asr.example.com/api/stt` (a whole address replaces the server address) | Leave **Endpoint path** empty for the usual `/audio/transcriptions`. The request itself is the same either way; see [The protocol](#the-protocol). ## A model or server per language Some models are much better at one language than another. With two or more languages enabled, a **Per language** group appears in the **Transcription server** group under the engine list, with one row per language. Each row says what that language uses. Tap it to change it: - **Same server, another model.** Leave **Server address** empty and fill in **Model**. The language uses the main server (the first one on the list), its route and its key, with this model instead of the main one. Leave **Model** empty too and the language goes back to the main server. - **A server of its own.** Fill in **Server address**. **Endpoint path** and **API key** appear for that server. Nothing is borrowed from the main server: the main key is never sent to a different address, and an empty **Model** lets this server pick its own. **Test** in the dialog sends one second of silence to what the dialog holds, before you save. **Use main server** clears the language's entry. Dictation picks the server from the language of the layout you are typing on, the same way [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) picks a model. **Send the keyboard language**, **Prompt** and the words to listen for apply to every server. A language's own API key is left out of backups unless you choose to include API keys, like the main one. ## Running a server The keyboard does not use a protocol of its own. It speaks OpenAI's audio transcription API, which most speech-to-text servers already support. Any of these works. ### speaches (faster-whisper) [speaches](https://github.com/speaches-ai/speaches) runs faster-whisper behind an OpenAI-compatible API. On a machine with Docker: ```bash docker run --detach --publish 8000:8000 --name speaches \ --volume hf-hub-cache:/home/ubuntu/.cache/huggingface/hub \ ghcr.io/speaches-ai/speaches:latest-cpu ``` Use the `latest-cuda` image with `--gpus=all` on a machine with an NVIDIA GPU. speaches only runs models you have downloaded, so fetch one once: ```bash curl -X POST http://localhost:8000/v1/models/Systran/faster-whisper-small ``` Then set: - **Server address**: `http://:8000/v1` - **Model**: the id you downloaded, here `Systran/faster-whisper-small`. For English only, `Systran/faster-distil-whisper-small.en` is faster. ### whisper.cpp [whisper.cpp](https://github.com/ggml-org/whisper.cpp) ships `whisper-server`, a single binary with no Python. Download a model and start it on all interfaces: ```bash whisper-server -m models/ggml-base.bin -l auto --host 0.0.0.0 --port 8080 \ --inference-path /v1/audio/transcriptions ``` With `--inference-path` as above, set **Server address** to `http://:8080/v1`. Without it, point the keyboard at `http://:8080/inference`. Leave **Model** empty: whisper-server uses the model it was started with. `-l auto` matters when **Send the keyboard language** is off. Without it whisper-server assumes English. whisper-server reads WAV, MP3 and FLAC on its own. Add `--convert` (it needs `ffmpeg` on the computer) and it also reads the keyboard's compressed recordings, which are a twelfth of the size. Without it, the first phrase tells the keyboard so, and that server gets WAV from then on. An address ending in `/inference` always gets WAV. ### Cloud services The same engine reaches the hosted APIs, if you would rather pay per minute than run a machine: | Service | Server address | Model | |---|---|---| | Groq | `https://api.groq.com/openai/v1` | `whisper-large-v3-turbo` | | OpenAI | `https://api.openai.com/v1` | `whisper-1` or `gpt-4o-mini-transcribe` | Both need an **API key**. LocalAI and other OpenAI-compatible gateways work too. ## The protocol This is everything the keyboard sends and expects, so you can build or proxy a server of your own. **Request.** `POST ` with `Content-Type: multipart/form-data`, and these parts: | Part | Sent | Contents | |---|---|---| | `file` | always | the clip, up to 30 seconds, as `speech.ogg`, `audio/ogg`: Opus in Ogg, 16 kHz mono at about 20 kbit/s (about 75 KB for 30 seconds). As `speech.wav`, `audio/wav` (16-bit PCM, 16 kHz, mono, under 1 MB) instead while the compression library is not on the phone, to an address ending in `/inference`, and to a server that could not read the Opus file | | `model` | when **Model** is filled in | the text of the field | | `language` | when **Send the keyboard language** is on and Whisper knows the language | an ISO-639-1 code such as `en`, `bn` or `de` | | `prompt` | when there are words to listen for, or **Prompt** is filled in | your words to listen for, then the text of **Prompt**. See [Words and the prompt](#words-and-the-prompt) | | `response_format` | always | `json` | An `Authorization: Bearer ` header is added when **API key** is filled in. Clips shorter than 0.3 seconds, and clips with nothing louder than a quiet room in them, are never sent. See [A silent clip types nothing](https://wmkeyboard.pages.dev/tools/whisper/#details--edge-cases). **Reply.** HTTP 200 with `{"text": "the words"}`. Other fields are ignored. A plain-text body is accepted too, and is used as the transcript. **Errors.** A 400, 415 or 422 answer to an Opus file is taken to mean the server cannot read Opus: the same phrase is sent again at once as WAV, and once a WAV is read, that address gets WAV for as long as the keyboard keeps running. Otherwise any status outside 200–299 fails the phrase. When the body is `{"error": {"message": "…"}}`, that message is what the voice panel shows. Otherwise the panel shows the keyboard's own wording for the status (401 and 403 read as a rejected key, 429 as a rate limit). Timeouts are 10 seconds to connect and 60 seconds for the reply. ## Words and the prompt A server gets the [words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for) inside OpenAI's `prompt` field, followed by whatever you type into **Prompt**. The model reads the prompt as the text that came before your clip, so a name in it is a name the model has just "seen" spelled your way. The **Prompt** field is also where to set a style, such as "Casual chat between friends." or a sentence in the spelling you want. Whisper models, which is what most servers run, read only the last 224 tokens of the prompt, and drop the rest without saying so. That's a limit of the model: Whisper's decoder has room for 448 tokens and gives half of them to the prompt. 224 tokens is roughly 150 short English words, fewer for long names or for scripts that take several tokens a character, so it can't hold a whole dictionary. Newer models such as OpenAI's `gpt-4o-transcribe` read far more. So the keyboard builds the prompt for that limit. It sends at most 150 words, and puts them in order of importance with the most important at the end, where a Whisper model keeps them: your **Prompt** text last, then your own list, then the words you added by hand and the personal dictionary, and the learned words at the front. When a Whisper server has to cut, it cuts the learned words first. The whole prompt is capped at 4,000 characters. ## Privacy With this engine, the recording of each phrase leaves your phone. It goes to the address you typed, or the one you set for the language you are typing in, and nowhere else, and only after you start dictating. Nothing is sent in the background. The API keys are stored with your other settings and are left out of backups unless you choose to include API keys. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/). An `http://` address sends the audio unencrypted. That is fine on your own network. Use `https://` for anything reached over the internet. With **Listen for my words** on, the prompt carries up to 150 of your own words, as described in [Words and the prompt](#words-and-the-prompt). Turn it off, and leave **More words to listen for** and **Prompt** empty, and no prompt is sent at all. Voice typing stays off in password fields, whichever engine is chosen. ## Details & edge cases - **Mobile data.** [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) has a **Transcription server** row. It is allowed by default. Set it to **Ask each time** and the first phrase on mobile data is held with a note. Pressing the microphone again sends it and allows the rest of that session. **Turn off** stops the engine on mobile data. - **No address yet.** Choosing the engine without filling in the address makes the voice panel say so, with a shortcut to these settings and a button that switches back to the system recognizer. - **Server unreachable.** The panel shows why: no connection, a timeout, or a server that refused the connection. Nothing is typed and the rest of your text is untouched. Press the microphone to try again. With [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) on and a [Whisper model](https://wmkeyboard.pages.dev/tools/whisper/) downloaded, the phrase is transcribed on the phone instead, and with no connection at all the next phrase goes to the Whisper model, or to the voice typing of your phone, from the start. - **No live words.** Like offline Whisper, the server gets the whole phrase at once, so nothing appears in the field while you speak. ## How much it sends Each phrase goes as Opus, the codec voice calls use, at about 20 kbit/s: around 75 KB for 30 seconds of speech, where the same recording as WAV is just under 1 MB. On 73 LibriSpeech recordings (8 minutes) the Opus files were 12.7 times smaller, and a Whisper small.en server got 5.74% of the words wrong from them against 5.65% from WAV: one word more in 1,150. The compression library is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/#downloads-dictionaries-models-and-addons), fetched the first time you dictate with this engine (from Play on a Google Play build, and on an F-Droid build only after you turn on **Download feature libraries**). Until it is there, the phrase goes as WAV. [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) shows the saving on each request: press a Transcription server row, and **Saved** says how much less was sent than the WAV would have been. --- # Translate URL: https://wmkeyboard.pages.dev/tools/translate/ Section: Tools Summary: Type in one language, read the translation live, and drop it into the field without switching apps. Online, or on the device with no connection. Translate opens a small typing window right above the keys. It grows with your phone's font size, so the translation under the language chips stays readable at a large text size. Type a sentence, or paste one, watch the translation appear as you go, then send it to your field with a tap. It translates whatever is in the panel's own text box, so it works the same in a chat app, a note, or a search bar. The one thing it reads from your field is text you selected before you opened it. ## What you need - **The standard GitHub and F-Droid builds:** nothing. Open the tool and type. It uses the free Google endpoint until you change [Providers](#providers). - **The Google Play build, or the On device engine:** one download. Type something, and when the panel says a model is missing, press **Download** in the panel. It translates as soon as the model lands. - **The offline build:** translates with Firefox Translations only, from files you [import](https://wmkeyboard.pages.dev/privacy/offline/#translation-models). You need its library and the files of each language direction, fetched on another device. It needs a 64-bit phone with Android 9 or later. ## Using it Open **Translate** from the toolbar, or from the toolbox if you haven't pinned it (it isn't pinned by default). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press R. The panel has its own text box at the top, which wraps onto several lines and scrolls once it holds more than four or so. Enter starts a new line in it rather than closing it. What you type there is what gets translated, not the text already in your field. On a phonetic layout such as Avro, the box spells in your script as you type, the same as a text field does, and Space settles the word. It's the same key-rerouting trick the GIF, sticker, and Wikipedia panels use: while the panel is focused, your keystrokes go to its text box instead of the app behind the keyboard. Opening Translate over a chat you're mid-way through never touches what you already wrote. Once you've typed, the keys cover most of the translation. Press Back, or tap the keyboard button at the right end of the suggestion strip, and the keys go down: your text stays in the box and the panel takes its full height again, so a long translation shows whole. Tap the box to bring the keys back. See [Putting the keys down](https://wmkeyboard.pages.dev/tools/overview/#putting-the-keys-down). **Typos in the box.** Since 0.6.6 the box autocorrects as you type, the way a text field does, so a slip doesn't reach the translator as a word it can't read. Space corrects the word you just finished, by the same rules and with the same **Autocorrect** switch and confidence as everywhere else, and ⌫ straight after puts back what you typed. It needs a layout that types letters straight in; on a phonetic layout such as Avro the box spells in your script and isn't corrected. **Translating text you selected.** Select some text in the field first, then open Translate, and the selection is already in the box with its translation under it. That is the way to translate a message in a language you can't type. The panel reads the selection once, as it opens, and never from a password field. With nothing selected, the box starts empty. The [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/#language) bar has a **Translate** chip that does the same in one tap. **Translating text in any app.** Text you cannot type into, such as a web page or a message you were sent, can be translated without copying it. Select it and pick **WM Keyboard** from the menu over the selection, then **Translate**, or turn on **WM Translate** to have an entry that translates straight away. The translation uses this tool's target language, engine and services, and can replace the selection in a box you can edit. See [In any app's selection menu](https://wmkeyboard.pages.dev/smart/selection-actions/#in-any-apps-selection-menu). **Pasting into the box.** The clipboard icon in the box's top corner pastes the text you copied last, without opening the clipboard. An image on the clipboard is skipped. If you weren't typing in the box, the translation runs straight away and stays on screen with the keys out of the way; if you were, the text goes in at the cursor and you carry on typing. You can also long-press the box where there is no word (an empty box anywhere) and a **Paste** bar comes up at the caret, with **Select all** beside it once the box holds text. Long-press a word to select it instead, and the bar offers **Cut**, **Copy**, **Paste** and **Select all**. The toolbar's **Paste** tool, and a layout's Paste key, paste into the box too while it has the keys. This works in every one of the keyboard's own text boxes, not only this one; see [A captured box is a real text field](https://wmkeyboard.pages.dev/tools/overview/#a-captured-box-is-a-real-text-field). **Clearing the box.** Once the box holds text, an ✕ sits beside the clipboard icon. It empties the box and its translation and hands the keys back to the box, so after inserting one translation you can start typing the next message straight away. To have **Insert** do that for you, turn on **Clear after inserting** under Tools / Translate (off by default): the translation goes into your field and the box is empty and ready for the next one. The header reads left to right like a sentence: *from* this language, *to* that one, *using* this engine. - **Source language** is the first chip. It starts on **Detect language** and switches to the language it found once a result comes back, for example "Bengali". Tap it to pick the source yourself when the detection is wrong, which mostly happens with a word or two. Your pick lasts until you close the panel. Pick **Detect language** to hand it back. - **Swap** is the arrows between the chips. It turns the translation round: the result becomes the text, its language the source, and the language it was read as the new target. It lights up once there is a result to swap. - **Target language** is the second chip. Tap it to open a dropdown of all 68 languages and pick one. You can also change it from the keys: type a slash and a language code at the start of an empty box, then Space. `/ja` makes Japanese the target and `/bn:ja` translates from Bangla to Japanese. The box empties, ready for the text. A code no language has stays as ordinary text. - **Engine** is the chip on the right: **Online**, **On device** or **Automatic**. See [On-device translation](#on-device-translation) below. It shows wherever an on-device engine runs: every build on a 64-bit phone with Android 9 or later, and every build but F-Droid's everywhere. The offline build has only the device, so it shows no chip and uses Firefox Translations. - The translation updates automatically about 400 milliseconds after you stop typing. Pressing return translates immediately, and changing the target language re-translates what's already in the box right away. - Once a translation is showing, two actions light up below it: - **Insert** types the translation into your field at the cursor, leaving whatever was already there in place. - **Replace text** swaps out the field's *entire* contents for the translation. Use this when the field only holds the sentence you were translating and you want a clean swap rather than an insertion. When the panel opened on a selection, it replaces only that selection, as long as the text is still selected. If you have since moved off it, the translation goes in at the cursor instead, so the text around the passage is never lost. While you type, the panel shrinks to leave room for the keys, and a long text gives way first: the box shows three lines, then scrolls, so the language chips, **Replace text** and **Insert** always stay on screen below it. While your text is shorter than that, the translation gets the spare room. With the keys away, the box shows up to five lines. On a phone turned sideways the panel is short, so it rearranges rather than stacking: the language chips, **Replace text** and **Insert** move up into the panel's header, and the text you type sits on the left with its translation beside it on the right. While you type, with the keys back under the panel, there is only room for one row, so that row holds your text, its translation, **Replace text** and **Insert**, and the language chips move below it when the screen has space for them. Long input is capped: anything past 2,500 characters is silently cut off before it's sent, with no on-screen warning that it happened. Hardware-keyboard focus-ring navigation reaches the whole panel: Tab and arrow-key cycling take in the header chips and the swap button, the open menu's rows, and the action buttons (or the download button while that is the offer), the same way they do in the GIF or search panels. One rough edge is left. Esc while a menu is open closes the whole panel rather than just the menu, so press Enter on the chip to bring the list back. ## On-device translation The keyboard can translate on the phone, with no connection and without the text leaving the device. Three engines can do it: - **ML Kit** google is Google's. It has one model per language, about 30 MB each, and comes in the builds that carry ML Kit. - **Firefox Translations** is Mozilla's Bergamot engine with the models Firefox translates web pages with. It is free software and needs no Google services, so it is the on-device engine of the F-Droid edition and of phones without Google Play services. Every build has it on 64-bit phones with Android 9 or later. Its models are per direction: French → English and English → French are two downloads. - **NLLB-200** is Meta's No Language Left Behind model, run by the open-source CTranslate2 engine. One download of 630 MB covers 200 languages and translates between any two of them directly, so it does the pairs the other two cannot: Swahili, Amharic, Yoruba, Khmer, or Bengali into Hindi without going through English. It is the slow, heavy one. A sentence takes seconds rather than a fraction of one, and the model holds about 1 GB of memory while the panel is open. Every build has it on 64-bit phones with 6 GB of memory or more. The model is licensed for non-commercial use only, so the keyboard shows that licence before the first download. All three are rows in the [Providers](#providers) list, in that order by default. The order decides which is asked first, and a pair that one of them has no models for goes on to the next. So with the default list, a pair Firefox Translations has downloaded translates there, a pair it has not goes to NLLB-200 if that is downloaded, and if neither has it the panel offers the smaller Firefox Translations download first. If you downloaded Japanese for Firefox Translations and nothing for ML Kit, Japanese translates with Firefox Translations. Switch a row off to keep an engine out of it, or set **How to use them** to **Choose in the panel** to pick one each time. Pick the engine from the chip on the panel or under Options: - **Online** is what Translate has always done: the online [providers](#providers) in your list, such as Google's endpoint, your Cloud key, a LibreTranslate server, [DeepL](#deepl) or [your own translation server](#your-own-translation-server). It knows the most languages and gives the best translations. It is the default on GitHub and F-Droid. - **On device** never sends the text anywhere. It needs models, downloaded once. - **Automatic** uses the device whenever the languages are downloaded, and the online service for everything else: a language with no model, a pair you have not downloaded, text the device cannot read. The engine chip's icon shows which one produced the result on screen. **On Google Play, the engines are downloads too.** The Play edition keeps ML Kit's translator (about 16 MB), Bergamot (about 7 MB) and CTranslate2 (about 3 MB) out of the install, because most people never use them. **On device** is the Play edition's default engine, so the first translation offers the download, and picking **On device** or **Automatic** later asks Google Play for ML Kit straight away. Bergamot comes with its first model, and CTranslate2 with the NLLB-200 model. The panel and the Tools / Translate page show that download and carry on by themselves when it lands, with no restart. If Play cannot deliver it, the panel says so and the button tries again. APKs from GitHub carry ML Kit inside and fetch Bergamot's and CTranslate2's libraries with their first model; the F-Droid edition builds both from source and fetches them once you turn on **Download feature libraries** under [Privacy](https://wmkeyboard.pages.dev/reference/settings/privacy/). **Models.** When a translation needs a model you do not have, the result area says which and how big, and the action row becomes a **Download** button. The same button cancels while the download runs. When the models arrive, the text you typed is translated at once. Downloads also show in the notification shade, and they carry on if you close the panel. With [data saver](https://wmkeyboard.pages.dev/reference/settings/data-saver/) holding downloads on a metered connection, the button asks first and a second press is the yes. - **ML Kit**: each language is one download, and every model translates to and from English. Bengali to German therefore needs both the Bengali and the German model and goes through English on the way, which costs some accuracy. English itself is part of the engine and is never downloaded. - **Firefox Translations**: each direction is one download of 13.5 to 36.5 MB (see [the table](#firefox-translations-models) below). A translation needs only the directions on its way: French into English needs French → English, and French into German needs French → English and English → German, run through English in one pass. The engine's own library is listed beside the models as **Firefox Translations engine** on builds that fetch it. - **NLLB-200**: one download of 630 MB for every language, plus its engine library, CTranslate2, of a few megabytes on builds that fetch it. The first time, the panel shows the model's licence (Creative Commons Attribution-NonCommercial 4.0) above the button, and the button reads **Accept and download**: pressing it is agreeing. Under Tools / Translate the same licence opens as a dialog with **Read licence**. The answer is remembered, and a download that stops part way picks up where it left off. **Which languages.** The list has 127, and NLLB-200 has all of them. ML Kit has 59: everything in the first 68 except Azerbaijani, Basque, Bosnian, Malayalam, Nepali, Punjabi, Serbian, Uyghur and Traditional Chinese (its Chinese model is Simplified). Firefox Translations has 58: the same 68 except Esperanto, Georgian, Haitian Creole, Irish, Macedonian, Maltese, Nepali, Punjabi, Swahili and Welsh, and it does have Traditional Chinese. The other 59, from Amharic and Armenian to Yoruba and Zulu, belong to NLLB-200 and the online services. With the engine on **On device** or **Automatic**, the language menus mark the languages that need no download with a tick and the ones neither engine on your list can do with **Online only**, and list the downloaded ones first. **A short list on the device.** With the engine on **On device**, each language menu lists only what it can use now: the downloaded languages (and any on their way), the languages you type in, and the current selection. **All languages**, the last row, opens the rest of that menu, so you can still pick a language and download it from the offer. The next menu opens short again. **Automatic** always lists every language, because it sends the ones you have not downloaded online. Both behaviours are switches under [Options](#options). **Detecting the source.** None of the engines works out by itself what language it is reading. Where ML Kit is on the phone, its on-device language identifier (bundled, no download) does the detecting for all of them. It is reliable on a sentence and shaky on a word or two. Without ML Kit (the F-Droid and offline editions), two open-source identifiers take its place, together a download of about 2.5 MB fetched the first time a translation detects its source: Meta's fastText, which knows 176 languages and holds up on a few words, and Google's CLD3, which answers when fastText is unsure and tells the languages apart in a text that mixes scripts. Until it is there, Firefox Translations and NLLB-200 make a rougher guess: from the script (Bengali, Korean, Greek, Thai and Khmer each have their own; Ukrainian, Belarusian and Serbian have letters Russian lacks, and Urdu and Uyghur letters Arabic lacks) and, for Latin letters, from the commonest short words of each language, which a sentence of five or more words nearly always has. When neither is sure, the keyboard falls back on the languages you type in, the active one first, and shows the source with a question mark, as in "French?". Tap the chip to correct it. Hindi, Japanese, Russian, Arabic, Greek, Bulgarian or Chinese typed in Latin letters is recognised for what it is and refused on the device, because no model reads it; Automatic sends it online instead. **What to expect.** On-device translation is good for messages and short text and noticeably less exact than the online service on long, technical or idiomatic text. Line breaks and blank lines survive: each line is translated on its own. The engines let go of their models when you close the panel, since a loaded language pair holds tens of megabytes of memory and NLLB-200 about a gigabyte. NLLB-200 also takes a few seconds to load its model the first time you translate after opening the panel. It translates sentence by sentence, so a long paragraph keeps its end. ### Firefox Translations models Sizes are what each direction downloads. On the phone it takes about half as much again, unpacked. The models come in two sizes: *tiny* ones of about 14 MB and larger *base* ones of about 24 MB (about 36 MB for Chinese, Japanese and Korean), whichever Mozilla currently ships for Firefox on Android. | Language | Into English | From English | |---|---|---| | Afrikaans | 25.0 MB | 24.8 MB | | Albanian | 14.3 MB | 14.8 MB | | Arabic | 24.7 MB | 23.8 MB | | Azerbaijani | 24.6 MB | 24.1 MB | | Basque | 24.5 MB | 24.2 MB | | Belarusian | 24.0 MB | 23.6 MB | | Bengali | 14.4 MB | 14.2 MB | | Bosnian | 24.6 MB | 24.0 MB | | Bulgarian | 23.6 MB | 24.5 MB | | Catalan | 24.2 MB | 24.6 MB | | Chinese (Simplified) | 36.2 MB | 36.1 MB | | Chinese (Traditional) | 36.4 MB | 35.2 MB | | Croatian | 24.6 MB | 23.8 MB | | Czech | 24.5 MB | 24.5 MB | | Danish | 14.2 MB | 14.0 MB | | Dutch | 24.3 MB | 25.0 MB | | Estonian | 24.2 MB | 24.1 MB | | Filipino | 24.8 MB | 24.5 MB | | Finnish | 24.6 MB | 23.6 MB | | French | 25.0 MB | 24.6 MB | | Galician | 24.6 MB | 24.6 MB | | German | 23.9 MB | 24.5 MB | | Greek | 14.2 MB | 14.0 MB | | Gujarati | 14.6 MB | 13.9 MB | | Hebrew | 14.8 MB | 14.4 MB | | Hindi | 14.6 MB | 14.2 MB | | Hungarian | 24.1 MB | 24.0 MB | | Icelandic | 24.7 MB | 23.7 MB | | Indonesian | 14.5 MB | 13.9 MB | | Italian | 25.1 MB | 25.6 MB | | Japanese | 36.0 MB | 34.9 MB | | Kannada | 14.9 MB | 14.3 MB | | Korean | 35.2 MB | 36.5 MB | | Latvian | 14.4 MB | 25.0 MB | | Lithuanian | 14.4 MB | 23.9 MB | | Malay | 14.2 MB | 14.8 MB | | Malayalam | 14.6 MB | 13.6 MB | | Marathi | 23.5 MB | 23.8 MB | | Norwegian | 15.2 MB | 14.5 MB | | Persian | 14.7 MB | 14.6 MB | | Polish | 24.2 MB | 23.6 MB | | Portuguese | 24.2 MB | 24.8 MB | | Romanian | 14.4 MB | 13.5 MB | | Russian | 24.1 MB | 22.4 MB | | Serbian | 24.6 MB | 23.8 MB | | Slovak | 14.7 MB | 23.9 MB | | Slovenian | 25.3 MB | 23.6 MB | | Spanish | 25.1 MB | 24.1 MB | | Swedish | 14.6 MB | 14.3 MB | | Tamil | 25.2 MB | 24.2 MB | | Telugu | 15.1 MB | 13.8 MB | | Thai | 24.4 MB | 24.0 MB | | Turkish | 14.1 MB | 14.5 MB | | Ukrainian | 14.1 MB | 23.8 MB | | Urdu | 24.0 MB | 23.5 MB | | Uyghur | 24.8 MB | 25.0 MB | | Vietnamese | 14.5 MB | 26.1 MB | The models are Mozilla's, released under the MPL-2.0 like the engine, and pinned in the app by their SHA-256: a file that does not match is never used. ## Providers Tools / Translate / Providers lists who translates, in order: the on-device engines (**On device (ML Kit)** in every build but F-Droid's, **On device (Firefox Translations)** on 64-bit phones with Android 9 and later, **On device (NLLB-200)** on 64-bit phones with 6 GB of memory or more), then the online services. It works like every other [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a row off to skip it, and use **Add a provider** for Google Translate, DeepL, LibreTranslate, a [translation front end](#translation-front-ends) (Mozhi, SimplyTranslate, Lingva or Translite) or your own translation server. Every build offers all of them. A LibreTranslate server, a front end or your own server can be added more than once, each with its own address. The first provider that answers is used. If one fails (no connection, a wrong key, a language it does not have), the next one that is on is asked. Set **How to use them** to **Choose in the panel** to pick the provider yourself instead: the panel's engine chip becomes a chip naming the provider, and its menu lists the providers that are on, the phone included. Only the one you pick translates. A provider that is off is never asked, so the text goes only to the ones that are on. The engine decides which part of the list is used: **On device** asks only the phone, **Online** only the services, and **Automatic** the whole list in its order. A fresh install starts with Google Translate, which needs no key, and the on-device engines at the top: ML Kit first where it is there, then Firefox Translations. Updating the app keeps the order your earlier settings gave, and puts Firefox Translations after the on-device rows you already have (at the top if you had none). ## Your own translation server You can send translations to a server that you run, or to a service that you pay for, as long as it answers OpenAI chat completion requests (`/v1/chat/completions`). llama.cpp's `llama-server`, Ollama, LM Studio, vLLM and LocalAI all do, and so do most hosted gateways. The keyboard itself then does no translating work: the phone only sends the text and shows the answer. - **Turning it on.** Add **Your translation server** under [Providers](#providers) and type its address. The **Online** engine then sends translations to your server. On **Automatic**, only the translations that the device cannot do go to it. **On device** never uses it. - **Nothing else gets the text.** A new server goes to the top of the online part of the list, and the other online providers are switched off, so when the server fails (it is off, the address is wrong, the model is missing) the panel shows the error and the text goes nowhere else. Switch another provider back on if you want it as a fallback. A small **Your server** label beside the engine chip says where the translation on screen came from. - **What the server gets.** A chat with two messages: an instruction to translate into the target language (and from the source language, if you picked one in the panel), then your text as the second message. The instruction tells the model to reply with the translation only and to translate questions and requests rather than answer them. The answer comes back as it is written, and the keyboard removes a `` block and a pair of quotation marks around the whole answer, if the model adds them. - **The address.** A bare `host:port`, the API root (`http://192.168.1.10:8080/v1`, `https://openrouter.ai/api/v1`) or the whole `…/chat/completions` address all work. With no path, the keyboard adds `/v1`. With no `http://` or `https://`, it uses `https://`, so type `http://` for a server on your home network. - **Which language it read.** The server does not say what language the text was in, so the source chip shows a language only when you picked one. - **Speed.** A language model is slower than a translation service. On a small model, expect a second or more for a sentence; a model that reasons first takes longer. ## Translation front ends A front end is a free server that passes your text on to Google Translate, DeepL or another service and hands the answer back. The service behind it sees the server, not your phone. WM Keyboard knows four kinds: | Kind | Translates with | Public instances | |---|---|---| | [Mozhi](https://codeberg.org/aryak/mozhi) | Google, DeepL, DuckDuckGo, Yandex or Reverso: you choose the **Engine** | [list](https://codeberg.org/aryak/mozhi/src/branch/master/instances.json) | | [SimplyTranslate](https://codeberg.org/ManeraKai/simplytranslate) | Google, Reverso or iCIBA: you choose the **Engine** | [list](https://codeberg.org/ManeraKai/simplytranslate/src/branch/main/instances.json) | | [Lingva](https://github.com/thedaviddelta/lingva-translate) | Google | [list](https://github.com/thedaviddelta/lingva-translate#instances) | | [Translite](https://codeberg.org/gospodin/translite) | Google | [list](https://codeberg.org/gospodin/translite/src/branch/master/instances.json) | - **Adding one.** Under [Providers](#providers), tap **Add a provider**, pick the kind and paste the address of an instance: a public one you trust, or your own. A bare host such as `mozhi.example.org`, the address from the browser's address bar, or an `/api/...` address all work. **Test** sends one word to check it. There is no default instance, because public instances come and go. - **Engine.** Mozhi and SimplyTranslate can pass the text to more than one service. The engine shows beside the address in the list, so you can add the same instance twice with two engines. An instance that does not run the engine you chose fails, and the next provider is asked. - **Languages.** Each engine spells some language codes its own way (Hebrew is `iw` on one and `he` on another). The keyboard reads each instance's language list once and uses the code that the engine knows. A language the engine does not have goes to the next provider. Traditional Chinese is never sent as Simplified. - **Detecting the source.** DeepL and Reverso behind Mozhi cannot detect the language you typed. With **Detect language** as the source, they pass the text to the next provider. Pick the source language in the panel to use them. - **When an instance breaks.** Front ends read the services behind them in ways those services do not support, and an instance can stop working without notice. An empty answer, an error or a Cloudflare check counts as a failure, so the next provider in the list answers. Keep Google Translate or another provider on below the front end if you want a fallback. ## DeepL DeepL is an optional online service that you bring yourself, with your own DeepL API key. Nothing about it shows or runs until you add **DeepL** under [Providers](#providers) with a key (or a server address). The keyboard has no DeepL key of its own. - **Translate with DeepL.** DeepL is asked in its place in the list. A small **DeepL** label beside the engine chip says when DeepL made the translation on screen. For a language pair that DeepL does not have, or when DeepL fails, the next provider in the list translates and the label goes away. On **Automatic**, DeepL only takes the part that would otherwise go online. To keep the key for DeepL Write only, switch the DeepL row off. - **DeepL Write.** Off until you turn it on. Write corrects and rewrites text in its own language: grammar, wording, style and tone. It adds a button to the [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/#deepl-write) panel and a **DeepL Write** action to [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/). DeepL sells Write with API Pro plans only, so a Free key gets an error from it (Free keys still translate). - **Which server.** DeepL API Free keys end in `:fx` and go to `api-free.deepl.com`; every other key goes to `api.deepl.com`. The keyboard picks the server from the key. **DeepL server** replaces both, for a proxy that you run and that answers DeepL's `/v2/translate` requests. - **Limits.** The Free plan includes 500,000 characters each month. When the month's characters are used up, the panel says so. ## Options Tools / Translate - **Turn on**: already on, like every tool. Switching it off removes Translate from both the toolbar and the toolbox. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) are turned on globally. It overrides Translate's icon color. - **Keyword shortcut**: a **Trigger words** field pre-filled with "translate", plus a **Match capitals** switch that's off by default. Type the word on its own and the suggestion strip offers to open the tool. See [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/). - **Translate into**: the target language, from the same 127-language list as the panel's picker. Defaults to **English**. Changing it here also changes what the panel opens to next time. - **Clear after inserting**: off by default. On, **Insert** empties the text box and its translation once the translation is in your field, so the next message starts in an empty box. - **Translate "in …" where it stands**: off by default, new in 0.6.6. Type `good morning in spanish` and the strip shows a [translate hint chip](https://wmkeyboard.pages.dev/smart/chips/). With this off, tapping it opens Translate on "good morning", already in Spanish, for you to **Replace**. With it on, tapping the chip puts the Spanish in place of "good morning in spanish" straight away, without opening the panel, and the chip's Translate button still opens it for a look first. The hint has to name its language; `translate this` always opens the panel. If no translation comes back (no connection, a language pair whose models aren't downloaded, a metered connection the panel has to ask about), the panel opens on the words instead, with the reason. - **Translation engine**: **Online**, **On device** or **Automatic**. The same choice as the panel's engine chip. The default is **On device** on Google Play and **Online** in the APKs from GitHub and F-Droid. Shown wherever an on-device engine runs. - **Providers**: who translates, in order. See [Providers](#providers). Each kind's setup asks for what it needs: - **Google Translate**: an optional Cloud Translation API key. Without one, translation uses Google's free public endpoint, or the key built into the build if it has one. With one, it uses the official API. - **DeepL**: a DeepL API key, a DeepL server for a proxy, or both. See [DeepL](#deepl). - **LibreTranslate**: the address of a LibreTranslate server, your own or one you trust, and an optional key for instances that ask for one. - **Mozhi** and **SimplyTranslate**: the address of an instance and the **Engine**. See [translation front ends](#translation-front-ends). - **Lingva** and **Translite**: the address of an instance. - **Your translation server**: the address, an optional model name (for example `qwen3:4b` on Ollama, blank for a server with one model), and an optional key sent as a bearer token. See [your own translation server](#your-own-translation-server). - **Advanced**, a closed fold under the list, shown wherever an on-device engine runs: - **Downloaded languages first**: on by default. On **On device** and **Automatic**, the panel's language menus start with the languages on the device. Off keeps the usual order: English and Bengali, then the rest by name. - **Only downloaded languages**: on by default. On **On device**, the panel's language menus show the downloaded languages, the languages you type in and the current selection, with **All languages** at the end for the rest. Off lists every language. Each provider's own settings are on its screen, opened by pressing its row: - **On device (ML Kit)**: google **Languages on the device**, the on-device models. The list starts with English (built in), the language you translate into, the languages you type in, and anything already downloaded or on its way. **More languages** below it folds out the rest. Each row downloads, cancels or deletes one model and shows its progress. A download started here shows as running in the panel, and the other way round. - **On device (Firefox Translations)**: **Firefox Translations models**, one row per direction with its download size. The list starts with the directions into and out of the language you translate into and the languages you type in, and anything already downloaded or on its way; **More directions** folds out the rest. On builds that fetch the engine's library, **Firefox Translations engine** is the first row until it is here. 64-bit phones with Android 9 and later. - **On device (NLLB-200)**: **NLLB-200 model**, one row for the 630 MB model, with **CTranslate2** above it on builds that fetch the engine's library. The first download shows the model's licence. Swipe the row to delete the model. 64-bit phones with 6 GB of memory or more. - **DeepL**: **DeepL Write**, shown once DeepL is set up, off by default. On adds DeepL Write to the Grammar check panel and to selection actions. Needs a DeepL API Pro key. **Writing style** is shown while DeepL Write is on: **As it is** (default), **Simple**, **Business**, **Academic**, **Casual**, **Friendly**, **Confident**, **Diplomatic** or **Enthusiastic**. A language that DeepL has no styles for gets a plain rewrite. - **Google Translate**: on the F-Droid build, the addresses of Google's two translation services. The keyboard sends the text that you translate only to the providers that are on, and only while the translate panel is open. Google's free endpoint is unofficial and rate limited. A Cloud Translation key makes it official and reliable. If you back up your settings with **Include API keys** turned on, a Translate key, DeepL key or translation server key you've pasted in travels with the backup along with your GIF, search, and AI keys. Without that switch, none of them leaves the phone in a backup. ## Details & edge cases - **On the device, nothing is sent.** With the engine on **On device**, the text you type is translated on the phone and goes nowhere. The only network use is the model downloads: ML Kit's come from Google's servers (`dl.google.com`) through Android's download manager, plus ML Kit's own [diagnostics](https://wmkeyboard.pages.dev/privacy/network/); Firefox Translations models come from Mozilla's attachment server (`firefox-settings-attachments.cdn.mozilla.net`), the same files Firefox downloads, and Bergamot sends nothing else. The NLLB-200 model comes from Hugging Face (`huggingface.co`), pinned to one revision and checked against its SHA-256. On **Automatic**, text goes online only when the device cannot translate it. - **Google, or a LibreTranslate server.** With Google Translate in the list and no key, WM Keyboard calls Google's free public translation endpoint. It's the same unofficial one browser translate extensions use: fine for personal typing volumes, with no uptime guarantee and its own rate limits. With a key, it calls the official Cloud Translation v2 API instead, billed to your Google Cloud project and reliable. Add a LibreTranslate server above Google to send translations there first, on any build. Whichever answers, only the text in the panel's box is sent: what you typed, pasted, or had selected when you opened it. Nothing else in the field is. - **Your own server, when you set it up.** With only the server on, every online translation goes to that server and to nothing else, errors included (see [Your own translation server](#your-own-translation-server)). It receives the text as the user message of a chat request, in a POST body, with the instruction as the system message and your key, if any, in a request header. - **DeepL, when you set it up.** Translations go to DeepL in its place in the list (see [DeepL](#deepl)). A pair that DeepL turns down goes to the next provider, and DeepL is not asked about that pair again until the keyboard restarts. Other DeepL errors (a wrong key, a used-up month) also hand the text to the next provider that is on. If DeepL is the only one on, the panel shows the error. - **Sent, precisely:** the text currently in the panel's search box (truncated to 2,500 characters), the target language code, the source language code if you picked one, and your key where one applies. DeepL receives the text in a POST body and the key in a request header. The free Google endpoint receives the text as part of the request URL. The official Google API and a configured LibreTranslate server both send it in a POST body, LibreTranslate's carrying its optional key alongside. Mozhi, SimplyTranslate and Translite receive the text in a POST body. Lingva's API takes the text only as part of the request URL, so a Lingva instance can see it in its logs. The first translation through a front end each session also asks the instance for its language list, which sends no text. - **When it's sent:** automatically ~400ms after you stop typing, immediately on return or retry, and immediately again if you change the target language, all only while the panel is open. Closing the panel cancels any translation in flight. - **Power Saving mode's "Background network" toggle doesn't touch this.** That toggle only stops fetches that happen without you asking: link previews, QR auto-scan, dictionary look-ups. Tools you open yourself, translate included, keep working even with it on. - **Not available before your phone is unlocked once after a restart.** Translate isn't on the short list of tools that work on a locked device, and the on-device models live in storage that is locked until then. It won't appear if you're typing on the lock screen before that first unlock. - **Online translation works the same in every build.** ML Kit is not in the F-Droid build. Firefox Translations is in every build, so the F-Droid build has the engine chip and its model list too, on 64-bit phones with Android 9 and later. Elsewhere, without ML Kit, there is no engine chip and Translate is online only, as before. - **64-bit Android 9 and later for Firefox Translations.** The engine's library needs parts of Android's C library that arrived in Android 9, and its ARM code needs a 64-bit processor (arm64), so older phones and 32-bit ones do not list it. An x86_64 Chromebook or emulator has it too. - **6 GB of memory for NLLB-200.** While it translates, the model and its working copy take about 1.25 GB, so phones with less memory, and 32-bit ones, do not list it. It is also the slowest of the three engines. - **Deleting a model** frees its space at once. If the panel was using it, the next translation asks for it again. - If a request fails outright, from no connection, a bad key, or a timeout, the panel shows the raw error message rather than a simplified one. An offline failure can read as a technical exception string instead of "No internet connection." - **Online, with no connection.** Turn on [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) and the Online engine hands the text to the on-device engine when the phone is offline or the service does not answer, as long as both languages are downloaded. The result says **On device, offline**. This goes for your own translation server too: the text still stays on the phone. Looking for something different named "translate"? The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) has its own Translate action, which uses an LLM for more natural phrasing than a literal translation. When you give it a chip of its own on the selection actions bar, that chip reads **AI translate**, to tell it apart from the **Translate** chip that opens this tool. [Offline voice dictation](https://wmkeyboard.pages.dev/tools/whisper/) has a separate "Translate to English" toggle for what you *say* rather than what you type. Both are configured on their own settings screens, independent of this one. For the full picture of what leaves your device and when, see [network policy](https://wmkeyboard.pages.dev/privacy/network/). --- # Utility tools URL: https://wmkeyboard.pages.dev/tools/utilities/ Section: Tools Summary: Calculator, unit and currency converters, a symbol picker, QR and password generators, a text repeater, and a typing-speed test: eight related tools, seven of them entirely offline. This page covers eight tools that share a theme: arithmetic, conversion and generation. The toolbox draws every tool as one ranked grid with no sections, so the split shows up on the Tools screen instead. A scientific **Calculator**, a **Unit converter**, a **Special symbols** picker, a **QR code generator**, a **Password generator** and **Text repeat** sit under **Create and convert**, alongside **Typing speed test**, which measures rather than generates but lands in the same group. **Currency converter** is filed under **Online tools**, because it's the only one that has to touch the network for live exchange rates. The other seven run their math or generation entirely on the device. tools/calculator-panel — The Calculator panel: live expression and result, the scientific function row, and the custom digit keypad. For Wikipedia, web/image search, and the AI writing tools (also reachable from the toolbox but documented on their own pages), see [Search, Wikipedia & dictionary](https://wmkeyboard.pages.dev/tools/search/) and [AI tools](https://wmkeyboard.pages.dev/tools/ai/). For what the toolbar and toolbox are and how pinning works, see [The toolbar](https://wmkeyboard.pages.dev/tools/overview/). ## Doing the math Open **Calculator** from the toolbar or toolbox. It isn't pinned by default. On a hardware keyboard, double-tap Ctrl for the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/), then press L. The panel evaluates your expression live as you type. It takes `+ - × ÷ * / mod % ^ ( )`, implicit multiplication (`2π`, `3(4+1)`), and the constants `π`/`pi` and `e`. A scrolling function row offers `C`, `sin`, `cos`, `tan`, `π`, `e`, `^`, `√`, `ln`, `log` (base 10), `abs` and `mod` as one-tap chips. Typed out by name, `asin`/`acos`/`atan`/`sinh`/`cosh`/`tanh`/`sqrt`/`cbrt`/`exp`/`floor`/`ceil`/`round` all work too. A trailing `%` reads the way it does on a pocket calculator: added or subtracted it is a share of what came before, so `100+20%` is `120` and `1500-20%` is `1200`, and a second one compounds on the new total. On its own, or multiplied and divided, it is a plain hundredth: `20%` is `0.2`, `100×20%` is `20`, `100÷20%` is `500`. Wrap it in brackets to force that reading where you don't want the share: `100+(20%)` is `100.2`. A `%` followed by a number is modulo, same as `mod`. Permutations and combinations use the keypad shorthand, `5p3` and `4c2`. - The **deg**/**rad** chip in the scrolling function row flips trig between degrees and radians, and remembers your choice. - **=** replaces the expression with its result, so `12*4` becomes `48` and the next thing you type carries on from that number. - An **Insert** chip appears next to the live result whenever it evaluates successfully, and types the formatted number at the cursor. - Opening Calculator from a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) (the inline `96` that appears when you type `12*8` on the strip) prefills the expression with what you already typed. The custom keypad (`7 8 9 ÷ ⌫`, `4 5 6 × (`, `1 2 3 − )`, `0 . % + =`, or `1 2 3` on top with **Phone layout**) replaces the letter rows entirely while the panel is open. There's nothing to type past what the calculator itself accepts. On a phone turned sideways there's no room for a row of chips over the keypad, so the functions move beside it instead: `C`, the eleven functions and the **deg**/**rad** toggle become keys, four to a row, to the keypad's left. ### Options Tools / Calculator / Options - **Suggestion chip**: shows whether Calculate as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives. - **Degrees**: on by default. Off switches trig functions to radians. - **Phone layout**: off by default. It puts 1 2 3 on the keypad's top row, like a dialer and the [numpad](https://wmkeyboard.pages.dev/tools/text-editing/), instead of 7 8 9 like a desk calculator. The operator keys stay where they are. - **Result precision**: a slider from 0 to 12 decimal places, defaulting to **8**. Trailing zeros are stripped either way, and this same setting shapes the unit converter's output below. ## Converting units and currencies **Unit converter** and **Currency converter** share a layout: category or currency chips up top, a swap icon between the two sides, a live result, and the same digit keypad. Only one of them touches the network. That keypad is simpler than Calculator's, just digits, a decimal point and backspace, because neither converter evaluates an expression. tools/unit-converter-panel — The Unit converter panel: category chips, From/To unit rows, the swap icon, and the shared digit keypad. **Unit converter** covers 14 categories, all converted locally: length, mass, temperature, area, volume, speed, time, data, energy, power, pressure, angle, frequency, and fuel economy. Pick a category. Pick a From and a To unit. Type a value, then tap **Insert**. The panel remembers the unit pair you used last *per category*, so switching to Volume and back to Length restores each one's own last pair rather than sharing a single memory slot. **Currency converter** needs live exchange rates. It fetches from `open.er-api.com` (roughly 160 currencies, updated daily) and falls back to `frankfurter.dev` (European Central Bank, roughly 30 currencies) if that fails. The F-Droid edition starts from the jsDelivr `currency-api` instead (about 340 codes, coins included, published as open data), with Frankfurter behind it, so a fresh install there reaches only sources that are free software. The others stay one setting away. None of them needs a key. Rates are quoted against USD and cross-converted locally, so one fetch covers every pair. A refresh icon on the panel forces a new fetch. Otherwise it only refetches once the cached rates are older than the refresh interval set below, and that cache doesn't survive the keyboard process restarting, so the first open after a while can show a brief "Downloading the exchange rates…" message. tools/currency-converter-panel — The Currency converter panel: a From/To currency pair, live converted amount, and the refresh icon. The currency picker pins 15 common codes to the front of the list: USD, EUR, BDT, GBP, INR, JPY, CNY, AUD, CAD, SGD, AED, SAR, MYR, TRY, PKR. Everything else the current rate table has follows, sorted alphabetically. **Cryptocurrency** rides in the same panel. With coins on (they are by default) the picker grows a **Crypto** section between the pinned codes and the alphabetical tail. Picking a coin on either side converts against it exactly like a currency. Coin prices come from `api.coinbase.com`, with the jsDelivr `currency-api` behind it and CoinGecko as the other choice (the F-Droid edition starts from `currency-api` alone), and they're only fetched when you actually pick a coin or type a coin amount. Coin results keep far more of the number than the two decimals a currency gets, since a fraction of a bitcoin at two decimals is just `0.00`. In the panel that means up to twelve decimal places with the trailing zeros stripped. The inline strip chip settles for five significant digits instead, because it has a chip's worth of room. Both converters are also reachable inline. Typing `1 ft` or `150 usd` on the strip offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) with the converted amount, and the sliders icon at the end of the chip opens the matching panel with the category or pair already set. That inline mechanism is covered on the smart chips page, not here. ### Options Tools / Unit converter / Options - **Suggestion chip**: shows whether Convert as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives. - **Feet and inches**: on by default. Anything converting into feet is read as feet plus inches, so 1 metre comes back as `3 ft 3.37 in` rather than `3.2808399 ft`. Turn it off for the plain decimal. Tools / Currency converter / Options - **Suggestion chip**: shows whether Convert as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives. - **Fetch rates as you type**: on by default, off in the F-Droid edition. On, a currency chip fetches the rates it needs as soon as you type an amount. Off, the chip reads **Get the rate** and fetches nothing until you tap it. The panel fetches when you open it either way, and rates the keyboard already has convert with no fetch. - **Decimal places**: a slider from 0 to 6, defaulting to **2**. - **Write the result as**: how a currency chip names the currency it converted into. **Name** (the default) writes `96.04 Rupee`, **Symbol** writes `₹96.04` and **Code** writes `96.04 INR`. A currency with no symbol of its own falls back to its code, and coins keep their ticker whichever you pick. - **Refresh rates every**: a slider from 1 to 48 hours, defaulting to **6**. Upstream rates only update about once a day, so anything under 24 hours mostly changes how soon a *failed* fetch gets retried. - **Currency rates from**: a [provider list](https://wmkeyboard.pages.dev/tools/providers/) of where fiat rates come from, best first: ExchangeRate-API, Frankfurter, AllRatesToday (European Central Bank reference rates, rebased to USD), Coinbase and the jsDelivr currency-api. The tool asks the first one that is on and the next only when it fails. Drag to reorder, switch a source off, or add one back. It starts with ExchangeRate-API then Frankfurter, or currency-api then Frankfurter in the F-Droid edition. Switch every source off and nothing is fetched. Tools / Currency converter / Cryptocurrency - **Convert coins**: on by default. Reads amounts like `1 btc` and shows the Crypto section on the panel. - **Coin decimal places**: a slider from Auto to 12, defaulting to **Auto**. On Auto the panel keeps up to twelve decimal places and trims the trailing zeros, and the inline chip keeps five significant digits. Pick a number instead and both work to that count. - **Refresh coin prices every**: a slider from 1 to 60 minutes, defaulting to **5**. - **Coin prices from**: the same kind of list for coins: Coinbase, the jsDelivr currency-api and CoinGecko. It starts with Coinbase then currency-api, or currency-api alone in the F-Droid edition. - **Coins**: a chip for each of the 39 coins the keyboard knows, 20 of them on to begin with, plus **Add a ticker** for anything else your source quotes. At least one coin always stays on. [Power saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) doesn't touch either converter's own network fetch. Its background-network toggle only forces off clipboard link previews, QR-scan link previews and dictionary auto-lookup, never the currency panel opening or refreshing. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what leaves the device and when. ## Typing special symbols **Special symbols** is a tap-to-type picker for characters that are otherwise a hunt to find: fractions, real math operators, Greek letters, arrows, currency signs, super/subscripts, punctuation marks, shapes and stars, and a Misc catch-all, nine categories in all. Tap a category chip, then tap a symbol to type it exactly like a regular key. tools/symbols-panel — The Symbols panel: category chips over an adaptive grid, with a Recents chip once you've used a few. Every symbol you tap is remembered under a **Recents** chip at the front of the category row, capped at 24 and most recent first. A repeat tap moves a symbol back to the front instead of duplicating it. A few characters render invisibly, so they get a text label instead of a blank cell: non-breaking space shows as "NBSP", zero-width space as "ZWSP", zero-width joiner as "ZWJ". Swipe sideways to move between categories. The last chip, **Unicode**, is every character the phone knows, with a chip per Unicode block to jump to. Its **Search** chip finds a character by its English name or its code, so `right arrow` and `U+2192` both find `→`, and Chinese characters and Korean syllables come up by code only, since their names are only their codes. Hold a character there to read its code and name. To change a category, open the tool's settings and press it under **Your symbols**: type the symbols you want with a space between each, or press **Reset** for the built-in set. Hold a symbol to see it larger, with its relatives beside it: the double and long arrows by an arrow, the other case of a Greek letter, the subscript by a superscript. Slide to one and let go to type it. Letting go where you started types the symbol you held. In your own list, join the ones that belong together with `|`: `→|⇒|⟶` shows `→` in the grid and offers the other two when held. This tool is separate from the **symbol row** (off by default), a strip of one-tap characters that sits above the key grid and is covered under [Modes & the symbol row](https://wmkeyboard.pages.dev/typing/modes/#the-symbol-row). That's a persistent row you configure once, while Special symbols is a picker you open on demand. ### Options Tools / Special symbols - **Clear recent symbols**: a single action. The row shows how many symbols are currently remembered, or "No recents yet". ## Making a QR code **QR code generator** turns whatever you type into a scannable code and sends it as an image. Like the typing test below, it keeps the letter rows on screen instead of replacing them, because you type into it. Opening it seeds its own editable buffer with the focused field's text as a starting point. Opened from a clip's **QR code** button in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/), it starts from that clip's text instead. From there it's independent: your keystrokes route into the QR content rather than the field, and the field itself is never touched until you send. tools/qr-generator-panel — The QR generator panel: editable content line, live QR preview, and Send as image. The preview updates as you type, up to **Longest text** characters (500 to 4,000, default 2,000). Past that it replaces the code with "The text is too long for one QR code," followed by your current character count. A longer payload encodes at a lower error correction level, which is why the two rows sit together in settings. **Send as image** commits the PNG through the same `commitContent` path the clipboard and sticker tools use. If the receiving field or app doesn't advertise support for it, the code falls back to the system clipboard instead, with a toast telling you to paste it in. ### Options Tools / QR code generator / Options - **Image size**: a slider from 256 to 2048 px, defaulting to **1024**. This only affects the PNG that gets sent. The on-panel preview is a fixed small size regardless. - **Send as**: Image or Sticker, defaulting to **Image**. It's the same Image/Sticker switch the GIF and sticker tools use, but it has no visible effect here. A WhatsApp-style sticker only goes out for WebP source images, and a generated QR code is always saved as a PNG, so it sends as a plain image either way. - **Save to gallery**: off by default. Also copies each generated code into **Pictures/WM Keyboard**. - **Error correction**: a segmented L/M/Q/H row, defaulting to **M**. Higher levels survive more smudging and damage but fit less text into the same code. - **Longest text**: a slider from 500 to 4,000 characters, defaulting to **2,000**. Raise it to fit a longer link or note. If the code then refuses to build, lower the error correction above it, or shorten the text. ## Generating a password **Password generator** makes passwords, passphrases or PINs with `SecureRandom` entirely on the device. Nothing generated here is ever stored or logged, and there's no history to clear because none is kept. tools/password-generator-panel — The Password generator panel: generated result, entropy estimate, and the Password/Passphrase tabs. **Password mode** picks one character from every enabled class first, fills the rest at random, then shuffles the whole string so the guaranteed characters aren't front-loaded. **Passphrase mode** draws words from the keyboard's own bundled English dictionary, filtered down to short (4–8 letter), plain-lowercase, distinct words. That works regardless of which language you're actually typing in, with no separate wordlist to download. **PIN mode** makes a code of digits only, for a phone, an app or a bank card: 6 digits by default, anywhere from 4 to 12 with the stepper. With **No easy PINs** on (the default) it draws again past the PINs many apps refuse and guessers try first: one digit repeated (`1111`), a run up or down (`1234`, `9876`), a short block repeated (`1212`, `123123`), and anything that reads as a year or a date (`1987`, `2512`, `250390`). All three modes show a rough "≈N bits of entropy" estimate, and both have **Copy** and **Insert** chips plus a regenerate icon. Once in a long while the entropy line picks up a second caption. See [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#read-your-password-generators-mind). ### Options Tools / Password generator **Password** - **Length**: a slider from 4 to 64, defaulting to **16**. - **Uppercase letters**, **Digits**, **Symbols**: all on by default. - **Symbols to use**: empty by default, which means the built-in punctuation set. Shown only while **Symbols** is on. Some sites accept only a few marks. Type just those and the generator draws from them alone, so you don't have to generate again and again until a password comes out clean. - **Exclude look-alikes**: off by default. Strips easily-confused characters (`Il1O0o5S8B` and a few punctuation marks) from the pool. **Passphrase** - **Words**: a slider from 2 to 10, defaulting to **4**. - **Separator**: free text between words, defaulting to `-`. Leave it blank for no separator. - **Capitalize words**: off by default (`correct-horse` → `Correct-Horse`). - **Include a digit**: off by default. Appends a random digit to one random word in the phrase. **PIN** - **Digits**: a slider from 4 to 12, defaulting to **6**. - **No easy PINs**: on by default. Skips repeated digits, runs, repeated blocks and dates. ## Repeating text **Text repeat** writes a piece of text many times over in one go: "ha" fifty times, a numbered list of the same line, "no" with its last letter stretched out. Type the text into the panel's box (the keys go to the panel, not the app), pick a count and what goes between the copies, and the card under the controls shows what Insert will write, with the number of copies and characters. **Insert** types it into the field, **Copy** puts it on the clipboard, and **Undo** takes the last insert back as long as the field still ends with it. Three modes sit in the header. **Repeat** writes the text the set number of times with the separator between. **Stretch** lengthens the last letter instead: "no" with a count of 4 becomes "nooooo", and an emoji or an accented letter stays whole. **Stairs** grows a copy at a time, "ha", "haha", "hahaha", one per line, which is the picture the name promises. The count has a stepper that keeps stepping while you hold − or +, faster the longer you hold, plus one-tap chips for 2, 3, 5, 10, 25, 50 and 100. The separator is **None**, **Space**, **New line**, **Comma**, or **Custom**, which opens a second box for your own, up to eight characters. Two toggles work in Repeat and Stairs: **1. 2. 3.** puts a number in front of each copy, and **a A a A** writes every second copy in capitals. The panel opens on the selection when there is one, else on the word before the cursor, as a convenience the box's × undoes in one tap. Select some text and the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/) offers **Repeat**, which opens the panel on the selection. Type the same thing three times in a row and the strip offers the tool by itself; see [Repeated text](https://wmkeyboard.pages.dev/smart/chips/#repeated-text) for that chip and what it does with the copies you already typed. The texts you repeat are remembered, up to ten, as chips at the end of the panel's bottom row. They live in a file of their own on the phone, never in a backup, and nothing is remembered in incognito mode, in a password field, or in a field that asks not to be learnt from. ### Options Tools / Text repeat The panel edits the first group live, so these are also how it opens next time. - **Times**: 1 to 1000, defaulting to **10**. - **Mode**: **Repeat**, **Stretch** or **Stairs**, default **Repeat**. - **Between the copies**: **None**, **Space**, **New line**, **Comma** or **Custom**, default **Space**. Picking Stairs with None switches it to New line, since a staircase with nothing between its steps is one long word. **Custom separator** appears for Custom. - **Number each copy** and **Alternate the case**: both off by default. - **Longest result**: 500 to 20,000 characters, default **5,000**. The panel stops at the last whole copy that fits and says so, rather than cutting a copy in half. - **When opened from the chip**: **Replace them** (default) or **Add after them**. See [Repeated text](https://wmkeyboard.pages.dev/smart/chips/#repeated-text). - **Chip after this many repeats**: 2 to 6, default **3**. The same row lives under Smart chips. - **Remember recent texts**: on by default, with a **Clear recent texts** row under it. ## Testing your typing speed **Typing speed test** leaves the key rows alone while a run is going, the same way the QR generator does. You type the prompt on the real keys while the panel scores it from above. The results screen needs no keys, so the panel takes their height back for the scores. Shift is forced off the moment a run starts, so a field's auto-capitalization can't turn your first keystroke into a false miss. The clock doesn't start until you type your first character either, so time spent reading the prompt costs you nothing. tools/typing-test-panel — A typing test in progress: the prompt with per-character correctness coloring, above the live key rows. Three modes decide when a run ends: **Time** (the default, 30 seconds), **Word count** (default 25 words), and **Quote** (types out one short public-domain quotation, however long it runs). Time and Word-count prompts draw from the 200 most common words of the test's language, deliberately short and high-frequency so the test measures typing rather than vocabulary. Turning on **Punctuation** or **Numbers** sprinkles capitals, commas, full stops and the occasional numeral into the prompt. Both are off by default, and both are hidden entirely in Quote mode. ### Other languages The test runs in whichever language the keyboard is in. The last chip on the settings strip names that language. Tap it to pick any language your enabled layouts cover, and the keyboard switches to that language's layout and deals a fresh prompt. A spacebar language swipe during a run does the same. English, Bangla and Hindi ship their own word lists and quotations. Any other language builds its prompt from the most common words of its downloaded or imported dictionary, so a language with no dictionary yet shows a note instead of a prompt, and Quote mode is greyed out for it. On a transliterating layout such as Avro or Hindi phonetic you type the romanised keys as usual, and the test scores the native-script word that comes out. It waits for the whole word before scoring it, because a key there can't be judged as it lands: `k` is ক on its way to খ, and a conjunct isn't itself until its last key. So the syllable under the cursor stays uncoloured while you spell it, and the keys behind it are marked right or wrong once the space closes the word. Keys you backspaced away still count against you, exactly as a mistyped letter does on a direct layout. A layout that converts what you type, such as Pinyin or kana, can't be scored, and the panel says so. Numbers and punctuation follow the language too: a Bangla prompt gets Bangla numerals and ends its sentences with the danda. Personal bests are kept per language, so a 30-second Bangla record never overwrites a 30-second English one. The records list in settings names the language on every non-English row. ### Glide and suggestions Two more chips on the strip, **Glide** and **Suggestions**, let the keyboard's own helpers take part in a run so you can compare scores with and without them. Both are off by default. - **Glide** lets you swipe words during a test. The stroke is decoded exactly as it would be in a text field, and the word lands as if you had typed every letter, followed by its space. A stroke that decodes to the wrong word costs what mistyping it would have. The chip is greyed out where gesture typing isn't available for the language and layout at all. This switch is independent of the main **Glide typing** setting, so you can measure gestures even if you keep them off day to day. - **Suggestions** puts the keyboard's own suggestion strip back where it always sits, in the row directly above the keys, with next-word predictions between words. It is the real strip, in the same slots, at the same size, under your own strip settings, so practising with it is practice at the thing you actually type against. Tapping one finishes the current word and moves on. The tap counts as one keystroke, correct only if the word matched. The words go into the test and never into the field behind the panel. Both switches also live under Tools / Typing speed test / Typing aids. tools/typing-test-results — Typing test results: WPM, accuracy, consistency, and the Insert score chip. Results show four numbers. Words per minute is correct characters ÷ 5 ÷ minutes, the usual "5 characters = 1 word" convention. Raw speed counts every character you typed whether or not it matched. Accuracy is keystroke-level, so it still counts a mistake after you correct it. Consistency scores how even your pace stayed through the run. **Insert score** types the result straight into your field as `"83 WPM · 96% accuracy (30s)"`, with your own numbers and config in place of that example. Personal bests are tracked per configuration, so a 15-second sprint and a 2-minute run don't share a record, alongside a rolling history of your last 24 runs and a running count of tests completed. There are also four achievement badges to chase, listed with their conditions on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#earn-typing-test-achievements). ### Options Tools / Typing speed test - **Default test**: Timed / Word count / Quote chips. The app opens on whichever you used last. - **Length**: for Timed, a slider from 15 to 120 seconds (default **30**); for Word count, a slider from 10 to 100 words (default **25**). Quote mode has no length control, since a quote runs to its own end. - **Difficulty**: the **Punctuation** and **Numbers** toggles, both off by default. This group is hidden entirely while Quote mode is selected. - **Records**: tests completed, a recent average once you have history, a best-WPM row per configuration you've actually run, and **Clear records** to wipe all of it. - **Achievements**: all four badges, listed whether you've earned them or not. **100 wpm club** (finish a run at 100 wpm or faster), **Flawless run** (nothing wrong, extra or missed in a run of at least 30 characters), **Quick brown fox** (finish a quote that uses every letter of the alphabet), and **50 runs**. Earned ones carry a green tick, the rest a faded padlock, so this group is where the goals are visible. The panel's own results screen only shows badges you already have. Same list, with the conditions written out, on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#earn-typing-test-achievements). The calculator and the two converters are the **Calculator and converters** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/); the QR code maker, the password generator and text repeat are the **Generators** bundle, and the typing test is the **Typing test** bundle. A bundle that is off takes its tools off the toolbar, the toolbox and the Tools list until it is switched back on; the special symbols tool belongs to none. ## Details & edge cases - **What's offline, and what's direct-boot safe.** All eight tools work with no network connection except Currency, which needs one to fetch rates. Separately, Symbols, Calculator, Unit converter, Password generator, QR generator and Text repeat all still work during **direct boot**, the window right after a restart before you've unlocked the device for the first time (Text repeat's recent texts read empty until then). Currency doesn't, having no network yet, and neither does Typing test, for reasons the app doesn't surface anywhere. See [The toolbar](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone) for the full direct-boot picture across every tool. - **Hardware shortcuts.** With a [physical keyboard](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) attached, double-tap Ctrl then a letter: Y for Symbols, L for Calculator, U for Unit converter, Q for QR generator, P for Password generator. Currency converter, Text repeat and Typing test have no default letter. Reach them through the toolbox (T) or [assign one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/). - **Discoverable by typing.** Typing one of a tool's keywords as a standalone word offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) straight into it: `symbol`/`symbols`, `calc`/`calculator`, `convert`/`unit`, `currency`/`forex`, `qr`, `password`/`passgen`, `repeat`, `wpm`. Each list is editable per tool under that tool's own **Keyword shortcut** field. - **None of these eight are build-gated.** Unlike the scanners and handwriting, which lean on ML Kit, every build ships all eight identically (see [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're comparing builds). - Every tool here has its own **Enabled** switch, and an **Icon colour** override if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/#browsing-every-tool) are on globally. Both live at the top of that tool's own settings screen, above everything documented in this page's Options sections. --- # Vocabulary URL: https://wmkeyboard.pages.dev/tools/vocabulary/ Section: Tools Summary: Word cards, gentle nudges towards stronger words, and flashcards, from downloadable vocabulary packs. The **Vocabulary** tool teaches you words while you type. Install a pack (the Word Smart and Barron's GRE lists come first), and when you type a plain word that one of the pack's words says better, a chip on the suggestion strip offers it: **hate → abhor**. Tap the chip for the word's card: its meaning, how to say it, where it comes from, and the words around it. Hold the chip to swap the word in. The same packs feed flashcards, in the keyboard and in the app, and a word of the day. tools/vocabulary-card — The card for abhor: pronunciation, senses, synonyms and antonyms, the word family, and where the word came from. ## Getting a pack Vocabulary is one of the [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/): the tool, the packs, the word of the day and the nudges all go when the **Vocabulary** bundle is off, and come back with your progress kept. Open Tools / Vocabulary / Vocabulary packs and download a list: Word Smart 1 and 2, Barron's 333, 800 and 1100, Magoosh 1000, Kaplan 900, Powerscore 700, SparkNotes 1000, Manhattan Prep 1000 and GregMat 1100. Each pack is a study list turned into a small dictionary: for every word, the part of speech, pronunciation in American and British English, up to three senses with an example sentence and dated quotations, synonyms and antonyms, the word family, the etymology, and a mnemonic. Definitions come from Wiktionary, with WordNet filling any gaps. Packs also come from [add-on repositories](https://wmkeyboard.pages.dev/addons/overview/) (type `vocabulary`), from a `.wmvocab.json` file opened in a file manager (gzipped or not), or from your own lists (see below). Several packs can be on at once; a word that appears in more than one shows every list it belongs to on its card. When the download cannot run here (the no-internet build, or a network that blocks it), **Get the pack files** under the installed packs lists every pack's address, each with a copy and an open button. Fetch the `.wmvocab.json.gz` file in a browser, then pick it with **Import a pack or flashcards**. ### Bringing your flashcards **Import a pack or flashcards** at the bottom of the installed packs takes the cards you already have elsewhere, as well as `.wmvocab.json` files: - **Anki**: a deck (`.apkg`) or a whole collection (`.colpkg`), from any Anki version, including the newer exports that older readers cannot open. Only the notes are read; images and sounds stay behind. - **Quizlet**: the text from **Export**, with a tab or a comma between term and definition and a new line between cards. Save it as a `.txt` file and pick that. - **Any CSV or TSV**: a spreadsheet saved with a word column and a meaning column. Anki's **Notes in Plain Text** export works too. The front of each card becomes the word and the back its definition. When the columns or the note's fields have names, they are used: *Front*, *Word* or *Term* is the word, *Back*, *Meaning* or *Definition* the definition, and a field called *Example* or *Sentence* fills the example, *Reading* or *Pronunciation* the pronunciation. Formatting is removed. A word that is on more than one card gets one entry with each meaning as a sense. Cloze cards, and cards whose front is a sentence rather than a word (more than 64 characters), are skipped. The pack takes the file's name, or the deck's name for a whole collection, and appears with the other installed packs, where it can be switched off or deleted. Translations are separate, small downloads per pack and language. The languages your keyboard is set to are fetched on their own when a pack lands or when you add a language (a romanised layout such as Banglish reads the Bangla glosses; a Croatian keyboard takes the Serbo-Croatian sidecar when a pack has no closer one), and you can pick others under **Translation languages** on the tool's page. The full list of a pack's languages folds out under it on the packs screen. Every gloss in another script carries a romanisation, so the card can show "ghrina" beside ঘৃণা. Tools / Vocabulary / Browse words lists every word across your installed packs, searched by word or definition. Chips filter it to one pack or to all of them, and to All, New, Learning or Learnt; tap a word for its card, and a check mark shows the ones you have learnt. ## The nudge The chip appears once you have typed the whole word, stays through the space or full stop after it, and goes away when you start the next word. It only ever offers; nothing is changed until you tap or hold. | Setting | What it does | |---|---| | Offer stronger words | Turns the nudge on. Off, the chip and every other row below it disappear | | Tapping the chip | Opens the card, or swaps the word. Holding does the other one | | Also for the words themselves | Typing *abhor* offers its card, so you can check a word you half remember | | Which words nudge | Words you have not learnt (the default), every word, or only words you have learnt, as practice | | Sensitivity | How much plainer the typed word has to be. *Low* nudges on "hate" but not on "detest" | | Repeat a nudge | Every time, once per text field, or once a day | Nudges run last among the smart chips: a sum, a currency, "define hate", and a tool keyword all win over one. A gloss in one of your languages nudges too: type ঘৃণা on a Bangla layout, or "ghrina" on the romanised one, and the chip offers *abhor*. A gloss of more than one word ("হেয় করা") is matched as a phrase. ## The card The headword, its pronunciation and a speaker button come first, with the parts of speech, the lists the word is in and how far along you are as small tags. The recording is Wiktionary's when it has one and the network allows; otherwise a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for the language says the word, or the phone's speech synthesiser does, in the accent you chose. Speech speed and pitch sliders tune the synthesiser, and disappear once Voice is pinned to the Wiktionary recording alone; **Play a sample** speaks *serendipity* to try the current voice, accent, speed and pitch together. In the app the rest is one section at a time: **Meaning** (senses grouped by part of speech, each with its example; quotations from print open on a tap, each with its year and a trimmed reference), **Related words**, the mnemonic set apart under **Remember it**, **In your languages** for the translations (each gloss with its own romanisation beside it; a language you downloaded by hand shows even when it is not one of yours), and **Where it comes from** with the origin chain up front and the etymology folded behind it. In the app, tapping a related word opens its own page when it is in a pack; any other is looked up online and shown in a sheet, from which it can go into *My words*. In the keyboard, a related word that is in a pack opens its card and one that is not is typed; holding always types it. Tools / Vocabulary / Tapping a synonym or antonym can change this, so a tap always types the word instead, or sends it to the Dictionary tool. The **Full dictionary** action hands the word to the [Dictionary tool](https://wmkeyboard.pages.dev/tools/search/#looking-up-definitions). The keyboard's card also has **Copy**, which copies the card as text, and with the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, **Ask AI**, which opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#asking-about-another-tools-text) about the card, and a long press on a definition or an example selects it, with **Ask AI**, **Copy** and **Insert** on a bar over it. Every section of the card can be shown in the keyboard and the app, in the app only, or not at all, under Tools / Vocabulary / Card sections. The word family, the broader and narrower words, topics, etymology, origin chain, quotations from print, rhymes, hyphenation, forms, the Proto-Indo-European root, the Wikipedia link and when the word was first used are in the app only by default; the mnemonic and translations are everywhere. ## Learning Mark a word **Learnt** from its card and it stops nudging. The **Review** tab (in the keyboard) and the Review screen (in the app) run flashcards over everything due today, plus new words up to the count set by Tools / Vocabulary / New words a day (10 by default): read the word, flip, grade yourself. The back of the card has the meaning in your language under the English one, unless Tools / Vocabulary / Card sections keeps translations off (in the keyboard, it takes translations shown there). Two schedulers are offered under Tools / Vocabulary / Flashcards: five **Leitner boxes** with gaps of 1, 3, 7, 14 and 30 days, or **SM-2**, the rule Anki uses. Both write the same record, so switching keeps your progress. Once today's queue is done, or there was nothing due to start with, a **Study ahead** row lets you pull tomorrow's cards in early. The **word of the day** is the same for everyone who has the same packs: the words are laid out in a fixed order and each day takes the next, so every word comes round once before any repeats, and a device that has learnt the day's word moves on to the next unlearnt one. It shows as a card on the settings home, with the word's meaning in your language under the English one when a translation is downloaded, and as a chip on the suggestion strip. The chip comes back in a few text fields spread over the day (how many is a setting) until you tap it or dismiss it. The card's **X** puts it away until the next word. Under Tools / Vocabulary / Word of the day you can turn either off, or have a new word every 12, 6 or 3 hours, or every hour. ## Your own lists Tools / Vocabulary / My lists shows every list you have made, each with its own switch, and a **New list** button. Name a new list to open its editor: type a word and the details fill in from an installed pack when one knows it, or from the network when you tap **Look up online**: Wiktionary's full entry (through kaikki.org, the same source the packs are built from), with the free dictionary API and Wiktionary's own definition endpoint as fallbacks when it is down. That order is the **Providers** list under Tools / Vocabulary, a [provider list](https://wmkeyboard.pages.dev/tools/providers/) you can reorder or switch sources off in. A look-up is one tap and a few kilobytes, so it is not held back by [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/). **Add many words** takes a pasted list, one word per line. **Add to my list** on any card files the word into a list called *My words*, and the Dictionary panel has the same chip. A list exports as a `.wmvocab.json` file, the same format the packs use, so it can be shared, opened on another phone, or published in an add-on repository. Lists and imported packs travel in [backups](https://wmkeyboard.pages.dev/reference/file-formats/#backups); downloaded packs do not, since they can be downloaded again. ## Privacy Everything here runs on the phone: the packs, the index that finds triggers while you type, and your learning record. The only network requests are the ones you start or set up: downloading a pack, the translations for the languages you set (fetched on their own only when Data saver allows downloads outright), a look-up you tap for in the list editor, and playing a Wiktionary recording. --- # Voice typing URL: https://wmkeyboard.pages.dev/tools/voice/ Section: Tools Summary: Dictation through your device's speech engine: a full panel, a compact bar over the keys, or a draggable bar with the keyboard out of the way. Voice typing turns speech into text using your device's own speech recognizer: no setup, works in any language your device supports. Tap the mic, talk, and words appear as you speak. tools/voice-panel-listening — The voice panel: a pulsing mic button on the left, the handwriting-style action rail on the right. ## Starting dictation Open **Voice typing** from the toolbar (or the toolbox if you haven't pinned it). What opens depends on **Voice typing view**, covered below. There are three of them: - **Full panel** (the default) replaces the whole keyboard with a large mic button and a level-driven pulse ring. A four-key action rail runs down the right side: Delete, Space, a context-aware Enter, and a key that closes the panel and returns you to normal typing. In a search box that Enter key shows a search icon, rather than promising a newline it won't insert. Drag sideways on Delete to delete by word or by letter, the way the keyboard's own ⌫ swipe does, highlighting what letting go will take and giving it back as you drag back; drag sideways on Space to move the cursor. The collapsed bar's backspace takes the same delete swipe. Next to it a column of punctuation keys, . , ? !, puts a mark in by hand without saying it (see [Fixing it while you talk](#fixing-it-while-you-talk)). - **Compact bar over the keys** replaces the suggestion strip with a small mic and status text, and the keys stay visible underneath. Dictate a sentence, then tap a letter key to fix a misheard word without leaving the view. While the mic is off and you're typing a word, the suggestion strip comes back over the bar, so you can pick the word (or, in Chinese, the characters for your pinyin). The bar returns as soon as the word is in. - **Collapsed bar, keyboard hidden** hands the keyboard's whole window back to the app you're typing in and leaves a small draggable pill of dictation controls floating over it. See [The collapsed bar](#the-collapsed-bar). You aren't locked into the one you picked. The panel and the compact bar each carry a collapse button down to the pill, and the pill carries a button back up, so switching is one tap from wherever you are. Tap the mic to start listening. Tap it again to finish. While it's listening you don't have to hit the mic itself: in the full panel the whole area left of the action rail is the stop button (it picks up a faint tint to show it), and on the compact bar anywhere that isn't another button stops it. The stop press tends to happen mid-thought without looking, so it gets the biggest target on the screen. The Voice tool on the toolbar is a stop button too. Tap it while a dictation is running on the panel or the compact bar and the phrase is finished exactly as if you had tapped the mic, then the view closes once the words are in. So one button starts a dictation and ends it, which matters most with [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) or a [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/), where nothing is typed until you say you're done. To throw a dictation away instead, use the compact bar's close button or the panel's keyboard key. With the Voice tool pinned to the toolbar, press and hold it instead of tapping and a small menu lists the three [voice typing modes](#typing-while-you-dictate), with the one in force ticked. Pick one and dictation starts in it at once, and that mode stays the mode until you pick another. It is the quick way to switch between one block at a time and typing while you speak without a trip through settings; **Hold the tool to pick a mode** on the Voice typing screen gives the hold back to the toolbar if you would rather bind it to another tool. The full panel adds walkie-talkie dictation on a long press: holding the mic past the **Hold to talk** threshold (200 to 1500 ms, default 600) starts listening only while your finger stays down, and releasing stops it immediately. A quick tap-and-release still toggles listening on, same as before. Neither bar has the gesture. Both bar mics are plain tap-to-toggle buttons. long-press — Hold the mic in the full panel to dictate walkie-talkie style. Recognized words stream into the text field live as you talk, underlined until the phrase is finished. That's the system recognizer and [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), the offline engine that hears as you speak. [Offline voice](https://wmkeyboard.pages.dev/tools/whisper/) covers how Whisper's panel differs: a whisper.cpp model shows its words under the microphone while it transcribes, and types them once it's done. WM Keyboard works out the spacing around what you dictate from the characters already next to the cursor, so starting mid-sentence or dictating over a selection doesn't leave a double space or a missing one. With **Automatic space after punctuation** on, a phrase that ends on a full stop, comma or other mark gets the space after it that a typed mark would, and a letter you type next on the keys takes a capital after a full stop the way it would after a typed one. Every word you dictate is learned the same way a typed word is, so frequent dictated vocabulary starts showing up in suggestions and autocorrect too. ### If the mic can't open - **Password and other secure fields** never get a mic. Tapping the voice tool in one shows a brief "You cannot use voice typing in a password field" toast and opens nothing. If you land in a password field with a view already open, the panel reads "Voice typing does not work in password fields." and the compact bar reads "Not available in password fields". - **No microphone permission** shows an explanation and an "Allow microphone" button. Tapping it opens a system permission prompt through a small trampoline screen (IMEs can't show permission dialogs directly), and the keyboard rechecks automatically once you're back. The bars have room for one line, so they say "The app needs microphone permission" with an **Allow** chip. - **No speech recognizer on the device** (a phone without Google services, such as a recent Huawei) means the system engine can't run. The panel says so, with a **Choose a voice engine** button that opens Voice typing settings, where an engine that runs on the keyboard itself, such as Whisper, works without any recognizer. Either bar reads "Speech recognition is not available". ### Opening on voice typing If you dictate more than you type, switch on **Reopen voice typing where you left it** on the Voice typing screen. After that, closing the keyboard with the voice panel, the compact bar or the collapsed bar on screen means the next field you tap opens on the same view and starts listening. That holds when you send a message, switch apps or tap into another box. You turn it off by leaving voice typing yourself. Press Back or the close button on the panel or the compact bar, the keyboard button on the collapsed bar, or the Voice tool, and the keyboard opens on its keys from then on. Open voice typing again and it sticks again. Password fields always open on the keys, and without the microphone permission the view asks for it instead of listening. ## Typing while you dictate Voice typing / Voice typing mode By default, dictation owns the field while it runs. The words being recognized sit there as composing text, and the first key you press ends the session. **Voice typing mode** offers two other ways to work, both of which leave the mic open while you use the keyboard. - **One block at a time** (the default) is the behaviour described everywhere else on this page. Speak, watch the words appear live, and the first keystroke stops the mic. - **Type while you speak** keeps the mic running through everything you do. Nothing goes into the field until you pause: each phrase lands whole, at wherever your cursor is by then. Keys, glide typing, suggestions, cursor moves, layout switches and panels-that-aren't-a-panel all carry on around it. A small mic chip sits in front of the suggestions; press and hold it for the whole control bar, with the language, the microphone and Cancel, in place of the suggestions. The down arrows on the bar fold it back to the chip. - **Type while you speak, plain text** is the same, minus every text rule. No spoken punctuation, no punctuation from the recognizer, and no spaces added around what lands. You control all of it from the keys. It's the one to pick for code, terminals, and anywhere you want exactly the words you said and nothing else. The two type-while-you-speak modes only make sense while the keys are on screen, and the only voice view that leaves them there is **Compact bar over the keys**. The full panel and the collapsed bar both take the keyboard's place, so with either of those the mode menu on the Voice tool offers **One block at a time** alone, and the hold gives itself back to the toolbar. Plain text also needs the system recognizer: it is the only engine that can be asked for no punctuation, while the offline models and a transcription server punctuate on their own, so plain text is left out of the menu for them. Settings lists the same choices, plus whichever mode is in force so you can move off it, and with another view it says which view brings the other two. A pause is not a full stop. The recognizer capitalizes the first word of every phrase as if it opened a sentence, because it can't see the field; the keyboard can, and it keeps that capital only where the words in front of your cursor end a sentence: nothing at all, a new line, or a full stop, question mark or exclamation mark, in whatever script wrote it. A phrase that lands after a comma, or after a word you just typed, starts small. A phrase that does open a sentence gets its capital even from a recognizer that writes in lower case, so "done period" followed by "and then" comes out as "Done. And then". A first word spelled with a capital further in (iPhone, eBay) is left as it is. This is about what's written, not about the keys: the capital lands on the first word of an empty field whether or not that field asks the keyboard for sentence capitals, and whether or not **Automatic capitals** is on. The word I and its contractions keep their capital anywhere, and so does anything with a second capital inside it (NASA, McKinsey). A name at the start of a phrase can't be told from a sentence capital, so shift it the way you would a typed one. Spacing and the capital are read again after every phrase, so a long dictation with pauses in it comes out as one sentence rather than a row of them. Plain text is judged the same way. It asks the recognizer for no capitals, so usually there is nothing to decide, but the system recognizer puts one on regardless and Whisper has no such switch at all. When a capital does arrive, where your cursor sits still decides it. Plain text never adds one the recognizer left off, though. What plain text drops is the punctuation and the spacing, not the first letter of a sentence. Sentences *inside* one phrase get the same treatment. A recognizer cases the phrase it was handed and nothing within it, so dictating "ready period this one" came back as "ready. this one". The keyboard raises the first letter after each full stop, question mark or exclamation mark. That works in any script that has capitals, behind a closing quote or bracket, and only where a space follows, so `3.5` and `site.com` are left alone. A stop that closes a single letter is read as an initial rather than an end: "e.g. this" and "9 a.m. tomorrow" keep their small words. Everything else stays as the recognizer cased it. The reason the default can't do this is worth knowing: a partial result is cumulative. Each one rewrites the whole phrase, not just the new words, so a letter you type into the middle of it gets overwritten by the next partial a moment later. The two interactive modes solve that by never putting a partial in the field at all. You lose the live preview of the sentence, and in exchange you get the whole keyboard back while the mic is open. Pair them with **Compact bar over the keys**, which is where they make the most sense. In that view an interactive session shrinks to a single pulsing mic at the left of the suggestion strip, and the strip keeps its room, so your candidates, the emoji row and the clipboard chip all stay reachable while you talk. Tap the mic to end the session. Tapping the voice tool does the same and puts the mic away once the last phrase has landed; with nothing running it just puts it away. If something goes wrong (no permission, no recognizer, no offline model), the full compact bar comes back for as long as it has something to tell you. Two smaller things change in these modes. They always keep listening, whatever **Keep listening** is set to, because a session that ended at the first pause would leave you typing into a closed mic. And they're far more patient with silence: twelve quiet retries instead of two, which is roughly a minute and a half of not talking, because silence is the normal state of a mode where you speak a line and then spend a while fixing it by hand. ## Dictating into the keyboard's own boxes The keyboard's own text boxes, such as the AI chat and its Custom prompt, the emoji, GIF, dictionary and clipboard searches, Find and replace, the clip editor and a plugin's text box, carry a small mic at the right end of their suggestion strip. Tap it and speak: the phrase goes into that box at its caret, in place of any selected text, and never into the app behind the keyboard. The Voice tool does the same while one of these boxes has the keys. While it listens, the strip shows what has been heard so far in place of the suggestions. Tap the mic or anywhere on that line to finish, or the close button to throw the phrase away. Dictation stops at the first pause, whatever **Keep listening** is set to, and it ends by itself if the box closes before the words arrive. It uses the same engine, language, spoken punctuation, spacing and capitals as dictation anywhere else. A search box drops the full stop a recognizer puts at the end of a phrase, so you search for `cat` rather than `Cat.`. Nothing dictated into these boxes is learned, the same as typing into them. A password field behind the keyboard doesn't stop you here, because the words never reach it. The mic sits on the suggestion strip, so it only shows while **Suggestions** is on (Typing → Suggestions). The typing test, the word card's spelling and the Learn from text speller don't get one. Neither do the calculator and the converters, which have no strip. ## The collapsed bar The third surface isn't a keyboard at all. Pick **Collapsed bar, keyboard hidden**, or tap the collapse button in the panel or the compact bar, and the keyboard gives its whole window back to the app you're typing in. What's left is a small pill of dictation controls floating on top. Only the pill itself is touchable: taps anywhere around it go through to the app underneath, so you can scroll the thing you're dictating into while you talk. Drag it wherever you want it. Where it settles is remembered, so it comes back in the same place next time: - Lying flat, it snaps to the nearest of three rests across the screen (left, centre or right) and keeps whatever height you dragged it to. It starts centred and docked at the bottom. - Standing upright, it docks to whichever side edge you let go nearest, and keeps its position along that edge. It starts on the right, halfway down. The hamburger swaps the row of controls for a second page: back, undo, the language chip, a button that stands the bar upright (or lays it flat again), and a shortcut into the voice settings. The first page has the status line, undo when there's something to undo, an exit button, backspace, and the mic. That exit button is two different buttons depending on how you got here, which is worth knowing because they don't do the same thing: - **Arrived by collapsing** from the panel or the compact bar, you get an expand button that takes you back to the surface you came from. It undoes the switch. - **Chose the collapsed bar in settings**, and you get a keyboard button instead. That brings the keys back for now, and the bar stays your default for the next time you open voice typing. The bar isn't just for one field. It stands in for the keyboard on every new field until you bring the keyboard back, and it survives the keyboard's process being killed in between. Two things put the keys back on their own without disarming it: a password field, which never gets the bar, and a panel opened over the top (a hardware shortcut can do that). Both end the current dictation session, and closing the panel brings the bar back. ## Undoing a dictated phrase An **Undo** control appears (bottom-left in the panel, as a small icon on either bar) right after a phrase commits, as long as it's still sitting immediately before your cursor. Tapping it removes exactly that dictated text in one step. It disappears again once you dictate something else, edit the text some other way, or the text it would remove is no longer where dictation left it. It stays while the mic is open, too. Tapped while a phrase is still being heard, it takes back that phrase instead: the words heard so far go, and listening starts over, so a sentence that came out wrong can be said again without stopping. Between phrases, with nothing being heard, it removes the last phrase that landed, as above. The engines that record a clip and type it once it's transcribed, such as [Whisper](https://wmkeyboard.pages.dev/tools/whisper/), a [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/), [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/), have no words to show while you talk, so for them Undo counts a phrase as being heard as soon as the clip has picked up speech. It shows from that moment, and tapping it throws the clip away untranscribed and starts a new one. Before you've said anything it removes the last phrase that landed, the same as with any engine. While a clip is being transcribed it can't be taken back yet; Undo then removes the phrase before it. ### Fixing it while you talk In the default mode a phrase being heard sits in the field as one piece that the recognizer keeps rewriting, so nothing can be typed into it. The keys on the voice surfaces work around that rather than ending the dictation. A punctuation key, Delete, Space or Enter on the panel's rails, or the collapsed bar's backspace, pressed in the middle of a phrase lands the words heard so far as if you had paused, puts the key in after them, and goes straight back to listening. So "meet me at noon", a tap on ?, then "or later" comes out as one line with your question mark in it. The few words you say in the moment the mic is restarting can be missed, so press at a natural break. The engines that record a clip get the same keys, a phrase at a time. A key pressed once the clip has picked up speech ends the clip there and sends it off to be transcribed; the key waits, goes in after the words when they land, and then the mic opens again for the next phrase, whether or not **Keep listening** is on. Keys pressed while a clip is still being transcribed, after a key, a pause or a tap on the mic, wait for it the same way and go in in the order you pressed them, so a quick ? then Enter still reads right. What these engines can't do is show the phrase in the field while you say it: there is no live text to type into until the clip is transcribed, which with a server or a large model can take a second or two. The mic stays open through that "Transcribing" stretch, so you can carry on talking: what you say goes into the next clip and lands after the key. A key pressed before you've said anything just goes in. Keys on the keyboard itself still end the dictation, as described under [Continuous mode](#continuous-mode); [Type while you speak](#typing-while-you-dictate) is the mode for typing freely with the mic open. ## Continuous mode Voice typing / Keep listening With **Keep listening** on (the default), finishing one sentence doesn't stop dictation. The next listening session starts on its own, so you can keep talking sentence after sentence without re-tapping the mic. Turn it off and each session ends after one utterance. Tap the mic again for the next one. **Stop after a pause**, next to it, is for the engines that record a whole clip, [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) among them: off by default, and from 0.5 to 4 seconds it ends their clip by itself once you have said something and then gone quiet for that long. [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) and [Offline Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) write while you speak and hear the same pause with the **Phrase end detector** below. The system recognizer already stops on a pause and ignores it. With **Keep listening** off the pause ends the whole dictation, so nothing has to be pressed to finish. ### Hearing the end of a phrase Voice typing / Phrase end detector **Phrase end detector** decides, for Asr4All, Sherpa, Omnilingual, Vosk and Parakeet, when you have stopped talking: that is when **Stop after a pause** starts counting, and where Omnilingual cuts its phrases. It shows on the screen once one of those engines is on the **Recognition engine** list. Each of those engines' own pages (press its row on the list) also has an **End of a phrase** group with **Stop after a pause** and the detector, the same two settings as here. **Stop after a pause** is off by default, and while it is off a phrase only ends when you press the microphone, whichever detector is picked. - **Auto**, the default, uses Silero where it can run and loudness everywhere else. Sherpa and Omnilingual models carry Silero, so they get it. Asr4All, Vosk and Parakeet get it once ONNX Runtime and a Silero copy are on the phone, and loudness until then. - **Silero VAD** is a 2 MB network that tells speech from other sound, so a fan or traffic does not hold the phrase open. Sherpa and Omnilingual bring their own copy; for Asr4All, Vosk and Parakeet, the row under the choice downloads one (1.8 MB). - **FireRedVAD** is a newer detector, 2.3 MB, downloaded from the row under the choice. On its makers' benchmark of 102 languages it scored an F1 of 97.6 against Silero's 96.0, and raised far fewer false alarms (2.7% of silence taken for speech, against 9.4%). - **Loudness only** uses no model. Anything loud enough counts as speech, the way the pause stop has always worked for Whisper and the server: it costs nothing and downloads nothing, but a noisy room can keep it listening and a soft voice can fall under it. - **Your own model** takes a Silero or TEN VAD `.onnx` file from **Pick file**. The keyboard opens it first and keeps it only if it is a model sherpa-onnx can run; for TEN, that means the copy in sherpa-onnx's releases, which carries the details it needs, not the one from TEN's own page. TEN is not offered as a download because its licence adds conditions to Apache-2.0. The model detectors run on ONNX Runtime, the runtime Sherpa uses. A detector that cannot run yet (no runtime, or its file not downloaded) gives way to what **Auto** would pick, so dictation always has some way to stop by itself. **Speech threshold**, under it, is how sure the detector must be that a moment is speech, from 10% to 90% (50% by default). Lower keeps listening through soft or distant speech, but noise can hold the phrase open; higher ends the phrase sooner in a noisy room, but a quiet voice can be taken for silence. For **Loudness only** it sets how loud counts as speech, and 50% is the level the pause stop has always used. **Smart Turn**, off by default, adds a second opinion at each pause. [Smart Turn v3.2](https://huggingface.co/pipecat-ai/smart-turn-v3) listens to the last 8 seconds and judges from your voice, not your words, whether you finished what you were saying or stopped mid-thought: the falling voice at the end of a sentence, a word left hanging, an "um". When you sound finished, the phrase ends after a short pause (0.3 seconds, or 0.5 for Omnilingual, which waits that long to cut a phrase anyway); when you don't, it waits out the whole **Stop after a pause**. You can then set a long pause for thinking and still not wait at the end of every sentence. It was trained on 23 languages, downloads 8.7 MB from the row under the switch, and runs on ONNX Runtime. Until its model is on the phone, every pause is waited out as without it. Whisper, Whistle, Moonshine and the transcription server record a whole clip and keep the recorder's own loudness stop; the detector does not apply to them. **Reduce background noise**, below it, cleans the microphone's audio before an offline engine hears it: Whisper, Whistle, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual and Vosk. The system recognizer and the transcription server get the microphone as it is. It has three settings, and Off is the default. - **Light** runs [RNNoise](https://github.com/xiph/rnnoise), a tiny network that adds about 10 ms. It makes the audio quieter to listen to, but a recognizer can lose words to it: on noisy test recordings (street, café and fan noise mixed into LibriSpeech at 0 to 10 dB) RNNoise 0.1.1 raised Whisper Tiny's word error rate from 27% to 33%. The 0.2 model it runs now did no better: on a second set made the same way it took Whisper Tiny from 23% to 29%, as 0.1.1 did. It is part of the offline engines' shared libraries, so on the Play build, or a GitHub or F-Droid build that has not fetched those yet, the first phrase after you turn it on goes through as recorded while they download. - **Strong** runs [DPDFNet](https://github.com/ceva-ip/DPDFNet), a DeepFilterNet model, on ONNX Runtime, the runtime [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) uses. It keeps a little of the microphone under the cleaned audio, because a recognizer copes better with some noise left in than with speech scrubbed thin. On the same recordings it brought Whisper Tiny's error rate down from 27% to 24%, and Sherpa's English model's from 33% to 31%. It costs more: a 9 MB model that downloads from the row under the setting, about five times RNNoise's work for the phone, and 40 ms of delay. Until the model and the runtime are on the phone, Strong runs as Light. Between sentences the mic is really closed for a moment: the recognizer has stopped hearing and is working out the words. The pulse ring goes away and the line shows "…" for exactly that stretch, so you can see when to wait. It comes back when the next sentence is listening. Pressing the mic while it's working out the words makes that sentence the last one. That stretch has a limit. A recognizer that stops on its own pause is expected to send the words it heard, but some don't always: Transcribro, a Whisper-based recognizer often installed on phones without Google's, shows the words a moment after the pause and then sends its result many seconds later, or never, which left the spinner turning. Once the words of a finished sentence are on screen, the keyboard waits 3 seconds for the recognizer to confirm them; with no words at all it waits 12. Then it ends the sentence itself, keeps the words already shown, and carries on listening. Continuous mode is also patient with silence: a pause that would normally time out the recognizer restarts listening quietly instead of surfacing an error, but only for two silent retries in a row. After that it gives up and goes idle, so an abandoned open mic doesn't sit there listening forever. Dictation ends completely the moment you type a key, swipe, or tap a suggestion. There's no pausing and picking up where you left off. Partial results build up as one continuous chunk, so typing in the middle of an utterance would corrupt it rather than interrupt it. What was heard before the key stays in the field. With an engine that records a clip, such as Whisper or a transcription server, that means the clip isn't thrown away either: once it has picked up speech, the key finishes it, and its words land when the transcription is done. They go in after whatever you typed in the meantime, since the key can't wait for them. Whichever view you're in stays open afterward, so you can tap the mic to resume. The keys on the voice surfaces themselves don't end it (see [Fixing it while you talk](#fixing-it-while-you-talk)). A tap on the Voice tool is the exception among the ways out: it finishes the phrase first (see [Starting dictation](#starting-dictation)). Dictation also stops when you close the panel, dismiss either bar, or switch to a different tool panel. That last paragraph describes the default mode. If you want the mic to survive typing, see [Typing while you dictate](#typing-while-you-dictate). Both interactive modes ignore the **Keep listening** setting and always chain. ## Spoken punctuation Voice typing / Spoken punctuation With this on (the default), saying certain words types the punctuation mark instead of the word itself. It only applies to finished phrases, never to the live partial text, and it knows four sets of words: English, Bangla, Portuguese and Spanish. The sentence marks come first: | Say (English) | Types | Say (Bangla) | Types | |---|---|---|---| | "new paragraph" | two line breaks | "নতুন প্যারা" | two line breaks | | "new line" | line break | "নতুন লাইন" | line break | | "question mark" | `?` | "প্রশ্নবোধক চিহ্ন" / "প্রশ্নবোধক" | `?` | | "exclamation mark" / "exclamation point" | `!` | "বিস্ময়সূচক চিহ্ন" / "বিস্ময়বোধক" | `!` | | "full stop" / "period" | `.` | "দাঁড়ি" | `।` | | "comma" | `,` | "কমা" | `,` | | "colon" | `:` | "কোলন" | `:` | | "semicolon" | `;` | "সেমিকোলন" | `;` | Punctuation attaches straight to the word before it with no space. The trade-off is deliberate. While this is on, a sentence that uses one of these words as a word types the symbol instead. Turn it off if you want to say "period" or "comma" out loud and have it come out as a word. English also knows the marks past the end of a sentence. Each one sits against its neighbours the way it would if you had typed it: an opening mark closes up to the word after it, a joining mark closes up on both sides, and an operator keeps a space on each side. | Say | Types | Spacing | |---|---|---| | "ellipsis" / "dot dot dot" | `…` | after the word: `wait…` | | "open quote", "close quote" | `"` | around the words: `said "hi" twice` | | "open parenthesis", "close parenthesis" | `(` `)` | around the words: `see (page two) now` | | "open bracket", "close bracket" | `[` `]` | same | | "open brace", "close brace" | `{` `}` | same | | "apostrophe" | `'` | joins: `rock'n` | | "hyphen" | `-` | joins: `well-known` | | "dash" | `-` | stands apart: `one - two` | | "en dash", "em dash" | `–` `—` | joins | | "slash" / "forward slash" | `/` | joins: `and/or` | | "backslash" | `\` | joins | | "underscore" | `_` | joins: `snake_case` | | "at sign" | `@` | joins: `me@example` | | "caret" | `^` | joins | | "hashtag" / "hash sign" / "pound sign" | `#` | before the word: `#blessed` | | "dollar sign" | `$` | before the word: `$5` | | "tilde" | `~` | before the word | | "percent sign" | `%` | after the word: `50%` | | "degree sign" | `°` | after the word | | "asterisk" | `*` | after the word | | "copyright sign", "registered sign", "trademark sign" | `©` `®` `™` | after the word | | "ampersand" | `&` | stands apart: `salt & pepper` | | "plus sign", "equals sign" | `+` `=` | stands apart: `two + two = four` | | "greater than sign", "less than sign" | `>` `<` | stands apart | | "vertical bar" | `\|` | stands apart | A symbol whose plain name is an everyday word only answers to its longer name, which is why so many of these end in "sign". "Fifty percent is greater than ten plus one" stays a sentence; "fifty percent sign" is `50%`. Portuguese and Spanish know the names Gboard uses. A bare "ponto" or "punto" is not among them, because both are everyday words; say "ponto final" or "punto final" for the full stop. | Say (Portuguese) | Say (Spanish) | Types | |---|---|---| | "novo parágrafo" | "nuevo párrafo" | two line breaks | | "nova linha" | "nueva línea" | line break | | "ponto de interrogação" / "interrogação" | "signo de interrogación" | `?` | | "ponto de exclamação" / "exclamação" | "signo de exclamación" | `!` | | "ponto final" | "punto final" | `.` | | "vírgula" | "coma" | `,` | | "dois pontos" | "dos puntos" | `:` | | "ponto e vírgula" | "punto y coma" | `;` | | "reticências" | "puntos suspensivos" | `…` | | "abre aspas", "fecha aspas" | "abrir comillas", "cerrar comillas" | `"` | | "abre parênteses", "fecha parênteses" | "abrir paréntesis", "cerrar paréntesis" | `(` `)` | | "hífen" | "guion" | `-` | | "travessão" | | `—` | | "arroba" | "arroba" | `@` | Any other language has no table, so its dictation is typed exactly as it was heard. ## Adding punctuation and capitals Voice typing / Add punctuation and capitals Three of the offline engines write the words they hear and nothing else. Omnilingual, [Vosk](https://wmkeyboard.pages.dev/tools/vosk/), and every [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) model but the German, Spanish, Korean and eight-language ones, hand over a phrase in lower case with no full stops or commas, so a minute of dictation would come out as one long run of words. **Add punctuation and capitals**, on by default, fixes that on the phone. When a phrase is finished, a punctuation model reads it and puts in full stops, commas and question marks, and the first letter of every sentence becomes a capital. The row shows only while Sherpa or [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) is on the engine list, and not in **Type while you speak, plain text**. The marks are the language's own: `।` after Bangla and Hindi, `،` and `؟` in Arabic, `۔` in Urdu, `。` and `,` in Chinese, `、` in Japanese, and an opening `¿` on a Spanish question. Names get their capitals from the keyboard's word lists and from the capitals it has learned from your typing, so "london" becomes "London" once a list or you have spelled it that way. An English phrase also takes the name capitals of Sherpa's **Punctuation (English)** model when that is downloaded. The model is Horizon Labs' punctuation-restoration-small, an mmBERT model trained in 88 languages, made smaller for phones. Download it from the **Punctuation model** row under the setting: 151 MB from the keyboard's data repository at a fixed version, every file checked against its SHA-256, and about 90 MB of memory while it is loaded. A phrase takes a few tens of milliseconds after you stop. Until it is downloaded, English still gets punctuation from **Punctuation (English)** if you have that model, and other languages stay as the engine wrote them. It needs ONNX Runtime, the same on-demand part Sherpa and Omnilingual run on. **Which punctuation model**, under the setting, narrows that. **Whichever is on the phone** is the behaviour above. **Multilingual only** ignores the English model. **English only, the small one** ignores the big model, so an English dictation gets by on 8 MB and nothing else is punctuated. **Import a punctuation model** takes any [sherpa-onnx online punctuation model](https://k2-fsa.github.io/sherpa/onnx/punctuation/index.html): pick its `.onnx` file and `bpe.vocab` together, name it, and give it language codes. It goes before the listed models for those languages. Sherpa's other kind of punctuation model, the offline CT-Transformer, is refused. It was picked by measurement. News paragraphs from FLORES-200, with their punctuation and capitals taken out, were put back together by each candidate. On the marks it scored 0.81 F1 in English, 0.73 in Bangla, 0.78 in Hindi, 0.84 in Spanish, 0.73 in Arabic and 0.83 in Chinese, ahead of every other model tried in each of them. Full stops and question marks are its strong side; commas are where it misses most. It never types a colon, a dash, an exclamation mark or quotes. Thai, Lao, Khmer, Burmese and Tibetan are not among its languages, and a dictation in a language it lacks is left alone. Engines that punctuate for themselves are never touched: Whisper, Whistle, Asr4All, Parakeet, Moonshine, those four Sherpa models, the phone's recognizer and a transcription server. Nor is dictation into the keyboard's own search boxes. [Spoken punctuation](#spoken-punctuation) still works with it on: a mark you say goes in first, and the model adds nothing next to it. ## Tidying dictation with AI A recognizer writes down what you said, word for word: the "um"s, the false starts, the word you said twice, and both halves of "let's meet at three, no, four". **Tidy dictation with AI**, off by default, hands each phrase to the model the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) uses, the way Gboard's Rambler does on Google's servers, and puts the cleaned version in its place. Filler and repeats go, a spoken correction keeps only the correction ("let's meet at four"), and punctuation and obvious mishearings get fixed. Your words, your language and your tone stay; it doesn't summarize, translate or answer what you said. The phrase goes into the field as heard first, and the surface says **Tidying up…** while the model works. The cleaned text only replaces it if the phrase is still exactly what sits in front of your cursor, so typing, moving the cursor or leaving the field in the meantime just keeps what you said. So does a model that fails, gives nothing back or is still thinking after 15 seconds. Nothing you dictated is ever lost to the rewrite. The next phrase of a continuous dictation starts once the answer is in, and tapping the mic while it's tidying makes that phrase the last one. **Undo** takes back the tidied phrase. It uses whatever provider and model the AI tool is set to, cloud or on-device, and is skipped without a word when that provider isn't set up yet, when [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is holding cloud AI back, in password fields, in the keyboard's own search boxes, for phrases of only a word or two, and in **Type while you speak, plain text**, whose point is the words exactly as said. A small on-device model tidies less reliably than a cloud one, and it holds the next phrase back for longer. ## Language and engine Voice typing / Voice typing language Out of the box, dictation listens for the language of the layout you're typing on, and switching layouts switches it. **Voice typing language** lets you break that link. It opens a list with **Follow the keyboard** at the top, your keyboard languages under it, and then the other languages speech recognition commonly handles: - **Follow the keyboard** is the default and works the way dictation always has. - **One language ticked** keeps dictation in that language whatever the layout. Type on your Bangla layout and still dictate in English, say. - **Several ticked** lets you speak any of them. Dictation starts in your keyboard's language when it's one of the ticked ones, otherwise in the first you ticked. How well "several" works depends on the engine. The system recognizer is asked to detect the language among your picks, and to follow a switch in the middle of a sentence, on Android 14 and later. That's a request, and only recognizers that support it (Google's is one) act on it. Others, and older Android versions, hear the starting language alone. [Offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) work the language out themselves with several picked. A whisper.cpp Whisper model chooses among your picks only, whatever they are. A LiteRT Whisper model chooses among every language it knows, so give it a multilingual one that covers all of them; its grouped models can only be told one language, and with one picked, they are. A language chip sits in the corner of the voice panel whenever there's a choice to make: two or more keyboard languages, or a choice already made. It shows what dictation listens for, **EN** or **EN+BN**, and it's drawn brighter when you've picked rather than followed the keyboard. Tap it for a menu: **Follow the keyboard**, each of your keyboard languages, **All my languages** to listen for every one of them at once, and **More languages…** to open the full list in settings. A pick from the menu is saved, the same as one made in settings, and a session that's listening starts again in it. The compact bar over the keys can carry the same chip. Turn on **Language button on the compact bar** (it shows under **Voice typing view** while the compact bar is the one picked) and the chip sits between the status text and the undo button, with the same menu behind it. It's off by default because that bar has room for one line. On Android 12 and up the system recognizer prefers to run fully on-device, once your phone has that language's model installed. It's faster, needs no network, and skips the little beep before it starts listening. If a language's on-device model can't handle recognition, WM Keyboard falls back to the network recognizer transparently. The same happens when the on-device recognizer opens the microphone and then hears nothing at all, with no level and no words for the first couple of seconds. Some phones' on-device model stalls that way, which used to look like a dead microphone. That language then stays on the network recognizer until the keyboard restarts. **Prefer on-device recognition**, shown while the phone's recognizer is on the engine list, turns the on-device recognizer off altogether. **Recognition service**, on the same screen, picks which app does the recognising. **Phone's default** is whichever one Android has set; the list also holds every other app on the phone that offers speech recognition to other apps, such as Marmalade, Transcribro or FUTO Voice Input. Pick one and the mic sends your speech to it instead, with no change to anything else on this page. **Prefer on-device recognition** is hidden while another app is picked, since it only steers the phone's default. If that app is uninstalled, dictation goes back to the phone's default, and the row says the app is gone until you pick again. ### Hinglish, Banglish and other romanized languages A romanized language is dictated as the language itself. No engine knows "Hindi in Latin letters" as a language of its own, so with **Hinglish · Hindi (Romanized)** picked, every engine is asked for Hindi, and the Devanagari it writes is then spelled the way you'd type it on the Hinglish layout. "कल मीटिंग है, प्लीज कैलेंडर पे डाल देना" lands as "Kal meeting hai, please calendar pe daal dena". The live words change script before they reach the field, so you never see the Devanagari. Each word goes through that language's spelling lists first. An English word the recognizer wrote in the native script comes back as the English word when the lists know it (मीटिंग is "meeting", not "miting"), and a common word takes the spelling people chat in. Every other word is spelled out by rules for its script. Tamil, Telugu, Kannada, Malayalam and Sinhala keep every vowel they say (nuvvu ekkada unnaavu, kohomada). Roman Russian follows Translit (privet, horosho). Words the recognizer already wrote in Latin letters stay as they are, and native digits come out as 0–9. Konkani's Romi has spelling rules of its own, so it is dictated as before. Languages & layouts / Hinglish / Voice typing / Romanized dictation model Rules spell a letter the same way every time, so a long or rare word can come out stiffer than anyone would write it. The **Romanized dictation model** is for those words. It is IndicXlit, the transliteration model by [AI4Bharat](https://github.com/AI4Bharat/IndicXlit) behind [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions), trained the other way round: from Bangla, Hindi, Urdu, Assamese, Marathi, Nepali, Gujarati, Punjabi, Odia, Tamil, Telugu, Kannada or Malayalam script back to Latin letters. Download it from the romanized language's page (Hinglish, Banglish and the rest), or from the native language's page under Neural suggestions. There is no switch: once it is on the phone, dictation uses it. The phrase still lands the moment it's heard, spelled as above. Then, before the next phrase starts, the model spells again each word of five letters or more that the lists didn't know, and the phrase is swapped for that version if it is still right in front of the cursor. Live words aren't touched. Shorter words stay with the rules: a common word like है or কি that the lists leave out reads better spelled by the rules, and the model tends to hear it as an English word ("key" for की). On 1,500 test words a language from AI4Bharat's Aksharantar set, the model spelled 54% the way people write them, against 29% for the rules alone; Hindi went from 39% to 60%, Bangla from 26% to 44%. It is a 13 MB download from the data repository's `models` release, checked against a SHA-256 built into the app, plus ONNX Runtime (about 33 MB) if nothing else has fetched it yet. It runs on the phone, and nothing you say leaves it. When the model isn't there, or hasn't got through a phrase's words within about half a second, the rules' spelling stays. Delete it from the same row, or from **Downloads** on the storage screen. Hinglish dictation runs on whatever engine, model and server Hindi is set to, and the same goes for every romanized language. Whistle knows none of them and steps aside for the next engine on the list. Picking Hindi and Hinglish together leaves the Devanagari as it was heard, since nothing says which of the two a word was meant for. With [Tidy dictation with AI](#tidying-dictation-with-ai) on, the model is told the phrase is Hindi (or Bangla, or Tamil) in Latin letters, so it keeps it in Latin letters and puts an English word the rules spelled by sound back to its English spelling. ### An engine for each language Voice typing / Engine for each language **Engine for each language** gives each of your keyboard languages its own engine, and for Offline Whisper, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual and Vosk its own model: English on the system recognizer and Bangla on a Whisper model, say. Tap a language to see what can take it on this phone. Only engines that know the language and models that are downloaded are listed. **Default** follows the **Recognition engine** list. Dictation picks the engine for the language it starts in. Turn on **Change engine from the voice tool** and the voice panel gets an engine chip in its bottom left corner. It names the engine the language you dictate in uses, and a tap lists the same choices for that language, with **As in settings** to go back to the default. A pick is saved the same way as one made in settings, and a session that's listening starts again on it. On Android 13 and up, a chip offers to download a model that's available for your language but not installed yet. It's named for the language you're actually dictating in: "Get Spanish for offline voice typing", "Get Bangla for offline voice typing", and so on. A dictation language the catalogue doesn't recognise reads "Get this language for offline voice typing" rather than guessing. Tap the chip to start the system's own download. Progress shows on the chip until the model is installed. ## Choosing the microphone Voice typing / Microphone All of this is behind **Microphone switch**, which is on by default. Turn it off and voice typing always listens through the microphone Android picks: the keyboard never moves the audio to a headset, the **Microphone** row is hidden and the switch leaves every voice view. By default voice typing listens through whichever microphone Android picks, which is a wired or USB headset when one is plugged in and the phone's own microphone otherwise. Android does not switch dictation to a Bluetooth headset on its own. **Microphone** changes that: choose **Bluetooth headset**, **Wired headset**, **USB microphone** or **Phone microphone**, and voice typing uses that kind whenever one is connected. When none is, it goes back to the automatic choice, so a headset left at home doesn't leave you with a dead mic. When more than one microphone is connected, every voice view gets a microphone switch: the bottom right corner of the panel, beside the collapse button on the compact bar, and next to the status line on the collapsed bar (in the column on the upright one). Its icon is the kind in use: a phone, a headset, USB or Bluetooth. It's filled while a microphone you picked is the one listening, and outlined while Android's own choice is. Tap it to move to the next connected microphone. The pick is saved as the **Microphone** setting, and a session that's listening starts again on the new one. When a session starts on a microphone the keyboard hasn't named yet, it says which, with the device's own name: "Using Bluetooth mic (X7)", "Using Built-in mic (Pixel 8)". The panel shows it as a toast along its bottom edge for a couple of seconds, and the two bars show it in their status line until the first words arrive. It's said after a tap on the switch, when the microphone changes, and on the first dictation each time the keyboard opens, not on every sentence of a continuous session. In **Type while you speak**, the compact bar shrinks to a single microphone button so the suggestions keep their room, and that button has no switch beside it. Change the microphone in settings there, or from the panel. How far the choice reaches depends on the engine. [Offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) record with the keyboard's own capture, so every kind works for them. The system recognizer records the audio itself, and the only way to move it is to route the phone's call audio to a Bluetooth headset, so with it the chip offers **Auto** and **Bluetooth** only. A Bluetooth headset's microphone works over the same link a phone call uses, so while it's in use music on that headset drops to call quality. The keyboard gives the link back about a second after dictation stops. ## Options Voice typing Everything below lives on the Voice typing screen, under Features on the settings home screen, rather than on the tool page. The microphone can be reached from a key or a hardware shortcut with the tool nowhere on the toolbar, so these aren't really the tool's settings. | Setting | Default | What it does | |---|---|---| | Use voice typing | On | The switch at the top of the screen. Off removes the microphone from the whole keyboard and hides every other row here. See [Turning voice typing off](#turning-voice-typing-off). | | Voice typing view | Full panel | A choice of three, not a switch: which surface the mic button opens. **Full panel**, **Compact bar over the keys**, or **Collapsed bar, keyboard hidden**. | | Language button on the compact bar | Off | Shown while **Voice typing view** is the compact bar. Adds the language chip to the bar, so you can pick what dictation listens for without opening the panel. See [Language and engine](#language-and-engine). | | Voice typing mode | One block at a time | Another choice of three: whether the keys work while the mic is open. **One block at a time**, **Type while you speak**, or **Type while you speak, plain text**. See [Typing while you dictate](#typing-while-you-dictate). | | Hold the tool to pick a mode | On | A press and hold on the toolbar's Voice tool opens a menu of the three modes; picking one makes it the mode and starts dictation. Off gives the hold back to the toolbar's own press-and-hold action. | | Reopen voice typing where you left it | Off | Close the keyboard with a voice view open and the next field opens on that view, already listening. See [Opening on voice typing](#opening-on-voice-typing). | | Pause media during voice typing | Off | The keyboard takes the audio focus while the microphone listens, so music, podcasts and video pause. They resume about a second after dictation ends, which keeps a continuous session from restarting the music between sentences. An app that ignores the request keeps playing. | | Microphone switch | On | Off leaves the microphone to Android and takes the switch off the voice views. See [Choosing the microphone](#choosing-the-microphone). | | Microphone | Automatic | Which microphone voice typing listens through: **Automatic**, **Bluetooth headset**, **Wired headset**, **USB microphone** or **Phone microphone**. A pick is preferred whenever one of its kind is connected, and automatic stands in when none is. See [Choosing the microphone](#choosing-the-microphone). | | Change engine from the voice tool | Off | An engine chip on the voice panel for the language you dictate in. See [An engine for each language](#an-engine-for-each-language). | | Hold to talk | 600 ms | How long you hold the microphone before a tap becomes a press-and-hold, from 200 to 1500 ms. Raise it if a slow press keeps stopping your dictation. Lower it if you mean to hold and it keeps latching on. | | Keep listening | On | Starts the next sentence automatically after each one commits. | | Keep the screen on | On | The screen stays on while the microphone is open or a clip is being transcribed. The screen turning off closes the keyboard, and that ends the dictation: a press of the power button still does, and loses what was being said. | | Stop after a pause | Off | Ends a phrase by itself once you have said something and then gone quiet for this long, from 0.5 to 4 seconds. See [Continuous mode](#continuous-mode). | | Phrase end detector | Auto | How Asr4All, Sherpa, Omnilingual and Vosk hear that pause: **Auto**, **Silero VAD**, **FireRedVAD**, **Loudness only** or **Your own model**. See [Hearing the end of a phrase](#hearing-the-end-of-a-phrase). | | Speech threshold | 50% | How sure the phrase end detector must be that a moment is speech. | | Smart Turn | Off | At each pause, ends the phrase early when you sound finished, and waits out the whole pause when you don't. Needs its 8.7 MB model. | | Spoken punctuation | On | Saying "comma", "question mark" or "দাঁড়ি" types the mark. | | Add punctuation and capitals | On | Full stops, commas, question marks and sentence capitals for Sherpa, Omnilingual and Vosk, which write bare words. Needs its 151 MB model, downloaded from the row under it. See [Adding punctuation and capitals](#adding-punctuation-and-capitals). | | Tidy dictation with AI | Off | Sends each phrase to the AI tool's model to take out filler words, repeats and spoken corrections. Hidden in plain text mode. See [Tidying dictation with AI](#tidying-dictation-with-ai). | | Voice input for other apps | Off | Puts WM Keyboard on Android's list of voice input apps, so other apps' microphone buttons can use these engines. See [Voice input for other apps](#voice-input-for-other-apps). | The screen reads from the top: **Recognition engine**, then a **Languages** group with **Voice typing language** and **Engine for each language** (see [Language and engine](#language-and-engine)), then **Dictation** with the view, the mode, **Keep listening**, **Stop after a pause**, **Phrase end detector** (with **Speech threshold** and **Smart Turn** under it, while Asr4All, Sherpa, Omnilingual or Vosk is on the list), **Spoken punctuation**, **Add punctuation and capitals** and **Tidy dictation with AI**, then **Other apps** with **Voice input for other apps**. The rest sit in a closed **Advanced** fold under it: **Hold the tool to pick a mode**, **Reopen voice typing where you left it**, **Pause media during voice typing**, the microphone switch and pick, **Change engine from the voice tool**, **Hold to talk** and **Reduce background noise**. **Words to listen for** is a fold of its own. At the top of the screen, **Recognition engine** is a [provider list](https://wmkeyboard.pages.dev/tools/providers/) of dictation engines. Dictation uses the first one on it that is switched on and can run on the phone; the others are there for a language that has an engine of its own, and the phone's recognizer answers when nothing on the list can. **Add a provider** offers **Phone's recognizer**, **Whistle** on a 64-bit ARM phone, a small fast model for seven languages (see [Offline voice (Whistle)](https://wmkeyboard.pages.dev/tools/whistle/)), **Whisper** in an edition with the offline engine built in (see [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/)), **Asr4All**, English dictation that shows the words while you speak (see [Offline voice (Asr4All)](https://wmkeyboard.pages.dev/tools/asr4all/)), **Parakeet**, 25 European languages that the model tells apart (see [Offline voice (Parakeet)](https://wmkeyboard.pages.dev/tools/parakeet/)), **Moonshine**, a small quick English model (see [Offline voice (Moonshine)](https://wmkeyboard.pages.dev/tools/moonshine/)), **Sherpa**, streaming models for 13 languages including Bangla that type while you speak (see [Offline voice (Sherpa)](https://wmkeyboard.pages.dev/tools/sherpa/)), **Omnilingual**, one model for about 380 keyboard languages, many with no other voice typing (see [Offline voice (Omnilingual)](https://wmkeyboard.pages.dev/tools/omnilingual/)), **Vosk**, small models for 29 languages that type while you speak and suit an older phone (see [Offline voice (Vosk)](https://wmkeyboard.pages.dev/tools/vosk/)), and **Transcription server** in every build, which sends each phrase to a speech-to-text server you run (see [Transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/)). A transcription server can be added more than once, each with its own address, key, model and route; the first one on the list is the one dictation sends to. Press an engine's row to open its own screen: its models for an offline engine, **Recognition service** and **Prefer on-device recognition** for the phone's recognizer, **Translate to English** for Whisper, and what every server is sent with for a transcription server. Until you edit the list, it is worked out from your earlier choice: that engine first, then your transcription server if you had one, then the phone's recognizer. "Use the system recognizer" on the voice panel moves the phone's recognizer to the top of the list. The phone's recognizer's **?** carries this note about what it sends: > Recognition uses the speech recognizer of your device. On Android 12 and later it runs on your device when the language model is installed. If it is not, the audio goes to the recognizer service while you speak. Press and hold the microphone to speak in walkie-talkie style. It stops when you let go. ### Turning voice typing off If you never dictate, turn off **Use voice typing** at the top of the Voice typing screen. With it off, the keyboard never opens the microphone: - The Voice tool leaves the toolbar and the toolbox, and a key, a hardware shortcut or a press-and-hold bound to it does nothing. On the Tools screen its row says that voice typing is off, and its own switch can't be turned on until you turn voice typing back on. - The keyboard's own boxes (the search, translate and AI fields, and the rest) lose the microphone at the end of their strip. - The [collapsed bar](#the-collapsed-bar) doesn't show, even if you left the keyboard collapsed to it. - A dictation that's running when you switch it off stops, and its view closes. Nothing of voice typing is loaded while it's off. The system recognizer is only asked for when a dictation starts, and an [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) model is only read when a phrase is transcribed, so a keyboard that never dictates never holds either one in memory. The rest of the screen is hidden while the switch is off, but your choices are kept for when you turn it on again. ### Words to listen for A recognizer trained on everyone's speech spells a name its own way. The **Words to listen for** group tells it about yours. It shows for the system recognizer, the transcription server, [Whistle](https://wmkeyboard.pages.dev/tools/whistle/), Whisper's whisper.cpp models and [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/#words-to-listen-for). Engines that can't take a hint (Whisper's older LiteRT models, Asr4All, Parakeet, Moonshine, Omnilingual, Vosk) don't show the group while they're the first engine on the list. - **Listen for my words** (on by default) sends a list of your own words with each phrase: the words you added by hand, the words in Android's personal dictionary (when **Use the Android dictionary** is on), and the words the keyboard learned from your typing that no word list has, such as names, most used first. Common words the recognizer already knows are left out, since they don't help. The system recognizer also gets permission to use what it knows about your phone itself, such as your contacts; the keyboard never reads your contacts for this. - **More words to listen for** takes your own list, separated by commas. These go first, ahead of the words from your dictionary. The system recognizer gets up to 200 words as a biasing list. It reads the list on Android 13 and later, and only if the recognizer on your phone supports biasing: Android leaves that to the recognizer, and one that doesn't ignores it. A transcription server gets up to 150 words as part of its prompt; see [Words and the prompt](https://wmkeyboard.pages.dev/tools/transcription-server/#words-and-the-prompt) for why that list is shorter and how it's ordered. Sherpa gets the same 200 as hotwords, less any its model can't spell. Tools / Voice typing The tool page carries only what belongs to the button: - **All voice typing settings**: opens the screen above. - **Enabled**: whether Voice typing appears on the toolbar and in the toolbox. Turning the tool off only takes the button away; the microphone is still reachable from a bound key and from the keyboard's own boxes. To switch voice typing off everywhere, use [Use voice typing](#turning-voice-typing-off) instead. - **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color. - **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel. ## Voice input for other apps Voice typing / Voice input for other apps Many apps have a microphone button of their own: the search box in a browser or a launcher, a notes app, a messaging app's voice search. Those buttons ask Android for its voice input, which on most phones is Google's. Turn on **Voice input for other apps** and WM Keyboard joins the list Android picks from, so those buttons can hear with the engines set up on this screen, with the keyboard closed. Turning the switch on changes nothing by itself. Pick WM Keyboard as the phone's voice input with **Choose the phone's voice input**, the row that appears under the switch. It opens Android's own page for it. Where that page lives depends on the phone: under **Voice input** in the language and input settings, or behind the gear of **Digital assistant app** in **Default apps** on recent Android versions. Turn the switch off again and WM Keyboard leaves the list; Android then goes back to another voice input app on its own. There are two ways an app can ask, and both are covered: - **The voice input window.** Most microphone buttons open a small window over the app. WM Keyboard's comes up at the bottom of the screen and starts listening at once, with the app's own prompt or "Speak now" over the microphone. Engines that hear as you speak (Sherpa, Omnilingual, Vosk, Asr4All) show the words as they come. The words go back to the app when you pause, or when you tap the microphone. Cancel, Back or a tap above the window returns nothing. If no engine is ready for the language, the window says so and offers **Set up**, which opens this screen. - **The speech recognizer.** Some apps, and the assistant, talk to Android's speech recognizer without opening a window. Once WM Keyboard is the phone's voice input, it answers those requests the same way, and apps that show the words as they come get them from the engines that can tell. Each request uses the engine of its language, as the keyboard's own microphone does. The language is the one the app asks for when WM Keyboard knows it, so a search box set to French is heard in French; otherwise it is your **Voice typing language**, or your first keyboard language when that follows the keyboard. A phrase ends at a pause. **Stop after a pause** sets how long; it is off by default for the keyboard, where you stop with the microphone, so other apps get 1.5 seconds unless you set it or the app asks for another length. **Spoken punctuation**, **Add punctuation and capitals**, **Reduce background noise**, **More words to listen for** and the picked microphone all apply. Things that only make sense in a text field, such as undo, the spacing around the cursor and AI tidying, are left to the app. Where the audio goes follows the engine you picked, and never anywhere else: - An offline engine (Whisper, Whistle, Sherpa and the rest) keeps it on the phone. If its model isn't downloaded, another downloaded model for the language stands in; if there is none, the request ends with an error rather than going to a recognizer you didn't pick. - A transcription server gets the phrase. Without an address, a downloaded model stands in. - With **Phone's recognizer** as the engine, the request goes on to another app's recognizer: the one picked under **Recognition service**, or the phone's own. On a phone with no other recognizer, a downloaded model stands in. With **Use voice typing** off, WM Keyboard's engines never hear anything. If WM Keyboard is still the phone's voice input then, each request goes straight on to another app's recognizer, so other apps' microphones keep working. The first request needs the microphone permission. The voice input window asks for it. A request through the speech recognizer can't ask, so WM Keyboard opens its permission prompt over the app if Android allows it, and the request ends with a permission error until you grant it. ## Privacy: what leaves your device On-device recognition (Android 12+, once the language's model is installed) never sends audio anywhere. Without that model installed, dictating with the system recognizer sends your audio to the OS's speech recognition service while you talk (the same behavior any app using Android's standard speech APIs has). See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the fuller picture of what WM Keyboard can and can't send. Want a hard guarantee that audio never leaves the device, whichever model is installed? [Offline voice typing with Whisper](https://wmkeyboard.pages.dev/tools/whisper/) is a second dictation engine that transcribes entirely on-device. It's a bigger download and it doesn't stream words live the way the system recognizer does. In exchange it makes no network calls at all, beyond the one-time model download. Want the phone to do less work instead? A [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) sends each phrase to a server you choose, on your own network or a hosted service, and types what comes back. The audio goes to that address and nowhere else. With **Listen for my words** on, the list of your own words goes to the same place the audio goes: the system recognizer, or your transcription server. On-device recognition, and the offline engines (Whistle, Whisper, Sherpa), keep it on the phone too. With **Tidy dictation with AI** on, the text of each phrase (never the audio) also goes to the AI tool's provider. Pick an on-device model there to keep it on the phone. [Voice input for other apps](#voice-input-for-other-apps) sends the audio of another app's request to the same place the keyboard's microphone would, and the words back to that app. The app gets the text, as it would from any voice input. ## Details & edge cases **Errors show specific messages.** A network problem says "No connection. Check your network and try again." A busy recognizer, or another app holding the mic, says "The microphone is busy. Close the other apps that use it and try again." A language the device can't recognise at all says "Speech recognition does not support this language on your device." A network hiccup mid-utterance keeps whatever was already heard instead of throwing it away. The Microphone access tile in Quick Settings, or the developer Sensors off toggle, can let a session start and then hand it silence. The panel notices and says "The microphone is blocked. Turn on Microphone access in Quick Settings, then try again." Either bar says "Microphone blocked in Quick Settings". Tap the mic again once you've turned access back on. **Switching tools closes dictation.** Opening any other panel (Handwriting, Clipboard, Emoji, and so on) ends the current dictation session first. **Every row carries the usual restore button** once you move it off the default listed above. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). Where the collapsed bar last rested isn't one of those rows. It has no control on the settings screen, and you reset it by dragging the bar somewhere else. **The three views aren't full equivalents.** Continuous mode, spoken punctuation, and undo behave the same way whichever one you're in, because they live in the dictation session rather than in the view. Two things are panel-only: hold-to-talk, and the offline-model download chip. The language chip sits on the panel and, behind the hamburger, on the collapsed bar. The compact bar gets one only with **Language button on the compact bar** on. Without it, dictation still listens for the language you picked, or follows your layout, and you change it in settings or with a layout switch instead. Neither bar prompts you to download an offline model. Related: [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/) covers the second dictation engine, including its model catalog, per-language routing, downloads, and the full offline privacy guarantee. --- # Offline voice (Vosk) URL: https://wmkeyboard.pages.dev/tools/vosk/ Section: Tools Summary: Small offline dictation models for 29 languages, about 40 MB each, with words that appear while you speak: Alpha Cephei's Vosk on your phone. Offline Vosk runs [Vosk](https://alphacephei.com/vosk/android), Alpha Cephei's speech recognizer built on the Kaldi toolkit, on your phone. Its small models are made for phones: most are about 40 MB, load in a second or two and run on older hardware. They decode while you speak, so the words go into the field as you say them. Each language has its own model, 32 in all for 29 languages. Once a model is downloaded, no audio leaves the phone. A language with no model on the phone uses the system recognizer instead. ## Setting it up Voice typing 1. Add **Vosk** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**. 2. Press **Vosk** on the list to open its screen, which holds the **Vosk models** group. The first time, a banner offers the Vosk library itself: a one-time download of about 10 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub build; the offline build carries it already. 3. Download the model for each language you dictate in. Each one is a zip from alphacephei.com, checked against a SHA-256 built into the app before it is unpacked on the phone. On mobile data it asks first. | Model | Language | Download | Licence | |---|---|---|---| | Vosk English (US) | English | 41 MB | Apache-2.0 | | Vosk English (UK) | English | 43 MB | Apache-2.0 | | Vosk English (India) | English | 38 MB | Apache-2.0 | | Vosk Arabic | Arabic | 104 MB | Apache-2.0 | | Vosk Arabic (Tunisian) | Tunisian Arabic | 166 MB | Apache-2.0 | | Vosk Catalan | Catalan | 43 MB | Apache-2.0 | | Vosk Chinese | Mandarin | 44 MB | Apache-2.0 | | Vosk Czech | Czech | 46 MB | MIT | | Vosk Dutch | Dutch | 40 MB | Apache-2.0 | | Vosk Esperanto | Esperanto | 44 MB | Apache-2.0 | | Vosk French | French | 42 MB | Apache-2.0 | | Vosk Georgian | Georgian | 46 MB | Apache-2.0 | | Vosk German | German | 46 MB | Apache-2.0 | | Vosk Gujarati | Gujarati | 108 MB | Apache-2.0 | | Vosk Hindi | Hindi | 44 MB | Apache-2.0 | | Vosk Italian | Italian | 50 MB | Apache-2.0 | | Vosk Japanese | Japanese | 50 MB | Apache-2.0 | | Vosk Kazakh | Kazakh | 60 MB | Apache-2.0 | | Vosk Korean | Korean | 87 MB | Apache-2.0 | | Vosk Kyrgyz | Kyrgyz | 51 MB | Apache-2.0 | | Vosk Persian | Persian | 53 MB | Apache-2.0 | | Vosk Polish | Polish | 53 MB | Apache-2.0 | | Vosk Portuguese | Portuguese | 32 MB | Apache-2.0 | | Vosk Russian | Russian | 46 MB | Apache-2.0 | | Vosk Spanish | Spanish | 40 MB | Apache-2.0 | | Vosk Swedish | Swedish | 304 MB | MIT | | Vosk Tajik | Tajik | 52 MB | Apache-2.0 | | Vosk Telugu | Telugu | 61 MB | Apache-2.0 | | Vosk Turkish | Turkish | 37 MB | Apache-2.0 | | Vosk Ukrainian | Ukrainian | 144 MB | Apache-2.0 | | Vosk Uzbek | Uzbek | 51 MB | Apache-2.0 | | Vosk Vietnamese | Vietnamese | 34 MB | Apache-2.0 | These are the small models from [Alpha Cephei's list](https://alphacephei.com/vosk/models). The large server models (1 to 2 GB each) are left out: they need several gigabytes of memory, which a phone keyboard cannot spare. Unpacked, a model takes a little more room than its download. ## How it works While you speak, the words go into the field as underlined composing text, rewritten as they grow, and the line under the microphone shows them too. When the phrase ends, the finished text replaces them and goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). **Where a phrase ends.** Vosk splits what it hears at its own short pauses, but those only divide the phrase; they never end it. The phrase ends after the pause set in **Stop after a pause**, heard by the [phrase end detector](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase) you picked, the same as for Asr4All. Vosk models carry no Silero, so **Auto** uses loudness until ONNX Runtime and a Silero copy are on the phone. With the setting off, the phrase runs until you press the microphone or two minutes pass. **Which model.** When two downloaded models know the language, the larger one dictates, except that the American English model is used over the British and Indian ones and the Arabic model over the Tunisian one. The model list marks each one in use, and **Engine for each language** can pin a language to a particular model. **Punctuation.** Vosk writes lower-case words with no punctuation. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a finished phrase gets full stops, commas, question marks and sentence capitals from the punctuation model for 88 languages, once that is downloaded. The Chinese and Japanese models put a space between words, which those languages do not write, so the spaces between their characters are taken out. ## Vosk, Sherpa or the others Vosk is the light one. Its models are a fraction of the size of [Whisper](https://wmkeyboard.pages.dev/tools/whisper/)'s or [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/)'s and need far less memory, so it suits an older or low-memory phone, and it streams words while you speak in many languages the other streaming engines lack: Catalan, Czech, Dutch, Esperanto, Georgian, Gujarati, Hindi, Italian, Kazakh, Kyrgyz, Persian, Polish, Portuguese, Swedish, Tajik, Telugu, Turkish, Ukrainian and Uzbek. Whisper and Parakeet are more accurate when the phone can run them. Where [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) has a model for your language, its Zipformers are usually more accurate than Vosk's small models too. Alpha Cephei's own tests put most small models at 10 to 30% of words wrong; the Telugu model is far weaker, with most words wrong on its test set. ## Details & edge cases **Words to listen for.** Vosk does not take them. Its small models can be told a fixed list of phrases to choose from, which suits voice commands, not dictation, so the **Words to listen for** group stays hidden while Vosk is the engine. **Several languages at once.** Each Vosk model knows one language, so a dictation that listens for more than one at a time cannot run on Vosk. The panel asks for a model, or another engine takes it. **Not in the F-Droid build.** The Vosk library is Alpha Cephei's prebuilt binary, and F-Droid builds everything it ships from source, so the F-Droid build leaves Vosk out. **Where the library comes from.** The app never carries a rebuilt copy of the Vosk library. The Play build gets it from Play, the offline build carries Alpha Cephei's own file inside the APK, and the GitHub build reads Alpha Cephei's file straight out of their vosk-android package on Maven Central, checked against a SHA-256 built into the app. An 8 KB bridge of the keyboard's own talks to it. See [Network activity](https://wmkeyboard.pages.dev/privacy/network/). **The licences.** Vosk is Apache-2.0, built on Kaldi and OpenFst (both Apache-2.0). The models are Apache-2.0, apart from the Czech and Swedish ones, repackaged from the Rhasspy project under MIT. All of them are credited in the app's About screen. --- # Offline voice (Whisper) URL: https://wmkeyboard.pages.dev/tools/whisper/ Section: Tools Summary: Dictation that never touches the network: Whisper models running on your device. Offline Whisper is a second dictation engine that runs entirely on your phone. Instead of streaming audio to a speech-recognition service, it records what you say and transcribes it locally with an OpenAI Whisper model, so nothing ever leaves the device. For English, German, French, Spanish, Italian, Dutch and Polish there is also [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/): one 17 MB model that answers faster, where Whisper has a catalog that covers far more languages. For English alone, [offline Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) writes the words while you are still speaking. tools/whisper-engine-choice — Recognition engine choice on the Voice typing screen. ## Why offline dictation The system recognizer, covered in [Voice typing](https://wmkeyboard.pages.dev/tools/voice/), is fast and shows words as you speak. It also leans on your OS and its installed languages, and without the on-device language pack it sends audio to a recognizer service. Offline Whisper trades all of that away for a hard guarantee: it works across many languages and never sends audio anywhere. You feel that trade every time you dictate, because Whisper doesn't stream. It records into a buffer and transcribes the whole clip once you stop talking, or once you hit the 30-second cap (see [Details & edge cases](#details--edge-cases)). The panel shows "Listening. Press when you are done." rather than live partial words. That press doesn't need aim: the whole panel left of the action rail takes it, so does anywhere on the compact bar, and so does the Voice tool on the toolbar (see [Starting dictation](https://wmkeyboard.pages.dev/tools/voice/#starting-dictation)). ## Two kinds of model Offline Whisper runs two kinds of model, and the catalog lists both. - **whisper.cpp models** run on [whisper.cpp](https://github.com/ggml-org/whisper.cpp), which is built into the app. They are the ones pointed at first, and they can do everything below that the other kind can't: - **Any language, by name.** Each of Whisper's 99 languages, Bangla included, can be told to the model instead of left for it to guess. A dictation in [several languages](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) is detected among those languages only, so a short phrase is never read as some third one. - **Your own words.** The same **Words to listen for** list the system recognizer gets goes to the model as a prompt, so names and terms come out spelled your way. - **Words while it works.** The line under the microphone shows the transcript as it is decoded, instead of only saying "Transcribing". - **Longer clips.** A clip can run to two minutes instead of 30 seconds. - **A closer search** on the smaller sizes (beam search), which gets a few more words right, and a transcription you cancel stops at once instead of finishing for nobody. - Each one is a single `.bin` file, checked against its published SHA-256 once it downloads. - **LiteRT models** are the community `.tflite` conversions the app started with. Each has the whole decoding loop compiled in, so none of the above can be asked of it, and a few can only be told one of a fixed set of languages. They still work, and a model you already downloaded keeps working. Where whisper.cpp is available their names end in "(LiteRT)". whisper.cpp is in every build, on 64-bit and 32-bit ARM phones and on x86_64. A phone whose cores all have the ARMv8.2 dot-product instructions (most from 2018 on) gets a build that uses them, which is several times faster at the arithmetic these models are made of. On a phone with a Qualcomm Snapdragon chip (an Adreno GPU), the whisper.cpp models can also run on the GPU; see [Running on the GPU](#running-on-the-gpu). Bigger models trade download size and speed for accuracy: - **Tiny**: fastest multilingual model. Fine for clear speech, struggles with noise. - **Base**: balanced accuracy and speed. - **Small** *(recommended)*: the best accuracy that still runs comfortably on a phone, noticeably better on accents and background noise than Base. - **Medium**: near-desktop accuracy. Expect several seconds per phrase and close to a gigabyte of RAM while it runs. - **Turbo**: large-v3-turbo, so it's about as accurate as Large at half the download, and much better than the small models at working out which language you're speaking. An 820 MB download. It was trained for transcription only, so translation isn't offered for it. - **Large**: the most accurate graphs there are, and far too big for most phones. A 1.5 GB download that only high-memory devices can load. Two are listed, and if you're going to spend the 1.5 GB, **Large v3** is the one to take: it's the newer model and it recognises languages much more reliably than the original Large. ## The model catalog On the Play Store build the interpreter that runs the LiteRT models is not in the app you install: it is a small extra part (about 2 MB) that Google Play delivers the first time you need it. The Whisper settings screen offers it at the top until it is here, the keyboard asks for it on its own the first time you dictate with a model downloaded, and until it lands dictation says so instead of recording. Direct-download builds carry it from the start. The F-Droid build fetches whisper.cpp's library the first time too, but only after you turn on **Settings › Privacy › Feature libraries**, which F-Droid requires apps to ask for. Until then the Whisper screen shows a banner that turns it on, and dictating from the keyboard says the downloads are off rather than that something is downloading. The offline build can't download it at all, so the keyboard points you to the import on the same screen. The catalog has 10 whisper.cpp models: Tiny, Base, Small, Medium, Turbo and Large v3, which take any language, and English-only Tiny, Base, Small and Medium, which show once English is enabled. Tiny to Small are 8-bit, which loses nothing measurable; Medium and up are 5-bit, which keeps the downloads to 540 MB for Medium, 574 MB for Turbo and 1.1 GB for Large v3. Beside them sit 29 LiteRT models. Ten are multilingual graphs: seven auto-detect the spoken language, and three are grouped models you can pin to one language. The other 19 are single-language models covering 13 languages, several at more than one size: English, German, Spanish, French, Hindi, Italian, Japanese, Portuguese, Russian, Tamil, Telugu, Urdu, and Chinese. A single-language model only appears in the catalog once you've enabled that language on the Languages screen. tools/whisper-catalog — Model catalog with size and language filters, grouped by tier. The screen is four sections in a row: the per-language routing list, then **Your voice models**, then a short **Suggested for your languages** shortlist, then a collapsed browse section holding everything else. Browse carries one filter, a chip per size, and it splits what's left into the models that take any language and the ones locked to a single one. Each row wears its size, how many languages it covers, and its download size as small chips. Small is the only model carrying a Recommended badge: the whisper.cpp one where it runs, else the LiteRT one. Turbo and Large v3 need a 128-band mel spectrogram, where every older Whisper model needs 80. Each one downloads a different filterbank file alongside its graph, and the audio pipeline reads the band count from that file. If the two files ever disagree, dictation fails with a message instead of decoding noise into confident-looking words. ### When a language can only be guessed at With a whisper.cpp model nothing has to be guessed: it is told the language you dictate in, whichever of Whisper's 99 it is. The rest of this section is about the LiteRT models, and only matters if you use one. Every LiteRT model has to decide which language you're speaking, unless it was built for one language or belongs to a grouped model that takes the language as an input. The grouped models list 40 and 26 languages, and those lists are compiled into the graph itself, so a language outside them cannot be forced no matter what the app asks for. For those languages, and Bangla is one of them, a model can decide you're speaking a bigger neighbouring language and hand back fluent sentences in the wrong one. Nothing errors, because as far as the model is concerned it did its job. Two things help: - The model-per-language list says so on any affected language, and points at Turbo and Large v3, which get the language right far more often than the small models do. - When the wrong language happens to be written in a different alphabet, WM Keyboard converts it back. Bangla read as Hindi is the common case: the model still transcribed the Bangla words it heard, it just spelled them in Devanagari, and the two alphabets line up closely enough to map across. You get readable Bangla instead of a line you can't read at all. It can't rescue a wrong language, only a wrong alphabet. Whisper itself has an "initial prompt" that biases spelling and vocabulary, and its own way of forcing a language. The whisper.cpp models use both. The LiteRT models can't: the community `.tflite` conversions were exported with the entire generation loop traced inside the graph. That leaves two inputs: the spectrogram, and on a grouped model a number selecting one of the languages it was built with. ### Picking a model per language ![Model-per-language routing list, with a coverage warning on one row.](@assets/screens/tools/whisper-routing.webp) Rather than one global model for every language, WM Keyboard resolves a model per enabled language and falls back in this order: 1. A model you've explicitly pinned to that language, if it's downloaded and covers it. 2. The largest downloaded single-language model for that language. 3. The model that **Model for other languages** names, if it's downloaded and covers the language. See [Options](#options). 4. The best-ranked downloaded model that covers the language at all. 5. As a last resort, that same fallback or the first downloaded model, even if it can't actually transcribe that language. That last step is deliberate: it produces wrong words rather than a dead microphone. When a language's assigned model can't cover it, the row is flagged in the error color with a note to download one that does. ## Downloading and managing models ![A model mid-download, with progress and the storage summary below the list.](@assets/screens/tools/whisper-download-progress.webp) Each catalog row carries its own **Download** button on the right. Tapping the row itself does something else. It expands the row to show what the model is and which languages it covers, so you can read up before spending the bandwidth. Once a model is on disk, that button becomes a delete icon. A failed attempt offers **Retry**, and an interrupted one offers **Resume**. A whisper.cpp model downloads as one `.bin` file from a fixed revision of the whisper.cpp project's repository, and is checked against its published SHA-256 before it counts as downloaded; a damaged file is deleted and the row says so. A LiteRT model downloads as two files (the `.tflite` graph and a companion vocabulary/filterbank file), with the smaller vocabulary file fetched first so a broken connection fails fast. Downloads are resumable: if you lose connection or leave the app, the next attempt picks up where it left off instead of restarting. Only one model downloads at a time. Before it starts, WM Keyboard checks that you have enough free space: the remaining bytes plus a safety margin. If you don't, the download stops with a message telling you roughly how much to free up. On a metered connection, any model at or above roughly 150 MB asks you to confirm before it uses mobile data. Downloaded models live in the app's private storage, not shared storage: the screen shows total space used and lets you delete a model to reclaim it. Deleting a model also clears any per-language pin that pointed at it, and clears the app's own fallback if that was the deleted model. If an app update ever drops a model from the catalog, leftover files show up as reclaimable space with a one-tap "Free up" button. If you downloaded **Tiny (English)** in version 0.5.8 or earlier and dictation with it came back empty most of the time, that was the model file, not your microphone. Its source has since changed to a conversion that works, and the old file is recognised and discarded, so the model shows as not downloaded until you download it again. The English-only LiteRT models (Tiny, Base, Small and Medium, each marked English) had a subtler fault of their own. Their conversion told the model to start every phrase from the wrong prompt, so a short phrase often came back empty, and more often still with other people talking nearby. The keyboard now corrects that inside the file the first time each one is used, so there's nothing to download again. The first phrase after the fix takes a moment longer while it happens. On recorded test speech it took Tiny (English) from 12 to 9 wrong words in a hundred, and from five empty results in sixteen short phrases to none. A phone that cannot download can import a model instead. Below the catalog, **Get the files** lists both files of each model that suits your languages, to fetch in a browser on any device, and **Import** installs them: pick the `.tflite` and its `filters_vocab_*.bin` together, or one after the other in either order. A model imported without its vocabulary file is reported as still needing it. See [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/#voice-models). When exactly one model is on disk, the app quietly adopts it as that fallback, so dictation works right after your first download with no setup step in between. It does the same again if you delete whichever model was holding the job. ## Options Voice typing The **Recognition engine** list on the **Voice typing** screen is where you add Whisper; **Add a provider** offers it in any build wherever whisper.cpp or LiteRT can run. Move it to the top to make it the engine for every language. Press its row to open Whisper's own screen, which holds the rest of the table: **Translate to English**, **Compute** and the model catalog. | Setting | Default | What it does | |---|---|---| | Recognition engine | System recognizer | Switches dictation between the system speech recognizer and offline Whisper. On the F-Droid build it offers the whisper.cpp models only, since that build has no LiteRT. | | Translate to English | Off | Speaks any language and types its English translation instead of a transcription, when the routed model supports it. | | Model per language | Automatic | Pins a specific downloaded model to a language, overriding automatic routing. One row per enabled language, each with a **Change** button. | | Model for other languages | Automatic | The fallback step 3 of the routing order uses. Only shown once more than one model is on disk. | | Compute | Automatic | Where the whisper.cpp models run: **Automatic**, **CPU** or **GPU**. Only shown once a whisper.cpp model is on disk. See [Running on the GPU](#running-on-the-gpu). | **Model for other languages** sets that fallback by hand. It sits under **Your models**, below the per-language list, and it only appears once you have more than one model on disk, where the choice means something. **Automatic** is the default and keeps the old behaviour: the best downloaded model that covers the language you're typing in. Pick a model instead and every unpinned language uses that one. Dictation still resolves per language, from the language of the active layout, the same way the system recognizer follows the layout's locale. A [Voice typing language](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) you picked takes the layout's place. With several picked, the model for the first is used and left to detect which one you speak, so pick a multilingual model that covers them all. The app also fills the fallback in for you when a model is the only one on disk, and clears the choice if you delete that model. Nine of the 10 multilingual models support translation. Turbo doesn't, because large-v3-turbo was trained for transcription only, and none of the 19 single-language models do either. If a language routes to one of those, the translate toggle has no visible effect for it. ### Running on the GPU **Compute** decides where the whisper.cpp models run. The LiteRT models always run on the processor (CPU), whatever it says. - **Automatic** *(default)*: the GPU for Base and larger models on a phone with a Snapdragon chip (an Adreno GPU). Tiny stays on the CPU, which reads a phrase about as fast as the GPU takes to get going. Other GPUs are used only when you pick **GPU**. - **CPU**: always the processor. - **GPU**: the GPU for every size, Tiny included. On Qualcomm's Adreno, the GPU in Snapdragon phones, whisper.cpp runs through the OpenCL driver, and its GPU code comes with the engines. Other GPUs (Mali, Xclipse, PowerVR) run it through Vulkan, from a build of whisper.cpp that is a separate download of about 12 MB (38 MB on the phone), fetched when you pick **GPU**: it carries every shader compiled in. On many of those GPUs it is no faster than the CPU, which is why **Automatic** leaves them out. A phone with neither runs on the CPU whatever you pick; the row's subtitle says so. The GPU is quickest at the part of the work that reads the sound (the encoder), which is most of the wait with Small and the bigger models, and it frees the processor while it runs. The words come out the same, give or take a comma in a rare phrase, since the GPU does its sums at a different precision. The first dictation on the GPU after an install or an update can take some seconds longer while the GPU prepares itself; the app keeps what it prepared, so later ones start at once. If the GPU won't start, has no room for the model, or stops in the middle of a phrase, that phrase goes to the CPU and comes out as usual, and from then on the CPU runs that model on this phone. The subtitle says when that has happened. Pick **GPU** again to give it another try; an update of the app or of Android does the same. ## Details & edge cases **A whisper.cpp clip runs to two minutes.** whisper.cpp moves Whisper's 30-second window along a longer clip, so its models record for up to two minutes before a clip ends by itself, with the same countdown over the last five seconds when dictation stops there. A transcription works through it window by window, and the words appear under the microphone as they come. **A LiteRT clip is a fixed 30-second window.** Whisper always processes a full 30-second spectrogram: a short utterance is padded, and hitting the 30-second mark auto-triggers transcription of whatever was captured. With **Keep listening** off, anything you say after that mark isn't recorded, so for the last five seconds the line under the microphone counts down ("Stops in 3 seconds") instead of saying "Listening", and it turns to "Transcribing…" the moment recording ends. The model's own work is therefore roughly constant per phrase rather than scaling with how long you spoke. The preparation in front of it isn't: the padding is silence, whose spectrogram is known without computing it, so only the part you spoke is analysed. The model also loads while you're still talking instead of after you stop, so the first phrase of a session doesn't wait for it. **Long dictation keeps every word.** With **Keep listening** on, the microphone never closes between clips. When one ends by itself, at the 30-second mark (two minutes on whisper.cpp), after a pause, or at a key on the panel, the next clip starts recording from the very next moment while the last one is transcribed, so you can keep talking through "Transcribing…" and nothing you say in that gap is lost. Its words land after the last clip's, in order. In a clip's last three seconds a short pause between words ends it early, so the cut falls between two words rather than through one, and there is no countdown because nothing is cut off. The microphone indicator stays on the whole time. Pressing stop, typing on the keyboard or closing the voice surface still closes the microphone at once. Engines that turn speech into text as you talk, [asr4all](https://wmkeyboard.pages.dev/tools/asr4all/), [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) and [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/), keep their own clip handling. **A silent clip types nothing.** Whisper has no way to answer "I heard nothing". Hand it a silent window and it writes the likeliest caption for one, which after training on subtitled video is "Thank you." or "Thanks for watching!". So a clip shorter than 0.3 seconds, or one whose loudest moment is no louder than a quiet room, is never transcribed at all. A clip that is faint but not silent is transcribed, and if all that comes back is one of those stock phrases it isn't typed. Say "thank you" at speaking volume and it's typed like anything else. A transcript that is only a sound caption, such as `[Music]` or `(coughs)`, is never typed, and a word or short phrase the decoder repeats five or more times in a row is folded back to one. The [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) gets the same treatment, which also saves the upload. **A pause can end the clip.** Set **Stop after a pause** under Voice typing (off by default, 0.5 to 4 seconds) and the clip ends by itself once you have said something and then gone quiet for that long, instead of waiting for a press or the 30-second mark. The quiet before you start talking never counts. With **Keep listening** off that ends dictation too, so the whole exchange is hands-free; with it on, the next clip starts at once on the same microphone, as described below. **An open microphone winds down.** With **Keep listening** on, a full 30-second clip with nothing in it starts the next one quietly, but only twice in a row; after that dictation goes idle. The [type-while-you-speak modes](https://wmkeyboard.pages.dev/tools/voice/#typing-while-you-dictate) allow twelve, as they do for the system recognizer. **A microphone that drops out keeps what it had.** If capture fails in the middle of a clip, because another app tore it down or the audio service restarted, what you said up to that point is transcribed as though you had pressed stop. If it fails before anything was recorded, the panel says the microphone couldn't be opened. **Only three rows here can be reset.** **Translate to English**, **Model for other languages** and **Compute** are ordinary settings, so each grows the usual restore button once you move it off the default. The **Recognition engine** list has a **Reset to default** row of its own once you change it. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). The per-language routing rows and the catalog aren't settings rows at all, so they have no button. You undo a pin by opening that language's dialog and picking **Automatic** again. **Speed has no benchmarked numbers.** The app carries no measured per-device timing table, for the CPU or the GPU. The only official guidance is the relative sizing above, with Tiny fastest and the two Large graphs slowest and heaviest on RAM, plus a general note that bigger models are more accurate and slower to download and to transcribe. **Every build, different catalogs.** The whisper.cpp models run in every build, F-Droid's included. The LiteRT models need the LiteRT runtime, which the F-Droid build doesn't carry, so its catalog lists the whisper.cpp models alone. A build made without the whisper.cpp library offers no Whisper engine at all. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/). **Power saving can silently override your choice.** Its "On-device models" option is on by default. Power saving never turns on by itself unless you set **Turn on by itself**, but once it's on, by hand or by a trigger you chose, dictation falls back to the system recognizer even with Whisper selected and a model downloaded. If dictation seems to have switched engines on its own, check your power-saving settings first. **No mic opens without a model.** With Whisper selected and nothing downloaded, the panel says so and offers both ways forward rather than opening the microphone. One chip opens this screen so you can download a model. The other switches back to the system recognizer and starts listening right away, and that switch is saved, the same as changing the engine in settings. The compact bar has room for one line, so it points you at settings, where both choices live. **Panel keys don't end a recording.** Space, backspace and enter on the voice panel's right-hand rail edit the text while the microphone stays open, so you can add a space or fix a character without losing what you've said. A keystroke that lands while a clip is being transcribed behaves the same way. The audio is already captured, so the words still arrive. They just land after whatever you typed. Typing on the keyboard itself during a system-recognizer dictation still ends that utterance, because its live partial text sits in the editor and a keystroke inside it would corrupt it. **Privacy and downloads are separate concerns.** Transcription itself makes no network calls. The only network activity Whisper causes is fetching a model file over HTTPS from its public repository when you tap download (see [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full picture of what WM Keyboard can send and when). **Attribution.** The whisper.cpp models run on whisper.cpp and ggml (MIT, the ggml authors), and are the whisper.cpp project's quantized conversions of OpenAI's weights. The LiteRT models run on TensorFlow Lite, branded LiteRT (Apache-2.0, Google). The model architecture and weights are OpenAI's Whisper (MIT). The `.tflite` graphs themselves are third-party conversions hosted on Hugging Face, not built by this project. Full credits are in the app's About screen. Related: [Voice typing](https://wmkeyboard.pages.dev/tools/voice/) covers starting dictation, strip vs. panel mode, continuous mode, and spoken punctuation, all shared behavior between the system recognizer and Whisper. --- # Offline voice (Whistle) URL: https://wmkeyboard.pages.dev/tools/whistle/ Section: Tools Summary: Fast dictation on your phone in seven languages, with one small Cactus Whistle model. Offline Whistle is a dictation engine that runs entirely on your phone, like [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/), but with one small model instead of a catalog. [Cactus Whistle](https://huggingface.co/Cactus-Compute/whistle) is a 16.9 MB speech recognition model by Cactus Compute, run by their Needle engine. Once the model is downloaded, no audio leaves the phone. It was contributed by [@Guillermode20](https://github.com/Guillermode20). ## Whistle or Whisper | | Whistle | Whisper | |---|---|---| | Languages | English, German, French, Spanish, Italian, Dutch, Polish | close to a hundred, depending on the model | | Download | one model, 16.9 MB | a catalog of 29, from about 40 MB to 1.5 GB | | Words to listen for | yes, as words to favour | no | | Translate to English | no | on most multilingual models | | Devices | 64-bit ARM phones (arm64) only | every phone, in every build but F-Droid's | Cactus's published figures put Whistle well ahead of Whisper Base on speed and ahead on several English and European test sets, though not all of them. Those are the model makers' own measurements on a desktop processor, not this app's on a phone. Pick Whistle when you dictate in its languages and want a small download and a quick answer. Pick Whisper when you need any other language, or want to choose a bigger model for accuracy. ## Setting it up Voice typing / Recognition engine Add **Whistle** to the **Recognition engine** list and move it to the top. Press its row to open its screen, where the **Whistle model** group has one row: **Download** fetches the model, a cross pauses it, **Resume** carries on from where it stopped, and the bin deletes it. On a metered connection the download asks first, as every model download does (see [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)). The option is only offered where the engine can run: a 64-bit ARM phone, in any build but F-Droid's. Cactus publishes no engine for x86 phones, and the one they publish for older 32-bit ARM phones cannot be built into an Android app, so those phones do not see Whistle at all. Dictate without the model downloaded and the voice panel says there is no model yet, with the same two ways forward Whisper offers: open settings to download it, or switch to the system recognizer. ## Languages Dictation follows the language of the layout you are typing on, or the language you set under **Voice typing language**. When that is one of Whistle's seven, Whistle hears it. When it is any other language, dictation uses the system recognizer instead, without a prompt, because there is no Whistle model that could help. Set to listen for [several languages at once](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine), Whistle hears them only when all of them are among its seven, and it works out which one you spoke. Otherwise the system recognizer takes the whole session. ## Words to listen for Unlike Whisper, Whistle can be told words to favour. The **Words to listen for** group stays on the screen with Whistle selected, and the same list the system recognizer gets, your own words and the ones you type into **More words to listen for**, goes to Whistle with each phrase. See [Words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for). ## How a phrase is heard Whistle works the way Whisper does in the keyboard: it records the phrase and transcribes it once you stop, when **Stop after a pause** ends it, or at 30 seconds. A clip with no speech in it is never shown to the model, a microphone that drops out keeps what it had, and the panel's own keys type without ending the recording. All of that is described under [offline Whisper's details](https://wmkeyboard.pages.dev/tools/whisper/#details--edge-cases) and applies here unchanged. The model loads while you are speaking your first phrase, so that phrase does not wait for it. It then stays loaded until the keyboard's process ends: the engine has no way to unload a model, and at 17 MB it is not worth reloading. ## Without a connection to your server With the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) as your engine and **Use on-device models offline** on under [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), a phrase that cannot reach the server is transcribed on the phone instead. Whistle does that job when its model is downloaded and it knows the language; otherwise a downloaded Whisper model does. ## Storage and importing The model is kept in the keyboard's private storage and listed under **Storage > Voice models** beside any Whisper models, where it can be deleted too. A phone that cannot download it can import it: fetch [`whistle.cact`](https://huggingface.co/Cactus-Compute/whistle/resolve/b358ddadd89b7a713b5aa131f23032d3cca1b251/whistle.cact) on any device and import it like any other file (see [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/#voice-models)). The download and the import both check the file's SHA-256 before it is used, so only that exact file is accepted. ## Privacy and credits Transcription makes no network calls. The one network request Whistle causes is the model download from Hugging Face, made when you press **Download**. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/). The Needle engine and the Whistle model are Cactus Compute's, under the Apache 2.0 licence. The engine ships in the app as Cactus's own published build, behind a small bridge of the keyboard's own; F-Droid's build carries neither. --- # WM Pass URL: https://wmkeyboard.pages.dev/tools/wm-pass/ Section: Tools Summary: A password book in the keyboard, built on the separate WM Pass password manager: unlock it with your fingerprint or master password without leaving the app, fill a login in two taps, browse and search the vault, type, copy or show a field, add a login, make or change a password, and get WM Pass's own buttons on the strip. The vault stays in that app. The **WM Pass** tool is a password book in the keyboard, built on [WM Pass](https://github.com/wasi-master/wm-pass), a separate password manager from the same author. The keyboard does not hold a vault of its own. It shows what WM Pass lists, asks for one value when you tap it, types it into the field, and keeps nothing. ## Setting it up 1. Install WM Pass and make a vault there. Until it is installed, the tool and its page in the Tools list have a **Get WM Pass** button that opens its page on Google Play. 2. In WM Pass, open **Settings**, then **Security**, and turn on **Let WM Keyboard fill**. It is off until you do this. 3. In WM Keyboard, add the **WM Pass** tool to your toolbar from the Tools list. ## Unlocking without leaving the app When the vault is locked, the tool shows a lock and the ways in. Neither takes you out of the app you are typing in. - **Fingerprint** brings up WM Pass's own fingerprint prompt over that app. WM Pass checks the fingerprint; the keyboard only starts the prompt. Once the vault opens, the keyboard comes back with the tool open on your vault. Close the prompt to go back as you were, or tap **Use password** on it to get WM Pass's unlock screen instead. Without fingerprint unlock set up in WM Pass, the button reads **Unlock in WM Pass** and opens that screen straight away, still over your app. - **Password** (**Type password** when there is no fingerprint) types your master password into the tool's own box, shown as dots. **Unlock** or Enter sends it to WM Pass, which checks it. The box empties as soon as the password is sent. A wrong one shakes the lock and the box is ready for another go; after too many, the tool shows how long WM Pass makes you wait. A duress password does what it does at WM Pass's own door. A vault that also needs a key file or a security key has to be unlocked in WM Pass. A vault opened from the keyboard locks after the time set under **Lock after leaving the app** in WM Pass, counted from when it opened, and never sooner than a minute. **Lock** at the top of the tool locks it at once. ## Using it Open the tool while the field you want is focused. It lists everything in the vault, logins, cards, notes, Wi-Fi networks and every other kind, except what is in WM Pass's trash or on its hidden shelf. Each item wears a coloured letter, the same colour every time, with a small badge for its kind, and a star when it is a favourite. Tap the search box at the top to look for one; it uses WM Pass's own search. Logins saved for the app in front sit above the list as cards, under **Saved for** and the app's name. A browser does not tell a keyboard which site it is showing, so in a browser there are none: search instead. The chips over the list narrow it to **This app**, **Favourites**, **Logins**, **Codes**, **Cards**, **Notes** or **Other**, each with how many it holds; tap the chip again to go back to **All**. A long list is cut into letters. At the bottom, the vault's name and how many items it has. Beside a login, the round buttons type its username, its password and its code straight away. Tap the item itself to open it. ## Filling a sign-in form **Fill**, on the cards above the list and at the top of an open login, fills a sign-in form in one tap. It types the username into the box you are in, moves on to the next box the way the form's Next key or a Tab would, and types the password there once that box is a password box. A form that asks for the password on a second page works too: the password goes in when its box comes up, as long as that is within 15 seconds and in the same app. If the next box is not a password box, nothing more is typed. Tapped while you are already in a password box, **Fill** types only the password. ## An item, opened An open item lists its fields. A plain one, such as a username, a website or a card's expiry date, shows as it is. A secret, such as a password, a card number or a hidden field you added, shows as dots: the keyboard has only its name. - **Type**, the keyboard button, puts that one field into the box you are typing in. A secret comes over from WM Pass only on that tap, and goes straight to the field. - **Show**, the eye beside a secret, shows it for ten seconds, with a ring counting them down, then hides it again. Digits and symbols are coloured so a password is easier to read out or copy by hand. Tap the eye again to hide it sooner. - **Copy** asks WM Pass to copy it. WM Pass puts it on the clipboard itself, marked sensitive and cleared after the time set in WM Pass, so a copied secret never passes through the keyboard. - **Open in WM Pass** opens the item there, once the vault is open. It is left off while you are typing in WM Pass itself. - **Change password**, on an item that has one, opens a box for the new password. **Save** hands it to WM Pass, which keeps the old one in the item's history. ## Making a password The key button at the top of the list opens WM Pass's generator: a new password, made the way you set the generator up in WM Pass, large, with its strength under it. **Type it** types it into your field, **Another** makes a new one, and **Save as a login** starts a new login with it as the password. ## Adding a login The plus button at the top of the list, **Add a login**, opens four boxes: name, username, password and website. The keys type into one box at a time; **Next** moves on, and a tap on any box in the list goes back to it. **New password** fills the password box from WM Pass's generator, set up the way you set it there, and **Show** shows what is in it. A bar under the password rates how hard it is to guess, from **Very weak** to **Very strong**, with a rough count of bits. **Save** hands the login to WM Pass. The name can be left empty: WM Pass names the login after the website, or after the app. A login added while an app is in front is tied to that app by its signing certificate, so it comes first there next time. In a browser it is not, since one browser shows every site; give it the website instead. What you type into these boxes stays in the keyboard's memory until you save or close the tool. It is not learned, is not offered back as a suggestion, and is not kept for undo. ## Two taps, and never in private fields Typing anything from WM Pass takes two taps: one to open the tool, one on the field you want. The tool types and copies nothing into a field that asks the keyboard not to learn from it, such as a private browser tab, and says so instead. ## Buttons in WM Pass's own fields While you type in some of WM Pass's own fields, the strip offers the buttons that sit beside that field there: - In a card's number, **Scan the card**. In the name on the card and the expiry date as well, when WM Pass's card scanner is set to Google's, since only that one reads them. - In a Wi-Fi network's name and password, **Fill in from a QR code** and **Pick a network saved on this phone**. A tap on a chip has WM Pass press that button, so it does exactly what the button does. The field tells the keyboard which buttons it has and nothing about what it holds, and the chips wear the keyboard's own words, so they follow its language. Each WM Pass screen names its fields afresh, so a tap meant for a screen you have left does nothing. The chips work whether or not **Let WM Keyboard fill** is on, since nothing from the vault comes to the keyboard. They give way to word suggestions once those appear. ## The code chip With one-time code chips turned on, focusing a code box in an app that has a login saved in WM Pass offers its current code on the suggestion strip. It uses the same chip as codes read from notifications, and it disappears when the code stops being valid. Tap it to type the code. ## API keys in settings The key fields in WM Keyboard's settings get a WM Pass button once WM Pass has a vault and **Let WM Keyboard fill** is on. They include the key and secret of every service under Providers (translation, AI, search, GIFs and the rest), smart home tokens, the Hugging Face and Telegram bot tokens, the GitHub token for imports, and a voice server's key. On a phone without WM Pass the fields look as they always did. - **Fill from WM Pass** lists the API key items in your vault, with the ones named for that service first. Pick one and its key goes into the field. - **Save in WM Pass** keeps what the field holds as an API key item named for the service, such as "DeepL", or "DeepL (Work)" for a second DeepL key you gave a name. If an item of that name is already there, WM Pass changes its key and keeps the old one in the item's history. A service that has a key and a secret ends up as one item that holds both. If the vault is locked, the button opens WM Pass. Unlock it there, come back, and press the button again. ## Places for copies Both apps can send backups to the same kinds of place: a folder, a server, or a cloud account. A place you set up in one can be picked in the other, so a server's address and password only have to be typed once. - In WM Keyboard, open [Backup & restore](https://wmkeyboard.pages.dev/reference/settings/backup/#locations), press **Add a location**, then **Use a place from WM Pass**. WM Pass opens, asks you to unlock it, and lists its places. Tap one. - In WM Pass, the place chooser has **Use a place from WM Keyboard**. WM Keyboard opens its list of locations, behind its app lock if you set one for Backup & restore. Tap one. The app that receives a place opens it as a new place it hasn't saved yet. Look it over and save it. Each app keeps its own copy from then on: changing or removing the place in one leaves the other alone. A login the server simply checks comes across whole: a WebDAV, FTP, SFTP, SMB, IMAP or NAS password, an SFTP key, an S3 key pair, a Git token. A sign-in does not. Google Drive, Dropbox, OneDrive and Box are signed in to separately by each app, and the token one app holds would not work in the other. Box and Proton Drive also replace their tokens every time they're used, so two apps sharing one would keep signing each other out. pCloud, MEGA and iCloud Drive keep a session too. For all of those, the receiving app asks you to sign in. A folder on the phone comes without the folder, because Android lets each app into a folder only when you pick it there. A folder inside an account that still has its app's default name, such as "WM Keyboard backups", becomes the other app's default. Nothing crosses unless you tap a place on the other app's screen. Each app answers only the other one, checked by its signing certificate, and the place goes straight from one app's memory to the other's. It never passes through a file or the clipboard. A nearby computer paired over KDE Connect belongs to WM Keyboard alone and isn't offered. ## What this means for privacy A password you type this way passes through the keyboard's process on its way to the field, as anything you type does. It goes through the same path as a generated password, so it is not learned, predicted from or added to your typing statistics. So does a password you type into **Add a login** or **Change password** on its way to WM Pass, and the master password you type to unlock: the keyboard hands it to WM Pass and keeps nothing. The keyboard sees every key of your master password when you type it at WM Pass's own door too, so typing it here shows the keyboard nothing more. Nothing is written to disk, and no network is involved. The fingerprint never reaches the keyboard at all. The prompt is WM Pass's own, and the keyboard can start it but cannot answer it. A password you copy in WM Pass instead goes through the clipboard, so the keyboard's [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/#passwords-are-handled-deliberately) sees it like any other copy. When WM Pass wipes the clipboard after the time set there, the history drops that clip at the same moment. Each side checks the other. WM Pass answers only a WM Keyboard signed by a known certificate, and WM Keyboard asks only a WM Pass signed by a known certificate. An app that copied the name gets nothing and is told nothing. If you would rather keep passwords out of the keyboard's process altogether, leave this off and use WM Pass as your device's autofill service instead. --- # Theming overview URL: https://wmkeyboard.pages.dev/themes/overview/ Section: Themes & appearance Summary: Material You, light/dark/AMOLED, and automatic theme switching. WM Keyboard's look is driven by a theme: a full set of colors, gradients, key shapes and corner radii bundled together. Pick a built-in theme, let it follow your wallpaper, switch it automatically between day and night, or [build your own](https://wmkeyboard.pages.dev/themes/editor/) from scratch. A sample of built-in themes. Gradients, translucent keys, pill and cut key shapes, and a hue-cycling animation. ## Choosing a theme Appearance / Keyboard themes themes/gallery-grid — The theme gallery: custom themes on top, built-in themes below. The theme gallery is a two-column grid: the **Default (system)** card first, then your saved custom themes (if you have any), then every built-in theme below them. Tapping a card applies it to the keyboard immediately. A layout, one of its pages or a panel can keep a [theme of its own](https://wmkeyboard.pages.dev/languages/custom-layouts/#a-theme-of-its-own), and that grid then ignores the card you tap here. When one of the layouts you type with does, a notice above the gallery names each place and the theme it uses, for example "the Numpad tab of QWERTY uses Default (system)". **Clear** takes those themes off, so the whole keyboard follows your pick again. The toolbox also has an **Appearance** tool that draws live mini-previews right over the keys. It carries a shortlist rather than the whole gallery. Your custom and downloaded themes always show up in it, along with whichever built-ins you pin: each built-in card in the settings gallery carries a pin that adds it to the tool or takes it out, and a fresh install starts with a spread of six. That makes the tool a quick mood switch, with a handful of favourites to flip between mid-typing and the full collection one tap away in Settings (the tool's caption says so too). The tool runs full-bleed. The toolbar row hides while it's open and its space becomes a header, with a back button on the left and the **Themes** / **Icons** / **Emoji** / **Fonts** tabs beside it, so the cards get the reclaimed rows. On the Themes tab, pressing a card while Auto theme is on sets whichever half of the pair, light or dark, is due right now. The grid goes read-only only when that half is set to Random, or when the active keyboard mode carries a theme of its own. A caption at the top names which of those two is holding it. There are 32 built-in themes. Twenty-two are original WM Keyboard designs: plain colours like Ocean and Forest, gradient themes like Nebula and the animated Sunset drift and Aurora, two shape examples (Bubble for pill keys, Facet for cut corners), the three [frosted glass](https://wmkeyboard.pages.dev/themes/glass/) looks of the Frost family, and Cyberpunk, which puts the whole feature set on screen at once. The other ten (Dracula, Alucard, Nord, Solarized Dark, Solarized Light, Catppuccin Mocha, Catppuccin Latte, Catppuccin Frappé, Catppuccin Macchiato and Tokyo Night) are ports of well-known editor colour schemes. Alucard is Dracula's own light half, spelled backwards, so it draws as a second look on the Dracula card rather than a theme of its own. Cyberpunk is filed beside them in the source because it is written in that style, but the palette is ours. The MIT notices for the ones with upstream licences ship in the app's About screen. They don't all get a card of their own, though. Twenty-seven of the 32 belong to one of seven families (Nightfall, Pastel, Gradients, Frost, Dracula, Solarized and Catppuccin), and a family shows as a single card with one dot per look, so the grouped built-in grid is twelve cards rather than 32. [Theme families and looks](https://wmkeyboard.pages.dev/themes/families/) covers the dots, the Grouped/Separate choice, and building a family of your own. None of the built-in themes are editable in place. Tapping the pencil on one makes a copy in Your themes and opens the editor on that copy, before you have changed anything, so the originals stay put as a fallback you can always get back to. See [the theme editor](https://wmkeyboard.pages.dev/themes/editor/) for building or duplicating a theme, [image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for putting a photo behind your keys, and [sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for exporting, importing, and addon repositories. ## Matching your wallpaper Turn on **Material You colors** and the **Default (system)** theme pulls its palette from your phone's wallpaper, the same way Android's own system UI does. It's off by default, because the app and the Default theme ship with a palette of their own (the deep indigo and violet the launcher icon is built from), and wallpaper color replaces it. It only takes effect on Android 12 and up. The switch still shows on older versions, where it has nothing to do. This switch only changes the Default theme, and it recolors the settings app as well as the keyboard. Any other theme can follow the wallpaper too, one theme at a time. Open it in the [theme editor](https://wmkeyboard.pages.dev/themes/editor/#following-your-wallpaper) and turn on **Follow wallpaper colors**. That works for your own themes, copies of the built-in ones, and themes imported from FlorisBoard, Gboard, Rboard or HeliBoard. A FlorisBoard theme written in Material You colors, such as FloriStyle, has it on from the moment you import it. ## Matching the app you type in Appearance / Keyboard themes / Match the app colors Turn on **Match the app colors** and the keyboard dresses itself in the colors of whatever app you are typing in: green in WhatsApp, red in YouTube, blue in Telegram. Nothing is written into your theme. Switch to another app and the colors change with it; turn the switch off and your theme is back exactly as it was. The colors come from two places. One is the colors an app sets for its own screens, such as its toolbar. That is how AnySoftKeyboard does it, and it matches the app most closely when the app sets them. Many newer apps set none, or leave the template's purple in place, so the keyboard also looks at the app's launcher icon, which every app has. **Find colors in** picks between **Auto** (the app's screen colors first, then its icon), **App icon** and **App screens**. Grays, black, white and the stock template colors are skipped, because a palette made from them looks the same in every app. The app's color is not used as is. It is grown into a full palette (light and dark tones of a primary, a secondary, a tertiary and two neutrals), the same way Android grows one from your wallpaper. **Palette style** decides how, with the same styles Android offers for wallpaper colors: | Style | Looks like | |---|---| | Tonal spot *(default)* | Calm colors. Android's own default | | Vibrant | The strongest version of the color | | Expressive | The colors turned well away from the app's, for a playful look | | Fidelity | As near to the exact app color as a palette can stay | | Neutral | Nearly gray, with a hint of the color | | Monochrome | Gray only | | Rainbow | Colored accents over plain grays | | Fruit salad | Colored accents turned away from the app's color | **Themes that change** decides which themes take the app's colors. **Default and wallpaper themes** (the default) covers the Default theme and any theme with **Follow wallpaper colors** on, the themes that already take their colors from outside. Every other theme keeps its own colors. **All themes** recolors every theme. Each color keeps its lightness, so a dark theme stays dark, a light one stays light, and the contrast between keys and labels does not change. ### Another color, or none, for one app While the setting is on, the keyboard's **Appearance** tool shows a **This app** row above the theme cards. It holds the colors the keyboard found in the app you are in (up to four), then a struck-through circle that keeps your plain theme in this app. Press one and the keyboard remembers it for that app only, so an app whose icon has two brand colors can wear the second one, and an app you would rather keep plain can stay plain. After them come the palette styles, each drawn in this app's color, so you can compare them before you pick. A style applies to every app. The settings screen counts the apps you changed under **Color for each app**, with a **Reset** button that puts every app back on its first color. The first time the keyboard opens in an app it reads that app's colors, which takes a moment, and then fades into them. After that it remembers them and opens in them straight away. The keyboard's own settings screens are never recolored, so a theme you are editing always shows as it is. ## Dark mode and AMOLED **Mode** is a four-way switch: System, Light, Dark, or AMOLED. Like Material You colors, it controls both the settings app and the Default theme. AMOLED always renders dark, and unlike System it doesn't wait on the phone's own light/dark setting. It forces the Default theme's board to pure black, and blacks out the settings app the same way: page, rows, bars, dialogs and sheets, not just the page behind them. Keys and other surfaces stay near-black blended tones rather than literal black, so key edges and the edges of settings rows are still visible against the black behind them. Built-in and custom themes ignore Mode entirely. If you want an AMOLED look on a fixed palette, the built-in **Pitch black** theme gets you there with its own true-black board. themes/amoled-default-theme — Mode set to AMOLED: a true-black board under the Default theme. ## Switching automatically themes/auto-theme-sun — Auto theme on, with Sunrise & sunset chosen as the trigger. **Auto (light and dark)** picks two themes (one for day, one for night) and swaps between them on its own, using one of three triggers: - **System light or dark** follows the phone's own light/dark setting. - **Time of day** switches at two clock times you set. It wraps across midnight correctly, so a schedule that starts light in the evening still works. - **Sunrise and sunset** is computed on the device from the location saved in the weather tool, with no network round trip. Without a saved location, or on a polar day when the sun never rises or sets, this falls back to following the system setting. While Auto is on, pressing a card in the keyboard's own Appearance tool updates whichever half of the pair is currently due, light or dark. That only holds while the half isn't set to Random; a random half stays read-only there instead. Pressing a card in the settings gallery instead stores a manual pick for later, since Auto ignores it outright while it's on. A caption on each surface says which theme is live. Turn Auto off to pick themes manually again. ### One theme, or a set Each half of the pair holds either one theme or a whole set to select from. Open the **Light theme** or **Dark theme** row and the picker opens on a two-way control: **One theme**, which is the original behaviour and still the default, or **Random**, which turns the list into checkboxes so you can tick as many as you like. Press a family's name to tick every look in it at once. The two halves are independent, so a fixed light theme and five rotating dark ones is a perfectly ordinary setup. Switching to Random ticks the theme that half already showed, and the last ticked theme cannot be unticked, so a random half always has something to show. Switching back to One theme keeps the set, ready for when you turn it on again. A **Change the theme** row appears once either half is random, and it decides how often a new theme comes up: every time the keyboard opens, every hour, every 6 hours, every day, every week, or only when you press Change. The theme never changes while you are typing. A new one is selected as the keyboard comes up for a new field, and the same theme stays for the whole session, so nothing moves under your hands mid-sentence. The next theme is never the one already showing, which is what makes the change worth having on a set of two or three. themes/random-picker-light — The light theme picker in Random mode, with four themes ticked. ## Options Appearance / Keyboard themes - **Match the app colors** (default off). Recolors the keyboard to the app you are typing in. Once on: **Themes that change** (default Default and wallpaper themes), **Find colors in** (default Auto) and **Palette style** (default Tonal spot). See [Matching the app you type in](#matching-the-app-you-type-in). - **Material You colors** (default off). Wallpaper-based dynamic color for the Default theme and the settings app, in place of the app's own indigo palette. It does nothing below Android 12, and nothing to built-in or custom themes. Those have their own **Follow wallpaper colors** switch in the theme editor. - **Mode** (default System). System, Light, Dark, or AMOLED, applied to the settings app and the Default theme. - **Auto theme** (default off). Once on, pick a light theme, a dark theme, and a trigger (System light or dark, Time of day, or Sunrise and sunset). The time-of-day picker starts at 7:00 AM for the light theme and 7:00 PM for the dark theme. - **Light theme** and **Dark theme** (default One theme, Default (system) in both). Each row opens a picker that holds one theme or a set to select from at random. - **Change the theme** (default Every time the keyboard opens). Only shows while a half is random. Every time the keyboard opens, every hour, every 6 hours, every day, every week, or only when you press Change. - **Change the theme now**. Selects a new theme for every random half straight away. This is the only way a new theme comes up under the Change-only setting. Switching themes crossfades over about a third of a second, by hand or under Auto. Turning on **Reduce motion** (under accessibility) skips the crossfade and snaps instead. ## Details & edge cases - **Contrast is worked out, not guessed.** A theme only has to specify seven colors: board, key background, key text, modifier key background, enter key background, enter key icon, and accent. Everything else (popup colors, panel chip colors, tool-circle tints, the pressed face, the gesture trail) is derived from those, using WCAG-style contrast math. That's why a custom theme built from one seed colour still has readable popups and legible active-tool highlights. The Default theme's dark mode goes further and steers clear of Material's own dark surface roles, which tend to flatten to near-identical grays. It blends the theme's text color over the darkened board at a few different strengths instead, which keeps panels and keys distinct. - **That's separate from the accessibility contrast tools.** High contrast keys, key outlines, and color vision filters are a user-facing layer that overrides theme colors outright rather than filling in gaps. They live under Settings → Accessibility, not here. See [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) for those. - **Key shape** is a per-theme setting with thirteen options, picked from a list that draws each shape beside its name: None (no shape at all, so only the labels show), Rounded (the default, and the one that follows the corner-radius value), Square, Pill, Cut (clamped to a 2–14dp radius regardless of the slider), Squircle, Arch, Leaf, Slanted, Hexagon, Scalloped, Ticket, and Circle (a typewriter-style round cap centred in the key). Apart from None, everything except Rounded and Cut sizes itself off the key instead of the radius slider. Full table on [the theme editor page](https://wmkeyboard.pages.dev/themes/editor/#key-shapes). - **Corner radius has a global slider and an optional per-theme override.** The global **Key corner radius** slider (Appearance → Keys, default 8dp, range 0–28dp) applies unless a theme defines its own. The theme editor's "Corner radii for this theme" toggle exposes separate sliders for key, popup, and tool-circle radius when you want a theme to ignore the global value. - **Borders are opt-in and off by default.** A theme has no key border unless it sets one explicitly. The built-in Solarized Dark and Solarized Light themes show when a border earns its keep: their two background tones sit close enough in contrast that a thin outline keeps key edges readable. The toolbar tools have their own outline pair in the theme editor, off in the same way. The accessibility "Key outlines" toggle adds a border of its own to the keys and the tools, but only where the active theme hasn't already defined one. A theme's own border always wins. - **No build differences.** Nothing on this page is gated: every built-in theme, gradient, animation, background image, and Material You itself works the same way in every build. --- # Image backgrounds URL: https://wmkeyboard.pages.dev/themes/backgrounds/ Section: Themes & appearance Summary: Put a photo behind your keys, with crop, opacity, blur and a separate landscape image. Any keyboard theme can carry a photo behind its keys instead of a flat color or gradient. You crop it to the keyboard's shape, dial in opacity and blur so keys stay readable, and optionally swap in a different image for landscape. This page covers a picture from your own device. A photo from Unsplash or Pexels, the collection they go into, and the background that changes on a schedule are all in [Online photos and rotation](https://wmkeyboard.pages.dev/themes/online-photos/). ![A theme with a background image showing through the keys.](@assets/screens/themes/background-image-live.webp) ## Setting an image Appearance / Keyboard themes / (edit a theme) / Board ![Cropping a background image to the keyboard's own shape.](@assets/screens/themes/background-crop-dialog.webp) Open a theme for editing. Scroll to the **Board** section. Tap **Background image** to open the **Select a background** dialog. **Add from this device** opens the system photo picker, which offers image files only. The app copies the picture you choose into its own storage, and it never reads the original file again. Picking an image changes two things about the theme so the photo is actually visible, both of which stay editable afterward: - **The board color's opacity drops to zero**, so the photo shows at full strength rather than being hidden under an opaque background. Raise it again if you want to tint or dim the photo. - **Fully opaque key and modifier colors drop to 62% opacity**, or to whatever [How solid the keys stay](https://wmkeyboard.pages.dev/themes/online-photos/#how-a-photo-changes-the-keyboard) is set to, anywhere from 20% to 100%. A keyboard is mostly keys, so leaving them solid means the photo is only visible around the edges. Keys you had already made see-through are left alone. See [Editing a theme](https://wmkeyboard.pages.dev/themes/editor/) for both color controls. The photo also joins your [photo collection](https://wmkeyboard.pages.dev/themes/online-photos/#the-photo-collection), so you can put it on another theme, or into the rotation, without finding it in your gallery again. ### Cropping to fit Once an image is set, a **Crop image** row appears. In the crop dialog, pinch to zoom and drag to reposition. The image always scales to cover the frame, so there's never a gap at the edges. Four crop ratios are available: - **Keyboard**: the shape your keyboard actually has on screen, in portrait for the portrait image and in landscape for the landscape one, so the frame shows exactly what ends up behind the keys. The keyboard measures itself each time it opens, so this follows your key height, number row, toolbar and width settings. Selected by default. Until the keyboard has been opened once in that orientation, it uses 2.4:1. - **Wide** (1.7:1) - **Square** (1:1) - **Free**: keeps the picture's own shape. The other three are all wider-than-tall keyboard shapes, which crop a split or floating keyboard down. Confirm the crop and the app saves the cropped picture, and keeps the uncropped one beside it. Opening **Crop image** again starts from the whole picture, with the frame where you left it, so you can move to a part an earlier crop cut away, or zoom back out. Cropping is optional, and an uncropped image still renders correctly, scaled to cover the board. Crop when you want to control which part of the photo lines up with the keys. The photo grid in [Online photos](https://wmkeyboard.pages.dev/themes/online-photos/) dims the parts of each photo outside the keyboard's shape, using the same measured portrait shape. ## Keeping keys readable A photo behind your keys can easily fight with legibility, so two sliders and a switch sit right below the image row: - **Image opacity** fades the image itself, from fully transparent to fully opaque (the default). Below 100%, with the board color still see-through, whatever is behind the keyboard shows through the photo. An image with transparent areas of its own does the same. - **Image blur** softens the image from sharp (the default) up to a heavy blur. - **Play animated image** plays the background when it's a GIF or an animated WebP, and turns itself on when the picture you picked is one. Turning it on zeroes the blur, and raising the blur turns it off: blurring a running animation would re-render the effect on every frame. The animation also stays still under reduce motion or power saving, and on app versions from before this feature the same file shows its first frame. There's no separate "dim" control. The board's own background color draws as a layer on top of the image, so raising that color's opacity tints or dims the photo. It's the same **Background** color row you'd use for a solid-color theme. A soft blur plus a translucent board color is usually enough to keep key labels legible over a busy photo. Turning on **High contrast keys** in accessibility settings strips background images (and gradients and animation) from every theme while it's active, since a photo behind the keys works against the contrast that setting is trying to guarantee. ## Separate landscape image Below the portrait image controls, **Landscape background image** lets you pick a second photo (with its own crop dialog) used only when the device is in landscape orientation. If you don't set one, the portrait image is used in landscape too. There's no picture that only shows in portrait while landscape falls back to something else. Opacity and blur are shared between both images rather than set per orientation, so whichever photo is showing uses the same two slider values. ![A theme with distinct portrait and landscape background images.](@assets/screens/themes/background-landscape.webp) ## Image sizes and templates Appearance / Keyboard themes / (edit a theme) / Board / Image sizes and templates If you draw a theme yourself, this screen tells you how big each picture should be before you start. It lists the background, the letter, function and Enter keys, the space bar, the pressed key, the key popup, stickers and press effect images, each with two sizes: - **On screen** is what the picture covers on the keyboard. - **Draw at** is the same shape, made as large as the keyboard keeps. A key image is kept at up to 256 pixels on its long side and a space bar image at up to 1024 × 320, so a larger drawing only gets made smaller again. The sizes are measured from the keyboard itself, so they are right for your layout, number row, key height and spacing. **Screen** picks what they are measured for: - **This phone** (the default): your own layout and sizes on this screen. - **Phone**: a 1080 pixel wide phone with the default sizes. Use this for a theme you want to share, since other people see the defaults. - **Phone, landscape**: the same phone on its side, for the [landscape image](#separate-landscape-image). - **Tablet**: a 1600 pixel wide tablet or an open foldable, with the default sizes. The picture under the selector is the board template: every key's outline over the background area, the toolbar band at the top, and the band under the navigation bar at the bottom. The background runs on under the navigation bar on Android 15 and later only, so keep that band plain. **Save board image** saves that template as a PNG. **Save all templates** saves a ZIP with the board template, one template each for the letter key, function key, Enter key, space bar and popup (with a dashed box where the label sits), and a `README.txt` with every size. The templates are transparent with white lines. Put one on the top layer of your drawing, and hide it before you export. ## Details and edge cases - **No image by default.** A freshly created theme, every built-in gallery theme, and the Default (system) theme all start with no background image, so the board is a flat color or gradient until you add one. - **Switching themes crossfades the rest of the theme, but not the image.** Colors and gradients blend smoothly between themes. The background image switches over abruptly partway through the transition rather than cross-dissolving, since blending two unrelated photos frame-by-frame wouldn't look right anyway. Blur switches at the same moment for the same reason. - **Changing the seed color keeps your photo visible.** Picking a seed swatch, or flipping the light and dark switch, rebuilds every color in the theme. When the theme has a background image, how see-through the board and keys are is carried across, so the new palette does not bury the photo under a fresh opaque color. - **Images are decoded at the size they are drawn.** A photo straight off a camera is far larger than the strip behind your keys, so the app samples it down first rather than holding it at full size. - **The gallery preview card only shows the portrait image.** The small theme thumbnail in the theme list never shows the landscape variant, even if one is set. - **Background images travel with exports.** Both a single-theme export and a full settings backup embed the image data directly in the file, so restoring the export or importing the theme on another device brings the photo with it; you don't need to re-pick the image after importing. See [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for what else a theme export contains. - **This page is about a single theme's own image.** Switching between two different themes automatically by time of day or by system light/dark mode (where each half of the pair might have its own background image) is covered in [Themes overview](https://wmkeyboard.pages.dev/themes/overview/). A single theme whose *photo* changes on a schedule is [Online photos and rotation](https://wmkeyboard.pages.dev/themes/online-photos/). --- # Theme editor URL: https://wmkeyboard.pages.dev/themes/editor/ Section: Themes & appearance Summary: Every color, key shape, gradient, corner and animation knob for a single custom theme. The theme editor is where a single custom theme gets built: every color, the key shape, optional gradients, corner-radius overrides, and a live-animated background if you want one. Every field here applies and saves the instant you touch it. There's no separate save step. ![The theme editor, with the live preview pinned above the color and shape controls.](@assets/screens/themes/editor-live-preview.webp) ## Creating a theme Appearance / Keyboard themes A custom theme's card opens the editor from its pencil, and the card's miniature grows into the editor's own preview on the way. The editor is headed with the theme's name rather than with the words "Edit theme", so the card you tapped and the page it opened read as one thing. Rename the theme and the heading follows. Every change saves as you make it. Once you have changed something, the undo arrow under the preview lights up, and after an undo the redo arrow beside it does too. They cover everything on the page, the theme's looks included: adding or deleting a variant is a step too. Changes made within about a second of each other undo together, so a slider's drag or a name typed out is one step rather than dozens. The history holds 30 steps and lasts until you leave the editor. A photo is the one thing an undo can't always bring back, because picking a new background deletes the old picture's file. **Create theme** on the gallery screen makes a new custom theme called "My theme". It's seeded from a blue swatch and generated light or dark to match whatever the phone is set to right now. The new theme becomes the active keyboard theme, then the editor opens on it. Built-in themes can't be edited in place. Their card shows a pencil instead, which copies the built-in into a new custom theme named "*(theme) copy*", makes the copy active, and opens the editor on the copy. The original built-in is untouched. The **Default (system)** card has no pencil at all. It's generated live from your device's Material You color scheme rather than stored anywhere, so there's nothing for this editor to open. See [Theming overview](https://wmkeyboard.pages.dev/themes/overview/) for the gallery itself and how Default, Material You, and the built-in set fit together. Importing a `.wmtheme.json` file (from the gallery's Import button or by opening the file itself) adds it to Your themes and switches the keyboard to it immediately, without opening this editor. See [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for both ways to import. ## The Looks row Straight under the preview, before the name field, sits a **Looks** row: one chip per look this theme carries, an **Add look** chip, and (while you have a look other than the first one open) a bin icon that deletes the one you're editing. Everything below that row edits the open look and nothing else, so a theme with four looks is four full colour sets, switched between with a chip. A theme starts as one look, and the row is just the one chip until you press Add look. Add a second and a **Group name** field appears below the chips: that's the label the gallery card shows over the dots. Add look copies the look you have open, so the new one starts where the old one left off, and it greys out after twelve extras (thirteen looks in all). [Theme families and looks](https://wmkeyboard.pages.dev/themes/families/) is the full story: what a family card looks like in the gallery, the Grouped/Separate choice, and what happens to a family when you copy, export or delete it. ## Starting from a color At the top of the editor, a **Dark theme** switch and a row of swatches let you regenerate every color at once from a single seed: ![Start from a color: the Dark theme switch and seed swatch row.](@assets/screens/themes/editor-seed-swatches.webp) Tapping a swatch, or flipping Dark theme, recomputes the board, key, accent, and popup colors from that seed and shade. It preserves everything that isn't seed-derived, so a reseed doesn't erase work you've done elsewhere in the editor. Your background image and its opacity/blur, both gradients, the key shape, the key border color and width, the gesture-trail color, all three corner-radius overrides, and the animation type and speed all carry over untouched. Anything you hand-picked that *is* seed-derived (a manually recolored key-text or popup color, say) gets overwritten, since there's no way to tell "kept the default" apart from "picked to match." ## Following your wallpaper Under the seed swatches, **Follow wallpaper colors** makes the theme change color with your wallpaper, the way the Default theme does with Material You on. It's off for a theme you make, and it needs Android 12 or later, because older phones have no wallpaper palette to read. The theme keeps its own colors. The keyboard moves them onto your wallpaper's palette as it draws them, so turning the switch off gives you the stored colors back, and the color rows in the editor always show the stored ones. How the colors move depends on where the theme came from: - **Most themes** (your own, copies of built-ins, Gboard, Rboard and HeliBoard imports) keep how light or dark each color is and take the wallpaper's hue. Greys go to the wallpaper's neutral tones, colors go to its main accent, and a color on the far side of the color wheel from the theme's accent goes to the wallpaper's third accent, so a two-color theme stays two colors. Because the lightness stays put, text that was easy to read stays easy to read. Pure black and pure white stay as they are. - **FlorisBoard themes written in Material You colors**, such as FloriStyle and Gboardish, remember which Material You color each part used. They follow the wallpaper exactly as they would in FlorisBoard, and the switch is already on when you import one. A color you change in the editor stays the color you picked. Tapping a seed swatch or flipping **Dark theme** keeps the switch as it is. ## Options Appearance / Keyboard themes / (edit a theme) The rest of the editor is grouped into sections, each opening the color picker when you tap a row. ### The color picker Every color row in the app opens the same dialog, here and over in the tool screens: - A **wheel** for hue and saturation. Hue runs around it, saturation runs outward from the middle, and the ring follows your finger from the moment you touch it. - A **Brightness** bar, plus an **Opacity** bar on the fields that support translucency. Each bar is painted in the colors it's about to hand you, and the opacity one sits on a checkerboard so you can see how much is showing through. - A **preview strip** across the top, split down the middle: the color you opened with on the left, the color you have now on the right. Tap the left half to throw away the change and start again. - A **Hex** field taking three, six or eight digits, with or without the `#`. Six digits keeps whatever opacity you already set rather than resetting it to solid. - A row of **presets**, the same seeds as the swatch row above. Tapping one changes the color and leaves your opacity alone, so a scrim you've already made translucent stays that way. The button in the dialog's title swaps the wheel for **Hue** and **Saturation** bars, for when you'd rather read exact numbers or move one channel at a time. Those bars are also the ones TalkBack can set. In landscape the wheel sits beside the bars instead of above them. From Android 12 the dialog has two tabs. **Custom** is everything above. **Palette** offers your wallpaper's Material You colors: five swatches for its Primary, Secondary, Tertiary, Neutral and Neutral variant palettes, and a **Tone** bar from 0 (black) to 100 (white) that sets how light all five are. A theme that follows the wallpaper opens on this tab. The shade you pick is saved as a plain color and keeps your opacity. ### What's what Every row in the editor paints one part of the keyboard, and the names are only obvious once you know which part is which. The two drawings below put a number on each part. The tables under them say which row colours it. ![A dark keyboard with numbered markers on the board, the toolbar row, the suggestion strip, a letter key and its hint, a pressed key, a bordered key, a modifier key, the enter key, the space bar and the navigation bar.](@assets/themes/anatomy-keyboard.svg) | | Part | Rows that paint it | |---|---|---| | 1 | **Board**: everything behind the keys, including the gaps between them | Board › Background, Gradient background, Background image | | 2 | **Toolbar row**: the tools, each on its own circle | Toolbar, strip and popups › Toolbar: Toolbar background, Tool circles, Tool icons, Active tool circle, Active tool icon, Tool outline; shape and size under Tool background shape and Corners | | 3 | **Suggestion strip**: the word candidates. The middle one is the primary, the others the runner-ups | Toolbar, strip and popups › Suggestion strip: Suggestion strip (the fill), Suggestion text (the primary), Other suggestions and Other suggestions size (the rest), Suggestion pills and Main suggestion pill (the boxes behind the words, with Coloured suggestions on); Panels & suggestions › Dividers (the hairlines between them) | | 4 | **Letter key** and its label | Keys › Letter keys, Key text; the sheen under Key gradient; the picture under Key textures › Letter keys | | 5 | **Key hint**: the small alternate in the corner | Keys › Key hint text; its size under Layout › Key hint font size | | 6 | **Pressed key**: the flash while a key is held | Keys › Pressed key; the picture under Key textures › Pressed keys | | 7 | **Key border** | Keys › Key border and Border width | | 8 | **Modifier key**: shift, delete, `?123`, the globe | Keys › Modifier keys, Modifier key text | | 9 | **Enter key** | Keys › Enter key, Enter key icon | | 10 | **Space bar** | Takes the letter-key colour and texture; give it its own under Single-key styles › Space | | 11 | **Navigation bar**: the band the phone's gesture bar sits on | Board › Navigation bar | | 12 | **Key shape and corners** | Keys › Key shape; Corners › Key corner radius; the lift under Key shadow | ![Three popups: the key preview bubble rising out of a pressed key, the press-and-hold alternates popup with one character highlighted, and a list menu with one row selected. Numbered markers point at the fill, text, outline, shape, highlight, spacing and shadow.](@assets/themes/anatomy-popups.svg) | | Part | Rows that paint it | |---|---|---| | 1 | **Popup fill**: the bubble that rises out of a pressed key, and the sheet the press-and-hold alternates sit on | Popups › Popup background; the picture under Key textures › Key preview popup | | 2 | **Popup text**: the character in the bubble, and every alternate that isn't under your finger | Popups › Popup text | | 3 | **Popup outline** | Popups › Popup outline and its width row | | 4 | **Popup shape and corners** | Popups › Popup shape; Corners › Popup corner radius | | 5 | **Popup height**: how tall the bubble is | Layout › Popup height; where it sits is Popup placement | | 6 | **Selected highlight**: the alternate your finger is on, and the selected row of a menu | Panels & suggestions › Selected highlight | | 7 | **Selected text**: the character or row on that highlight | Panels & suggestions › Selected text | | 8 | **Spacing between alternates**: the room around each character, which is also the gap between the highlight and the popup's edge | Not a theme field. It's Key press / Alternate keys / Alternate key spacing, and the characters' size is **Alternate key size** beside it | | 9 | **Popup shadow** | Panels & suggestions › Popup shadow | | 10 | **Menu shape**: the list menus' own outline | Popups › Menu shape | The popup rows do nothing to a bubble you have coloured by hand under Key press / Key popup: **Popup color** and **Popup text color** there win over the theme, and their reset controls hand the bubble back to it. A [single-key style](#single-key-styles) wins over both, for its one key. ### Board - **Background** is the board's base color. It supports alpha, which starts to matter once a background image is set. See [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for the image picker, crop, opacity, and blur controls that live in this same section. - **Gradient background** is covered under [Gradients](#gradients) below. - **Navigation bar** fills the band below the bottom key row, where the phone's own gesture bar or navigation buttons sit. Auto leaves it on the board, the same as the [suggestion strip](#toolbar-strip-and-popups). Only the system's own band is painted. The extra space that **Bottom padding** (under Layout & size / Size and position) adds above it belongs to the keyboard and stays on the board. On a phone with hardware navigation keys there is no band at all, so the color has nothing to paint and nothing changes. On Android 15 and later the keyboard draws that band itself, so a gradient, image or animation carries through it. On Android 14 and earlier the keyboard window stops short of the bar and can only hand the system one flat color, which it does: the **Navigation bar** color if you set one, otherwise the far end of the gradient, otherwise the board color. Either way the bar follows the theme instead of staying whatever color the phone picked, and the system's hide-keyboard and language-switch glyphs flip between dark and light to stay readable on it. ### Glass Appearance / Keyboard themes / (edit a theme) / Glass **Frosted glass** turns the theme's surfaces into see-through panes over a blurred backdrop, with a rim of light and a little grain. The section opens with a **Presets** row (Frosted, Clear, Mica, Smoked, Liquid), **Kind of glass** (Frosted, or Liquid with its **Refraction** and **Color split** sliders), then **Blur behind the keyboard** (Android 12 and later; the app under the keyboard shows through the board blurred), **Blur strength**, **Board opacity without blur**, a row of **Glass surfaces** chips, **Behind the glass** (Auto, Image, Wallpaper or Nothing) with **Part of the wallpaper** and, on Android 12 and below, a **Use your real wallpaper** permission row, the three backdrop sliders (**Backdrop blur**, **Backdrop saturation**, **Backdrop brightness**), **Rim**, **Rim color** and **Grain**. [Frosted glass](https://wmkeyboard.pages.dev/themes/glass/) explains what each does, what the phone has to support, and how the built-in Frost family uses them. ### Keys - **Key shape**: a row that opens a picker with **None** and all twelve shapes listed by name, each drawn beside its name at key proportions and at this theme's own corner radius, so you can see what you're choosing before you choose it. The full list, and which shapes react to the radius slider, is under [Key shapes](#key-shapes) below. - **Letter keys** sets their color, and carries an optional **Key gradient**, a subtle sheen painted over letter keys only (see [Gradients](#gradients)). - **Key text**, **Modifier keys**, **Modifier key text** (falls back to Key text when left on Auto), **Key hint text** (the small long-press alternate in a key's corner; Auto draws the key's own text color at 55% opacity, and this one keeps alpha so you can pick a translucent hint in another hue), **Enter key**, **Enter key icon**. - **Make every key match the letter keys** gives the modifier keys (Shift, delete, `?123`, the globe, the emoji and tool keys) and Enter the letter keys' background and text colours in one step, for a board where every key looks the same. It is one undo step, and each colour stays editable afterwards. - **Pressed key** is the color a key flashes while held. Auto derives it from Letter keys. - **Key border** stays off (Auto, no visible border) until you pick a color. A **Border width** slider (0–3dp) then appears, starting at 0dp. Picking a border color on its own shows nothing until you raise the width too. #### See-through keys Keys don't need a separate opacity slider: the colour itself carries it. **Letter keys**, **Modifier keys** and **Enter key** all open the picker with the **Opacity** bar, and the hex field takes eight digits, so `#80303338` is the stock key at half strength. Pull the opacity down and the board shows through the key, gradient, photo or animation included, and the gap between keys stops being a gap. Setting a background image does the first step for you: it drops opaque key and modifier colours to about 62%. A key you can see through casts no [shadow](#key-shadow), whatever Key shadow says. That is a translucent face. For no face at all, pick **None** under [Key shape](#no-key-shape): the labels then sit straight on the board, and only a pressed or latched key draws anything behind its label. ### Key shapes The picker lists **None** first, then the twelve shapes in this order. The five simple ones come first, then the seven decorative ones: | Shape | What it looks like | Reads the radius slider? | |---|---|---| | None | No shape at all, just the labels on the board. See [No key shape](#no-key-shape) below | Only for the flash while a key is pressed | | Rounded *(default)* | The ordinary rounded rectangle | Yes, 0–28dp | | Square | Four sharp corners, no rounding at all | No | | Pill | A full stadium, rounded to half the key's height | No | | Cut | Corners chamfered off at 45° | Yes, but clamped to 2–14dp | | Squircle | A superellipse, the same curve family as an iOS app icon | No | | Arch | Round across the top, nearly square at the bottom | No | | Leaf | Top-left and bottom-right fully round, the other two nearly square | No | | Slanted | A parallelogram, both vertical sides leaning right | No | | Hexagon | Points at the left and right, flat top and bottom | No | | Scalloped | A rim of even bumps, like the edge of a biscuit | No | | Ticket | Corners bitten inwards by a circle, like a cinema ticket | No | | Circle | A round cap centred in the key, like a typewriter's; wide keys (space, enter) keep a stadium | No | The shapes that ignore the slider aren't being lazy about it. Their whole look is a proportion of the key rather than a corner size, so they measure themselves off the key's own width and height. The arch is always half the key's height. The hexagon's points are always a sixth of its width. The scallops count out at roughly the same size whether it's a letter key or a spacebar, up to 64 along a side; a box only a few pixels tall, such as a panel sliding open, is drawn as a plain rectangle until it is big enough for bumps. Wire them to a 0–28dp slider instead and one end of it flattens them into plain rectangles. Only the drawing changes. Touch targets stay rectangular for every shape, so the gap between two hexagons still belongs to one key or the other, and nothing gets harder to hit. Slanted is the one shape that draws slightly outside its key. A parallelogram kept inside the key would lose about a third of the width at the top and the bottom, which on a phone-sized key reads as a row of thin straps. So it leans half its slant into the gap on each side instead. Every key stays exactly as wide as it would be with any other shape, and the gap between two keys keeps its width. The gap just runs on a diagonal. Where there is no gap to lean into, the lean is trimmed to fit. #### Key shadow **Key shadow** lifts every key off the board, 0 to 16 dp. It's drawn by the platform from the key's own outline, so it follows whatever shape you picked. **Key shadow colour** shows up once there is a shadow. Unset, it's the usual black; pick any colour, and lower its opacity for a soft glow rather than a hard edge. Themes imported from Gboard and FlorisBoard bring their own. Android 8 and older draw every shadow black whatever this says. Two shapes stay flat however far you push it. Scallop and Ticket are concave, and Android tessellates a concave shadow through a path that pins the render thread hard enough for the system to kill the keyboard, so they keep the shape and lose the shadow. A key you can see through at all stays flat too, even at 90% opacity: the shadow fills the key's whole outline, and through a translucent face it reads as a lighter box inset in every key. That's what keeps a borderless theme looking borderless. #### No key shape **None** takes the shape away completely, which isn't quite the same as a see-through key color. A key at rest draws no fill, no border and no texture, so every label sits straight on the board, Enter and the space bar included. The Enter icon switches to your **Modifier key text** color while it has no fill of its own, since **Enter key icon** is picked to read on the Enter key's fill. The keys still answer your finger. A pressed key flashes a rounded shape at your key corner radius, and an armed or locked modifier still lights up. A key you give its own face color under [Single-key styles](#single-key-styles) keeps that face, so a colored Enter key on an otherwise bare board is one style away. The keypads in the tool panels, like the calculator and the voice and handwriting rails, follow the same rule as the main keys. **High contrast keys** and **Key outlines** in [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) both bring rounded keys back while they're on. Keys without visible edges are exactly what those two settings are there to fix. Only Rounded, Pill, Cut and Squircle shipped with the first version of the format. The other eight, Square among them, came later, and so did None. A theme exported with one of them won't import into a version of the app that predates it: the importer drops a theme whose shape name it doesn't recognise rather than guessing. Stick to Rounded, Pill, Cut, or Squircle in a theme you plan to share widely. ### Key textures Keys can wear an image instead of a flat color. One picture for the letter keys is enough to theme the whole board. The modifier keys, the enter key, the space bar, the pressed state and the key-preview popup each take an optional image of their own, and each falls back sensibly when you don't set one: space reuses the letter texture, pressed draws the press tint over it, and the popup draws color-only rather than borrowing a key's image. - Each texture row picks an image from your gallery. It's stored downscaled to 512px, so a camera photo won't bloat the theme file. Rows that have an image show it as a small thumbnail, the same way the background and press-effect image rows do. - **Crop / Stretch / Tile** decides how the image fits a key. Crop keeps its proportions, stretch fills the key exactly, and tile repeats it at its natural size. Tile is the one you want for pixel-art. - **Texture opacity** blends the image over the key color underneath. That color stays the base coat, and it's what high-contrast accessibility falls back to, since textures don't draw there at all. Textures travel inside the theme file when you export it. An older app version imports the same file as a plain color theme and loses nothing else. To find out how big to draw each texture, open **Image sizes and templates** in the **Board** section. It measures your keyboard and saves templates to draw on. See [Image sizes and templates](https://wmkeyboard.pages.dev/themes/backgrounds/#image-sizes-and-templates). ### Single-key styles One key can carry a style of its own. The quickest way to choose it is **Pick on keyboard**: the preview stops typing, and pressing any key on it opens that key's style. A row of chips above the preview switches it between the letters, the two symbol pages, the Fn page when your layout has one, and every panel that is a grid of keys (text editing, numpad, clipboard, emoji, GIF, sticker, trackpad and handwriting), so a key on any of them can be picked the same way. **Done** or Back returns the preview to typing. **Add a key** still takes a letter typed by hand, or one of the special keys (Enter, Space, Shift, Delete, Symbols…), for a key the preview isn't showing. A letter's style follows that letter onto every layout that has it. A key that does one particular thing gets a style for that thing alone: each arrow, Home and End on the text-editing pad, each tool key, each page key. That is how the arrows of the text-editing pad go red while Home and End stay dark. A style an older theme or an imported one gave the whole family (every text-editing key at once, say) keeps applying to every key that has no style of its own. The key's style opens on the page straight under the preview, which stays pinned above it, so every change shows on the key as you make it. Back or the × beside its title closes it. Set any of these. Everything you leave alone keeps following the theme, so a key that only recolors its hint carries exactly that. - **Colors**: its face, its label, its hint, its border, and the two colors of the popup bubble it shows. - **Key image**: a picture for this one key, drawn with the same Crop/Stretch/Tile fit and opacity the board's [key textures](#key-textures) use, so a dressed key still reads as part of the set. It beats the class texture it would otherwise wear, and a key image works on a board with no class textures at all. - **Press effect**: the burst this key throws: Stars, Hearts, Sparkle, Confetti, or Emoji with its own field for the emoji. It overrides the board's [key press effect](#key-press-effect) for this key alone, and a board whose only effect is one key's still bursts on that key. The board's Intensity, Colour and the physics sliders shape every burst; a single-key style picks what is thrown, not how. Images aren't offered per key. - **Key shape**: this one key's outline. Automatic follows the board's shape; pick one to make a single key round, pill, cut or anything else in the list, including None. The board's corner radius still decides how round a rounded key is, and the shapes that measure themselves off the key (circle, pill, hexagon and the rest) ignore it as they always do. This is how a round Enter key on a grid of soft rectangles is made, and it is what carries over from an imported FlorisBoard theme built that way. - **Bold label** and **Label size**: how the label is drawn. Bold is a three-way choice: Automatic follows the board, On and Off answer for this key. Label size replaces the automatic rule rather than scaling it, which is how you get a `?123` drawn at full letter size. A size the *layout* gave the key in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key) wins over the theme's, since the layout is the more specific answer. An armed or locked modifier still shows the accent or pressed face whatever color you gave it, because state has to stay visible. A key image and a press effect travel inside the exported theme the same way the board's do. High-contrast accessibility drops single-key styles with the rest of the decoration, and a press effect never plays under reduce motion or power saving. ### Stickers Up to six images laid over the keys, purely for decoration: typing goes straight through them. Transparent PNGs work best. Each sticker has position, size, rotation and opacity sliders, adjusted over a live miniature preview so what you drag is what the keyboard shows. Stickers travel with the exported theme the same way textures do. ### Key press effect A small particle burst under every key press. Pick **Stars**, **Hearts**, **Sparkle**, **Confetti**, **Emoji** with a field for the emoji to throw (each one becomes its own particle kind), or **Images**, which takes up to six of your own pictures from the gallery, each a particle kind the same way. Transparent PNGs look best. They're stored small and keep their proportions in flight. An **Intensity** slider scales how many particles a press throws. Six more controls shape every particle at the moment it spawns: a **Colour** picker (Natural, Key text, Accent, Trail, Custom, or Random) and five sliders, **Size**, **Speed**, **Spread**, **Gravity** (how fast particles fall; zero leaves them drifting, and below zero they float upward), and **Duration** (200-2500 ms). The effect never plays under reduce motion or power saving, and high-contrast accessibility drops it with the rest of the decoration. ### Key material What the keys look like they're made of. Pick **Key cap** (a raised cap on a darker skirt that sinks when pressed), **Metal**, **Wood**, **Stone**, **Stitched cloth**, **Leather**, **Carbon fibre**, **Paper cut-out**, **Letterpress**, **Neon**, **Holographic**, **Terminal**, **Cartoon**, **Art glass**, **Candy**, **Ceramic**, **Resin**, **Arcade button**, **Typewriter**, **Instrument panel** or **Molten**. **Custom** starts from a flat key. Each material is a preset of the same few parts, and every part has its own control under it: **Key cap height**, **Top face inset** and **Cap side shade** for the raised cap; **Gloss** and **Light direction** for the highlight; **Surface texture** (grain, linen, dots, scan lines, brushed, wood grain, stone, paper, carbon weave or leather) and its strength; **Edge** (stitching, a glow, an ink outline, a glazed rim, a pressed-in top edge or a double ring) with its colour and width; **Label effect** (glow, pressed in, raised or a shadow); and **Rainbow sheen**. Picking another material starts its preset afresh. The textures are drawn in greys, so they take each key's own colour. ### Moving background Something moving on the board behind the keys: **Snow**, **Rain**, **Code rain**, **Bokeh**, **Stars**, **Warp speed**, **Fireflies**, **Dust**, **Embers**, **Bubbles**, **Confetti**, **Fireworks**, **Aurora**, **Nebula**, **Lava lamp**, **Waves**, **Smoke**, **Dot grid**, **Contour lines**, **Equaliser**, **Scanner**, **Sheen** or **Ripples**. **Amount**, **Speed**, **Brightness** and **Size** shape it, and two colour pickers set its colours (the accent and the label colour when left on Auto). **Reacts to typing** makes each key press push the effect: it speeds up and glows brighter, then settles back as the typing stops. **Ripples** and **Dot grid** also send a ring out from each key you press. Under reduce motion or power saving the background stays as a still picture. While Reduce motion is on, or the phone is set to remove animations, the editor says so above **Moving background**, with a button that turns Reduce motion off. ### Key lighting Light on the keys, the way a backlit keyboard has it. **Steady**, **Breathe**, **Wave**, **Sweep**, **Twinkle** and **Heartbeat** run on their own. **Pressed key**, **Ripple**, **Echo**, **Splash**, **Cross** and **Rain** start from the key you press and go dark again when you stop. **Light on** puts it on the key faces, round the key edges, or behind the keys, where it shows in the gaps. **Rainbow spread** turns the colour across the keyboard (360° shows the whole rainbow), and **Fade time** sets how long a press stays lit. Lighting is off under reduce motion and power saving. ### Touch bloom The keyboard answers your finger itself. Turn on **Touch bloom** and a soft light spreads out from where you touch, across the background behind the keys, while the keys near your finger sink a little and spring back when you lift, with a small wobble. Keys further away stay still. When you glide, the light and the dip travel with your finger, and the bubble over a pressed key lights up in the same colour. **Light colour** is the theme's accent unless you pick one. **Light strength** sets how bright the bloom is (0% leaves only the dip), **Reach** how far from your finger the light spreads and the keys move, **Key dip** how deep they sink (0% leaves only the light), and **Wobble** how much they bounce on the way back. Touch bloom is off under reduce motion and power saving. ### Key art Hand-drawn keys from a file. **Load key art from a file** reads a skin: each kind of key (letters, special keys, enter, space, delete, shift, the mode key, punctuation, emoji, the globe) drawn as layers of vector shapes, plus an optional picture for the board. The colours in it are names of the theme's own colours, such as the key's face or the accent, made lighter, darker or more see-through, so the art takes whatever palette the theme has. Layers can move, fade, grow or turn while a key is held. A key with no drawing of its own uses its group's, then the letter key's. Key art replaces the key colours, material and border on the keys it draws. A file can also draw the popups: the bubble over a pressed key, the alternates and the language list then wear that drawing. The file layout is the one the Kyng keyboard uses for its skins, so a skin document in that layout loads as it is. High contrast drops key art, materials, the moving background, lighting and touch bloom with the rest of the decoration. ### Font and sound The theme can bring its own key font and key sound. You pick both from what's on the device: the styles the app ships, plus anything installed from the font and sound libraries. The sound list holds your installed sounds *and* your installed sound packs, each as its own row, so picking one is picking the sound itself rather than a category. They're referenced by name and never copied into the theme file. If someone imports your theme without that font or sound installed, their keyboard quietly uses their own global choice instead. Someone who has picked a key font of their own under [Keyboard fonts](https://wmkeyboard.pages.dev/themes/fonts/) keeps it too: the theme's font only applies while that setting is left on System default. Publish the font and sound as their own add-ons and list them under the theme's `requires` in a [repository manifest](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/), so the install screen offers to fetch them together. **Key font** covers Latin, Cyrillic and Greek. Every other script keeps its own face, because a Latin-only display font draws a Bengali board as empty boxes. **Add a font for a script** is how a theme keeps its look on those boards anyway. Pick a script, pick a face that actually carries its glyphs, and that script uses it instead of its automatic Noto face. Add as many as you have fonts for; the scripts you skip are unaffected. A pixel theme usually needs one file per script, since a single pixel font almost never covers Latin and Bengali and Arabic at once. Each of these is another add-on to publish and list under `requires`. Your own pick in Appearance → Fonts still beats the theme's, the English one included. You chose that font on purpose, and a theme doesn't get to overrule it. For a theme you plan to share, use the forms that travel. Google Font ids (`google:Caveat`) resolve the same on every device, and an installed sound or font can be named by its catalogue name: "Typewriter" for a sound, `installed:Bloxat` for a font. The ids the libraries mint locally differ from phone to phone. A theme's sound is only ever a sound it can actually make. A theme whose sound is missing on this device (never installed, or since deleted) falls back to the global **Key press → Sound style** pick, and so does a theme file whose `soundStyle` is `CUSTOM` or `PACK` with no `soundCustomId` beside it. Those two styles name something installed rather than a waveform of their own, so without a name there is nothing to play, and overriding your own pick with silence-turned-system-click would leave the whole keyboard clicking with nothing on screen to say why. ### Accent - **Accent** paints the shift-on tint, the patch behind a locked Shift, the active-tool color, the buttons across the panels, and the [smart chips](https://wmkeyboard.pages.dev/smart/chips/) on the suggestion strip, the contact chip among them. Those chips stay in Accent rather than the suggestion text color, so they read as answers rather than words. - **Gesture trail** is the color of the swipe-typing trail. Auto falls back to Accent, and the field supports alpha. ### Popups The popups have a section of their own: open it from the **Popups** row under [Toolbar, strip and popups](#toolbar-strip-and-popups), or from the **Popups** chip while picking on the keyboard. At its top is a drawing of the two popups in the theme you are editing, the bubble over a pressed key and the press-and-hold alternates with one lit, on a patch of the board's own fill, so a colour, the shape, the outline and the shadow can be judged without holding a key down. The bubble is left out while key popups are off under Key press, since the keyboard never draws it then. - **Popup background** and **Popup text** color the bubble that appears above a key on press, and the press-and-hold alternates with it: the sheet the alternates sit on is Popup background, and every alternate not under your finger is Popup text. Both support Auto fallbacks, deriving from the board and key colors and from Key text respectively. Popup background supports alpha. The highlight that follows your finger across the alternates is **Selected highlight** under [Panels & suggestions](#panels--suggestions), and the character on it is **Selected text**, so the gap between that highlight and the sheet's edge is the alternates' spacing, which is Key press / Alternate keys / Alternate key spacing rather than a theme field. See [What's what](#whats-what) for a drawing of each part. - **Popup shape** sets the outline of every popup surface the keyboard draws: the character bubble, the long-press alternates, the language list, the panel menus. It's the same picker and the same twelve shapes as Key shape, drawn at this theme's popup radius. Until you pick one, the theme follows Key press / Key popup / Popup shape. - **Popup placement** decides whether the character bubble grows out of the pressed key (the stock style) or floats detached above it. **Default** leaves the choice to Key press. The other two make it part of the theme, so a bubble-popup theme looks right on every phone it lands on. - **Popup outline** and its width row draw a border around the bubble, off until you pick a color, exactly like the key border. It draws on every popup surface the keyboard opens: the character bubble, the long-press alternates, the spacebar language picker, the clipboard and emoji menus, and the small pickers inside the tool panels. - The popup can also wear an image: the **Key preview popup** row under [Key textures](#key-textures) paints one inside the bubble, clipped to the popup shape, using the same Crop/Stretch/Tile mode and opacity as the key textures. - A theme with a [key material](#key-material) draws its popups in the same material: the bubble, the alternates and the language list of a wooden board are wood too. [Key art](#key-art) with a drawing for popups uses that drawing instead. - **Menu shape** exists because the list menus (the spacebar language picker, the clipboard and emoji menus) don't blindly follow the popup shape. On **Automatic** they take a safe derivative: the plain outlines (Rounded, Square, Cut, Squircle) pass through, and the decorative ones fall back to Rounded. A slanted character bubble is fine. A slanted menu clips its own rows. Pick a shape here only to overrule that judgement. ### Toolbar, strip and popups The bar above the keys is either the toolbar or the suggestion strip, and each has a section of its own rather than a long group on this page, as do the [popups](#popups). Open them from the **Toolbar**, **Suggestion strip** and **Popups** rows in this group, or pick the part itself: with **Tap a part to style it** (the button under the preview), pressing a tool opens the toolbar's section and pressing a suggestion opens the strip's, and the chip row above the preview carries all three as well. The section opens on the page straight under the preview, which stays pinned above it however far you scroll, and while the strip's section is open the preview puts a few words on the strip so there is something to look at. Only one part is open at a time; Back or the × beside its title closes it. The **Toolbar** section: - **Toolbar background** fills the bar behind the tools: their own row, when Appearance / Toolbar gives them one, and the suggestion strip while it is showing the tools instead of words. On Auto it matches **Suggestion strip** in the strip's section, which in turn matches the board, so the bars stay one colour until you pull this one apart. Alpha works here, so a half-transparent fill tints the strip's own colour rather than covering it. The strip takes its own colour back the moment words arrive, so a bar that flips between words and tools flips colour with them; if that reads as a flicker, leave the two the same and use the tools' own row instead. - **Tool background shape** is the outline of the background behind each toolbar tool, from the same twelve shapes. It follows Appearance / Toolbar / Tool background shape until you pick one here. Nothing draws at all while the tool radius is 0, which is the setting for bare icons. - **Tool icons** sets the toolbar's icon color, Auto by default. - **Tool circles** and **Active tool circle** paint the background behind a toolbar icon, at rest and while pressed or active. Both support alpha. - **Tool shadow** lifts that background off the bar. - **Active tool icon** colors the glyph sitting on an active tool. Auto picks whichever of black or white reads on the active circle, which is what this was before the row existed. Set it when the automatic pick is technically legible but not the colour you want. - **Tool outline** and **Tool outline width** put a border around that background, off until you pick a color, exactly like the key border in [Keys](#keys). The width row appears once a color is set, and nothing draws until it's above 0. A theme that sets one keeps it even with the accessibility **Key outlines** toggle on. The **Suggestion strip** section: - **Suggestion strip** fills the bar the word candidates sit on, and every other bar around the keys with it: the toolbar row, the emoji row, the symbol row, the dictionary bar, the fancy text bar and the selection macros. Those rows stack straight against each other, so one color across all of them is the only thing that reads as deliberate. It sits on Auto until you pick a color, and Auto means the bars carry the board straight through, gradient, image, animation and all. Alpha works here, so a half-transparent fill tints the board rather than covering it. One thing to watch: the words on the strip take their color from the board, not from this fill, so if you paint the bars a long way from the board color, set **Suggestion text** just below to match. - **Suggestion text** colors the suggestion strip and the panels' body text. Auto falls back to Key text, contrast-guarded against the board, so a theme whose keys invert against its board (dark ink on cream keys over a near-black board) still gets a legible strip. An explicit choice here is honoured as written. - **Other suggestions** colors the strip's suggestions other than the primary one: the runner-up on its left and the rest on its right, when the primary sits in the middle, or everything after the first word when it doesn't. Auto draws them in Suggestion text, which is what they always were. The primary keeps Suggestion text (and its bold), and the word autocorrect has settled on keeps the colour **Autocorrect word color** under Appearance / Toolbar gives it, so a strip that colours its runner-ups quieter never hides the one word a space is about to type. A word no list knows yet keeps **New word color** from the same screen in the same way. - **Other suggestions size** draws those words at a share of the primary's size, 50% to 120% in 5% steps, 100% being the same size. It's a ratio, not a size, so it rides on the suggestion-size slider under Typing the way the primary does, and a word squeezed to fit its slot shrinks from this size rather than from the primary's. The conversion candidates of the CJK layouts and Avro's candidate list are one row of equals and ignore it. - **Suggestion pills** colors the rounded box behind each word, which the strip draws only while **Coloured suggestions** is on under Typing / Suggestions. Auto is the theme's accent made faint (16% on a dark theme, 10% on a light one), so the pills follow the accent the way they always did; pick a color here to set them apart from it. Alpha counts, so a half-clear pill lets the strip show through. - **Main suggestion pill** colors the pill behind the primary word, the one a space puts in. Auto lays the Suggestion pills color over itself, so the main word still reads a step deeper without a second pick; with both on Auto it's the accent at 34% on a dark theme and 24% on a light one. ### Panels & suggestions - **Cards & search bar** is the `chipBackground` field. It covers suggestion chips and the search-bar surface across the tool panels. Auto falls back to Modifier keys, and the field supports alpha. - **Popup shadow** lifts the key preview and the long-press alternates off the board. - **Selected highlight** and **Selected text** color the letter your finger is on while you hold a key, and the selected row of the language list. Auto uses the accent, which is what these were before they had rows of their own. - **One-handed rail** and **One-handed rail icons** color the strip beside the keyboard in one-handed mode. It used to draw in the settings app's colors and ignore your keyboard theme entirely. - **Secondary text** colors the quieter text beside the main text: the second word of a suggestion, and the kind and time on a clipboard entry. Auto draws Suggestion text at 65% alpha, which is what it was before the row existed. Alpha works here. - **Dividers** colors the hairlines between the parts of a panel and between suggestions. Auto draws Suggestion text at 25% alpha. Alpha works here too, so you can keep the rule faint in a hue of its own. - **Card shadow** lifts the panel cards off the panel. ### Chips & buttons Chips are the small tappable surfaces inside the tool panels: style pickers, tab strips, converter controls, dictionary suggestions, search bars, the cards in the clipboard, snippet, weather, calendar, grammar and plugin lists, and the buttons and fields a [plugin](https://wmkeyboard.pages.dev/plugins/overview/) draws. They take the card color above as their background, and this group styles the rest. The panels' own keypads (calculator, numpad, the converters, the text-editing pad, the handwriting and voice rails) count as keys rather than chips. They follow the theme's key shape and key border, so a circle-key theme gets circular calculator keys for free. - **Chip text** and **Selected chip** / **Selected chip text** cover the resting label, plus the fill and label of the chip that's currently active. Auto derives all three from the theme, and the selected fill follows the active tool circle. - **Chip outline** and its width row draw a border around every chip, off until you pick a color. - **Chip shape** and **Chip corner radius** use the same twelve-shape picker as everywhere else. The default is the 12dp soft rectangle chips have always drawn. - **Card shape** governs the bigger surfaces: clipboard and snippet cards, plugin rows, search bars, the converters' displays. They follow the chip shape through the same safety rule as the menu shape, so plain outlines pass through, decorative ones fall back to Rounded, and an explicit pick here overrules both. Any row left on Auto shows an "Automatic" label under its name instead of a fixed value. Once you've overridden it, that row's color picker gains an **Automatic** button (next to Apply) that clears the override and puts the row back on Auto. On a row that is still on Auto, the picker opens on the colour Auto stands for, and pressing Apply without changing it leaves the row on Auto. A strip or toolbar on Auto paints nothing over the board, so writing the board's own colour over it would draw a lighter band. ### Gradients Both **Board** and **Keys** can carry an optional gradient, painted over their solid color and toggled on with a switch next to each. Turning one on seeds it with a sensible default that you can then reshape. The board gradient starts as a 135° linear blend from Background to Accent, and the key gradient as a subtle 90° white sheen. ![A board gradient turned on: type, angle, and color-stop controls.](@assets/screens/themes/editor-gradient.webp) - **Type**: Linear, Radial, or Sweep. - **Angle**: 0–360°, shown for Linear and Sweep only (Radial ignores it). - **Color stops**: 2 to 4, each with its own color and alpha. **Add color** and **Remove color** grow and shrink the list. ### Corners Off by default, so keys everywhere follow the global sliders (two under Appearance, one under Key press). Turning on **Corner radii for this theme** seeds three independent sliders for this theme alone, each starting at whatever its global counterpart is set to: ![Corner radii for this theme, with the Key, Popup, and Tool circle sliders.](@assets/screens/themes/editor-corners-animation.webp) - **Key corner radius**: 0–28dp, seeded from the global Key corner radius slider (default 8dp) when you turn the switch on. - **Popup corner radius**: 0–40dp, seeded from the global **Popup corner radius** slider under Key press (default 12dp). - **Tool circle radius**: 0–20dp, where 0 draws no background behind the icon at all (the slider reads "Off" there), seeded from the global Tool circle radius slider (default 20dp). ### Layout A second opt-in group, **Layout for this theme**, works the same way: one switch seeds a set of size overrides from your current global settings, and turning it off clears them all again. Tool background width, toolbar height, popup height, key height, key gap, left and right padding, key label size, bold labels, corner hint size, and the gesture trail's width and opacity all live here. Screen-specific sizes from Layout settings (folded, unfolded, landscape) still win over a theme's own. Popup height is the height of the key-preview bubble. The global slider under Key press keeps one value for the on-key bubble and another for the floating one, and the theme keeps the same pair. The slider here shows and edits the height of the style the theme is drawing, its own placement if it names one and otherwise the global one, and the other style's height stays as it was. An on-key bubble is also never drawn shorter than the key plus its label, whatever height it is given, so it cannot vanish under your finger. ### Animation Adds continuous motion to the board while the keyboard is on screen: - **None** (default), **Flow**, or **Hue cycle**, as a segmented picker. - Flow drifts the board gradient along its own axis (or orbits its center, for a radial gradient). Hue cycle slowly rotates the hue of the board gradient, or the solid board color if no gradient is set. - A **Speed** slider (0.25×–3×, default 1×) appears once animation is on. Only three built-in themes ship with animation turned on: Sunset drift and Cyberpunk use Flow, Aurora uses Hue cycle. Every other built-in, and every fresh custom theme, starts on None. ## Live preview while you edit The keyboard itself is pinned at the top of the editor, drawn small enough to leave room for the controls but at the proportions it has on screen. It is the real keyboard, not a picture of one: press a key and it flashes, the popup rises, the key effect fires, the strip shows the word, and a Flow or Hue cycle animation moves. Every change you make below redraws it at once, and switching looks or themes crossfades the way the keyboard does. The preview always draws the theme you are editing, panels included: a layout or panel that names a theme of its own keeps that pairing on the keyboard, but here the Numpad, the emoji panel and the rest show your draft. When one does, a notice above the editor says which and what theme it uses, and **Clear** lets it follow the theme you picked again. Typing goes into the preview alone. Nothing you type there reaches an app, is learned, or is saved. The keys work the way they do when you type: shift and caps lock, the `?123` and `ABC` keys, the globe key cycling your enabled languages, and the emoji key and the toolbar tools opening their panels in the theme. Tools that need something the editor does not have, a microphone or your clipboard history for example, open their panel and stop there. The keyboard runs edge to edge across the screen. **Tap a part to style it**, the button under the preview, or **Pick on the keyboard** under [Toolbar, strip and popups](#toolbar-strip-and-popups), turns the preview from something you type on into something you pick from. Press a key and its [single-key style](#single-key-styles) opens; press a tool and the toolbar's section opens; press a suggestion and the strip's does. The chips above the preview switch it between the typing pages and the panels, since the keys that would normally get you there are being picked too, and swipe typing is off for as long as you are picking so a finger sliding to a key isn't read as a word. **Done** or Back puts the preview back to typing, and opening the docked keyboard does too. When the preview takes too much of a small screen, drag either of the two corner marks under it inward or up. The whole keyboard shrinks at its own proportions, text and icons included, down to 35% of its full size, and stays centred. Drag outward or down to grow it again, or double-tap a mark to go back to full size. The editor remembers the size on this phone, and the docked keyboard opened by the button below is always full size. The floating keyboard button at the bottom right opens the same keyboard at full size, docked along the bottom of the screen with the sections scrolling above it, the way an app sits over the keyboard. A line above the keys shows what you type, with a button to clear it. Tap the button again, press Back, or use the keyboard's own hide tool to put it away. ## Duplicating and deleting There's no Save or Apply button anywhere in this screen: every field write goes straight to storage, so closing the editor (or the app) never loses anything or asks you to confirm. **Duplicating** an existing custom theme isn't available from inside the editor itself; the copy-then-edit flow described in [Creating a theme](#creating-a-theme) only applies to built-ins, from the gallery. **Deleting** a custom theme is a gallery action too: tap the trash icon on its card. A confirmation dialog asks "Delete this theme?" with a note that it will be removed permanently. The theme's image files (background, textures, stickers, particles) are not deleted with it. A housekeeping pass clears them once nothing in any theme still points at them, and only after they have been unreferenced for a day, because looks in the same family share their files: themes/editor-delete-confirm — Deleting a custom theme asks for confirmation before removing it. After a delete, an **Undo** bar shows at the bottom of the screen for a few seconds. It puts the theme back as it was, with all its looks and pictures, and switches back to it if it was the theme in use. To delete several at once, long-press a card in Your themes. The cards there turn into checkboxes: tap more of them to tick them, or use **Select all** in the bar at the bottom, then the trash icon in that bar. It asks once for all of them, and the same Undo bar brings back every theme it deleted. While picking, a tap ticks a card instead of switching the keyboard to it; the close button or Back stops picking. The upload icon in the same bar saves every ticked card in one `.zip` of `.wmtheme.json` files, one per theme, the way each card's own export writes it: a family card as the whole family, a card in the flat gallery as its one look. **Import** reads that zip straight back. See [Several files at once](https://wmkeyboard.pages.dev/themes/sharing/#several-files-at-once). The same bar regroups themes. With two or more cards ticked, **Group into one theme** makes them looks of a single theme, named after the first card, with the look dots on its card to switch between them; rename it with the **Group name** field in the editor. **Ungroup** shows up when a ticked theme has more than one look, and turns every look into a theme of its own. A look that only had a short name of its own, such as "Dark", takes the theme's name with it and becomes "Nord (Dark)". In the flat gallery a card is one look, so Group and Ungroup move just the looks you ticked and leave the rest of their theme together. Neither changes the theme in use, and both offer Undo. Delete isn't offered for built-in themes or for the Default theme, only for themes in Your themes. Export is offered on built-in cards as well, so you can hand someone a copy of one as a file; only the Default card has no export button, since it's generated live rather than stored. ## Details and edge cases - **Editing an existing custom theme doesn't switch to it.** Tapping the pencil on a theme already in Your Themes opens the editor without changing your active keyboard theme. Only Create, Duplicate (from a built-in), and Import set the edited theme as active, as a side effect of those actions rather than of editing. - **Typography is part of a theme, but only once you set it.** The key font, the per-script fonts, key label size, bold key labels and corner hint size are all theme fields. Each one is empty until you touch it, and an empty field follows your global setting instead. So a theme you never gave a font to picks up whatever [Keyboard fonts](https://wmkeyboard.pages.dev/themes/fonts/) is set to, and changing that global font changes how the theme looks. Fonts are the one exception to "the theme's value wins once set": the theme's font shows only while your own font for that script is on its default, because a font you picked is a deliberate answer and a theme's is a suggestion. The sizes work the same way through the **Layout for this theme** switch. - **The key-preview bubble has no band to rise into here.** On the real keyboard, the bubble for a top-row key is drawn in an empty strip of window kept above the board, over whatever app you are typing into. The preview has no window of its own, so it holds no such strip. Keeping one would be dead space taken out of the board's own room, and on the miniature it cost the board most of the width it had. The docked keyboard draws that bubble over the sections above it instead, the way the real one draws it over the app; the miniature, which is clipped to its own rounded corners, sits it on the board's top edge rather than letting it be cut in half. - **Selected highlight used to miss the alternates.** Before version 0.5.14 the press-and-hold popup drew the highlight under your finger in the **Pressed key** colour and ignored Selected highlight and Selected text, so changing either row did nothing visible there. Both now reach the alternates as well as the menus. A theme that relied on the old behaviour and wants the pressed colour back sets Selected highlight to it. - **Two "Auto" swatches don't match the real keyboard.** Pressed key and Active tool circle, when left on Auto, are worked out by a different formula in the editor's swatch than on the board itself. The swatch blends the key colour 40% toward the accent; the keyboard blends it 25% toward the key text. On a theme with a strong accent the two are visibly different colours, so trust the live preview over the swatch. Every other Auto fallback (popup background, tool circles at rest, and so on) matches exactly. - **A theme's background image lives in this same editor**, under Board, but the picker, crop dialog, and opacity/blur sliders are covered in full on [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) rather than here. - **Corner-radius overrides here are theme-specific**; the sliders they inherit from and the global key-shape basics are covered in [Theming overview](https://wmkeyboard.pages.dev/themes/overview/). - **Exporting, importing, and installing a theme from a repository** are covered in [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/), including what happens to a theme's colors and images when it travels as a file. --- # Theme families and looks URL: https://wmkeyboard.pages.dev/themes/families/ Section: Themes & appearance Summary: One theme, several looks: family cards with swatch dots, the Grouped and Separate gallery styles, and building a family of your own. A theme can carry more than one **look**. The dark colourways are really one design in seven colours. Catppuccin is four flavours of the same palette. Solarized is a light half and a dark half, and so is Dracula, whose light half is Alucard. Rather than spilling those across the gallery as unrelated cards, they group into one **family**: one card, one preview, and a row of dots under it, one per look. Each look is still a whole theme underneath. It has its own complete set of colours and its own id, so anything that names a theme can name one specific look. ## Family cards in the gallery Appearance / Keyboard themes A family card shows the family's name as its title, the name of the look it's currently showing underneath that, and a dot per look beneath the preview. Each dot is painted with that look's board (its gradient if it has one, its plain board colour otherwise) with the accent as a small core, so two colourways with the same board still tell apart. Tapping a dot does two things at once: the card turns its preview to that look, and the keyboard switches to it. There's no second screen to open, which is the point of putting the dots on the card. The rest of the card behaves the way a single-theme card does, with one wrinkle each: - **The pencil** on a built-in family asks **Make a copy** first, offering **Only this look** or **All looks**. On a family of your own it just opens the editor, on the look the card is showing. - **Export** saves the whole family as one file while the gallery is grouped, and the single look the card shows while it's separate. - **Delete** goes by the card, not by the look showing on it. A grouped family card deletes the whole family. To drop one look, either switch the gallery to Separate and delete that look's own card, or use the bin beside the Looks chips in the editor. The one card that still takes the whole family with it is the first look's, since that's the theme the others hang off. - **The pin** (built-ins only) is per look, not per family, because the keyboard's Appearance tool lists looks. ## Grouped or separate Appearance / Keyboard themes / Theme gallery style The dots aren't compulsory. A three-way control above the grid decides how a multi-look theme is drawn: | Choice | What the grid does | |---|---| | **Auto** *(default)* | Follows the answer you gave the setup wizard | | **Grouped** | A theme with more than one look is one card with a dot per look | | **Separate** | Every look of every theme is its own card | **Auto** reads the "What kind of keyboard do you want?" question from the setup wizard: **Keep it simple** gets the separate list, and every other answer, including never having answered at all, gets the grouped cards. So an install from before families existed picks a side on its own, and an explicit Grouped or Separate always wins over the guess. A caption above the control tells you which way it's currently resolving. It makes a real difference to how far the built-in section scrolls. Twenty-four of the 29 built-in themes belong to a family, so grouped they fit into eleven cards. ## The built-in families The card title is the family's name, which isn't always the name of the look heading it: the dark colourways card reads **Nightfall**, not Ocean. | Family | Looks | |---|---| | Nightfall | Ocean, Forest, Sunset, Berry, Crimson, Slate, Pitch black | | Pastel | Snow, Mint, Rose, Sand, Glacier | | Gradients | Deep sea, Sunset drift, Aurora, Nebula | | Frost | Frost, Frost wallpaper, Frost light, Frost liquid | | Dracula | Dracula, Alucard | | Solarized | Solarized Dark, Solarized Light | | Catppuccin | Catppuccin Mocha, Catppuccin Latte, Catppuccin Frappé, Catppuccin Macchiato | Bubble, Facet, Nord, Tokyo Night and Cyberpunk stand alone, so they draw as ordinary single cards either way. Switch the gallery to Separate and those family names disappear with the cards: every look goes back to its own name, which is the name it has always had and the one an export writes. ## Building a family of your own Appearance / Keyboard themes / (edit a theme) / Looks Every custom theme starts as one look, and the editor's **Looks** row (right under the live preview) is where you add more: - **Add look** copies the look you have open into a new one and switches the editor to it, so the second look starts from the first rather than from nothing. It stops at twelve extras, thirteen looks in all. - **The chips** switch which look the whole editor below is editing. The colours, the shape, the background, all of it belongs to the open look. - **Group name** appears once there are two looks. That's the title the gallery card shows over the dots. Leave it empty and the card falls back to the first look's own name. - **The bin icon** beside the chips deletes the look you have open, and only shows while that isn't the first one. Looks don't nest. A look can't have looks of its own, and anything that would create one (a hand-edited file, an import) has the nesting stripped on the way in. ## What a look's id buys you Because every look keeps its own id rather than being an index into a family, a look is addressable everywhere a theme is: - **Auto theme's** light and dark slots name looks, one each or a whole set. The picker lists them flat with the family's name as a heading above its members, so you can put Solarized Light in one slot and Solarized Dark in the other. In Random mode the family's name carries a checkbox of its own that ticks every look under it, which is the quick way to say "any of the four Catppuccins". See [Theming overview](https://wmkeyboard.pages.dev/themes/overview/#one-theme-or-a-set). - **The Appearance tool's** pin list holds look ids, so you can pin Aurora without pinning the rest of the gradient family. - **Per-mode themes** name a look the same way. The ids didn't change when families landed, either. Every built-in id that ever shipped still resolves, so a selection, a pin or an auto-theme slot set before the grouping still points at the same colours. ## Details & edge cases - **A built-in family's label is translated; yours is typed.** Built-in family names come from string resources so they localise, which is why there's no Group name field on a built-in. Your own families carry the text you typed in that field, exported verbatim. - **A separate card exports a look on its own.** The export strips the variants and the group name, so what the other person imports is one theme, not the family minus a few looks. - **Importing a family mints fresh ids for every look.** The ids inside the file are discarded (the head gets a new one and each look is reminted off it), so importing the same family twice gives you two independent families rather than one overwritten one. - **Older builds see the head look only.** The looks live in a key an older importer doesn't know, so a family imported into a build from before this shipped comes in as the first look alone rather than failing. - **A FlorisBoard theme with a day and a night version arrives as one theme with two looks.** See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/). - **Image files are shared between looks, not copied.** Add look points the new look at the same background file, and a file is only deleted once nothing in the theme still points at it. ## Related pages - [Theming overview](https://wmkeyboard.pages.dev/themes/overview/) for the gallery, Mode, Material You and Auto theme - [Theme editor](https://wmkeyboard.pages.dev/themes/editor/) for everything below the Looks row - [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for exporting and importing - [Keyboard themes reference](https://wmkeyboard.pages.dev/reference/settings/themes/) for the field-by-field tables --- # Keyboard fonts URL: https://wmkeyboard.pages.dev/themes/fonts/ Section: Themes & appearance Summary: Change the key-label typeface: Google Fonts, your own files, or addon fonts. The keyboard doesn't have to draw its key labels in the system font. Pick from a curated set of Google Fonts (20 for Latin, a handful for each other script), import your own `.ttf`/`.otf` file, or install one from an addon repository, and do it separately for Latin keys, Bengali keys, and each other script your enabled languages use. themes/font-picker-english — The Keyboard font screen: curated Google Fonts, each row previewed in its own face. ## Choosing a Google Font Every picker is a scrollable list of `SettingsGroup` rows, and every row renders its own label live in the face it names: nothing to imagine from a font name alone. Fonts are fetched once through the Google Play Services downloadable-fonts provider and cached on-device after that, so the second time you pick one there's no download at all. On a device with no Play Services (a de-Googled ROM, most commonly), the Google Font rows don't appear at all. Picking one there would resolve to the system face and change nothing on screen. Rather than a list of rows that all look identical, you get one paragraph at the top of the screen: Play Services supplies those fonts, and the lists below hold the system font, your imports, and anything you installed from the store. The theme editor's own font picker drops its Google list on the same devices, for the same reason. - **English** gets 20 curated faces: Roboto, Inter, Open Sans, Lato, Montserrat, Poppins, Nunito, Rubik, Ubuntu, Source Sans 3, Merriweather, Lora, Playfair Display, Roboto Slab, Roboto Mono, JetBrains Mono, Comfortaa, Pacifico, Caveat, and Orbitron. This same picker also draws Cyrillic and Greek keys, since neither script has one of its own. - **Bengali** gets an **Automatic (Noto)** row (which draws Noto Sans Bengali) plus 8 alternatives: Hind Siliguri, Noto Serif Bengali, Anek Bangla, Baloo Da 2, Tiro Bangla, Atma, Mina, and Galada, shown only while a Bengali-script language is enabled. - **21 other scripts** (Arabic, Hebrew, Armenian, Georgian, Devanagari, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Khmer, Myanmar, Ethiopic, Korean, Japanese, and Chinese) each get their own short curated list (from a single alternative for Armenian or Georgian, up to nine for Thai, Chinese, Korean, or Japanese), plus an **Automatic (Noto)** default row. A script's section only appears while a language using that script is enabled, so the settings screen never shows pickers for scripts you don't type in. Myanmar's automatic face is fetched from the font provider like the others, unless Burmese's [Unicode key font](https://wmkeyboard.pages.dev/languages/burmese/#unicode-key-font) is on: then it's the keyboard's own downloaded copy of Noto Sans Myanmar, for phones whose font is Zawgyi. - Dozens of other scripts draw with an automatic Noto face too, with no picker at all, since none has a second well-supported face to offer. That covers real shipped-language scripts such as Thaana, Syriac, Tifinagh, Cherokee, N'Ko, Canadian Aboriginal Syllabics, Tibetan, Ol Chiki, Meetei Mayek, and Tai Le, plus notations like music and Braille. Selecting a font applies it immediately. The English face governs English mode, the Bengali face governs Avro, প্রভাত, and জাতীয় modes. Each script face governs typing in that script, across key labels, the suggestion strip, and the keyboard's own panels. A font you pick here beats one that comes with a theme or a layout. A theme's key font and a layout's label font only fill in while that script's row is left on its default, so leave English on **System default** if you want a theme or layout to bring its own type. ## Importing your own font file Every script section has an **Import a font file (.ttf or .otf)** button, and its own installed-fonts list once there's a font that fits it. The button adds the file to one shared library rather than overwriting a single slot, so importing a second font doesn't evict the first. Each list is filtered to the fonts that declare that script, plus any that declare no coverage at all. Import a Devanagari or Thai font from any section, or install one from an addon repository, and it turns up in that script's own picker. English and Bengali also keep a legacy single-file slot from older builds. A picked file goes through two checks before it's accepted: 1. A byte-signature sniff that gives a specific rejection message for common near-misses: WOFF/WOFF2 web fonts ("use the .ttf or .otf version"), zip archives, or HTML/XML files saved with the wrong extension. 2. An actual font-load attempt, so a file with a plausible header but a broken internal table is still caught before it's registered. You can pick several files at once in the file picker, and each one joins the library. Two limits apply: 50 fonts in the library at once, and 64 MB per file. Hitting either shows a plain-language dialog instead of a silent failure. Imported fonts live in the app's private storage under a generated id, never the file's original name, and if two fonts share a display name the newer one becomes "Name (2)". Older builds stored one custom English file and one custom Bengali file directly, before the multi-font library existed. Those two legacy files still work, and they still show up as selectable rows for anyone who set one before upgrading. New imports always go into the library instead. ## Installing fonts from an addon repository Fonts are one of the addon types repositories can ship: a text font (`font`) and an emoji font (`emoji_font`) are distinct types, each capped at 64 MB, matching the manual-import limit. Neither type is previewable in the catalogue: a font can only really be judged by looking at the keyboard wearing it, so there's no static preview image, just an install-and-try workflow. Installing a text font addon adds it straight to the library with no follow-up question. The catalogue's **Use** button takes you to the Keyboard font screen and scrolls to the font's own row, in whichever section lists it, so a Devanagari face lands in the Devanagari section rather than at the top. The **Installed fonts** heading under English is only the fallback, for when no row on the screen carries that font. An emoji font addon works differently, and asks whether to start drawing emoji with it right away (see below). Uninstalling a text font removes it from the library outright, and clears it from every slot that named it: the English face, the Bengali face, and any per-script override all go back to their automatic default in the same step. See [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general install flow, and [Addon repository format](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/) if you're building a repository of your own. ## Why a font only shows up where it fits A font can declare which languages it covers. That claim is what keeps a Latin-only display face out of the Bengali picker's installed list, where selecting it would draw every key as an empty box. A section lists a font when one of its declared languages matches that section's script, or when the font declares no coverage at all. A font you import by hand makes no claim, so every section offers it. An addon font's claim comes from the repository's own manifest, which is how a Devanagari face lands in the Devanagari section and stays out of the others. The filter only curates which fonts a picker *offers*. It isn't what keeps keys from going blank. Rendering falls back per glyph to the system font automatically, whatever a font claims, so an exotic display face never leaves a key empty. themes/font-picker-script — A script-specific picker (Devanagari): the automatic Noto face, a short curated list, and any installed fonts that declare coverage for the script. ## Emoji fonts are separate The font you pick here never touches emoji: key labels and emoji are two independent settings, with their own pickers, their own addon type, and their own defaults. See [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/) for choosing between your phone's system emoji set, Google's Noto Color Emoji, an installed addon font, or a custom file. ## Options Appearance / Keyboard font - **English font** takes the system default, one of 20 Google Fonts, or an imported or installed file. It also governs Cyrillic and Greek. Default: system default. - **One section per enabled script**: the script's Automatic (Noto) face, one of its curated Google Fonts, or anything in the library that covers it. Default: Automatic (Noto). English and Bengali also keep a legacy single-file slot. - **Installed fonts** is the shared library. A section lists the fonts that declare its script or declare no coverage at all, and the heading stays hidden when none of them fit. Delete a font from any section that lists it. A related but separate control: Accessibility / Readable font is a one-tap shortcut that sets the English font to Atkinson Hyperlegible, the Braille Institute's low-vision face where lookalike characters (capital I, lowercase l, digit 1) are drawn distinctly. It's the same setting as the English font picker above, just reached from Accessibility instead. Font face and font size are different settings. The size of the labels on the keys (a 0.7×–2.0× scale, independent of typeface) lives on the main Appearance screen, not here. ## Details & edge cases - Fonts apply to key labels, the suggestion strip, and the keyboard's own panels, but not to the text you type, which always commits as plain Unicode and renders with whatever font the receiving app and device use. - The language switcher (the spacebar's preview, the picker list and the carousel) draws each language's name in the face of the script it's written in, not the active layout's: বাংলা in your Bengali face, English and names like Avro phonetic in your English one. So switching language never changes how the names look or how wide they are. - On a board in another script, Latin text keeps your English font: an English suggestion, a copied word in the clipboard chip, the digits on the number row. Every letter your English font has comes from it, and the rest come from the script's face. This needs Android 10 or later; older versions draw the whole board in the script's face, Latin included. On **System default** the Latin comes from your phone's own font, including one your ROM or a font app put in place of Roboto, so a phonetic board like Avro shows its letter keys the way the rest of the phone does. That part needs Android 12; on 10 and 11 the script's face draws a System default board's Latin. - No Play Services means no Google Font rows: they're removed from every picker rather than left there resolving to the system face, and a short paragraph at the top of the screen says why. The system font, your imported files and the font library are all the app's own, so those stay. - With the provider present but the device offline the first time a Google Font is needed, that row quietly renders in the system font instead of erroring; it downloads normally as soon as the provider is reachable. - The 50-font and 64 MB limits are shared across the whole library: text and emoji fonts, manually imported and addon-installed, all count against the same caps. - Deleting a font from its row on the Keyboard font screen resets that setting to the automatic default first if it was currently selected, so the keyboard is never left pointing at a file that's about to disappear. Uninstalling the same font from the Addons screen does the same thing, and clears all three kinds of slot (English, Bengali, per-script) rather than only the one you were looking at. - A script's picker section only appears while a language using that script is enabled; disabling the last language of a script hides the section, though the underlying setting isn't deleted. --- # Frosted glass URL: https://wmkeyboard.pages.dev/themes/glass/ Section: Themes & appearance Summary: See-through keys, bars and popups over a blurred backdrop: how glass themes work, what they need from the phone, and every knob in the editor. A glass theme makes the keyboard's surfaces see-through, blurs what is behind them, and dresses each one with a rim of light and a little grain so a translucent key reads as a pane of frosted glass rather than as a colour you can see through. It is a per-theme setting: every custom theme carries its own **Glass** section in the [theme editor](https://wmkeyboard.pages.dev/themes/editor/#glass), and the built-in **Frost** family shows the ways it can be used. Appearance / Keyboard themes / (edit a theme) / Glass ## What is behind the glass Glass blurs whatever is behind it, and a theme has three things it can put there. **The app behind the keyboard.** With **Blur behind the keyboard** on, the keyboard asks Android to blur whatever app is under its window, and the board's own colour becomes a tint over that blur. This is the system's own window blur, the one the notification shade uses, so it costs the keyboard nothing per frame. It needs Android 12 or later, and it only runs while the phone allows window blur at all. The keyboard never turns it off because of the phone's battery saver; Android does that itself, switching window blur off for every app while battery saver is on, as do the developer option *Allow window-level blurs* and some manufacturers on their cheaper models. The keyboard watches that state live and switches over the moment it changes. When the phone isn't blurring windows, the theme editor says so in red under **Blur behind the keyboard**: on such a phone the glass shows your wallpaper, which stays the same in every app, so a theme that changes with the app on someone else's phone looks fixed on yours. Whenever the blur cannot run, the board stands on your wallpaper instead (see below), or, with nothing behind the glass, is drawn at least as opaque as the theme's **Board opacity without blur** slider says (92% by default). The keys of a glass theme were designed to sit on a blur, and over a sharp app at 40% they would be unreadable; the fallback keeps them readable and the theme still looks like itself. The floating keyboard never blurs behind itself either, because its window spans the whole screen and the blur would cover the whole app, so it uses the fallback too. WM Keyboard's own [power saving](https://wmkeyboard.pages.dev/typing/power-saving/) switches the blur off too, through its **Glass blur** switch, and only while that switch is on. **The theme's background image.** A theme with a [background image](https://wmkeyboard.pages.dev/themes/backgrounds/) keeps that image sharp on the board, and the glass surfaces show it *blurred* where they cover it. The keyboard bakes one blurred, re-saturated copy of the image when the theme is applied, and every glass surface draws the slice of that copy that lies under it, then its own translucent colour on top. Nothing is blurred while you type: a key's face is one image draw with the right offset. This works on every Android version. The bake follows three sliders: **Backdrop blur**, **Backdrop saturation** and **Backdrop brightness**. **Blur the background too**, on by default, draws the board's own picture through the same bake, so the board between the keys is frosted like the keys and the three sliders visibly reach the picture. Turn it off to keep the board as sharp as its own **Image blur** leaves it, with only the panes frosted. **Your wallpaper.** With no image of its own, a glass theme stands on a band of your wallpaper: the bottom of it by default, where the keyboard sits, or the middle or the top under **Part of the wallpaper**. The band is drawn as the board's own picture, under the board colour, and baked for the keys the same way an image is. That is what a window-blur theme falls back to where the app cannot be blurred (an older Android, battery saver, the floating keyboard, the editor's preview), so instead of going opaque the board shows your wallpaper blurred, which is close to what the blurred app would have looked like. The built-in **Frost wallpaper** look asks for the wallpaper outright and never blurs the app, so its backdrop is steady whatever you are typing into. Whether that band is the wallpaper's real picture depends on the phone. Android 13 and later keep the wallpaper's pixels private from every app, so there the keyboard paints soft fields of light in the wallpaper's own three colours instead, which the system does publish; under the glass's blur the two read much the same. On Android 12 and below the picture itself can be read once the theme editor's **Use your real wallpaper** row has been allowed storage access; until then the colour fields stand in there too. A phone with no wallpaper colours at all (Android 8.0) gets the fields in the theme's own colours. **Behind the glass** in the editor picks between these: **Auto** (the image if the theme has one, otherwise the wallpaper whenever the app is not being blurred), **Image**, **Wallpaper** (always, even where the app could be blurred instead) and **Nothing**. A board with nothing behind its glass is its own see-through colour, with the rim and the grain. Over a gradient that already looks right, since blurring a smooth gradient changes nothing. An animated background image (a GIF or animated WebP) that is playing cannot be baked either; its glass surfaces are tint-only until the animation is stopped or reduce motion stills it. ## Which surfaces are glass **Glass surfaces** is a row of chips, one per part of the keyboard: Letter keys, Modifier keys, Enter key, Space bar, Bars (the suggestion strip and every row stacked with it), Tool buttons, Chips, Cards, Popups (the press-and-hold alternates and the key-preview bubble) and Menus (the language picker and the clipboard, emoji and symbol menus). A fresh glass theme has every surface on except the Enter key, whose solid accent is what makes it easy to find. Inside the tool panels the same split holds: every chip there (the GIF, sticker and theme tabs, filter and action chips, search fields) is a Chip, every card (snippets, clipboard items, notices like "this field does not accept GIFs") is a Card, and a panel's own keys (the calculator, the number pad, the handwriting and voice rails) follow the Letter keys and Modifier keys chips. Popups and menus sample the same backdrop as the keys wherever there is one, a theme image or the wallpaper band. Where there isn't, because the glass is the live blur of the app, Android offers no way to blur only what lies under a popup's own window, so on Android 12 and later the keyboard softens *itself* while a glass popup or menu is open, and the popup reads as glass over softened keys. Nothing outside the keyboard is touched. On older phones, or with reduce motion on, those popups are drawn at the fallback opacity instead, like the board. ## Rim and grain Every glass surface gets a one-pixel **Rim**: a hairline of light along its top-left edge fading into a hairline of shadow along its bottom-right, the way light catches a pane. The slider is its strength; at zero there is none. **Rim color** is the colour of the lit edge, Automatic being a soft white on a dark theme and a brighter white on a light one, where the panes are pale and need the edge more. The shadowed edge is always a translucent black. **Grain** lays a faint, fixed noise over each surface. A few percent is enough; it is what stops a large pane of glass from looking like flat plastic. The grain is the same tile on every surface and every launch, so a theme never twinkles. ## Liquid glass Every pane, popups and menus included, shows the backdrop under the board's own colour or gradient, laid out as the board lays it out, so a gradient runs on unbroken across the keys and the toolbar. Popups and menus find their place on the board through the screen, so the press-and-hold alternates, the key bubble and the spacebar's language bar are panes of the same glass as the keys, with the letter keys' fill. **Kind of glass** has two settings. **Frosted** is everything above: the blurred slice under the surface's colour. **Liquid** is the look Apple calls liquid glass, and it changes what happens at the edge of every pane. Each pane is flat glass in the middle with a bevel about 12 dp wide at its rim, the same on every key so the space bar and a letter look like one material. Almost all of the bending happens in the last few pixels of that bevel: what lies just inside the edge is folded back into a thin, squeezed mirror ring, while the middle shows the backdrop straight through. Red, green and blue bend by slightly different amounts there, so the ring is fringed with colour like the edge of a prism. The light is a hairline on the edges facing the upper left and an equal one on the edges facing the lower right, where the inside of the glass reflects it, fading out along the sides, with a short glow inward on the lit side; there is no dark edge. Pressing a key bends its rim harder and swells the middle a little. Full-width bars stay frosted, since a lens bent a wedge into their ends. **Refraction** sets how far the edge bends the backdrop, **Color split** how far the colours part, and **Rim** how bright the hairlines are. The Liquid preset also drops the blur to almost nothing, turns the grain off and brightens the rim, since a lens is clear. A lens needs two things. It needs a picture to bend, so it works over a background image or the wallpaper band and not over the blurred app, whose pixels belong to the system and cannot be read; a window-blur theme set to Liquid is a lens on a phone that cannot blur and frosted on one that can, unless **Behind the glass** says Wallpaper. And it needs Android 13 or later, where a pane can run a small pixel program; older phones draw a liquid theme as very clear frosted glass, with the same two hairlines of light and no dark edge. The program runs on the graphics chip for the keys' pixels only, and it is recorded once per key, not re-run on every keystroke. The optics follow the open-source [Liquid-Glass-Android](https://github.com/QWEA0/Liquid-Glass-Android) lens, whose rim profile and light were measured off iOS 26. What is not here: Apple's panes also merge and stretch into each other as they move, and their highlight follows the phone's tilt. Neither is part of a theme yet. ## Presets and dressing a theme The **Presets** row sets the glass sliders in one tap and makes the theme's fills see-through where they are not already: | Preset | What it does | |---|---| | Frosted | Milky glass: a soft blur, colours lifted a little, a quiet rim. The default. | | Clear | Nearly transparent panes with a bright rim, the way polished glass catches light. | | Mica | Heavy blur and muted colour, like a wall of frosted acrylic. | | Smoked | The backdrop dimmed, a low rim, a touch more grain. | | Liquid | Thick clear lenses: the backdrop bent and colour-split at the edge, a bright rim, no grain. Android 13 and later draw the lens. | A preset lowers the opacity of the letter-key, modifier-key, chip, tool and popup colours to what it needs, but only where a fill is still more opaque than that: a colour you have already made more see-through is left alone. It also gives the letter keys a [key gradient](https://wmkeyboard.pages.dev/themes/editor/#gradients) sheen if the theme has none, so each pane catches the light across its top. Whether the app behind is blurred is left as you set it. Afterwards nothing remembers which preset was picked, and every slider still moves. ## The Frost family Three built-in looks on one card, all glass: - **Frost** is styled after FrostKeys' frosted glass: it blurs the app behind the keyboard and lays only a thin tint over it, about a third, in your wallpaper's colours, with the letters as faint panes in the same hues, the action keys (shift, delete, ?123 and Enter) a solid accent blue, and no rim or grain. **Accent-tinted board**, in the Glass section of a copy of Frost, turns the board's accent off: the board keeps a darker, quieter tint in the wallpaper's hue while the keys stay as they are. That thin tint is the look, and its cost is that labels get faint over a white app, as they do in FrostKeys. On a phone that cannot blur, the board stands on your wallpaper band under the same thin tint instead. - **Frost wallpaper** stands on your wallpaper outright and never blurs the app, so its backdrop is steady whatever you type into, and it is the look every phone and the gallery can show in full. - **Frost light** is the same glass as Frost for a light phone. - **Frost liquid** sets the glass to Liquid: a board barely tinted over the blurred app, clear panes with a bright rim and no grain, the action keys a quieter pane than the letters and Enter a pale grey. Over a live blur its keys draw as very clear frosted glass; where the app cannot be blurred, the board turns nearly opaque. Like Frost, its thin board gets faint over a white app. ## Details and edge cases - **Contrast.** Glass is see-through by design, so what is behind it changes how readable the labels are. The Frost looks are checked against a white app and a black app, blurred and not; a theme of your own is yours to judge in the editor's preview, which has no app behind it and so shows the theme the way a phone that cannot blur draws it, on your wallpaper band. - **The wallpaper band refreshes** when the wallpaper changes, when the theme is applied, and when the keyboard's size changes. It is one small picture in the same cache as the theme's background image. - **High contrast** (under Accessibility) drops the glass with the rest of the decoration: the board goes solid and the keys with it. - **Theme crossfade.** Switching themes eases the rim, the grain and the fallback opacity. The blur itself, its radius and the surface set switch over at the midpoint, since each would otherwise ask for a new bake or a new window layout on every frame of the fade. - **The key-preview band.** The keyboard normally keeps an empty strip of window above the top row, where a top-row key's bubble pops up over the app. A window blur covers all of the window, that strip included, and it sits over the field you are typing in, so while the app behind is blurred the strip is not kept and a top-row bubble stays on the board's top edge instead. - **The bars' held band.** The keyboard normally keeps a strip of empty window above the keys while a bar row such as the tools row is folded away, so toggling it never resizes the window. With the window blurred that strip would be a band of blurred app above the keys, so a glass theme gives the band up and takes the one resize instead. - **Export.** A theme's glass settings travel in its `.wmtheme.json` file, the wallpaper choice included; the wallpaper itself is the other phone's own. An app version from before glass existed imports the same file as a plain translucent theme and loses nothing else. --- # Icon packs URL: https://wmkeyboard.pages.dev/themes/icon-packs/ Section: Themes & appearance Summary: Replace toolbar and key icons one at a time, or all at once by installing an icon pack. Every glyph the keyboard draws around your keys (toolbar tools, the Shift and Enter keys, the emoji tab strip) is a named icon slot you can replace. Change one icon at a time, or install a pack that redraws the whole set. The built-in icons, in the keyboard and in the settings app alike, are Google's Material Symbols in their Rounded style, the same family Android itself draws with. themes/icons-screen-overview — The Icons screen: the active pack, and every replaceable slot grouped by where it appears. ## What you can replace There are 113 replaceable slots, split into four groups: | Group | Covers | Count | |---|---|---| | Tools | Every toolbar tool's own icon (clipboard, GIF, voice, and so on) | 72 | | Keys | Shift (off/on/locked), the Caps Lock key, backspace, forward delete, globe, switch keyboard, emoji key, the four arrow-row keys, the Tab key, and Enter in each of its wordings (search, send, go, next, previous, done) | 21 | | Toolbar chrome | Toolbox, back-to-keys, show-tools, emoji shortcut, close search, incognito badge, power saving badge | 7 | | Emoji tabs | Search, recent, most used, and one per emoji category | 13 | The tools group grows on its own as new tools ship, since it's generated from the app's tool list rather than hand-maintained. A pack doesn't have to cover every slot: anything it leaves out keeps the app's built-in icon, so a two-icon pack that only reskins Shift and Enter is a completely normal thing to install. One thing icon packs deliberately don't touch: the per-key glyphs a language layout author drew into that layout. Those belong to the layout, not to the app's chrome, so swapping icon packs never changes how a layout's own keys look. **Colour.** An icon that sets no colour of its own (`none`, `currentColor`, or nothing at all) is drawn in the current theme's colour, and picks up that tool's own accent colour if **Colorful tool icons** (under Tools) is on. An icon that sets real colours draws exactly as authored and stops following the theme. Most packs should lean on the first behaviour; only go colourful on purpose. **Vector or picture.** An icon can be an SVG or a picture: PNG, WebP, JPEG or GIF (issue #504). A vector is the better of the two where you have one: it scales to any key height, recolours cleanly and costs a few hundred bytes. A picture is what you have when you drew the icon in anything but a vector editor, and it is the only form a [Gboard theme's own key glyphs](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#theme-glyphs-and-icon-packs) ever come in. The two behave the same everywhere else in this page, including which slot they fill and whether they follow the theme's colour; a picture counts as "sets no colour of its own" when every visible pixel in it is the same colour, which is what a glyph drawn on a transparent background looks like. A picture may be up to 512 KB and 2048 px on its longest edge; an SVG may be up to 256 KB. A pack decides which of the two a file is by reading its first bytes, not by its name, so an SVG in a file called `.png` is stored and drawn as the SVG it is. SVGs are read by a small built-in parser rather than a full SVG engine. It supports paths, basic shapes, and simple color and transform attributes. Gradients, filters, embedded images, and `` references are skipped quietly rather than rejected. A `` is the one thing that isn't forgiven: that file is refused, dropped from the pack with a note saying so, and the rest of the pack installs around it. See [Icon packs](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/#icon-packs) in the addon repository format for the exact supported subset, the size limits, and why the declaration is refused. ## Replacing a single icon Appearance / Icons Tap any slot in the list to open its picker. Choose an icon from the built-in Material Symbols set, or tap **Use my own picture…** to import a file of your own: an SVG, or a PNG, WebP, JPEG or GIF. Shift is three separate slots (off, armed, locked) and Enter is one per wording, so "a different picture when caps lock is on" is a matter of filling two slots rather than one. The Caps Lock key a tablet board adds has a slot of its own, apart from the locked Shift: both keys sit on the same board, and each keeps its own face. A key that sends Tab, whether the tablet board's or one you built in the layout editor, draws from the Tab slot. ![Picking a replacement for the Shift icon: built-in alternatives, or an SVG of your own.](@assets/screens/themes/icons-slot-picker.webp) A single icon you import this way goes into a reserved pack called **My icons**, and that slot is pinned to pull from it. Pick files for a few more slots and they land in the same pack, so "My icons" quietly becomes a real, exportable pack as you go. See [Making your own pack](#making-your-own-pack) below. **Reset all icons** turns off the active pack and clears every per-slot choice in one tap. It's a full reset, not only the single-icon changes. Installed packs stay on the device, so delete a pack separately if you want it gone. ## Other looks of the same icon Some icons come in more than one drawing, and their picker opens with an **Other looks** row above the search. The GIF tool is the obvious one: its default is the word GIF inside a box, and **Letters** draws the word on its own. With the built-in icons, the emoji tool and key (a simpler smiley), clipboard (a plain clipboard), settings (a filled gear), voice (a filled microphone, or a sound wave), web search (a magnifier), appearance (a brush), sound & haptics (a speaker), AI writing tools (a robot), hide keyboard (a down arrow) and incognito (hat and glasses) have other looks too. The full list is on the [Icons reference](https://wmkeyboard.pages.dev/reference/settings/icons/#other-looks). A pack can ship other looks of its own. When the active pack draws a slot, the row offers the pack's looks instead of the built-in ones, and a look you picked carries across packs by name: switch to a pack with its own "Letters" GIF and you get that pack's drawing, switch to one without and you get the pack's ordinary GIF icon. ## Installing a pack A pack can come from two places, and both end up in the same list of installed packs. **From an addon repository.** Icon packs are one of the addon types a repository can host. They're capped at 8 MB and, unlike dictionaries or sticker packs, can't be previewed before you install. A set of glyphs is hard to judge from a text description, so it's install-and-look. Installing is a two-step handoff. The pack lands on the device first, then a dialog asks **"Use its icons on the keyboard now?"** with a **Switch to it** button. Declining leaves the pack installed but inactive, so you can come back to it later from the Icons screen without reinstalling. themes/icons-addon-apply-prompt — After installing an icon pack from an addon repository, the app asks whether to switch to it. **Importing a file directly.** You don't need a repository at all: **Import an icon pack** on the Icons screen opens a file picker for any `.wmicons` file, the same format a repository ships. This is the flow for a pack a friend sent you directly rather than one hosted anywhere. Importing switches to the pack immediately, since installing one and not using it isn't usually what you wanted. Pick several files at once and each is installed without switching to any of them, then one message counts them and names any it could not read. **Several packs at once.** Long-press a pack on the Icons screen to start picking, then tap the others, or use **Select all** in the bar at the bottom. The upload icon there saves the ticked packs in one `.zip` holding a `.wmicons` file per pack, and the trash icon deletes them after asking once. **Import an icon pack** reads that zip back, installing every pack inside it. The built-in icons can't be picked. Either way, the device holds at most 20 icon packs at once (the reserved "My icons" pack counts toward that total too). Inside a pack, an SVG is limited to 256 KB and a picture to 512 KB, and the whole archive to 24 MB. ## Making your own pack There are two ways to end up with a pack of your own. One suits a pack you're building for yourself. The other suits one you mean to publish. **Build it in the app.** Pick files slot by slot from the per-slot picker described above. They accumulate into the reserved "My icons" pack on their own. When you're happy with it, tap the export icon on that pack's row on the Icons screen to save it as a real `.wmicons` file through the system file picker. There's no manifest to write by hand, which makes this the fastest way to get a working pack you can keep or hand on. **Hand-author a manifest for a repository.** A `.wmicons` file is a renamed ZIP: a `pack.json` manifest plus one file per replaced slot, named after the slot id (`icons/tool.clipboard.svg`, `icons/key.shift.png`, and so on). Another look for a slot adds `@` and a name to the slot id, as in `icons/tool.gif@text.svg`; give it one of the app's own look names (`text`, `filled`, `simple`…) and a user who picked that look keeps it when they switch to your pack. The extension in the entry name only says where the slot id ends; what the file *is* comes from its bytes. The full manifest shape, the slot-id table, and the supported SVG subset are documented once, in [Icon packs](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/#icon-packs) under the addon repository format. Follow that spec when you publish a pack into a repository. Export a pack from the app first, as above, and you have a real seed file to edit by hand. ## Options Appearance / Icons - **Icon pack** picks which installed pack supplies icons for every slot you haven't overridden individually. Default: none (built-in icons). - **Per-slot overrides** are the icons you've set one at a time from the slot picker. None by default. - **Import an icon pack** opens a `.wmicons` file someone shared, without going through a repository. - **Reset all icons** turns off the active pack and drops every per-slot override in one step. Installed packs stay on the device. - **Other looks** switches one slot to another drawing of the same icon, from the app or from the active pack. None by default. - **Use my own picture…** sets one slot from a file of your own (SVG, PNG, WebP, JPEG or GIF) from inside that slot's picker. When a slot could be filled four different ways, a single icon you picked by hand wins over an active pack, an active pack wins over the glyphs the theme you are wearing brought with it, and those win over the app's own default. Overriding one icon from a pack you've installed sticks even after the pack updates or gets swapped out. A related setting worth knowing about: Tools has **Colorful tool icons** (on by default), which is what makes a monochrome tool icon (built-in or from a pack) pick up that tool's own accent colour instead of a flat theme colour. ## Details & edge cases - **Deleting a pack cleans up after itself.** Along with the pack's own files, deleting it drops any per-slot override pinned to it, so those slots go back to the built-in icon right away rather than pointing at something that no longer exists. Installing a different pack under the same id later doesn't bring the old per-slot choices back. You'd pick them again from the slot picker. - **Deleting the active pack turns off the "active pack" setting** as well as removing it from the list. Any slot that was relying on it, and wasn't separately overridden, reverts to the built-in icon. - **No build differences.** Icon packs work the same way in every build; nothing here is limited to one. - **Icon packs travel with backups.** They're one of the sections you can choose to include in a full settings export (off by default, since the SVGs add to the file size), so a restore on another phone can bring your custom icons along too. - **Replacing a slot's icon replaces its file.** Put a PNG on a slot that held an SVG and the SVG is removed, so the slot never has two files and an export never carries the one you replaced. - **A pack's own `slots` list is advisory, not authoritative.** The app looks at what's actually inside the archive and keeps anything that matches a slot it recognizes, so a pack assembled by hand still installs correctly even if its manifest is out of date. --- # Online photos and rotation URL: https://wmkeyboard.pages.dev/themes/online-photos/ Section: Themes & appearance Summary: Search Unsplash and Pexels for a background, keep photos in a collection, and let the keyboard change its background on a schedule. Besides picking a picture from your gallery, a theme can take its background from Unsplash or Pexels. Photos you keep go into a **photo collection** that any theme can draw on, and the keyboard can work through that collection on a schedule so the background changes on its own. Searching Unsplash and Pexels for a keyboard background. ## Where it lives Appearance / Keyboard themes / (edit a theme) / Board / Background image Everything starts from the same **Background image** row you use for a gallery picture. Tapping it opens **Select a background** with four choices: - **Add from this device** opens the system photo picker, exactly as before. - **Find a photo online** searches Unsplash and Pexels. - **Photo collection** lists the photos you already kept. - **Remove** only appears when the slot already has an image. Two more rows can appear in the same **Board** section, and both stay hidden until you have actually started using photos: - **Rotating background** appears once your collection has at least one photo, or once rotation is already on. - **Photo services** appears once you have set up a key of your own, or once your collection holds a photo that came from a service. That is deliberate. Most people set one picture and stop, and there is no reason to show them a service-key screen they will never open. Both screens stay reachable: the picker's own **Add a key** button goes straight to **Photo services**. ## Finding a photo The browse screen has a search field, chips for each service when both have a key, a row of subject chips, and a color filter. **Subjects and search words are optional.** With neither, you get each service's own selected photos. Type a word, tap a subject, or do both. The **color swatches** always search for something, because neither service can filter by color on its own: their feed endpoints ignore a color, and their search endpoints refuse a query with no words. So a color on its own searches for the subject you picked, and failing that for the color's own name. Each tile dims the parts of the photo the keyboard crops away, leaving the strip it actually uses bright, so what stands out in the grid is what ends up behind your keys. Scrolling to the bottom loads more. ### The photo page Tapping a photo grows its tile into a page that shows it three ways: the photo itself, then a live keyboard mock-up with this theme's keys over this photo, and the photographer's name with a link back. - **Preview this photo for** switches the mock-up between portrait and landscape. It changes nothing on your theme. - **Use it for** decides which slot the photo lands in: portrait, landscape or both. - **Use as the background** downloads it and applies it. - **Add to the collection** keeps it without changing the current theme. After it is applied the page does not close. It swaps in **Undo**, **Done**, and **Match the colors to the photo**, so the things you are most likely to want next are already in front of you. ## What applying a photo changes Putting a photo behind your keys changes three theme values, all of which stay editable afterwards: 1. **The board color's opacity drops to zero**, so the photo shows at full strength rather than sitting under an opaque color. 2. **Fully opaque key and modifier colors drop to whatever How solid the keys stay says**, 62% by default and adjustable from 20% to 100%. A keyboard is mostly keys, so leaving them solid means the photo is only visible around the edges. Keys you had already made see-through are left alone. 3. **The photographer's credit is stored on the theme**, so it survives an export and shows with no network. If the keys end up hard to read over a busy or bright photo, the page says so and offers a one-tap fix. See [Keeping keys readable](https://wmkeyboard.pages.dev/themes/backgrounds/#keeping-keys-readable). ## The photo collection Appearance / Keyboard themes / (edit a theme) / Board / Rotating background / Photo collection Anything that becomes a background joins the collection, whether it came from a service or from your gallery. Two buttons at the top add more: **Find photos online** and **Add from this device**. Photos are copied into the collection rather than referenced. The file a theme holds is deleted the moment that theme takes a different picture, and a gallery URI stops working once the picker's permission lapses, so a reference would rot either way. A service photo is stored under its own id, so using one photo on two themes keeps one copy. Each tile's menu can use the photo as the current theme's background, match the theme colors to it, or delete it. A line at the top shows how much storage the collection is using. ## Rotating background Appearance / Keyboard themes / (edit a theme) / Board / Rotating background Turned off, this screen is one explanation and one button. Turned on, it fills in, and the first row is **Rotate the background**, the same switch the button turned on. Turn it off there to stop the rotation and keep whichever photo is up. **In use now** shows how many photos are ready, or the progress of the first fetch, plus **Change the photo now**. **Schedule** - **How frequently**: every time the keyboard opens, every hour, every 6 hours, every day, every week, or only when you press Change. - **Change the background of**: the theme in use, every theme, or themes you select. Because the override happens as a theme is drawn rather than being written into it, a built-in theme can rotate too, even though a built-in has nowhere to store an image of its own. **Sources**, both on to begin with - **Photo collection** means the photos you added yourself. - **Online photos** means new photos fetched by subject and search word. Turning a source off hides its photos immediately rather than only stopping new ones from arriving. The files stay on disk, so turning it back on brings them back instead of fetching them again. **Online photos** (shown only when that source is on) holds subjects, search words, **Wide photos only**, **Safe photos only**, **Use mobile data**, and a link to **Photo services**. **Photos and storage** holds **Photos to keep ready** (3 to 20), **Space for ready photos** (8 to 128 MB, default 24), a link to the collection, and **Delete the photos that the app got**. Once the pool passes that size, the app drops photos you have already seen, least-recently-shown first. A photo you have not seen yet, or one you chose yourself, is never dropped. ## How a photo changes the keyboard Three rows under **Rotating background** decide what a photo does to the rest of the keyboard. **Take colours from each photo** is off by default. Turn it on and every new picture rebuilds the theme's colours, which is the same thing **Match the colors to the photo** does by hand on the photo page. **Dim a photo that hides the keys** is on by default, and it's the scrim. When a photo is downloaded, the app measures how bright the strip behind the keys is. It then works out how opaque the board colour would have to be for key labels to clear a 4.5:1 contrast ratio over it. If that's more opaque than the board already is, the extra opacity applies while the photo shows, up to a ceiling of 65%. Past that you've stopped seeing the photo you asked for. A photo that already reads fine gets nothing, so a rotation that looks dimmer than the same photo set by hand is this and not your imagination. The measurement is taken once, when the photo lands, against a standard dark board rather than against your theme's own colours. That keeps a rotation from re-measuring on every draw, and it does mean a very light theme can get a scrim it didn't strictly need. **How solid the keys stay** is the opacity that applying a photo gives fully opaque keys, 62% by default and adjustable from 20% to 100%. It's read at the moment a photo is applied, so it covers a picture from your gallery and one from a service alike. Your colours are safe from the first two rows. A rotation lays them over the theme as it's drawn, so nothing it does is written back into the theme you built. ## When the background actually changes The keyboard moves to the next photo when a session starts, never in the middle of one. A picture changing under your fingers while you type reads as a glitch rather than a feature, and a timer running inside an input method is a leak waiting to happen. So "every hour" means *at most* once an hour, applied the next time you open the keyboard. The swap itself is a local file read: the photo is already on disk, which is the whole reason the app keeps a few ready. Refilling happens as the keyboard goes away, so a download never competes with the first frame of a session. Refilling is skipped while the device is locked, under power saving, on mobile data unless you allow it, and when **High contrast keys** is on. Rotation itself is never skipped for any of those. A phone with no signal for a week still works through the photos it already has. ## Service keys Appearance / Keyboard themes / (edit a theme) / Board / Photo services Both services are free but rate limited: Unsplash allows 50 requests an hour on a free key, Pexels 200. A key built into the app is shared by everyone using that build, so adding your own key is what gets you a budget of your own. The screen is a [provider list](https://wmkeyboard.pages.dev/tools/providers/) of the services the picker searches, all at once, mixed in one grid: **Unsplash** (get a free key at unsplash.com/developers), **Pexels** (get a free key at pexels.com/api) and **Wikimedia Commons**, which needs no key and can point at another MediaWiki. Add, remove or switch each one off, and set up its key in its row. The F-Droid build starts with Commons; the other builds start with the services they carry keys for. Changing a provider clears the remembered budget and the cached search pages, since both belonged to the old key. If a search says there are no requests left, the picker falls back to the other service where it can, and says which one ran out rather than emptying the grid. ## Credit and licensing Both services require the photographer to be named wherever their photo is shown, with a link back to them. The app does this in three places: on each grid tile, on the photo's page, and on the **Background image** row of any theme using a service photo. - Thumbnails are loaded straight from each service's own servers rather than copied first. That is how a photographer is credited with the view. - Unsplash is told when you really take a photo, which is how its download counts stay honest. Two taps on the same photo inside a minute count as one; using it again later counts again. - Links back to Unsplash carry the referral parameters its terms ask for. - The credit is stored on the theme, so it survives an export and shows with no network and no key. ## Details and edge cases - **Photos are downloaded at keyboard size, not full size.** A service original is commonly 6000 px wide, which is about 96 MB of bitmap once decoded. What lands on your device is a few hundred KB. - **A rotating photo never rewrites your theme.** It is laid over the theme as it is drawn, so nothing a rotation does can damage the colors you set. - **Light and dark themes rotate separately.** Each theme keeps its own current photo, so an automatic light/dark pair does not share one. - **Nothing shows on the lock screen.** Photos live in storage that is unreadable before the first unlock, so a theme draws its plain colors there, with no credit line. - **High contrast keys hides background photos entirely.** Every photo screen says so rather than letting a picked photo quietly do nothing. - **Rotation settings are shared, not per theme.** One schedule and one set of sources, with **Change the background of** deciding which themes it applies to. - **Exports carry the photo, not the rotation pool.** A theme export embeds its current background and its credit. It does not carry the collection, which is a cache of your own making rather than part of the theme. ## See also - [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for cropping, opacity, blur, and the separate landscape image - [Editing a theme](https://wmkeyboard.pages.dev/themes/editor/) for the color controls a photo interacts with - [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for what a theme export contains --- # Sharing themes URL: https://wmkeyboard.pages.dev/themes/sharing/ Section: Themes & appearance Summary: Export a theme as a file, import someone else's, or publish it in an addon repository. Every theme, yours or a built-in, exports to a single self-contained file. Any `.wmtheme.json` file imports back in, no matter where it came from. An addon repository serves that same format, so publishing a theme takes no extra packaging step. themes/gallery-export-import — The theme gallery, with export and import actions above the cards. ## Exporting a theme Appearance / Keyboard themes Every card in the gallery has an export icon (a small upload glyph), except the **Default (system)** card. That one is generated live from your device's Material You color scheme rather than stored anywhere, so there's nothing fixed to export. Custom themes and every built-in theme export fine. Tap export and the system "Save as" picker opens, so you choose the destination and filename yourself. The suggested name is the theme's own name plus the `.wmtheme.json` extension, so a theme called "Midnight" suggests `Midnight.wmtheme.json`. The picker uses the plain `application/json` MIME type on purpose rather than a custom one, so file managers and chat apps don't hide the file the way they might hide an unrecognized vendor type. What a card exports depends on how the gallery is drawing it. A grouped family card exports the whole family, every look in one file. A separate card exports the single look it shows, with the other looks and the group name stripped out, so the person you send it to gets one theme rather than a family with holes in it. Flip **Theme gallery style** if you want the other one; see [Theme families and looks](https://wmkeyboard.pages.dev/themes/families/). The exported file is the whole theme, written as plain JSON with no wrapper or format tag around it: colors, gradients and radii, but also the typeface, sound and key-texture choices the theme carries. Unlike a layout or settings export, a theme file identifies itself by its `.wmtheme.json` extension rather than by a marker inside. If the theme has a background image, the app reads that image off disk and embeds it as base64 instead of leaving behind a path that only exists on your device. The export is self-contained, so opening it on another phone doesn't mean re-picking the photo. A theme with no background image has empty image fields. There's no size limit on a local export, so a theme with a large background photo makes a correspondingly large file. ## Importing a theme There are three ways a theme gets imported, and they don't all behave the same way: two apply the theme immediately, one doesn't. Seven more buttons on the same row, **FlorisBoard**, **HeliBoard**, **FUTO**, **Multiling O**, **Kinetica**, **Clink** and **Gboard**, read someone else's format rather than this one, and **Screenshot** reads a picture. They're covered separately below. ### From the Import button Appearance / Keyboard themes The **Import** button, first of the row above the cards, opens a file picker scoped to JSON-ish files. It's intentionally permissive about the exact MIME type, since file providers report a `.json` file as `text/plain` or `application/octet-stream` about as often as they get it right. Pick a `.wmtheme.json` file and it's added to Your themes under a freshly generated ID. The ID stored inside the file itself is discarded, so importing the same file twice creates two separate custom themes rather than updating one in place. Any embedded background image is written back out to local storage as part of the import. Importing this way both adds the theme and switches the keyboard to it immediately: there's no separate confirmation step for "use it now." ### Several files at once Every import button that opens a file picker takes more than one file: **Import**, **FlorisBoard**, **FUTO**, **Kinetica**, **Clink**, **Gboard**, and **Open a file** under **HeliBoard** and **Multiling O**. Hold a file in the picker to start selecting, tick the rest, then confirm. The files are read one after another through the same reader a single file goes through, and the import ends on one message instead of a dialog per file: how many themes came in (*"The app imported 7 themes"*), then, under it, the names of any files that could not be read (*"The app could not read 1 file: notes.txt"*). A file that fails doesn't stop the ones after it. A batch saves without switching the keyboard to anything, even from the **Import** button, which applies a single file straight away. There's no theme in a batch you picked over the others, so the keyboard keeps what it's wearing until you tap a card. A Gboard file skips its preview in a batch, and an Rboard pack in the batch adds every theme inside it, the way **Add all** does. **Screenshot** isn't a batch import: the pictures it takes are views of one keyboard, read together into one theme. To send several themes the other way, long-press a card in Your themes, tick the others, and press the upload icon in the bar at the bottom. It saves one `.zip` holding a `.wmtheme.json` file per theme. **Import** takes that zip back as it is: every theme in it is added, none switched to, and one message counts them. Each file inside is an ordinary theme file, so one taken out of the zip by hand imports on its own too. ### By opening the file elsewhere Tapping a `.wmtheme.json` file in a file manager, or choosing "open with" on one from a chat app, hands it to a small confirmation dialog rather than dropping you into the settings app. It asks "Import *theme name*?" with the note that the theme is added to your themes and applied to the keyboard, then **Import** and **Cancel**. Confirming has the same immediate-apply effect as the in-app Import button: added to Your themes and switched on right away. A theme file carries no format tag, so the app recognizes one in two ways: by its `.wmtheme.json` name, or by the color fields every export writes. The second is what catches a theme whose name got lost on the way, as happens with files opened from Telegram and other chat apps or downloaded in a browser. A hand-written JSON file that leaves those fields out and isn't named `.wmtheme.json` won't be read as a theme; WM Keyboard offers to open it in its editor instead. The in-app Import button above isn't affected, since it doesn't check either. ### From an addon repository See [Addon repositories](https://wmkeyboard.pages.dev/development/addon-repos/) for how repositories work generally. Installing a theme from one behaves differently from the two file-based flows above: it's added to Your themes but not switched on automatically. Browsing a repository shouldn't repaint your keyboard behind your back, so after the install finishes you get a separate, dismissable prompt: "*Theme name* is installed", asking "Use it as your keyboard theme now?" with a **Switch to it** button and a **Not now** dismiss option. Declining leaves the theme sitting in Your themes, installed but not worn, until you tap it yourself. A theme listing in a repository also isn't previewable in the addon browser the way a dictionary or sticker pack is. You judge a theme by seeing the keyboard wearing it, which a static preview panel can't honestly show. Unlike a plugin, a theme's checksum in the repository manifest is optional rather than enforced, since a theme isn't executable code. A theme with no checksum installs exactly like one that has it, with no extra warning either way. ### From a FlorisBoard file The gallery's **FlorisBoard** button reads FlorisBoard's own `.flex` theme packages instead of `.wmtheme.json`, and converts every theme inside one into a custom theme here. A converted theme is saved without being switched on, so you can look at it before wearing it. You can open a `.flex` straight from a file manager too. A file that carries both a day and a night version becomes one theme with two looks rather than two entries. Both FlorisBoard style formats are read, the 0.4 one and the 0.5 one. What converts cleanly and what gets reported instead of guessed at are in [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/). ### From a HeliBoard or LeanType theme The **HeliBoard** button reads the colour themes those two keyboards share. They have no theme file at all: a theme is a line of JSON copied out of the menu at the top of their colour screen, which is why their themes circulate as forum posts. So the button asks where yours is, **Paste** or **Open a file**, and reads either. Both of that keyboard's export shapes work, the ten named colours and the forty-odd internal roles of its *all colours* mode. Like a FlorisBoard import, the theme is saved without being switched on, and the result message says how many of its colours landed. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/). ### From a Multiling O theme The **FUTO** button reads a FUTO Keyboard theme `.zip`, the hand-written `theme.txt` kind or one saved by FUTO's own theme editor, with its background picture and font. The theme is saved without being switched on, and the result message lists anything that did not come across, such as pictures drawn per key. A FUTO theme also opens from a file manager. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#futo-keyboard-themes). The **Multiling O** button reads the colours of a Multiling O Keyboard theme. That keyboard shares a theme as a block of text from its settings export, starting with `OK_Settings_Begin`, or as a link with the block in it. The button asks for **Paste** or **Open a file**, the same as the HeliBoard one. The theme is saved without being switched on, and the result message says how many of its colours landed. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#multiling-o-themes). ### From a Kinetica backup Kinetica has no theme file: its look is a handful of settings (where the colours come from, light or dark, one typed colour, key borders and the swipe trail), and they travel in its backup. So the **Kinetica** button opens a file picker for that backup, the `kinetica_backup_….txt` that **Backup**, then **Export a backup**, saves in Kinetica's settings. Only the theme is read out of it; the words and the rest stay where they are. The colours are worked out by Kinetica's own rules, so a typed colour spreads over the keys, the bar and the popups as it did there. A theme set to follow the phone's light or dark mode comes across as one theme with a dark and a light look, and Material You comes across as a theme that follows the wallpaper. The theme is saved without being switched on, and the result message says what did not come across: a rainbow trail takes the theme's colour, and keys that light up under a swipe aren't drawn. Kinetica leaves its background picture out of its backup, so the picture has to be added again in the theme editor. A Kinetica backup also opens from a file manager. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#kinetica-themes). ### From a Clink theme The **Clink** button reads a `.clinktheme`, the file that **Export** in the theme menu of Clink (a keyboard for iPhone and iPad) saves, and the file its theme repositories publish. The colours, gradients, key borders, shadows, key shape and bold labels come across, and a theme that switched with the phone's light and dark mode becomes one theme with a dark and a light look. The finish a Clink theme is drawn with (metal, stitched cloth, ceramic glaze, raised 3D caps, holographic foil and the rest) becomes the [key material](https://wmkeyboard.pages.dev/themes/editor/#key-material) of the same kind, and its moving background (snow, bokeh, embers, a nebula) the [moving background](https://wmkeyboard.pages.dev/themes/editor/#moving-background) of the same name. The theme is saved without being switched on. A `.clinktheme` also opens from a file manager, and a whole Clink theme repository can be added under Addons. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-themes). ### From a screenshot of a keyboard The **Screenshot** button makes a theme from a picture of a keyboard: yours, another app's, or one somebody posted. Pick an image that shows all the letter keys. It works on the phone, with nothing uploaded, and it isn't a trained model: it finds the key grid and measures what it sees. It reads the board (one color, a gradient, or a photo rebuilt from what shows around the keys), the key, modifier and enter colors, translucent keys, the key shape and corner radius, borders, key shadows, a sheen or texture on the keys, label and hint colors, the bars above and below the keys, and tool backgrounds. A key with a color of its own keeps it, as long as the rows follow QWERTY. A font and the icons on the keys can't be copied, and neither can frosted glass, which shows up as a plain translucent color. Keys that don't look like the rest get their own entry: a pill enter key or space bar on a board of rounded keys, a different color on shift, delete, the mode key, the comma or a single letter. On a board with no key backgrounds at all, the keys that do have one still come out as shapes. You can pick up to three screenshots of the same keyboard. Letters and symbols have different labels, so what one label hides another shows, and a photo behind the keys comes back cleaner. If the keys can't be found, or the copy doesn't match the picture, the letters read by the Paddle text scan model say where the keys are, when that model is already on the phone. It isn't downloaded just for this. The dialog shows a preview before anything is saved, and a warning if the theme redrawn over the picture doesn't match it closely. A small picture gets a note too, since thin lines and small labels are lost in it. Key shape and key opacity can be corrected right there before you save. A screenshot with a busy photo behind translucent keys is the hardest case, so check that kind first. Like the other conversions, the theme is saved without being switched on. **Also copy the sizes** takes the proportions from the picture too, off by default. Key height, gaps, side padding, toolbar height, label size and weight are pinned on the theme, so they leave when you pick another theme. Whether the number row shows, its height, the bottom row's height and the space under the keys are your global settings, and the option changes them. Sizes are worked out for this phone's screen from the proportions of the key grid, so a picture from a tablet gives the same look at this width. A landscape screenshot has no portrait height to offer, so the option isn't shown for one. The reader is in the same library as the built-in document scanner, so the button offers that download the first time on a build that doesn't carry it. ### From a Gboard theme or the Rboard collection The **Gboard** button asks where the theme is. **Open a file** reads a Gboard theme `.zip`: the file Gboard itself exports, and the format the Rboard community shares hundreds of themes in. A ZIP that holds several themes (an Rboard *pack*) lists them so you can pick one, or **Add all**. **Browse Rboard** opens a list of the packs in the Rboard repository on GitHub; the list is fetched only when you open that screen, and a pack only when you press it. The Google Play build has no Browse Rboard, so there the button opens the file picker straight away. Every theme ends on a dialog with a preview, the count of style rules that came across, and a line for each thing that could not. Like the other two conversions, a theme is saved without being switched on. Gboard draws a theme with or without key borders, and a theme that ships both becomes one theme with two looks. You can also open a Gboard theme or an Rboard pack straight from a file manager. See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#gboard-themes-and-rboard-packs). ## Publishing a theme The quickest way to share a theme with everyone is the globe icon on its card, which sends it to the community addon repository through a GitHub issue. See [Sharing with everyone](https://wmkeyboard.pages.dev/addons/community/). To run a repository of your own instead, publishing requires no extra packaging. A repository serves the exact `.wmtheme.json` file the Export button produces. Export the theme. Drop the file into your repository's `themes/` folder. Reference it from the repository manifest. See [Addon repository format](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/) for the manifest shape and a worked theme entry. ## Details and edge cases - **No limit on custom themes.** Unlike sticker packs or plugins, there's no cap on how many themes you can import or create, and none on how many files one batch import takes. - **Re-importing a file always creates a new theme.** Because the ID inside the file is discarded on import, editing a file externally and re-importing it adds another entry rather than replacing the original; delete the old one yourself if you don't want both. - **Deleting the active theme falls back to Default.** If you delete a custom theme that's currently applied, the keyboard falls back to the Default (system) theme. Its background image files stay on disk for now: two themes can share an image file, so only a later housekeeping pass removes the ones nothing points at any more, and only once they've gone a full day unreferenced. - **Import and export work the same in every build.** There's no feature gap between builds for exporting, importing, or installing themes from a repository. - **A background image travels with every export**, portrait and landscape both, so restoring a theme on a new device doesn't require re-picking either photo. See [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for how those images are set in the first place. --- # What are addons? URL: https://wmkeyboard.pages.dev/addons/overview/ Section: Addons Summary: Fourteen kinds of installable content, published by anyone with a static file host, installed with nothing more than a URL. Addons are how the keyboard grows past what ships in the app. Themes, layouts, dictionaries, fonts and sounds can all come from anyone who can host a JSON file. You add a repository by its URL, browse what it lists, and install straight from the phone. No app store, no build step, no account. addons/addons-home — The Addons screen: repositories you've added, plus everything you've installed from them. ## What you can install There are fourteen addon types, and every one of them maps onto something the app already knows how to produce or import on its own: | Type | Max download | Live preview before installing | |---|---|---| | Theme | 16 MB | No | | Layout | 4 MB | No | | Dictionary | 32 MB | Yes | | Emoji keyword pack | 8 MB | Yes | | Snippet pack | 4 MB | Yes | | Espanso pack | 4 MB | Yes | | Sticker pack | 64 MB | Yes | | Icon pack | 8 MB | No | | Font | 64 MB | No | | Emoji font | 64 MB | No | | Key sound | 4 MB | Yes | | Sound pack | 16 MB | Yes | | Plugin | 1 MB | Yes | | Vocabulary pack | 8 MB | Yes | Some types you judge by wearing rather than by looking at in isolation. A theme or a font only makes sense on the keyboard itself, so its detail page has no content preview to offer, only whatever screenshots the publisher chose to include. Dictionaries, emoji keyword packs, sticker packs, snippet packs, Espanso packs, key sounds, sound packs, vocabulary packs and plugins preview their actual content, because the content is the thing you're deciding on. For the type-by-type walkthrough of what each one looks like once installed, see [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) and [Managing addons](https://wmkeyboard.pages.dev/addons/managing/). ## How installing works A repository is an index over files the app already knows how to read, and nothing more. The theme, layout, dictionary or plugin you install from one arrives in exactly the file format the app writes when you export the same thing yourself. Opening a `.wmtheme.json` you found on a forum and installing a theme from a repository both end up running the same import code. Publishers do no packaging, no signing and no building. Installing and applying are two separate steps. Downloading an addon only puts the file on your device. It doesn't repaint your keyboard, swap your icon pack, or start a plugin running. For addon types with an obvious "slot" to fill (theme, icon pack, emoji font, key sound, sound pack, layout, plugin), the app asks you afterward whether to switch to what you just installed. Fonts, dictionaries, emoji keyword packs, snippet packs, Espanso packs, sticker packs and vocabulary packs skip that prompt, because installing already is the whole effect: a font waits for you in a picker, and the rest are live the moment they land. Browsing a repository, or even opening a `wmkeyboard://` link from someone else's page, can never change what your keyboard does on its own. Every install still ends on a screen where you tap Install yourself. ## What a repository can and can't do addons/plugin-trust-preview — A plugin's detail page shows what it can do before you install it. This is the one addon type that isn't pure data. With one exception, every addon type is plain data. No code runs when you install or use one. A theme is colors and shapes, a dictionary is a word list, a sound is an audio file. Nothing in any of those formats can do anything besides be read. A few rules keep that trust model intact even before an addon reaches your device: - **HTTPS only.** A repository or asset URL that isn't https fails outright rather than silently upgrading. A link that was supposed to be intercepted fails visibly instead of loading anyway. - **A relative path stays inside the repository.** A reference written relative to the manifest resolves under the manifest's own folder and nowhere else: a `../` walking up, a `/`-rooted path, or a protocol-relative `//host/path` are all refused, so one manifest can't quietly serve files that belong to somebody else on the same host. A publisher can still name an absolute `https://` URL, on any host, which is how a release asset or a CDN gets used. - **Checksums, verified when present.** A repository can publish a `sha256` for any file. If it does, the app verifies it after download and refuses the install on a mismatch. - **Size caps are enforced mid-download.** A download that exceeds its type's limit (the table above) is aborted in flight rather than flagged after the fact. Plugins are the sole exception: they carry Python that actually runs. That code is sandboxed with no way to read what you type and no access to the text field, the clipboard, or the network. The sandbox has no such API to withhold in the first place. A plugin can ask for local on-device storage and nothing else, and because nothing in the sandbox can reach the network, whatever it stores can't leave the device either. Because a plugin runs code, it's held to stricter rules than the other thirteen types: - A plugin **must** publish a checksum. Installing one with no `sha256` is refused before any network call happens at all, unlike every other type where a checksum is optional. - A plugin you install from a repository always lands switched off. Installing never doubles as "run this," so it doesn't even appear where plugins run until you turn it on yourself. - Installing is refused outright if plugins are switched off at the subsystem level. See [Options](#options) below. A `wmkeyboard://` link (from a repository's README, a chat message, wherever) can only navigate to a screen that shows you the repository address and the addon before you decide anything. It can't add a repository or install anything by itself. ## Browsing on the web The same store is on this site at [wmkeyboard.pages.dev/addons](https://wmkeyboard.pages.dev/addons/): every repository the app seeds, plus any you add by URL, with live previews of themes, layouts, fonts, sounds and plugins before you install anything. On Android each addon page has an *Open in app* button; elsewhere, scan the QR code. It also has a [repository checker](https://wmkeyboard.pages.dev/addons/check/) and [builders](https://wmkeyboard.pages.dev/addons/new/) for themes, layouts, snippet packs, sticker packs, sound packs, icon packs and whole repositories. A file downloaded from an addon page is named with the addon's version, as in `kasroz-1.2.0.wmlayout.json`, so two versions in your Downloads folder can be told apart. The same goes for a plugin saved from the web plugin editor, and a layout exported from the layout builder carries the time it was exported, as it does from the app. ## Finding repositories to add WM Keyboard ships pointed at three repositories, so the Addons screen isn't empty the first time you open it. The first is the sample: [`wmkeyboard-addon-repository`](https://github.com/wasi-master/wmkeyboard-addon-repository) on GitHub. It's a plain repository (a `wmkeyboard-repo.json` index plus the addon files it lists, served over https with no build step), so forking it is the fastest way to start publishing your own. It also doubles as the canonical copy of the repository spec and JSON Schema. The second is `wmkeyboard-monkeytype-sounds`, a companion repository of typing sounds. The third is `wmkeyboard-community-addons`, themes and layouts people made in the app and sent in; see [Sharing with everyone](https://wmkeyboard.pages.dev/addons/community/). If you remove either one, the app won't add it back on its own. The seeding pass remembers which addresses it has already offered, so a repository you removed stays gone, and a new default shipped in a later version still turns up on your next visit. For the technical side of writing your own (the manifest schema, hosting requirements, and what each addon type's payload format looks like), see the [addon repository format](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/) reference. ## Options Tools / Plugins / Allow plugins Plugins have their own subsystem switch, off by default. With it off, no plugin can run, whether you installed it locally or from a repository. Installing a new plugin from a repository is refused outright rather than silently landing disabled. Turn this on before you install your first plugin. The **Plugins tool** you can add to the toolbar is a separate setting from this switch. It only decides whether a Plugins panel shows up on the toolbar, not whether plugins are allowed to run at all. Turning the subsystem off here doesn't remove the toolbar tool, and adding the toolbar tool doesn't turn plugins on. ## Details & edge cases - An addon can declare a minimum app version. If your build is older than what it needs, its detail page still shows it, but installing is refused until you update. - A repository's manifest is capped at 1 MB to fetch, so a repository that's mostly index and little payload stays cheap to add and refresh. - You can add up to 30 repositories at once. - A `github.com/user/repo` link resolves to that repository's `wmkeyboard-repo.json` automatically (from its default branch, or a specific branch if the link points at one), so you don't need to type the raw file path yourself. - Removing every repository doesn't uninstall anything you've already installed from them. Installed content and the repository list are tracked separately. See [Managing addons](https://wmkeyboard.pages.dev/addons/managing/) for uninstalling and update checks. - Config backup and restore includes your repository list, though not the addons you've installed from it. Restoring merges the list into whatever you already have rather than replacing it. See [Backup & restore](https://wmkeyboard.pages.dev/reference/settings/backup/) for the full picture. Related reading: [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the browse-and-install flow, [Managing addons](https://wmkeyboard.pages.dev/addons/managing/) for updates and uninstalling, the [Addons settings reference](https://wmkeyboard.pages.dev/reference/settings/addons/) for a screen-by-screen breakdown, and [Addon repositories](https://wmkeyboard.pages.dev/development/addon-repos/) for publishing your own. --- # Sharing with everyone URL: https://wmkeyboard.pages.dev/addons/community/ Section: Addons Summary: Send a theme, layout, icon pack, sticker pack, sound pack or snippets you made to the community addon repository, and how it gets checked, previewed and published. The community addon repository, [`wmkeyboard-community-addons`](https://github.com/wasi-master/wmkeyboard-community-addons) on GitHub, holds themes, layouts, icon packs, sticker packs, sound packs and snippet packs that people made and sent in. It is pre-added like the other two repositories, so what it holds shows up under Addons, and its themes and layouts also behind the **Community themes** and **Community layouts** rows. You can send your own from the app in a few taps. A GitHub account is the only requirement. ## Sending a theme Appearance / Keyboard themes Every theme under Your themes has a globe icon on its card, **Share with everyone**. Built-in themes don't, since they aren't yours to publish. A family card sends the whole family, a separate card the one look it shows, the same as Export. The dialog asks for: - **Name**, the theme's own name to begin with. - **Description** and **Tags**, both optional. Tags are separated by commas. - **Licence**: CC0-1.0 (anyone can use and change it, no credit needed), CC-BY-4.0 (they must credit you) or CC-BY-SA-4.0 (they must credit you and share their changes the same way). - **Replaces your earlier one**, optional: the id of a theme you published before, to send a new version of it instead of a second theme. **Open GitHub** opens a new issue in the community repository with all of that filled in. You check the box saying you made it, or have the right to share every picture in it, and press Submit. Nothing is sent from the app itself. The issue is filed by you, under your account, which is how the repository knows a later update comes from the same person. ## Sending a layout Layout & size / Key layouts Each layout of your own, and each secondary layout, has **Share with everyone** in its ⋮ menu. The dialog is the same as a theme's. Layouts that ship with the app have no such item. ## Sending an icon pack Appearance / Icons Each installed pack with at least one icon in it has a globe button, **Share with everyone**, beside Export. The built-in icons have none. The dialog is the same as a theme's, and it sends the same `.wmicons` file Export writes. ## Sending a sticker pack Tools / Stickers / Your stickers / Sticker packs Each pack under Your packs with stickers in it has the globe button too. GitHub takes files of up to 24 MB, so a bigger pack is stopped in the dialog with a note to take some stickers out. The app itself installs packs of up to 64 MB. ## Sending snippets Text expansion **Export** asks which format to write, and its last choice is **Share with everyone**. Snippets often hold personal text, so the dialog sends one folder at a time (or **All snippets**, when you pick that) and shows how many it is about to send, with a reminder to check them for addresses, phone numbers and passwords. ## Sound packs The app has no way to make a sound pack, so there's no button for one. Build it in the web store's sound pack builder and send it from there, or open **Submit a sound pack** on the repository's GitHub page and drag the pack in. ## Sending from the web store The theme, layout, icon pack, sticker pack, sound pack and snippet builders in the [web store](https://wmkeyboard.pages.dev/addons/new/) have the same form under **Submit to community**, below Export. It builds the same issue, so something made on a computer goes through the same checks as one sent from the phone. ## How the file gets to GitHub The file reaches the issue one of three ways, picked by its size: - **In the link.** Most colour and gradient themes and every ordinary layout fit in the link that opens the issue, so the form arrives filled in and you only press Submit. - **Pasted.** Too long for a link but under about 60,000 characters: the app copies it, and you paste it into the **Addon** box on GitHub. - **As a file.** A theme with photos in it is usually too big to paste. The app first offers **Make smaller**, which re-saves the background pictures as JPEG at keyboard width; that often brings it down to a paste. Animated GIFs and pictures with transparent parts are left as they are. Otherwise **Save file** writes a `.zip`, and you drag it into the **Addon** box on GitHub. - Icon, sticker and sound packs nearly always go as a file. The `.zip` the app saves is the pack itself under a name GitHub accepts, so a `.wmicons`, `.wmstickers` or `.wmsoundpack` you renamed to `.zip` by hand works the same way. Snippets usually fit in the link. ## What happens after you submit The repository's bot reads the issue within a minute or two. It checks the file with the same importer the app uses, so anything the app would refuse is refused there too, with the importer's reason. It also checks the form itself: that the id is free, or belongs to your earlier submission when you are sending an update, and that a licence is chosen. It then comments on the issue with a quick preview drawn from the file, and links to try it before anyone approves it: one opens the addon in the app, the other in the [web store](https://wmkeyboard.pages.dev/addons/). The preview is the keyboard for a theme or layout, a grid of the icons for an icon pack and of the first 24 stickers for a sticker pack. Sound and snippet packs have nothing to draw, so their comment is the checks alone. For a theme or layout, a few minutes later the bot replaces that preview with a render of the real keyboard wearing it. If something needs fixing, the comment says what. Edit the issue and the checks run again. When a maintainer approves it, the bot adds it to the repository and closes the issue with a link to install it. It then appears under Addons for everyone, and a theme or layout also under **Community themes** or **Community layouts**. ## Remixes Only themes and layouts carry a remix credit. A theme or layout installed from any repository remembers where it came from. Copy it, change it and send the copy, and the dialog says *Credited as a remix of …* with the original's name and author. The repository's page for your version shows **Remix of** with a link to the original. A theme you made from scratch, or from a built-in, has no such line. ## Details - Removing the community repository from Addons works like removing the others: permanent, and the **Community themes** row then offers to add it back instead of opening it. - The issue is always filed on GitHub, even when the F-Droid build's Servers screen points the repository at a mirror. - Your theme's own id and any background file paths are never sent; the export already replaces them, and the app installs a published theme under a fresh id. --- # Importing from a link URL: https://wmkeyboard.pages.dev/addons/from-a-link/ Section: Addons Summary: Share a GitHub, Codeberg, GitLab or gist address into WM Keyboard and it works out what is behind it, fetches it, and imports it. Most things you can import are published as a *page* rather than as a file you already have: a theme in somebody's repository, an asset on a release, a gist, a build artifact from a pull request. WM Keyboard takes the address directly, so you never have to download the file first and then find it again in your file manager. There are three ways in: - **Share the link.** From a browser, a chat app, an issue tracker, anything with a share button: choose **Import from a link**. - **Paste it**, on the Add-ons screen: Advanced / Add-ons / Import from a link - **Follow a `wmkeyboard://import?url=…` link**, which a README or a support reply can write for you. addons/import-from-link-confirm — Sharing a GitHub link into WM Keyboard. The dialog names the host it will ask before it asks anything. ## What a link can point at The address alone decides most of it, and the rest is one request to the forge's own API: | You share | WM Keyboard fetches | |---|---| | a file on a repository page (`/blob/`, `/src/branch/`, `/-/blob/`) | that file's raw address | | a raw address, or a release asset (`/releases/download/…`) | the file itself | | a release page (`/releases/tag/v1`, `/releases/latest`, `/releases`) | that release's assets, filtered to the formats this app reads | | a gist | the files in it | | a repository root | its `wmkeyboard-repo.json` if it has one, and otherwise a listing of the files at the top | | a folder inside a repository | a listing of the files in that folder | | a GitHub Actions artifact | the artifact, unzipped to the file inside it | | any other `https://` address | the file at it, identified by what it holds | GitHub, Codeberg and other Forgejo or Gitea instances, GitLab (including a self-managed one), SourceHut and Bitbucket are all understood. A host none of those covers is still fetched: what a file is gets decided by reading it, not by where it came from, so an address that turns out to be a theme imports as a theme. If a link holds more than one thing this app can read, you choose from a list. If it holds nothing recognisable, it says so rather than importing something at random. A link to a repository that publishes an addon index opens the [add-repository dialog](https://wmkeyboard.pages.dev/addons/installing/#adding-a-repository) instead, which is the better answer: you get the whole catalogue rather than one file out of it. ## Nothing happens until you say so A shared address is untrusted input, so it is treated like one: - **Only `https://`.** A plain `http://` address is refused, not upgraded. - **The host is named before it is contacted.** The first dialog says which host will be asked and what is expected back. Nothing is fetched until you press **Download**. - **The import is still confirmed separately.** What comes back goes through the same confirm dialog as a file you opened from your file manager, with the same preview, and the same **Import** button. - **Data saving applies.** On a metered connection the transfer asks first, or is refused if you told it to be. See [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/). - **Downloads are capped and temporary.** Anything fetched lands in the cache and is cleared on the next import. ## Build artifacts A link to a GitHub Actions artifact (`/actions/runs/…/artifacts/…`) is a special case: GitHub does not let an app download one without an account, even when the repository is public. WM Keyboard offers two ways around that, and says which one it is using before it uses it: - **Without a token**, the artifact is fetched through [nightly.link](https://nightly.link), a service this project does not run. - **With a token**, it comes straight from GitHub. Artifacts are always a ZIP around whatever the workflow uploaded, so WM Keyboard looks inside and imports the file it finds. Where there is more than one, you choose. ### The optional token The Add-ons screen takes an optional personal access token: Advanced / Add-ons / GitHub token It is only used for build artifacts and for files in a private repository. The token: - is sent to GitHub and to nowhere else, - is **never** included in a settings backup or an exported config, so sharing a backup cannot leak it, - can be cleared on the same screen at any time. A read-only token is enough. For artifacts it needs the `actions:read` scope; for private repository files, `contents:read`. This is the same rule the `wmkeyboard://` addon links follow. A link can open a dialog and fill in an address. It cannot fetch, install or apply anything without a press. See [Deep links](https://wmkeyboard.pages.dev/reference/deep-links/). --- # Installing addons URL: https://wmkeyboard.pages.dev/addons/installing/ Section: Addons Summary: Paste a repository URL, browse the catalogue, install what you like. An addon repository is a URL (a GitHub repo or any `https://` host) that points at a small JSON index of themes, layouts, dictionaries and the rest. You add the repository once, browse what it offers in a searchable grid, and install one addon at a time. addons/repo-catalogue-grid — A repository's catalogue: search, type filters, and a two-column grid of addon cards. ## Adding a repository Open **Add repository**. Paste an address into the field. As you type, the dialog shows what the address resolved to (`The app reads `), so you can see where the data comes from before anything is fetched: | You paste | It fetches | |---|---| | `github.com/user/repo` (with or without `.git`, with or without a scheme) | `raw.githubusercontent.com/user/repo/HEAD/wmkeyboard-repo.json` | | `github.com/user/repo/tree/branch` or `.../blob/branch` | the same, from `branch` instead of the default | | a direct link ending in `.json` | used as-is | | a repository page on Codeberg, GitLab, SourceHut, Bitbucket or a self-hosted Forgejo | that forge's own raw `wmkeyboard-repo.json` address | | any other `https://` address | treated as a folder; `wmkeyboard-repo.json` is appended | | `user/repo` on its own, the way Clink asks for one | the GitHub repository `user/repo`, as the first row | | a bare `host/path` with no scheme | assumed to be `https://` | Plain `http://` is rejected rather than silently upgraded to https, and so is anything with a non-https scheme. The **Add** button stays disabled until the address resolves. If the address doesn't resolve, or the resulting page can't be read as an addon repository, you get an error dialog rather than a half-added entry. Pasting a repository you've already added isn't an error either: it opens the existing one, so following the same link twice is harmless. A repository, a release, a gist or a single file can be shared straight into the app from a browser or a chat app, with no repository to add first. See [Importing from a link](https://wmkeyboard.pages.dev/addons/from-a-link/). A `wmkeyboard://` link from a README or a chat message can pre-fill this dialog or jump straight to one addon's detail page, but it never adds a repository or installs anything on its own. You still tap **Add**, and then **Install**. See [Deep links](https://wmkeyboard.pages.dev/reference/deep-links/) for the full set. ### Clink repositories Clink, a keyboard for iPhone and iPad, publishes its add-ons from GitHub repositories too, such as the official [`anti-ltd/clink-themes`](https://github.com/anti-ltd/clink-themes) and the forks people make of it. Paste one the way Clink asks for it, `owner/repository`, or as a GitHub address. When the repository has no `wmkeyboard-repo.json`, the app reads the `manifest.json` that Clink's release workflow publishes instead, and lists what it can install: | In the Clink repository | Lands as | |---|---| | Themes (`.clinktheme`) | A theme, converted as in [Clink themes](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-themes), with the gallery picture when the repository has one | | Layouts (`.clinklayout`) | A custom layout of its letter rows, in the language its letters suggest | | Fonts (`.ttf`, `.otf`) | A font in your font library, as it is | | Sounds (`.clinkpack`) | A sound pack that plays one of its recordings on each key, as Clink does | | Language packs | The word list (`.clex`), as a dictionary for that language, and the emoji names (`.emoji.json`), as emoji keywords. A Latin-script pack such as `hi_latn` goes to the romanized language (`hi_rom`); a regional one such as `pt_br` to its language | | Actions (`.clinkext`) | A plugin that adds the action to the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/#from-plugins), run on the same port of Clink's own Python interpreter that every plugin runs on. Needs plugins turned on, like any plugin; see [Clink actions](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions) | A language pack's prediction models and input-method tables only run in Clink, so they aren't offered. Neither are Clink's panels, which draw through Clink's own interface, or its profiles, which tune Clink's own animations; the repository's description says when it holds any. Clink gives each entry no version of its own, so an update is offered whenever the file itself changes, and the version line stays empty. ## Browsing the catalogue Each repository gets its own screen. At the top is a search field that matches an addon's name, description, author and tags. Under it sits a row of type filter chips built from whatever that repository actually contains, and then the grid of cards. Every card shows the addon's first screenshot (or a plain icon in its type's colour if it has none), a status badge that doubles as the install button, and a line of `version · size · author`. Tap a card to open its detail page, where the fuller description and licence live. If the manifest declares a licence, the detail page shows it as its own row: the identifier (`MIT`, `OFL-1.1`, and so on) inline where there is one, or "Press to read the licence" when there's only full text to show. Tapping opens the complete text in a scrolling dialog. An addon that declares no licence information at all gets no licence row, rather than a row saying it has none. ## Installing an addon addons/apply-prompt — The prompt after a first install, offering to switch to what you just installed. Tap the download arrow on a card, or **Install** on its detail page. Installing always does two things and stops. It puts the file on your device, and then, for themes, layouts, icon packs, emoji fonts, key sounds, sound packs and plugins, it asks whether you want to switch to it. Fonts, dictionaries, emoji keyword packs, snippet packs and sticker packs have nothing to "switch to". They're live the moment they land. That split matters because a repository is meant to be safe to browse. Installing three themes while you're comparing them should not repaint your keyboard three times. The question only appears the first time you install something with a slot to fill: updating an addon that wasn't already active updates it quietly. If the addon you're updating *was* the one you had active, it's automatically re-applied under its new id instead of asking you to re-confirm a choice you never revoked. | Type | Question | Confirms as | |---|---|---| | Theme | "Use it as your keyboard theme now?" | Switch to it | | Icon pack | "Use its icons on the keyboard now?" | Switch to it | | Emoji font | "Draw emoji with it now?" | Switch to it | | Key sound | "Use it as your key sound now?" | Switch to it | | Sound pack | "Use this sound pack for your keys now?" | Switch to it | | Layout | "Turn it on so you can type with it?" | Turn on | | Plugin | "Turn it on so it shows in the plugins panel?" | Turn on | Dismissing the prompt just says "Not now". The addon stays installed either way, and every detail page carries an outlined "Use it: open …" button that takes you straight to the right settings screen if you change your mind later. Some addons were designed alongside others. A theme that wants a particular font and key sound says so, and installing it offers to fetch them in the same tap. You still get asked about one thing, the addon you tapped. Its fonts and sounds install quietly, and saying yes to the theme is what puts them on the board. "Only this one" skips them, and the theme falls back to your global font and sound. ### Where each type lands Installing hands the downloaded file to the same importer the app uses for a file you open by hand, so an addon from a repository and one you drag in from Downloads end up identical: | Type | Lands as | Opened from | |---|---|---| | Theme | A new custom theme in your gallery | Themes | | Layout | A new custom layout, off until you enable it | Layout & size → Your layouts | | Dictionary | Extra words for one language (needs the manifest to say which) | Custom dictionaries | | Emoji keywords | Extra search terms for one language's emoji | Emoji keywords | | Snippets, Espanso | Added to your snippet list under fresh ids | Snippets | | Stickers | A new local sticker pack | Sticker packs → Your packs | | Icon pack | Added to your icon library | Icons → Icon pack | | Font | Added to your font library | Fonts → Installed fonts | | Emoji font | Added to your font library | Emoji → Emoji font | | Key sound | Added to your sound library | Key press → Sound style | | Sound pack | Added to your sound library as a whole pack | Key press → Sound pack | | Plugin | Lands on disk switched off | Plugins → Allow plugins | | Vocabulary | Extra words for one language's downloadable list (needs the manifest to say which) | Vocabulary packs → Installed packs | Some dictionaries come in more than one language. Their page has a **Languages** section with a row for each one, so you can install the Latin American Spanish list of a medical dictionary without the English one. Each language installs, updates and uninstalls on its own and goes into that language's custom dictionaries. The download arrow on the card installs one list for each keyboard language you have turned on, in your phone's region where the addon has one. A dictionary, emoji keyword pack or vocabulary pack that doesn't say which language it's for gets rejected rather than guessed at, and each type has its own size cap on what it will download: | Cap | Types | |---|---| | 1 MB | Plugin | | 4 MB | Layout, Snippets, Espanso, Key sound | | 8 MB | Emoji keywords, Icon pack, Vocabulary | | 16 MB | Theme, Sound pack | | 32 MB | Dictionary | | 64 MB | Font, Emoji font, Stickers | **Plugins are a special case.** Installing one requires "Allow plugins" to already be on. If it is off, the detail page tells you so before you even tap Install, with a shortcut straight to the switch: Tools / Plugins / Allow plugins Even with the switch on, a plugin installed from a repository always lands disabled. You still turn it on yourself, through the prompt above or from the plugins list. A plugin is also the one type where the manifest's checksum isn't optional: without one to verify against, the install is refused outright. See [Plugin permissions](https://wmkeyboard.pages.dev/plugins/permissions/) for what a plugin can and can't reach once it's running. An addon can also declare a minimum app version. If your build is older than that, its detail page still shows it, but the install itself is refused until you update WM Keyboard. ## Managing your repositories The Addons screen lists every repository you've added, with the count of addons in each and a three-dot menu for **Refresh**, **Open homepage** (when the manifest declares one), and **Remove**. Removing a repository only removes it from that list. Anything you already installed from it keeps working. You only stop getting update checks for it. The app adds two repositories automatically the first time you open the Addons screen (a sample one and a repository of typing sounds), so there's something to browse before you've found one of your own. Removing either is permanent, and it won't come back. Repositories refresh every time you open the Addons screen, as long as **Check when you open this screen** is on (it is by default). **Only on Wi-Fi**, under it and off by default, skips that check on a metered connection. Pulling the list down ignores both of those switches, because making the request is the point. A repository that's briefly unreachable keeps showing what it last had rather than emptying out. ## Options Advanced / Add-ons Reach the Add-ons screen from the home grid, or find it through settings search. Adding and browsing repositories, and installing from them, all happen on this one screen and the ones you navigate to from it. Two addon-related switches sit elsewhere: the plugin subsystem switch above, and **Add-on repositories** on the Backup screen, which decides whether your repository list rides along in a settings export. See the [Addons settings reference](https://wmkeyboard.pages.dev/reference/settings/addons/) for a full screen-by-screen breakdown, and [Updates & management](https://wmkeyboard.pages.dev/addons/managing/#options) for the repository cap and what backup covers. ## Details & edge cases - **Repository files are plain data, never code.** Adding a repository or installing a theme, dictionary, sticker pack or any other type never executes anything (the one exception, a plugin, still only lands as an inert file until you separately turn it on). There are fourteen addon types in total. That's themes, layouts, dictionaries, emoji keyword packs, snippets, Espanso packs, sticker packs, icon packs, fonts, emoji fonts, key sounds, sound packs, plugins and vocabulary packs. - **Updating replaces rather than duplicates.** When a newer version of an installed addon comes in, the old copy is removed only after the new one verifies successfully, so you never end up with two copies of the same theme. - **A failed install doesn't need cleanup.** If a download is rejected (wrong checksum, over the size cap, not enough free space), the partial download is always deleted, whether the install succeeded or failed. - **The catalogue card's install button and the detail page's Install button do the same thing.** You never have to open an addon just to install it. See [What are addons?](https://wmkeyboard.pages.dev/addons/overview/) for the trust model and the full addon-type table, [Updates & management](https://wmkeyboard.pages.dev/addons/managing/) for update checks and uninstalling, and the [addon repository format](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/) reference if you want to publish your own. --- # Updates & management URL: https://wmkeyboard.pages.dev/addons/managing/ Section: Addons Summary: How installed addons check for updates, what happens when a repository changes, and what uninstalling actually cleans up. Installing an addon isn't the end of the story. Repositories publish new versions, get removed, or occasionally disappear out from under you. This page covers how WM Keyboard notices any of that, and exactly what an update or an uninstall does and doesn't touch. ## Checking for updates An addon's version is compared to what the repository's manifest currently lists, using a lenient reading of semantic versioning: dot-separated numbers compared left to right, with anything missing read as zero. A version without a prerelease suffix (`1.2.0`) always beats the same version with one (`1.2.0-rc1`). The comparison forgives hand-written manifests, so `1.2`, `v1.2.3`, and `2026.07` all parse. The bias is always toward caution. In the worst case an update isn't offered, never that you get talked into a downgrade. There's no background check and no push notification for updates. Status is only ever recomputed when you're actually looking: - **Opening the Addons screen** refreshes every added repository's manifest, once per visit, as long as **Check when you open this screen** is on (it is by default). **Only on Wi-Fi** under it, off by default, skips that check on a metered connection. - **Pulling the repository list down** refreshes all repositories at once, on demand rather than waiting for your next visit. A single repository's overflow menu (⋮) has its own **Refresh** that fetches only that one. Both ignore the two settings above, because making the request is the point. - **Opening a repository's catalogue or an addon's detail page** recomputes status against whatever manifest is already cached, with no network round trip. The one exception is a deep link into a repository you haven't added: there's nothing cached to read, so that page fetches the manifest to have anything to show. If a manifest fetch fails, the previously cached copy is kept rather than cleared, so a repository that's briefly offline doesn't make its addons look uninstalled. ## Applying an update There's no separate "update" action. Installing an addon you already have *is* the update: it downloads the newer payload and points your install at it. The old version stays in place and working until the new one has finished downloading and been verified. Only then is it removed and the new one put in its place, so a failed or interrupted update leaves you on the working version rather than somewhere in between. Because each install gets a freshly generated local ID, an update is really delete-old-then-install-new rather than a patch. If the addon you're updating was the one currently in use (your active theme, your selected icon pack, an enabled layout, and so on), the update re-applies it automatically under its new ID with no prompt, since you never asked to stop using it. If it wasn't already in use, updating doesn't prompt you to start using it either. That "installed, use it now?" prompt only ever appears the first time you install something new. Checksums (`sha256`) are optional for every addon type but one. A [plugin](https://wmkeyboard.pages.dev/plugins/overview/) is executable code, so its manifest entry must carry a checksum or the install is refused outright. "Unverified" isn't a state code gets to be in the way a theme or a word list can. ## When things change underneath you Three different things can put an installed addon out of sync with what you last saw, and each is handled differently. **The manifest changes remotely.** If a repository drops an addon, goes offline, or the publisher renames something, the addon's detail page falls back to one built entirely from what was saved at install time: name, type, version, author, description, repository. It says plainly that it can't read the repository now, so it can't check for updates. Uninstalling still works from this fallback page, and so does the button through to where the addon is used. Only the version comparison is unavailable. The repository's catalogue keeps drawing whatever manifest it last cached, and only says it can't be read when there's no cached copy to fall back on. **You remove the repository from your list.** Installs survive. A theme you installed stays your theme whether or not its repository is still in your list, and only the update check goes away. The Addons screen's **Installed** section keeps listing everything you have, and tapping an entry still opens its detail page using the URL saved at install time, even after the repository entry itself is gone. If that URL is still reachable, update status keeps working too. If not, it degrades to the offline fallback page above. **You delete the installed thing from its own screen.** Deleting a theme from the theme gallery, a font from the font picker, or a pack from the Icons screen doesn't go anywhere near the Addons screen, so nothing there would otherwise notice. Every time an Addons screen, repository catalogue, or addon detail page opens, it first sweeps every installed record and asks the subsystem that actually owns it whether the thing still exists: - **Theme, Layout**: still in your custom themes or layouts - **Dictionary, Emoji keyword pack, Vocabulary pack**: the file is still on disk - **Sticker pack, Icon pack, Font, Emoji font, Key sound, Sound pack, Plugin**: still present in that content type's own store - **Snippets, Espanso**: survives as long as *any one* of the pack's snippets still exists, so deleting one of five by hand doesn't drop the record for the other four A record that fails this check is dropped, and the sweep always runs before status is recomputed, so a theme you deleted by hand from the gallery goes back to showing as available to install, rather than incorrectly claiming to still be installed. ## Uninstalling Uninstalling is best-effort and different per type. The end state is always the same, though: gone, and no longer claiming to be installed, whether or not you'd already removed it by hand from somewhere else. | Type | What's removed | Extra cleanup | |---|---|---| | Theme | Removed from your custom themes, falling back to the default theme if it was active | Background images copied into app storage when the theme was installed stay on disk. A housekeeping pass clears the ones nothing points at any more, once they have gone a day unreferenced | | Layout | Removed from your custom layouts, turned off in your enabled layouts, and unset as the active layout if it was active | None | | Dictionary, Emoji keyword pack | The word list or keyword file is deleted directly | The running keyboard is told to reload, so it stops using it right away | | Vocabulary pack | The pack file and its translation sidecar files are deleted | Your review progress for its words is kept | | Snippets, Espanso | Every snippet the pack installed is removed from your snippet list | If you'd already deleted one snippet from the pack by hand, only the remaining ones are removed now | | Sticker pack | Removed from your sticker packs | None | | Icon pack | Removed from your icon packs | Any per-icon override sourced from this pack is cleared, and the active icon pack falls back if this was it | | Font | Removed from your installed fonts | Every slot that named it (the English face, the Bengali face, any per-script override) goes back to its automatic default | | Emoji font | Removed from your installed fonts | Falls back to the system emoji font if this was selected | | Key sound | Removed from your custom sounds | Falls back to the default click sound if this was selected, and the decoded copy is dropped from the live sound player | | Sound pack | Removed from your sound packs | Falls back to the default click sound if this pack was selected, and its decoded samples are dropped from the live sound player | | Plugin | The plugin's whole folder (script, stored data, and log) is deleted | None | ## Options Advanced / Add-ons The Addons screen holds the addon subsystem's own settings, and they live in its own files rather than in the main settings store, since a repository list and an installed-addons record are files rather than preferences. From here: - **Add repository** adds a repository by URL, up to a limit of 30 in your list at once. - **Check when you open this screen** (on by default) refreshes every repository's manifest each visit. **Only on Wi-Fi** under it (off by default) skips that check on a metered connection. - **Pulling the list down** re-fetches every added repository's manifest, regardless of those two settings. - Two repositories are added automatically the first time you open this screen: a sample one, and one carrying typing sounds. Removing either is permanent. It won't come back on a later visit. Two related settings live elsewhere: - **Backup → Addon repositories** (on by default) controls whether your list of repository URLs is included when you export your settings. It only carries the addresses. The addons you've actually installed travel inside whichever section owns that content instead: a custom theme in the Themes section, an icon pack in the Icon packs section, and so on. Restoring a backup elsewhere brings your repositories back, but never a separate "installed addons" list. - **Tools › Plugins › Allow plugins** (off by default) gates plugin addons specifically. See [WM Keyboard plugins](https://wmkeyboard.pages.dev/plugins/overview/). It's the one addon type where this setting blocks the *install* itself rather than only using the thing afterward. A `.wmplugin` downloaded while it's off is rejected, with a message pointing you to turn the setting on and try again. ## How big an addon can be Every addon type has a maximum payload size, enforced while it downloads. If the server sends more than the limit, the download is cut off and the install fails, rather than filling your storage with something the importer would refuse anyway. | Limit | Types | |---|---| | 1 MB | Plugin | | 4 MB | Layout, Snippets, Espanso, Key sound | | 8 MB | Emoji keyword pack, Icon pack, Vocabulary pack | | 16 MB | Theme, Sound pack | | 32 MB | Dictionary | | 64 MB | Font, Emoji font, Sticker pack | Before any download starts, WM Keyboard also checks that your device has enough free space: the addon's published size plus a 32 MB margin. If there isn't enough room, the install fails immediately with a message telling you so, rather than downloading partway and running out. ## Details and edge cases - **Previews don't require installing.** Nine addon types can be previewed straight from the catalogue before you commit to installing: snippets, Espanso packs, dictionaries, emoji keyword packs, key sounds, sound packs, sticker packs, plugins, and vocabulary packs. A plugin's preview exists for a different reason than the rest. It isn't there to help you judge the content. It's there so you can read what the thing is allowed to do before its code runs anywhere near your keyboard. - **Count caps are shared with locally-added content, not addon-specific.** The ceiling on how many fonts, icon packs, sticker packs, sounds, or plugins you can have applies to everything you've added, whether it came from an addon repository or was imported by hand. A plugin also has its own small cap on how much it can persist through its own storage, separate from the size of the plugin file itself. - **Repository files carry no executable code, with one exception.** Everything a repository publishes is read as data: a theme's colors, a dictionary's words, a sticker's image. The exception is a plugin, which is a script the [plugin sandbox](https://wmkeyboard.pages.dev/plugins/security/) runs. Installing a non-plugin addon never runs anything. - **A killed download can resume.** The transfer asks the server for a byte range, so a partial file left behind when the app was killed mid-download is continued rather than refetched. It's the same range-request-and-partial-file approach the on-device AI model downloads use elsewhere in the app. An attempt that ends inside the app (finished, failed, or cancelled) deletes its partial file on the way out, so retrying after an error starts over. [Downloadable wordlists](https://wmkeyboard.pages.dev/languages/dictionaries/) never resume at all: a dictionary download restarts from the top, since it's a capped stream that's deliberately cut off early and can't be resumed midway. See [What are addons?](https://wmkeyboard.pages.dev/addons/overview/) for the full list of addon types and the trust model behind them, and [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for adding a repository and installing something for the first time. --- # WM Keyboard plugins URL: https://wmkeyboard.pages.dev/plugins/overview/ Section: Plugins Summary: Plugins are small sandboxed Python scripts that add tools to the keyboard. A plugin is a small Python script that adds a tool to the keyboard. It draws a panel (some text, a few buttons, maybe a text box) and does whatever you write it to do. ```python title="main.py" def render(): return ui.column( ui.label("Hello!", style="title"), ui.button("wave", "Wave back"), ) def on_event(e): if e.id == "wave": wm.log("👋") ``` That is a complete plugin, minus its `plugin.json`. You can also write one on the phone. Tools / Plugins / Write a plugin opens an in-app editor with a live preview and starter templates, and its Install button installs the result directly, with no ZIP file. ## Start here Getting started — Build a working cipher tool from an empty folder, in plain Python. — /plugins/getting-started/ API reference — The manifest, every widget, every event, every wm.* function, and every limit. — /plugins/api-reference/ Permissions — The one thing a plugin can ask for, and the many it cannot. — /plugins/permissions/ Security — What the sandbox does, what it leaves out on purpose, and where the limits really are. — /plugins/security/ ## What a plugin can do Compute things, draw its own panel, and add buttons to the bar over selected text. That is the whole list. A plugin can also be nothing but those buttons: a [selection action](https://wmkeyboard.pages.dev/plugins/api-reference/#selection-actions) that turns the text you selected into something else, which is also how a [Clink action](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions) installs. ## What a plugin cannot do - **See what you type.** There is no key event API. A plugin cannot observe, change or delay a single keystroke going to your app. - **Read the text you are writing in.** No API returns the contents of the field. A selection action is handed the text you selected, when you tap it, and nothing else. - **Read your clipboard.** No API. - **Use the internet.** No HTTP, no sockets, no URLs. - **Reach Android.** No files, no reflection, no other apps, no device information, no permissions of its own. - **Run in the background.** A plugin only runs while its panel is open. A plugin with the `reminders` permission can ask the app to show a line of text at a set time, and the app shows it; the plugin does not run for that. No version of a plugin gets around them. The interpreter has no function for any of it. See [Security](https://wmkeyboard.pages.dev/plugins/security/) for how that is enforced, and what the residual risks honestly are. Text still gets into a plugin: you type into its box, tap **Paste** on it, or select text and tap one of its buttons on the selection bar. Results come back out when you tap **Insert** under a result, or in place of the selection. Every one of those is a deliberate tap, on a control the keyboard drew. ## Trying the examples The five demo plugins in the [addon repository](https://github.com/wasi-master/wmkeyboard-addon-repository) are meant to be read. Each one is a single `main.py`: | Plugin | Shows | |---|---| | **Cipher Tool** | Tabs, inputs, buttons, insertable output. The getting-started build. | | **UI Kitchen Sink** | Every widget and every event, with a live event log. | | **Todo List** | `wm.storage`, `wm.reminders`, `json` and the date widget: items with due dates, and a reminder when one is due. | | **Text Tools** | The paste-in, transform, insert-out flow. | | **Math Mode** | A real text engine: a tokenizer, symbol tables, a live result on every keystroke, and options kept in `wm.storage`. | Plugins are **off until you ask for them**. Turn them on here, with **Allow plugins**. The first time, the app fetches the library that runs them, from Google Play on a Play install and from the project's own data repository otherwise. Then install one from the Addons screen, or open a `.wmplugin` file: Tools / Plugins / Manage plugins ## Lua plugins were retired Plugins were written in Lua until version 0.6.6, which replaced Lua with Python. When you update to 0.6.6, every Lua plugin on the phone is removed, since nothing is left to run it. The keyboard's plugin list and the Plugins screen say which ones went. What each one saved is kept. Install a Python plugin with the same id and its data comes back as it was. The five demos above kept their ids in the move, so installing the Python **Todo List** brings your list back. Only what a plugin saved with `wm.storage` is kept; its log and any reminders it had set are not. Data that no plugin has claimed is listed on the Plugins screen, each with a button to delete it now. Anything still unclaimed 90 days after the update is deleted on its own. Tools / Plugins / Manage plugins A Lua plugin file opened after the update is refused with a reason that says it was written in Lua; [Lua is refused](https://wmkeyboard.pages.dev/plugins/api-reference/#lua-is-refused) has the exact rule. --- # Plugin API reference URL: https://wmkeyboard.pages.dev/plugins/api-reference/ Section: Plugins Summary: Everything a Python plugin can use: manifest, hooks, widgets, events, wm.*, modules, limits. Everything a plugin can use. If it is not here, it does not exist. Plugins are written in Python, a subset of it that matches [PyMini](https://github.com/anti-ltd/PyMini), the small interpreter Clink uses. WM Keyboard runs it on its own Rust port of PyMini, the same one on the phone and in the web editor's live preview. [The Python you get](#the-python-you-get) lists what that subset has. ## The manifest `plugin.json`, at the top level of the archive. | Field | Required | Meaning | |---|---|---| | `format` | yes | Always `"wmkeyboard-plugin"`. The one tag that identifies the file. | | `version` | no | Container format version. `1`. | | `id` | yes | Unique, lowercase, 3–64 characters of `a-z 0-9 . _ -`, starting alphanumeric. Becomes the plugin's folder on the device. Reverse-DNS by convention. | | `name` | yes | Shown everywhere. Trimmed to 40 characters, control and direction-override characters removed. | | `pluginVersion` | yes | Your version, e.g. `"1.2.0"`. | | `author` | no | Shown on the install screen. | | `description` | no | One or two sentences. | | `apiVersion` | yes | `4`. A higher number than the app knows is refused. Left out, it counts as `1`, which a manifest without `language` cannot be; see [Lua is refused](#lua-is-refused). | | `language` | no | `"python"`, the only language there is. Leave it out or set it; nothing else is accepted. | | `entry` | no | The script inside the archive. Defaults to `main.py`. | | `permissions` | no | See [Permissions](https://wmkeyboard.pages.dev/plugins/permissions/). Usually `[]`. | | `actions` | no | Buttons on the bar over selected text. See [Selection actions](#selection-actions). Needs the `selection` permission. | | `panel` | no | `false` for a plugin made of actions alone: it has no `render()` and is left out of the keyboard's plugin list. Defaults to `true`. | A manifest this build does not fully understand is refused. That includes any permission string it does not recognize. Half-understood code does not get to run. ### Lua is refused Plugins were written in Lua before 0.6.6. A manifest that says `"language": "lua"`, or that leaves `language` out and gives an `apiVersion` of 3 or lower, is a Lua plugin, and the install is refused with this reason: > written in Lua, which WM Keyboard no longer runs; look for its Python version. A manifest that says `"language": "python"` with a lower `apiVersion` still installs. That is how an action plugin made from a Clink action before 0.6.6 looks; a [Clink action](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions) converted today gets `"apiVersion": 4`. What happened to Lua plugins already on a phone is under [Lua plugins were retired](https://wmkeyboard.pages.dev/plugins/overview/#lua-plugins-were-retired). ## Your functions ```python def render(): # a widget, or a list of widgets ... def on_event(e): # optional ... def transform(text, action): # only for selection actions ... ``` `render()` is called after loading and after every event. `on_event(e)` is called when the user does something. Change your variables in `on_event`, describe the result in `render()`. All three are plain module-level functions. There is no registration step. State lives in module globals, the variables at the top level of your script. They keep their values for as long as the panel is open. A function that assigns to one has to say so with `global` first, the way Python always asks: ```python count = 0 def on_event(e): global count count += 1 ``` Leave the `global` line out and the assignment makes a local variable instead, with no error to tell you. `wm` and `ui` are globals too. Neither needs an `import`. ## Selection actions A plugin can add its own buttons to the bar that appears over selected text. List them in the manifest, ask for the `selection` permission, and write `transform`: ```json title="plugin.json (excerpt)" "apiVersion": 4, "permissions": ["selection"], "actions": [ { "id": "rot13", "name": "ROT13", "summary": "Shift each letter 13 places", "icon": "lock" }, { "id": "quote", "name": "Wrap in quotes" } ] ``` ```python def transform(text, action): # a string, or None to leave the text alone if action == "rot13": return rot13(text) if action == "quote": return "“" + text + "”" ``` A plugin with a single action can take one parameter, `transform(text)`. When the user selects text and taps one of your buttons, the keyboard loads your script in a fresh interpreter, calls `transform` with the selected text and the action's `id`, and puts the string you return in place of the selection. Undo on the bar takes it back. Nothing is kept between taps except what you write to `wm.storage`: a global you set during one call is gone by the next. | Action field | Meaning | |---|---| | `id` | Unique within the plugin, up to 32 characters of `a-z 0-9 _ -`. | | `name` | The button's label. Trimmed to 24 characters. | | `summary` | One line for the settings list. Optional. | | `icon` | A key icon name, the same names a layout key takes (`lock`, `translate`, `link`). Optional; a plugin glyph otherwise. | | `replaces` | `false` keeps the selection and adds your result after it. Defaults to `true`. | | `input` | `"selection"`, the default, or `"none"` to be called with an empty string. | Up to 16 actions. A call gets the same `wm` as the panel, without `wm.ui.set_input`, since there is no panel for it to write into. It has the same step and time allowance as loading your script, because every tap loads it. The user can switch each action off in Advanced / Selection actions, where actions from plugins come after the built-in ones. They also appear in the **WM Keyboard** entry of other apps' selection menus. A plugin can have a panel and actions at once, the way the **Selection actions** template keeps the quote style its action uses. One with actions and no panel sets `"panel": false`. A [Clink action](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions) installs as exactly that: one action, no panel. ## Widgets Built by `ui.*`, which return plain dicts. You can write the dicts yourself if you prefer: every builder below gives a dict with a `type` key and one key per parameter, named as the parameter is. `column` and `row` keep their children under `children`, `tabs` its pages under `pages`, and a page is `{"title": ..., "children": [...]}`. ### Layout ```python ui.column(child, child, ...) # stacked vertically ui.row(child, child, ...) # side by side, equal widths ui.spacer(height=8) # vertical gap, 0-64 ui.divider() # a horizontal line ``` `column` and `row` also take one list, which is the easy way to build children in a loop: ```python ui.column([ui.label(item) for item in items]) ``` ### Text ```python ui.label(text, style=None) # style: "title", "body" or "caption" ``` `body` is the default. Labels are not interactive and have no id. ```python ui.output( text, id=None, mono=False, # monospace, for anything with alignment insertable=True, # show the Insert button copyable=True, # show the Copy button ) ``` **`ui.output` is how your results reach the user's text.** The keyboard draws Insert and Copy buttons under it. A tap on Insert puts the text where they are writing. There is no function that types for them. ### Controls ```python ui.button(id, text, style=None, enabled=True) ui.toggle(id, label, checked=False) ui.input(id, label=None, placeholder=None) ``` `style="primary"` highlights a button. Anything else is plain. `enabled=False` grays one out. **`ui.input` carries no value.** The keyboard owns what is in the box: a tap points the keys at it, and you are told the contents through an `input_changed` event. Use `wm.ui.set_input(id, text)` to write to one. This is why a plugin never sees a keystroke. It is handed the whole contents of its own box after each one, and nothing else. ### Tabs ```python ui.tabs("modes", ui.page("First", child, child), ui.page("Second", child), ) ``` `ui.tabs(id, *pages)` takes its pages after the id, and `ui.page(title, *children)` its children after the title. Top level only, up to 8 pages. Which page is showing is the keyboard's business. You get a `tab_selected` event if you care. ### Date ```python ui.date( id, label=None, value=None, # seconds since 1970, as time.time() or wm.timestamp() give; None for no date yet time=False, # pick a time of day too step=15, # minutes the smaller time buttons move, 1-60 ) ``` The keyboard draws the date between a back and a forward arrow, the time (with `time=True`) between buttons that move it by `step` minutes and by an hour, and **Today** and **Clear** under them. With no `value` it shows **Set date**, which picks today, or the next whole hour when `time` is on. Like `ui.input`, the widget keeps nothing. Each press sends a `date_changed` event with the new value, and the next render draws whatever you kept. Without `time` the value is the start of a day. Days move on the calendar in the phone's time zone, so 9:00 stays 9:00 across a clock change. ### Progress ```python ui.progress() # an indeterminate bar ``` ## Events `on_event(e)` receives a dict. It always has `type` and `id`; some events carry more. Read them as `e.type` or `e["type"]`, whichever you like. | `e.type` | Extra | Fired when | |---|---|---| | `"click"` | none | A button was tapped. | | `"toggle"` | `e.value` (`True` or `False`) | A toggle was flipped. | | `"input_changed"` | `e.value` (a string) | The contents of one of *your* boxes changed. | | `"tab_selected"` | `e.index` (an int, from 0) | A tab was picked. | | `"date_changed"` | `e.value` (seconds, or `None`) | A date widget moved, or was cleared. | `input_changed` fires per keystroke into your own input widget. That is how a live preview works. It is bounded to your panel: you are told what is in your box, never what is typed anywhere else. ## `wm.*` The complete host API. ### Always available ```python wm.api_version # 4 wm.plugin_id # your manifest id wm.plugin_version # your manifest pluginVersion wm.log(*values) # a line in your log, readable in Settings ``` `print` writes to the same log. A line is cut at 512 characters. ```python wm.ui.set_input(id, text) # write to one of your own input widgets ``` Applied after your handler returns. Capped at 8 KB. Panel only: a selection action has no boxes to write to. ```python wm.timestamp(year, month, day, hour=12, minute=0, second=0) ``` That moment in the phone's time zone, in seconds since 1970. A field past its range rolls over, so `day=d + 1` on the last of the month is the first of the next, and `month=13` is January of the following year. This is what a date widget and a reminder take. ### With the `storage` permission ```python wm.storage.get(key, default=None) # the string, or default wm.storage.set(key, value) # strings only wm.storage.remove(key) wm.storage.keys() # a list of strings ``` Strings only, so use `json.dumps` and `json.loads` for anything structured. Local to your plugin, local to the device, deleted when the user uninstalls you. `set` raises `ValueError` when you are over quota. Catch it and say something rather than losing the data silently: ```python try: wm.storage.set("items", json.dumps(items)) except ValueError as err: status = "Not saved: " + str(err) ``` If the manifest did not declare `storage`, `wm.storage` is `None`. ### With the `reminders` permission ```python wm.reminders.set(id, seconds, text) wm.reminders.cancel(id) # True when there was one wm.reminders.list() # [{"id": ..., "time": ..., "text": ...}, ...] ``` At `seconds`, a time as `wm.timestamp()` or `time.time()` gives it, the app shows `text` in a notification with your plugin's name as its title. Setting an `id` that is already set replaces it. `list` gives every reminder not yet shown, soonest first; a reminder leaves the list once it is shown. Your script does not run to show it. The app keeps the id, the time and the text, and posts the notification itself, so a reminder fires with the keyboard closed. It does not fire while plugins, or your plugin, are switched off; a reminder that comes due then is dropped. `set` raises `ValueError` for a time already past, for empty text, or past 64 reminders. Text is cut to 200 characters, with control and direction-override characters removed. If the manifest did not declare `reminders`, `wm.reminders` is `None`. A plugin can check for that and work without it. ### Not present, and never will be `wm.text`, `wm.clipboard`, `wm.http`, `wm.net`, `wm.files`. None of these exists. There is no API for reading what the user types, reading the field, reading the clipboard, or reaching the network. See [Security](https://wmkeyboard.pages.dev/plugins/security/). ## The Python you get A subset of Python 3 that matches PyMini. **The language:** functions with default and `*args` parameters, `lambda`, `if`/`elif`/`else`, `for` and `while` with `break`, `continue` and `else`, `try`/`except`/`finally` and `raise`, `global`, `del`, list, dict and set comprehensions, f-strings, slicing, tuple unpacking, chained comparisons and `import`. **Not there:** `class`, `with`, `yield`, `async` and `await`, `match`, decorators, `nonlocal`, `assert` and `**kwargs`. A script that uses one is refused with a message that names it, not a vague syntax error. **Built-in functions:** `abs`, `all`, `any`, `bin`, `bool`, `callable`, `chr`, `dict`, `divmod`, `enumerate`, `filter`, `float`, `format`, `hex`, `int`, `isinstance`, `len`, `list`, `map`, `max`, `min`, `oct`, `ord`, `pow`, `print`, `range`, `repr`, `reversed`, `round`, `set`, `sorted`, `str`, `sum`, `tuple`, `type` and `zip`. There is no `open`, `eval`, `exec`, `compile` or `__import__`. **Strings** count characters as you see them, so an emoji with a skin tone or a letter with a combining accent is one character to `len`, indexing and `for`. Two strings that look the same compare equal even when one spells an accent as a separate mark. **Dicts answer attribute reads.** `d.name` is `d["name"]` when the key is there, and otherwise a dict method such as `d.get`. That is why `e.type` works on an event. Writing still takes brackets: `d["name"] = value`. **Modules:** `import` reaches these six and nothing else. | Module | What it has | |---|---| | `math` | `sqrt`, `floor`, `ceil`, `trunc`, `fabs`, `pow`, `exp`, `log`, `log2`, `log10`, the trigonometric functions, `degrees`, `radians`, `hypot`, `gcd`, `lcm`, `factorial`, `isnan`, `isinf`, `isfinite`, `copysign`, `fmod`, `comb`, `prod`, and `pi`, `e`, `tau`, `inf`, `nan` | | `json` | `dumps` and `loads`. Bad JSON raises an error. | | `re` | `search`, `match`, `fullmatch`, `findall`, `finditer`, `sub`, `subn`, `split`, `escape`, and the flags `I`, `M`, `S` (also spelled out). Matching spends steps like any loop. | | `time` | `time()` and `monotonic()` in seconds; `parts()` or `parts(seconds)`, a dict of `year`, `month`, `day`, `hour`, `minute`, `second`, `weekday` (Monday is 0) and `yearday` in the phone's time zone; `format(pattern)` or `format(pattern, seconds)` with a Unicode date pattern such as `"EEE d MMM yyyy"`, which writes day and month names in English. Left out, `seconds` is now. | | `random` | `random`, `randint`, `randrange`, `choice`, `choices`, `shuffle`, `sample`, `uniform`, `seed` | | `sys` | `version` | `time.format` takes the patterns Android and Java use (`yyyy`, `MM`, `d`, `HH`, `mm`, `EEE`, `MMMM` and so on), not C's `%Y-%m-%d`. ## Limits Exceeding one of these is something you can see: either an error, or a note on the panel saying what had to be dropped. Two are quiet, and the table says which. | | | |---|---| | Script size | 256 KB | | Archive size | 1 MB, 16 entries | | Steps | 3M on load, 2M per event, 400k per render, 3M per selection action | | Time | 3 s on load, 2 s per event, 0.5 s per render, 3 s per selection action | | Lists, dicts and sets | 10,000 items each | | Strings | 100,000 characters | | Recursion | 300 calls deep | | Selection action result | 64K characters | | Widgets | 256 nodes, 12 deep, 8 tabs *(dropped, and reported)* | | Widget text | 2 KB per node, 64 KB per tree *(truncated, and reported)* | | Storage | 128 keys, 64 chars per key, 8 KB per value, 64 KB total | | Reminders | 64 pending, 64 chars per id, 200 chars of text, up to ten years ahead | | Log | 200 lines of 512 characters *(truncated quietly)* | | Input box | 8 KB *(truncated quietly)* | A step is one statement, one turn of a loop, or one step of a regular expression's matching. Going past the container, string or recursion limit stops the call with an error that `try` cannot catch. A plugin that runs out of steps or time is stopped, the user is told, and it takes a strike. So does one the watchdog has to abandon for going unresponsive. Two strikes and the plugin is switched off until they turn it back on, which also forgives the strikes. That last part is a setting: **Switch off a plugin that hangs**, on the Plugins screen, on by default. With it off the strikes are still counted and still shown, and only the automatic switch-off goes away. --- # Writing your first plugin URL: https://wmkeyboard.pages.dev/plugins/getting-started/ Section: Plugins Summary: Build the Cipher Tool in Python: a plugin panel with two tabs, made with nothing but a text editor. By the end of this you will have built the Cipher Tool: a keyboard panel with two tabs that encodes and decodes text. Plugins are written in Python. If you have written any programming language at all, you already know enough; the Python here is the plain kind, functions, `if` and `for`. To follow this walkthrough by hand, you need a text editor and a way to make a ZIP file. That is the whole toolchain for it. WM Keyboard also ships an in-app plugin editor with live preview and starter templates, including one that builds this exact Cipher Tool. Its Install button validates the manifest and installs the plugin directly. No ZIP file is needed. **Save as a file** in its menu writes the `.wmplugin` for you, named after the plugin and its version, like `Cipher Tool-1.0.0.wmplugin`. This tutorial still walks through the manual path below, since it's the clearest way to see how the pieces fit together. Tools / Plugins / Write a plugin ## Two files in a folder - cipher/ - plugin.json who your plugin is - main.py what it does **`plugin.json`** says who your plugin is: ```json title="plugin.json" { "format": "wmkeyboard-plugin", "version": 1, "id": "com.yourname.cipher", "name": "Cipher Tool", "pluginVersion": "1.0.0", "author": "Your Name", "description": "Caesar and Vigenere ciphers.", "apiVersion": 4, "entry": "main.py", "permissions": [] } ``` The `id` has to be unique, lowercase, and look like a reverse domain name. It becomes the folder your plugin lives in on the device. `permissions` is empty because this plugin does not need anything, and most do not. **`main.py`** says what it does: ```python title="main.py" def render(): return ui.label("Hello from a plugin") ``` `ui` is already there. So is `wm`, the rest of what the keyboard offers a plugin. Neither needs an `import`. ## Make it a .wmplugin A `.wmplugin` is a ZIP with those two files at the top level: ```bash cd cipher && zip ../cipher.wmplugin plugin.json main.py ``` Nothing else to it. Plugins stay off until you turn them on, so start there and switch **Allow plugins** on: Tools / Plugins / Manage plugins The first time, the keyboard downloads the library that runs plugins. Copy `cipher.wmplugin` to your phone. Open it from a file manager. Confirm the install. Then open the **Plugins** tool on the keyboard and tap Cipher Tool. Your greeting appears. You will replace the ZIP and reinstall it as you go. Each round takes about ten seconds. ## How a plugin works Two functions. That is the entire model. ```python def render(): # describe what should be on screen right now ... def on_event(e): # react to the user doing something ... ``` The keyboard calls `render()`, draws what it returns, and waits. When the user taps something, it calls `on_event(e)`, then calls `render()` again and redraws. So you never update the screen yourself. You change a variable and describe the result. If you have used React, this will feel familiar. If you have not, the whole rule is *`render()` says what things look like, `on_event` says what changes*. `render()` returns dicts. The `ui` helpers build them for you: ```python ui.button("go", "Go") # is exactly {"type": "button", "id": "go", "text": "Go"} ``` ## A button that does something Replace `main.py` with: ```python title="main.py" clicks = 0 def on_event(e): global clicks if e.type == "click" and e.id == "go": clicks += 1 def render(): return ui.column( ui.label(f"Clicked {clicks} times"), ui.button("go", "Click me", style="primary"), ) ``` `ui.column` stacks things vertically. Every control needs an `id`, which is what comes back to you in `e.id`. Two Python details are doing work here. `clicks` lives at the top of the file, so it keeps its value between events; your plugin's state is just its module globals. And `on_event` says `global clicks` before it changes it. Without that line, `clicks += 1` would put its result in a new local variable that vanishes when the function returns, and the count on screen would never move. No error tells you so, which makes it the easiest mistake to make here. Any function that assigns to a global needs the `global` line. One that only reads it, like `render()`, does not. `e` is a dict, so `e["type"]` works too. Attribute reads are a shortcut the keyboard's Python allows on every dict. Rebuild the ZIP. Reinstall it. Tap the button a few times. ## Getting text from the user A plugin cannot read what you are typing in your messaging app. There is no API for it, on purpose. Instead you draw your own box, and the user types or pastes into it: ```python title="main.py" message = "" def on_event(e): global message if e.type == "input_changed" and e.id == "message": message = e.value def render(): return ui.column( ui.input("message", label="Message", placeholder="Type here"), ui.label("You wrote: " + message), ) ``` Tap the box and the keyboard types into it instead of into your app. A **Paste** button sits beside it for text you already have. You get an `input_changed` event with the new contents each time it changes. ## The actual cipher A Caesar cipher shifts every letter along the alphabet: ```python LOWER = "abcdefghijklmnopqrstuvwxyz" UPPER = LOWER.upper() def caesar(text, by): out = "" for c in text: if c in LOWER: out += LOWER[(LOWER.index(c) + by) % 26] elif c in UPPER: out += UPPER[(UPPER.index(c) + by) % 26] else: out += c return out ``` Reading that: the loop walks the text one character at a time. A lowercase letter is looked up in `LOWER`, moved `by` places, and wrapped around with `% 26`, so `z` moved by one is `a`. Uppercase works the same against `UPPER`. Anything else, a space or a digit or an accented letter, passes through unchanged. Python's `%` always gives a result from 0 to 25 here, even when `by` is negative, which is what lets decoding be the same function with the shift turned around. Now wire it up: ```python title="main.py" LOWER = "abcdefghijklmnopqrstuvwxyz" UPPER = LOWER.upper() message = "" shift = "3" output = "" def caesar(text, by): out = "" for c in text: if c in LOWER: out += LOWER[(LOWER.index(c) + by) % 26] elif c in UPPER: out += UPPER[(UPPER.index(c) + by) % 26] else: out += c return out def shift_amount(): try: return int(shift) except ValueError: return 0 def on_event(e): global message, shift, output if e.type == "input_changed": if e.id == "message": message = e.value elif e.id == "shift": shift = e.value elif e.type == "click": if e.id == "encode": output = caesar(message, shift_amount()) elif e.id == "decode": output = caesar(message, -shift_amount()) def render(): return ui.column( ui.input("message", label="Message", placeholder="Type or paste"), ui.input("shift", label="Shift", placeholder="3"), ui.row( ui.button("encode", "Encode", style="primary"), ui.button("decode", "Decode"), ), ui.output(output, id="result", mono=True), ) ``` `int(shift)` raises a `ValueError` when the box holds something that is not a number, so `shift_amount` catches that and uses 0. An uncaught error would stop the event and show it on the panel; the plugin would keep running, but the tap would do nothing. `ui.row` puts things side by side. `ui.output` is the one to know: it is a result block, and the keyboard draws an **Insert** button under it that puts the text into whatever the user is writing in. That is the only way a plugin's output reaches their text. A plugin cannot type on its own. The user taps Insert. Rebuild the ZIP. Reinstall it. Type something. Tap Encode, then Insert. ## Two tabs The finished demo has a second cipher on its own tab. Vigenère is Caesar with the shift taken from a repeating keyword, one letter of the key per letter of the message: ```python def vigenere(text, key, direction): shifts = [LOWER.index(c) for c in key.lower() if c in LOWER] if not shifts: return text out = "" i = 0 for c in text: if c in LOWER or c in UPPER: out += caesar(c, direction * shifts[i % len(shifts)]) i += 1 else: out += c return out ``` `ui.tabs` takes an id and then one `ui.page` per tab. A page takes a title and then its children, the same way `ui.column` does: ```python title="main.py" def result(): if output == "": return ui.label("Type something and pick a button.", style="caption") return ui.output(output, id="result", mono=True) def render(): return ui.tabs("cipher", ui.page("Caesar", ui.input("message", label="Message", placeholder="Type or paste"), ui.input("shift", label="Shift", placeholder="3"), ui.row( ui.button("caesar_encode", "Encode", style="primary"), ui.button("caesar_decode", "Decode"), ), result(), ), ui.page("Vigenere", ui.input("message", label="Message", placeholder="Type or paste"), ui.input("keyword", label="Keyword", placeholder="lemon"), ui.row( ui.button("vigenere_encode", "Encode", style="primary"), ui.button("vigenere_decode", "Decode"), ), result(), ), ) ``` Both pages draw a box with the id `message`, so the text stays when you switch tabs. The buttons now have one id per cipher, and `on_event` grows to match: ```python def on_event(e): global message, shift, keyword, output if e.type == "input_changed": if e.id == "message": message = e.value elif e.id == "shift": shift = e.value elif e.id == "keyword": keyword = e.value elif e.type == "click": if e.id == "caesar_encode": output = caesar(message, shift_amount()) elif e.id == "caesar_decode": output = caesar(message, -shift_amount()) elif e.id == "vigenere_encode": output = vigenere(message, keyword, 1) elif e.id == "vigenere_decode": output = vigenere(message, keyword, -1) elif e.type == "tab_selected": # A result from the other cipher would only confuse. output = "" ``` Add `keyword = ""` next to the other globals at the top. The full source is [`plugins-src/cipher-tool/main.py`](https://github.com/wasi-master/wmkeyboard-addon-repository/tree/main/plugins-src/cipher-tool) in the addon repository. ## Debugging `print()` and `wm.log()` both write to your plugin's log. That is your only window into a running plugin, so use it freely. Tap your plugin in the installed list and read the last 40 lines there: Tools / Plugins / Manage plugins / your plugin If the script fails, the panel shows the error with its line number and the plugin stays loaded. Fix the script. Rebuild the ZIP. Reinstall it. The keyboard's Python is a small one, the same subset [Clink](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions) runs. Functions, lambdas, comprehensions, f-strings, slicing and `try` are all there; `class`, `with`, `yield` and decorators are not. A script that uses one is refused with a message that names it. The [API reference](https://wmkeyboard.pages.dev/plugins/api-reference/#the-python-you-get) has the whole list. Your code gets 2 million steps and two seconds per event, and a tighter 400,000 steps and half a second for each render. A step is roughly a statement or a turn of a loop. An accidental infinite loop is stopped and reported rather than left to hang the keyboard. Running out of steps or time counts a strike, and so does going unresponsive for long enough that the watchdog gives up. Two strikes and the plugin is switched off until you turn it back on. You can stop that from happening: turn **Switch off a plugin that hangs** off under Tools / Plugins / Manage plugins, where it is on by default. The strikes are still counted and still shown, so you keep the evidence and give up only the automatic switch-off. That is the trade you want while you are writing plugins, or when a slow device is the real cause. ## Publishing Anyone can install a `.wmplugin` file directly. To list it in an addon repository, add an entry pointing at the file: ```json title="wmkeyboard-repo.json (excerpt)" { "id": "cipher-tool", "type": "plugin", "name": "Cipher Tool", "version": "1.0.0", "author": "Your Name", "description": "Caesar and Vigenere ciphers.", "path": "plugins/cipher-tool.wmplugin", "sha256": "…", "sizeBytes": 1693, "license": "MIT" } ``` The `sha256` is **required** for plugins, unlike every other addon type. The app will not install code it cannot verify. `tools/build_index.py` in the sample repository fills it in for you, and `tools/validate.py` checks it. ## Where to next API reference — Every widget, event and function. — /plugins/api-reference/ Permissions — How to ask for storage and reminders, and why there is nothing else to ask for. — /plugins/permissions/ Also worth a look: the **UI Kitchen Sink** demo. It puts every widget on screen at once, with a live log of the events they produce. --- # Permissions URL: https://wmkeyboard.pages.dev/plugins/permissions/ Section: Plugins Summary: The three permissions a plugin can declare, and what they grant. There are three. ```json title="plugin.json (excerpt)" "permissions": ["storage", "reminders", "selection"] ``` Most plugins declare `[]` and need nothing. ## `storage` Gives you `wm.storage`: a small string-to-string map that belongs to your plugin alone. ```python wm.storage.set("items", json.dumps(items)) items = json.loads(wm.storage.get("items", "[]")) ``` - Local to the device. It cannot leave, because the interpreter has no network API for it to leave through. - Local to your plugin. No other plugin can read it. - Deleted when the user uninstalls you, and clearable by them any time from Tools / Plugins / Manage plugins / your plugin. An update to your plugin leaves it alone, so a saved list survives a new version. - 128 keys, 64 characters per key, 8 KB per value, 64 KB in total. `set` raises `ValueError` when you are over quota. Catch it and say something. Do not lose the user's data quietly. ## `reminders` Gives you `wm.reminders`: ask the app to show a line of text in a notification at a time you set. ```python due = wm.timestamp(2026, 12, 24, hour=9) wm.reminders.set("item:7", due, "Buy milk") wm.reminders.cancel("item:7") ``` - The app shows it, not your script. It keeps the id, the time and the text, and when the time comes it posts the notification. No plugin code runs in the background, and none runs when it fires. - The title is always your plugin's name. You choose the words under it, never the name, so a reminder cannot pretend to come from the system or another app. - Tapping it opens your plugin's page in Tools / Plugins / Manage plugins / your plugin, which lists every pending reminder with a button to cancel them all. - Nothing shows while plugins, or your plugin, are turned off, or while the user has the app's notifications off. - 64 pending, 200 characters of text, up to ten years ahead. ## `selection` Lets your plugin add buttons to the bar over selected text, listed under `actions` in the manifest. A manifest with actions must declare it, as one with this permission must have actions. ```python def transform(text, action): return text.upper() ``` - Your script sees text only when the user selected it and tapped one of your buttons. It never sees what they type, the rest of the field, or a password field: the bar does not appear on one. - Each tap runs in a fresh interpreter, discarded when `transform` returns. Only what you write to `wm.storage` stays, and storage has no way off the device. - The user sees your action names before they install, and can switch each one off in Advanced / Selection actions. [Selection actions](https://wmkeyboard.pages.dev/plugins/api-reference/#selection-actions) has the manifest fields. Declared permissions are shown to the user *before* they install, on the addon page and in the file-import confirmation. There is no runtime prompt. With no way for data to leave the device, a prompt would have nothing to protect, and a prompt for something harmless mostly teaches people to dismiss prompts without reading them. ## What you cannot ask for This is not a list of things that need a permission. It is a list of things with no API at all: | | | |---|---| | What the user types | No key event API of any kind. | | The text they are writing in | No API returns the field's contents. A [selection action](#selection) is handed the selection the user tapped it on, and nothing else. | | The clipboard | No API. | | The network | No HTTP, no sockets, no URL handling. | | Files | No paths, no filesystem. | | Other apps, or the device | No package list, no identifiers, no sensors. | | Android permissions | A plugin cannot request any; it does not have that reach. | | Background work | No timers. Execution stops when the panel closes. A reminder is text the app shows, not code that runs. | Adding `"permissions": ["network"]` to your manifest enables nothing. It makes the app refuse to install your plugin, because the app will not install something whose capabilities it cannot explain to the user. That refusal is deliberate: it is also how an old app version safely declines a plugin built for a newer one. ## Getting text without reading it The replacement for the text APIs is the user, and it is less limiting than it sounds. **In:** draw a `ui.input`. The user types into it, or taps **Paste** beside it to drop in whatever they already copied. You are told the contents through `input_changed`. They can see exactly what they gave you. **Out:** draw a `ui.output`. The keyboard puts an **Insert** button under it, and tapping that puts your text where they are writing. So the flow for "encrypt the message I just wrote" is: select it, copy it, open your plugin, tap Paste, tap Encode, tap Insert. Six taps instead of two, and in exchange nothing on the keyboard can read anyone's messages. **Both at once:** a [selection action](#selection). The user selects the text and taps your button on the bar, and what you return replaces it. Two taps, and still only the text they chose. ## Why it is this short A keyboard sees everything its user types. Third-party code that could read that text *and* reach the network is a keylogger with extra steps. No consent dialog makes that untrue, because nobody can audit what a script does with a permission after they grant it. Google Play's Device and Network Abuse policy takes the same view of interpreted code an app loads at run time, and names Python among its examples: such code "must not allow potential violations of Google Play policies". That is a question about what is *possible*, not about what is consented to. The only answer that holds is not to build the capability. [Security](https://wmkeyboard.pages.dev/plugins/security/) covers how that is enforced. --- # The plugin sandbox URL: https://wmkeyboard.pages.dev/plugins/security/ Section: Plugins Summary: What the Python plugin sandbox can do, how that is enforced, and where the limits are. What plugins can do, how that is enforced, and where the limits honestly are. Written for three audiences: users deciding whether to trust the feature, developers who want to know what they are working inside, and reviewers checking what third-party code on a keyboard is allowed to reach. ## The invariants Each of these is a property of what was built. None of them is a policy that could be configured differently. Each has a test. 1. **Plugins cannot read what you type.** There is no key-event API. The IME's typing, prediction and autocorrect paths have no plugin hook at all. A plugin cannot observe, alter or delay a single keystroke on its way to your app. 2. **Plugins cannot read the text field.** No `InputConnection` read path is exposed. No function returns the contents of what you are writing in. A plugin with the `selection` permission is handed one thing: the text you selected, when you tap one of its buttons on the selection bar. Never without the tap, never more than the selection, never on a password field. Each tap runs in a fresh interpreter that is discarded afterwards, so the text outlives the call only if the plugin writes it to its own storage, which has nowhere to send it. 3. **Plugins cannot read the clipboard.** No API. 4. **Plugins cannot reach the network.** No HTTP client, no sockets, no URL handling. The interpreter has zero egress. 5. **Plugins cannot execute downloaded code.** There is no `eval`, `exec`, `compile` or `__import__`, and `import` reaches six built-in modules and nothing else, so a plugin cannot turn a string into code that runs. There is no bytecode format to smuggle one in either: a plugin is source, read by the interpreter's own parser. 6. **Plugins cannot reach Android.** No `open`, no `os`, no `subprocess`, no reflection, no class loading, no file paths, no Intents, no `Context`. The interpreter is native code that never calls back into the app: the app hands it JSON and reads JSON back, and that is the whole connection. 7. **Plugins cannot see other apps or the device.** No package enumeration, no identifiers, no location, contacts, camera, microphone, SMS or accessibility. 8. **Plugins cannot run in the background.** Execution exists only while the plugin's panel is open. No timers, no callbacks that outlive it. A plugin with the `reminders` permission can leave a time and a line of text with the app; at that time the app, not the plugin, shows the text in a notification titled with the plugin's name. No plugin code runs for it. 9. **Plugins cannot change the app.** They render inside their own panel below a host-drawn title bar, and cannot alter keyboard behaviour, request permissions, or affect any other feature. Anything a plugin holds, you gave it on purpose: typed or pasted into its panel, or selected and handed to it with a tap. It also has nowhere to send it. The app collects and shares nothing new because of this feature. ## How it is enforced ### The interpreter Plugins run on `wm-python`, a Rust port of [PyMini](https://github.com/anti-ltd/PyMini), the small Python interpreter written for Clink. The app loads it as a native library over JNI; the web editor's preview runs the same code compiled to WebAssembly. The sandbox is mostly a matter of what was never written. PyMini has no file, network or process access because none exists in it, not because a check stands in the way. `import` reaches `math`, `re`, `json`, `time`, `random` and `sys`, and each of those only computes: `time` reads the clock, `random` draws from a seedable generator, `sys` holds a version string. The built-in functions have no `open`, `eval`, `exec`, `compile`, `getattr` or `__import__`, and there are no classes, so there is no object model to climb through to reach something the API left out. The boundary with the app is coarse on purpose. The app passes in a plugin's source, its settings and each event as JSON, and gets JSON back: a widget tree, an error, log lines, writes to its own input boxes. The interpreter never calls into the JVM, so no host object is ever within a script's reach. Storage and reminders are files the interpreter itself reads and writes, at the paths and under the quotas the app gives it when it opens the session. ### Stopping runaway scripts Every statement, every turn of a loop and every step of a regular expression's matching spends from a step budget. Each step also checks a cancel flag and a wall-clock deadline. When the budget or the time runs out, or the flag is set, the interpreter unwinds and returns an error that no `try` in the script can catch. A script cannot swallow its own termination. Regular expressions run on PyMini's own backtracking engine, and that engine spends steps like everything else. A pattern that backtracks for ever is stopped the same way a `while True:` is, which closes the usual gap where an interpreter hands a regex to a library it cannot interrupt. The app also keeps a watchdog. When a call passes its deadline, the watchdog sets the cancel flag from its own thread, and since the flag is checked on every step, the call stops. Abandoning the thread is now only a fallback for a call that does not return even then. Abandoning is not killing: the session is revoked so nothing the thread does afterwards reaches anything, its priority is dropped, and the runtime forgets it. A plugin that runs out of steps or time, or has to be abandoned, takes a strike. Two strikes and it is switched off until the user re-enables it. The one exception is a user who has turned **Switch off a plugin that hangs** off (Tools / Plugins, on by default). With it off, the strikes are still counted and still shown, so the user keeps the evidence and gives up only the automatic switch-off. That is the trade you want if you write plugins, or if a slow device is the real cause. ### Memory There is no per-thread heap limit on Android, so this is layered rather than absolute. The script is capped at 256 KB, and the step budget bounds the allocation *rate*. No string can pass 100,000 characters, no list, dict or set 10,000 items, and no call chain 300 calls, so the doubling trick (`s = s + s`) stops after a few turns with an error instead of reaching gigabytes. Every string crossing the boundary with the app is capped, the widget tree is capped, storage is quota'd, and only one plugin runs at a time. Honest residual: those caps bound each value, not the total. A script that fills many containers with long strings can still use a lot of memory before its steps run out. Worst case, Android ends the keyboard process and it restarts clean. That is a crash rather than a leak: no data crosses any boundary. ### Cross-plugin isolation Every session is its own interpreter, with its own globals and its own modules, created when the panel opens and discarded when it closes. A selection action gets a fresh one per tap. Nothing a script can write to is shared with another plugin or with a later session, so one plugin has no way to change how Python behaves for the next. ### The key-routing gate The one mechanism that could break invariant 1 if it were wrong. A plugin's text box is not a real text field. There are no `TextField`s anywhere in the keyboard UI, since they fight the `InputConnection`. A tap on one instead makes the keyboard route keystrokes into a host-owned buffer, and hand the plugin the resulting *contents*. While that routing is on, what the user types goes to a script rather than to their app, so: - it requires both the Plugins panel to be open *and* a widget to be focused; - it is gated at every keystroke entry point that could reach the field: characters, backspace, the backspace swipe, forward delete, space and enter each return before the `InputConnection` is touched, and a hardware keyboard runs through the same handlers. Gesture typing needs no gate here, because glide is only armed while no panel is open at all; - it is switched off when the panel closes, when the keyboard hides, when the focused field changes, and when the plugin stops drawing that widget; - the runtime is torn down at the same moment, so afterwards there is no plugin left in the process to receive anything. ### Distribution Plugins arrive as `.wmplugin` archives from an addon repository or a local file. Archive entry names are never used as filesystem paths, the plugin id is proven to be a single safe lowercase path segment before it becomes a directory, and archives are capped in size and entry count. The interpreter is a feature library rather than part of the APK. It is fetched when plugins are first turned on, or the editor first opened. A Play install gets it from Google Play. Every other build gets it from the project's data repository and checks it against the checksum the app was built with before loading it. For plugins alone, a **SHA-256 is mandatory**. For every other addon type a missing checksum only means "unverified". The app will not install code it cannot verify. The whole subsystem is **off until the user turns it on**. While it is off, installing from a file or the addon catalogue is refused outright. The in-app plugin editor is the exception. Its live preview runs a script as you write it. Its Install button writes the plugin straight to disk the same way a catalogue install would, both before you ever turn the switch on. Since you already wrote the manifest yourself, Install skips the confirmation screen described below. Both still go through the same interpreter as an installed plugin, so every invariant above still holds. Before installing from a file or the catalogue, the user sees what the plugin says it is and what it would be allowed to do. Only the manifest is read to build that screen, never the script. ## What this does not protect against Stated plainly, because a security document that only lists strengths is not useful. - **A bug in the interpreter itself.** It runs in the app's process, so an interpreter escape would be an app compromise. Mitigations: it is written in Rust, which rules out the memory-corruption bugs that usually turn an interpreter flaw into an escape, outside the thin JNI layer; it holds no host objects at all, only JSON in and out; and it has no reflection, no class loading and no file or network code to misuse. The runtime is kept behind an interface so it could be moved into an `isolatedProcess` later. That is not done today. - **A plugin that wastes CPU.** Contained rather than prevented. Every call is bounded by its step budget and deadline, so the waste ends when the call does, and a plugin that keeps hitting the limit is switched off. - **A plugin that lies about what it does.** Nothing stops a plugin called "Calculator" from being a poem generator. It still cannot read your text or reach the network, so the worst case is a waste of your time. - **Anything you paste into it, or select and hand to it.** If you paste a password into a plugin's box, or select one and tap a plugin's action, that plugin has your password. It cannot send it anywhere, but it is worth saying out loud. ## Reporting Found something wrong with any of this? Please open an issue on the [WM Keyboard repository](https://github.com/wasi-master/WMKeyboard). A way for a plugin to reach text, the clipboard or the network is a security bug rather than a feature request, and it will be treated as one. --- # Privacy at a glance URL: https://wmkeyboard.pages.dev/privacy/overview/ Section: Privacy & security Summary: Offline by default, no telemetry, network per-tool and opt-in. The receipts are in this section. Typing never touches the network on this keyboard, in any language and on any layout. That covers composing, prediction, autocorrect, gesture typing, spell check and learning. A short list of optional tools does reach the network. This page names every one of them, what sets it off, and how to switch it off. privacy/privacy-screen-groups — The Privacy screen: On-device learning, Android backup, and Your data groups. ## The short version Everything the keyboard needs to type, predict, autocorrect and learn from you runs locally. Bundled and downloaded dictionaries, Bangla and other transliteration, gesture typing, emoji search and on-device learning are all local computation, reading and writing files under the app's own storage. None of it needs a network connection. For what that learning stores and where it lands, see [Your data on device](https://wmkeyboard.pages.dev/privacy/data/). This page is about what leaves the device. No analytics, crash-reporting, or telemetry SDK is built into the app: there's no Firebase, Crashlytics, Mixpanel, Sentry, Bugsnag, or Amplitude anywhere in the source tree, and no first-party WM Keyboard server that the app phones home to for any reason. There is one thing that reports without being a tool you pressed: Google's ML Kit, which is what handwriting, OCR, QR scanning, document scanning and sticker background removal are built on in every build but F-Droid's. Google [documents](https://developers.google.com/ml-kit/android-data-disclosure) that its SDK sends device and app information, a per-install identifier, and timing and error data to Google for diagnostics and usage analytics whenever one of those features runs. It never sees what you type, and it can't be switched off from inside the app. The only control you have is whether you use those features. The lite edition ships no ML Kit at all. [Network policy](https://wmkeyboard.pages.dev/privacy/network/#google-components-and-what-they-report) has the full table, alongside the two smaller Google paths (downloadable fonts, and the Play Store libraries). Play's own in-app update check is the other non-tool request, and it exists only in builds installed from Google Play: opening the settings app asks the Play Store whether a newer version exists, and nothing about you goes with the question. The same builds can ask Play to show its [rating card](https://wmkeyboard.pages.dev/reference/settings/about/#being-asked-for-a-rating), now and then. Builds from any other channel (F-Droid, GitHub, sideload) don't contain those libraries at all and never check or ask. Every other network call the app makes is a request you can trace to one specific, named tool, and you don't have to take that on trust: Privacy / Network activity logs each one as it happens, with the feature, the server and the size. See [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/). A fresh install asks for no permissions at all. Each one belongs to a single optional feature: microphone to voice input, camera to the camera tool, and so on. The app asks for it the first time you use that feature, and it explains itself before Android's prompt appears. There's an inventory of the lot at Privacy / Permissions, where you can see what's granted and hand something back. The full list, along with what refusing does, is on [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/). If somebody else picks up your unlocked phone, the [App lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/) at Privacy / App lock is what stands in their way: your fingerprint, or a PIN, password or pattern set in the app, in front of the settings screens and the delete buttons you pick. Be clear about its limits, because they are the point of this page. It hides screens inside the settings app and encrypts nothing, so an export file, the Android backup, USB debugging and a rooted phone all still read the same data. The Privacy screen's own caption names the network side by hand: translate, weather, web search, image search, GIFs, stickers, dictionary lookup and the AI tools, each one only on the tap that uses it. What a one-paragraph caption can't fit is the handful of fetches that happen without you pressing a tool. The list below covers that part. ## The only things that touch the network Every one of these is a distinct, optional tool. Most fetch only while you have that tool open. Three fetch automatically under default settings, and they're called out below. | Feature | Reaches | Key required | |---|---|---| | Weather | Open-Meteo | No | | Translate | Google Cloud Translation (or the free `translate.googleapis.com` endpoint). None with the on-device engines | Optional | | Dictionary | `kaikki.org`, `en.wiktionary.org`, `api.dictionaryapi.dev`, `api.urbandictionary.com` if you turn it on | No | | Synonyms, from the word menu | `api.datamuse.com`, `kaikki.org`, `api.dictionaryapi.dev` | No | | Encyclopedia | Wikipedia | No | | Currency converter | `open.er-api.com`, falling back to `api.frankfurter.dev` (F-Droid edition: currency-api on `cdn.jsdelivr.net`, then Frankfurter) | No | | Cryptocurrency prices | `api.coinbase.com`, falling back to `cdn.jsdelivr.net` (F-Droid edition: `cdn.jsdelivr.net` alone) | No | | GIF & sticker search | KLIPY and/or GIPHY | Yes | | Web & image search | Brave Search, or Tavily with your own key | Yes | | Photo backgrounds | Unsplash and/or Pexels | Yes | | AI writing tools | Anthropic, OpenAI, Gemini, Grok (xAI), DeepSeek, Brave, any other OpenAI-compatible service you name, or a self-hosted Ollama/LM Studio server on your own network | Yes (bring your own key) | Every one of those stays off until you open that specific tool. Photo backgrounds carry one qualification: searching happens only while the picker is open, but a rotating background keeps a small set of photos topped up once you turn it on. See [Online photos and rotation](https://wmkeyboard.pages.dev/themes/online-photos/#when-the-background-actually-changes) for when that fetch runs and the four conditions that stop it. The AI tools go further. Every provider field starts blank, so the feature is inert on a fresh install even with the panel open, and nothing sends until you supply your own key or point it at a server you run. A self-hosted Ollama or LM Studio server is normally reached over plain HTTP rather than HTTPS, because it sits on your own network rather than the public internet. The app allows cleartext traffic app-wide to make that possible, so a backup S3 endpoint you type with an `http://` address is reached in the clear too (the app warns you on that screen when it is). Every service in the table above is still contacted over HTTPS. ### The exceptions: what fires without you opening a tool Three kinds of fetch happen automatically, under their default settings, without you opening anything you'd think of as a "network tool": privacy/smart-chips-currency-toggle — Convert currencies, on by default under Smart chips: the one smart chip that reaches the network. 1. **Smart chips on the suggestion strip.** Type something that reads as a currency amount, say "150 usd", and the strip fetches exchange rates on its own. No tool is open and you tap nothing. It's gated by Typing / Smart chips, where the master switch and its **Convert currencies** toggle are both on by default. Results are cached for hours rather than fetched per keystroke. **Answer weather questions**, in the same group and also on by default, is the other chip that reaches out: type "will it rain" and the strip fetches the forecast, though only once you've saved a location for the weather tool, since without one there's nothing to ask about. The calculator, unit-converter and date chips beside them are fully local, and the lookup and translate-hint chips only offer to open a tool rather than fetching anything themselves. See [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) for the exact cache window and the rest of the chip family. The in-app disclosure for currency says the same: the amount is recognized on your phone, and only the exchange rates are fetched. In the F-Droid edition the currency chip waits instead: it reads **Get the rate** until you tap it, unless you turn on **Fetch rates as you type** under Tools / Currency converter / Options. The weather chip waits the same way there: it reads **Get the forecast** until you tap it, unless you turn on **Fetch the forecast as you type** under Tools / Weather / Options. 2. **Language data downloads.** Turning a language on fetches its emoji-keyword pack and its word-pair (n-gram) data in the background. Both are small downloads from the project's data repository on GitHub. They're gated by Languages & layouts / Language data / Download data automatically, on by default everywhere except the F-Droid build, where it starts off so that edition's "network is something you opt into" claim holds, with a narrower **Download automatically** under Emoji / Emoji panel / Emoji keywords covering the emoji half. Turn the first one off and both stop. The same switch also fetches a missing word list for an enabled language, larger at a few megabytes, and only over an unmetered connection such as Wi-Fi. A word list you deleted is not fetched again. Adding a language asks before it downloads anything either way. 3. **Dictionary auto-lookup.** Open the Dictionary tool and it looks up the word at your cursor straight away, with no separate search tap. It's gated by **Look up the word at the cursor** under Tools / Dictionary, on by default. Unlike the two above, it still waits for you to open the tool. Two more fetch automatically but are off by default, so they don't belong on that list until you turn them on: **Link previews** under Clipboard, and QR-scan link previews. Each fetches a page's preview metadata the moment you copy or scan a matching link. [Power saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) is the one-tap way to shut off the background half of that list. While it's active, clipboard link previews, QR-scan link previews and dictionary auto-lookup all drop back to off, and a separate switch in the same group drops the smart chips, so the currency and weather fetches stop with them. See that page for the rest of what it changes. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the complete tool-by-tool breakdown: exact endpoints, what payload each one sends, and how downloads (wordlists, CJK dictionaries, speech and AI models, addons) are fetched and verified. ## Open source: verify, don't trust WM Keyboard is MIT-licensed and the source is public on GitHub. You don't have to take this page's word for any of it. Anyone can read the code behind every claim above, including the exact list of domains each tool calls. Releases ship as APKs on GitHub (see [Installation](https://wmkeyboard.pages.dev/start/installation/)), signed by the maintainer, each with the SHA-256 GitHub lists beside it to check a download against. The strongest verification is still to build the APK yourself from that same public source and read what goes into it. It is also on [F-Droid](https://f-droid.org/packages/com.wasimaster.wmkeyboard/). F-Droid builds from published source itself and rejects proprietary tracking outright, so that listing is a second, independent check on the no-telemetry claim above. The F-Droid build is also reproducible: F-Droid publishes it only after its own build comes out identical to the signed `-fdroid.apk` on the GitHub release, so the APK carrying the maintainer's signature is proven to come from that source. It is on [Google Play](https://play.google.com/store/apps/details?id=com.wasimaster.wmkeyboard) as well, as the standard build; the [Google Play edition privacy policy](https://wmkeyboard.pages.dev/privacy/policy/) lists the Google libraries it carries. Play re-signs what it serves, so that copy can't be checked against the GitHub checksums. The three other repositories the app talks to for downloads are public too. One holds word lists, CJK dictionaries and emoji-keyword packs, and the other two are the sample addon repositories seeded on first launch: the general one and a key-sounds one. That means you can inspect what a download contains before installing it, or instead of installing it. Full attribution for every bundled component, data pack and third-party service lives in the app itself; see [About & diagnostics](https://wmkeyboard.pages.dev/reference/settings/about/). Whether any particular build of the app ships with a working built-in key for Translate, KLIPY, GIPHY, Brave Search, Unsplash or Pexels is a build-time secret, not something visible in the public source. Those fields are populated from environment variables when the APK is built, including when you build it yourself. Don't assume a tool works out of the box, and don't assume it needs your own key. Either is possible depending on how a given build was produced. ## Options Privacy The Privacy screen has four groups, each documented in full on its own page: - **Permissions and the lock** (an unnamed group at the top, holding two rows). The first opens the inventory of everything the keyboard can reach, and what it currently holds. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/) and, field by field, [Permissions settings](https://wmkeyboard.pages.dev/reference/settings/permissions/). The second opens the **App lock**, which puts your fingerprint, or a PIN, password or pattern set in the app, in front of the settings screens and delete buttons you choose. It is off until you turn it on, and turning it on or off both ask for a check. See [App lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/). - **On-device learning** covers what gets learned, incognito mode, and private-browsing detection. See [Your data on device](https://wmkeyboard.pages.dev/privacy/data/) and [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/). - **Android backup** folds the keyboard into Android's own backup system, off by default. See [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/). - **Your data** holds one row, **Learned words**, whose delete button sits behind a confirmation. See [Your data on device](https://wmkeyboard.pages.dev/privacy/data/#clearing-whats-stored). Every per-tool network toggle mentioned above lives on that tool's own settings screen rather than on Privacy. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full list, or the [tool settings reference](https://wmkeyboard.pages.dev/reference/settings/tools/) for a screen-by-screen field list. ## Details & edge cases - **Voice dictation can leave the device without appearing on the table above.** The default voice engine is Android's own `SpeechRecognizer`. It prefers on-device recognition, but for a language it can't handle locally it falls back to a network recognizer without saying so. That's Android mediating the request rather than a call this app makes, and nothing about it is saved by WM Keyboard. Downloading an offline Whisper model google replaces the fallback with fully local recognition. - **Not every network request in the app goes through the same client.** First-party API calls (everything in the table above) go through one shared HTTP client. Remote thumbnails and preview images, like GIF and sticker results or addon repository preview art, load through a separate image-loading library. Automatic backup uploads use a third, an ordinary HTTP library. The same rule applies to the first two: they load only inside the panel you're already using, gated the same way. - **ML Kit's on-device models** google (OCR, barcode/QR scanning, handwriting recognition, document scanning) run recognition locally once their model is present. Handwriting models are an explicit download you trigger yourself. The OCR, barcode and document-scanner models can be fetched by Google Play services infrastructure the first time you use that feature, which is Google's own delivery path rather than a request this app makes. - **Opening Settings → Addons re-checks every repository you've added**, once per visit. The very first time you open that screen it also silently adds two sample addon repositories, so the screen isn't empty, and the same visit's refresh is what fetches their catalogues. Removing a starter repository is permanent, and a later visit won't add it back. - **Downloads confirm before using mobile data, and a few smaller fetches don't.** The **Downloads** row in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) starts at **Ask each time**, so a language's whole data set (word list, emoji keywords and word-pair pack), an offline Whisper model and an on-device AI model each stop and name their size on a metered connection. Whisper and AI models also have their own size thresholds, around 150 MB and 500 MB, which still prompt even when the row is set to allow. Downloading one word list, one CJK dictionary or one addon from its own row doesn't ask. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/#downloads-dictionaries-models-and-addons) for the full download-by-download table, including which of these get a checksum verified. Where to go next: [Network policy](https://wmkeyboard.pages.dev/privacy/network/) has the complete tool-by-tool table. [Your data on device](https://wmkeyboard.pages.dev/privacy/data/) covers what's stored locally. [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/) explains which feature asks for what, and what happens if you refuse. [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/) covers pausing learning and clipboard capture. [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/) covers what leaves the device only when you choose to export or sync it. --- # Backup & restore URL: https://wmkeyboard.pages.dev/privacy/backup/ Section: Privacy & security Summary: Export everything, back up on a timer to places you choose, keep devices in sync, and control what Android's own backup may take. WM Keyboard backs itself up three ways. One is a config file you export and move yourself. One is an automatic backup, on a timer, to places you choose. The third is Android's own backup system, off until you turn it on. All of them exist so a reinstall or a new phone doesn't mean retyping every setting and rebuilding every theme. Separately, [sync](#sync-between-devices) keeps two or more devices the same while you use them all. privacy/backup-restore-screen — Backup & restore: pick what to include, then export or import a file. ## Exporting a config file Open **Backup & restore**, then **What goes in an export**. Each section is its own switch: | Section | Default | What it covers | |---|---|---| | Settings | On | Every keyboard preference | | Include API keys | Off | Translate, GIF, search and AI keys (only shown while Settings is on) | | Themes | On | Your custom themes, including their background images | | Dictionary | Off | Words and next-word patterns the keyboard learned from you | | Clipboard | Off | Saved clipboard history (text only; images and files stay local) | | Snippets | On | Your saved text snippets | | Sticker packs | Off | Your own sticker packs, stickers and all (but not the photos you cut them out of) | | Icon packs | Off | Your own icon packs, images and all. Backups made before 0.6.6 restored the pack names with no icons in them; restoring one of those now brings the icons back too | | Custom word lists | On | Word lists you imported for any language | | Addon repositories | On | The repository addresses you added, so a restore can reinstall from them | | Emoji history | On | Your recent emoji, your favourites and the skin tone you last picked for each | | Typing statistics | On | Your daily typing counts and your all-time speed | | Vocabulary | On | Your word lists, imported packs and learning progress. Downloaded packs are left out; they can be downloaded again | | Swipe style | Off | Where your finger lands when you swipe, the readings you corrected, and the shape you draw each word in | | Notes | On | Your notes, their tags, and what is in the trash. Locked notes stay out unless API keys are included; see [Notes](https://wmkeyboard.pages.dev/tools/notes/#backups) | | AI memory | Off | What the AI remembers about you, from AI / Memory. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) | | To-do list | On | Your tasks and their steps, done or not. See [To-do list](https://wmkeyboard.pages.dev/tools/todo/#backups) | Sticker packs and icon packs default off because their images make the file large, not because there's anything private about them. Turn them on and a restore on another phone gets working packs rather than empty shells. The personal ones, dictionary, clipboard and swipe style, default off because they're your typing history rather than configuration. AI memory is off for the same reason: its facts come out of your chats. Emoji history and typing statistics are personal too but default on, since both are small and neither can be downloaded again if you lose it. The statistics are counts only, never anything you typed. Tap **Export to a file** to write a `wmkeyboard-backup--