Skip to content

Commit 5ad6088

Browse files
authored
Merge pull request #1456 from keymapperorg/develop
Version 2.8.1
2 parents 7e5eaae + 0135ccf commit 5ad6088

File tree

27 files changed

+378
-186
lines changed

27 files changed

+378
-186
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## [2.8.1](https://github.com/sds100/KeyMapper/releases/tag/v2.8.1)
2+
3+
#### 18 February 2025
4+
5+
## Bug fixes
6+
7+
- #1433 open Key Mapper by default and not the Assistant Trigger app.
8+
- #1386 wait for sequence trigger timeout before triggering other overlapping triggers.
9+
- #1449 improve the key mapper crashed dialog.
10+
- #1415 make the discard changes dialog less confusing.
11+
- #1440 do not show the "Button not detected?" bottom sheet every time you open the config key map screen in some cases.
12+
- #1447 the app bar when configuring an Intent action would extend to the top of the screen.
13+
- #1444 use the correct icon for screen on/off constraints.
14+
115
## [2.8.0](https://github.com/sds100/KeyMapper/releases/tag/v2.8.0)
216

317
#### 13 February 2025

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
**Project back under slower development.**
2-
> **UPDATE**: I miss working on this project and Key Mapper still has so much potential so I am back! 😊
1+
**UPDATE**: I miss working on this project and Key Mapper still has so much potential so I am back! 😊
32
>
43
> Well, working on this project was a fun ride 🎢! This project has taught me so much about Android, software development and how to collaborate with an online community. It has been my dream to lead a big FOSS project with people from all over the world so a **huge** thank you goes to everyone that spread the word and helped on GitHub along the way ☺.
54
>

app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/detection/KeyMapController.kt

