├── .gitignore ├── modules ├── common │ ├── common.gradle.kts │ └── src │ │ └── nativeMain │ │ └── kotlin │ │ ├── Logger.kt │ │ └── Either.kt ├── io │ ├── src │ │ └── nativeMain │ │ │ ├── cinterop │ │ │ └── glibc.def │ │ │ └── kotlin │ │ │ ├── FileUtil.kt │ │ │ ├── Environment.kt │ │ │ └── File.kt │ └── io.gradle.kts ├── tomlParser │ ├── src │ │ ├── nativeMain │ │ │ ├── kotlin │ │ │ │ ├── ParseException.kt │ │ │ │ ├── TomlValueContainer.kt │ │ │ │ ├── TomlParser.kt │ │ │ │ ├── internal │ │ │ │ │ └── TomlC99Extensions.kt │ │ │ │ ├── TomlTable.kt │ │ │ │ └── TomlArray.kt │ │ │ ├── cinterop │ │ │ │ └── tomlc99.def │ │ │ └── c │ │ │ │ └── tomlc99 │ │ │ │ ├── .editorconfig │ │ │ │ ├── .gitignore │ │ │ │ ├── Makefile │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── toml.h │ │ │ │ └── toml.c │ │ └── nativeTest │ │ │ └── kotlin │ │ │ ├── util │ │ │ └── successResultOrFail.kt │ │ │ ├── TomlParserTest.kt │ │ │ ├── TomlTableTest.kt │ │ │ └── TomlArrayTest.kt │ └── tomlParser.gradle.kts ├── app │ ├── src │ │ └── nativeMain │ │ │ └── kotlin │ │ │ ├── internal │ │ │ ├── Util.kt │ │ │ └── ErrorMessages.kt │ │ │ ├── main.kt │ │ │ └── Application.kt │ └── app.gradle.kts ├── xcb │ ├── src │ │ └── nativeMain │ │ │ ├── cinterop │ │ │ └── xcb.def │ │ │ └── kotlin │ │ │ ├── internal │ │ │ ├── ResponseType.kt │ │ │ ├── XcbNone.kt │ │ │ ├── GrabMode.kt │ │ │ ├── ModifierMask.kt │ │ │ ├── EventMask.kt │ │ │ ├── Connection.kt │ │ │ ├── Setup.kt │ │ │ ├── Screen.kt │ │ │ └── EventWrapping.kt │ │ │ └── Xcb.kt │ └── xcb.gradle.kts └── config │ ├── config.gradle.kts │ └── src │ ├── nativeMain │ └── kotlin │ │ ├── Configuration.kt │ │ ├── internal │ │ ├── v1 │ │ │ ├── V1ConfigurationValidation.kt │ │ │ └── V1ConfigurationLoader.kt │ │ └── ConfigurationLoader.kt │ │ └── InvalidConfiguration.kt │ └── nativeTest │ └── kotlin │ └── ConfigurationTest.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .editorconfig ├── gradle.properties ├── config.toml.sample ├── settings.gradle.kts ├── README.md ├── gradlew.bat └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build/ 4 | 5 | /config.toml 6 | -------------------------------------------------------------------------------- /modules/common/common.gradle.kts: -------------------------------------------------------------------------------- 1 | kotlin { 2 | strictExplicitApi() 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edrd-f/xmg/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /modules/io/src/nativeMain/cinterop/glibc.def: -------------------------------------------------------------------------------- 1 | headers = unistd.h 2 | package = internal.cinterop.glibc 3 | compilerOpts = -D_GNU_SOURCE 4 | linkerOpts = -lc 5 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeMain/kotlin/ParseException.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.toml 2 | 3 | public class ParseException(message: String) : Exception(message) 4 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeMain/cinterop/tomlc99.def: -------------------------------------------------------------------------------- 1 | # Static library linking options can be found in this modules' Gradle config 2 | headers = toml.h toml.c 3 | package = internal.cinterop.tomlc99 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = tab 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.mpp.enableGranularSourceSetsMetadata=true 3 | kotlin.mpp.stability.nowarn=true 4 | kotlin.native.enableDependencyPropagation=false 5 | kotlinCoroutinesVersion=1.5.0-RC 6 | -------------------------------------------------------------------------------- /modules/app/src/nativeMain/kotlin/internal/Util.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.internal 2 | 3 | import kotlin.system.exitProcess 4 | 5 | fun fail(message: String): Nothing { 6 | println(message) 7 | exitProcess(1) 8 | } 9 | -------------------------------------------------------------------------------- /modules/io/io.gradle.kts: -------------------------------------------------------------------------------- 1 | kotlin { 2 | nativeTarget.compilations["main"].cinterops { 3 | create("glibc") { 4 | defFile("src/nativeMain/cinterop/glibc.def") 5 | includeDirs("/usr/include") 6 | } 7 | } 8 | strictExplicitApi() 9 | } 10 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/cinterop/xcb.def: -------------------------------------------------------------------------------- 1 | headers = xcb/xcb.h 2 | headerFilter = xcb/** 3 | package = internal.cinterop.xcb 4 | strictEnums = xcb_event_mask_t xcb_grab_mode_t xcb_mod_mask_t 5 | linkerOpts = -L/usr/lib -lxcb 6 | libraryPaths = /usr/lib 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/kotlin/internal/ResponseType.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.xcb.internal 2 | 3 | import internal.cinterop.xcb.XCB_BUTTON_PRESS 4 | 5 | internal object ResponseType { 6 | const val ButtonPress = XCB_BUTTON_PRESS 7 | } 8 | -------------------------------------------------------------------------------- /modules/io/src/nativeMain/kotlin/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.io 2 | 3 | public val File.extension: String? get() = 4 | path 5 | .substringAfterLast('/') 6 | .substringAfterLast('.', missingDelimiterValue = "") 7 | .ifBlank { null } 8 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/kotlin/internal/XcbNone.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.xcb.internal 2 | 3 | import internal.cinterop.xcb.XCB_NONE 4 | import internal.cinterop.xcb.xcb_cursor_t 5 | import kotlinx.cinterop.convert 6 | 7 | internal val XcbNone = XCB_NONE.convert() 8 | -------------------------------------------------------------------------------- /modules/io/src/nativeMain/kotlin/Environment.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.io 2 | 3 | import kotlinx.cinterop.toKStringFromUtf8 4 | import platform.posix.getenv 5 | 6 | public object Environment { 7 | public operator fun get(name: String): String? = getenv(name)?.toKStringFromUtf8() 8 | } 9 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeMain/c/tomlc99/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | trim_trailing_whitespace = true 7 | 8 | [*.{c,h}] 9 | indent_style = tab 10 | indent_size = 4 11 | 12 | [Makefile] 13 | indent_style = tab 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /modules/config/config.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.bnorm.power.kotlin-power-assert") version "0.10.0" 3 | } 4 | 5 | kotlin { 6 | sourceSets.getByName("nativeMain") { 7 | dependencies { 8 | implementation(project(":common")) 9 | implementation(project(":tomlParser")) 10 | } 11 | } 12 | 13 | strictExplicitApi() 14 | } 15 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeMain/kotlin/TomlValueContainer.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.toml 2 | 3 | public interface TomlValueContainer { 4 | public fun getLong(key: K): Long? 5 | 6 | public fun getString(key: K): String? 7 | 8 | public fun getArray(key: K): TomlArray? 9 | 10 | public fun getTable(key: K): TomlTable? 11 | } 12 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/kotlin/internal/GrabMode.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.xcb.internal 2 | 3 | import internal.cinterop.xcb.uint8_t 4 | import internal.cinterop.xcb.xcb_grab_mode_t.XCB_GRAB_MODE_ASYNC 5 | import kotlinx.cinterop.convert 6 | 7 | internal object GrabMode { 8 | val Async = XCB_GRAB_MODE_ASYNC.value.convert() 9 | } 10 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/kotlin/internal/ModifierMask.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.xcb.internal 2 | 3 | import internal.cinterop.xcb.uint16_t 4 | import internal.cinterop.xcb.xcb_mod_mask_t.XCB_MOD_MASK_ANY 5 | import kotlinx.cinterop.convert 6 | 7 | internal object ModifierMask { 8 | val Any = XCB_MOD_MASK_ANY.value.convert() 9 | } 10 | -------------------------------------------------------------------------------- /modules/xcb/src/nativeMain/kotlin/internal/EventMask.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.xcb.internal 2 | 3 | import internal.cinterop.xcb.uint16_t 4 | import internal.cinterop.xcb.xcb_event_mask_t.XCB_EVENT_MASK_BUTTON_PRESS 5 | import kotlinx.cinterop.convert 6 | 7 | internal object EventMask { 8 | val ButtonPress = XCB_EVENT_MASK_BUTTON_PRESS.value.convert() 9 | } 10 | -------------------------------------------------------------------------------- /config.toml.sample: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[mappings]] 4 | button = 8 5 | command = ''' 6 | qdbus org.kde.kglobalaccel 7 | /component/kwin invokeShortcut "ExposeAll" 8 | ''' 9 | 10 | [[mappings]] 11 | button = 9 12 | command = ''' 13 | dbus-send 14 | --type=method_call 15 | --dest=org.mpris.MediaPlayer2.spotify 16 | /org/mpris/MediaPlayer2 17 | org.mpris.MediaPlayer2.Player.PlayPause 18 | ''' 19 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeTest/kotlin/util/successResultOrFail.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import io.gitlab.edrd.xmousegrabber.common.Either 4 | import kotlin.test.fail 5 | 6 | fun Either.successResultOrFail(): B = when (this) { 7 | is Either.Left -> { 8 | val actual = "${this.value::class.qualifiedName}(${this.value.message})" 9 | fail("Expected success result but got $actual instead") 10 | } 11 | is Either.Right -> this.value 12 | } 13 | -------------------------------------------------------------------------------- /modules/xcb/xcb.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlinCoroutinesVersion: String by project 2 | 3 | kotlin { 4 | nativeTarget.compilations["main"].cinterops { 5 | create("xcb") { 6 | defFile("src/nativeMain/cinterop/xcb.def") 7 | includeDirs("/usr/include") 8 | } 9 | } 10 | 11 | sourceSets.getByName("nativeMain") { 12 | dependencies { 13 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") 14 | } 15 | } 16 | 17 | strictExplicitApi() 18 | } 19 | -------------------------------------------------------------------------------- /modules/tomlParser/src/nativeMain/c/tomlc99/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Object files 4 | *.o 5 | *.ko 6 | *.obj 7 | *.elf 8 | 9 | # Precompiled Headers 10 | *.gch 11 | *.pch 12 | 13 | # Libraries 14 | *.lib 15 | *.a 16 | *.la 17 | *.lo 18 | 19 | # Shared objects (inc. Windows DLLs) 20 | *.dll 21 | *.so 22 | *.so.* 23 | *.dylib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | *.i*86 30 | *.x86_64 31 | *.hex 32 | toml_cat 33 | toml_json 34 | toml_sample 35 | 36 | # Debug files 37 | *.dSYM/ 38 | *.su 39 | -------------------------------------------------------------------------------- /modules/app/app.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlinCoroutinesVersion: String by project 2 | 3 | kotlin { 4 | nativeTarget.apply { 5 | binaries.executable { 6 | entryPoint = "io.gitlab.edrd.xmousegrabber.main" 7 | } 8 | } 9 | 10 | sourceSets.getByName("nativeMain") { 11 | dependencies { 12 | implementation(project(":config")) 13 | implementation(project(":io")) 14 | implementation(project(":xcb")) 15 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "x-mouse-grabber" 2 | 3 | val modulesDir = file("modules") 4 | 5 | requireNotNull(modulesDir.listFiles()) { "Unable to list files under ./$modulesDir directory" } 6 | .asSequence() 7 | .filter(File::isDirectory) 8 | .map(File::getName) 9 | .forEach { dir -> 10 | val moduleName = dir.replace('.', ':') 11 | include(":$moduleName") 12 | project(":$moduleName").apply { 13 | projectDir = File("${modulesDir.name}/$dir") 14 | buildFileName = "$dir.gradle.kts" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/config/src/nativeMain/kotlin/Configuration.kt: -------------------------------------------------------------------------------- 1 | package io.gitlab.edrd.xmousegrabber.config 2 | 3 | import io.gitlab.edrd.xmousegrabber.common.Either 4 | import io.gitlab.edrd.xmousegrabber.config.internal.ConfigurationLoader 5 | 6 | public typealias ConfigurationEither = Either 7 | 8 | public data class Configuration(val version: Double, val buttons: List