Skip to content
CleverKeys Wiki
implemented v1.2.7 User guide

Adding Layouts Technical Specification

Overview

The layout management system handles installing, configuring, and organizing keyboard layouts from built-in sources, language packs, and custom definitions.

Key Components

ComponentFilePurpose
LayoutManagerLayoutManager.ktLayout lifecycle management
LayoutRegistryLayoutRegistry.ktAvailable layouts catalog
LayoutStoreLayoutStore.ktPersistence layer
LayoutParserLayoutParser.ktXML/JSON parsing
ConfigConfig.ktLayout preferences

Data Model

Layout Definition

// LayoutManager.kt
data class Layout(
    val id: String,                    // Unique identifier
    val name: String,                  // Display name
    val localeTag: String,             // Language code (en-US)
    val source: LayoutSource,          // Built-in, pack, custom
    val rows: List<LayoutRow>,         // Key rows
    val metadata: LayoutMetadata       // Additional info
)

data class LayoutRow(
    val keys: List<LayoutKey>,
    val height: Float = 1.0f           // Row height multiplier
)

data class LayoutKey(
    val primary: KeyValue,             // Main character/action
    val shifted: KeyValue?,            // Shift variant
    val subkeys: Map<Direction, KeyValue>, // 8-direction subkeys
    val width: Float = 1.0f            // Key width multiplier
)

enum class LayoutSource {
    BUILT_IN,      // Shipped with app
    LANGUAGE_PACK, // Downloaded pack
    CUSTOM         // User created
}

Layout Metadata

// LayoutRegistry.kt
data class LayoutMetadata(
    val version: Int,
    val author: String?,
    val description: String?,
    val iconRes: Int?,
    val isRTL: Boolean = false,
    val supportedLanguages: List<String>
)

Layout Storage

Built-in Layouts

// LayoutRegistry.kt
object BuiltInLayouts {
    val QWERTY = Layout(
        id = "qwerty_en",
        name = "QWERTY",
        localeTag = "en-US",
        source = LayoutSource.BUILT_IN,
        rows = listOf(
            LayoutRow(listOf(
                LayoutKey(KeyValue.Char('q'), KeyValue.Char('Q')),
                LayoutKey(KeyValue.Char('w'), KeyValue.Char('W')),
                // ...
            )),
            // ... more rows
        )
    )

    val all = listOf(QWERTY, AZERTY, QWERTZ, DVORAK, COLEMAK)
}

Installed Layouts Config

// Config.kt
// Stored as JSON array of layout IDs in order
{
    "installed_layouts": [
        "qwerty_en",
        "azerty_fr",
        "custom_123"
    ],
    "active_layouts": [  // Included in quick switch
        "qwerty_en",
        "azerty_fr"
    ]
}

Layout Installation Flow

User selects layout to add

LayoutRegistry.getLayout(id)

Validate layout definition

LayoutStore.addLayout(layout)

Update Config.installed_layouts

Notify listeners (keyboard reloads)

Layout available in picker

Layout Parsing

XML Format (Legacy)

<!-- res/raw/layout_qwerty.xml -->
<layout id="qwerty_en" name="QWERTY" locale="en-US">
    <row>
        <key char="q" shifted="Q">
            <subkey dir="NW" char="1"/>
            <subkey dir="N" char="!"/>
        </key>
        <!-- more keys -->
    </row>
    <!-- more rows -->
</layout>

JSON Format (Preferred)

{
    "id": "qwerty_en",
    "name": "QWERTY",
    "locale": "en-US",
    "rows": [
        {
            "keys": [
                {
                    "primary": "q",
                    "shifted": "Q",
                    "subkeys": {"NW": "1", "N": "!"}
                }
            ]
        }
    ]
}

Layout Validation

// LayoutManager.kt
fun validateLayout(layout: Layout): ValidationResult {
    val errors = mutableListOf<String>()

    // Check required fields
    if (layout.id.isBlank()) errors.add("Missing layout ID")
    if (layout.rows.isEmpty()) errors.add("Layout must have at least one row")

    // Check row consistency
    layout.rows.forEach { row ->
        if (row.keys.isEmpty()) errors.add("Row cannot be empty")
    }

    // Check for space key
    val hasSpace = layout.rows.any { row ->
        row.keys.any { it.primary == KeyValue.Event(Event.SPACE) }
    }
    if (!hasSpace) errors.add("Layout must have spacebar")

    return ValidationResult(errors.isEmpty(), errors)
}

Configuration

SettingKeyDefaultDescription
Installed Layoutsinstalled_layouts[“qwerty_en”]List of layout IDs
Active Layoutsactive_layouts[“qwerty_en”]Quick switch layouts
Max Layouts-10Hard limit
Current Layoutcurrent_layoutFirst installedActive layout