A full PC-style virtual keyboard for Android, built for foldable devices
(Samsung Galaxy Fold, Pixel Fold, …) but equally usable on regular phones.
Same key set as a desktop keyboard — function row, modifier cluster, arrow
keys, AltGr — with proper IME integration so apps see real Ctrl+C,
Alt+Tab, Shift+Home, etc.
Esc F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 Home End
` 1 2 3 4 5 6 7 8 9 0 - = ⌫
Tab q w e r t y u i o p [ ] \
Caps a s d f g h j k l ; ' ⏎
⇧ z x c v b n m , . / ▲ ⇧
Ctrl 🌐 Alt space 123 ◀ ▼ ▶
- Real PC layout — F-row, Esc, Tab, Caps Lock, Ctrl + Alt + Win as actual
modifiers (not toggles to a symbols page), arrow cluster with both ▲ above ▼
and ◀ / ▶ flanking. Modifier presses become
KeyEventmeta flags so shortcuts work everywhere. - Foldable-first — at ≥ 600 dp the full PC layout shows; on narrow phones the function row is dropped automatically, but can be force-enabled from the globe menu so power users keep their F-keys.
- Two-phase touchpad in Space — slide into the centre indicator to arm, release to enter free touchpad mode with relative cursor deltas, repeat drags any number of times, ✕ to exit.
- Full emoji picker (~1500 emojis across 9 categories + dynamic Recents) with inline search using the real keyboard for typing the query.
- Clipboard manager with cards for the last 20 entries — tap commits, long-press edits, ✕ deletes one, "Clear all" wipes the rest with a two-tap confirm.
- Per-language AltGr layer — held Alt commits the locale's xkb glyph
(e.g. HU
e→€,w→|; DEq→@,<→|; ES2→@). - In-app self-update to the app's own cache directory with a live progress dialog and a FileProvider hand-off to the system installer.
- Custom themes with an ARGB colour picker (sliders + hex display + Default reset) and a live preview against the actual keyboard view.
- Offline autocorrect & suggestions — per-language frequency
dictionaries (HU 400k / DE 250k / EN 250k / ES 250k word forms,
inflections included, ~5.4 MB total) power a passive suggestion strip
with accent restoration (
kerdojel→kérdőjel), typo correction and word completion; an opt-in Auto mode fixes obvious typos at word boundaries (Space, punctuation, Enter, tap-away) — retyping a corrected word keeps it and teaches it permanently. A built-in Hunspell checker (the LibreOffice engine, pure-Java Lucene port) validates words morphologically from stems + affix rules, so rare inflections and Hungarian compounds the corpus never saw are recognised as correct and never "fixed". A per-language learning dictionary picks up the user's own words: anything typed twice is suggested from then on and shielded from auto-correction. Off / Suggest / Auto in Settings.
English (US — ANSI QWERTY), Hungarian (ISO QWERTZ), German (ISO QWERTZ), Spanish
(ISO QWERTY). Each comes with its xkb AltGr table baked in as Key.altLabel
and the same set is also at the head of the long-press popup, so every
diacritic / AltGr glyph is reachable two ways — held-Alt for one-shot, long-
press for browsing.
The active layout is cycled from the globe long-press menu, restricted to the
languages the user actually enabled in Settings → Languages. New locales are
one *.Layout.kt file plus a LayoutPack entry in LayoutRegistry and a
<subtype> in res/xml/method.xml.
- Function row (always shown on ≥ 600 dp, optional on narrower screens):
Esc F1–F11 Home End. Toggle from the globe menu. - Number row: locale-specific (HU has
0 1-9 ö ü ó, DE has^ 1-0 ß ', ES hasº 1-0 ' ¡, EN has`1-0 - =). - Top letter row: locale-specific, ending with the bracket/backslash set most users need.
- Home row: starts with Caps Lock (where Ctrl traditionally lives on consumer PCs), ends with Enter.
- Bottom letter row: ⇧ + letters + ▲ + ⇧, so the up-arrow sits directly above ▼ in the row below for a real inverted-T arrow cluster.
- Control row:
Ctrl 🌐 Alt Space 123 ◀ ▼ ▶. The slot to the right of Space is configurable in Settings — pick123(default) or😀for one-tap access to the emoji picker.
Every row totals weight 14, so a "weight 1" letter is exactly the same width in any row.
123 flips into the first symbols page (1-0, punctuation, =\< toggles
into page 2 for ~ • ° § © ® ™ € £ ¥ ¢ ÷ × ± ¬ < > { } [ ] \ …). ABC flips
back.
Shift, Ctrl, Alt, Meta (Win), Fn and Caps Lock follow the classic
OFF → ONCE → LOCKED tap cycle:
- Off — default appearance.
- Once — thick accent border + a small accent dot in the corner. Applies to the next key press, then auto-releases.
- Locked — full accent background + an underline under the label. Stays on until tapped a third time.
State converts to KeyEvent meta flags via ModifierState.toMetaState(), so
apps see proper Ctrl+C, Shift+Tab, Alt+F4, etc. The state resets on
input dismiss, so a Shift you forgot to clear in one app never carries over
into the next.
Long-pressing any key with alternates opens a popup directly above it. To
make sure the popup fits even when the pressed key is in the very top row,
KeyboardView is intentionally rendered taller than the visible keyboard
(90 dp of transparent space at the top), while the IME service reports a
smaller contentTopInsets so the app keeps fitting above the keys. That
reserved zone hosts the popup, and the app shows through it when nothing is
being shown.
- The base character sits in the centre of the popup, pre-selected — a straight release commits it; sliding left/right walks symmetrically through the alternates.
- The selected cell is highlighted with an accent-coloured rounded pill that's slightly larger than the surrounding cells, with rounded corners on all four sides.
- For symbol keys the shift-label is folded into the alternates — long-
pressing
,exposes<,«,‹,„. - Cell width and text size scale with screen width so even keys with 8+ accented variants don't spill off-screen.
Long-press Space → trackpad overlay appears.
Phase 1 — Arming. A circular indicator sits in the centre of the keyboard area; the user must slide their finger from Space into the indicator to arm it. Releasing without entering the indicator restores the keyboard and emits no Space character — an accidental long-press costs nothing.
Phase 2 — Free touchpad. On release after arming, the trackpad doesn't close — it switches into a real touchpad surface:
- Each finger-down sets a new origin; subsequent moves emit relative pixel deltas. Slow drags move the cursor character-by-character (precision), fast drags fly across lines (range).
- A small accent dot follows the finger so the user can see where the trackpad is reading them from.
- An ✕ button at the top-right closes the trackpad. It's hidden while the finger is on the surface, so a stray edge-touch during a drag never accidentally dismisses the session.
- The user can do as many discrete drags as they want — no need to hold one long gesture.
Cursor speed is tunable from Settings → Trackpad sensitivity (0.3× – 3.0×).
Arrow keys, Home / End, Page Up / Page Down and Enter go through
InputConnection.setSelection / performEditorAction rather than
KeyEvent.KEYCODE_DPAD_*. That keeps the gesture inside the focused editor —
on foldables and in free-form / multi-window mode the system window manager
would otherwise steal a DPAD event to select the floating-window grab handle,
jump focus to a sibling Send button, etc.
- ← / → — move caret one character; with Ctrl jump by word boundary; with Shift extend selection instead of moving.
- ↑ / ↓ — move caret one line, preserving column.
- Home / End — start / end of current line (extends selection under Shift).
- Page Up / Page Down — ±10 lines.
- Enter — runs the editor's declared
IME_ACTION_*(Send / Done / Search / Next / …) when one is set; otherwise commits\n. Holding any modifier (Shift+Enter,Ctrl+Enter, …) always commits\n— the universal "newline in a chat field" shortcut.
Opens from the globe menu, or one-tap from the control row if the user picked "😀 Emoji" for the right-of-Space slot.
- 9 categories: 🕒 Recents (dynamic), 😀 Smileys, 👋 People, 🐶 Animals, 🍎 Food, ⚽ Activities, 🚗 Travel, 💡 Objects, ❤️ Symbols, 🏁 Flags (every UN country flag plus the rainbow / transgender / pirate / England / Scotland / Wales variants). ~1500 emojis total.
- Recents tab is fed by an
EmojiUsageTrackerthat ranks by use-count then most-recent timestamp, with a starter pack so the tab isn't empty on first open. Search-picked emojis count toward Recents too. - Inline search: the magnifier pinned at the right of the tab strip flips the picker into search mode. A query bar sits at the top, search results fill the middle, and the actual keyboard stays mounted underneath for typing the query — no separate Activity, no second window, no IME juggle.
- Search is prefix-token against
EmojiKeywords(1500+ entries). Country names work for flags (germany→ 🇩🇪,magyar→ 🇭🇺,japan→ 🇯🇵,usa→ 🇺🇸 …); professions for ZWJ profession emojis (doctor,chef,astronaut); gestures, weather, currency etc. all by everyday name. - Bottom row:
ABCreturns to the keyboard,⌫deletes a character. Both are rendered as rounded accent-coloured pills with a visible gap between them so each touch target reads as its own button. - Category tabs are separated by faint divider lines and a divider runs between the last tab and the pinned 🔍.
Opens from the globe menu. A vertical list of up to 20 cards, newest first, each showing up to 4 lines of the captured text.
- The IME service registers a
ClipboardManager.OnPrimaryClipChangedListenerso any copy from any app lands here automatically. - Tap a card to commit the text into the focused editor.
- Long-press opens a full-screen
ClipboardEditorActivitywhere the text can be tweaked — Send commits the edited version (and replaces the history entry so the next paste has the fix); X cancels. - Each card has a ✕ button on the right that removes that one entry.
- A
🗑 Clear allbutton at the top-right wipes the whole history with a two-tap confirmation — the first tap arms it ("Tap to confirm" in accent colour), the second confirms; it auto-disarms after 3 seconds.
Long-press the 🌐 key to open a vertical action menu anchored above it.
- Lists every language the user enabled in Settings → Languages, with the current one highlighted.
☐ / ☑ Function row (Esc, F1…)toggle to force the F-row on on narrow phones.😀 Emoji,📋 Clipboard,⚙ Keyboard settingsfor the rest of the overlays.- Slide-to-select: keep the finger pressed, slide up onto the menu and a small haptic ticks every time a row is crossed; releasing on a row fires it. Releasing back on the globe leaves the menu up for tap-to-select.
- Auto-scroll while sliding: if the list is taller than fits, the menu starts scrolled to the bottom (the row the finger lands on first), and the finger drifting near the top edge scrolls more rows into view.
- Sizing:
- Height scale (50 % – 160 %).
- Width margin (0 % – 30 % per side) — narrows the keyboard symmetrically from both sides, useful on tablets and unfolded foldables.
- Split keyboard mode (≥ 600 dp screens only) with a centre-gap slider (0.5 – 6.0 row weight). Space stretches across the gap so either thumb can hit it.
- Long-press delay (150 – 1000 ms in 10 ms steps) — applies to every key, both popup-character and trackpad gestures.
- Trackpad sensitivity (0.3× – 3.0×) — captured at arming time so cursor speed stays consistent for the whole touchpad session.
- Right-of-Space slot —
123 Symbols(default),😀 Emojior⌥ Alt(a second sticky AltGr for the right thumb). Also reassignable straight from the keyboard by long-pressing the slot itself. - Autocorrect —
Off/Suggest(default; passive candidate strip above the keys, never edits your text) /Auto(additionally replaces a confidently-wrong word at word boundaries: Space, punctuation, Enter, or tapping elsewhere in the text; retyping the corrected word keeps your spelling, vetoes it and teaches it permanently — Backspace itself always just deletes). Suggestions are disabled automatically in password / URL / email fields and terminals. - Themes — built-in (Light / Dark / Black) plus any custom themes the user has saved; + New theme opens the editor.
- Languages — switches per language; the globe cycles only the enabled set.
- Updates — Auto-update toggle (on / off), interval toggle (12 h / 24 h), manual Check now button, Open install permissions shortcut with an inline explainer about Samsung Auto Blocker (which silently blocks installs that don't come from Play Store / Galaxy Store — temporarily disable it to update).
Built-in Light, Dark, Black (AMOLED) and an unlimited number of custom themes.
The theme editor has:
- Live preview against the real keyboard view.
- One row per colour (background, key, pressed, text, secondary text,
modifier, modifier text, accent, accent text) with a hex input and a
tinted swatch chip at the end of the field — tapping the swatch opens an
ARGB colour picker dialog (4 SeekBars + a live preview rectangle + a
read-only
#AARRGGBBlabel). - The picker has OK / Default / Cancel: Default puts every slider back to the colour the picker opened with, without closing the dialog.
- Sliders for corner radius and key spacing.
Custom themes are persisted to SharedPreferences via ThemeRepository and
listed alongside the built-ins.
Polls https://api.github.com/repos/9hm2/pcKeyboard/releases/latest and
compares the release tag against BuildConfig.VERSION_NAME. A manual Check
now in Settings hits the network unconditionally; automatic checks (via
WorkManager) are throttled to the user-chosen 12 h / 24 h interval and
only run while pcKeyboard is the default IME.
When a newer release is found:
- Foreground (Setup / Settings dialog): a Material dialog shows the
version + release notes and offers Download / View on GitHub / Later.
Tapping Download opens a non-cancellable progress dialog that shows
47 % · 2.3 MB / 4.9 MBwhileUpdateDownloaderstreams the APK intocontext.cacheDir(no storage permission needed). On completion it fires the system installer through a FileProvider URI, so the user goes straight from the in-app download to the install prompt. - Background (
UpdateCheckWorker): same downloader, no progress UI; on completion it fires the same FileProvider install intent.
Samsung's Auto Blocker overrides the system "install unknown apps" permission and silently blocks installs from non-store sources. The Updates card in Settings has an Open install permissions button (with a fallback to Application details) plus an inline note explaining that the user has to flip Auto Blocker off briefly to update (it auto-re-enables 30 minutes later if you keep that option on).
- Install the APK.
- Launch the app → Open system input settings → enable pcKeyboard.
- Tap Switch keyboard → pick pcKeyboard.
gradle :app:assembleDebug # debug APK, signed with bundled debug.keystore
gradle :app:assembleRelease # release APK; signed only if env vars are setCI is manual-only (workflow_dispatch) — see
.github/workflows/android.yml. The workflow installs the Android SDK, runs
lint and builds the debug APK. Release APKs are built on the developer's
device only, never on CI, so the release signing key never has to leave the
local machine.
Every workflow run always publishes a GitHub Release with the freshly- built debug APK attached, so the in-app updater always has something newer to find. Inputs:
release_tag— optional. Custom tag likev1.2.0. If empty, auto-tags withci-YYYYMMDD-HHMMSS(UTC).prerelease— optional boolean. Marks the release as a pre-release, which excludes it from/releases/latest(and therefore from the in-app updater) so you can publish test builds without notifying users.
The debug keystore (app/debug.keystore, the well-known Android default) is
committed so debug APKs are signed identically on every machine. The release
keystore is not in the repo — see app/build.gradle.kts for the env
vars assembleRelease reads.
model/ Key, KeyType, KeyboardLayout, ModifierState (sticky tap-once /
tap-lock cycle, KeyEvent meta-flag conversion).
layout/ LayoutBlocks — shared Fn / number / control / symbols
rows reused by every locale.
EnglishLayout / HungarianLayout / GermanLayout /
SpanishLayout — locale-specific letter rows with
Key.altLabel for the held-Alt AltGr
glyphs from each xkb table.
LayoutRegistry — locale id → LayoutPack lookup.
LayoutVariant /
LayoutSelector — COMPACT (no Fn row) ↔ FULL PC variant
chosen from widthDp (overridable via
KeyboardPrefs.showFunctionRow).
theme/ KeyboardTheme data class, built-in Themes (Light / Dark /
Black), ThemeRepository (custom theme persistence).
view/ KeyboardView — FrameLayout that hosts mainContainer
(popupZone + optional emojiSearchHeader
+ rowsContainer) plus the optional
overlays (popup, action menu, emoji
picker, clipboard view, trackpad).
Reports a smaller contentTopInsets in
PcKeyboardService.onComputeInsets so the
popup zone stays transparent over the
app behind.
KeyView — single key renderer (off / armed / locked
states, repeatable hold, long-press
timer).
KeyPopupView — horizontal alternate-character strip
with dynamic cell sizing and a fully-
rounded accent-coloured selection pill.
ActionMenuView — vertical globe-long-press menu with
slide-to-select + auto-scroll.
TrackpadView — two-phase touchpad overlay: arming
indicator → free touchpad with relative
pixel deltas, finger-follow dot, and a
✕ close button that hides while
touching.
EmojiView /
EmojiCatalog /
EmojiKeywords /
EmojiUsageTracker /
EmojiSearchHeaderView — picker, search bar overlay above
the keyboard rows, 9 categories +
Recents.
clipboard/ ClipboardView /
ClipboardHistory /
ClipboardEditorActivity /
ClipboardEditorBridge — list of cards, per-entry delete,
clear-all-with-confirm, full-screen
edit window with a static-singleton
bridge back into the IME service.
editor/ ThemeEditorActivity — live-preview keyboard + hex + swatch
chip per colour.
ColorPickerDialog — ARGB SeekBars + preview + Default /
OK / Cancel.
updater/ UpdateChecker — GitHub releases API + version compare.
UpdateDownloader — HttpURLConnection stream → cacheDir
+ FileProvider install intent.
UpdateUi — Setup / Settings glue: Material dialog
+ progress dialog.
UpdateScheduler /
UpdateCheckWorker — WorkManager periodic background check.
settings/ SetupActivity — onboarding (enable + switch IME).
SettingsActivity — sizing / theme picker / languages /
updates / right-of-Space.
KeyboardPrefs — every persisted preference.
InsetUtils — system-bar inset helpers.
service/ PcKeyboardService — InputMethodService entry-point. Converts
Key + ModifierState to InputConnection
calls: commitText for characters,
setSelection for cursor / arrow keys
(with Ctrl word-jump and Shift selection
extension), performEditorAction for
Enter, sendKeyEvent only for keys whose
modifier semantics actually need it
(Backspace, Delete, Tab, Esc, F-row).
Reports contentTopInsets for the popup
zone in onComputeInsets, resets modifier
+ emoji + clipboard + symbol-page state
on onFinishInputView so the next session
always starts on the main letters.
GPL v3 — see LICENSE.
The word-frequency dictionaries in app/src/main/assets/dict/ are
derived from the OpenSubtitles-2018 frequency lists published in
hermitdave/FrequencyWords
(CC-BY-SA 4.0); see scripts/generate_dictionaries.py for how they're
produced.
The bigram (word-pair) models in app/src/main/assets/dict/*.bigrams
are computed by scripts/generate_bigrams.py from the news-2020 1M
sentence corpora of the Leipzig Corpora Collection
(CC BY).
The Hunspell dictionaries in app/src/main/assets/hunspell/ come from
the LibreOffice dictionaries
collection: hu_HU (magyarispell, GPL/LGPL/MPL tri-license), en_US
(SCOWL), de_DE (frami, GPL 3), es_ES (GPL 3 / LGPL / MPL). Spell
checking runs on Apache Lucene's pure-Java Hunspell implementation
(Apache License 2.0).