Skip to content

Releases: AdamLovattDevOps/better-handheld-keyboard

v1.0.5 — Upgrades that actually upgrade

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:26
d33eca2

Two bugs with the same shape: files that were only ever written on a fresh install, so
an upgrade left the system in a state the rest of the code didn't expect.

Fixed

  • Opacity cycling stopped working after a re-install. The KWin script had two writers:
    the installer copied a static main.js with w.opacity hardcoded to 0.72 and no
    var OP line, while the swap daemon generated a different one that had it. The opacity
    key patches var OP, so against the static copy it silently patched nothing and every
    reload snapped the keyboard back to 0.72 — the config recorded each step down, the
    window ignored them. There is now one writer, bin/handheld-kbd-kwin-script, shared by
    the installer, the daemon and the opacity key; the static copy is gone. The key
    regenerates through it, falling back to patching either historical script shape.
  • New keys never reached existing installs. Layouts are only written when absent, so a
    release could ship the code for a key while the user's layout had no button for it —
    which is exactly what happened to v1.0.4's ✥. The installer now merges in any action key
    (locale, hide, size, opacity, move) the release has and the layout lacks,
    backing the file up first, and prunes settings that no longer do anything (dock and
    dock_edges, from the superseded slot design). Idempotent, and it leaves your own
    customisations alone.

v1.0.4 — Unlock, drag, lock

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:16
78fd817

The v1.0.3 move key cycled through preset docking slots. It didn't work well, so this
replaces it with the obvious thing: unlock the keyboard, put it where you want, lock it.

Changed

  • The move key (✥) is now a lock toggle. Press it and the keyboard unlocks: KWin
    stops forcing its position and size, and a bar appears along the top — drag anywhere on
    it to move the window, or use either end (⤡ / ⤢) to resize. Press again and it locks
    exactly where you left it, saving the result as geometry so a respawn or relogin comes
    back to the same place.
    • Wayland clients can't place their own windows, so both gestures hand off to the
      compositor (begin_move_drag / begin_resize_drag) — the same mechanism a titlebar
      uses — and the position is read back from what KWin remembered.
    • Unlocking switches the rule to Remember (4) and locking switches it back to Force (2),
      so nothing nudges the keyboard while it's locked.
    • It always starts locked; an unlocked keyboard that got respawned would drift.
    • Desktop Mode only — in Game Mode there's no window to drag, and the key says so.
  • Removed the docking-slot cycle, dock and dock_edges. handle_height (default 30)
    sets the drag bar's height.

The v1.0.3 resolution clamping stays: the configured geometry is still clamped to the
panel it lands on, so a saved position can't put the keyboard off-screen.

v1.0.3 — A move key

v1.0.3 — A move key Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:00
2200e8a

Added

  • Move key (kind: "move", ✥). Steps the keyboard through docking slots instead of
    leaving it pinned wherever the KWin rule put it:
    • slot 0 is the configured geometry, anchored to the internal panel — unchanged
      default, so nothing moves unless you ask it to
    • then one slot per (display, edge) pair — bottom, top, middle by default via
      dock_edges — internal panel first, centred horizontally on that display
    • the slot list is resolved live from kscreen-doctor, so plugging in a monitor adds
      its slots without a restart, and unplugging wraps the selection back into range
    • it also forces the KWin rule to be rewritten even when the rect looks unchanged,
      which is the way out of a position that has got stuck
    • the chosen slot persists to config.json as dock, and the key's label shows the
      edge — plus the display name once there's more than one to choose from

Fixed

  • Works on any panel resolution. Every rect — including the configured one — is now
    clamped to the display it's going onto: sizes shrink to fit, then the position is pulled
    back inside. The shipped geometry is sized for a 1280×800 Steam Deck, so on a 1280×720
    panel it used to hang 78px off the bottom, and a partly-off-screen Wayland window still
    takes taps while not being fully visible. No-op where it already fits, so the Deck and
    the Legion Go 2 are unchanged.
  • The installer sizes for the panel it's on. On a fresh install, geometry and
    big_geometry are derived from the internal display (full width, 55% / 64% height,
    bottom-docked) instead of assuming 1280×800, and the forced KWin rule is written from
    that geometry rather than a hardcoded 0,378 1280,422. Existing configs are left alone.

Changed

  • Display enumeration moved into one _outputs() helper (name, position, current-mode
    size, internal flag), which _panel_origin() now uses too.

v1.0.2 — Predictive text and a bigger keyboard

Choose a tag to compare

@github-actions github-actions released this 05 Aug 19:40
56bd145

Everything from v1.0.1 stays: the trigger detection fix, the fail-visible KWin rule, the
recovery script. This adds the features that had been living on my own Legion Go 2.

