Skip to content
CleverKeys Wiki
implemented v1.2.7 User guide

Input Behavior Settings Technical Specification

Overview

The input behavior system controls text processing, auto-capitalization, punctuation handling, and gesture sensitivity.

Key Components

ComponentFilePurpose
TextProcessorTextProcessor.ktText transformations
CapitalizationHandlerCapitalizationHandler.ktAuto-cap logic
PunctuationProcessorPunctuationProcessor.ktSmart punctuation
GestureConfigConfig.ktGesture thresholds
PointersPointers.ktTouch handling

Auto-Capitalization

Capitalization Logic

// CapitalizationHandler.kt
class CapitalizationHandler(private val config: Config) {

    fun shouldCapitalize(ic: InputConnection): Boolean {
        if (config.auto_capitalize == AutoCapMode.OFF) return false

        val textBefore = ic.getTextBeforeCursor(2, 0) ?: return false

        return when (config.auto_capitalize) {
            AutoCapMode.SENTENCES -> {
                // Capitalize after sentence-ending punctuation
                val trimmed = textBefore.trim()
                trimmed.isEmpty() ||
                trimmed.lastOrNull() in listOf('.', '!', '?') ||
                trimmed.endsWith(". ") ||
                trimmed.endsWith("! ") ||
                trimmed.endsWith("? ")
            }
            AutoCapMode.WORDS -> {
                // Capitalize after any whitespace
                textBefore.lastOrNull()?.isWhitespace() == true
            }
            AutoCapMode.CHARACTERS -> true
            else -> false
        }
    }

    fun applyCapitalization(char: Char): Char {
        return if (shouldCapitalize()) char.uppercaseChar() else char
    }
}

InputType Integration

// CapitalizationHandler.kt
fun getCapModeFromInputType(inputType: Int): AutoCapMode {
    // Respect app's capitalization hints
    return when {
        inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS != 0 ->
            AutoCapMode.CHARACTERS
        inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS != 0 ->
            AutoCapMode.WORDS
        inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES != 0 ->
            AutoCapMode.SENTENCES
        inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS != 0 ->
            AutoCapMode.OFF
        else -> config.auto_capitalize
    }
}

Double-Space Period

// PunctuationProcessor.kt
class PunctuationProcessor {
    private var lastSpaceTime = 0L

    fun onSpace(ic: InputConnection): Boolean {
        if (!config.double_space_period) return false

        val now = System.currentTimeMillis()
        val timeSinceLastSpace = now - lastSpaceTime
        lastSpaceTime = now

        // Check if double-tap
        if (timeSinceLastSpace > DOUBLE_TAP_TIMEOUT) return false

        // Check context - don't add period after punctuation
        val before = ic.getTextBeforeCursor(2, 0) ?: return false
        if (before.isEmpty() || before.last().isPunctuation()) return false

        // Replace " " with ". "
        ic.deleteSurroundingText(1, 0)
        ic.commitText(". ", 1)

        // Next letter should be capitalized
        capitalizationHandler.forceNextCapital()

        return true
    }
}

Smart Punctuation

// PunctuationProcessor.kt
fun processSmartPunctuation(char: Char, ic: InputConnection): String {
    if (!config.smart_punctuation) return char.toString()

    val before = ic.getTextBeforeCursor(1, 0)?.lastOrNull()

    return when {
        // Auto-space after punctuation
        char.isLetter() && before in listOf(',', '.', ';', ':') -> {
            " $char"
        }

        // Remove space before punctuation
        char.isPunctuation() && before == ' ' -> {
            ic.deleteSurroundingText(1, 0)
            char.toString()
        }

        // Smart quotes
        char == '"' && config.smart_quotes -> {
            if (before?.isWhitespace() != false) "\u201c" else "\u201d"
        }

        else -> char.toString()
    }
}

Gesture Thresholds

Swipe Detection

