Skip to content

Commit bf74cfe

Browse files
committed
#1983 fix: inputting a modifier key and another key as actions through Expert mode applies the correct key character map.
1 parent 86dc23e commit bf74cfe

File tree

5 files changed

+129
-21
lines changed

5 files changed

+129
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- [#1976](https://github.com/keymapperorg/KeyMapper/issues/1976) Panic in Rust system bridge code on some devices.
1717
- [#1971](https://github.com/keymapperorg/KeyMapper/issues/1971) Media actions work again in some apps, like YouTube.
1818
- [#1961](https://github.com/keymapperorg/KeyMapper/issues/1961) Disabling setup assistant shows a notification asking for pairing code immediately.
19+
- [#1983](https://github.com/keymapperorg/KeyMapper/issues/1983) Inputting a modifier key and another key as actions through Expert mode applies the correct key character map.
1920
- Bugs with expert mode auto starting time.
2021

2122
## [4.0.0 Beta 6](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.06)

base/src/main/java/io/github/sds100/keymapper/base/detection/DetectKeyMapsUseCase.kt

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,22 @@ import io.github.sds100.keymapper.data.repositories.PreferenceRepository
3030
import io.github.sds100.keymapper.system.popup.ToastAdapter
3131
import io.github.sds100.keymapper.system.vibrator.VibratorAdapter
3232
import io.github.sds100.keymapper.system.volume.VolumeAdapter
33+
import kotlinx.coroutines.CoroutineScope
3334
import kotlinx.coroutines.Dispatchers
3435
import kotlinx.coroutines.flow.Flow
36+
import kotlinx.coroutines.flow.SharingStarted
37+
import kotlinx.coroutines.flow.StateFlow
3538
import kotlinx.coroutines.flow.combine
3639
import kotlinx.coroutines.flow.flowOn
3740
import kotlinx.coroutines.flow.map
41+
import kotlinx.coroutines.flow.stateIn
3842
import timber.log.Timber
3943

4044
class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
4145
@Assisted
4246
private val accessibilityService: IAccessibilityService,
47+
@Assisted
48+
private val coroutineScope: CoroutineScope,
4349
private val keyMapRepository: KeyMapRepository,
4450
private val floatingButtonRepository: FloatingButtonRepository,
4551
private val groupRepository: GroupRepository,
@@ -53,7 +59,10 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
5359

5460
@AssistedFactory
5561
interface Factory {
56-
fun create(accessibilityService: IAccessibilityService): DetectKeyMapsUseCaseImpl
62+
fun create(
63+
accessibilityService: IAccessibilityService,
64+
coroutineScope: CoroutineScope,
65+
): DetectKeyMapsUseCaseImpl
5766
}
5867

5968
companion object {
@@ -161,6 +170,11 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
161170
.map { it ?: PreferenceDefaults.VIBRATION_DURATION }
162171
.map { it.toLong() }
163172

173+
override val injectKeyEventsWithSystemBridge: StateFlow<Boolean> =
174+
preferenceRepository.get(Keys.keyEventActionsUseSystemBridge)
175+
.map { it ?: PreferenceDefaults.KEY_EVENT_ACTIONS_USE_SYSTEM_BRIDGE }
176+
.stateIn(coroutineScope, SharingStarted.Eagerly, false)
177+
164178
override fun showTriggeredToast() {
165179
toastAdapter.show(resourceProvider.getString(R.string.toast_triggered_keymap))
166180
}
@@ -188,16 +202,20 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
188202

189203
if (inputEventHub.isSystemBridgeConnected()) {
190204
Timber.d(
191-
"Imitate button press ${KeyEvent.keyCodeToString(
192-
keyCode,
193-
)} with system bridge, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode",
205+
"Imitate button press ${
206+
KeyEvent.keyCodeToString(
207+
keyCode,
208+
)
209+
} with system bridge, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode",
194210
)
195211
inputEventHub.injectKeyEventAsync(model)
196212
} else {
197213
Timber.d(
198-
"Imitate button press ${KeyEvent.keyCodeToString(
199-
keyCode,
200-
)}, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode",
214+
"Imitate button press ${
215+
KeyEvent.keyCodeToString(
216+
keyCode,
217+
)
218+
}, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode",
201219
)
202220

