How to trigger PC Keyboard inputs in Kotlin Desktop Compose
Asked Answered
C

3

4

I am going to develop a POS system using Kotlin Jetpack Compose and I wanna know how to trigger keyboard input events inside my project.

Contraceptive answered 3/9, 2021 at 6:18 Comment(0)
N
7

In Compose Desktop You can listen for key events using onKeyEvent Window parameter:

Window(
    onCloseRequest = ::exitApplication,
    visible = visible,
    onKeyEvent = {
        if (it.isCtrlPressed && it.key == Key.A) {
            println("Ctrl + A is pressed")
            true
        } else {
            // let other handlers receive this event
            false
        }
    }
) {
    App()
}

An other options, which will also work for Compose in Android, is using Modifier.onKeyEvent. As documentation says:

will allow it to intercept hardware key events when it (or one of its children) is focused.

So you need to make an item or one of its children focusable and focused. Check out more about focus in compose in this article

To do this you need a FocusRequester, in my example I'm asking focus when view renders using LaunchedEffect.

For the future note, that if user taps on a text field, or an other focusable element will gain focus, your view will loose it. If this focused view is inside your view with onKeyEvent handler, it still gonna work.

An empty box cannot become focused, so you need to add some size with a modifier. It still will be invisible:

val requester = remember { FocusRequester() }
Box(
    Modifier
        .onKeyEvent {
            if (it.isCtrlPressed && it.key == Key.A) {
                println("Ctrl + A is pressed")
                true
            } else {
                // let other handlers receive this event
                false
            }
        }
        .focusRequester(requester)
        .focusable()
        .size(10.dp)
)
LaunchedEffect(Unit) {
    requester.requestFocus()
}

Alternatively just add content to Box so it will stretch and .size modifier won't be needed anymore

Noctambulous answered 3/9, 2021 at 6:31 Comment(6)
What about if I want to trigger 2 keyEvents. If I copy & Paste the same for another button, only second one works. Any help?Labrie
@XaviSeguraArdévol only one item can be focused in your app. Check out more about focus in compose in this article (originally link in the answer was broken, updated it). Do you wanna listen key event for the whole app, or for specific views? As an example using requester.requestFocus() you can make button focused on click.Noctambulous
@PhilipDukhov what tutorial do you suggest to start to learn jetpack compose in kotlin androidTrichroism
@sashabeliy start with the documentation, it's pretty good and has a simple tutorial tooNoctambulous
Is important to note that, ins this stage of the API, after clicking in the window the focused box with mouse the focus appears to be lost. Is really needed handle this manually such as applying the focus again in a click?Southeastwardly
@LucasSousa This extra click handling shouldn't be needed if you put your views inside the Box. onKeyEvent should work fine if the focused view is inside the Box. Also consider using Window onKeyEvent as mentioned in the beginning of my answer if you need to track key events for the whole window.Noctambulous
S
0

Following the second option of the Philip answer is possible to get a strange behavior when you set the focus and, for some reason, click inside application window. Doing this, is possible "lost" the focus and the key events are not propper handled.

In order to avoid this the suggestion is manually handle this problem by adding a click/tap modifier, which just specifies that when detect a click/tap the requester requests the focus again. See below:

val requester = FocusRequester()
  Box(
    Modifier
       //pointer input handles [onPress] to force focus to the [requester]
      .pointerInput(key1 = true) {
        detectTapGestures(onPress = {
          requester.requestFocus()
        })
      }
      .onKeyEvent {
        if (it.isCtrlPressed && it.key == Key.A) {
          println("Ctrl + A is pressed")
          true
        } else {
          // let other handlers receive this event
          false
        }
      }
      .focusRequester(requester)
      .focusable()
      .fillMaxSize()
      .background(Color.Cyan)
  )
  LaunchedEffect(Unit) {
    requester.requestFocus()
  }
Southeastwardly answered 10/11, 2021 at 23:14 Comment(0)
H
0

You have access to java.awt.robot so that's one possibility:

import java.awt.Robot

@Composable
@Preview
fun App(r:Robot) {
    var text by remember { mutableStateOf("Hello, World!") }

    MaterialTheme {
        Button(onClick = {
            text = "Hello, Desktop!"
            r.keyPress(10)
            r.keyRelease(10)
        }) {
            Text(text)
        }
    }
}

fun main() = application {
    val state = rememberWindowState(
        placement = WindowPlacement.Floating,
        size = DpSize(200.dp, 200.dp)
        )

    Window(
        onCloseRequest = ::exitApplication,
        alwaysOnTop = true,
        focusable = false,
        state = state
    ){
        val r = Robot()
        App(r)
    }
}
Hush answered 26/5 at 18:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.