Skip to content

Commit 94813ad

Browse files
authored
Merge pull request #11 from Animeshz/0.2.x
Fix Linux and Windows issues, add support for Linux x86 on JVM.
2 parents 1dbe0d2 + 62556f2 commit 94813ad

17 files changed

Lines changed: 134 additions & 103 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Following are the future plans for the project:
3737
, [How to solve missing javah in Java 10 – ugly way](https://www.owsiak.org/how-to-solve-missing-javah-ugly-way)
3838
- [X] Implement way to cross compile the C/C++ library from any OS to any OS and then package it up in the resulting
3939
Jar. Done with PR [#4](https://github.com/Animeshz/keyboard-mouse-kt/pull/4).
40-
- [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
40+
- [X] Implement JNI each for different platforms. I've considered it to do via C++ instead of reusing Kotlin/Native because
4141
it will result in low performance and maybe huge sizes (if K/N becomes stable and performance wise equivalent we can
4242
directly reuse the sources we've written).
4343
- Add Linux Device (`/dev/uinput` | `/dev/input/xxx`) based implementation of interaction of Keyboard/Mouse as a

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ know more!
4040
- [X] Windows x86_64 (64 bit)
4141
- [X] Windows x86 (32 bit)
4242
- [X] Linux x86_64 (64 bit)
43-
- [ ] Linux x86 (32 bit)
43+
- [X] Linux x86 (32 bit)
4444
- [ ] Linux Arm32
4545
- [ ] Linux Arm64
4646
- [ ] Mouse
@@ -151,6 +151,8 @@ High Level API depends on [Keyboard][4] which is a wrapper around the [NativeKey
151151
keyboard.play(records, speedFactor = 1.25)
152152
```
153153

154+
__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).__
155+
154156
## Contributing and future plans
155157

156158
The Github dicussions are open! Be sure to show your existence, say hi! and share if you have any upcoming ideas :)

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55

66
allprojects {
77
this.group = "com.github.animeshz"
8-
this.version = "0.1.2"
8+
this.version = "0.2.0"
99

1010
repositories {
1111
mavenCentral()

docker/jvm-build/linux-x64/Dockerfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
88
ENV X11_HEADERS_DIR=/usr/include/X11/
99

1010
RUN \
11-
apt-get update && \
12-
apt-get install --no-install-recommends --yes \
11+
apt update && \
12+
apt install --no-install-recommends --yes \
1313
curl \
1414
libx11-dev \
15-
libxi-dev && \
15+
libxi-dev \
16+
libxtst-dev && \
1617
mkdir -p ${JNI_HEADERS_DIR} && \
1718
cd ${JNI_HEADERS_DIR} && \
1819
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
1920
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h
2021

21-
WORKDIR ${WORK_DIR}
22+
WORKDIR ${WORK_DIR}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM dockcross/linux-x86
2+
3+
LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com"
4+
5+
ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x86
6+
ENV WORK_DIR=/work
7+
ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
8+
ENV X11_HEADERS_DIR=/usr/include/X11/
9+
10+
RUN \
11+
dpkg --add-architecture i386 && \
12+
apt update && \
13+
apt install --no-install-recommends --yes \
14+
curl \
15+
libx11-dev:i386 \
16+
libxi-dev:i386 \
17+
libxtst-dev:i386 && \
18+
mkdir -p ${JNI_HEADERS_DIR} && \
19+
cd ${JNI_HEADERS_DIR} && \
20+
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
21+
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h
22+
23+
WORKDIR ${WORK_DIR}

keyboard/build.gradle.kts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ plugins {
1212
id("org.jlleitschuh.gradle.ktlint") version "9.4.1"
1313
}
1414

15-
val ideaActive = System.getProperty("idea.active") == "true"
16-
1715
val mainSourceSets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet>()
1816
val testSourceSets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet>()
1917

@@ -136,7 +134,8 @@ fun KotlinMultiplatformExtension.configureJvm() {
136134
val targets = listOf(
137135
Target("windows", "x64", "animeshz/keyboard-mouse-kt:jni-build-windows-x64"),
138136
Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"),
139-
Target("linux", "x64", "animeshz/keyboard-mouse-kt:jni-build-linux-x64")
137+
Target("linux", "x64", "animeshz/keyboard-mouse-kt:jni-build-linux-x64"),
138+
Target("linux", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86")
140139
)
141140

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

148-
val work: () -> String = {
147+
val work: () -> Pair<Int, String> = {
149148
ByteArrayOutputStream().use {
150149
project.exec {
151-
val args = arrayOf(
150+
commandLine(
152151
"docker",
153152
"run",
154153
"--rm",
@@ -165,30 +164,29 @@ fun KotlinMultiplatformExtension.configureJvm() {
165164
"cp -rf libKeyboardKt${target.arch}.{dll,so,dylib} \$WORK_DIR/project/build/jni 2>/dev/null || : && " +
166165
"cd .. && rm -rf compile-jni-${target.os}-${target.arch}"
167166
)
168-
println(args.joinToString(" "))
169-
commandLine(*args)
170167

171168
isIgnoreExitValue = true
172169
standardOutput = System.out
173170
errorOutput = it
174-
}
175-
it.toString()
171+
}.exitValue to it.toString()
176172
}
177173
}
178-
var output = work()
174+
var (exit, error) = work()
179175

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

185181
do {
186182
Thread.sleep(500)
187-
output = work()
188-
} while (output.startsWith(nonDaemonError))
183+
val result = work()
184+
exit = result.first
185+
error = result.second
186+
} while (error.startsWith(nonDaemonError))
189187
}
190188

191-
println(output)
189+
if (exit != 0) throw GradleException(error)
192190
}
193191
}
194192
}

keyboard/src/commonMain/kotlin/com/github/animeshz/keyboard/Keyboard.kt

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.github.animeshz.keyboard.events.KeyEvent
99
import com.github.animeshz.keyboard.events.KeyState
1010
import kotlinx.atomicfu.AtomicRef
1111
import kotlinx.atomicfu.atomic
12-
import kotlinx.coroutines.CancellationException
1312
import kotlinx.coroutines.CoroutineExceptionHandler
1413
import kotlinx.coroutines.CoroutineScope
1514
import kotlinx.coroutines.Dispatchers
@@ -18,7 +17,8 @@ import kotlinx.coroutines.Job
1817
import kotlinx.coroutines.SupervisorJob
1918
import kotlinx.coroutines.cancel
2019
import kotlinx.coroutines.delay
21-
import kotlinx.coroutines.flow.collect
20+
import kotlinx.coroutines.flow.launchIn
21+
import kotlinx.coroutines.flow.onEach
2222
import kotlinx.coroutines.launch
2323
import kotlinx.coroutines.suspendCancellableCoroutine
2424
import kotlin.coroutines.CoroutineContext
@@ -161,11 +161,7 @@ public class Keyboard(
161161

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

164-
val recJob = scope.launch {
165-
handler.events.collect {
166-
record.add(mark.elapsedNow() to it)
167-
}
168-
}
164+
val recJob = handler.events.onEach { record.add(mark.elapsedNow() to it) }.launchIn(scope)
169165
handlers[keySet] = {
170166
handlers.remove(keySet)
171167
recJob.cancel()
@@ -197,10 +193,10 @@ public class Keyboard(
197193
}
198194

199195
/**
200-
* Cancels all the [Job]s running under this Keyboard instance.
196+
* Disposes this [Keyboard] instance.
201197
*/
202-
public fun cancel(cause: CancellationException? = null) {
203-
scope.cancel(cause)
198+
public fun dispose() {
199+
scope.cancel(null)
204200
job.value = null
205201

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

215-
job.value = scope.launch {
216-
handler.events.collect {
217-
when (it.state) {
218-
KeyState.KeyDown -> {
219-
pressedKeys.add(it.key)
220-
handleKeyDown()
221-
}
222-
else -> {
223-
handleKeyUp()
224-
pressedKeys.remove(it.key)
225-
}
226-
}
211+
job.value = handler.events.onEach {
212+
if (it.state == KeyState.KeyDown) {
213+
pressedKeys.add(it.key)
214+
handleKeyDown()
215+
} else {
216+
handleKeyUp()
217+
pressedKeys.remove(it.key)
227218
}
228-
}
219+
}.launchIn(scope)
229220
}
230221

231222
private fun stopIfNeeded() {

keyboard/src/commonTest/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandlerTest.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import com.github.animeshz.keyboard.events.KeyState
66
import io.kotest.matchers.comparables.shouldNotBeEqualComparingTo
77
import io.kotest.matchers.should
88
import io.kotest.matchers.shouldBe
9+
import kotlinx.coroutines.delay
10+
import kotlinx.coroutines.flow.dropWhile
911
import kotlinx.coroutines.flow.take
1012
import kotlinx.coroutines.flow.toList
1113
import kotlinx.coroutines.launch
14+
import kotlinx.coroutines.withTimeout
1215
import kotlin.test.Test
1316

1417
@ExperimentalKeyIO
@@ -36,11 +39,12 @@ class NativeKeyboardHandlerTest {
3639
val handler = nativeKbHandlerForPlatform()
3740

3841
launch {
42+
delay(5) // Make sure we didn't missed any event
3943
handler.sendEvent(KeyEvent(Key.LeftCtrl, KeyState.KeyDown))
4044
handler.sendEvent(KeyEvent(Key.LeftCtrl, KeyState.KeyUp))
4145
}
4246

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

4549
events[0] should {
4650
it.key shouldBe Key.LeftCtrl

0 commit comments

Comments
 (0)