Added

  • Predictive text. A row of tappable suggestions above the keys, backed by
    handheld_kbd_predict.py. Learns the words you commit (predict_learn, stored in
    ~/.local/share/handheld-kbd/learned.json, never leaves the device) and blends that
    with corpus frequencies built once by the new handheld-kbd-build-dict. Tapping a
    suggestion erases the partial word and types the full one as real keystrokes, so it
    works in any application. Falls back to a plain keyboard if the engine or its data is
    missing, and off entirely with "prediction": false.
  • Big mode. A size key (⤢) toggles between geometry and the new big_geometry,
    stretching every key to fill the window and rewriting the KWin rule live — no relogin.
    start_big comes up in it.
  • Opacity cycling. An opacity key (◐) steps through opacity_steps and persists
    the choice, instead of editing JSON to see what's behind the keyboard.
  • Swipe typing. Drag across the letters to type a word (handheld_kbd_swipe.py).
    A drag only counts once it travels swipe_min_travel key-widths and crosses
    swipe_min_keys distinct letters, so ordinary taps are untouched. Runner-up decodings
    appear in the suggestion row.
  • Two more ways to summon it. gesture_summon shows the keyboard on a two-finger
    swipe up from the bottom edge of the touchscreen; show_on_focus shows it whenever a
    text field takes focus, via AT-SPI (handheld-kbd-focus-probe helps identify what the
    bridge reports). Both show-only and both off by default.
  • Resume handling. handheld-kbd-resume-watch.py listens for logind's
    PrepareForSleep and re-initialises the keyboard on wake, fixing a stale trigger or
    uinput handle after sleep. Installed as an autostart entry.
  • Multi-display docking. The window anchors to the internal panel (internal_output,
    auto-detecting eDP*) so an external display doesn't drag the keyboard off-screen.

Changed

  • The KWin script now also keeps the keyboard above fullscreen windows (temporarily
    dropping the fullscreen window to keepBelow, restored afterwards) and re-asserts
    focus on the window you were actually typing into when the keyboard maps.
  • full.json gains the ◐ and ⤢ keys.

Prediction data (unigrams.txt, bigrams.txt, learned.json, raw/) is generated on
device and git-ignored — the repo ships the builder, not the data.

v1.0.1 — Legion Go 1 keyboard recovery

Choose a tag to compare

@github-actions github-actions released this 05 Aug 19:14
5ae8259

If you installed v1.0.0 on a Legion Go 1 — or on a Steam Deck or ROG Ally running Bazzite
or ChimeraOS — and lost your on-screen keyboard entirely, this release fixes it.
Sorry
about that.

Recover an affected install

Double-click Recover My Keyboard.desktop, or run:

handheld-kbd-recover

Re-running ./install.sh repairs the same thing. Either route restores Steam's keyboard and
switches this one to mirror mode. handheld-kbd-recover --stock-only stands everything down
and hands the desktop back to Steam's keyboard.

What was broken

The installer chose its "seamless" trigger by grepping InputPlumber's default profile for
button: Keyboard. That profile is generic — it carries the mapping on every device — so
seamless mode was selected on hardware where no button can ever emit the event. The Legion
Go 1's InputPlumber driver declares Gamepad:Button:Keyboard but nothing physical sends it;
the Steam Deck and ROG Ally drivers don't reference it at all. The remapped button did
nothing, and because the KWin script still forced Steam's on-screen keyboard to
opacity = 0.0, both keyboards were gone.

Stock SteamOS on a Steam Deck was unaffected — it doesn't ship InputPlumber — as was the
Legion Go 2, which has a real keyboard button.

Fixed

  • Seamless mode is selected by DMI product name (Legion Go 2) rather than by the generic
    InputPlumber profile; every other device defaults to mirror mode, which needs no hardware
    button
  • Steam's keyboard is only made transparent once this one is known to appear — mirror mode
    qualifies inherently, seamless mode waits for proof the trigger fired. A dead trigger now
    leaves you with the stock keyboard instead of nothing
  • The daemon falls back to mirror mode for the session if the InputPlumber remap fails
  • handheld-kbd-ip-remap verifies the profile actually loaded, warns when a device reports
    no keyboard button, and fails loudly instead of silently
  • Home and End dropped from the full layout — the arrow cluster and PgUp/PgDn already
    cover that navigation

Added

  • handheld-kbd-recover and a double-clickable Recover My Keyboard.desktop, because typing
    is not an option when you have no keyboard
  • This changelog, and a tag-driven release workflow

Thanks to the Legion Go 1 user who reported it and confirmed the fix.

v1.0.0

Choose a tag to compare

@AdamLovattDevOps AdamLovattDevOps released this 05 Aug 17:52

Real-keystroke on-screen keyboard for SteamOS / KDE Plasma handhelds (Desktop Mode).

Keys are injected through /dev/uinput as a virtual input device, so Ctrl, Alt, Super, F1-F12, Tab, Esc and the arrows reach any focused application — including Ctrl+C in a terminal.

Trigger — the hardware keyboard button. Default mode mirrors Steam's OSK visibility; "mirror": false instead remaps the button via InputPlumber to fire a DBus event, so no keystroke leaks to Steam or KDE.

Configuration~/.config/handheld-kbd/config.json: opacity (default 0.72), geometry, key sizes, theme, layout and locale. Layouts and locales are plain JSON beside it.

Layouts — full and compact, US and UK. The locale key switches KDE's XKB layout and re-skins the labels so printed and typed stay in sync.

Install./install.sh or double-click the desktop launcher, then log out and back in (the input group is read at session start).

Requires KDE Plasma 6 (Wayland), python3, python-gobject (GTK 3), python-evdev.