// Pointers.kt
private fun detectSwipe(ptr: Pointer): SwipeResult? {
    val dx = ptr.x - ptr.downX
    val dy = ptr.y - ptr.downY
    val distance = sqrt(dx * dx + dy * dy)
    val velocity = distance / (ptr.eventTime - ptr.downTime)

    // Threshold based on config
    val threshold = when (config.swipe_threshold) {
        SwipeThreshold.LOW -> keyWidth * 0.3f
        SwipeThreshold.NORMAL -> keyWidth * 0.5f
        SwipeThreshold.HIGH -> keyWidth * 0.7f
    }

    // Velocity requirement
    val minVelocity = when (config.swipe_speed) {
        SwipeSpeed.SLOW -> 0.3f
        SwipeSpeed.NORMAL -> 0.5f
        SwipeSpeed.FAST -> 0.8f
    }

    if (distance < threshold || velocity < minVelocity) return null

    return SwipeResult(
        direction = calculateDirection(dx, dy),
        distance = distance,
        velocity = velocity
    )
}

Long Press Detection

// Pointers.kt
private val longPressRunnable = Runnable {
    if (currentPointer?.isDown == true) {
        onLongPress(currentPointer!!)
    }
}

private fun scheduleLongPress() {
    val delay = config.long_press_delay.toLong()
    handler.postDelayed(longPressRunnable, delay)
}

Short Swipe Distance

// Pointers.kt
private fun detectShortSwipe(ptr: Pointer): ShortSwipeResult? {
    val dx = ptr.x - ptr.downX
    val dy = ptr.y - ptr.downY
    val distance = sqrt(dx * dx + dy * dy)

    val threshold = when (config.short_swipe_distance) {
        ShortSwipeDistance.SHORT -> keyWidth * 0.25f
        ShortSwipeDistance.NORMAL -> keyWidth * 0.4f
        ShortSwipeDistance.LONG -> keyWidth * 0.6f
    }

    if (distance < threshold) return null

    return ShortSwipeResult(
        direction = calculateDirection(dx, dy),
        distance = distance
    )
}

Delete Behavior

// TextProcessor.kt
fun deleteBackward(ic: InputConnection) {
    when (config.delete_mode) {
        DeleteMode.CHARACTER -> {
            ic.deleteSurroundingText(1, 0)
        }
        DeleteMode.SELECTION_FIRST -> {
            val selection = ic.getSelectedText(0)
            if (selection?.isNotEmpty() == true) {
                ic.commitText("", 1)
            } else {
                ic.deleteSurroundingText(1, 0)
            }
        }
    }
}

fun deleteWord(ic: InputConnection) {
    when (config.delete_word_mode) {
        DeleteWordMode.WHOLE_WORD -> {
            // Find word start
            val text = ic.getTextBeforeCursor(50, 0) ?: return
            val wordStart = text.lastIndexOfAny(charArrayOf(' ', '\n', '\t'))
            ic.deleteSurroundingText(text.length - wordStart - 1, 0)
        }
        DeleteWordMode.TO_BOUNDARY -> {
            // Delete to next boundary (space, punct)
            val text = ic.getTextBeforeCursor(50, 0) ?: return
            val boundary = text.indexOfLast { !it.isLetterOrDigit() }
            ic.deleteSurroundingText(text.length - boundary - 1, 0)
        }
    }
}

Configuration

SettingKeyDefaultRange
Auto-Capitalizeauto_capitalizeSENTENCESOff/Sentences/Words/Characters
Double-Space Perioddouble_space_periodtruebool
Smart Punctuationsmart_punctuationtruebool
Smart Quotessmart_quotesfalsebool
Long Press Delaylong_press_delay400200-1200ms
Swipe Thresholdswipe_thresholdNORMALLow/Normal/High
Swipe Speedswipe_speedNORMALSlow/Normal/Fast
Short Swipe Distanceshort_swipe_distanceNORMALShort/Normal/Long
Delete Modedelete_modeCHARACTERCharacter/SelectionFirst
Delete Word Modedelete_word_modeTO_BOUNDARYWholeWord/ToBoundary