From 7f771589baa2af40b0512cf9d4a7f26b91442d52 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Tue, 19 Jan 2021 21:37:57 +0530 Subject: [PATCH 01/26] Update contributing.md --- docs/docs/contributing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index 61a1652..41b8392 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -51,4 +51,6 @@ To build and publish to mavenLocal: The only requirement is to install Docker when building for JVM due to cross-compilation requirement of JNI native libs to be able to pack the full Jar from any platform that is supported cross-platform. +**Note: If you are using Windows, please enable Symbolic Links at the time of installation of git and use the [following instructions](https://stackoverflow.com/a/40914277/11377112) in order to fetch the symbolic links from the git which is present in jni folders.** + [1]: https://github.com/Animeshz/keyboard-mouse-kt/issues/1 From 108d31aa8975259ad2bd409715d855eb336532ab Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 21 Jan 2021 13:06:22 +0530 Subject: [PATCH 02/26] Implement skeletal structure of Kotlin/JS Keyboard handler. --- docker/js-build/windows-x64/Dockerfile | 13 ++++++ .../github/animeshz/keyboard/Cancellable.java | 6 +++ .../com/github/animeshz/keyboard/JKeyboard.kt | 6 ++- keyboard-kt/build.gradle.kts | 19 +++++++- .../com/github/animeshz/keyboard/Keyboard.kt | 4 +- .../keyboard/NativeKeyboardHandler.kt | 6 +++ .../github/animeshz/keyboard/entity/Key.kt | 4 ++ .../animeshz/keyboard/events/KeyEvent.kt | 6 +++ .../animeshz/keyboard/JsKeyboardHandler.kt | 40 +++++++++++++++++ .../github/animeshz/keyboard/NativeUtils.kt | 45 +++++++++++++++++++ .../src/jsMain/napi/JsKeyboardHandler.cpp | 2 + .../animeshz/keyboard/JvmKeyboardHandler.kt | 2 +- .../github/animeshz/keyboard/NativeUtils.kt | 4 +- 13 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 docker/js-build/windows-x64/Dockerfile create mode 100644 integration/keyboard-kt-jdk8/src/main/java/com/github/animeshz/keyboard/Cancellable.java create mode 100644 keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt create mode 100644 keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt create mode 100644 keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp diff --git a/docker/js-build/windows-x64/Dockerfile b/docker/js-build/windows-x64/Dockerfile new file mode 100644 index 0000000..a5b4166 --- /dev/null +++ b/docker/js-build/windows-x64/Dockerfile @@ -0,0 +1,13 @@ +FROM dockcross/windows-shared-x64 + +LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" + +ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:js-build-windows-x64 + +RUN \ + apt-get update && \ + apt-get install --no-install-recommends --yes \ + python3 \ + nodejs \ + npm && \ + npm install -g node-gyp diff --git a/integration/keyboard-kt-jdk8/src/main/java/com/github/animeshz/keyboard/Cancellable.java b/integration/keyboard-kt-jdk8/src/main/java/com/github/animeshz/keyboard/Cancellable.java new file mode 100644 index 0000000..85d9ef8 --- /dev/null +++ b/integration/keyboard-kt-jdk8/src/main/java/com/github/animeshz/keyboard/Cancellable.java @@ -0,0 +1,6 @@ +package com.github.animeshz.keyboard; + +@FunctionalInterface +public interface Cancellable { + void invoke(); +} diff --git a/integration/keyboard-kt-jdk8/src/main/kotlin/com/github/animeshz/keyboard/JKeyboard.kt b/integration/keyboard-kt-jdk8/src/main/kotlin/com/github/animeshz/keyboard/JKeyboard.kt index 413b6ee..8a80179 100644 --- a/integration/keyboard-kt-jdk8/src/main/kotlin/com/github/animeshz/keyboard/JKeyboard.kt +++ b/integration/keyboard-kt-jdk8/src/main/kotlin/com/github/animeshz/keyboard/JKeyboard.kt @@ -43,7 +43,11 @@ public class JKeyboard { keySet: KeySet, trigger: KeyState = KeyState.KeyDown, handler: ShortcutHandler - ): Cancellable = delegate.addShortcut(keySet, trigger) { handler.handle() } + ): Cancellable { + val kCancellable = delegate.addShortcut(keySet, trigger) { handler.handle() } + + return Cancellable { kCancellable() } + } /** * Presses and releases the [keySet] on the host machine. diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 805745c..28a125a 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -192,6 +192,22 @@ fun KotlinMultiplatformExtension.configureJvm() { } } +fun KotlinMultiplatformExtension.configureJs() { + js(IR) { + nodejs() + } + + val jsMain by sourceSets.getting { + dependencies { + implementation(devNpm("node-addon-api", "*")) + } + } + val jsTest by sourceSets.getting { dependsOn(jsMain) } + + mainSourceSets.add(jsMain) + testSourceSets.add(jsTest) +} + fun KotlinMultiplatformExtension.configureLinux() { linuxX64 { val main by compilations.getting @@ -217,6 +233,7 @@ fun KotlinMultiplatformExtension.configureMingw() { kotlin { configureJvm() + configureJs() configureLinux() configureMingw() @@ -234,7 +251,7 @@ kotlin { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) implementation("io.mockk:mockk-common:1.10.3") - implementation("io.kotest:kotest-assertions-core:4.3.1") + implementation("io.kotest:kotest-assertions-core:4.4.0.RC2") } } diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/Keyboard.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/Keyboard.kt index 0a2ae4c..023f5d4 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/Keyboard.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/Keyboard.kt @@ -29,7 +29,7 @@ import kotlin.time.TimeSource /** * A typealias of lambda returned from [Keyboard.addShortcut] for better readability. */ -public fun interface Cancellable : () -> Unit +public typealias Cancellable = () -> Unit /** * Represents a keypress sequence with each element in ascending order of duration from the start time. @@ -79,7 +79,7 @@ public class Keyboard( handlers.value += keySet to handler startIfNeeded() - return Cancellable { + return { handlers.value -= keySet stopIfNeeded() } diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt index c78b5cc..d7d292c 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt @@ -13,11 +13,15 @@ import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport /** * A low-level implementation for handling [KeyEvent]s (sending and receiving). */ +@ExperimentalJsExport @ExperimentalKeyIO +@JsExport public interface NativeKeyboardHandler { /** * A [SharedFlow] of [KeyEvent] for receiving Key events from the target platform. @@ -56,9 +60,11 @@ public interface NativeKeyboardHandler { * Gets the [NativeKeyboardHandler] for the particular platform. * Always returns the same instance. */ +@ExperimentalJsExport @ExperimentalKeyIO public expect fun nativeKbHandlerForPlatform(): NativeKeyboardHandler +@ExperimentalJsExport @ExperimentalKeyIO internal abstract class NativeKeyboardHandlerBase : NativeKeyboardHandler { protected val eventsInternal: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 8) diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt index 5fc9427..e1ca35e 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt @@ -1,14 +1,18 @@ package com.github.animeshz.keyboard.entity import com.github.animeshz.keyboard.ExperimentalKeyIO +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport /** * Represents corresponding key of the keyboard. * * [keyCode] matches with hardware scan codes. */ +@ExperimentalJsExport @Suppress("unused") @ExperimentalKeyIO +@JsExport public enum class Key(public val keyCode: Int) { Unknown(-1), Esc(1), diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt index e289bbd..0b4a5a7 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt @@ -2,6 +2,8 @@ package com.github.animeshz.keyboard.events import com.github.animeshz.keyboard.ExperimentalKeyIO import com.github.animeshz.keyboard.entity.Key +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport /** * When a user presses a key on a hardware keyboard, a [KeyEvent] is sent. @@ -9,7 +11,9 @@ import com.github.animeshz.keyboard.entity.Key * @param key The [Key] that is associated with the event. * @param state The type of the event (see [KeyState]). */ +@ExperimentalJsExport @ExperimentalKeyIO +@JsExport public class KeyEvent( public val key: Key, public val state: KeyState @@ -20,7 +24,9 @@ public class KeyEvent( /** * The State of [Key]. */ +@ExperimentalJsExport @ExperimentalKeyIO +@JsExport public enum class KeyState { /** * Type of state when the user lifts their finger off a key on the keyboard. diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt new file mode 100644 index 0000000..192a9fb --- /dev/null +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt @@ -0,0 +1,40 @@ +package com.github.animeshz.keyboard + +import com.github.animeshz.keyboard.entity.Key +import com.github.animeshz.keyboard.events.KeyEvent +import com.github.animeshz.keyboard.events.KeyState + +@ExperimentalJsExport +@ExperimentalKeyIO +internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { + val nApiNativeHandler = NativeUtils.getNApiNativeHandler().also { it.init() } + + override fun sendEvent(keyEvent: KeyEvent) { + nApiNativeHandler.send(keyEvent.key.keyCode, keyEvent.state == KeyState.KeyDown) + } + + override fun getKeyState(key: Key): KeyState = + if (nApiNativeHandler.isPressed(key.keyCode)) KeyState.KeyDown else KeyState.KeyUp + + override fun isCapsLockOn(): Boolean = nApiNativeHandler.isCapsLockOn() + + override fun isNumLockOn(): Boolean = nApiNativeHandler.isNumLockOn() + + override fun isScrollLockOn(): Boolean = nApiNativeHandler.isScrollLockOn() + + override fun startReadingEvents() { + val code = nApiNativeHandler.startReadingEvents() + if (code != 0) { + error("Unable to set native hook. Error code: $code") + } + } + + override fun stopReadingEvents() = nApiNativeHandler.stopReadingEvents() +} + +@ExperimentalJsExport +@ExperimentalKeyIO +@JsExport +public actual fun nativeKbHandlerForPlatform(): NativeKeyboardHandler { + return JsKeyboardHandler +} diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt new file mode 100644 index 0000000..000ce9b --- /dev/null +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -0,0 +1,45 @@ +package com.github.animeshz.keyboard + +public external fun require(module: String): dynamic + +@JsModule("os") +public external fun arch(): String + +@JsModule("os") +public external fun platform(): String + +internal object NativeUtils { + private val suffix = when(val architecture = arch()) { + "x64" -> "x64" + "x32" -> "x86" + else -> error("Non x86 architectures are not supported, current architecture: $architecture") + } + private val identifier = when(val platform = platform()) { + "darwin" -> error("Mac os is currently not supported") + "linux" -> "linux" + "win32" -> "windows" + else -> error("OS not supported. Current OS: $platform") + } + + @ExperimentalJsExport + @ExperimentalKeyIO + fun getNApiNativeHandler(): NApiNativeHandler = + require("./lib/$identifier$suffix.node") as NApiNativeHandler +} + +@ExperimentalKeyIO +@ExperimentalJsExport +internal external class NApiNativeHandler( + publicHandler: JsKeyboardHandler +) { + fun send(scanCode: Int, isPressed: Boolean) + fun isPressed(scanCode: Int): Boolean + + fun isCapsLockOn(): Boolean + fun isNumLockOn(): Boolean + fun isScrollLockOn(): Boolean + + fun init(): Int + fun startReadingEvents(): Int + fun stopReadingEvents() +} diff --git a/keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp new file mode 100644 index 0000000..19347df --- /dev/null +++ b/keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp @@ -0,0 +1,2 @@ +#include + diff --git a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt index 0f4db2e..dcd5a0f 100644 --- a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt +++ b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt @@ -47,7 +47,7 @@ internal object JvmKeyboardHandler : NativeKeyboardHandlerBase() { } private external fun nativeInit(): Int - private external fun nativeSendEvent(scanCode: Int, isDown: Boolean) + private external fun nativeSendEvent(scanCode: Int, isPressed: Boolean) private external fun nativeIsPressed(scanCode: Int): Boolean private external fun nativeStartReadingEvents(): Int private external fun nativeStopReadingEvents() diff --git a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index 9a46668..94818b8 100644 --- a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -16,14 +16,14 @@ internal object NativeUtils { "aarch64" in arch || "arm" in arch -> error("Arm not supported") /* "arm" */ "64" in arch -> "x64" "86" in arch || "32" in arch -> "x86" - else -> error("CPU architecture not supported") + else -> error("CPU architecture not supported. Current CPU architecture: $arch") } extension = when { "mac" in os || "darwin" in os -> error("Mac is not supported currently") /* "libKeyboardKt.dylib" */ "win" in os -> "dll" "nux" in os || "nix" in os || "aix" in os -> "so" - else -> error("OS not supported") + else -> error("OS not supported. Current OS: $os") } } From 19c4ed04dabb0debd360b256121257c4af3b1fd8 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 21 Jan 2021 15:52:53 +0530 Subject: [PATCH 03/26] Prototype Promise based API for consumption from Node.js --- .../keyboard/NativeKeyboardHandler.kt | 6 - .../github/animeshz/keyboard/entity/Key.kt | 2 - .../animeshz/keyboard/events/KeyEvent.kt | 4 - .../github/animeshz/keyboard/JsKeyboard.kt | 116 ++++++++++++++++++ .../animeshz/keyboard/JsKeyboardHandler.kt | 55 +++++++-- .../github/animeshz/keyboard/NativeUtils.kt | 14 +-- 6 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt index d7d292c..c78b5cc 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandler.kt @@ -13,15 +13,11 @@ import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlin.js.ExperimentalJsExport -import kotlin.js.JsExport /** * A low-level implementation for handling [KeyEvent]s (sending and receiving). */ -@ExperimentalJsExport @ExperimentalKeyIO -@JsExport public interface NativeKeyboardHandler { /** * A [SharedFlow] of [KeyEvent] for receiving Key events from the target platform. @@ -60,11 +56,9 @@ public interface NativeKeyboardHandler { * Gets the [NativeKeyboardHandler] for the particular platform. * Always returns the same instance. */ -@ExperimentalJsExport @ExperimentalKeyIO public expect fun nativeKbHandlerForPlatform(): NativeKeyboardHandler -@ExperimentalJsExport @ExperimentalKeyIO internal abstract class NativeKeyboardHandlerBase : NativeKeyboardHandler { protected val eventsInternal: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 8) diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt index e1ca35e..0573a6a 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/entity/Key.kt @@ -9,10 +9,8 @@ import kotlin.js.JsExport * * [keyCode] matches with hardware scan codes. */ -@ExperimentalJsExport @Suppress("unused") @ExperimentalKeyIO -@JsExport public enum class Key(public val keyCode: Int) { Unknown(-1), Esc(1), diff --git a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt index 0b4a5a7..7a4d9b9 100644 --- a/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt +++ b/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt @@ -11,9 +11,7 @@ import kotlin.js.JsExport * @param key The [Key] that is associated with the event. * @param state The type of the event (see [KeyState]). */ -@ExperimentalJsExport @ExperimentalKeyIO -@JsExport public class KeyEvent( public val key: Key, public val state: KeyState @@ -24,9 +22,7 @@ public class KeyEvent( /** * The State of [Key]. */ -@ExperimentalJsExport @ExperimentalKeyIO -@JsExport public enum class KeyState { /** * Type of state when the user lifts their finger off a key on the keyboard. diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt new file mode 100644 index 0000000..9059095 --- /dev/null +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt @@ -0,0 +1,116 @@ +package com.github.animeshz.keyboard + +import com.github.animeshz.keyboard.entity.KeySet +import com.github.animeshz.keyboard.events.KeyEvent +import com.github.animeshz.keyboard.events.KeyState +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.promise +import kotlin.js.Promise +import kotlin.time.ExperimentalTime +import kotlin.time.seconds + +@ExperimentalJsExport +@ExperimentalTime +@JsExport +@JsName("KeyPressSequence") +public class TimedKeyEvent( + public val durationInSeconds: Double, + public val key: String, + public val isPressed: Boolean +) + +@Suppress("unused") +@ExperimentalJsExport +@ExperimentalKeyIO +@ExperimentalCoroutinesApi +@JsExport +@JsName("Keyboard") +public class JsKeyboard { + private val delegate = Keyboard() + + public val handler: JsKeyboardHandlerExport = JsKeyboardHandlerExport + + private fun parseKeySet(str: String): KeySet = + str.split(Regex("""\s*\+\s*""")) + .asSequence() + .map { it.toKey() } + .toSet() + .let { KeySet(it) } + + /** + * Adds the [handler] to be invoked at either on press or release defined by [triggerOnPressed] of the [keySet]. + * + * @return Returns a Cancellable [kotlin.reflect.KFunction], which when invoked the handler is removed. + */ + @JsName("addShortcut") + public fun addShortcut( + keySet: String, + triggerOnPressed: Boolean = true, + handler: () -> Unit + ): Cancellable { + return delegate.addShortcut(parseKeySet(keySet), triggerOnPressed.toKeyState()) { handler() } + } + + /** + * Presses and releases the [keySet] on the host machine. + */ + @JsName("send") + public fun send(keySet: String) { + delegate.send(parseKeySet(keySet)) + } + + /** + * Writes the following [string] on the host machine. + */ + public fun write(string: String) { + delegate.write(string) + } + + /** + * Returns a [Promise] that notifies for completion when [keySet] are pressed. + */ + @JsName("completeWhenPressed") + public fun completeWhenPressed( + keySet: String, + triggerOnPressed: Boolean = true + ): Promise = + GlobalScope.promise { + delegate.awaitTill(parseKeySet(keySet), triggerOnPressed.toKeyState()) + null + } + + /** + * Records and returns a [KeyPressSequence] of all the keypress till a [keySet] is/are pressed. + */ + @ExperimentalTime + public fun recordKeyPressesTill( + keySet: String, + triggerOnPressed: Boolean = true + ): Promise> = + GlobalScope.promise { + delegate.recordKeyPressesTill(parseKeySet(keySet), triggerOnPressed.toKeyState()) + .map { TimedKeyEvent(it.first.inSeconds, it.second.key.name, it.second.state == KeyState.KeyDown) } + .toTypedArray() + } + + /** + * Plays the given [orderedPresses] with a speed of [speedFactor]. + * + * @return A [Promise] for subscribing to get notified when does play finishes. + */ + @ExperimentalTime + public fun play(orderedPresses: Array, speedFactor: Double = 1.0): Promise = + GlobalScope.promise { + val sequence = orderedPresses.map { it.durationInSeconds.seconds to KeyEvent(it.key.toKey(), it.isPressed.toKeyState()) } + delegate.play(sequence, speedFactor) + null + } + + /** + * Disposes this [Keyboard] instance. + */ + public fun dispose() { + delegate.dispose() + } +} diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt index 192a9fb..0d5c388 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt @@ -1,20 +1,22 @@ package com.github.animeshz.keyboard +import com.github.animeshz.keyboard.NativeUtils.nApiNativeHandler import com.github.animeshz.keyboard.entity.Key import com.github.animeshz.keyboard.events.KeyEvent import com.github.animeshz.keyboard.events.KeyState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach -@ExperimentalJsExport @ExperimentalKeyIO internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { - val nApiNativeHandler = NativeUtils.getNApiNativeHandler().also { it.init() } - override fun sendEvent(keyEvent: KeyEvent) { - nApiNativeHandler.send(keyEvent.key.keyCode, keyEvent.state == KeyState.KeyDown) + nApiNativeHandler.send(keyEvent.key.keyCode, keyEvent.state.isPressed()) } override fun getKeyState(key: Key): KeyState = - if (nApiNativeHandler.isPressed(key.keyCode)) KeyState.KeyDown else KeyState.KeyUp + nApiNativeHandler.isPressed(key.keyCode).toKeyState() override fun isCapsLockOn(): Boolean = nApiNativeHandler.isCapsLockOn() @@ -32,9 +34,48 @@ internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { override fun stopReadingEvents() = nApiNativeHandler.stopReadingEvents() } -@ExperimentalJsExport @ExperimentalKeyIO -@JsExport public actual fun nativeKbHandlerForPlatform(): NativeKeyboardHandler { return JsKeyboardHandler } + +@ExperimentalKeyIO +internal fun String.toKey(): Key = Key.values().first { it.name == this } + +@ExperimentalKeyIO +internal fun KeyState.isPressed() = this == KeyState.KeyDown + +@ExperimentalKeyIO +internal fun Boolean.toKeyState() = if (this) KeyState.KeyDown else KeyState.KeyUp + +@ExperimentalJsExport +@ExperimentalKeyIO +@JsExport +@JsName("KeyboardHandler") +public object JsKeyboardHandlerExport { + private val scope = CoroutineScope(Dispatchers.Unconfined) + + @JsName("addHandler") + public fun addHandler(handler: (key: String, isPressed: Boolean) -> Unit) { + JsKeyboardHandler.events.onEach { handler(it.key.name, it.state.isPressed()) } + .launchIn(scope) + } + + @JsName("send") + public fun NativeKeyboardHandler.sendEvent(key: String, isPressed: Boolean): Unit = + JsKeyboardHandler.sendEvent(KeyEvent(key.toKey(), isPressed.toKeyState())) + + @JsName("getKeyState") + public fun NativeKeyboardHandler.getKeyState(key: String): Boolean = + getKeyState(key.toKey()).isPressed() + + @JsName("isCapsLockOn") + public fun isCapsLockOn(): Boolean = JsKeyboardHandler.isCapsLockOn() + + @JsName("isNumLockOn") + public fun isNumLockOn(): Boolean = JsKeyboardHandler.isNumLockOn() + + @JsName("isScrollLockOn") + public fun isScrollLockOn(): Boolean = JsKeyboardHandler.isScrollLockOn() +} + diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index 000ce9b..38b7cf6 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -3,9 +3,11 @@ package com.github.animeshz.keyboard public external fun require(module: String): dynamic @JsModule("os") +@JsNonModule public external fun arch(): String @JsModule("os") +@JsNonModule public external fun platform(): String internal object NativeUtils { @@ -21,17 +23,13 @@ internal object NativeUtils { else -> error("OS not supported. Current OS: $platform") } - @ExperimentalJsExport @ExperimentalKeyIO - fun getNApiNativeHandler(): NApiNativeHandler = - require("./lib/$identifier$suffix.node") as NApiNativeHandler + val nApiNativeHandler: NApiNativeHandler = + (require("./lib/$identifier$suffix.node") as NApiNativeHandler).also { it.init() } } @ExperimentalKeyIO -@ExperimentalJsExport -internal external class NApiNativeHandler( - publicHandler: JsKeyboardHandler -) { +internal external class NApiNativeHandler { fun send(scanCode: Int, isPressed: Boolean) fun isPressed(scanCode: Int): Boolean @@ -40,6 +38,6 @@ internal external class NApiNativeHandler( fun isScrollLockOn(): Boolean fun init(): Int - fun startReadingEvents(): Int + fun startReadingEvents(handler: () -> Unit): Int fun stopReadingEvents() } From 8672350e2ec4081eb4e1e59fa9b19f705a8af945 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 21 Jan 2021 18:48:33 +0530 Subject: [PATCH 04/26] Finish prototyping JS frontend. --- docs/docs/status-and-installation.md | 24 +++++++++++++------ keyboard-kt/build.gradle.kts | 10 ++++++-- .../keyboard/NativeKeyboardHandlerTest.kt | 3 +++ .../{napi => cpp}/JsKeyboardHandler.cpp | 0 .../github/animeshz/keyboard/JsKeyboard.kt | 4 ++-- .../animeshz/keyboard/JsKeyboardHandler.kt | 23 ++++++++++-------- .../github/animeshz/keyboard/NativeUtils.kt | 12 ++-------- .../com/github/animeshz/keyboard/node_os.kt | 7 ++++++ .../com/github/animeshz/keyboard/TestUtils.kt | 8 +++++++ 9 files changed, 60 insertions(+), 31 deletions(-) rename keyboard-kt/src/jsMain/{napi => cpp}/JsKeyboardHandler.cpp (100%) create mode 100644 keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/node_os.kt create mode 100644 keyboard-kt/src/jsTest/kotlin/com/github/animeshz/keyboard/TestUtils.kt diff --git a/docs/docs/status-and-installation.md b/docs/docs/status-and-installation.md index 9248a9f..6cedffb 100644 --- a/docs/docs/status-and-installation.md +++ b/docs/docs/status-and-installation.md @@ -3,13 +3,6 @@ ## Status - [ ] Keyboard - - [ ] Windows - - [X] x86_64 (64 bit) - - [ ] x86 (32 bit) - - [ ] Linux - - [X] x86_64 (64 bit) - - [ ] x86 (32 bit) - - [ ] MacOS - [ ] JVM - [X] Windows x86_64 (64 bit) - [X] Windows x86 (32 bit) @@ -17,6 +10,23 @@ - [X] Linux x86 (32 bit) - [ ] Linux Arm32 - [ ] Linux Arm64 + - [ ] MacOS + - [ ] JS + - [ ] Windows x86_64 (64 bit) + - [ ] Windows x86 (32 bit) + - [ ] Linux x86_64 (64 bit) + - [ ] Linux x86 (32 bit) + - [ ] Linux Arm32 + - [ ] Linux Arm64 + - [ ] MacOS + - [ ] Native + - [X] Windows x86_64 (64 bit) + - [ ] Windows x86 (32 bit) + - [X] Linux x86_64 (64 bit) + - [ ] Linux x86 (32 bit) + - [ ] Linux Arm32 + - [ ] Linux Arm64 + - [ ] MacOS - [ ] Mouse - [ ] Windows - [ ] Linux diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 28a125a..ea2e3f2 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -193,7 +193,8 @@ fun KotlinMultiplatformExtension.configureJvm() { } fun KotlinMultiplatformExtension.configureJs() { - js(IR) { + js { + useCommonJs() nodejs() } @@ -202,7 +203,12 @@ fun KotlinMultiplatformExtension.configureJs() { implementation(devNpm("node-addon-api", "*")) } } - val jsTest by sourceSets.getting { dependsOn(jsMain) } + val jsTest by sourceSets.getting { + dependsOn(jsMain) + dependencies { + implementation(kotlin("test-js")) + } + } mainSourceSets.add(jsMain) testSourceSets.add(jsTest) diff --git a/keyboard-kt/src/commonTest/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandlerTest.kt b/keyboard-kt/src/commonTest/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandlerTest.kt index a0e2fe8..2438cef 100644 --- a/keyboard-kt/src/commonTest/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandlerTest.kt +++ b/keyboard-kt/src/commonTest/kotlin/com/github/animeshz/keyboard/NativeKeyboardHandlerTest.kt @@ -12,11 +12,13 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout +import kotlin.js.JsName import kotlin.test.Test @ExperimentalKeyIO class NativeKeyboardHandlerTest { @Test + @JsName("Caps_lock_key_should_be_toggled_when_KeyDown_event_is_triggered") fun `Caps lock key should be toggled when KeyDown event is triggered`() { val handler = nativeKbHandlerForPlatform() @@ -35,6 +37,7 @@ class NativeKeyboardHandlerTest { } @Test + @JsName("Test_send_and_receive_event") fun `Test send and receive event`() = runBlockingTest { val handler = nativeKbHandlerForPlatform() diff --git a/keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/JsKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jsMain/napi/JsKeyboardHandler.cpp rename to keyboard-kt/src/jsMain/cpp/JsKeyboardHandler.cpp diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt index 9059095..d716dbc 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt @@ -25,11 +25,11 @@ public class TimedKeyEvent( @ExperimentalKeyIO @ExperimentalCoroutinesApi @JsExport -@JsName("Keyboard") +@JsName("JsKeyboard") public class JsKeyboard { private val delegate = Keyboard() - public val handler: JsKeyboardHandlerExport = JsKeyboardHandlerExport + public val handler: JsKeyboardHandler = JsKeyboardHandler private fun parseKeySet(str: String): KeySet = str.split(Regex("""\s*\+\s*""")) diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt index 0d5c388..0aca44f 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @ExperimentalKeyIO -internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { +internal object KotlinJsKeyboardHandler : NativeKeyboardHandlerBase() { override fun sendEvent(keyEvent: KeyEvent) { nApiNativeHandler.send(keyEvent.key.keyCode, keyEvent.state.isPressed()) } @@ -25,7 +25,10 @@ internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { override fun isScrollLockOn(): Boolean = nApiNativeHandler.isScrollLockOn() override fun startReadingEvents() { - val code = nApiNativeHandler.startReadingEvents() + val code = nApiNativeHandler.startReadingEvents { scanCode, isPressed -> + eventsInternal.tryEmit(KeyEvent(Key.fromKeyCode(scanCode), isPressed.toKeyState())) + } + if (code != 0) { error("Unable to set native hook. Error code: $code") } @@ -36,7 +39,7 @@ internal object JsKeyboardHandler : NativeKeyboardHandlerBase() { @ExperimentalKeyIO public actual fun nativeKbHandlerForPlatform(): NativeKeyboardHandler { - return JsKeyboardHandler + return KotlinJsKeyboardHandler } @ExperimentalKeyIO @@ -51,31 +54,31 @@ internal fun Boolean.toKeyState() = if (this) KeyState.KeyDown else KeyState.Key @ExperimentalJsExport @ExperimentalKeyIO @JsExport -@JsName("KeyboardHandler") -public object JsKeyboardHandlerExport { +@JsName("JsKeyboardHandler") +public object JsKeyboardHandler { private val scope = CoroutineScope(Dispatchers.Unconfined) @JsName("addHandler") public fun addHandler(handler: (key: String, isPressed: Boolean) -> Unit) { - JsKeyboardHandler.events.onEach { handler(it.key.name, it.state.isPressed()) } + KotlinJsKeyboardHandler.events.onEach { handler(it.key.name, it.state.isPressed()) } .launchIn(scope) } @JsName("send") public fun NativeKeyboardHandler.sendEvent(key: String, isPressed: Boolean): Unit = - JsKeyboardHandler.sendEvent(KeyEvent(key.toKey(), isPressed.toKeyState())) + KotlinJsKeyboardHandler.sendEvent(KeyEvent(key.toKey(), isPressed.toKeyState())) @JsName("getKeyState") public fun NativeKeyboardHandler.getKeyState(key: String): Boolean = getKeyState(key.toKey()).isPressed() @JsName("isCapsLockOn") - public fun isCapsLockOn(): Boolean = JsKeyboardHandler.isCapsLockOn() + public fun isCapsLockOn(): Boolean = KotlinJsKeyboardHandler.isCapsLockOn() @JsName("isNumLockOn") - public fun isNumLockOn(): Boolean = JsKeyboardHandler.isNumLockOn() + public fun isNumLockOn(): Boolean = KotlinJsKeyboardHandler.isNumLockOn() @JsName("isScrollLockOn") - public fun isScrollLockOn(): Boolean = JsKeyboardHandler.isScrollLockOn() + public fun isScrollLockOn(): Boolean = KotlinJsKeyboardHandler.isScrollLockOn() } diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index 38b7cf6..ee40638 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -2,14 +2,6 @@ package com.github.animeshz.keyboard public external fun require(module: String): dynamic -@JsModule("os") -@JsNonModule -public external fun arch(): String - -@JsModule("os") -@JsNonModule -public external fun platform(): String - internal object NativeUtils { private val suffix = when(val architecture = arch()) { "x64" -> "x64" @@ -25,7 +17,7 @@ internal object NativeUtils { @ExperimentalKeyIO val nApiNativeHandler: NApiNativeHandler = - (require("./lib/$identifier$suffix.node") as NApiNativeHandler).also { it.init() } + (require("./lib/$identifier$suffix.node").NApiNativeHandler() as NApiNativeHandler).also { it.init() } } @ExperimentalKeyIO @@ -38,6 +30,6 @@ internal external class NApiNativeHandler { fun isScrollLockOn(): Boolean fun init(): Int - fun startReadingEvents(handler: () -> Unit): Int + fun startReadingEvents(handler: (scanCode: Int, isPressed: Boolean) -> Unit): Int fun stopReadingEvents() } diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/node_os.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/node_os.kt new file mode 100644 index 0000000..39e456a --- /dev/null +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/node_os.kt @@ -0,0 +1,7 @@ +@file:JsModule("os") +@file:JsNonModule +package com.github.animeshz.keyboard + +public external fun arch(): String + +public external fun platform(): String diff --git a/keyboard-kt/src/jsTest/kotlin/com/github/animeshz/keyboard/TestUtils.kt b/keyboard-kt/src/jsTest/kotlin/com/github/animeshz/keyboard/TestUtils.kt new file mode 100644 index 0000000..0530e59 --- /dev/null +++ b/keyboard-kt/src/jsTest/kotlin/com/github/animeshz/keyboard/TestUtils.kt @@ -0,0 +1,8 @@ +package com.github.animeshz.keyboard + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.promise + +actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit): dynamic = + GlobalScope.promise { this.block() } From 8cc88b235bd609fe72fc1f4cb01bfd7e49b378b9 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 21 Jan 2021 23:33:34 +0530 Subject: [PATCH 05/26] Make native methods ready for cross-usage (need to remove JVM threading). --- .../{ => windows-x64}/JsKeyboardHandler.cpp | 0 .../src/jsMain/cpp/windows-x64/binding.gyp | 8 + .../github/animeshz/keyboard/NativeUtils.kt | 11 +- .../jni/linux-x64/JvmKeyboardHandler.cpp | 19 ++- .../jni/windows-x64/JvmKeyboardHandler.cpp | 133 +++------------ .../BaseKeyboardHandler.h | 6 +- .../linux}/X11KeyboardHandler.cpp | 63 ++++--- .../windows/WindowsKeybaordHandler.cpp | 155 ++++++++++++++++++ 8 files changed, 250 insertions(+), 145 deletions(-) rename keyboard-kt/src/jsMain/cpp/{ => windows-x64}/JsKeyboardHandler.cpp (100%) create mode 100644 keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp rename keyboard-kt/src/{jvmMain/jni/linux-x64 => nativeCommon}/BaseKeyboardHandler.h (66%) rename keyboard-kt/src/{jvmMain/jni/linux-x64 => nativeCommon/linux}/X11KeyboardHandler.cpp (86%) create mode 100644 keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp diff --git a/keyboard-kt/src/jsMain/cpp/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows-x64/JsKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jsMain/cpp/JsKeyboardHandler.cpp rename to keyboard-kt/src/jsMain/cpp/windows-x64/JsKeyboardHandler.cpp diff --git a/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp b/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp new file mode 100644 index 0000000..4105d39 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "KeyboardKtWindowsX64", + "sources": [ "JsKeyboardHandler.cpp" ] + } + ] +} diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index ee40638..e8d185d 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -4,20 +4,21 @@ public external fun require(module: String): dynamic internal object NativeUtils { private val suffix = when(val architecture = arch()) { - "x64" -> "x64" - "x32" -> "x86" + "x64" -> "X64" + "x32" -> "X86" else -> error("Non x86 architectures are not supported, current architecture: $architecture") } private val identifier = when(val platform = platform()) { "darwin" -> error("Mac os is currently not supported") - "linux" -> "linux" - "win32" -> "windows" + "linux" -> "Linux" + "win32" -> "Windows" else -> error("OS not supported. Current OS: $platform") } @ExperimentalKeyIO val nApiNativeHandler: NApiNativeHandler = - (require("./lib/$identifier$suffix.node").NApiNativeHandler() as NApiNativeHandler).also { it.init() } + (require("./lib/KeyboardKt$identifier$suffix.node").NApiNativeHandler() as NApiNativeHandler) + .also { it.init() } } @ExperimentalKeyIO diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp index c4bddd4..679814b 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp @@ -6,6 +6,8 @@ extern "C" { #endif BaseKeyboardHandler *handler = NULL; +JavaVM *jvm; +jobject JvmKeyboardHandler = NULL; JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeInit(JNIEnv *env, jobject obj) { handler = X11KeyboardHandler::Create(); @@ -36,13 +38,26 @@ JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_ } JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStartReadingEvents(JNIEnv *env, jobject obj) { + env->GetJavaVM(&jvm); + JvmKeyboardHandler = env->NewGlobalRef(obj); jmethodID emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); - handler->startReadingEvents(env, obj, emitEvent); - return 0; + + return handler->startReadingEvents( + [emitEvent](int scanCode, bool isPressed) { + JNIEnv *newEnv; + if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK) { + newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); + } + } + ); } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStopReadingEvents(JNIEnv *env, jobject obj) { return handler->stopReadingEvents(); + + env->DeleteGlobalRef(JvmKeyboardHandler); + JvmKeyboardHandler = NULL; + jvm = NULL; } #ifdef __cplusplus diff --git a/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp index 3a3f43f..13cbc2d 100644 --- a/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp @@ -1,9 +1,8 @@ #include -#include -#include #include +#include "WindowsKeybaordHandler.cpp" #include "com_github_animeshz_keyboard_JvmKeyboardHandler.h" #ifdef __cplusplus @@ -12,138 +11,48 @@ extern "C" { #define FAKE_ALT LLKHF_INJECTED | 0x20 -DWORD threadId = 0; JavaVM *jvm = NULL; jobject JvmKeyboardHandler = NULL; -jmethodID emitEvent = NULL; - -LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) { - tagKBDLLHOOKSTRUCT *keyInfo = (tagKBDLLHOOKSTRUCT *)lParam; - jint vk = keyInfo->vkCode; - - if (vk != VK_PACKET && keyInfo->flags & FAKE_ALT != FAKE_ALT) { - jboolean isPressed = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN; - jboolean extended = keyInfo->flags & 1; - - JNIEnv *env; - if (jvm->AttachCurrentThread((void **)&env, NULL) >= JNI_OK) { - jint scanCode = keyInfo->scanCode; - switch (vk) { - case 0x21: - scanCode = 104; - break; - case 0x22: - scanCode = 109; - break; - case 0x23: - scanCode = 107; - break; - case 0x24: - scanCode = 102; - break; - case 0x25: - scanCode = 105; - break; - case 0x26: - scanCode = 103; - break; - case 0x27: - scanCode = 106; - break; - case 0x28: - scanCode = 108; - break; - case 0x5B: - scanCode = 125; - break; - } - if (extended) { - if (scanCode == 56) { - scanCode = 100; - } else if (scanCode == 29) { - scanCode = 97; - } - } +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { return WindowsKeyboardHandler::getInstance()->isCapsLockOn(); } - env->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); - } - } - - return CallNextHookEx(NULL, nCode, wParam, lParam); -} +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { return WindowsKeyboardHandler::getInstance()->isNumLockOn(); } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { return GetKeyState(0x14) & 1; } - -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { return GetKeyState(0x90) & 1; } - -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isScrollLockOn(JNIEnv *env, jobject obj) { return GetKeyState(0x91) & 1; } +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isScrollLockOn(JNIEnv *env, jobject obj) { return WindowsKeyboardHandler::getInstance()->isScrollLockOn(); } JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeInit(JNIEnv *env, jobject obj) { return 0; } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeSendEvent(JNIEnv *env, jobject obj, jint scanCode, jboolean isDown) { - INPUT input; - input.type = INPUT_KEYBOARD; - input.ki.time = 0; - input.ki.dwExtraInfo = 0; - - // Send Windows/Super key with virtual code, because there's no particular scan code for that. - jint extended = 0; - switch (scanCode) { - case 54: - case 97: - case 100: - case 126: - extended = 1; - break; - } - - if (scanCode == 125) { - input.ki.wVk = 0x5B; - input.ki.dwFlags = (isDown ? 0 : 2) | extended; - } else { - input.ki.wScan = scanCode; - input.ki.dwFlags = 8U | (isDown ? 0 : 2) | extended; - } - - SendInput(1, &input, sizeof(INPUT)); + WindowsKeyboardHandler::getInstance()->sendEvent(scanCode, isDown); } JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeIsPressed(JNIEnv *env, jobject obj, jint scanCode) { - int vk; - if (scanCode == 125) - vk = 0x5B; - else - vk = MapVirtualKeyA(scanCode, MAPVK_VSC_TO_VK_EX); - - return GetKeyState(vk) < 0; + return WindowsKeyboardHandler::getInstance()->isPressed(scanCode); } JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStartReadingEvents(JNIEnv *env, jobject obj) { - HHOOK hook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0); - if (hook == NULL) return GetLastError(); - env->GetJavaVM(&jvm); - threadId = GetCurrentThreadId(); JvmKeyboardHandler = env->NewGlobalRef(obj); - emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); - - MSG msg; - while (GetMessageW(&msg, NULL, 0, 0)) { - TranslateMessage(&msg); - DispatchMessageA(&msg); - } - - UnhookWindowsHookEx(hook); - env->DeleteGlobalRef(JvmKeyboardHandler); - - return 0; + jmethodID emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); + + return WindowsKeyboardHandler::getInstance()->startReadingEvents( + [emitEvent](int scanCode, bool isPressed) { + JNIEnv *newEnv; + if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK && JvmKeyboardHandler != NULL) { + newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); + } + } + ); } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStopReadingEvents(JNIEnv *env, jobject obj) { if (JvmKeyboardHandler == NULL) return; - PostThreadMessage(threadId, WM_QUIT, 0, 0L); + WindowsKeyboardHandler::getInstance()->stopReadingEvents(); + + env->DeleteGlobalRef(JvmKeyboardHandler); + JvmKeyboardHandler = NULL; + jvm = NULL; } #ifdef __cplusplus diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/BaseKeyboardHandler.h b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h similarity index 66% rename from keyboard-kt/src/jvmMain/jni/linux-x64/BaseKeyboardHandler.h rename to keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h index 2e62338..9cc05df 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/BaseKeyboardHandler.h +++ b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h @@ -1,4 +1,4 @@ -#include +#include class BaseKeyboardHandler { public: @@ -6,11 +6,13 @@ class BaseKeyboardHandler { virtual bool isNumLockOn() = 0; + virtual bool isScrollLockOn() = 0; + virtual void sendEvent(int scanCode, bool isPressed) = 0; virtual bool isPressed(int scanCode) = 0; - virtual void startReadingEvents(JNIEnv *env, jobject obj, jmethodID emitEvent) = 0; + virtual int startReadingEvents(std::function) = 0; virtual void stopReadingEvents() = 0; }; \ No newline at end of file diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/X11KeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp similarity index 86% rename from keyboard-kt/src/jvmMain/jni/linux-x64/X11KeyboardHandler.cpp rename to keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp index ba9b575..30a6bc7 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/X11KeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp @@ -2,11 +2,13 @@ #include #include #include -#include #include #include -#include "BaseKeyboardHandler.h" +#include +#include + +#include "../BaseKeyboardHandler.h" class X11KeyboardHandler : BaseKeyboardHandler { private: @@ -17,7 +19,8 @@ class X11KeyboardHandler : BaseKeyboardHandler { int xiOpcode; volatile bool stopReading = false; - X11KeyboardHandler(void *x11, void *xInput2, void *xTest, Display *display, int xiOpcode) { + X11KeyboardHandler(void *x11, void *xInput2, void *xTest, Display *display, + int xiOpcode) { this->x11 = x11; this->xInput2 = xInput2; this->xTest = xTest; @@ -55,7 +58,8 @@ class X11KeyboardHandler : BaseKeyboardHandler { return NULL; } - // Check XInput2 functions are present, since libXi may contain XInput or XInput2. + // Check XInput2 functions are present, since libXi may contain XInput + // or XInput2. void *f = dlsym(xInput2, "XISelectEvents"); if (f == NULL) { dlclose(x11); @@ -91,24 +95,11 @@ class X11KeyboardHandler : BaseKeyboardHandler { return NULL; } - Window root = XDefaultRootWindow(display); - XIEventMask *xiMask = new XIEventMask; - xiMask->deviceid = XIAllMasterDevices; - xiMask->mask_len = XIMaskLen(XI_LASTEVENT); - xiMask->mask = new unsigned char[xiMask->mask_len](); - - XISetMask(xiMask->mask, XI_RawKeyPress); - XISetMask(xiMask->mask, XI_RawKeyRelease); - XISelectEvents(display, root, xiMask, 1); - XSync(display, 0); - - delete [] xiMask->mask; - delete xiMask; - int xiOpcode; int queryEvent; int queryError; - XQueryExtension(display, "XInputExtension", &xiOpcode, &queryEvent, &queryError); + XQueryExtension(display, "XInputExtension", &xiOpcode, &queryEvent, + &queryError); return new X11KeyboardHandler(x11, xInput2, xTest, display, xiOpcode); } @@ -125,6 +116,8 @@ class X11KeyboardHandler : BaseKeyboardHandler { bool isNumLockOn() { return toggleStates() & 2; } + bool isScrollLockOn() { return 0; } + void sendEvent(int scanCode, bool isPressed) { // https://stackoverflow.com/a/42020068/11377112 XTestFakeKeyEvent(display, scanCode + 8, isPressed, 0); @@ -139,7 +132,27 @@ class X11KeyboardHandler : BaseKeyboardHandler { return keyStates[xKeyCode / 8] & (1 << (xKeyCode % 8)); } - void startReadingEvents(JNIEnv *env, jobject obj, jmethodID emitEvent) { + int startReadingEvents(std::function callback) { + std::thread th(&X11KeyboardHandler::readInThread, this, callback); + + return 0; + } + + void readInThread(std::function callback) { + Window root = XDefaultRootWindow(display); + XIEventMask *xiMask = new XIEventMask; + xiMask->deviceid = XIAllMasterDevices; + xiMask->mask_len = XIMaskLen(XI_LASTEVENT); + xiMask->mask = new unsigned char[xiMask->mask_len](); + + XISetMask(xiMask->mask, XI_RawKeyPress); + XISetMask(xiMask->mask, XI_RawKeyRelease); + XISelectEvents(display, root, xiMask, 1); + XSync(display, 0); + + delete[] xiMask->mask; + delete xiMask; + stopReading = false; XEvent event; @@ -148,10 +161,11 @@ class X11KeyboardHandler : BaseKeyboardHandler { if (stopReading) break; XGenericEventCookie cookie = event.xcookie; - if (cookie.type != GenericEvent || cookie.extension != xiOpcode) continue; + if (cookie.type != GenericEvent || cookie.extension != xiOpcode) + continue; if (XGetEventData(display, &cookie)) { - jboolean keyEventType; + bool keyEventType; if (cookie.evtype == XI_RawKeyPress) keyEventType = 1; else if (cookie.evtype == XI_RawKeyRelease) @@ -160,7 +174,7 @@ class X11KeyboardHandler : BaseKeyboardHandler { continue; XIRawEvent *cookieData = (XIRawEvent *)cookie.data; - env->CallVoidMethod(obj, emitEvent, cookieData->detail - 8, keyEventType); + callback(cookieData->detail - 8, keyEventType); } XFreeEventData(display, &cookie); @@ -176,7 +190,8 @@ class X11KeyboardHandler : BaseKeyboardHandler { dummyEvent.type = 33; dummyEvent.format = 32; - XSendEvent(display, XDefaultRootWindow(display), 0, 0, (XEvent *)&dummyEvent); + XSendEvent(display, XDefaultRootWindow(display), 0, 0, + (XEvent *)&dummyEvent); XFlush(display); } }; diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp new file mode 100644 index 0000000..6d55e21 --- /dev/null +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "../BaseKeyboardHandler.h" + +#define FAKE_ALT LLKHF_INJECTED | 0x20 + +class WindowsKeyboardHandler : BaseKeyboardHandler { + private: + static WindowsKeyboardHandler *instance; + DWORD threadId = 0; + WindowsKeyboardHandler() {} + + public: + static std::function callback; + + static BaseKeyboardHandler *getInstance() { + if (!instance) instance = new WindowsKeyboardHandler(); + return instance; + } + + ~WindowsKeyboardHandler() { stopReadingEvents(); } + + bool isCapsLockOn() { return GetKeyState(0x14) & 1; } + + bool isNumLockOn() { return GetKeyState(0x90) & 1; } + + bool isScrollLockOn() { return GetKeyState(0x91) & 1; } + + void sendEvent(int scanCode, bool isPressed) { + INPUT input; + input.type = INPUT_KEYBOARD; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + + // Send Windows/Super key with virtual code, because there's no particular scan code for that. + int extended = 0; + switch (scanCode) { + case 54: + case 97: + case 100: + case 126: + extended = 1; + break; + } + + if (scanCode == 125) { + input.ki.wVk = 0x5B; + input.ki.dwFlags = (isPressed ? 0 : 2) | extended; + } else { + input.ki.wScan = scanCode; + input.ki.dwFlags = 8U | (isPressed ? 0 : 2) | extended; + } + + SendInput(1, &input, sizeof(INPUT)); + } + + bool isPressed(int scanCode) { + int vk; + if (scanCode == 125) + vk = 0x5B; + else + vk = MapVirtualKeyA(scanCode, MAPVK_VSC_TO_VK_EX); + + return GetKeyState(vk) < 0; + } + + int startReadingEvents(std::function callback) { + std::promise p; + std::future f = p.get_future(); + std::thread th(readInThread, callback, std::move(p)); + + threadId = GetThreadId((HANDLE)th.native_handle()); + + return f.get(); + } + + void stopReadingEvents() { PostThreadMessage(threadId, WM_QUIT, 0, 0L); } + + static void readInThread(std::function callback, std::promise &&p) { + HHOOK hook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0); + if (hook == NULL) { + p.set_value(GetLastError()); + } else { + p.set_value(0); + } + + MSG msg; + while (GetMessageW(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + UnhookWindowsHookEx(hook); + } + + static LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) { + tagKBDLLHOOKSTRUCT *keyInfo = (tagKBDLLHOOKSTRUCT *)lParam; + int vk = keyInfo->vkCode; + + if (vk != VK_PACKET && keyInfo->flags & FAKE_ALT != FAKE_ALT) { + bool isPressed = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN; + bool extended = keyInfo->flags & 1; + + int scanCode = keyInfo->scanCode; + switch (vk) { + case 0x21: + scanCode = 104; + break; + case 0x22: + scanCode = 109; + break; + case 0x23: + scanCode = 107; + break; + case 0x24: + scanCode = 102; + break; + case 0x25: + scanCode = 105; + break; + case 0x26: + scanCode = 103; + break; + case 0x27: + scanCode = 106; + break; + case 0x28: + scanCode = 108; + break; + case 0x5B: + scanCode = 125; + break; + } + + if (extended) { + if (scanCode == 56) { + scanCode = 100; + } else if (scanCode == 29) { + scanCode = 97; + } + } + + ((WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance())->callback(scanCode, isPressed); + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); + } +}; \ No newline at end of file From ef3f67e441e8fba368e8e036dde50ed594f87435 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Fri, 22 Jan 2021 22:58:50 +0530 Subject: [PATCH 06/26] Use Win32 threads because std::thread is not available in MinGW, and fix Windows builds. --- keyboard-kt/build.gradle.kts | 6 +-- .../jni/linux-x64/JvmKeyboardHandler.cpp | 37 +++++++------- .../jvmMain/jni/windows-x64/CMakeLists.txt | 5 +- .../jni/windows-x64/JvmKeyboardHandler.cpp | 21 ++++---- .../jvmMain/jni/windows-x86/CMakeLists.txt | 5 +- .../src/nativeCommon/BaseKeyboardHandler.h | 2 +- .../windows/WindowsKeybaordHandler.cpp | 48 +++++++++++-------- 7 files changed, 68 insertions(+), 56 deletions(-) diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index ea2e3f2..12887cf 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -133,9 +133,9 @@ 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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") + Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86") + // 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) { diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp index 679814b..dd87595 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp @@ -6,26 +6,23 @@ extern "C" { #endif BaseKeyboardHandler *handler = NULL; -JavaVM *jvm; +JavaVM *jvm = NULL; jobject JvmKeyboardHandler = NULL; +jmethodID emitEvent = NULL; JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeInit(JNIEnv *env, jobject obj) { handler = X11KeyboardHandler::Create(); if (handler != NULL) return 0; - + // handler = DeviceKeyboardHandler.Create(); // if (handler != NULL) return 0; - + return 1; } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { - return handler->isCapsLockOn(); -} +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { return handler->isCapsLockOn(); } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { - return handler->isNumLockOn(); -} +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { return handler->isNumLockOn(); } JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isScrollLockOn(JNIEnv *env, jobject obj) { return 0; } @@ -33,23 +30,21 @@ JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nati return handler->sendEvent(scanCode, isPressed); } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeIsPressed(JNIEnv *env, jobject obj, jint scanCode) { - return handler->isPressed(scanCode); +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeIsPressed(JNIEnv *env, jobject obj, jint scanCode) { return handler->isPressed(scanCode); } + +void emitEventToJvm(int scanCode, bool isPressed) { + JNIEnv *newEnv; + if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK) { + newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); + } } JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStartReadingEvents(JNIEnv *env, jobject obj) { env->GetJavaVM(&jvm); JvmKeyboardHandler = env->NewGlobalRef(obj); - jmethodID emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); - - return handler->startReadingEvents( - [emitEvent](int scanCode, bool isPressed) { - JNIEnv *newEnv; - if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK) { - newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); - } - } - ); + emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); + + return handler->startReadingEvents(emitEventToJvm); } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStopReadingEvents(JNIEnv *env, jobject obj) { diff --git a/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt index 8b966e9..d586418 100644 --- a/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt @@ -1,12 +1,15 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKtx64) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +add_definitions(-D_WIN32_WINNT=0x600) + include_directories(KeyboardKtx64 PRIVATE $ENV{JNI_HEADERS_DIR}) include_directories(KeyboardKtx64 PRIVATE "../../generated/jni") +include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/windows") add_library( KeyboardKtx64 SHARED diff --git a/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp index 13cbc2d..64a573e 100644 --- a/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp @@ -13,6 +13,7 @@ extern "C" { JavaVM *jvm = NULL; jobject JvmKeyboardHandler = NULL; +jmethodID emitEvent = NULL; JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { return WindowsKeyboardHandler::getInstance()->isCapsLockOn(); } @@ -30,19 +31,19 @@ JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_ return WindowsKeyboardHandler::getInstance()->isPressed(scanCode); } +void emitEventToJvm(int scanCode, bool isPressed) { + JNIEnv *newEnv; + if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK) { + newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); + } +} + JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStartReadingEvents(JNIEnv *env, jobject obj) { env->GetJavaVM(&jvm); JvmKeyboardHandler = env->NewGlobalRef(obj); - jmethodID emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); - - return WindowsKeyboardHandler::getInstance()->startReadingEvents( - [emitEvent](int scanCode, bool isPressed) { - JNIEnv *newEnv; - if (jvm->AttachCurrentThread((void **)&newEnv, NULL) >= JNI_OK && JvmKeyboardHandler != NULL) { - newEnv->CallVoidMethod(JvmKeyboardHandler, emitEvent, scanCode, isPressed); - } - } - ); + emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); + + return WindowsKeyboardHandler::getInstance()->startReadingEvents(emitEventToJvm); } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStopReadingEvents(JNIEnv *env, jobject obj) { diff --git a/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt index 2b7ec81..122a20f 100644 --- a/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt @@ -1,12 +1,15 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKtx86) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +add_definitions(-D_WIN32_WINNT=0x600) + include_directories(KeyboardKtx86 PRIVATE $ENV{JNI_HEADERS_DIR}) include_directories(KeyboardKtx86 PRIVATE "../../generated/jni") +include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/windows") add_library( KeyboardKtx86 SHARED diff --git a/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h index 9cc05df..c9202e8 100644 --- a/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h +++ b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h @@ -12,7 +12,7 @@ class BaseKeyboardHandler { virtual bool isPressed(int scanCode) = 0; - virtual int startReadingEvents(std::function) = 0; + virtual int startReadingEvents(void (*callback)(int, bool)) = 0; virtual void stopReadingEvents() = 0; }; \ No newline at end of file diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp index 6d55e21..bd5156c 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp @@ -1,11 +1,9 @@ -#include -#include #include #include +#include +#include #include -#include -#include #include "../BaseKeyboardHandler.h" @@ -13,13 +11,15 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { private: - static WindowsKeyboardHandler *instance; + inline static WindowsKeyboardHandler *instance = NULL; + inline static void (*callback)(int, bool) = NULL; DWORD threadId = 0; + CRITICAL_SECTION cs; + CONDITION_VARIABLE cv; + WindowsKeyboardHandler() {} public: - static std::function callback; - static BaseKeyboardHandler *getInstance() { if (!instance) instance = new WindowsKeyboardHandler(); return instance; @@ -71,25 +71,34 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { return GetKeyState(vk) < 0; } - int startReadingEvents(std::function callback) { - std::promise p; - std::future f = p.get_future(); - std::thread th(readInThread, callback, std::move(p)); + int startReadingEvents(void (*callback)(int, bool)) { + int ret = 0; + WindowsKeyboardHandler::callback = callback; - threadId = GetThreadId((HANDLE)th.native_handle()); + InitializeCriticalSection (&cs); + InitializeConditionVariable (&cv); + + EnterCriticalSection(&cs); + CreateThread(NULL, 0, readInThread, (LPVOID)&ret, 0, &threadId); + SleepConditionVariableCS(&cv, &cs, INFINITE); + LeaveCriticalSection(&cs); - return f.get(); + return ret; } void stopReadingEvents() { PostThreadMessage(threadId, WM_QUIT, 0, 0L); } - static void readInThread(std::function callback, std::promise &&p) { + static DWORD WINAPI readInThread(LPVOID data) { HHOOK hook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0); if (hook == NULL) { - p.set_value(GetLastError()); - } else { - p.set_value(0); + *(int *)data = GetLastError(); } + data = NULL; // Immediately remove pointer to the stack variable + + auto handler = (WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance(); + EnterCriticalSection(&handler->cs); + WakeConditionVariable(&handler->cv); + LeaveCriticalSection(&handler->cs); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { @@ -98,9 +107,10 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } UnhookWindowsHookEx(hook); + return 0; } - static LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) { + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { tagKBDLLHOOKSTRUCT *keyInfo = (tagKBDLLHOOKSTRUCT *)lParam; int vk = keyInfo->vkCode; @@ -147,7 +157,7 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } } - ((WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance())->callback(scanCode, isPressed); + WindowsKeyboardHandler::callback(scanCode, isPressed); } return CallNextHookEx(NULL, nCode, wParam, lParam); From 41b2a9386d62af78b4c58fa8b2679f73a54c0cc1 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 23 Jan 2021 09:44:58 +0530 Subject: [PATCH 07/26] Fix linux errors and fix JVM error propagation :) --- keyboard-kt/build.gradle.kts | 8 ++++---- keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt | 3 +-- .../src/jvmMain/jni/linux-x86/BaseKeyboardHandler.h | 1 - .../src/jvmMain/jni/linux-x86/JvmKeyboardHandler.cpp | 1 - .../src/jvmMain/jni/linux-x86/X11KeyboardHandler.cpp | 1 - .../com/github/animeshz/keyboard/JvmKeyboardHandler.kt | 10 +++------- .../src/nativeCommon/linux/X11KeyboardHandler.cpp | 4 ++-- 7 files changed, 10 insertions(+), 18 deletions(-) delete mode 120000 keyboard-kt/src/jvmMain/jni/linux-x86/BaseKeyboardHandler.h delete mode 120000 keyboard-kt/src/jvmMain/jni/linux-x86/JvmKeyboardHandler.cpp delete mode 120000 keyboard-kt/src/jvmMain/jni/linux-x86/X11KeyboardHandler.cpp diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 12887cf..5f43f4c 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -133,9 +133,9 @@ 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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") + Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"), + 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) { @@ -160,7 +160,7 @@ fun KotlinMultiplatformExtension.configureJvm() { "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cmake \$WORK_DIR/project/src/jvmMain/jni/${target.os}-${target.arch} && " + - "cmake --build . --config Release && " + + "cmake --build . --verbose --config Release && " + // optional --verbose, need to find a way "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}" ) diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt index c0f6bf2..ceb6872 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt @@ -7,13 +7,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") include_directories(KeyboardKtx64 PRIVATE $ENV{JNI_HEADERS_DIR}) include_directories(KeyboardKtx64 PRIVATE "../../generated/jni") +include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/linux") include_directories($ENV{X11_HEADERS_DIR}) include_directories($ENV{X11_HEADERS_DIR}/extensions) add_library( KeyboardKtx64 SHARED - BaseKeyboardHandler.h - X11KeyboardHandler.cpp JvmKeyboardHandler.cpp ) diff --git a/keyboard-kt/src/jvmMain/jni/linux-x86/BaseKeyboardHandler.h b/keyboard-kt/src/jvmMain/jni/linux-x86/BaseKeyboardHandler.h deleted file mode 120000 index 1d44062..0000000 --- a/keyboard-kt/src/jvmMain/jni/linux-x86/BaseKeyboardHandler.h +++ /dev/null @@ -1 +0,0 @@ -../linux-x64/BaseKeyboardHandler.h \ No newline at end of file diff --git a/keyboard-kt/src/jvmMain/jni/linux-x86/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/linux-x86/JvmKeyboardHandler.cpp deleted file mode 120000 index 6e61f54..0000000 --- a/keyboard-kt/src/jvmMain/jni/linux-x86/JvmKeyboardHandler.cpp +++ /dev/null @@ -1 +0,0 @@ -../linux-x64/JvmKeyboardHandler.cpp \ No newline at end of file diff --git a/keyboard-kt/src/jvmMain/jni/linux-x86/X11KeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/linux-x86/X11KeyboardHandler.cpp deleted file mode 120000 index 93ca2ee..0000000 --- a/keyboard-kt/src/jvmMain/jni/linux-x86/X11KeyboardHandler.cpp +++ /dev/null @@ -1 +0,0 @@ -../linux-x64/X11KeyboardHandler.cpp \ No newline at end of file diff --git a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt index dcd5a0f..f0ff573 100644 --- a/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt +++ b/keyboard-kt/src/jvmMain/kotlin/com/github/animeshz/keyboard/JvmKeyboardHandler.kt @@ -4,7 +4,6 @@ import com.github.animeshz.keyboard.entity.Key import com.github.animeshz.keyboard.events.KeyEvent import com.github.animeshz.keyboard.events.KeyState import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlin.concurrent.thread @ExperimentalCoroutinesApi @ExperimentalKeyIO @@ -33,12 +32,9 @@ internal object JvmKeyboardHandler : NativeKeyboardHandlerBase() { external override fun isScrollLockOn(): Boolean override fun startReadingEvents() { - thread { - val code = nativeStartReadingEvents() - if (code != 0) { - // Cannot throw, execute will consume it - IllegalStateException("Unable to set native hook. Error code: $code").printStackTrace() - } + val code = nativeStartReadingEvents() + if (code != 0) { + error("Unable to set native hook. Error code: $code") } } diff --git a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp index 30a6bc7..0bb7152 100644 --- a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp @@ -132,13 +132,13 @@ class X11KeyboardHandler : BaseKeyboardHandler { return keyStates[xKeyCode / 8] & (1 << (xKeyCode % 8)); } - int startReadingEvents(std::function callback) { + int startReadingEvents(void (*callback)(int, bool)) { std::thread th(&X11KeyboardHandler::readInThread, this, callback); return 0; } - void readInThread(std::function callback) { + void readInThread(void (*callback)(int, bool)) { Window root = XDefaultRootWindow(display); XIEventMask *xiMask = new XIEventMask; xiMask->deviceid = XIAllMasterDevices; From 6ffc1f46901b549f8588a1a3af1014f3244dba78 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 23 Jan 2021 13:27:57 +0530 Subject: [PATCH 08/26] Unify the docker images for the Node build, fix linux x86 jni build --- build.gradle.kts | 2 +- .../linux-x64/Dockerfile | 11 ++++++++++- .../linux-x86/Dockerfile | 11 ++++++++++- .../windows-x64/Dockerfile | 14 ++++++++++++-- .../windows-x86/Dockerfile | 14 ++++++++++++-- docker/js-build/windows-x64/Dockerfile | 13 ------------- keyboard-kt/build.gradle.kts | 2 +- .../src/jvmMain/jni/linux-x86/CMakeLists.txt | 3 +-- 8 files changed, 47 insertions(+), 23 deletions(-) rename docker/{jvm-build => cross-build}/linux-x64/Dockerfile (63%) rename docker/{jvm-build => cross-build}/linux-x86/Dockerfile (65%) rename docker/{jvm-build => cross-build}/windows-x64/Dockerfile (54%) rename docker/{jvm-build => cross-build}/windows-x86/Dockerfile (54%) delete mode 100644 docker/js-build/windows-x64/Dockerfile diff --git a/build.gradle.kts b/build.gradle.kts index 1ff1294..f886bc0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { allprojects { this.group = "com.github.animeshz" - this.version = "0.2.4" + this.version = "0.2.5" repositories { mavenCentral() diff --git a/docker/jvm-build/linux-x64/Dockerfile b/docker/cross-build/linux-x64/Dockerfile similarity index 63% rename from docker/jvm-build/linux-x64/Dockerfile rename to docker/cross-build/linux-x64/Dockerfile index cd0e187..e4320aa 100644 --- a/docker/jvm-build/linux-x64/Dockerfile +++ b/docker/cross-build/linux-x64/Dockerfile @@ -6,17 +6,26 @@ ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x64 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni ENV X11_HEADERS_DIR=/usr/include/X11/ +ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ apt update && \ apt install --no-install-recommends --yes \ curl \ + python3 \ + nodejs \ + npm \ libx11-dev \ libxi-dev \ libxtst-dev && \ + npm install -g node-gyp && \ 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 + curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h && \ + mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + npm pack node-addon-api@3.1.0 && \ + tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ + rm node-addon-api-3.1.0.tgz WORKDIR ${WORK_DIR} diff --git a/docker/jvm-build/linux-x86/Dockerfile b/docker/cross-build/linux-x86/Dockerfile similarity index 65% rename from docker/jvm-build/linux-x86/Dockerfile rename to docker/cross-build/linux-x86/Dockerfile index 1a184e4..db31a7c 100644 --- a/docker/jvm-build/linux-x86/Dockerfile +++ b/docker/cross-build/linux-x86/Dockerfile @@ -6,18 +6,27 @@ 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/ +ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ dpkg --add-architecture i386 && \ apt update && \ apt install --no-install-recommends --yes \ curl \ + python3 \ + nodejs \ + npm \ libx11-dev:i386 \ libxi-dev:i386 \ libxtst-dev:i386 && \ + npm install -g node-gyp && \ 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 + curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h && \ + mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + npm pack node-addon-api@3.1.0 && \ + tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ + rm node-addon-api-3.1.0.tgz WORKDIR ${WORK_DIR} diff --git a/docker/jvm-build/windows-x64/Dockerfile b/docker/cross-build/windows-x64/Dockerfile similarity index 54% rename from docker/jvm-build/windows-x64/Dockerfile rename to docker/cross-build/windows-x64/Dockerfile index 7f8aeaa..b36f4fb 100644 --- a/docker/jvm-build/windows-x64/Dockerfile +++ b/docker/cross-build/windows-x64/Dockerfile @@ -5,13 +5,23 @@ LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-windows-x64 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni +ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ apt-get update && \ - apt-get install --no-install-recommends --yes curl && \ + apt-get install --no-install-recommends --yes \ + curl \ + python3 \ + nodejs \ + npm && \ + npm install -g node-gyp && \ 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/windows/native/include/jni_md.h' > jni_md.h + curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' > jni_md.h && \ + mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + npm pack node-addon-api@3.1.0 && \ + tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ + rm node-addon-api-3.1.0.tgz WORKDIR ${WORK_DIR} diff --git a/docker/jvm-build/windows-x86/Dockerfile b/docker/cross-build/windows-x86/Dockerfile similarity index 54% rename from docker/jvm-build/windows-x86/Dockerfile rename to docker/cross-build/windows-x86/Dockerfile index cccfe88..ad3736b 100644 --- a/docker/jvm-build/windows-x86/Dockerfile +++ b/docker/cross-build/windows-x86/Dockerfile @@ -5,13 +5,23 @@ LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-windows-x86 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni +ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ apt-get update && \ - apt-get install --no-install-recommends --yes curl && \ + apt-get install --no-install-recommends --yes \ + curl \ + python3 \ + nodejs \ + npm && \ + npm install -g node-gyp && \ 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/windows/native/include/jni_md.h' > jni_md.h + curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' > jni_md.h && \ + mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + npm pack node-addon-api@3.1.0 && \ + tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ + rm node-addon-api-3.1.0.tgz WORKDIR ${WORK_DIR} diff --git a/docker/js-build/windows-x64/Dockerfile b/docker/js-build/windows-x64/Dockerfile deleted file mode 100644 index a5b4166..0000000 --- a/docker/js-build/windows-x64/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM dockcross/windows-shared-x64 - -LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" - -ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:js-build-windows-x64 - -RUN \ - apt-get update && \ - apt-get install --no-install-recommends --yes \ - python3 \ - nodejs \ - npm && \ - npm install -g node-gyp diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 5f43f4c..d26eb17 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -160,7 +160,7 @@ fun KotlinMultiplatformExtension.configureJvm() { "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cmake \$WORK_DIR/project/src/jvmMain/jni/${target.os}-${target.arch} && " + - "cmake --build . --verbose --config Release && " + // optional --verbose, need to find a way + "cmake --build . --config Release && " + // optional --verbose, need to find a way "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}" ) diff --git a/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt index 4fdffee..5fe1bcf 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt @@ -7,13 +7,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") include_directories(KeyboardKtx86 PRIVATE $ENV{JNI_HEADERS_DIR}) include_directories(KeyboardKtx86 PRIVATE "../../generated/jni") +include_directories(KeyboardKtx86 PRIVATE "../../../nativeCommon/linux") include_directories($ENV{X11_HEADERS_DIR}) include_directories($ENV{X11_HEADERS_DIR}/extensions) add_library( KeyboardKtx86 SHARED - BaseKeyboardHandler.h - X11KeyboardHandler.cpp JvmKeyboardHandler.cpp ) From 64216c6773b155edeca4c2531760b21ba100183f Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 23 Jan 2021 14:51:35 +0530 Subject: [PATCH 09/26] Migrate to composite builds! Move publishing logic there. --- composite-build-src/build.gradle.kts | 22 ++++++ .../publishing/PublishingPlugin.kt | 75 +++++++++++++++++++ keyboard-kt/build.gradle.kts | 57 ++------------ settings.gradle.kts | 2 + 4 files changed, 104 insertions(+), 52 deletions(-) create mode 100644 composite-build-src/build.gradle.kts create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt diff --git a/composite-build-src/build.gradle.kts b/composite-build-src/build.gradle.kts new file mode 100644 index 0000000..aceb1e9 --- /dev/null +++ b/composite-build-src/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + `kotlin-dsl` + `java-gradle-plugin` +} + +repositories { + jcenter() +} + +gradlePlugin { + plugins.register("keyboard-mouse-publishing") { + id = "keyboard-mouse-publishing" + implementationClass = "com.github.animeshz.keyboard_mouse.publishing.PublishingPlugin" + } + + plugins.register("class-loader-plugin") { + id = "class-loader-plugin" + implementationClass = "com.example.ClassLoaderPlugin" + } + +// plugins.register() +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt new file mode 100644 index 0000000..010b0bb --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt @@ -0,0 +1,75 @@ +package com.github.animeshz.keyboard_mouse.publishing + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.withType + +open class PublishingConfigurationExtension { + var repository: String? = null + var username: String? = null + var password: String? = null +} + +class PublishingPlugin : Plugin { + override fun apply(target: Project) { + val ext = target.extensions.create("publishingConfig", PublishingConfigurationExtension::class.java) + + target.afterEvaluate { + val repository = ext.repository ?: error("publishingConfig.repository must not be null") + + target.extensions.configure("publishing") { + val projectUrl = "https://github.com/Animeshz/keyboard-mouse-kt" + + repositories { + maven { + setUrl(repository) + credentials { + username = ext.username + password = ext.password + } + } + } + + publications.withType { + pom { + name.set(project.name) + version = project.version as String + description.set("A multiplatform kotlin library for interacting with global keyboard and mouse events.") + url.set(projectUrl) + + licenses { + license { + name.set("MIT License") + url.set("$projectUrl/blob/master/LICENSE") + distribution.set("repo") + } + } + + developers { + developer { + id.set("Animeshz") + name.set("Animesh Sahu") + email.set("animeshsahu19@yahoo.com") + } + } + + scm { + url.set(projectUrl) + connection.set("scm:git:$projectUrl.git") + developerConnection.set("scm:git:git@github.com:Animeshz/keyboard-mouse-kt.git") + } + } + + val publication = this@withType + if (publication.name == "kotlinMultiplatform") { + publication.artifactId = project.name + } else { + publication.artifactId = "${project.name}-${publication.name}" + } + } + } + } + } +} diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index d26eb17..daf3387 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -11,6 +11,7 @@ import kotlin.system.exitProcess plugins { kotlin("multiplatform") id("maven-publish") + id("keyboard-mouse-publishing") id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } @@ -276,58 +277,10 @@ kotlin { explicitApi() } -afterEvaluate { - publishing { - val projectUrl = "https://github.com/Animeshz/keyboard-mouse-kt" - - repositories { - maven { - setUrl("https://api.bintray.com/maven/animeshz/maven/keyboard-kt/;publish=1;override=1") - credentials { - username = System.getenv("BINTRAY_USER") - password = System.getenv("BINTRAY_KEY") - } - } - } - - publications.withType { - pom { - name.set(project.name) - version = project.version as String - description.set("A multiplatform kotlin library for interacting with global keyboard and mouse events.") - url.set(projectUrl) - - licenses { - license { - name.set("MIT License") - url.set("$projectUrl/blob/master/LICENSE") - distribution.set("repo") - } - } - - developers { - developer { - id.set("Animeshz") - name.set("Animesh Sahu") - email.set("animeshsahu19@yahoo.com") - } - } - - scm { - url.set(projectUrl) - connection.set("scm:git:$projectUrl.git") - developerConnection.set("scm:git:git@github.com:Animeshz/keyboard-mouse-kt.git") - } - } - - val publication = this@withType - if (publication.name == "kotlinMultiplatform") { - publication.artifactId = project.name - } else { - publication.artifactId = "${project.name}-${publication.name}" - } - } - } +publishingConfig { + repository = "https://api.bintray.com/maven/animeshz/maven/keyboard-kt/;publish=1;override=1" + username = System.getenv("BINTRAY_USER") + password = System.getenv("BINTRAY_KEY") } tasks.withType { diff --git a/settings.gradle.kts b/settings.gradle.kts index 5f0d181..1c12a5c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,6 +6,8 @@ fun module(path: String) { project(":$name").projectDir = file(path) } +includeBuild("composite-build-src") + module("keyboard-kt") module("integration/keyboard-kt-jdk8") // include("mouse-kt") From 1adfae05264b8dda943f33399176e3124ed4bc6f Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 23 Jan 2021 18:39:24 +0530 Subject: [PATCH 10/26] Unify jni architecture builds. --- composite-build-src/build.gradle.kts | 15 ++++++++++++--- .../publishing/PublishingPlugin.kt | 6 ++++-- keyboard-kt/build.gradle.kts | 13 +++++++------ .../src/jvmMain/jni/linux-x86/CMakeLists.txt | 18 ------------------ .../jni/{linux-x64 => linux}/CMakeLists.txt | 12 +++++++----- .../JvmKeyboardHandler.cpp | 0 .../jvmMain/jni/windows-x64/CMakeLists.txt | 17 ----------------- .../jvmMain/jni/windows-x86/CMakeLists.txt | 17 ----------------- .../jni/windows-x86/JvmKeyboardHandler.cpp | 1 - .../src/jvmMain/jni/windows/CMakeLists.txt | 19 +++++++++++++++++++ .../JvmKeyboardHandler.cpp | 0 .../windows/WindowsKeybaordHandler.cpp | 2 +- 12 files changed, 50 insertions(+), 70 deletions(-) delete mode 100644 keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt rename keyboard-kt/src/jvmMain/jni/{linux-x64 => linux}/CMakeLists.txt (54%) rename keyboard-kt/src/jvmMain/jni/{linux-x64 => linux}/JvmKeyboardHandler.cpp (100%) delete mode 100644 keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt delete mode 100644 keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt delete mode 120000 keyboard-kt/src/jvmMain/jni/windows-x86/JvmKeyboardHandler.cpp create mode 100644 keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt rename keyboard-kt/src/jvmMain/jni/{windows-x64 => windows}/JvmKeyboardHandler.cpp (100%) diff --git a/composite-build-src/build.gradle.kts b/composite-build-src/build.gradle.kts index aceb1e9..8e89046 100644 --- a/composite-build-src/build.gradle.kts +++ b/composite-build-src/build.gradle.kts @@ -7,16 +7,25 @@ repositories { jcenter() } +dependencies { + implementation(kotlin("gradle-plugin")) +} + gradlePlugin { plugins.register("keyboard-mouse-publishing") { id = "keyboard-mouse-publishing" implementationClass = "com.github.animeshz.keyboard_mouse.publishing.PublishingPlugin" } - plugins.register("class-loader-plugin") { - id = "class-loader-plugin" - implementationClass = "com.example.ClassLoaderPlugin" + plugins.register("keyboard-mouse-multiplatform-configuration") { + id = "keyboard-mouse-multiplatform-plugin" + implementationClass = "com.github.animeshz.keyboard_mouse.multiplatform_configuration.MppConfigurationPlugin" } +// plugins.register("keyboard-mouse-multiplatform-configuration") { +// id = "keyboard-mouse-multiplatform-plugin" +// implementationClass = "com.github.animeshz.keyboard_mouse.multiplatform_configuration.MppConfigurationPlugin" +// } + // plugins.register() } diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt index 010b0bb..9a07a4d 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt @@ -4,8 +4,11 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.withType +const val projectUrl = "https://github.com/Animeshz/keyboard-mouse-kt" + open class PublishingConfigurationExtension { var repository: String? = null var username: String? = null @@ -15,13 +18,12 @@ open class PublishingConfigurationExtension { class PublishingPlugin : Plugin { override fun apply(target: Project) { val ext = target.extensions.create("publishingConfig", PublishingConfigurationExtension::class.java) + target.apply(plugin = "maven-publish") target.afterEvaluate { val repository = ext.repository ?: error("publishingConfig.repository must not be null") target.extensions.configure("publishing") { - val projectUrl = "https://github.com/Animeshz/keyboard-mouse-kt" - repositories { maven { setUrl(repository) diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index daf3387..e020792 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -10,7 +10,7 @@ import kotlin.system.exitProcess plugins { kotlin("multiplatform") - id("maven-publish") +// id("keyboard-mouse-multiplatform-configuration") id("keyboard-mouse-publishing") id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } @@ -134,9 +134,9 @@ 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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") + Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86") +// 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) { @@ -160,7 +160,7 @@ fun KotlinMultiplatformExtension.configureJvm() { "mkdir -p \$WORK_DIR/project/build/jni && " + "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + - "cmake \$WORK_DIR/project/src/jvmMain/jni/${target.os}-${target.arch} && " + + "cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/jni/${target.os} && " + "cmake --build . --config Release && " + // optional --verbose, need to find a way "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}" @@ -187,7 +187,8 @@ fun KotlinMultiplatformExtension.configureJvm() { } while (error.startsWith(nonDaemonError)) } - if (exit != 0) throw GradleException(error) + System.err.println(error) + if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.") } } } diff --git a/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt deleted file mode 100644 index 5fe1bcf..0000000 --- a/keyboard-kt/src/jvmMain/jni/linux-x86/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(KeyboardKtx86) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") - -include_directories(KeyboardKtx86 PRIVATE $ENV{JNI_HEADERS_DIR}) -include_directories(KeyboardKtx86 PRIVATE "../../generated/jni") -include_directories(KeyboardKtx86 PRIVATE "../../../nativeCommon/linux") - -include_directories($ENV{X11_HEADERS_DIR}) -include_directories($ENV{X11_HEADERS_DIR}/extensions) - -add_library( - KeyboardKtx86 SHARED - JvmKeyboardHandler.cpp -) diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/linux/CMakeLists.txt similarity index 54% rename from keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt rename to keyboard-kt/src/jvmMain/jni/linux/CMakeLists.txt index ceb6872..cb4664c 100644 --- a/keyboard-kt/src/jvmMain/jni/linux-x64/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/linux/CMakeLists.txt @@ -1,18 +1,20 @@ cmake_minimum_required(VERSION 3.10) -project(KeyboardKtx64) +project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") -include_directories(KeyboardKtx64 PRIVATE $ENV{JNI_HEADERS_DIR}) -include_directories(KeyboardKtx64 PRIVATE "../../generated/jni") -include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/linux") +option(ARCH "architecture") + +include_directories($ENV{JNI_HEADERS_DIR}) +include_directories("../../generated/jni") +include_directories("../../../nativeCommon/linux") include_directories($ENV{X11_HEADERS_DIR}) include_directories($ENV{X11_HEADERS_DIR}/extensions) add_library( - KeyboardKtx64 SHARED + KeyboardKt${ARCH} SHARED JvmKeyboardHandler.cpp ) diff --git a/keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/linux/JvmKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jvmMain/jni/linux-x64/JvmKeyboardHandler.cpp rename to keyboard-kt/src/jvmMain/jni/linux/JvmKeyboardHandler.cpp diff --git a/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt deleted file mode 100644 index d586418..0000000 --- a/keyboard-kt/src/jvmMain/jni/windows-x64/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(KeyboardKtx64) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") - -add_definitions(-D_WIN32_WINNT=0x600) - -include_directories(KeyboardKtx64 PRIVATE $ENV{JNI_HEADERS_DIR}) -include_directories(KeyboardKtx64 PRIVATE "../../generated/jni") -include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/windows") - -add_library( - KeyboardKtx64 SHARED - JvmKeyboardHandler.cpp -) diff --git a/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt deleted file mode 100644 index 122a20f..0000000 --- a/keyboard-kt/src/jvmMain/jni/windows-x86/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(KeyboardKtx86) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") - -add_definitions(-D_WIN32_WINNT=0x600) - -include_directories(KeyboardKtx86 PRIVATE $ENV{JNI_HEADERS_DIR}) -include_directories(KeyboardKtx86 PRIVATE "../../generated/jni") -include_directories(KeyboardKtx64 PRIVATE "../../../nativeCommon/windows") - -add_library( - KeyboardKtx86 SHARED - JvmKeyboardHandler.cpp -) diff --git a/keyboard-kt/src/jvmMain/jni/windows-x86/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/windows-x86/JvmKeyboardHandler.cpp deleted file mode 120000 index 1b69bbd..0000000 --- a/keyboard-kt/src/jvmMain/jni/windows-x86/JvmKeyboardHandler.cpp +++ /dev/null @@ -1 +0,0 @@ -../windows-x64/JvmKeyboardHandler.cpp \ No newline at end of file diff --git a/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt new file mode 100644 index 0000000..40f3d68 --- /dev/null +++ b/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.10) +project(KeyboardKt) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") + +option(ARCH "architecture") + +add_definitions(-D_WIN32_WINNT=0x600) + +include_directories($ENV{JNI_HEADERS_DIR}) +include_directories("../../generated/jni") +include_directories("../../../nativeCommon/windows") + +add_library( + KeyboardKt${ARCH} SHARED + JvmKeyboardHandler.cpp +) diff --git a/keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jvmMain/jni/windows-x64/JvmKeyboardHandler.cpp rename to keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp index bd5156c..bbc9e85 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp @@ -162,4 +162,4 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { return CallNextHookEx(NULL, nCode, wParam, lParam); } -}; \ No newline at end of file +}; From d537aba4899a8863604a79ace1c34606360c10c2 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 23 Jan 2021 21:35:46 +0530 Subject: [PATCH 11/26] Migrate the Jni configuration to the composite-build-src and cleanup the build.gradle.kts --- build.gradle.kts | 12 + composite-build-src/build.gradle.kts | 6 +- .../configuration/ConfigurationPlugin.kt | 57 ++++ .../configuration/Configurations.kt | 31 ++ .../configuration/JniCompilationTask.kt | 82 +++++ .../configuration/JniHeaderGenerationTask.kt | 96 ++++++ .../publishing/PublishingPlugin.kt | 2 +- keyboard-kt/build.gradle.kts | 288 ++++-------------- 8 files changed, 343 insertions(+), 231 deletions(-) create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt diff --git a/build.gradle.kts b/build.gradle.kts index f886bc0..d1a1b62 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,3 +11,15 @@ allprojects { jcenter() } } + +subprojects { + subprojects { + // Address https://github.com/gradle/gradle/issues/4823: Force parent project evaluation before sub-project evaluation for Kotlin build scripts + if (gradle.startParameter.isConfigureOnDemand + && buildscript.sourceFile?.extension?.toLowerCase() == "kts" + && parent != rootProject) { + generateSequence(parent) { project -> project.parent.takeIf { it != rootProject } } + .forEach { evaluationDependsOn(it.path) } + } + } +} diff --git a/composite-build-src/build.gradle.kts b/composite-build-src/build.gradle.kts index 8e89046..b1a6036 100644 --- a/composite-build-src/build.gradle.kts +++ b/composite-build-src/build.gradle.kts @@ -17,9 +17,9 @@ gradlePlugin { implementationClass = "com.github.animeshz.keyboard_mouse.publishing.PublishingPlugin" } - plugins.register("keyboard-mouse-multiplatform-configuration") { - id = "keyboard-mouse-multiplatform-plugin" - implementationClass = "com.github.animeshz.keyboard_mouse.multiplatform_configuration.MppConfigurationPlugin" + plugins.register("keyboard-mouse-configuration") { + id = "keyboard-mouse-configuration" + implementationClass = "com.github.animeshz.keyboard_mouse.configuration.ConfigurationPlugin" } // plugins.register("keyboard-mouse-multiplatform-configuration") { diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt new file mode 100644 index 0000000..e156b54 --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt @@ -0,0 +1,57 @@ +package com.github.animeshz.keyboard_mouse.configuration + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.creating +import org.gradle.kotlin.dsl.getValue +import org.gradle.kotlin.dsl.register +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +class ConfigurationPlugin : Plugin { + override fun apply(target: Project) { + target.apply(plugin = "org.jetbrains.kotlin.multiplatform") + val ext = target.extensions.create("configureJni", JniConfiguration::class.java) + + setup(target, ext) + } + + private fun setup(project: Project, extension: JniConfiguration) { + project.afterEvaluate { + with(extension.headers) { + if (inputDir.isEmpty() || outputDir.isEmpty()) return@afterEvaluate + } + + val compileJniAll by project.tasks.creating { group = "jni" } + project.tasks.getByName("jvmProcessResources") { dependsOn(compileJniAll) } + + val headersTask = project.tasks + .register("generateJniHeaders", extension.headers).get() + + with(extension.compilation) { + if (baseInputPath.isEmpty() || outputDir.isEmpty() || targets.isEmpty()) return@afterEvaluate + } + + extension.compilation.targets.forEach { target -> + project.tasks + .register("compileJni${target.os.capitalize()}${target.arch.capitalize()}", target) + .also { compileJniAll.dependsOn(it.get()) } + .configure { + dockerImage = target.dockerImage + + inputs.dir(extension.compilation.baseInputPath / target.os) + outputs.dir(extension.compilation.outputDir) + + dependsOn(headersTask) + } + } + + project.configure { + sourceSets.getByName("jvmMain").resources.srcDir(extension.compilation.outputDir) + } + } + } + + private operator fun String.div(other: String) = "$this/$other" +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt new file mode 100644 index 0000000..e98ac9b --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt @@ -0,0 +1,31 @@ +package com.github.animeshz.keyboard_mouse.configuration + +class Target( + val os: String, + val arch: String, + val dockerImage: String +) + +open class JniConfiguration { + val headers: JniHeaderConfiguration = JniHeaderConfiguration() + val compilation: JniCompilationConfiguration = JniCompilationConfiguration() + + fun headers(configuration: JniHeaderConfiguration.() -> Unit) { + headers.apply(configuration) + } + + fun compilation(configuration: JniCompilationConfiguration.() -> Unit) { + compilation.apply(configuration) + } +} + +open class JniHeaderConfiguration { + var inputDir: String = "" + var outputDir: String = "" +} + +open class JniCompilationConfiguration { + var baseInputPath: String = "" + var outputDir: String = "" + var targets: List = emptyList() +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt new file mode 100644 index 0000000..06df8cc --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt @@ -0,0 +1,82 @@ +package com.github.animeshz.keyboard_mouse.configuration + +import groovy.cli.Option +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import java.io.ByteArrayOutputStream +import javax.inject.Inject + +/** + * For building shared libraries out of C/C++ sources + */ +open class JniCompilationTask @Inject constructor( + private val target: Target +) : DefaultTask() { + init { + group = "jni" + } + + @get:Input + @set:Option(shortName = "verbose", description = "Configures the URL to be verified.") + var isVerbose: Boolean = false + + var dockerImage: String = "" + + @TaskAction + fun run() { + val tmpVar = project.file(".").absolutePath + 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: () -> Pair = { + ByteArrayOutputStream().use { + project.exec { + commandLine( + "docker", + "run", + "--rm", + "-v", + "$path:/work/project", + target.dockerImage, + "bash", + "-c", + "mkdir -p \$WORK_DIR/project/build/jni && " + + "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + + "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + + "cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/jni/${target.os} && " + + "cmake --build . ${if (isVerbose) "--verbose " else ""}--config Release && " + + "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}" + ) + + isIgnoreExitValue = true + standardOutput = System.out + errorOutput = it + }.exitValue to it.toString() + } + } + 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) && error.startsWith(nonDaemonError)) { + project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue() + + do { + Thread.sleep(500) + val result = work() + exit = result.first + error = result.second + } while (error.startsWith(nonDaemonError)) + } + + System.err.println(error) + if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.") + } +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt new file mode 100644 index 0000000..f74a7d6 --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt @@ -0,0 +1,96 @@ +package com.github.animeshz.keyboard_mouse.configuration + +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.jvm.Jvm +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import java.io.ByteArrayOutputStream +import javax.inject.Inject +import kotlin.system.exitProcess + +open class JniHeaderGenerationTask @Inject constructor( + private val configuration: JniHeaderConfiguration +) : DefaultTask() { + val metaRegex = """public \w*\s*class (.+)\.(\w+) (?:implements|extends).*\{\R([^\}]*)\}""".toRegex() + val methodRegex = """.*\bnative\b.*""".toRegex() + + init { + group = "jni" + + inputs.dir(configuration.inputDir) + outputs.dir(configuration.outputDir) + + dependsOn(project.tasks.getByName("compileKotlinJvm")) + } + + private fun check() { + println("Checking docker installation") + + val exit = project.exec { + commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) + isIgnoreExitValue = true + }.exitValue + if (exit != 0) { + println("Please install docker before running this task") + exitProcess(1) + } + } + + @TaskAction + fun run() { + check() + + val javaHome = Jvm.current().javaHome + val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found") + val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found") + val buildDir = project.file("build/classes/kotlin/jvm/main") + val tmpDir = project.file("build/tmp/jvmJni").apply { mkdirs() } + + buildDir.walkTopDown() + .filter { "META" !in it.absolutePath } + .forEach { file -> + if (!file.isFile) return@forEach + + val output = ByteArrayOutputStream().use { + project.exec { + commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath) + standardOutput = it + }.assertNormalExitValue() + it.toString() + } + + val (packageName, className, methodInfo) = metaRegex.find(output)?.destructured ?: return@forEach + val nativeMethods = methodRegex.findAll(methodInfo).mapNotNull { it.groups }.flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList() + if (nativeMethods.isEmpty()) return@forEach + + val source = buildString { + appendln("package $packageName;") + appendln("public class $className {") + for (method in nativeMethods) { + if ("()" in method) appendln(method) + else { + val updatedMethod = StringBuilder(method).apply { + var count = 0 + var i = 0 + while (i < length) { + if (this[i] == ',' || this[i] == ')') insert(i, " arg${count++}".also { i += it.length + 1 }) + else i++ + } + } + appendln(updatedMethod) + } + } + appendln("}") + } + + val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() } + outputFile.writeText(source) + + project.exec { + commandLine(javac, "-h", project.file(configuration.outputDir).absolutePath, outputFile.absolutePath) + }.assertNormalExitValue() + } + } +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt index 9a07a4d..f91377b 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt @@ -9,7 +9,7 @@ import org.gradle.kotlin.dsl.withType const val projectUrl = "https://github.com/Animeshz/keyboard-mouse-kt" -open class PublishingConfigurationExtension { +open class PublishingConfigurationExtension { var repository: String? = null var username: String? = null var password: String? = null diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index e020792..44b2d00 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -1,250 +1,30 @@ @file:Suppress("UNUSED_VARIABLE") -import org.apache.tools.ant.taskdefs.condition.Os +import com.github.animeshz.keyboard_mouse.configuration.Target import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.internal.jvm.Jvm -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import kotlin.system.exitProcess +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile plugins { kotlin("multiplatform") -// id("keyboard-mouse-multiplatform-configuration") + id("keyboard-mouse-configuration") id("keyboard-mouse-publishing") id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } -val mainSourceSets = mutableListOf() -val testSourceSets = mutableListOf() - -fun KotlinMultiplatformExtension.configureJvm() { +kotlin { jvm() - tasks.withType { - kotlinOptions.jvmTarget = "1.8" - } - tasks.getByName("jvmTest") { - useJUnitPlatform() - } - - val jvmMain by sourceSets.getting - val jvmTest by sourceSets.getting { - dependsOn(jvmMain) - dependencies { - implementation(kotlin("test-junit5")) - runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") - implementation("io.kotest:kotest-assertions-core:4.3.2") - } - } - mainSourceSets.add(jvmMain) - testSourceSets.add(jvmTest) - - // JNI-C++ configuration - val jniHeaderDirectory = file("src/jvmMain/generated/jni").apply { mkdirs() } - jvmMain.resources.srcDir("build/jni") - - val generateJniHeaders by tasks.creating { - group = "build" - dependsOn(tasks.getByName("compileKotlinJvm")) - - // For caching - inputs.dir("src/jvmMain/kotlin") - outputs.dir("src/jvmMain/generated/jni") - - doLast { - val javaHome = Jvm.current().javaHome - val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found") - val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found") - val buildDir = file("build/classes/kotlin/jvm/main") - val tmpDir = file("build/tmp/jvmJni").apply { mkdirs() } - - buildDir.walkTopDown() - .filter { "META" !in it.absolutePath } - .forEach { file -> - if (!file.isFile) return@forEach - - val output = ByteArrayOutputStream().use { - project.exec { - commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath) - standardOutput = it - }.assertNormalExitValue() - it.toString() - } - - val (packageName, className, methodInfo) = - """public \w*\s*class (.+)\.(\w+) (?:implements|extends).*\{\R([^\}]*)\}""".toRegex().find(output)?.destructured ?: return@forEach - val nativeMethods = - """.*\bnative\b.*""".toRegex().findAll(methodInfo).mapNotNull { it.groups }.flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList() - if (nativeMethods.isEmpty()) return@forEach - - val source = buildString { - appendln("package $packageName;") - appendln("public class $className {") - for (method in nativeMethods) { - if ("()" in method) appendln(method) - else { - val updatedMethod = StringBuilder(method).apply { - var count = 0 - var i = 0 - while (i < length) { - if (this[i] == ',' || this[i] == ')') insert(i, " arg${count++}".also { i += it.length + 1 }) - else i++ - } - } - appendln(updatedMethod) - } - } - appendln("}") - } - val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() } - outputFile.writeText(source) - - project.exec { - commandLine(javac, "-h", jniHeaderDirectory.absolutePath, outputFile.absolutePath) - }.assertNormalExitValue() - } - } - } - - // For building shared libraries out of C/C++ sources - val compileJni by tasks.creating { - group = "build" - dependsOn(generateJniHeaders) - tasks.getByName("jvmProcessResources").dependsOn(this) - - // For caching - inputs.dir("src/jvmMain/jni") - outputs.dir("build/jni") - - doFirst { - println("Checking docker installation") - - val exit = project.exec { - commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) - isIgnoreExitValue = true - }.exitValue - if (exit != 0) { - println("Please install docker before running this task") - exitProcess(1) - } - } - - doLast { - class Target(val os: String, val arch: String, val dockerImage: String) - - 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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") - ) - - for (target in targets) { - // Integrate with CMake - val tmpVar = file(".").absolutePath - 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: () -> Pair = { - ByteArrayOutputStream().use { - project.exec { - commandLine( - "docker", - "run", - "--rm", - "-v", - "$path:/work/project", - target.dockerImage, - "bash", - "-c", - "mkdir -p \$WORK_DIR/project/build/jni && " + - "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + - "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + - "cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/jni/${target.os} && " + - "cmake --build . --config Release && " + // optional --verbose, need to find a way - "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}" - ) - - isIgnoreExitValue = true - standardOutput = System.out - errorOutput = it - }.exitValue to it.toString() - } - } - 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) && error.startsWith(nonDaemonError)) { - project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue() - - do { - Thread.sleep(500) - val result = work() - exit = result.first - error = result.second - } while (error.startsWith(nonDaemonError)) - } - - System.err.println(error) - if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.") - } - } - } -} - -fun KotlinMultiplatformExtension.configureJs() { js { useCommonJs() nodejs() } - - val jsMain by sourceSets.getting { - dependencies { - implementation(devNpm("node-addon-api", "*")) - } - } - val jsTest by sourceSets.getting { - dependsOn(jsMain) - dependencies { - implementation(kotlin("test-js")) - } - } - - mainSourceSets.add(jsMain) - testSourceSets.add(jsTest) -} - -fun KotlinMultiplatformExtension.configureLinux() { linuxX64 { val main by compilations.getting main.cinterops.create("device") { defFile("src/linuxX64Main/cinterop/device.def") } main.cinterops.create("x11") { defFile("src/linuxX64Main/cinterop/x11.def") } } - - val linuxX64Main by sourceSets.getting - val linuxX64Test by sourceSets.getting { dependsOn(linuxX64Main) } - mainSourceSets.add(linuxX64Main) - testSourceSets.add(linuxX64Test) -} - -fun KotlinMultiplatformExtension.configureMingw() { mingwX64() - val mingwX64Main by sourceSets.getting - val mingwX64Test by sourceSets.getting { dependsOn(mingwX64Main) } - mainSourceSets.add(mingwX64Main) - testSourceSets.add(mingwX64Test) -} - -kotlin { - configureJvm() - configureJs() - configureLinux() - configureMingw() - sourceSets { val commonMain by getting { dependencies { @@ -263,11 +43,40 @@ kotlin { } } - configure(mainSourceSets) { - dependsOn(commonMain) + val jvmMain by sourceSets.getting { dependsOn(commonMain) } + val jvmTest by sourceSets.getting { + dependsOn(commonTest) + dependsOn(jvmMain) + dependencies { + implementation(kotlin("test-junit5")) + runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") + implementation("io.kotest:kotest-assertions-core:4.3.2") + } } - configure(testSourceSets) { + + val jsMain by sourceSets.getting { + dependencies { + implementation(devNpm("node-addon-api", "*")) + } + } + val jsTest by sourceSets.getting { + dependsOn(commonTest) + dependsOn(jsMain) + dependencies { + implementation(kotlin("test-js")) + } + } + + val linuxX64Main by sourceSets.getting { dependsOn(commonMain) } + val linuxX64Test by sourceSets.getting { + dependsOn(commonTest) + dependsOn(linuxX64Main) + } + + val mingwX64Main by sourceSets.getting { dependsOn(commonMain) } + val mingwX64Test by sourceSets.getting { dependsOn(commonTest) + dependsOn(mingwX64Main) } all { @@ -284,6 +93,24 @@ publishingConfig { password = System.getenv("BINTRAY_KEY") } +configureJni { + headers { + inputDir = "src/jvmMain/kotlin" + outputDir = "src/jvmMain/generated/jni" + } + compilation { + baseInputPath = "src/jvmMain/jni" + outputDir = "build/jni" + + 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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") + ) + } +} + tasks.withType { testLogging { showStackTraces = true @@ -292,6 +119,13 @@ tasks.withType { } } +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} +tasks.getByName("jvmTest") { + useJUnitPlatform() +} + ktlint { verbose.set(true) outputToConsole.set(true) From 8df7eba1c6b898db56d99fe5a2fc561886120c3e Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sun, 24 Jan 2021 09:26:56 +0530 Subject: [PATCH 12/26] C++ code cleanup. --- .../configuration/JniCompilationTask.kt | 16 ++++++++++++ .../configuration/JniHeaderGenerationTask.kt | 15 ----------- .../src/jvmMain/jni/windows/CMakeLists.txt | 2 +- .../jni/windows/JvmKeyboardHandler.cpp | 4 --- .../src/nativeCommon/BaseKeyboardHandler.h | 4 +-- .../windows/WindowsKeybaordHandler.cpp | 25 ++++++++----------- 6 files changed, 29 insertions(+), 37 deletions(-) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt index 06df8cc..b4452fa 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt @@ -8,6 +8,7 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.ByteArrayOutputStream import javax.inject.Inject +import kotlin.system.exitProcess /** * For building shared libraries out of C/C++ sources @@ -25,8 +26,23 @@ open class JniCompilationTask @Inject constructor( var dockerImage: String = "" + private fun check() { + println("Checking docker installation") + + val exit = project.exec { + commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) + isIgnoreExitValue = true + }.exitValue + if (exit != 0) { + println("Please install docker before running this task") + exitProcess(1) + } + } + @TaskAction fun run() { + check() + val tmpVar = project.file(".").absolutePath val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) { "/run/desktop/mnt/host/${tmpVar[0].toLowerCase()}${tmpVar.substring(2 until tmpVar.length).replace('\\', '/')}" diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt index f74a7d6..504aa76 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt @@ -25,23 +25,8 @@ open class JniHeaderGenerationTask @Inject constructor( dependsOn(project.tasks.getByName("compileKotlinJvm")) } - private fun check() { - println("Checking docker installation") - - val exit = project.exec { - commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) - isIgnoreExitValue = true - }.exitValue - if (exit != 0) { - println("Please install docker before running this task") - exitProcess(1) - } - } - @TaskAction fun run() { - check() - val javaHome = Jvm.current().javaHome val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found") val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found") diff --git a/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt index 40f3d68..2f3f5ec 100644 --- a/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") diff --git a/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp index 64a573e..824d338 100644 --- a/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp @@ -1,7 +1,3 @@ -#include - -#include - #include "WindowsKeybaordHandler.cpp" #include "com_github_animeshz_keyboard_JvmKeyboardHandler.h" diff --git a/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h index c9202e8..77e0206 100644 --- a/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h +++ b/keyboard-kt/src/nativeCommon/BaseKeyboardHandler.h @@ -1,5 +1,3 @@ -#include - class BaseKeyboardHandler { public: virtual bool isCapsLockOn() = 0; @@ -15,4 +13,4 @@ class BaseKeyboardHandler { virtual int startReadingEvents(void (*callback)(int, bool)) = 0; virtual void stopReadingEvents() = 0; -}; \ No newline at end of file +}; diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp index bbc9e85..dd20934 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp @@ -1,18 +1,15 @@ #include #include -#include -#include -#include - #include "../BaseKeyboardHandler.h" #define FAKE_ALT LLKHF_INJECTED | 0x20 +BaseKeyboardHandler *instance; +void (*callback)(int, bool); + class WindowsKeyboardHandler : BaseKeyboardHandler { private: - inline static WindowsKeyboardHandler *instance = NULL; - inline static void (*callback)(int, bool) = NULL; DWORD threadId = 0; CRITICAL_SECTION cs; CONDITION_VARIABLE cv; @@ -21,8 +18,8 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { public: static BaseKeyboardHandler *getInstance() { - if (!instance) instance = new WindowsKeyboardHandler(); - return instance; + if (!::instance) ::instance = new WindowsKeyboardHandler(); + return ::instance; } ~WindowsKeyboardHandler() { stopReadingEvents(); } @@ -73,11 +70,11 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { int startReadingEvents(void (*callback)(int, bool)) { int ret = 0; - WindowsKeyboardHandler::callback = callback; + ::callback = callback; InitializeCriticalSection (&cs); InitializeConditionVariable (&cv); - + EnterCriticalSection(&cs); CreateThread(NULL, 0, readInThread, (LPVOID)&ret, 0, &threadId); SleepConditionVariableCS(&cv, &cs, INFINITE); @@ -96,9 +93,9 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { data = NULL; // Immediately remove pointer to the stack variable auto handler = (WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance(); - EnterCriticalSection(&handler->cs); - WakeConditionVariable(&handler->cv); - LeaveCriticalSection(&handler->cs); +// EnterCriticalSection(&handler->cs); +// WakeConditionVariable(&handler->cv); +// LeaveCriticalSection(&handler->cs); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { @@ -157,7 +154,7 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } } - WindowsKeyboardHandler::callback(scanCode, isPressed); + ::callback(scanCode, isPressed); } return CallNextHookEx(NULL, nCode, wParam, lParam); From 9858ae944ffe800156826dee0d2c978fcba8f314 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sun, 24 Jan 2021 20:42:51 +0530 Subject: [PATCH 13/26] Update docs and use hack to not use the gnu's c++ stdlib ~2MB that is not shipped with windows. --- .../configuration/ConfigurationPlugin.kt | 4 ++-- .../configuration/Configurations.kt | 2 +- .../configuration/JniCompilationTask.kt | 5 ++--- docs/docs/contributing.md | 2 +- keyboard-kt/build.gradle.kts | 2 +- .../windows/WindowsKeybaordHandler.cpp | 15 +++++++++------ 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt index e156b54..c03f9be 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt @@ -30,7 +30,7 @@ class ConfigurationPlugin : Plugin { .register("generateJniHeaders", extension.headers).get() with(extension.compilation) { - if (baseInputPath.isEmpty() || outputDir.isEmpty() || targets.isEmpty()) return@afterEvaluate + if (baseInputPaths.isEmpty() || outputDir.isEmpty() || targets.isEmpty()) return@afterEvaluate } extension.compilation.targets.forEach { target -> @@ -40,7 +40,7 @@ class ConfigurationPlugin : Plugin { .configure { dockerImage = target.dockerImage - inputs.dir(extension.compilation.baseInputPath / target.os) + for (path in extension.compilation.baseInputPaths) inputs.dir(path / target.os) outputs.dir(extension.compilation.outputDir) dependsOn(headersTask) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt index e98ac9b..8360285 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt @@ -25,7 +25,7 @@ open class JniHeaderConfiguration { } open class JniCompilationConfiguration { - var baseInputPath: String = "" + var baseInputPaths: List = emptyList() var outputDir: String = "" var targets: List = emptyList() } diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt index b4452fa..17eab1d 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt @@ -1,11 +1,11 @@ package com.github.animeshz.keyboard_mouse.configuration -import groovy.cli.Option import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option import java.io.ByteArrayOutputStream import javax.inject.Inject import kotlin.system.exitProcess @@ -20,8 +20,7 @@ open class JniCompilationTask @Inject constructor( group = "jni" } - @get:Input - @set:Option(shortName = "verbose", description = "Configures the URL to be verified.") + @Option(option = "verbose", description = "Configures the URL to be verified.") var isVerbose: Boolean = false var dockerImage: String = "" diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index 41b8392..e5e8b36 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -51,6 +51,6 @@ To build and publish to mavenLocal: The only requirement is to install Docker when building for JVM due to cross-compilation requirement of JNI native libs to be able to pack the full Jar from any platform that is supported cross-platform. -**Note: If you are using Windows, please enable Symbolic Links at the time of installation of git and use the [following instructions](https://stackoverflow.com/a/40914277/11377112) in order to fetch the symbolic links from the git which is present in jni folders.** +If you ever get clock gets skewed (and Makefile get modified in the future) at the time of compilation of C++ sources, please restart the docker from the system tray. [1]: https://github.com/Animeshz/keyboard-mouse-kt/issues/1 diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 44b2d00..97c9c25 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -99,7 +99,7 @@ configureJni { outputDir = "src/jvmMain/generated/jni" } compilation { - baseInputPath = "src/jvmMain/jni" + baseInputPaths = listOf("src/jvmMain/jni", "src/nativeCommon") outputDir = "build/jni" targets = listOf( diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp index dd20934..bd7a8b6 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -18,12 +19,14 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { public: static BaseKeyboardHandler *getInstance() { - if (!::instance) ::instance = new WindowsKeyboardHandler(); + if (!::instance) { + // Hack to avoid `libstdc++-6.dll` a ~2MB GNU's stdlib that is not bundled in windows. + ::instance = (WindowsKeyboardHandler *) malloc(sizeof(WindowsKeyboardHandler)); + new(::instance) WindowsKeyboardHandler; + } return ::instance; } - ~WindowsKeyboardHandler() { stopReadingEvents(); } - bool isCapsLockOn() { return GetKeyState(0x14) & 1; } bool isNumLockOn() { return GetKeyState(0x90) & 1; } @@ -93,9 +96,9 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { data = NULL; // Immediately remove pointer to the stack variable auto handler = (WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance(); -// EnterCriticalSection(&handler->cs); -// WakeConditionVariable(&handler->cv); -// LeaveCriticalSection(&handler->cs); + EnterCriticalSection(&handler->cs); + WakeConditionVariable(&handler->cv); + LeaveCriticalSection(&handler->cs); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { From d2390c5c31107ee104eee256cfdea377c11ba285 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Tue, 26 Jan 2021 08:40:19 +0530 Subject: [PATCH 14/26] Write node-js binding for Windows --- composite-build-src/build.gradle.kts | 13 +-- .../Configurations.kt | 2 +- .../JniCompilationTask.kt | 2 +- .../JniHeaderGenerationTask.kt | 2 +- .../NativeCompilationPlugin.kt} | 4 +- docker/cross-build/windows-x64/Dockerfile | 10 ++- keyboard-kt/build.gradle.kts | 12 +-- keyboard-kt/src/jsMain/cpp/linux/binding.gyp | 19 ++++ .../cpp/windows-x64/JsKeyboardHandler.cpp | 2 - .../src/jsMain/cpp/windows-x64/binding.gyp | 8 -- .../jsMain/cpp/windows/JsKeyboardHandler.cpp | 89 +++++++++++++++++++ .../src/jsMain/cpp/windows/binding.gyp | 20 +++++ .../animeshz/keyboard/JsKeyboardHandler.kt | 15 ++-- .../github/animeshz/keyboard/NativeUtils.kt | 33 ++++--- .../jvmMain/{jni => cpp}/linux/CMakeLists.txt | 0 .../{jni => cpp}/linux/JvmKeyboardHandler.cpp | 0 .../{jni => cpp}/windows/CMakeLists.txt | 0 .../windows/JvmKeyboardHandler.cpp | 0 ...Handler.cpp => WindowsKeyboardHandler.cpp} | 0 19 files changed, 168 insertions(+), 63 deletions(-) rename composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/{configuration => native_compile}/Configurations.kt (93%) rename composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/{configuration => native_compile}/JniCompilationTask.kt (98%) rename composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/{configuration => native_compile}/JniHeaderGenerationTask.kt (98%) rename composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/{configuration/ConfigurationPlugin.kt => native_compile/NativeCompilationPlugin.kt} (95%) create mode 100644 keyboard-kt/src/jsMain/cpp/linux/binding.gyp delete mode 100644 keyboard-kt/src/jsMain/cpp/windows-x64/JsKeyboardHandler.cpp delete mode 100644 keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp create mode 100644 keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp create mode 100644 keyboard-kt/src/jsMain/cpp/windows/binding.gyp rename keyboard-kt/src/jvmMain/{jni => cpp}/linux/CMakeLists.txt (100%) rename keyboard-kt/src/jvmMain/{jni => cpp}/linux/JvmKeyboardHandler.cpp (100%) rename keyboard-kt/src/jvmMain/{jni => cpp}/windows/CMakeLists.txt (100%) rename keyboard-kt/src/jvmMain/{jni => cpp}/windows/JvmKeyboardHandler.cpp (100%) rename keyboard-kt/src/nativeCommon/windows/{WindowsKeybaordHandler.cpp => WindowsKeyboardHandler.cpp} (100%) diff --git a/composite-build-src/build.gradle.kts b/composite-build-src/build.gradle.kts index b1a6036..a311a9b 100644 --- a/composite-build-src/build.gradle.kts +++ b/composite-build-src/build.gradle.kts @@ -17,15 +17,8 @@ gradlePlugin { implementationClass = "com.github.animeshz.keyboard_mouse.publishing.PublishingPlugin" } - plugins.register("keyboard-mouse-configuration") { - id = "keyboard-mouse-configuration" - implementationClass = "com.github.animeshz.keyboard_mouse.configuration.ConfigurationPlugin" + plugins.register("keyboard-mouse-native-compile") { + id = "keyboard-mouse-native-compile" + implementationClass = "com.github.animeshz.keyboard_mouse.native_compile.NativeCompilationPlugin" } - -// plugins.register("keyboard-mouse-multiplatform-configuration") { -// id = "keyboard-mouse-multiplatform-plugin" -// implementationClass = "com.github.animeshz.keyboard_mouse.multiplatform_configuration.MppConfigurationPlugin" -// } - -// plugins.register() } diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt similarity index 93% rename from composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt rename to composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt index 8360285..88d1f9f 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/Configurations.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt @@ -1,4 +1,4 @@ -package com.github.animeshz.keyboard_mouse.configuration +package com.github.animeshz.keyboard_mouse.native_compile class Target( val os: String, diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt similarity index 98% rename from composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt rename to composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt index 17eab1d..0723c35 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt @@ -1,4 +1,4 @@ -package com.github.animeshz.keyboard_mouse.configuration +package com.github.animeshz.keyboard_mouse.native_compile import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt similarity index 98% rename from composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt rename to composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt index 504aa76..550909d 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/JniHeaderGenerationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt @@ -1,4 +1,4 @@ -package com.github.animeshz.keyboard_mouse.configuration +package com.github.animeshz.keyboard_mouse.native_compile import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt similarity index 95% rename from composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt rename to composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt index c03f9be..8ffd206 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/configuration/ConfigurationPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt @@ -1,4 +1,4 @@ -package com.github.animeshz.keyboard_mouse.configuration +package com.github.animeshz.keyboard_mouse.native_compile import org.gradle.api.Plugin import org.gradle.api.Project @@ -9,7 +9,7 @@ import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.register import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -class ConfigurationPlugin : Plugin { +class NativeCompilationPlugin : Plugin { override fun apply(target: Project) { target.apply(plugin = "org.jetbrains.kotlin.multiplatform") val ext = target.extensions.create("configureJni", JniConfiguration::class.java) diff --git a/docker/cross-build/windows-x64/Dockerfile b/docker/cross-build/windows-x64/Dockerfile index b36f4fb..cd59b9f 100644 --- a/docker/cross-build/windows-x64/Dockerfile +++ b/docker/cross-build/windows-x64/Dockerfile @@ -2,24 +2,26 @@ FROM dockcross/windows-shared-x64 LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" -ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-windows-x64 +ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-windows-x64 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni -ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api +ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}/support-files/headers/node-addon-api RUN \ apt-get update && \ + curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ + apt-get install -f && \ apt-get install --no-install-recommends --yes \ curl \ python3 \ - nodejs \ - npm && \ + nodejs && \ npm install -g node-gyp && \ 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/windows/native/include/jni_md.h' > jni_md.h && \ mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + cd $NODE_ADDON_API_HEADERS_DIR && \ npm pack node-addon-api@3.1.0 && \ tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ rm node-addon-api-3.1.0.tgz diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 97c9c25..5bd21de 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -1,12 +1,12 @@ @file:Suppress("UNUSED_VARIABLE") -import com.github.animeshz.keyboard_mouse.configuration.Target +import com.github.animeshz.keyboard_mouse.native_compile.Target import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile plugins { kotlin("multiplatform") - id("keyboard-mouse-configuration") + id("keyboard-mouse-native-compile") id("keyboard-mouse-publishing") id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } @@ -54,11 +54,7 @@ kotlin { } } - val jsMain by sourceSets.getting { - dependencies { - implementation(devNpm("node-addon-api", "*")) - } - } + val jsMain by sourceSets.getting { dependsOn(commonMain) } val jsTest by sourceSets.getting { dependsOn(commonTest) dependsOn(jsMain) @@ -99,7 +95,7 @@ configureJni { outputDir = "src/jvmMain/generated/jni" } compilation { - baseInputPaths = listOf("src/jvmMain/jni", "src/nativeCommon") + baseInputPaths = listOf("src/jvmMain/cpp", "src/nativeCommon") outputDir = "build/jni" targets = listOf( diff --git a/keyboard-kt/src/jsMain/cpp/linux/binding.gyp b/keyboard-kt/src/jsMain/cpp/linux/binding.gyp new file mode 100644 index 0000000..603b0cf --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/linux/binding.gyp @@ -0,0 +1,19 @@ +{ + "variables": { + "ARCH%": "X64" + }, + "targets": [ + { + "target_name": "KeyboardKtWindows<(ARCH)", + "sources": [ "JsKeyboardHandler.cpp" ], + "cflags": [ "-s -D_WIN32_WINNT=0x600" ], + "cflags_cc": [ "-std=c++11 -s -D_WIN32_WINNT=0x600" ], + + "include_dirs": [ + " - diff --git a/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp b/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp deleted file mode 100644 index 4105d39..0000000 --- a/keyboard-kt/src/jsMain/cpp/windows-x64/binding.gyp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "targets": [ - { - "target_name": "KeyboardKtWindowsX64", - "sources": [ "JsKeyboardHandler.cpp" ] - } - ] -} diff --git a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp new file mode 100644 index 0000000..d44ed10 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp @@ -0,0 +1,89 @@ +#include + +#include "WindowsKeyboardHandler.cpp" + +typedef struct EventData { + int scanCode; + bool isPressed; +} EventData; + +void EmitEventToJs(Napi::Env env, Napi::Function callback, std::nullptr_t* context, EventData* data); +using TSFN = Napi::TypedThreadSafeFunction; + +TSFN ts_callback; + +// No checks because these are part of internal API. + +void Send(const Napi::CallbackInfo& info) { + int scanCode = info[0].As().Int32Value(); + bool isPressed = info[0].As().Value(); + + WindowsKeyboardHandler::getInstance()->sendEvent(scanCode, isPressed); +} + +Napi::Value IsPressed(const Napi::CallbackInfo& info) { + int scanCode = info[0].As().Int32Value(); + + bool res = WindowsKeyboardHandler::getInstance()->isPressed(scanCode); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsCapsLockOn(const Napi::CallbackInfo& info) { + bool res = WindowsKeyboardHandler::getInstance()->isCapsLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsNumLockOn(const Napi::CallbackInfo& info) { + bool res = WindowsKeyboardHandler::getInstance()->isNumLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsScrollLockOn(const Napi::CallbackInfo& info) { + bool res = WindowsKeyboardHandler::getInstance()->isScrollLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value Init(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), 0); } + +void EmitEventToJs(Napi::Env env, Napi::Function callback, std::nullptr_t* context, EventData* data) { + if (env != NULL && callback != NULL && data != NULL) { + callback.Call({Napi::Number::New(env, data->scanCode), Napi::Boolean::New(env, data->isPressed)}); + } + + if (data != NULL) { + delete data; + } +} + +void EmitEventToTSCallback(int scanCode, bool isPressed) { + EventData* data = new EventData; + data->scanCode = scanCode; + data->isPressed = isPressed; + + ts_callback.BlockingCall(data); +} + +Napi::Value StartReadingEvents(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + ts_callback = TSFN::New(env, info[0].As(), "StartReadingEvents", 0, 1, NULL, NULL); + + int res = WindowsKeyboardHandler::getInstance()->startReadingEvents(EmitEventToTSCallback); + return Napi::Number::New(env, res); +} + +void StopReadingEvents(const Napi::CallbackInfo& info) { WindowsKeyboardHandler::getInstance()->stopReadingEvents(); } + +Napi::Object InitModule(Napi::Env env, Napi::Object exports) { + exports.Set("send", &Send); + exports.Set("isPressed", &IsPressed); + exports.Set("isCapsLockOn", &IsCapsLockOn); + exports.Set("isNumLockOn", &IsNumLockOn); + exports.Set("isScrollLockOn", &IsScrollLockOn); + exports.Set("init", &Init); + exports.Set("startReadingEvents", &StartReadingEvents); + exports.Set("stopReadingEvents", &StopReadingEvents); + + return exports; +} + +NODE_API_MODULE(KeyboardKt, InitModule) diff --git a/keyboard-kt/src/jsMain/cpp/windows/binding.gyp b/keyboard-kt/src/jsMain/cpp/windows/binding.gyp new file mode 100644 index 0000000..b07a4b7 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/windows/binding.gyp @@ -0,0 +1,20 @@ +{ + "variables": { + "ARCH%": "X64" + }, + "targets": [ + { + "target_name": "KeyboardKtWindows<(ARCH)", + "sources": [ "JsKeyboardHandler.cpp" ], + "cflags": [ "-s -D_WIN32_WINNT=0x600" ], + "cflags_cc": [ "-std=c++11 -s -D_WIN32_WINNT=0x600 -w" ], + + "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ], + + "include_dirs": [ + " + val code = NApiNativeHandler.startReadingEvents { scanCode, isPressed -> eventsInternal.tryEmit(KeyEvent(Key.fromKeyCode(scanCode), isPressed.toKeyState())) } @@ -34,7 +33,7 @@ internal object KotlinJsKeyboardHandler : NativeKeyboardHandlerBase() { } } - override fun stopReadingEvents() = nApiNativeHandler.stopReadingEvents() + override fun stopReadingEvents() = NApiNativeHandler.stopReadingEvents() } @ExperimentalKeyIO diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index e8d185d..a5126fd 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -2,27 +2,24 @@ package com.github.animeshz.keyboard public external fun require(module: String): dynamic -internal object NativeUtils { - private val suffix = when(val architecture = arch()) { - "x64" -> "X64" - "x32" -> "X86" - else -> error("Non x86 architectures are not supported, current architecture: $architecture") - } - private val identifier = when(val platform = platform()) { - "darwin" -> error("Mac os is currently not supported") - "linux" -> "Linux" - "win32" -> "Windows" - else -> error("OS not supported. Current OS: $platform") - } - - @ExperimentalKeyIO - val nApiNativeHandler: NApiNativeHandler = - (require("./lib/KeyboardKt$identifier$suffix.node").NApiNativeHandler() as NApiNativeHandler) - .also { it.init() } +private val suffix = when(val architecture = arch()) { + "x64" -> "X64" + "x32" -> "X86" + else -> error("Non x86 architectures are not supported, current architecture: $architecture") +} +private val identifier = when(val platform = platform()) { + "darwin" -> error("Mac os is currently not supported") + "linux" -> "Linux" + "win32" -> "Windows" + else -> error("OS not supported. Current OS: $platform") } @ExperimentalKeyIO -internal external class NApiNativeHandler { +internal val NApiNativeHandler: NApiNativeHandlerI = + require("./lib/KeyboardKt$identifier$suffix.node").unsafeCast() + +@ExperimentalKeyIO +internal interface NApiNativeHandlerI { fun send(scanCode: Int, isPressed: Boolean) fun isPressed(scanCode: Int): Boolean diff --git a/keyboard-kt/src/jvmMain/jni/linux/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt similarity index 100% rename from keyboard-kt/src/jvmMain/jni/linux/CMakeLists.txt rename to keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt diff --git a/keyboard-kt/src/jvmMain/jni/linux/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/cpp/linux/JvmKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jvmMain/jni/linux/JvmKeyboardHandler.cpp rename to keyboard-kt/src/jvmMain/cpp/linux/JvmKeyboardHandler.cpp diff --git a/keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt similarity index 100% rename from keyboard-kt/src/jvmMain/jni/windows/CMakeLists.txt rename to keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt diff --git a/keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/jvmMain/jni/windows/JvmKeyboardHandler.cpp rename to keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp similarity index 100% rename from keyboard-kt/src/nativeCommon/windows/WindowsKeybaordHandler.cpp rename to keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp From ac4979d9b25acf2dff1ad148687697a421c8c93f Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 28 Jan 2021 18:43:31 +0530 Subject: [PATCH 15/26] Fix linux handler exiting the VM with exit code 134. --- .../native_compile/JniCompilationTask.kt | 2 +- docker/cross-build/windows-x64/Dockerfile | 2 +- .../src/jsMain/cpp/windows/CMakeLists.txt | 23 +++ .../jsMain/cpp/windows/JsKeyboardHandler.cpp | 29 ++- .../src/jsMain/cpp/windows/binding.gyp | 20 -- .../src/jvmMain/cpp/linux/CMakeLists.txt | 2 +- .../jvmMain/cpp/linux/JvmKeyboardHandler.cpp | 21 +-- .../src/jvmMain/cpp/windows/CMakeLists.txt | 2 +- .../cpp/windows/JvmKeyboardHandler.cpp | 2 +- .../linux/LinuxKeyboardHandler.cpp | 37 ++++ .../nativeCommon/linux/X11KeyboardHandler.cpp | 173 ++++++++++++------ .../windows/WindowsKeyboardHandler.cpp | 17 +- 12 files changed, 215 insertions(+), 115 deletions(-) create mode 100644 keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt delete mode 100644 keyboard-kt/src/jsMain/cpp/windows/binding.gyp create mode 100644 keyboard-kt/src/nativeCommon/linux/LinuxKeyboardHandler.cpp diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt index 0723c35..f1b29c2 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt @@ -64,7 +64,7 @@ open class JniCompilationTask @Inject constructor( "mkdir -p \$WORK_DIR/project/build/jni && " + "mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + "cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " + - "cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/jni/${target.os} && " + + "cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/cpp/${target.os} && " + "cmake --build . ${if (isVerbose) "--verbose " else ""}--config Release && " + "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}" diff --git a/docker/cross-build/windows-x64/Dockerfile b/docker/cross-build/windows-x64/Dockerfile index cd59b9f..59e4158 100644 --- a/docker/cross-build/windows-x64/Dockerfile +++ b/docker/cross-build/windows-x64/Dockerfile @@ -15,7 +15,7 @@ RUN \ curl \ python3 \ nodejs && \ - npm install -g node-gyp && \ + npm install -g cmake-js && \ 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 && \ diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt new file mode 100644 index 0000000..1e4c2dc --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(KeyboardKt) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") + +option(ARCH "architecture") + +add_definitions(-D_WIN32_WINNT=0x600) +add_definitions(-DNAPI_VERSION=5) +include_directories(${CMAKE_JS_INC}) +include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) +include_directories("../../../nativeCommon/windows") + +add_library( + ${PROJECT_NAME} SHARED + JsKeyboardHandler.cpp + ${CMAKE_JS_SRC} +) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) \ No newline at end of file diff --git a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp index d44ed10..5dfa84f 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp +++ b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp @@ -2,6 +2,10 @@ #include "WindowsKeyboardHandler.cpp" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct EventData { int scanCode; bool isPressed; @@ -65,7 +69,7 @@ void EmitEventToTSCallback(int scanCode, bool isPressed) { Napi::Value StartReadingEvents(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - ts_callback = TSFN::New(env, info[0].As(), "StartReadingEvents", 0, 1, NULL, NULL); + ts_callback = TSFN::New(env, info[0].As(), "StartReadingEvents", 0, 1); int res = WindowsKeyboardHandler::getInstance()->startReadingEvents(EmitEventToTSCallback); return Napi::Number::New(env, res); @@ -74,16 +78,21 @@ Napi::Value StartReadingEvents(const Napi::CallbackInfo& info) { void StopReadingEvents(const Napi::CallbackInfo& info) { WindowsKeyboardHandler::getInstance()->stopReadingEvents(); } Napi::Object InitModule(Napi::Env env, Napi::Object exports) { - exports.Set("send", &Send); - exports.Set("isPressed", &IsPressed); - exports.Set("isCapsLockOn", &IsCapsLockOn); - exports.Set("isNumLockOn", &IsNumLockOn); - exports.Set("isScrollLockOn", &IsScrollLockOn); - exports.Set("init", &Init); - exports.Set("startReadingEvents", &StartReadingEvents); - exports.Set("stopReadingEvents", &StopReadingEvents); + exports["send"] = Napi::Function::New(env, Send); + exports["isPressed"] = Napi::Function::New(env, IsPressed); + exports["isCapsLockOn"] = Napi::Function::New(env, IsCapsLockOn); + exports["isNumLockOn"] = Napi::Function::New(env, IsNumLockOn); + exports["isScrollLockOn"] = Napi::Function::New(env, IsScrollLockOn); + exports["init"] = Napi::Function::New(env, Init); + exports["startReadingEvents"] = Napi::Function::New(env, StartReadingEvents); + exports["stopReadingEvents"] = Napi::Function::New(env, StopReadingEvents); return exports; } -NODE_API_MODULE(KeyboardKt, InitModule) +// Put a preprocessor to append arch +NODE_API_MODULE(KeyboardKtWindows, InitModule) + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/keyboard-kt/src/jsMain/cpp/windows/binding.gyp b/keyboard-kt/src/jsMain/cpp/windows/binding.gyp deleted file mode 100644 index b07a4b7..0000000 --- a/keyboard-kt/src/jsMain/cpp/windows/binding.gyp +++ /dev/null @@ -1,20 +0,0 @@ -{ - "variables": { - "ARCH%": "X64" - }, - "targets": [ - { - "target_name": "KeyboardKtWindows<(ARCH)", - "sources": [ "JsKeyboardHandler.cpp" ], - "cflags": [ "-s -D_WIN32_WINNT=0x600" ], - "cflags_cc": [ "-std=c++11 -s -D_WIN32_WINNT=0x600 -w" ], - - "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ], - - "include_dirs": [ - "isCapsLockOn(); } +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isCapsLockOn(JNIEnv *env, jobject obj) { return LinuxKeyboardHandler::getInstance()->isCapsLockOn(); } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { return handler->isNumLockOn(); } +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isNumLockOn(JNIEnv *env, jobject obj) { return LinuxKeyboardHandler::getInstance()->isNumLockOn(); } JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_isScrollLockOn(JNIEnv *env, jobject obj) { return 0; } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeSendEvent(JNIEnv *env, jobject obj, jint scanCode, jboolean isPressed) { - return handler->sendEvent(scanCode, isPressed); + return LinuxKeyboardHandler::getInstance()->sendEvent(scanCode, isPressed); } -JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeIsPressed(JNIEnv *env, jobject obj, jint scanCode) { return handler->isPressed(scanCode); } +JNIEXPORT jboolean JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeIsPressed(JNIEnv *env, jobject obj, jint scanCode) { return LinuxKeyboardHandler::getInstance()->isPressed(scanCode); } void emitEventToJvm(int scanCode, bool isPressed) { JNIEnv *newEnv; @@ -44,11 +39,11 @@ JNIEXPORT jint JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nati JvmKeyboardHandler = env->NewGlobalRef(obj); emitEvent = env->GetMethodID(env->GetObjectClass(obj), "emitEvent", "(IZ)V"); - return handler->startReadingEvents(emitEventToJvm); + return LinuxKeyboardHandler::getInstance()->startReadingEvents(emitEventToJvm); } JNIEXPORT void JNICALL Java_com_github_animeshz_keyboard_JvmKeyboardHandler_nativeStopReadingEvents(JNIEnv *env, jobject obj) { - return handler->stopReadingEvents(); + return LinuxKeyboardHandler::getInstance()->stopReadingEvents(); env->DeleteGlobalRef(JvmKeyboardHandler); JvmKeyboardHandler = NULL; diff --git a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt index 2f3f5ec..1aa562a 100644 --- a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt @@ -14,6 +14,6 @@ include_directories("../../generated/jni") include_directories("../../../nativeCommon/windows") add_library( - KeyboardKt${ARCH} SHARED + ${PROJECT_NAME}${ARCH} SHARED JvmKeyboardHandler.cpp ) diff --git a/keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp b/keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp index 824d338..dacf6e0 100644 --- a/keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp +++ b/keyboard-kt/src/jvmMain/cpp/windows/JvmKeyboardHandler.cpp @@ -1,4 +1,4 @@ -#include "WindowsKeybaordHandler.cpp" +#include "WindowsKeyboardHandler.cpp" #include "com_github_animeshz_keyboard_JvmKeyboardHandler.h" #ifdef __cplusplus diff --git a/keyboard-kt/src/nativeCommon/linux/LinuxKeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/LinuxKeyboardHandler.cpp new file mode 100644 index 0000000..be384ec --- /dev/null +++ b/keyboard-kt/src/nativeCommon/linux/LinuxKeyboardHandler.cpp @@ -0,0 +1,37 @@ +#include "X11KeyboardHandler.cpp" + +namespace LinuxKeyboardHandler { + +enum HandlerType { X11, DEVICE, UNINITIALIZED, NONE }; + +HandlerType type = UNINITIALIZED; + +BaseKeyboardHandler *getInstance() { + switch (type) { + case NONE: + return NULL; + + case X11: + return X11KeyboardHandler::getInstance(); + + case DEVICE: + return NULL; + + case UNINITIALIZED: + BaseKeyboardHandler *handler; + if ((handler = X11KeyboardHandler::getInstance()) != NULL) { + type = X11; + return handler; + } + + if (false /* Device */) { + type = DEVICE; + return NULL; + } + + type = NONE; + return NULL; + } +} + +} // namespace LinuxKeyboardHandler \ No newline at end of file diff --git a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp index 0bb7152..827df30 100644 --- a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include +#include #include #include "../BaseKeyboardHandler.h" @@ -16,16 +18,20 @@ class X11KeyboardHandler : BaseKeyboardHandler { void *xInput2; void *xTest; Display *display; - int xiOpcode; - volatile bool stopReading = false; - X11KeyboardHandler(void *x11, void *xInput2, void *xTest, Display *display, - int xiOpcode) { + std::condition_variable cv; + std::mutex cv_m; + bool stopReading; + + static X11KeyboardHandler *instance; + void (*callback)(int, bool); + + X11KeyboardHandler(void *x11, void *xInput2, void *xTest, + Display *display) { this->x11 = x11; this->xInput2 = xInput2; this->xTest = xTest; this->display = display; - this->xiOpcode = xiOpcode; } inline int toggleStates() { @@ -34,8 +40,7 @@ class X11KeyboardHandler : BaseKeyboardHandler { return mask.led_mask; } - public: - static BaseKeyboardHandler *Create() { + static void Create() { if (getenv("DISPLAY") == NULL) { return NULL; } @@ -60,8 +65,7 @@ class X11KeyboardHandler : BaseKeyboardHandler { // Check XInput2 functions are present, since libXi may contain XInput // or XInput2. - void *f = dlsym(xInput2, "XISelectEvents"); - if (f == NULL) { + if (dlsym(xInput2, "XISelectEvents") == NULL) { dlclose(x11); dlclose(xInput2); dlclose(xTest); @@ -87,7 +91,12 @@ class X11KeyboardHandler : BaseKeyboardHandler { dlsym(xTest, "XTestFakeKeyEvent"); - Display *display = XOpenDisplay(NULL); + std::promise p; + auto f = p.get_future(); + std::thread t(setupDisplayAndReadWhenRequired, std::move(p)); + t.detach(); + Display *display = f.get(); + if (display == NULL) { dlclose(x11); dlclose(xInput2); @@ -95,13 +104,96 @@ class X11KeyboardHandler : BaseKeyboardHandler { return NULL; } + instance = new X11KeyboardHandler(x11, xInput2, xTest, display); + + // Wait till we've done setting up the masks + std::lock_guard lk(instance->cv_m); + instance->cv.wait(lk); + } + + static void setupDisplayAndReadWhenRequired(std::promise &&p) { + Display *display = XOpenDisplay(NULL); + p.set_value(display); + + if (display == NULL) { + return; + } + int xiOpcode; - int queryEvent; - int queryError; - XQueryExtension(display, "XInputExtension", &xiOpcode, &queryEvent, - &queryError); - return new X11KeyboardHandler(x11, xInput2, xTest, display, xiOpcode); + // Discard single use variables from stack as we don't need them + { + int queryEvent; + int queryError; + XQueryExtension(display, "XInputExtension", &xiOpcode, &queryEvent, + &queryError); + + Window root = XDefaultRootWindow(display); + int maskLen = XIMaskLen(XI_LASTEVENT); + unsigned char mask[maskLen]; + + XIEventMask xiMask; + xiMask->deviceid = XIAllMasterDevices; + xiMask->mask_len = maskLen; + xiMask->mask = mask; + + XISetMask(xiMask->mask, XI_RawKeyPress); + XISetMask(xiMask->mask, XI_RawKeyRelease); + XISelectEvents(display, root, xiMask, 1); + XSync(display, 0); + } + + X11KeyboardHandler *handler; + while ((handler = instance) == NULL) { + // We shouldn't fall here, but who knows? + usleep(20); + } + + // Notify we've set up masking, so the handler becomes usable. + handler->cv.notify_all(); + + std::unique_lock lk(handler->cv_m); + while (true) { + // Wait till we asked to collect the events + handler->cv.wait(lk); + + handler->stopReading = false; + + // Clear old events in the queue. + XSync(display, 1); + XEvent event; + + while (true) { + XNextEvent(display, &event); + if (handler->stopReading) break; + + XGenericEventCookie cookie = event.xcookie; + if (cookie.type != GenericEvent || cookie.extension != xiOpcode) + continue; + + if (XGetEventData(display, &cookie)) { + bool keyEventType; + if (cookie.evtype == XI_RawKeyPress) + keyEventType = 1; + else if (cookie.evtype == XI_RawKeyRelease) + keyEventType = 0; + else + continue; + + XIRawEvent *cookieData = (XIRawEvent *)cookie.data; + handler->callback(cookieData->detail - 8, keyEventType); + } + + XFreeEventData(display, &cookie); + } + } + } + + public: + static BaseKeyboardHandler *getInstance() { + if (!instance) Create(); + + return instance; } ~X11KeyboardHandler() { @@ -133,54 +225,15 @@ class X11KeyboardHandler : BaseKeyboardHandler { } int startReadingEvents(void (*callback)(int, bool)) { - std::thread th(&X11KeyboardHandler::readInThread, this, callback); + { + std::lock_guard lk(cv_m); + this->callback = callback; + } + cv.notify_all(); return 0; } - void readInThread(void (*callback)(int, bool)) { - Window root = XDefaultRootWindow(display); - XIEventMask *xiMask = new XIEventMask; - xiMask->deviceid = XIAllMasterDevices; - xiMask->mask_len = XIMaskLen(XI_LASTEVENT); - xiMask->mask = new unsigned char[xiMask->mask_len](); - - XISetMask(xiMask->mask, XI_RawKeyPress); - XISetMask(xiMask->mask, XI_RawKeyRelease); - XISelectEvents(display, root, xiMask, 1); - XSync(display, 0); - - delete[] xiMask->mask; - delete xiMask; - - stopReading = false; - XEvent event; - - while (true) { - XNextEvent(display, &event); - if (stopReading) break; - - XGenericEventCookie cookie = event.xcookie; - if (cookie.type != GenericEvent || cookie.extension != xiOpcode) - continue; - - if (XGetEventData(display, &cookie)) { - bool keyEventType; - if (cookie.evtype == XI_RawKeyPress) - keyEventType = 1; - else if (cookie.evtype == XI_RawKeyRelease) - keyEventType = 0; - else - continue; - - XIRawEvent *cookieData = (XIRawEvent *)cookie.data; - callback(cookieData->detail - 8, keyEventType); - } - - XFreeEventData(display, &cookie); - } - } - void stopReadingEvents() { stopReading = true; @@ -195,3 +248,5 @@ class X11KeyboardHandler : BaseKeyboardHandler { XFlush(display); } }; + +X11KeyboardHandler *X11KeyboardHandler::instance = NULL; \ No newline at end of file diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp index bd7a8b6..f96a3c8 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp @@ -6,15 +6,15 @@ #define FAKE_ALT LLKHF_INJECTED | 0x20 -BaseKeyboardHandler *instance; -void (*callback)(int, bool); - class WindowsKeyboardHandler : BaseKeyboardHandler { private: DWORD threadId = 0; CRITICAL_SECTION cs; CONDITION_VARIABLE cv; + static WindowsKeyboardHandler *instance; + void (*callback)(int, bool); + WindowsKeyboardHandler() {} public: @@ -95,10 +95,9 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } data = NULL; // Immediately remove pointer to the stack variable - auto handler = (WindowsKeyboardHandler *)WindowsKeyboardHandler::getInstance(); - EnterCriticalSection(&handler->cs); - WakeConditionVariable(&handler->cv); - LeaveCriticalSection(&handler->cs); + EnterCriticalSection(&instance->cs); + WakeConditionVariable(&instance->cv); + LeaveCriticalSection(&instance->cs); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { @@ -157,9 +156,11 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } } - ::callback(scanCode, isPressed); + instance->callback(scanCode, isPressed); } return CallNextHookEx(NULL, nCode, wParam, lParam); } }; + +WindowsKeyboardHandler *WindowsKeyboardHandler::instance = NULL; \ No newline at end of file From b06ed9fc301ad48583ebaf1697f028ded175a9e8 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 28 Jan 2021 19:13:41 +0530 Subject: [PATCH 16/26] Fix errors caused mistakenly. --- .../native_compile/JniCompilationTask.kt | 3 +- .../src/jsMain/cpp/windows/CMakeLists.txt | 4 ++- .../jsMain/cpp/windows/JsKeyboardHandler.cpp | 5 ++-- .../nativeCommon/linux/X11KeyboardHandler.cpp | 30 +++++++++---------- .../windows/WindowsKeyboardHandler.cpp | 12 ++++---- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt index f1b29c2..4a2d6d6 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt @@ -20,7 +20,8 @@ open class JniCompilationTask @Inject constructor( group = "jni" } - @Option(option = "verbose", description = "Configures the URL to be verified.") + @get:Input + @set:Option(option = "verbose", description = "Configures the URL to be verified.") var isVerbose: Boolean = false var dockerImage: String = "" diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt index 1e4c2dc..6648acf 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -9,6 +9,8 @@ option(ARCH "architecture") add_definitions(-D_WIN32_WINNT=0x600) add_definitions(-DNAPI_VERSION=5) +add_definitions(-DARCH=${ARCH}) + include_directories(${CMAKE_JS_INC}) include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) include_directories("../../../nativeCommon/windows") @@ -20,4 +22,4 @@ add_library( ) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp index 5dfa84f..09b4d6b 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp +++ b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp @@ -90,9 +90,8 @@ Napi::Object InitModule(Napi::Env env, Napi::Object exports) { return exports; } -// Put a preprocessor to append arch -NODE_API_MODULE(KeyboardKtWindows, InitModule) +NODE_API_MODULE(KeyboardKtWindows##ARCH, InitModule) #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp index 827df30..266e6e8 100644 --- a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp @@ -42,25 +42,25 @@ class X11KeyboardHandler : BaseKeyboardHandler { static void Create() { if (getenv("DISPLAY") == NULL) { - return NULL; + return; } void *x11 = dlopen("libX11.so.6", RTLD_GLOBAL | RTLD_LAZY); if (x11 == NULL) { - return NULL; + return; } void *xInput2 = dlopen("libXi.so.6", RTLD_GLOBAL | RTLD_LAZY); if (xInput2 == NULL) { dlclose(x11); - return NULL; + return; } void *xTest = dlopen("libXtst.so.6", RTLD_GLOBAL | RTLD_LAZY); if (xTest == NULL) { dlclose(x11); dlclose(xInput2); - return NULL; + return; } // Check XInput2 functions are present, since libXi may contain XInput @@ -69,7 +69,7 @@ class X11KeyboardHandler : BaseKeyboardHandler { dlclose(x11); dlclose(xInput2); dlclose(xTest); - return NULL; + return; } // Load definitions @@ -101,13 +101,13 @@ class X11KeyboardHandler : BaseKeyboardHandler { dlclose(x11); dlclose(xInput2); dlclose(xTest); - return NULL; + return; } instance = new X11KeyboardHandler(x11, xInput2, xTest, display); - + // Wait till we've done setting up the masks - std::lock_guard lk(instance->cv_m); + std::unique_lock lk(instance->cv_m); instance->cv.wait(lk); } @@ -133,13 +133,13 @@ class X11KeyboardHandler : BaseKeyboardHandler { unsigned char mask[maskLen]; XIEventMask xiMask; - xiMask->deviceid = XIAllMasterDevices; - xiMask->mask_len = maskLen; - xiMask->mask = mask; + xiMask.deviceid = XIAllMasterDevices; + xiMask.mask_len = maskLen; + xiMask.mask = mask; - XISetMask(xiMask->mask, XI_RawKeyPress); - XISetMask(xiMask->mask, XI_RawKeyRelease); - XISelectEvents(display, root, xiMask, 1); + XISetMask(xiMask.mask, XI_RawKeyPress); + XISetMask(xiMask.mask, XI_RawKeyRelease); + XISelectEvents(display, root, &xiMask, 1); XSync(display, 0); } @@ -249,4 +249,4 @@ class X11KeyboardHandler : BaseKeyboardHandler { } }; -X11KeyboardHandler *X11KeyboardHandler::instance = NULL; \ No newline at end of file +X11KeyboardHandler *X11KeyboardHandler::instance = NULL; diff --git a/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp index f96a3c8..d408ae8 100644 --- a/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/windows/WindowsKeyboardHandler.cpp @@ -19,12 +19,12 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { public: static BaseKeyboardHandler *getInstance() { - if (!::instance) { + if (!instance) { // Hack to avoid `libstdc++-6.dll` a ~2MB GNU's stdlib that is not bundled in windows. - ::instance = (WindowsKeyboardHandler *) malloc(sizeof(WindowsKeyboardHandler)); - new(::instance) WindowsKeyboardHandler; + instance = (WindowsKeyboardHandler *) malloc(sizeof(WindowsKeyboardHandler)); + new(instance) WindowsKeyboardHandler; } - return ::instance; + return instance; } bool isCapsLockOn() { return GetKeyState(0x14) & 1; } @@ -73,7 +73,7 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { int startReadingEvents(void (*callback)(int, bool)) { int ret = 0; - ::callback = callback; + this->callback = callback; InitializeCriticalSection (&cs); InitializeConditionVariable (&cv); @@ -163,4 +163,4 @@ class WindowsKeyboardHandler : BaseKeyboardHandler { } }; -WindowsKeyboardHandler *WindowsKeyboardHandler::instance = NULL; \ No newline at end of file +WindowsKeyboardHandler *WindowsKeyboardHandler::instance = NULL; From 56d787ea18c0410f28007c223c6a13172482228a Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 28 Jan 2021 19:23:30 +0530 Subject: [PATCH 17/26] Linux bindings for NodeJS --- .../src/jsMain/cpp/linux/CMakeLists.txt | 23 ++++ .../jsMain/cpp/linux/JsKeyboardHandler.cpp | 102 ++++++++++++++++++ keyboard-kt/src/jsMain/cpp/linux/binding.gyp | 19 ---- .../src/jsMain/cpp/windows/CMakeLists.txt | 2 +- 4 files changed, 126 insertions(+), 20 deletions(-) create mode 100644 keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt create mode 100644 keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp delete mode 100644 keyboard-kt/src/jsMain/cpp/linux/binding.gyp diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt new file mode 100644 index 0000000..903a8d2 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(KeyboardKt) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") + +option(ARCH "architecture") + +add_definitions(-DARCH=${ARCH}) + +include_directories(${CMAKE_JS_INC}) +include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) +include_directories("../../../nativeCommon/linux") + +add_library( + ${PROJECT_NAME}${ARCH} SHARED + JsKeyboardHandler.cpp + ${CMAKE_JS_SRC} +) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp new file mode 100644 index 0000000..973a0d0 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp @@ -0,0 +1,102 @@ +#include + +#include "LinuxKeyboardHandler.cpp" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct EventData { + int scanCode; + bool isPressed; +} EventData; + +void EmitEventToJs(Napi::Env env, Napi::Function callback, std::nullptr_t* context, EventData* data); +using TSFN = Napi::TypedThreadSafeFunction; + +TSFN ts_callback; + +// No checks because these are part of internal API. + +void Send(const Napi::CallbackInfo& info) { + int scanCode = info[0].As().Int32Value(); + bool isPressed = info[0].As().Value(); + + LinuxKeyboardHandler::getInstance()->sendEvent(scanCode, isPressed); +} + +Napi::Value IsPressed(const Napi::CallbackInfo& info) { + int scanCode = info[0].As().Int32Value(); + + bool res = LinuxKeyboardHandler::getInstance()->isPressed(scanCode); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsCapsLockOn(const Napi::CallbackInfo& info) { + bool res = LinuxKeyboardHandler::getInstance()->isCapsLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsNumLockOn(const Napi::CallbackInfo& info) { + bool res = LinuxKeyboardHandler::getInstance()->isNumLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value IsScrollLockOn(const Napi::CallbackInfo& info) { + bool res = LinuxKeyboardHandler::getInstance()->isScrollLockOn(); + return Napi::Boolean::New(info.Env(), res); +} + +Napi::Value Init(const Napi::CallbackInfo& info) { + int ret = 1; + if (LinuxKeyboardHandler::getInstance() != NULL) ret = 0; + + return Napi::Number::New(info.Env(), ret); +} + +void EmitEventToJs(Napi::Env env, Napi::Function callback, std::nullptr_t* context, EventData* data) { + if (env != NULL && callback != NULL && data != NULL) { + callback.Call({Napi::Number::New(env, data->scanCode), Napi::Boolean::New(env, data->isPressed)}); + } + + if (data != NULL) { + delete data; + } +} + +void EmitEventToTSCallback(int scanCode, bool isPressed) { + EventData* data = new EventData; + data->scanCode = scanCode; + data->isPressed = isPressed; + + ts_callback.BlockingCall(data); +} + +Napi::Value StartReadingEvents(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + ts_callback = TSFN::New(env, info[0].As(), "StartReadingEvents", 0, 1); + + int res = LinuxKeyboardHandler::getInstance()->startReadingEvents(EmitEventToTSCallback); + return Napi::Number::New(env, res); +} + +void StopReadingEvents(const Napi::CallbackInfo& info) { LinuxKeyboardHandler::getInstance()->stopReadingEvents(); } + +Napi::Object InitModule(Napi::Env env, Napi::Object exports) { + exports["send"] = Napi::Function::New(env, Send); + exports["isPressed"] = Napi::Function::New(env, IsPressed); + exports["isCapsLockOn"] = Napi::Function::New(env, IsCapsLockOn); + exports["isNumLockOn"] = Napi::Function::New(env, IsNumLockOn); + exports["isScrollLockOn"] = Napi::Function::New(env, IsScrollLockOn); + exports["init"] = Napi::Function::New(env, Init); + exports["startReadingEvents"] = Napi::Function::New(env, StartReadingEvents); + exports["stopReadingEvents"] = Napi::Function::New(env, StopReadingEvents); + + return exports; +} + +NODE_API_MODULE(KeyboardKtLinux##ARCH, InitModule) + +#ifdef __cplusplus +} +#endif diff --git a/keyboard-kt/src/jsMain/cpp/linux/binding.gyp b/keyboard-kt/src/jsMain/cpp/linux/binding.gyp deleted file mode 100644 index 603b0cf..0000000 --- a/keyboard-kt/src/jsMain/cpp/linux/binding.gyp +++ /dev/null @@ -1,19 +0,0 @@ -{ - "variables": { - "ARCH%": "X64" - }, - "targets": [ - { - "target_name": "KeyboardKtWindows<(ARCH)", - "sources": [ "JsKeyboardHandler.cpp" ], - "cflags": [ "-s -D_WIN32_WINNT=0x600" ], - "cflags_cc": [ "-std=c++11 -s -D_WIN32_WINNT=0x600" ], - - "include_dirs": [ - " Date: Thu, 28 Jan 2021 21:09:08 +0530 Subject: [PATCH 18/26] Written task for compiling Napi (NodeJS wrapper) and made it compile for Linux properly. --- .../native_compile/Configurations.kt | 19 +++++ .../native_compile/JniCompilationTask.kt | 24 +----- .../native_compile/JniHeaderGenerationTask.kt | 2 +- .../native_compile/NapiCompilationTask.kt | 79 +++++++++++++++++++ .../native_compile/NativeCompilationPlugin.kt | 33 ++++++-- .../keyboard_mouse/native_compile/Utils.kt | 18 +++++ docker/cross-build/linux-x64/Dockerfile | 7 +- docker/cross-build/linux-x86/Dockerfile | 6 +- docker/cross-build/windows-x64/Dockerfile | 1 - keyboard-kt/build.gradle.kts | 32 +++++--- .../src/jsMain/cpp/linux/CMakeLists.txt | 6 +- .../jsMain/cpp/linux/JsKeyboardHandler.cpp | 3 +- .../src/jsMain/cpp/windows/CMakeLists.txt | 6 +- .../jsMain/cpp/windows/JsKeyboardHandler.cpp | 3 +- .../github/animeshz/keyboard/NativeUtils.kt | 4 +- 15 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt create mode 100644 composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Utils.kt diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt index 88d1f9f..43f37c5 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Configurations.kt @@ -6,6 +6,19 @@ class Target( val dockerImage: String ) +open class NativeConfiguration { + val jni = JniConfiguration() + val napi = JsCompilationConfiguration() + + fun jni(configuration: JniConfiguration.() -> Unit) { + jni.apply(configuration) + } + + fun napi(configuration: JsCompilationConfiguration.() -> Unit) { + napi.apply(configuration) + } +} + open class JniConfiguration { val headers: JniHeaderConfiguration = JniHeaderConfiguration() val compilation: JniCompilationConfiguration = JniCompilationConfiguration() @@ -29,3 +42,9 @@ open class JniCompilationConfiguration { var outputDir: String = "" var targets: List = emptyList() } + +open class JsCompilationConfiguration { + var baseInputPaths: List = emptyList() + var outputDir: String = "" + var targets: List = emptyList() +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt index 4a2d6d6..9bbc688 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniCompilationTask.kt @@ -8,40 +8,24 @@ import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import java.io.ByteArrayOutputStream import javax.inject.Inject -import kotlin.system.exitProcess /** - * For building shared libraries out of C/C++ sources + * For building shared libraries out of C/C++ sources for JVM */ open class JniCompilationTask @Inject constructor( private val target: Target ) : DefaultTask() { init { - group = "jni" + group = "nativeCompilation" } @get:Input - @set:Option(option = "verbose", description = "Configures the URL to be verified.") + @set:Option(option = "verbose", description = "Sets verbosity of output.") var isVerbose: Boolean = false - var dockerImage: String = "" - - private fun check() { - println("Checking docker installation") - - val exit = project.exec { - commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) - isIgnoreExitValue = true - }.exitValue - if (exit != 0) { - println("Please install docker before running this task") - exitProcess(1) - } - } - @TaskAction fun run() { - check() + project.checkDockerInstallation() val tmpVar = project.file(".").absolutePath val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) { diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt index 550909d..900a3ab 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/JniHeaderGenerationTask.kt @@ -17,7 +17,7 @@ open class JniHeaderGenerationTask @Inject constructor( val methodRegex = """.*\bnative\b.*""".toRegex() init { - group = "jni" + group = "nativeCompilation" inputs.dir(configuration.inputDir) outputs.dir(configuration.outputDir) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt new file mode 100644 index 0000000..ce1a5e8 --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt @@ -0,0 +1,79 @@ +package com.github.animeshz.keyboard_mouse.native_compile + +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import java.io.ByteArrayOutputStream +import javax.inject.Inject + +/** + * For building shared libraries out of C/C++ sources for NodeJS + */ +open class NapiCompilationTask @Inject constructor( + private val target: Target +) : DefaultTask() { + init { + group = "nativeCompilation" + } + + @get:Input + @set:Option(option = "verbose", description = "Sets verbosity of output.") + var isVerbose: Boolean = false + + @TaskAction + fun run() { + project.checkDockerInstallation() + + val tmpVar = project.file(".").absolutePath + 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: () -> Pair = { + ByteArrayOutputStream().use { + project.exec { + commandLine( + "docker", + "run", + "--rm", + "-v", + "$path:/work/project", + target.dockerImage, + "bash", + "-c", + "mkdir -p \$WORK_DIR/project/build/napi && " + + "cmake-js --CDARCH=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + + "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/Release/KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + + "rm -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build" + ) + + isIgnoreExitValue = true + standardOutput = System.out + errorOutput = it + }.exitValue to it.toString() + } + } + 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) && error.startsWith(nonDaemonError)) { + project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue() + + do { + Thread.sleep(500) + val result = work() + exit = result.first + error = result.second + } while (error.startsWith(nonDaemonError)) + } + + System.err.println(error) + if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.") + } +} diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt index 8ffd206..c4f137d 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt @@ -12,18 +12,19 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension class NativeCompilationPlugin : Plugin { override fun apply(target: Project) { target.apply(plugin = "org.jetbrains.kotlin.multiplatform") - val ext = target.extensions.create("configureJni", JniConfiguration::class.java) + val ext = target.extensions.create("nativeCompilation", NativeConfiguration::class.java) - setup(target, ext) + setupJni(target, ext.jni) + setupNapi(target, ext.napi) } - private fun setup(project: Project, extension: JniConfiguration) { + private fun setupJni(project: Project, extension: JniConfiguration) { project.afterEvaluate { with(extension.headers) { if (inputDir.isEmpty() || outputDir.isEmpty()) return@afterEvaluate } - val compileJniAll by project.tasks.creating { group = "jni" } + val compileJniAll by project.tasks.creating { group = "nativeCompilation" } project.tasks.getByName("jvmProcessResources") { dependsOn(compileJniAll) } val headersTask = project.tasks @@ -38,8 +39,6 @@ class NativeCompilationPlugin : Plugin { .register("compileJni${target.os.capitalize()}${target.arch.capitalize()}", target) .also { compileJniAll.dependsOn(it.get()) } .configure { - dockerImage = target.dockerImage - for (path in extension.compilation.baseInputPaths) inputs.dir(path / target.os) outputs.dir(extension.compilation.outputDir) @@ -53,5 +52,27 @@ class NativeCompilationPlugin : Plugin { } } + private fun setupNapi(project: Project, extension: JsCompilationConfiguration) { + project.afterEvaluate { + val compileNapiAll by project.tasks.creating { group = "nativeCompilation" } + + with(extension) { + if (baseInputPaths.isEmpty() || outputDir.isEmpty() || targets.isEmpty()) return@afterEvaluate + } + + extension.targets.forEach { target -> + project.tasks + .register("compileNapi${target.os.capitalize()}${target.arch.capitalize()}", target) + .also { compileNapiAll.dependsOn(it.get()) } + .configure { + for (path in extension.baseInputPaths) inputs.dir(path / target.os) + outputs.dir(extension.outputDir) + } + } + + // Specify the js include dir + } + } + private operator fun String.div(other: String) = "$this/$other" } diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Utils.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Utils.kt new file mode 100644 index 0000000..5eadc5d --- /dev/null +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/Utils.kt @@ -0,0 +1,18 @@ +package com.github.animeshz.keyboard_mouse.native_compile + +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.Project +import kotlin.system.exitProcess + +internal fun Project.checkDockerInstallation() { + println("Checking docker installation") + + val exit = exec { + commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker")) + isIgnoreExitValue = true + }.exitValue + if (exit != 0) { + println("Please install docker before running this task") + exitProcess(1) + } +} diff --git a/docker/cross-build/linux-x64/Dockerfile b/docker/cross-build/linux-x64/Dockerfile index e4320aa..d481946 100644 --- a/docker/cross-build/linux-x64/Dockerfile +++ b/docker/cross-build/linux-x64/Dockerfile @@ -2,7 +2,7 @@ FROM dockcross/linux-x64 LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" -ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x64 +ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-linux-x64 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni ENV X11_HEADERS_DIR=/usr/include/X11/ @@ -10,20 +10,21 @@ ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ apt update && \ + curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ apt install --no-install-recommends --yes \ curl \ python3 \ nodejs \ - npm \ libx11-dev \ libxi-dev \ libxtst-dev && \ - npm install -g node-gyp && \ + npm install -g cmake-js && \ 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 && \ mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ + cd $NODE_ADDON_API_HEADERS_DIR && \ npm pack node-addon-api@3.1.0 && \ tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ rm node-addon-api-3.1.0.tgz diff --git a/docker/cross-build/linux-x86/Dockerfile b/docker/cross-build/linux-x86/Dockerfile index db31a7c..6e16d1b 100644 --- a/docker/cross-build/linux-x86/Dockerfile +++ b/docker/cross-build/linux-x86/Dockerfile @@ -2,7 +2,7 @@ FROM dockcross/linux-x86 LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" -ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x86 +ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-linux-x86 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni ENV X11_HEADERS_DIR=/usr/include/X11/ @@ -11,15 +11,15 @@ ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ dpkg --add-architecture i386 && \ apt update && \ + curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ apt install --no-install-recommends --yes \ curl \ python3 \ nodejs \ - npm \ libx11-dev:i386 \ libxi-dev:i386 \ libxtst-dev:i386 && \ - npm install -g node-gyp && \ + npm install -g cmake-js && \ 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 && \ diff --git a/docker/cross-build/windows-x64/Dockerfile b/docker/cross-build/windows-x64/Dockerfile index 59e4158..f7ba5ff 100644 --- a/docker/cross-build/windows-x64/Dockerfile +++ b/docker/cross-build/windows-x64/Dockerfile @@ -10,7 +10,6 @@ ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}/support-files/headers/node-addon-api RUN \ apt-get update && \ curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ - apt-get install -f && \ apt-get install --no-install-recommends --yes \ curl \ python3 \ diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 5bd21de..32c2033 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -89,20 +89,34 @@ publishingConfig { password = System.getenv("BINTRAY_KEY") } -configureJni { - headers { - inputDir = "src/jvmMain/kotlin" - outputDir = "src/jvmMain/generated/jni" +nativeCompilation { + jni { + headers { + inputDir = "src/jvmMain/kotlin" + outputDir = "src/jvmMain/generated/jni" + } + compilation { + baseInputPaths = listOf("src/jvmMain/cpp", "src/nativeCommon") + outputDir = "build/jni" + + targets = listOf( + Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-build-windows-x64"), + Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"), + Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64"), + Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") + ) + } } - compilation { - baseInputPaths = listOf("src/jvmMain/cpp", "src/nativeCommon") + + napi { + baseInputPaths = listOf("src/jsMain/cpp", "src/nativeCommon") outputDir = "build/jni" targets = listOf( - Target("windows", "x64", "animeshz/keyboard-mouse-kt:jni-build-windows-x64"), + Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-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", "x86", "animeshz/keyboard-mouse-kt:jni-build-linux-x86") + Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64"), + Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") ) } } diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt index 903a8d2..23cdb90 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -14,10 +14,10 @@ include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) include_directories("../../../nativeCommon/linux") add_library( - ${PROJECT_NAME}${ARCH} SHARED + ${PROJECT_NAME}Linux${ARCH} SHARED JsKeyboardHandler.cpp ${CMAKE_JS_SRC} ) -set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) +set_target_properties(${PROJECT_NAME}Linux${ARCH} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME}Linux${ARCH} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp index 973a0d0..48f4894 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp +++ b/keyboard-kt/src/jsMain/cpp/linux/JsKeyboardHandler.cpp @@ -95,7 +95,8 @@ Napi::Object InitModule(Napi::Env env, Napi::Object exports) { return exports; } -NODE_API_MODULE(KeyboardKtLinux##ARCH, InitModule) +#define MODULE_NAME KeyboardKtLinux ## ARCH +NODE_API_MODULE(MODULE_NAME, InitModule) #ifdef __cplusplus } diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt index ad9567c..dce8726 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -16,10 +16,10 @@ include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) include_directories("../../../nativeCommon/windows") add_library( - ${PROJECT_NAME}${ARCH} SHARED + ${PROJECT_NAME}Windows${ARCH} SHARED JsKeyboardHandler.cpp ${CMAKE_JS_SRC} ) -set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) +set_target_properties(${PROJECT_NAME}Windows${ARCH} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME}Windows${ARCH} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp index 09b4d6b..c836aa0 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp +++ b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp @@ -90,7 +90,8 @@ Napi::Object InitModule(Napi::Env env, Napi::Object exports) { return exports; } -NODE_API_MODULE(KeyboardKtWindows##ARCH, InitModule) +#define MODULE_NAME KeyboardKtWindows ## ARCH +NODE_API_MODULE(MODULE_NAME, InitModule) #ifdef __cplusplus } diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index a5126fd..bd04338 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -3,8 +3,8 @@ package com.github.animeshz.keyboard public external fun require(module: String): dynamic private val suffix = when(val architecture = arch()) { - "x64" -> "X64" - "x32" -> "X86" + "x64" -> "x64" + "x32" -> "x86" else -> error("Non x86 architectures are not supported, current architecture: $architecture") } private val identifier = when(val platform = platform()) { From 41d77f75bf3a9ced6580de3b1629911e61cbdc28 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 28 Jan 2021 21:23:37 +0530 Subject: [PATCH 19/26] Skip supporting NodeJS on x86 Linux for now, because it is not shipped as 32 bit anyways. --- .../native_compile/NapiCompilationTask.kt | 2 +- docker/cross-build/linux-x86/Dockerfile | 14 +++++++------- keyboard-kt/build.gradle.kts | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt index ce1a5e8..faec380 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt @@ -47,7 +47,7 @@ open class NapiCompilationTask @Inject constructor( "bash", "-c", "mkdir -p \$WORK_DIR/project/build/napi && " + - "cmake-js --CDARCH=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + + "cmake-js --arch=${target.arch} --CDARCH=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/Release/KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + "rm -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build" ) diff --git a/docker/cross-build/linux-x86/Dockerfile b/docker/cross-build/linux-x86/Dockerfile index 6e16d1b..f69781e 100644 --- a/docker/cross-build/linux-x86/Dockerfile +++ b/docker/cross-build/linux-x86/Dockerfile @@ -11,22 +11,22 @@ ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api RUN \ dpkg --add-architecture i386 && \ apt update && \ - curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ + # curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ apt install --no-install-recommends --yes \ curl \ python3 \ - nodejs \ + # nodejs \ libx11-dev:i386 \ libxi-dev:i386 \ libxtst-dev:i386 && \ - npm install -g cmake-js && \ + # npm install -g cmake-js && \ 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 && \ - mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ - npm pack node-addon-api@3.1.0 && \ - tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ - rm node-addon-api-3.1.0.tgz + mkdir -p $NODE_ADDON_API_HEADERS_DIR + # npm pack node-addon-api@3.1.0 && \ + # tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ + # rm node-addon-api-3.1.0.tgz WORKDIR ${WORK_DIR} diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 32c2033..4e9eedb 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -115,8 +115,9 @@ nativeCompilation { targets = listOf( Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-build-windows-x64"), Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"), - Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64"), - Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") + Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64") + // NodeJS doesn't ship in x86, so people must be building the nodejs theirselves, so supporting it is not really necessary for now + // Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") ) } } From c2685e04e2c98fa1e9602314b31a040b1c214436 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 30 Jan 2021 17:23:49 +0530 Subject: [PATCH 20/26] Successfully built Windows packages for NodeJS. --- .../native_compile/NapiCompilationTask.kt | 4 +- docker/cross-build/windows-x64/Dockerfile | 26 ++- docker/cross-build/windows-x86/Dockerfile | 33 ++- keyboard-kt/build.gradle.kts | 4 +- .../src/jsMain/cpp/linux/CMakeLists.txt | 8 +- .../src/jsMain/cpp/windows/CMakeLists.txt | 10 +- keyboard-kt/src/jsMain/cpp/windows/node.def | 206 ++++++++++++++++++ 7 files changed, 266 insertions(+), 25 deletions(-) create mode 100644 keyboard-kt/src/jsMain/cpp/windows/node.def diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt index faec380..240618f 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt @@ -47,8 +47,8 @@ open class NapiCompilationTask @Inject constructor( "bash", "-c", "mkdir -p \$WORK_DIR/project/build/napi && " + - "cmake-js --arch=${target.arch} --CDARCH=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + - "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/Release/KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + + "cmake-js compile --CDARCH=${target.arch} --arch=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + + "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/{Release/,}KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + "rm -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build" ) diff --git a/docker/cross-build/windows-x64/Dockerfile b/docker/cross-build/windows-x64/Dockerfile index f7ba5ff..b9d9e43 100644 --- a/docker/cross-build/windows-x64/Dockerfile +++ b/docker/cross-build/windows-x64/Dockerfile @@ -6,6 +6,9 @@ ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-windows-x64 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}/support-files/headers/node-addon-api +ENV WINDOWS_NODE_LINK_DIR=${WORK_DIR}/support-files/link/node + +COPY keyboard-kt/src/jsMain/cpp/windows/node.def ${WINDOWS_NODE_LINK_DIR}/node.def RUN \ apt-get update && \ @@ -13,16 +16,27 @@ RUN \ apt-get install --no-install-recommends --yes \ curl \ python3 \ + unzip \ nodejs && \ npm install -g cmake-js && \ + # Download and pack JNI headers 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/windows/native/include/jni_md.h' > jni_md.h && \ - mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ - cd $NODE_ADDON_API_HEADERS_DIR && \ - npm pack node-addon-api@3.1.0 && \ + curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' && \ + curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' && \ + # Download and pack node-addon-api headers + mkdir -p ${NODE_ADDON_API_HEADERS_DIR} && \ + cd ${NODE_ADDON_API_HEADERS_DIR} && \ + curl -LO 'https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz' && \ tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ - rm node-addon-api-3.1.0.tgz + rm node-addon-api-3.1.0.tgz && \ + # Download, build and pack linkable object file + cd ${WINDOWS_NODE_LINK_DIR} && \ + curl -LO 'https://nodejs.org/dist/v14.15.4/node-v14.15.4-win-x64.zip' && \ + unzip node-v14.15.4-win-x64.zip "node-v14.15.4-win-x64/node.exe" && \ + x86_64-w64-mingw32.shared-dlltool -d node.def -y node.a && \ + rm node-v14.15.4-win-x64.zip && \ + rm -rf node-v14.15.4-win-x64 && \ + rm node.def WORKDIR ${WORK_DIR} diff --git a/docker/cross-build/windows-x86/Dockerfile b/docker/cross-build/windows-x86/Dockerfile index ad3736b..184b0d2 100644 --- a/docker/cross-build/windows-x86/Dockerfile +++ b/docker/cross-build/windows-x86/Dockerfile @@ -2,26 +2,41 @@ FROM dockcross/windows-shared-x86 LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com" -ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-windows-x86 +ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-windows-x86 ENV WORK_DIR=/work ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api +ENV WINDOWS_NODE_LINK_DIR=${WORK_DIR}/support-files/link/node + +COPY keyboard-kt/src/jsMain/cpp/windows/node.def ${WINDOWS_NODE_LINK_DIR}/node.def RUN \ apt-get update && \ + curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ apt-get install --no-install-recommends --yes \ curl \ python3 \ - nodejs \ - npm && \ - npm install -g node-gyp && \ + unzip \ + nodejs && \ + npm install -g cmake-js && \ + # Download and pack JNI headers 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/windows/native/include/jni_md.h' > jni_md.h && \ - mkdir -p $NODE_ADDON_API_HEADERS_DIR && \ - npm pack node-addon-api@3.1.0 && \ + curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' && \ + curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' && \ + # Download and pack node-addon-api headers + mkdir -p ${NODE_ADDON_API_HEADERS_DIR} && \ + cd ${NODE_ADDON_API_HEADERS_DIR} && \ + curl -LO 'https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz' && \ tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \ - rm node-addon-api-3.1.0.tgz + rm node-addon-api-3.1.0.tgz && \ + # Download, build and pack linkable object file + cd ${WINDOWS_NODE_LINK_DIR} && \ + curl -LO 'https://nodejs.org/dist/v14.15.4/node-v14.15.4-win-x86.zip' && \ + unzip node-v14.15.4-win-x86.zip "node-v14.15.4-win-x86/node.exe" && \ + i686-w64-mingw32.shared-dlltool -d node.def -y node.a && \ + rm node-v14.15.4-win-x86.zip && \ + rm -rf node-v14.15.4-win-x86 && \ + rm node.def WORKDIR ${WORK_DIR} diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 4e9eedb..a39b9c8 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -101,7 +101,7 @@ nativeCompilation { targets = listOf( Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-build-windows-x64"), - Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"), + Target("windows", "x86", "animeshz/keyboard-mouse-kt:cross-build-windows-x86"), Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64"), Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") ) @@ -114,7 +114,7 @@ nativeCompilation { targets = listOf( Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-build-windows-x64"), - Target("windows", "x86", "animeshz/keyboard-mouse-kt:jni-build-windows-x86"), + Target("windows", "x86", "animeshz/keyboard-mouse-kt:cross-build-windows-x86"), Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64") // NodeJS doesn't ship in x86, so people must be building the nodejs theirselves, so supporting it is not really necessary for now // Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt index 23cdb90..bc4e02d 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -7,6 +7,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") option(ARCH "architecture") +set(TARGET ${PROJECT_NAME}Windows${ARCH}) + add_definitions(-DARCH=${ARCH}) include_directories(${CMAKE_JS_INC}) @@ -14,10 +16,10 @@ include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) include_directories("../../../nativeCommon/linux") add_library( - ${PROJECT_NAME}Linux${ARCH} SHARED + ${TARGET} SHARED JsKeyboardHandler.cpp ${CMAKE_JS_SRC} ) -set_target_properties(${PROJECT_NAME}Linux${ARCH} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME}Linux${ARCH} ${CMAKE_JS_LIB}) +set_target_properties(${TARGET} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${TARGET} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt index dce8726..d64033d 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -7,6 +7,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") option(ARCH "architecture") +set(TARGET ${PROJECT_NAME}Windows${ARCH}) + add_definitions(-D_WIN32_WINNT=0x600) add_definitions(-DNAPI_VERSION=5) add_definitions(-DARCH=${ARCH}) @@ -16,10 +18,12 @@ include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) include_directories("../../../nativeCommon/windows") add_library( - ${PROJECT_NAME}Windows${ARCH} SHARED + ${TARGET} SHARED JsKeyboardHandler.cpp ${CMAKE_JS_SRC} ) -set_target_properties(${PROJECT_NAME}Windows${ARCH} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME}Windows${ARCH} ${CMAKE_JS_LIB}) +target_link_libraries(${TARGET} $ENV{WINDOWS_NODE_LINK_DIR}/node.a) + +set_target_properties(${TARGET} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${TARGET} ${CMAKE_JS_LIB}) diff --git a/keyboard-kt/src/jsMain/cpp/windows/node.def b/keyboard-kt/src/jsMain/cpp/windows/node.def new file mode 100644 index 0000000..3eff3c4 --- /dev/null +++ b/keyboard-kt/src/jsMain/cpp/windows/node.def @@ -0,0 +1,206 @@ +LIBRARY node.exe +EXPORTS +napi_create_double +napi_get_boolean +napi_get_value_int32 +napi_get_boolean +napi_get_undefined +napi_get_value_int32 +napi_get_value_bool +napi_get_undefined +napi_create_string_utf8 +napi_create_threadsafe_function +napi_create_double +napi_get_undefined +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_double +napi_get_boolean +napi_get_undefined +napi_call_function +napi_call_threadsafe_function +napi_delete_reference +napi_async_destroy +napi_delete_reference +napi_fatal_error +napi_close_callback_scope +napi_get_last_error_info +napi_is_exception_pending +napi_create_string_utf8 +napi_create_error +napi_create_reference +napi_create_type_error +napi_get_and_clear_last_exception +napi_delete_reference +napi_create_double +napi_get_boolean +napi_get_undefined +napi_call_function +napi_close_handle_scope +napi_open_handle_scope +napi_get_reference_value +napi_throw +napi_close_handle_scope +napi_get_cb_info +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_get_named_property +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_value_string_utf8 +napi_open_handle_scope +napi_close_handle_scope +napi_create_string_utf8 +napi_create_error +napi_create_reference +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_get_reference_value +napi_close_escapable_handle_scope +napi_delete_reference +napi_escape_handle +napi_delete_reference +napi_module_register +napi_create_double +napi_get_boolean +napi_get_value_int32 +napi_get_boolean +napi_get_undefined +napi_get_value_int32 +napi_get_value_bool +napi_get_undefined +napi_create_string_utf8 +napi_create_threadsafe_function +napi_create_double +napi_get_undefined +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_function +napi_add_finalizer +napi_set_named_property +napi_create_double +napi_get_boolean +napi_get_undefined +napi_call_function +napi_call_threadsafe_function +tonapi_delete_reference +napi_async_destroy +napi_delete_reference +napi_fatal_error +napi_close_callback_scope +napi_get_last_error_info +napi_is_exception_pending +napi_create_string_utf8 +napi_create_error +napi_create_reference +napi_create_type_error +napi_get_and_clear_last_exception +napi_delete_reference +napi_create_double +napi_get_boolean +napi_get_undefined +napi_call_function +napi_close_handle_scope +napi_open_handle_scope +napi_get_reference_value +napi_throw +napi_close_handle_scope +napi_get_cb_info +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_get_named_property +napi_escape_handle +napi_close_escapable_handle_scope +napi_get_value_string_utf8 +napi_open_handle_scope +napi_close_handle_scope +napi_create_string_utf8 +napi_create_error +napi_create_reference +napi_get_reference_value +napi_open_escapable_handle_scope +napi_get_reference_value +napi_call_function +napi_is_exception_pending +napi_get_reference_value +napi_close_escapable_handle_scope +napi_delete_reference +napi_escape_handle +napi_delete_reference +napi_module_register \ No newline at end of file From f534f71afebf72622304e22edbacb752c2a0d128 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 30 Jan 2021 19:09:57 +0530 Subject: [PATCH 21/26] Minor fixes --- .../native_compile/NapiCompilationTask.kt | 2 +- .../native_compile/NativeCompilationPlugin.kt | 15 +++++++++------ keyboard-kt/build.gradle.kts | 4 ++-- keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt | 2 +- .../github/animeshz/keyboard/JsKeyboardHandler.kt | 6 ++++++ .../com/github/animeshz/keyboard/NativeUtils.kt | 6 +++--- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt index 240618f..8411018 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NapiCompilationTask.kt @@ -48,7 +48,7 @@ open class NapiCompilationTask @Inject constructor( "-c", "mkdir -p \$WORK_DIR/project/build/napi && " + "cmake-js compile --CDARCH=${target.arch} --arch=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " + - "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/{Release/,}KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + + "cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/{,Release/}KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " + "rm -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build" ) diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt index c4f137d..d80f5db 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/native_compile/NativeCompilationPlugin.kt @@ -7,7 +7,8 @@ import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.creating import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.register -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.gradle.kotlin.dsl.withType +import org.gradle.language.jvm.tasks.ProcessResources class NativeCompilationPlugin : Plugin { override fun apply(target: Project) { @@ -25,8 +26,6 @@ class NativeCompilationPlugin : Plugin { } val compileJniAll by project.tasks.creating { group = "nativeCompilation" } - project.tasks.getByName("jvmProcessResources") { dependsOn(compileJniAll) } - val headersTask = project.tasks .register("generateJniHeaders", extension.headers).get() @@ -46,8 +45,9 @@ class NativeCompilationPlugin : Plugin { } } - project.configure { - sourceSets.getByName("jvmMain").resources.srcDir(extension.compilation.outputDir) + tasks.withType().named("jvmProcessResources") { + dependsOn(compileJniAll) + from(project.file(extension.compilation.outputDir)) } } } @@ -70,7 +70,10 @@ class NativeCompilationPlugin : Plugin { } } - // Specify the js include dir + tasks.withType().named("jsProcessResources") { + dependsOn(compileNapiAll) + from(project.file(extension.outputDir)) + } } } diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index a39b9c8..6d41687 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -110,13 +110,13 @@ nativeCompilation { napi { baseInputPaths = listOf("src/jsMain/cpp", "src/nativeCommon") - outputDir = "build/jni" + outputDir = "build/napi" targets = listOf( Target("windows", "x64", "animeshz/keyboard-mouse-kt:cross-build-windows-x64"), Target("windows", "x86", "animeshz/keyboard-mouse-kt:cross-build-windows-x86"), Target("linux", "x64", "animeshz/keyboard-mouse-kt:cross-build-linux-x64") - // NodeJS doesn't ship in x86, so people must be building the nodejs theirselves, so supporting it is not really necessary for now + // NodeJS doesn't ship in x86, so people must be building the nodejs their selves, so supporting it is not really necessary for now // Target("linux", "x86", "animeshz/keyboard-mouse-kt:cross-build-linux-x86") ) } diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt index bc4e02d..a02ac50 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") option(ARCH "architecture") -set(TARGET ${PROJECT_NAME}Windows${ARCH}) +set(TARGET ${PROJECT_NAME}Linux${ARCH}) add_definitions(-DARCH=${ARCH}) diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt index ec28fb4..427dd3e 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt @@ -10,6 +10,12 @@ import kotlinx.coroutines.flow.onEach @ExperimentalKeyIO internal object KotlinJsKeyboardHandler : NativeKeyboardHandlerBase() { + init { + if (NApiNativeHandler.init() != 0) { + error("Native initialization failed") + } + } + override fun sendEvent(keyEvent: KeyEvent) { NApiNativeHandler.send(keyEvent.key.keyCode, keyEvent.state.isPressed()) } diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index bd04338..49a60d5 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -15,11 +15,11 @@ private val identifier = when(val platform = platform()) { } @ExperimentalKeyIO -internal val NApiNativeHandler: NApiNativeHandlerI = - require("./lib/KeyboardKt$identifier$suffix.node").unsafeCast() +internal val NApiNativeHandler: INApiNativeHandler = + require("./lib/KeyboardKt$identifier$suffix.node").unsafeCast() @ExperimentalKeyIO -internal interface NApiNativeHandlerI { +internal interface INApiNativeHandler { fun send(scanCode: Int, isPressed: Boolean) fun isPressed(scanCode: Int): Boolean From 0dcb137626e3c9fc455dbaa051b3ae617f27734f Mon Sep 17 00:00:00 2001 From: Animeshz Date: Sat, 30 Jan 2021 23:41:15 +0530 Subject: [PATCH 22/26] Setup npm-publish. --- README.md | 2 +- .../publishing/PublishingPlugin.kt | 2 +- keyboard-kt/build.gradle.kts | 24 ++++++++++++++ keyboard-kt/src/jsMain/package.template.json | 33 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 keyboard-kt/src/jsMain/package.template.json diff --git a/README.md b/README.md index 254d3e2..977ac08 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ __Notice: Project is on hold till about one month or somewhat longer (I'm busy). ## What is KeyboardMouse.kt -KeyboardMouse.kt is a lightweight, coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin and Java. +KeyboardMouse.kt is a lightweight, coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin, Java and NodeJS. We aim to provide high-level as well as high-performant low-level access to such APIs. See the documentation below to know more! diff --git a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt index f91377b..d41ea7e 100644 --- a/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt +++ b/composite-build-src/src/main/kotlin/com/github/animeshz/keyboard_mouse/publishing/PublishingPlugin.kt @@ -21,7 +21,7 @@ class PublishingPlugin : Plugin { target.apply(plugin = "maven-publish") target.afterEvaluate { - val repository = ext.repository ?: error("publishingConfig.repository must not be null") + val repository = ext.repository ?: return@afterEvaluate println("publishingConfig.repository is missing, skipping...") target.extensions.configure("publishing") { repositories { diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 6d41687..b2ea9c8 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -8,6 +8,7 @@ plugins { kotlin("multiplatform") id("keyboard-mouse-native-compile") id("keyboard-mouse-publishing") + id("lt.petuska.npm.publish") version "1.0.4" id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } @@ -89,6 +90,29 @@ publishingConfig { password = System.getenv("BINTRAY_KEY") } +npmPublishing { + repositories { + repository("npmjs") { + registry = uri("https://registry.npmjs.org") + authToken = System.getenv("NPM_TOKEN") + } + } + + publications { + val js by getting { + files { + from(project.file("build/napi")) + } + + packageJsonFile = project.file("src/jsMain/package.template.json") + packageJson { + version = project.version as String + readme = rootProject.file("README.md") + } + } + } +} + nativeCompilation { jni { headers { diff --git a/keyboard-kt/src/jsMain/package.template.json b/keyboard-kt/src/jsMain/package.template.json new file mode 100644 index 0000000..aae515d --- /dev/null +++ b/keyboard-kt/src/jsMain/package.template.json @@ -0,0 +1,33 @@ +{ + "name": "keyboard-kt", + "description": "A lightweight multiplatform library for interacting with global keyboard and mouse events and states from Kotlin, Java and NodeJS.", + "keywords": [ + "kotlin", + "kotlin", + "java", + "keyboard", + "mouse", + "java-native-interface", + "hotkey", + "multiplatform", + "keyboard-listeners", + "global-events", + "keyboard-events", + "mouse-events", + "keyboard-hooks", + "keyboard-state", + "multiplatform-kotlin-library" + ], + "homepage": "https://animeshz.github.io/keyboard-mouse-kt", + "licence": "MIT", + "author": { + "name": "Animesh Sahu", + "email": "animeshsahu19@yahoo.com" + }, + "repository": { + "url": "git+https://animeshz.github.io/keyboard-mouse-kt.git" + }, + "bugs": { + "url": "https://animeshz.github.io/keyboard-mouse-kt/issues" + } +} From b930df4f7b0e677d3121d330be8bfaa9131e5d4d Mon Sep 17 00:00:00 2001 From: Animeshz Date: Tue, 2 Feb 2021 16:24:41 +0530 Subject: [PATCH 23/26] Optimizations and fully setup the NodeJS in Windows and Linux. --- build.gradle.kts | 2 +- keyboard-kt/build.gradle.kts | 11 +++++++++-- keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt | 4 ++-- keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt | 4 ++-- .../src/jsMain/cpp/windows/JsKeyboardHandler.cpp | 4 ++-- keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt | 4 ++-- .../src/nativeCommon/linux/X11KeyboardHandler.cpp | 2 +- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d1a1b62..0671cbb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { allprojects { this.group = "com.github.animeshz" - this.version = "0.2.5" + this.version = "0.3.0" repositories { mavenCentral() diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index b2ea9c8..9a38d9c 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -2,13 +2,14 @@ import com.github.animeshz.keyboard_mouse.native_compile.Target import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile plugins { kotlin("multiplatform") id("keyboard-mouse-native-compile") id("keyboard-mouse-publishing") - id("lt.petuska.npm.publish") version "1.0.4" + id("lt.petuska.npm.publish") version "1.1.1" id("org.jlleitschuh.gradle.ktlint") version "9.4.1" } @@ -104,7 +105,9 @@ npmPublishing { from(project.file("build/napi")) } - packageJsonFile = project.file("src/jsMain/package.template.json") + bundleKotlinDependencies = false + shrinkwrapBundledDependencies = false + packageJsonTemplateFile = project.file("src/jsMain/package.template.json") packageJson { version = project.version as String readme = rootProject.file("README.md") @@ -157,6 +160,10 @@ tasks.withType { tasks.withType { kotlinOptions.jvmTarget = "1.8" } +tasks.withType { + kotlinOptions.sourceMap = false +} + tasks.getByName("jvmTest") { useJUnitPlatform() } diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt index a02ac50..f1a0f85 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt index d64033d..b77d872 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -static-libgcc -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -static-libgcc -static-libstdc++ -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") diff --git a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp index c836aa0..0b95473 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp +++ b/keyboard-kt/src/jsMain/cpp/windows/JsKeyboardHandler.cpp @@ -55,12 +55,12 @@ void EmitEventToJs(Napi::Env env, Napi::Function callback, std::nullptr_t* conte } if (data != NULL) { - delete data; + free(data); } } void EmitEventToTSCallback(int scanCode, bool isPressed) { - EventData* data = new EventData; + EventData* data = (EventData *) malloc(sizeof(EventData)); data->scanCode = scanCode; data->isPressed = isPressed; diff --git a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt index 1aa562a..2a91099 100644 --- a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") diff --git a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp index 266e6e8..037fa9b 100644 --- a/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp +++ b/keyboard-kt/src/nativeCommon/linux/X11KeyboardHandler.cpp @@ -130,7 +130,7 @@ class X11KeyboardHandler : BaseKeyboardHandler { Window root = XDefaultRootWindow(display); int maskLen = XIMaskLen(XI_LASTEVENT); - unsigned char mask[maskLen]; + unsigned char mask[maskLen] = { 0 }; XIEventMask xiMask; xiMask.deviceid = XIAllMasterDevices; From 5204d85697dac3c81edda6f6caa28dfb1d51ec1f Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 4 Feb 2021 15:13:10 +0530 Subject: [PATCH 24/26] Update Docs and add optimization options in Native builds. --- docs/docs/contributing.md | 2 +- docs/docs/index.md | 2 +- docs/docs/keyboard/high-level-api.md | 36 +++++++++++++ docs/docs/keyboard/key.md | 2 + docs/docs/keyboard/low-level-api.md | 34 +++++++++++- docs/docs/status-and-installation.md | 54 +++++++++++++++++-- keyboard-kt/build.gradle.kts | 5 +- .../src/jsMain/cpp/linux/CMakeLists.txt | 5 +- .../src/jsMain/cpp/windows/CMakeLists.txt | 5 +- .../animeshz/keyboard/JsKeyboardHandler.kt | 6 +-- .../github/animeshz/keyboard/NativeUtils.kt | 4 +- keyboard-kt/src/jsMain/package.template.json | 10 ++++ .../src/jvmMain/cpp/linux/CMakeLists.txt | 4 +- .../src/jvmMain/cpp/windows/CMakeLists.txt | 4 +- 14 files changed, 153 insertions(+), 20 deletions(-) diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index e5e8b36..6dc258e 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -49,7 +49,7 @@ Following are the future plans for the project: To build and publish to mavenLocal: `$ ./gradlew build publishToMavenLocal` -The only requirement is to install Docker when building for JVM due to cross-compilation requirement of JNI native libs to be able to pack the full Jar from any platform that is supported cross-platform. +The only requirement is to install Docker when building for JVM & JS due to cross-compilation requirement of JNI & NApi native libs. If you ever get clock gets skewed (and Makefile get modified in the future) at the time of compilation of C++ sources, please restart the docker from the system tray. diff --git a/docs/docs/index.md b/docs/docs/index.md index 2a2a33d..abd873f 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -22,7 +22,7 @@ __KeyboardMouse.kt is still in an experimental stage, as such we can't guarantee ## What is KeyboardMouse.kt -KeyboardMouse.kt is a lightweight (~60Kb per native platform, and ~70Kb on JVM), coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin and Java. +KeyboardMouse.kt is a lightweight, coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin, Java and NodeJS. We aim to provide high-level as well as high-performant low-level access to such APIs. See the usage (Keyboard/Mouse) section below to know more! diff --git a/docs/docs/keyboard/high-level-api.md b/docs/docs/keyboard/high-level-api.md index 00fd2a6..3cb3a36 100644 --- a/docs/docs/keyboard/high-level-api.md +++ b/docs/docs/keyboard/high-level-api.md @@ -4,6 +4,8 @@ **Java:** High Level API depends on [JKeyboard][4]. +**NodeJS:** High Level API depends on [JsKeyboard][5]. + ## Adding a shortcut (Hotkey). === "Kotlin" @@ -14,6 +16,13 @@ ``` **Note: The lambda is in suspend context, launched in context provided at time of instantiation of Keyboard (defaults to Dispatchers.Default).** +=== "NodeJS" + ```js + keyboard.addShortcut('LeftCtrl + E', true, + () => console.log("triggered") + ); + ``` + === "Java 8" ```java Set keys = new HashSet<>(); @@ -42,6 +51,11 @@ keyboard.send(Key.LeftAlt + Key.M) ``` +=== "NodeJS" + ```js + keyboard.send('LeftAlt + M'); + ``` + === "Java 8" ```java Set keys = new HashSet<>(); @@ -64,6 +78,11 @@ keyboard.write("Hello Keyboard!") ``` +=== "NodeJS" + ```js + keyboard.write('Hello Keyboard!'); + ``` + === "Java 8 or above" ```java keyboard.write("Hello Keyboard!"); @@ -78,6 +97,11 @@ Suspensive wait in Kotlin, whereas asynchronous `CompletableFuture<>` for Java keyboard.awaitTill(Key.LeftCtrl + Key.LeftShift + Key.R, trigger = KeyState.KeyDown) ``` +=== "NodeJS" + ```js + await keyboard.completeWhenPressed('LeftCtrl + LeftShift + R'); + ``` + === "Java 8" ```java Set keys = new HashSet<>(); @@ -109,6 +133,11 @@ Recorded KeyPresses is pushed into a [KeyPressSequence][1] (`List keys = new HashSet<>(); @@ -134,6 +163,11 @@ Recorded KeyPresses is pushed into a [KeyPressSequence][1] (`List onFinish = keyboard.play(records, 1.25) @@ -148,3 +182,5 @@ Recorded KeyPresses is pushed into a [KeyPressSequence][1] (`List**Note: For NodeJS, strings are used instead of these enums, but their names are the same.** + The full list of all the supported Keys are the following: ```kotlin diff --git a/docs/docs/keyboard/low-level-api.md b/docs/docs/keyboard/low-level-api.md index 2425723..afca8aa 100644 --- a/docs/docs/keyboard/low-level-api.md +++ b/docs/docs/keyboard/low-level-api.md @@ -4,6 +4,8 @@ **Java:** Low Level API depends on [JNativeKeyboardHandler][4] that can be obtained via `JNativeKeyboardHandler.INSTANCE`. +**NodeJS:** Low Level API depends on [JsKeyboardHandler][5] that can be obtained via `JNativeKeyboardHandler` (`const handler = require('keyboard-kt').com.github.animeshz.keyboard.JsKeyboardHandler`). This large import is due to limitations of K/JS to not able to export to global namespace currently, see [KT-37710](https://youtrack.jetbrains.com/issue/KT-37710). + ## Listening to events using Flow (Kotlin) or callback (Java). === "Kotlin" @@ -14,6 +16,15 @@ .collect { println(it) } ``` +=== "NodeJS" + ```js + handler.addHandler((key, pressed) => { + if (keyEvent.state == KeyState.KeyDown) { + console.log(keyEvent.key); + } + }); + ``` + === "Java 8 or above" ```java handler.addHandler(keyEvent -> { @@ -30,6 +41,11 @@ handler.sendEvent(KeyEvent(Key.A, KeyState.KeyDown)) ``` +=== "NodeJS" + ```js + handler.sendEvent('A+KeyDown'); + ``` + === "Java 8 or above" ```java handler.sendEvent(new KeyEvent(Key.A, KeyState.KeyDown)); @@ -44,8 +60,15 @@ handler.getKeyState(Key.RightAlt) ``` +=== "NodeJS" + ```kotlin + handler.getKeyState('A'); + handler.getKeyState('RightAlt'); + ``` + **Note: In JS it returns a boolean** + === "Java 8 or above" - ```java + ```js handler.getKeyState(Key.A); handler.getKeyState(Key.RightAlt); ``` @@ -59,6 +82,13 @@ handler.isScrollLockOn() ``` +=== "NodeJS" + ```js + handler.isCapsLockOn(); + handler.isNumLockOn(); + handler.isScrollLockOn(); + ``` + === "Java 8 or above" ```java handler.isCapsLockOn(); @@ -73,3 +103,5 @@ [3]: https://github.com/Animeshz/keyboard-mouse-kt/blob/master/keyboard-kt/src/commonMain/kotlin/com/github/animeshz/keyboard/events/KeyEvent.kt [4]: https://github.com/Animeshz/keyboard-mouse-kt/blob/master/integration/keyboard-kt-jdk8/src/main/kotlin/com/github/animeshz/keyboard/JNativeKeyboardHandler.kt + +[5]: https://github.com/Animeshz/keyboard-mouse-kt/blob/master/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt#L63 \ No newline at end of file diff --git a/docs/docs/status-and-installation.md b/docs/docs/status-and-installation.md index 6cedffb..f9bdc3b 100644 --- a/docs/docs/status-and-installation.md +++ b/docs/docs/status-and-installation.md @@ -12,9 +12,9 @@ - [ ] Linux Arm64 - [ ] MacOS - [ ] JS - - [ ] Windows x86_64 (64 bit) - - [ ] Windows x86 (32 bit) - - [ ] Linux x86_64 (64 bit) + - [X] Windows x86_64 (64 bit) + - [X] Windows x86 (32 bit) + - [X] Linux x86_64 (64 bit) - [ ] Linux x86 (32 bit) - [ ] Linux Arm32 - [ ] Linux Arm64 @@ -64,6 +64,22 @@ implementation("com.github.animeshz:mouse-kt-jvm:") } ``` + + === "Kotlin/JS" + ```kotlin + plugins { + kotlin("js") version "" + } + + repositories { + maven(url = "https://dl.bintray.com/animeshz/maven") + } + + dependencies { + implementation("com.github.animeshz:keyboard-kt-js:") + implementation("com.github.animeshz:mouse-kt-js:") + } + ``` === "Java/JVM" ```kotlin @@ -98,6 +114,7 @@ kotlin { // Your targets jvm() + js() // IR not supported right now, but will be soon. mingwX64 { binaries { executable { entryPoint = "main" } } } @@ -145,6 +162,22 @@ implementation("com.github.animeshz:mouse-kt-jvm:") } ``` + + === "Kotlin/JVM" + ```groovy + plugins { + id "kotlin-js" version "" + } + + repositories { + maven { url "https://dl.bintray.com/animeshz/maven" } + } + + dependencies { + implementation("com.github.animeshz:keyboard-kt-js:") + implementation("com.github.animeshz:mouse-kt-js:") + } + ``` === "Java/JVM" ```groovy @@ -179,6 +212,7 @@ kotlin { // Your targets jvm() + js() // IR not supported right now, but will be soon. mingwX64 { binaries { executable { entryPoint = "main" } } } @@ -269,6 +303,20 @@ ``` +=== "NPM/NodeJS (package.json)" + + === "JS/NodeJS" + ```json + { + "name": "", + "version": "", + + "dependencies": { + "keyboard-kt": "^0.3.0" + } + } + ``` + ## Use interactively with Jupyter Notebook Don't have time to setup a Gradle/Maven project? No worries, roll up a [Kotlin's Jupyter Kernel](https://github.com/Kotlin/kotlin-jupyter), and use it as a REPL (it even has kotlin-autocompletion). diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 9a38d9c..085f60a 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -15,9 +15,12 @@ plugins { kotlin { jvm() - js { + js(IR) { + moduleName = "keyboard-kt" + useCommonJs() nodejs() + binaries.library() } linuxX64 { val main by compilations.getting diff --git a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt index f1a0f85..f08e515 100644 --- a/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/linux/CMakeLists.txt @@ -2,14 +2,15 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -Os -fno-rtti -fno-exceptions -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") set(TARGET ${PROJECT_NAME}Linux${ARCH}) add_definitions(-DARCH=${ARCH}) +add_definitions(-DNAPI_DISABLE_CPP_EXCEPTIONS) include_directories(${CMAKE_JS_INC}) include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) diff --git a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt index b77d872..2b402ef 100644 --- a/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jsMain/cpp/windows/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -static-libgcc -fdata-sections -ffunction-sections -Wl,--gc-sections") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -static-libgcc -static-libstdc++ -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++ -s -Os -fno-rtti -fno-exceptions -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") @@ -12,6 +12,7 @@ set(TARGET ${PROJECT_NAME}Windows${ARCH}) add_definitions(-D_WIN32_WINNT=0x600) add_definitions(-DNAPI_VERSION=5) add_definitions(-DARCH=${ARCH}) +add_definitions(-DNAPI_DISABLE_CPP_EXCEPTIONS) include_directories(${CMAKE_JS_INC}) include_directories($ENV{NODE_ADDON_API_HEADERS_DIR}) diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt index 427dd3e..dabb5f3 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboardHandler.kt @@ -70,12 +70,12 @@ public object JsKeyboardHandler { } @JsName("send") - public fun NativeKeyboardHandler.sendEvent(key: String, isPressed: Boolean): Unit = + public fun sendEvent(key: String, isPressed: Boolean): Unit = KotlinJsKeyboardHandler.sendEvent(KeyEvent(key.toKey(), isPressed.toKeyState())) @JsName("getKeyState") - public fun NativeKeyboardHandler.getKeyState(key: String): Boolean = - getKeyState(key.toKey()).isPressed() + public fun getKeyState(key: String): Boolean = + KotlinJsKeyboardHandler.getKeyState(key.toKey()).isPressed() @JsName("isCapsLockOn") public fun isCapsLockOn(): Boolean = KotlinJsKeyboardHandler.isCapsLockOn() diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt index 49a60d5..a689bec 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/NativeUtils.kt @@ -16,10 +16,10 @@ private val identifier = when(val platform = platform()) { @ExperimentalKeyIO internal val NApiNativeHandler: INApiNativeHandler = - require("./lib/KeyboardKt$identifier$suffix.node").unsafeCast() + require("./KeyboardKt$identifier$suffix.node").unsafeCast() @ExperimentalKeyIO -internal interface INApiNativeHandler { +internal external interface INApiNativeHandler { fun send(scanCode: Int, isPressed: Boolean) fun isPressed(scanCode: Int): Boolean diff --git a/keyboard-kt/src/jsMain/package.template.json b/keyboard-kt/src/jsMain/package.template.json index aae515d..c906079 100644 --- a/keyboard-kt/src/jsMain/package.template.json +++ b/keyboard-kt/src/jsMain/package.template.json @@ -1,6 +1,16 @@ { "name": "keyboard-kt", "description": "A lightweight multiplatform library for interacting with global keyboard and mouse events and states from Kotlin, Java and NodeJS.", + "dependencies": { + "kotlinx-coroutines-core": "*", + "kotlinx-atomicfu": "*", + "kotlin": "*", + "kotlin-test-js-runner": "*", + "kotlin-test": "*" + }, + "devDependencies": { + "dukat": "0.5.8-rc.3" + }, "keywords": [ "kotlin", "kotlin", diff --git a/keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt index db67c25..d17536b 100644 --- a/keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/cpp/linux/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") diff --git a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt index 2a91099..4852cb3 100644 --- a/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt +++ b/keyboard-kt/src/jvmMain/cpp/windows/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) project(KeyboardKt) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -Os -fdata-sections -ffunction-sections -Wl,--gc-sections") option(ARCH "architecture") From fda2a32e360b106798412e51ef5e8a3c0dad8a84 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 4 Feb 2021 15:29:44 +0530 Subject: [PATCH 25/26] Fix infinite recursion caused by default parameter before non-default. --- .../kotlin/com/github/animeshz/keyboard/JsKeyboard.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt index d716dbc..a573011 100644 --- a/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt +++ b/keyboard-kt/src/jsMain/kotlin/com/github/animeshz/keyboard/JsKeyboard.kt @@ -29,6 +29,7 @@ public class TimedKeyEvent( public class JsKeyboard { private val delegate = Keyboard() + @JsName("handler") public val handler: JsKeyboardHandler = JsKeyboardHandler private fun parseKeySet(str: String): KeySet = @@ -46,7 +47,7 @@ public class JsKeyboard { @JsName("addShortcut") public fun addShortcut( keySet: String, - triggerOnPressed: Boolean = true, + triggerOnPressed: Boolean, handler: () -> Unit ): Cancellable { return delegate.addShortcut(parseKeySet(keySet), triggerOnPressed.toKeyState()) { handler() } @@ -63,6 +64,7 @@ public class JsKeyboard { /** * Writes the following [string] on the host machine. */ + @JsName("write") public fun write(string: String) { delegate.write(string) } @@ -84,6 +86,7 @@ public class JsKeyboard { * Records and returns a [KeyPressSequence] of all the keypress till a [keySet] is/are pressed. */ @ExperimentalTime + @JsName("recordKeyPressesTill") public fun recordKeyPressesTill( keySet: String, triggerOnPressed: Boolean = true @@ -100,6 +103,7 @@ public class JsKeyboard { * @return A [Promise] for subscribing to get notified when does play finishes. */ @ExperimentalTime + @JsName("play") public fun play(orderedPresses: Array, speedFactor: Double = 1.0): Promise = GlobalScope.promise { val sequence = orderedPresses.map { it.durationInSeconds.seconds to KeyEvent(it.key.toKey(), it.isPressed.toKeyState()) } @@ -110,6 +114,7 @@ public class JsKeyboard { /** * Disposes this [Keyboard] instance. */ + @JsName("dispose") public fun dispose() { delegate.dispose() } From 56a3b6522bea9fbcf1061e6542e5e65621f65697 Mon Sep 17 00:00:00 2001 From: Animeshz Date: Thu, 4 Feb 2021 15:55:59 +0530 Subject: [PATCH 26/26] Fix README file. --- build.gradle.kts | 2 +- keyboard-kt/build.gradle.kts | 3 ++- keyboard-kt/src/jsMain/package.template.json | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0671cbb..1d6f72a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { allprojects { this.group = "com.github.animeshz" - this.version = "0.3.0" + this.version = "0.3.1" repositories { mavenCentral() diff --git a/keyboard-kt/build.gradle.kts b/keyboard-kt/build.gradle.kts index 085f60a..b90da5b 100644 --- a/keyboard-kt/build.gradle.kts +++ b/keyboard-kt/build.gradle.kts @@ -95,6 +95,8 @@ publishingConfig { } npmPublishing { + readme = project.rootProject.file("README.md") + repositories { repository("npmjs") { registry = uri("https://registry.npmjs.org") @@ -113,7 +115,6 @@ npmPublishing { packageJsonTemplateFile = project.file("src/jsMain/package.template.json") packageJson { version = project.version as String - readme = rootProject.file("README.md") } } } diff --git a/keyboard-kt/src/jsMain/package.template.json b/keyboard-kt/src/jsMain/package.template.json index c906079..9845f4f 100644 --- a/keyboard-kt/src/jsMain/package.template.json +++ b/keyboard-kt/src/jsMain/package.template.json @@ -4,9 +4,7 @@ "dependencies": { "kotlinx-coroutines-core": "*", "kotlinx-atomicfu": "*", - "kotlin": "*", - "kotlin-test-js-runner": "*", - "kotlin-test": "*" + "kotlin": "*" }, "devDependencies": { "dukat": "0.5.8-rc.3" @@ -35,7 +33,7 @@ "email": "animeshsahu19@yahoo.com" }, "repository": { - "url": "git+https://animeshz.github.io/keyboard-mouse-kt.git" + "url": "https://animeshz.github.io/keyboard-mouse-kt" }, "bugs": { "url": "https://animeshz.github.io/keyboard-mouse-kt/issues"