Skip to content

Gboard+

An independent Android customization module that adds a productivity toolbar and local vaults above the Gboard keyboard.

GitHub Downloads
599
GitHub Stars
0

Overview

An independent Android systems-engineering module built with LSPosed that adds a toolbar row above the Gboard keyboard, sized to the user's layout. It provides a clipboard vault for text and images, text shortcuts, cursor and trackpad tools, a select mode and screenshot capture into the vault. Root is optional: the core toolbar, vault and screenshots work without it, and root only unlocks a one-tap reload. The module owns an isolated scope, preferences and storage.

Tech stack

Features

Clipboard vault

UX

Text history with pin, favourite and search.

The vault keeps clipboard history with pinning, favourites, search, capacity limits and full-text or one-line rows, all stored locally.

Joystick trackpad

UX

Precise text movement with the joystick, without leaving the keyboard.

Cursor navigation, a joystick trackpad and a select-mode toggle cover precise edits, with haptic strength and sensitivity controls.

Clipboard and image support

UX

Captured images are deduplicated and budgeted.

Clipboard history with text and image entries.

Copied images and screenshots can be saved into the vault, deduplicated by hash and kept within entry and byte budgets, with optional clipboard handoff.

Deep-dive

The module hooks only the Gboard process and inserts its toolbar into the keyboard's own view hierarchy. Clipboard and image vaults persist locally behind hash-keyed entries and explicit entry/byte budgets, so deduplication and eviction are deterministic. A liveness card reports whether the hook is actually loaded and can restart Gboard when root is available.

Kotlin
fun putImage(bytes: ByteArray): Boolean {
    val hash = MessageDigest.getInstance("SHA-256")
        .digest(bytes)
        .joinToString("") { "%02x".format(it) }

    if (dao.exists(hash)) return false
    if (dao.count() >= MAX_ENTRIES) return false
    if (dao.bytes() + bytes.size > MAX_BYTES) return false

    dao.insert(ClipEntity(hash = hash, bytes = bytes))
    return true
}