Skip to content

Commit

Permalink
Merge pull request #11 from Animeshz/0.2.x
Browse files Browse the repository at this point in the history
Fix Linux and Windows issues, add support for Linux x86 on JVM.
  • Loading branch information
Animeshz committed Jan 8, 2021
2 parents 1dbe0d2 + 62556f2 commit 94813ad
Show file tree
Hide file tree
Showing 17 changed files with 134 additions and 103 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Following are the future plans for the project:
, [How to solve missing javah in Java 10 – ugly way](https://www.owsiak.org/how-to-solve-missing-javah-ugly-way)
- [X] Implement way to cross compile the C/C++ library from any OS to any OS and then package it up in the resulting
Jar. Done with PR [#4](https://github.com/Animeshz/keyboard-mouse-kt/pull/4).
- [X] (Complete for Windows x64 and Linux x64 currently) Implement JNI each for different platforms. I've considered it to do via C++ instead of reusing Kotlin/Native because
- [X] Implement JNI each for different platforms. I've considered it to do via C++ instead of reusing Kotlin/Native because
it will result in low performance and maybe huge sizes (if K/N becomes stable and performance wise equivalent we can
directly reuse the sources we've written).
- Add Linux Device (`/dev/uinput` | `/dev/input/xxx`) based implementation of interaction of Keyboard/Mouse as a
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ know more!
- [X] Windows x86_64 (64 bit)
- [X] Windows x86 (32 bit)
- [X] Linux x86_64 (64 bit)
- [ ] Linux x86 (32 bit)
- [X] Linux x86 (32 bit)
- [ ] Linux Arm32
- [ ] Linux Arm64
- [ ] Mouse
Expand Down Expand Up @@ -151,6 +151,8 @@ High Level API depends on [Keyboard][4] which is a wrapper around the [NativeKey
keyboard.play(records, speedFactor = 1.25)
```

__Note: The `Keyboard.dispose()` must be called in order to avoid memory leaks (when process is alive, but the Keyboard instance is no longer referenced).__

## Contributing and future plans

The Github dicussions are open! Be sure to show your existence, say hi! and share if you have any upcoming ideas :)
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {

allprojects {
this.group = "com.github.animeshz"
this.version = "0.1.2"
this.version = "0.2.0"

repositories {
mavenCentral()
Expand Down
9 changes: 5 additions & 4 deletions docker/jvm-build/linux-x64/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
ENV X11_HEADERS_DIR=/usr/include/X11/

RUN \
apt-get update && \
apt-get install --no-install-recommends --yes \
apt update && \
apt install --no-install-recommends --yes \
curl \
libx11-dev \
libxi-dev && \
libxi-dev \
libxtst-dev && \
mkdir -p ${JNI_HEADERS_DIR} && \
cd ${JNI_HEADERS_DIR} && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h

WORKDIR ${WORK_DIR}
WORKDIR ${WORK_DIR}
23 changes: 23 additions & 0 deletions docker/jvm-build/linux-x86/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM dockcross/linux-x86

LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com"

ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x86
ENV WORK_DIR=/work
ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
ENV X11_HEADERS_DIR=/usr/include/X11/

RUN \
dpkg --add-architecture i386 && \
apt update && \
apt install --no-install-recommends --yes \
curl \
libx11-dev:i386 \
libxi-dev:i386 \
libxtst-dev:i386 && \
mkdir -p ${JNI_HEADERS_DIR} && \
cd ${JNI_HEADERS_DIR} && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h

WORKDIR ${WORK_DIR}
26 changes: 12 additions & 14 deletions keyboard/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ plugins {
id("org.jlleitschuh.gradle.ktlint") version "9.4.1"
}

val ideaActive = System.getProperty("idea.active") == "true"

val mainSourceSets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet>()
val testSourceSets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet>()

Expand Down Expand Up @@ -136,7 +134,8 @@ fun KotlinMultiplatformExtension.configureJvm() {
val targets = listOf(
Target("windows", "x64", "animeshz/keyboard-mouse-kt:jni-build-windows-x64"),
Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"),
Target("linux", "x64", "animeshz/keyboard-mouse-kt:jni-build-linux-x64")
Target("linux", "x64", "animeshz/keyboard-mouse-kt:jni-build-linux-x64"),
Target("linux", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86")
)

for (target in targets) {
Expand All @@ -145,10 +144,10 @@ fun KotlinMultiplatformExtension.configureJvm() {
val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) "/run/desktop/mnt/host/${tmpVar[0].toLowerCase()}${tmpVar.substring(2 until tmpVar.length).replace('\\', '/')}"
else tmpVar

val work: () -> String = {
val work: () -> Pair<Int, String> = {
ByteArrayOutputStream().use {
project.exec {
val args = arrayOf(
commandLine(
"docker",
"run",
"--rm",
Expand All @@ -165,30 +164,29 @@ fun KotlinMultiplatformExtension.configureJvm() {
"cp -rf libKeyboardKt${target.arch}.{dll,so,dylib} \$WORK_DIR/project/build/jni 2>/dev/null || : && " +
"cd .. && rm -rf compile-jni-${target.os}-${target.arch}"
)
println(args.joinToString(" "))
commandLine(*args)

isIgnoreExitValue = true
standardOutput = System.out
errorOutput = it
}
it.toString()
}.exitValue to it.toString()
}
}
var output = work()
var (exit, error) = work()

// Fix non-daemon docker on Docker for Windows
val nonDaemonError = "docker: error during connect: This error may indicate that the docker daemon is not running."
if (Os.isFamily(Os.FAMILY_WINDOWS) && output.startsWith(nonDaemonError)) {
if (Os.isFamily(Os.FAMILY_WINDOWS) && error.startsWith(nonDaemonError)) {
project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue()

do {
Thread.sleep(500)
output = work()
} while (output.startsWith(nonDaemonError))
val result = work()
exit = result.first
error = result.second
} while (error.startsWith(nonDaemonError))
}

println(output)
if (exit != 0) throw GradleException(error)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.github.animeshz.keyboard.events.KeyEvent
import com.github.animeshz.keyboard.events.KeyState
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -18,7 +17,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -161,11 +161,7 @@ public class Keyboard(

val handlers = if (trigger == KeyState.KeyDown) keyDownHandlers else keyUpHandlers

val recJob = scope.launch {
handler.events.collect {
record.add(mark.elapsedNow() to it)
}
}
val recJob = handler.events.onEach { record.add(mark.elapsedNow() to it) }.launchIn(scope)
handlers[keySet] = {
handlers.remove(keySet)
recJob.cancel()
Expand Down Expand Up @@ -197,10 +193,10 @@ public class Keyboard(
}

/**
* Cancels all the [Job]s running under this Keyboard instance.
* Disposes this [Keyboard] instance.
*/
public fun cancel(cause: CancellationException? = null) {
scope.cancel(cause)
public fun dispose() {
scope.cancel(null)
job.value = null

keyUpHandlers.dispose()
Expand All @@ -212,20 +208,15 @@ public class Keyboard(
val jobCopy = job.value
if (jobCopy != null && jobCopy.isActive) return

job.value = scope.launch {
handler.events.collect {
when (it.state) {
KeyState.KeyDown -> {
pressedKeys.add(it.key)
handleKeyDown()
}
else -> {
handleKeyUp()
pressedKeys.remove(it.key)
}
}
job.value = handler.events.onEach {
if (it.state == KeyState.KeyDown) {
pressedKeys.add(it.key)
handleKeyDown()
} else {
handleKeyUp()
pressedKeys.remove(it.key)
}
}
}.launchIn(scope)
}

private fun stopIfNeeded() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import com.github.animeshz.keyboard.events.KeyState
import io.kotest.matchers.comparables.shouldNotBeEqualComparingTo
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlin.test.Test

@ExperimentalKeyIO
Expand Down Expand Up @@ -36,11 +39,12 @@ class NativeKeyboardHandlerTest {
val handler = nativeKbHandlerForPlatform()

launch {
delay(5) // Make sure we didn't missed any event
handler.sendEvent(KeyEvent(Key.LeftCtrl, KeyState.KeyDown))
handler.sendEvent(KeyEvent(Key.LeftCtrl, KeyState.KeyUp))
}

val events = handler.events.take(2).toList()
val events = withTimeout(200) { handler.events.dropWhile { it.key != Key.LeftCtrl }.take(2).toList() }

events[0] should {
it.key shouldBe Key.LeftCtrl
Expand Down

0 comments on commit 94813ad

Please sign in to comment.