diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index a2027703b1..1acb88303f 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -17,9 +17,6 @@ jobs:
java-version: 17
cache: 'gradle'
- - name: Setup Android SDK
- uses: android-actions/setup-android@v2
-
- name: Unit tests
run: bash ./gradlew testDebugUnitTest
@@ -46,12 +43,48 @@ jobs:
java-version: 17
cache: 'gradle'
- - name: Setup Android SDK
- uses: android-actions/setup-android@v2
-
- name: Ktlint check
run: ./gradlew ktlintCheck
+ rust:
+ name: Rust code style and tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt, clippy
+
+ - name: Install Android Rust targets
+ run: |
+ rustup target add armv7-linux-androideabi
+ rustup target add aarch64-linux-android
+ rustup target add i686-linux-android
+ rustup target add x86_64-linux-android
+
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ evdev/src/main/rust/evdev_manager/target/
+ key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-rust-
+
+ - name: Check Rust formatting
+ working-directory: evdev/src/main/rust/evdev_manager
+ run: cargo fmt --check
+
+ - name: Run Rust tests
+ working-directory: evdev/src/main/rust/evdev_manager
+ run: cargo test --package evdev_manager_core
+
apk:
name: Build APK
runs-on: ubuntu-latest
@@ -77,6 +110,18 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v2
+ with:
+ ndk-version: "27.2.12479018"
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install Android Rust targets
+ run: |
+ rustup target add armv7-linux-androideabi
+ rustup target add aarch64-linux-android
+ rustup target add i686-linux-android
+ rustup target add x86_64-linux-android
- name: set up Ruby for fastlane
uses: ruby/setup-ruby@v1
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 18188155de..5d9ed84b96 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -23,9 +23,6 @@ jobs:
java-version: 17
cache: 'gradle'
- - name: Setup Android SDK
- uses: android-actions/setup-android@v2
-
- name: Unit tests
run: bash ./gradlew testDebugUnitTest
@@ -52,12 +49,48 @@ jobs:
java-version: 17
cache: 'gradle'
- - name: Setup Android SDK
- uses: android-actions/setup-android@v2
-
- name: Ktlint check
run: ./gradlew ktlintCheck
+ rust:
+ name: Rust code style and tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt, clippy
+
+ - name: Install Android Rust targets
+ run: |
+ rustup target add armv7-linux-androideabi
+ rustup target add aarch64-linux-android
+ rustup target add i686-linux-android
+ rustup target add x86_64-linux-android
+
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ evdev/src/main/rust/evdev_manager/target/
+ key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-rust-
+
+ - name: Check Rust formatting
+ working-directory: evdev/src/main/rust/evdev_manager
+ run: cargo fmt --check
+
+ - name: Run Rust tests
+ working-directory: evdev/src/main/rust/evdev_manager
+ run: cargo test --package evdev_manager_core
+
apk:
name: Generate and upload APK to Discord
runs-on: ubuntu-latest
@@ -89,6 +122,18 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v2
+ with:
+ ndk-version: "27.2.12479018"
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install Android Rust targets
+ run: |
+ rustup target add armv7-linux-androideabi
+ rustup target add aarch64-linux-android
+ rustup target add i686-linux-android
+ rustup target add x86_64-linux-android
- name: set up Ruby for fastlane
uses: ruby/setup-ruby@v1
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2b0504c7a9..7d234f10e3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -152,6 +152,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":sysbridge"))
implementation(project(":system"))
+ implementation(project(":evdev"))
compileOnly(project(":systemstubs"))
coreLibraryDesugaring(libs.desugar.jdk.libs)
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 3ba9020698..f5464edfae 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -116,7 +116,7 @@
# Keep all AIDL interface classes and their methods
-keep class io.github.sds100.keymapper.sysbridge.ISystemBridge** { *; }
--keep class io.github.sds100.keymapper.sysbridge.IEvdevCallback** { *; }
+-keep class io.github.sds100.keymapper.evdev.IEvdevCallback** { *; }
-keep class io.github.sds100.keymapper.sysbridge.IShizukuStarterService** { *; }
-keepclassmembers class io.github.sds100.keymapper.sysbridge.shizuku.ShizukuStarterService {
@@ -138,7 +138,8 @@
-keep class io.github.sds100.keymapper.sysbridge.** extends android.content.ContentProvider { *; }
# Keep parcelable classes used in AIDL
--keep class io.github.sds100.keymapper.common.models.EvdevDeviceHandle { *; }
+-keep class io.github.sds100.keymapper.common.models.GrabbedDeviceHandle { *; }
+-keep class io.github.sds100.keymapper.common.models.EvdevDeviceInfo { *; }
# Keep all rikka.hidden classes and interfaces as they contain AIDL files
-keep class rikka.hidden.** { *; }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c4e87ba265..d74d55e0eb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,7 +13,7 @@
-
+
) {
+ @Suppress("DEPRECATION")
+ Looper.prepareMainLooper()
+ SystemBridge()
+ Looper.loop()
+ }
+ }
+}
diff --git a/app/version.properties b/app/version.properties
index 4b2046f38c..f1feecf3c2 100644
--- a/app/version.properties
+++ b/app/version.properties
@@ -1,2 +1,2 @@
VERSION_NAME=5.0.0-alpha
-VERSION_CODE=197
+VERSION_CODE=198
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/detection/DetectKeyMapsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/detection/DetectKeyMapsUseCase.kt
index 97de63d1bd..befa0d8122 100644
--- a/base/src/main/java/io/github/sds100/keymapper/base/detection/DetectKeyMapsUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/detection/DetectKeyMapsUseCase.kt
@@ -30,7 +30,6 @@ import io.github.sds100.keymapper.data.repositories.PreferenceRepository
import io.github.sds100.keymapper.system.popup.ToastAdapter
import io.github.sds100.keymapper.system.vibrator.VibratorAdapter
import io.github.sds100.keymapper.system.volume.VolumeAdapter
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -49,17 +48,12 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
private val toastAdapter: ToastAdapter,
private val resourceProvider: ResourceProvider,
private val vibrator: VibratorAdapter,
- @Assisted
- private val coroutineScope: CoroutineScope,
private val inputEventHub: InputEventHub,
) : DetectKeyMapsUseCase {
@AssistedFactory
interface Factory {
- fun create(
- accessibilityService: IAccessibilityService,
- coroutineScope: CoroutineScope,
- ): DetectKeyMapsUseCaseImpl
+ fun create(accessibilityService: IAccessibilityService): DetectKeyMapsUseCaseImpl
}
companion object {
@@ -228,15 +222,15 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
}
}
- override fun imitateEvdevEvent(devicePath: String, type: Int, code: Int, value: Int) {
+ override fun imitateEvdevEvent(deviceId: Int, type: Int, code: Int, value: Int) {
if (inputEventHub.isSystemBridgeConnected()) {
Timber.d(
- "Imitate evdev event, device path: $devicePath, type: $type, code: $code, value: $value",
+ "Imitate evdev event, device id: $deviceId, type: $type, code: $code, value: $value",
)
- inputEventHub.injectEvdevEvent(devicePath, type, code, value)
+ inputEventHub.injectEvdevEvent(deviceId, type, code, value)
} else {
Timber.w(
- "Cannot imitate evdev event without system bridge connected. Device path: $devicePath, type: $type, code: $code, value: $value",
+ "Cannot imitate evdev event without system bridge connected.",
)
}
}
@@ -268,5 +262,5 @@ interface DetectKeyMapsUseCase {
source: Int = InputDevice.SOURCE_UNKNOWN,
)
- fun imitateEvdevEvent(devicePath: String, type: Int, code: Int, value: Int)
+ fun imitateEvdevEvent(deviceId: Int, type: Int, code: Int, value: Int)
}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt b/base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt
index 7429079ace..af9d559dc5 100644
--- a/base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt
@@ -648,13 +648,8 @@ class KeyMapAlgorithm(
val event = EvdevEventAlgo(
keyCode = inputEvent.androidCode,
clickType = null,
- devicePath = inputEvent.device.path,
- device = EvdevDeviceInfo(
- name = inputEvent.device.name,
- bus = inputEvent.device.bus,
- vendor = inputEvent.device.vendor,
- product = inputEvent.device.product,
- ),
+ deviceId = inputEvent.deviceId,
+ device = inputEvent.deviceInfo,
scanCode = inputEvent.code,
)
@@ -1490,14 +1485,14 @@ class KeyMapAlgorithm(
)
} else if (event is EvdevEventAlgo) {
useCase.imitateEvdevEvent(
- devicePath = event.devicePath,
+ deviceId = event.deviceId,
KMEvdevEvent.TYPE_KEY_EVENT,
event.scanCode,
KMEvdevEvent.VALUE_DOWN,
)
useCase.imitateEvdevEvent(
- devicePath = event.devicePath,
+ deviceId = event.deviceId,
KMEvdevEvent.TYPE_KEY_EVENT,
event.scanCode,
KMEvdevEvent.VALUE_UP,
@@ -1544,20 +1539,20 @@ class KeyMapAlgorithm(
} else if (event is EvdevEventAlgo) {
if (imitateUpKeyEvent) {
useCase.imitateEvdevEvent(
- devicePath = event.devicePath,
+ deviceId = event.deviceId,
type = KMEvdevEvent.TYPE_KEY_EVENT,
code = event.scanCode,
value = KMEvdevEvent.VALUE_UP,
)
} else {
useCase.imitateEvdevEvent(
- devicePath = event.devicePath,
+ deviceId = event.deviceId,
type = KMEvdevEvent.TYPE_KEY_EVENT,
code = event.scanCode,
value = KMEvdevEvent.VALUE_DOWN,
)
useCase.imitateEvdevEvent(
- devicePath = event.devicePath,
+ deviceId = event.deviceId,
type = KMEvdevEvent.TYPE_KEY_EVENT,
code = event.scanCode,
value = KMEvdevEvent.VALUE_UP,
@@ -1944,7 +1939,7 @@ class KeyMapAlgorithm(
}
private data class EvdevEventAlgo(
- val devicePath: String,
+ val deviceId: Int,
val device: EvdevDeviceInfo,
val scanCode: Int,
val keyCode: Int,
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevDevicesDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevDevicesDelegate.kt
new file mode 100644
index 0000000000..fd7aba73b2
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevDevicesDelegate.kt
@@ -0,0 +1,117 @@
+package io.github.sds100.keymapper.base.input
+
+import androidx.annotation.RequiresApi
+import io.github.sds100.keymapper.common.models.EvdevDeviceInfo
+import io.github.sds100.keymapper.common.models.GrabbedDeviceHandle
+import io.github.sds100.keymapper.common.utils.Constants
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.common.utils.onSuccess
+import io.github.sds100.keymapper.common.utils.valueIfFailure
+import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
+import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
+import io.github.sds100.keymapper.system.devices.DevicesAdapter
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+
+/**
+ * Need to use a cache that maps a device id to the other device information. This information
+ * could be sent in the onEvdevEvent callback instead, but sending non-primitive strings for the
+ * device name introduces extra overhead across Binder and JNI.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
+@Singleton
+class EvdevDevicesDelegate @Inject constructor(
+ private val coroutineScope: CoroutineScope,
+ private val devicesAdapter: DevicesAdapter,
+ private val systemBridgeConnectionManager: SystemBridgeConnectionManager,
+) {
+ private val grabbedDevicesById: MutableStateFlow