203221
when (keyCode) {
@@ -208,9 +226,11 @@ class DetectKeyMapsUseCaseImpl @AssistedInject constructor(
208226
KeyEvent.KEYCODE_BACK -> accessibilityService.doGlobalAction(
209227
AccessibilityService.GLOBAL_ACTION_BACK,
210228
)
229+
211230
KeyEvent.KEYCODE_HOME -> accessibilityService.doGlobalAction(
212231
AccessibilityService.GLOBAL_ACTION_HOME,
213232
)
233+
214234
KeyEvent.KEYCODE_APP_SWITCH -> accessibilityService.doGlobalAction(
215235
AccessibilityService.GLOBAL_ACTION_POWER_DIALOG,
216236
)
@@ -263,4 +283,6 @@ interface DetectKeyMapsUseCase {
263283
)
264284

265285
fun imitateEvdevEvent(deviceId: Int, type: Int, code: Int, value: Int)
286+
287+
val injectKeyEventsWithSystemBridge: StateFlow<Boolean>
266288
}

base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -910,10 +910,15 @@ class KeyMapAlgorithm(
910910
if (overlappingSequenceTrigger == null) {
911911
val actionKeys = triggerActions[triggerIndex]
912912

913-
actionKeys.forEach { actionKey ->
914-
val action = actionMap[actionKey] ?: return@forEach
913+
for (actionKey in actionKeys) {
914+
val action = actionMap[actionKey] ?: continue
915915

916-
if (action.data is ActionData.InputKeyEvent) {
916+
// If the key event is being injected with the system bridge
917+
// then it will be passed back around through the accessibility
918+
// service and processed again.
919+
if (action.data is ActionData.InputKeyEvent &&
920+
!useCase.injectKeyEventsWithSystemBridge.value
921+
) {
917922
val actionKeyCode = action.data.keyCode
918923

919924
if (isModifierKey(actionKeyCode)) {
@@ -1073,7 +1078,6 @@ class KeyMapAlgorithm(
10731078
key.matchesEvent(event.withShortPress) -> true
10741079
key.matchesEvent(event.withLongPress) -> true
10751080
key.matchesEvent(event.withDoublePress) -> true
1076-
10771081
else -> false
10781082
}
10791083

@@ -1794,6 +1798,7 @@ class KeyMapAlgorithm(
17941798

17951799
return when (this.device) {
17961800
KeyEventTriggerDevice.Any -> codeMatches && this.clickType == event.clickType
1801+
17971802
is KeyEventTriggerDevice.External ->
17981803
event.isExternal &&
17991804
codeMatches &&

base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityServiceController.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ abstract class BaseAccessibilityServiceController(
8282

8383
private val detectKeyMapsUseCase = detectKeyMapsUseCaseFactory.create(
8484
accessibilityService = service,
85+
coroutineScope = service.lifecycleScope,
8586
)
8687

8788
val detectConstraintsUseCase = detectConstraintsUseCaseFactory.create(service)

base/src/test/java/io/github/sds100/keymapper/base/keymaps/KeyMapAlgorithmTest.kt

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ class KeyMapAlgorithmTest {
193193
}
194194

195195
whenever(detectKeyMapsUseCase.currentTime).thenAnswer { testScope.currentTime }
196+
whenever(
197+
detectKeyMapsUseCase.injectKeyEventsWithSystemBridge,
198+
).thenReturn(MutableStateFlow(false))
196199

197200
performActionsUseCase = mock {
198201
MutableStateFlow(REPEAT_DELAY).apply {
@@ -235,6 +238,80 @@ class KeyMapAlgorithmTest {
235238
mockedKeyEvent.close()
236239
}
237240

241+
/**
242+
* Issue #1983
243+
*/
244+
@Test
245+
fun `do not imitate keys with meta state when injecting key event actions with system bridge`() =
246+
runTest(testDispatcher) {
247+
whenever(
248+
detectKeyMapsUseCase.injectKeyEventsWithSystemBridge,
249+
).thenReturn(MutableStateFlow(true))
250+
251+
val trigger = singleKeyTrigger(
252+
EvdevTriggerKey(
253+
keyCode = KeyEvent.KEYCODE_S,
254+
scanCode = Scancode.KEY_S,
255+
device = FAKE_VOLUME_EVDEV_DEVICE,
256+
),
257+
)
258+
259+
val actions = listOf(
260+
Action(
261+
data = ActionData.InputKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT),
262+
holdDown = true,
263+
),
264+
Action(
265+
data = ActionData.InputKeyEvent(KeyEvent.KEYCODE_3),
266+
holdDown = true,
267+
),
268+
)
269+
270+
loadKeyMaps(KeyMap(trigger = trigger, actionList = actions))
271+
272+
inputDownEvdevEvent(
273+
KeyEvent.KEYCODE_S,
274+
Scancode.KEY_S,
275+
device = FAKE_VOLUME_EVDEV_DEVICE,
276+
)
277+
278+
// Simulate the SHIFT and 3 being reinputted from the grabbed evdev device
279+
inputKeyEvent(
280+
keyCode = KeyEvent.KEYCODE_SHIFT_LEFT,
281+
action = KeyEvent.ACTION_DOWN,
282+
metaState =
283+
KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON,
284+
)
285+
inputKeyEvent(
286+
keyCode = KeyEvent.KEYCODE_3,
287+
action = KeyEvent.ACTION_DOWN,
288+
metaState =
289+
KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON,
290+
)
291+
292+
inputUpEvdevEvent(
293+
KeyEvent.KEYCODE_S,
294+
Scancode.KEY_S,
295+
device = FAKE_VOLUME_EVDEV_DEVICE,
296+
)
297+
298+
// Simulate the SHIFT and 3 being reinputted from the grabbed evdev device
299+
inputKeyEvent(keyCode = KeyEvent.KEYCODE_SHIFT_LEFT, action = KeyEvent.ACTION_UP)
300+
inputKeyEvent(keyCode = KeyEvent.KEYCODE_3, action = KeyEvent.ACTION_UP)
301+
302+
verify(
303+
detectKeyMapsUseCase,
304+
never(),
305+
).imitateKeyEvent(
306+
keyCode = any(),
307+
metaState = any(),
308+
deviceId = any(),
309+
action = any(),
310+
scanCode = any(),
311+
source = any(),
312+
)
313+
}
314+
238315
@Test
239316
fun `Detect mouse button which only has scan code`() = runTest(testDispatcher) {
240317
val trigger = singleKeyTrigger(
@@ -1832,16 +1909,18 @@ class KeyMapAlgorithmTest {
18321909
loadKeyMaps(KeyMap(trigger = trigger, actionList = actionList))
18331910

18341911
// WHEN
1835-
whenever(performActionsUseCase.getErrorSnapshot()).thenReturn(object :
1836-
ActionErrorSnapshot {
1837-
override fun getError(action: ActionData): KMError {
1838-
return KMError.NoCompatibleImeChosen
1839-
}
1840-
1841-
override fun getErrors(actions: List<ActionData>): Map<ActionData, KMError?> {
1842-
return mapOf(actionList[0].data to KMError.NoCompatibleImeChosen)
1843-
}
1844-
})
1912+
whenever(performActionsUseCase.getErrorSnapshot()).thenReturn(
1913+
object :
1914+
ActionErrorSnapshot {
1915+
override fun getError(action: ActionData): KMError {
1916+
return KMError.NoCompatibleImeChosen
1917+
}
1918+
1919+
override fun getErrors(actions: List<ActionData>): Map<ActionData, KMError?> {
1920+
return mapOf(actionList[0].data to KMError.NoCompatibleImeChosen)
1921+
}
1922+
},
1923+
)
18451924

18461925
assertThat(
18471926
inputKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN),

0 commit comments

Comments
 (0)