Skip to content

FieldVrKeyboard.kt

src/jvmMain/kotlin/afield/media/browser/FieldVrKeyboard.kt · 79 lines

package afield.media.browser

import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.util.concurrent.CompletableFuture

class FieldVrKeyboard

A focus-preserving keyboard inside the offscreen IDE; keys go through Chromium's input path.

class FieldVrKeyboard(
    private val browser: PuppetBrowserControl,
    private val recenter: () -> Unit,
    private val focusKeyboard: () -> Unit = {},
) : AutoCloseable {
    private val removeRequest = browser.onPageRequest("field-vr-input") { value ->
        val message = value.jsonObject
        when (message["action"]?.jsonPrimitive?.content) {
            "text" -> browser.insertText(message["text"]!!.jsonPrimitive.content.take(4096))
            "key" -> press(message["key"]!!.jsonPrimitive.content,
                message["shift"]?.jsonPrimitive?.content == "true",
                message["control"]?.jsonPrimitive?.content == "true",
                message["alt"]?.jsonPrimitive?.content == "true")
            "recenter" -> recenter()
            "keyboard-focus" -> focusKeyboard()
            else -> throw IllegalArgumentException("Unknown VR keyboard action")
        }
        browser.flushInput().thenApply<JsonElement> { JsonNull }
    }

fun install(): CompletableFuture<BrowserJavascriptResult> = browser.evaluate(script)

Idempotent; also called after navigation to a different document.

fun reportInputStatus(status: String): CompletableFuture<BrowserJavascriptResult>

    fun reportInputStatus(status: String): CompletableFuture<BrowserJavascriptResult> = browser.evaluate(
        "(() => { const node = document.getElementById('field-vr-tools')?.shadowRoot?.getElementById('input-status'); " +
            "if (node) node.textContent = ${JsonPrimitive(status)}; return !!node; })()")

fun press(key: String, shift: Boolean = false, control: Boolean = false, alt: Boolean = false)

    fun press(key: String, shift: Boolean = false, control: Boolean = false, alt: Boolean = false) {
        val descriptor = keys[key] ?: throw IllegalArgumentException("Unsupported VR key: $key")
        val modifiers = buildSet {
            if (shift) add(BrowserModifier.SHIFT)
            if (control) add(BrowserModifier.CONTROL)
            if (alt) add(BrowserModifier.ALT)
        }
        browser.key(BrowserKeyEvent(BrowserKeyEventType.RAW_KEY_DOWN, descriptor.first, descriptor.second,
            modifiers = modifiers, windowsVirtualKeyCode = descriptor.third))
        browser.key(BrowserKeyEvent(BrowserKeyEventType.KEY_UP, descriptor.first, descriptor.second,
            modifiers = modifiers, windowsVirtualKeyCode = descriptor.third))
    }

override fun close() { removeRequest() }

companion object

internal val script: String by lazy

        internal val script: String by lazy {
            checkNotNull(FieldVrKeyboard::class.java.getResourceAsStream("field-vr-keyboard.js"))
                .bufferedReader().use { it.readText() }
        }
        private val keys = mapOf(
            "Backspace" to Triple("Backspace", "Backspace", 8),
            "Delete" to Triple("Delete", "Delete", 46),
            "Enter" to Triple("Enter", "Enter", 13),
            "Tab" to Triple("Tab", "Tab", 9),
            "Escape" to Triple("Escape", "Escape", 27),
            "ArrowLeft" to Triple("ArrowLeft", "ArrowLeft", 37),
            "ArrowUp" to Triple("ArrowUp", "ArrowUp", 38),
            "ArrowRight" to Triple("ArrowRight", "ArrowRight", 39),
            "ArrowDown" to Triple("ArrowDown", "ArrowDown", 40),
            "Home" to Triple("Home", "Home", 36),
            "End" to Triple("End", "End", 35),
            "F1" to Triple("F1", "F1", 112),
            "a" to Triple("a", "KeyA", 65),
            "c" to Triple("c", "KeyC", 67),
            "v" to Triple("v", "KeyV", 86),
            "x" to Triple("x", "KeyX", 88),
            "z" to Triple("z", "KeyZ", 90),
        )
    }
}