Lines changed: 101 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class KeyMapController(
249249
MutableList(triggers.size) { mutableSetOf<Int>() }
250250

251251
for (triggerIndex in parallelTriggers) {
252-
val trigger = triggers[triggerIndex]
252+
val parallelTrigger = triggers[triggerIndex]
253253

254254
otherTriggerLoop@ for (otherTriggerIndex in sequenceTriggers) {
255255
val otherTrigger = triggers[otherTriggerIndex]
@@ -259,7 +259,7 @@ class KeyMapController(
259259
continue@otherTriggerLoop
260260
}
261261

262-
for ((keyIndex, key) in trigger.keys.withIndex()) {
262+
for ((keyIndex, key) in parallelTrigger.keys.withIndex()) {
263263
var lastMatchedIndex: Int? = null
264264

265265
for ((otherKeyIndex, otherKey) in otherTrigger.keys.withIndex()) {
@@ -271,7 +271,7 @@ class KeyMapController(
271271
continue@otherTriggerLoop
272272
}
273273

274-
if (keyIndex == trigger.keys.lastIndex) {
274+
if (keyIndex == parallelTrigger.keys.lastIndex) {
275275
sequenceTriggersOverlappingParallelTriggers[triggerIndex].add(
276276
otherTriggerIndex,
277277
)
@@ -489,30 +489,36 @@ class KeyMapController(
489489

490490
private var modifierKeyEventActions: Boolean = false
491491
private var notModifierKeyEventActions: Boolean = false
492-
private var keyCodesToImitateUpAction: MutableSet<Int> = mutableSetOf<Int>()
492+
private var keyCodesToImitateUpAction: MutableSet<Int> = mutableSetOf()
493493
private var metaStateFromActions: Int = 0
494494
private var metaStateFromKeyEvent: Int = 0
495495

496-
private val eventDownTimeMap: MutableMap<Event, Long> = mutableMapOf<Event, Long>()
496+
private val eventDownTimeMap: MutableMap<Event, Long> = mutableMapOf()
497+
498+
/**
499+
* This solves issue #1386. This stores the jobs that will wait until the sequence trigger
500+
* times out and check whether the overlapping sequence trigger was indeed triggered.
501+
*/
502+
private val performActionsAfterSequenceTriggerTimeout: MutableMap<Int, Job> = mutableMapOf()
497503

498504
/**
499505
* The indexes of parallel triggers that didn't have their actions performed because there is a matching trigger but
500506
* for a long-press. These actions should only be performed if the long-press fails, otherwise when the user
501507
* holds down the trigger keys for the long-press trigger, actions from both triggers will be performed.
502508
*/
503-
private val performActionsOnFailedLongPress: MutableSet<Int> = mutableSetOf<Int>()
509+
private val performActionsOnFailedLongPress: MutableSet<Int> = mutableSetOf()
504510

505511
/**
506512
* The indexes of parallel triggers that didn't have their actions performed because there is a matching trigger but
507513
* for a double-press. These actions should only be performed if the double-press fails, otherwise each time the user
508514
* presses the keys for the double press, actions from both triggers will be performed.
509515
*/
510-
private val performActionsOnFailedDoublePress: MutableSet<Int> = mutableSetOf<Int>()
516+
private val performActionsOnFailedDoublePress: MutableSet<Int> = mutableSetOf()
511517

512518
/**
513519
* Maps jobs to perform an action after a long press to their corresponding parallel trigger index
514520
*/
515-
private val parallelTriggerLongPressJobs: SparseArrayCompat<Job> = SparseArrayCompat<Job>()
521+
private val parallelTriggerLongPressJobs: SparseArrayCompat<Job> = SparseArrayCompat()
516522

517523
/**
518524
* Keys that are detected through an input method will potentially send multiple DOWN key events
@@ -849,31 +855,63 @@ class KeyMapController(
849855
mappedToParallelTriggerAction = true
850856
parallelTriggersAwaitingReleaseAfterBeingTriggered[triggerIndex] = true
851857

852-
val actionKeys = triggerActions[triggerIndex]
858+
// See issue #1386.
859+
val overlappingSequenceTrigger =
860+
sequenceTriggersOverlappingParallelTriggers[triggerIndex]
861+
// Only consider the sequence triggers where this
862+
// short press trigger has been already pressed
863+
// or will be pressed next.
864+
.filter {
865+
for (i in 0..(lastMatchedEventIndices[it] + 1)) {
866+
val matchingEvent = triggers[it].matchingEventAtIndex(
867+
event.withShortPress,
868+
i,
869+
)
870+
871+
if (matchingEvent) {
872+
return@filter true
873+
}
874+
}
875+
876+
return@filter false
877+
}
878+
.maxByOrNull { sequenceTriggerTimeout(triggers[it]) }
879+
880+
if (overlappingSequenceTrigger == null) {
881+
val actionKeys = triggerActions[triggerIndex]
853882

854-
actionKeys.forEach { actionKey ->
855-
val action = actionMap[actionKey] ?: return@forEach
883+
actionKeys.forEach { actionKey ->
884+
val action = actionMap[actionKey] ?: return@forEach
856885

857-
if (action.data is ActionData.InputKeyEvent) {
858-
val actionKeyCode = action.data.keyCode
886+
if (action.data is ActionData.InputKeyEvent) {
887+
val actionKeyCode = action.data.keyCode
859888

860-
if (isModifierKey(actionKeyCode)) {
861-
val actionMetaState =
862-
InputEventUtils.modifierKeycodeToMetaState(actionKeyCode)
863-
metaStateFromActions =
864-
metaStateFromActions.withFlag(actionMetaState)
889+
if (isModifierKey(actionKeyCode)) {
890+
val actionMetaState =
891+
InputEventUtils.modifierKeycodeToMetaState(actionKeyCode)
892+
metaStateFromActions =
893+
metaStateFromActions.withFlag(actionMetaState)
894+
}
865895
}
866-
}
867896

868-
detectedShortPressTriggers.add(triggerIndex)
897+
detectedShortPressTriggers.add(triggerIndex)
869898

870-
val vibrateDuration = when {
871-
trigger.vibrate -> vibrateDuration(trigger)
872-
forceVibrate.value -> defaultVibrateDuration.value
873-
else -> -1L
899+
val vibrateDuration = when {
900+
trigger.vibrate -> vibrateDuration(trigger)
901+
forceVibrate.value -> defaultVibrateDuration.value
902+
else -> -1L
903+
}
904+
905+
vibrateDurations.add(vibrateDuration)
874906
}
907+
} else {
908+
performActionsAfterSequenceTriggerTimeout[triggerIndex]?.cancel()
875909

876-
vibrateDurations.add(vibrateDuration)
910+
performActionsAfterSequenceTriggerTimeout[triggerIndex] =
911+
performActionsAfterSequenceTriggerTimeout(
912+
triggerIndex,
913+
overlappingSequenceTrigger,
914+
)
877915
}
878916
}
879917
}
@@ -1443,14 +1481,14 @@ class KeyMapController(
14431481
metaStateFromKeyEvent = 0
14441482
keyCodesToImitateUpAction = mutableSetOf()
14451483

1446-
parallelTriggerLongPressJobs.valueIterator().forEach {
1447-
it.cancel()
1448-
}
1449-
1484+
parallelTriggerLongPressJobs.valueIterator().forEach { it.cancel() }
14501485
parallelTriggerLongPressJobs.clear()
14511486

14521487
parallelTriggerActionPerformers.values.forEach { it.reset() }
14531488
sequenceTriggerActionPerformers.values.forEach { it.reset() }
1489+
1490+
performActionsAfterSequenceTriggerTimeout.forEach { (_, job) -> job.cancel() }
1491+
performActionsAfterSequenceTriggerTimeout.clear()
14541492
}
14551493

14561494
/**
@@ -1499,6 +1537,40 @@ class KeyMapController(
14991537
return detectedTriggerIndexes.isNotEmpty()
15001538
}
15011539

1540+
/**
1541+
* For parallel triggers only.
1542+
*/
1543+
private fun performActionsAfterSequenceTriggerTimeout(
1544+
triggerIndex: Int,
1545+
sequenceTriggerIndex: Int,
1546+
) = coroutineScope.launch {
1547+
val timeout = sequenceTriggerTimeout(triggers[sequenceTriggerIndex])
1548+
1549+
delay(timeout)
1550+
1551+
// If it equals -1 then it means the sequence trigger was triggered
1552+
// and it reset the counter.
1553+
if (lastMatchedEventIndices[sequenceTriggerIndex] == -1) {
1554+
return@launch
1555+
}
1556+
1557+
parallelTriggerActionPerformers[triggerIndex]?.onTriggered(
1558+
calledOnTriggerRelease = true,
1559+
metaState = metaStateFromActions.withFlag(metaStateFromKeyEvent),
1560+
)
1561+
1562+
if (triggers[triggerIndex].vibrate ||
1563+
forceVibrate.value ||
1564+
triggers[triggerIndex].longPressDoubleVibration
1565+
) {
1566+
useCase.vibrate(vibrateDuration(triggers[triggerIndex]))
1567+
}
1568+
1569+
if (triggers[triggerIndex].showToast) {
1570+
useCase.showTriggeredToast()
1571+
}
1572+
}
1573+
15021574
private fun encodeActionList(actions: List<KeyMapAction>): IntArray = actions.map { getActionKey(it) }.toIntArray()
15031575

15041576
/**

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,17 @@
8787
android:theme="@style/Theme.App.Starting">
8888
<intent-filter>
8989
<action android:name="android.intent.action.MAIN" />
90+
9091
<category android:name="android.intent.category.LAUNCHER" />
92+
<!-- Set as default so the Assistant Trigger app isn't opened by default. -->
93+
<category android:name="android.intent.category.DEFAULT" />
9194
</intent-filter>
9295
<intent-filter>
9396
<action android:name="android.intent.action.MAIN" />
97+
9498
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
99+
<!-- Set as default so the Assistant Trigger app isn't opened by default. -->
100+
<category android:name="android.intent.category.DEFAULT" />
95101
</intent-filter>
96102
</activity>
97103

app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintUiHelper.kt

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,12 @@ class ConstraintUiHelper(
192192
)
193193

194194
Constraint.ScreenOff -> IconInfo(
195-
drawable = getDrawable(R.drawable.ic_outline_stay_current_portrait_24),
195+
drawable = getDrawable(R.drawable.ic_baseline_mobile_off_24),
196196
tintType = TintType.OnSurface,
197197
)
198198

199199
Constraint.ScreenOn -> IconInfo(
200-
drawable = getDrawable(R.drawable.ic_baseline_mobile_off_24),
200+
drawable = getDrawable(R.drawable.ic_outline_stay_current_portrait_24),
201201
tintType = TintType.OnSurface,
202202
)
203203

@@ -277,48 +277,6 @@ class ConstraintUiHelper(
277277
)
278278
}
279279

280-
/**
281-
* Get a title for a constraint that is not specific to a particular instance.
282-
*/
283-
fun getGenericTitle(constraint: Constraint): String = when (constraint) {
284-
is Constraint.AppInForeground -> getString(
285-
R.string.constraint_app_foreground_description,
286-
"",
287-
)
288-
289-
is Constraint.AppNotInForeground -> getString(
290-
R.string.constraint_app_not_foreground_description,
291-
"",
292-
)
293-
294-
is Constraint.AppNotPlayingMedia -> TODO()
295-
is Constraint.AppPlayingMedia -> TODO()
296-
is Constraint.BtDeviceConnected -> TODO()
297-
is Constraint.BtDeviceDisconnected -> TODO()
298-
Constraint.Charging -> TODO()
299-
Constraint.DeviceIsLocked -> TODO()
300-
Constraint.DeviceIsUnlocked -> TODO()
301-
Constraint.Discharging -> TODO()
302-
is Constraint.FlashlightOff -> TODO()
303-
is Constraint.FlashlightOn -> TODO()
304-
is Constraint.ImeChosen -> TODO()
305-
is Constraint.ImeNotChosen -> TODO()
306-
Constraint.InPhoneCall -> TODO()
307-
Constraint.MediaPlaying -> TODO()
308-
Constraint.NoMediaPlaying -> TODO()
309-
Constraint.NotInPhoneCall -> TODO()
310-
is Constraint.OrientationCustom -> TODO()
311-
Constraint.OrientationLandscape -> TODO()
312-
Constraint.OrientationPortrait -> TODO()
313-
Constraint.PhoneRinging -> TODO()
314-
Constraint.ScreenOff -> TODO()
315-
Constraint.ScreenOn -> TODO()
316-
is Constraint.WifiConnected -> TODO()
317-
is Constraint.WifiDisconnected -> TODO()
318-
Constraint.WifiOff -> TODO()
319-
Constraint.WifiOn -> TODO()
320-
}
321-
322280
private fun getAppIconInfo(packageName: String): IconInfo? = getAppIcon(packageName).handle(
323281
onSuccess = { IconInfo(it, TintType.None) },
324282
onError = { null },

app/src/main/java/io/github/sds100/keymapper/constraints/CreateConstraintUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class CreateConstraintUseCaseImpl(
4848

4949
if (!savedWifiSSIDsList.contains(ssid)) {
5050
if (savedWifiSSIDsList.size == 3) {
51-
savedWifiSSIDsList.removeLast()
51+
savedWifiSSIDsList.removeAt(savedWifiSSIDsList.lastIndex)
5252
}
5353

5454
if (savedWifiSSIDsList.isEmpty()) {

app/src/main/java/io/github/sds100/keymapper/data/PreferenceDefaults.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ object PreferenceDefaults {
88
const val DARK_THEME = "2"
99

1010
const val SHOW_TOAST_WHEN_AUTO_CHANGE_IME = true
11+
const val CHANGE_IME_ON_INPUT_FOCUS = false
1112

1213
const val FORCE_VIBRATE = false
1314
const val LONG_PRESS_DELAY = 500

app/src/main/java/io/github/sds100/keymapper/home/FixAppKillingActivity.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.sds100.keymapper.home
22

33
import android.os.Bundle
4+
import android.view.View
45
import androidx.activity.viewModels
56
import androidx.core.os.bundleOf
67
import androidx.fragment.app.Fragment
@@ -27,7 +28,12 @@ class FixAppKillingActivity : AppIntro2() {
2728
override fun onCreate(savedInstanceState: Bundle?) {
2829
super.onCreate(savedInstanceState)
2930

30-
viewModel.showPopups(this, findViewById(R.id.background))
31+
val rootView: View = findViewById(R.id.background)
32+
33+
// Don't show behind the status/navigation bar on Android 15+
34+
rootView.fitsSystemWindows = true
35+
showStatusBar(true)
36+
viewModel.showPopups(this, rootView)
3137

3238
isSkipButtonEnabled = false
3339

app/src/main/java/io/github/sds100/keymapper/mappings/ConfigMappingFragment.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,14 @@ abstract class ConfigMappingFragment : Fragment() {
178178
private fun showOnBackPressedWarning() {
179179
viewLifecycleScope.launchWhenResumed {
180180
onBackPressedDialog = requireContext().materialAlertDialog {
181-
titleResource = R.string.dialog_title_discard_changes
182-
messageResource = R.string.dialog_message_discard_changes
181+
titleResource = R.string.dialog_title_unsaved_changes
182+
messageResource = R.string.dialog_message_unsaved_changes
183183

184-
positiveButton(R.string.pos_confirm) {
184+
positiveButton(R.string.pos_discard_changes) {
185185
findNavController().navigateUp()
186186
}
187187

188-
negativeButton(R.string.neg_cancel) { it.cancel() }
188+
negativeButton(R.string.neg_keep_editing) { it.cancel() }
189189
}
190190

191191
onBackPressedDialog?.show()

app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutFragment.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ class CreateKeyMapShortcutFragment : SimpleRecyclerViewFragment<KeyMapListItem>(
9595

9696
private fun showOnBackPressedWarning() {
9797
requireContext().alertDialog {
98-
titleResource = R.string.dialog_title_discard_changes
99-
messageResource = R.string.dialog_message_discard_changes
98+
titleResource = R.string.dialog_title_unsaved_changes
99+
messageResource = R.string.dialog_message_unsaved_changes
100100

101-
positiveButton(R.string.pos_confirm) {
101+
positiveButton(R.string.pos_discard_changes) {
102102
requireActivity().finish()
103103
}
104104

105-
negativeButton(R.string.neg_cancel) { it.cancel() }
105+
negativeButton(R.string.neg_keep_editing) { it.cancel() }
106106
show()
107107
}
108108
}

0 commit comments

Comments
 (0)