Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6282d7b
bump version to 4.7.0
sds100 Oct 4, 2026
41db631
update screenshot
sds100 Oct 4, 2026
2a451be
fix checking google play version in fastfile
sds100 Oct 4, 2026
dc70a64
fix fastfile
sds100 Oct 4, 2026
c402f03
fix: remove button is visible on key map group constraints with long …
sds100 Oct 5, 2026
6471a55
#2290 fix: place floating buttons correctly when the display is rotated
sds100 Oct 8, 2026
b066359
set version to 4.6.1
sds100 Oct 8, 2026
d7bf767
#2290 fix again: place floating buttons correctly when the display is…
sds100 Oct 9, 2026
23caeaa
fix: make spotlight items in whats new dialog have same width
sds100 Oct 9, 2026
77d101b
bump version code
sds100 Oct 9, 2026
fa41c4a
fix: floating buttons and actions now use logical display size
sds100 Oct 9, 2026
3afbd8f
#2287 fix: key maps that launch different apps open the same app when…
sds100 Oct 9, 2026
d6da760
#2289 fix: Expert Mode setup and auto start no longer get stuck on An…
sds100 Oct 9, 2026
bb6adde
bump version code
sds100 Oct 10, 2026
47968cf
#2260 feat: the "Do not remap" trigger key option is now called "Pass…
sds100 Oct 10, 2026
bf7d4c0
New translations title.txt (Chinese Simplified)
sds100 Oct 10, 2026
198d6c6
New translations full_description.txt (Chinese Simplified)
sds100 Oct 10, 2026
0222546
New translations short_description.txt (Chinese Simplified)
sds100 Oct 10, 2026
9b126b6
set changelog release date
sds100 Oct 10, 2026
7d95852
Merge pull request #2292 from keymapperorg/l10n/develop
sds100 Oct 10, 2026
1027e1b
fix: do not crash if WRITE_SECURE_SETTINGS permission not granted and…
sds100 Oct 10, 2026
49d3354
Merge remote-tracking branch 'origin/develop' into develop
sds100 Oct 10, 2026
5a54b6b
#2294 On Android 17 QPR1 enabling USB debugging while Wireless Debugg…
sds100 Oct 10, 2026
3ff9417
bump version code
sds100 Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## [4.6.1](https://github.com/sds100/KeyMapper/releases/tag/v4.6.1)

#### 10 October 2026

## Changed

- #2260 the "Do not remap" trigger key option is now called "Passthrough key events".

## Fixed

- Remove button is visible on key map group constraints with long text.
- #2290 Regression in 4.6. Place floating buttons correctly and tap actions when the display is rotated or resolution is changed.
- #2287 Key maps that launch different apps open the same app when the device is locked.
- #2289 Expert Mode setup and auto start no longer get stuck on Android 17 devices that hide whether developer options are enabled.
- Do not crash if WRITE_SECURE_SETTINGS permission not granted and disabling wireless debugging after starting system bridge.
- #2294 On Android 17 QPR1 enabling USB debugging while Wireless Debugging is enabled kills the Expert Mode system bridge on startup.

## [4.6.0](https://github.com/sds100/KeyMapper/releases/tag/v4.6.0)

#### 4 October 2026
Expand Down
4 changes: 2 additions & 2 deletions app/version.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION_NAME=4.6.0
VERSION_CODE=278
VERSION_NAME=4.6.1
VERSION_CODE=286
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,15 @@ class PerformActionsUseCaseImpl @AssistedInject constructor(
}

is ActionData.TapScreen -> {
val displaySize = getActiveDisplay()!!.activeSize
val displaySize = getActiveDisplay()!!.logicalSize
val point =
scaleCoordinate(action.x, action.y, action.screenResolution, displaySize)

result = service.tapScreen(point.x, point.y, inputEventAction)
}

is ActionData.SwipeScreen -> {
val displaySize = getActiveDisplay()!!.activeSize
val displaySize = getActiveDisplay()!!.logicalSize
val start = scaleCoordinate(
action.xStart,
action.yStart,
Expand All @@ -409,7 +409,7 @@ class PerformActionsUseCaseImpl @AssistedInject constructor(
}

is ActionData.PinchScreen -> {
val displaySize = getActiveDisplay()!!.activeSize
val displaySize = getActiveDisplay()!!.logicalSize
val point =
scaleCoordinate(action.x, action.y, action.screenResolution, displaySize)
val distance =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class ScreenshotPickerDelegate(
val activeDisplay =
displayAdapter.getDisplay(displayAdapter.activityDisplayId ?: Display.DEFAULT_DISPLAY)!!

return screenshotResolution.value ?: loadedResolution.value ?: activeDisplay.activeSize
return screenshotResolution.value ?: loadedResolution.value ?: activeDisplay.logicalSize
}

fun recycle() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class LazyConstraintSnapshot(
}
private val isScreenOn: Boolean by lazy { displayAdapter.isScreenOn.firstBlocking() }
private val displaySize: SizeKM by lazy {
getActiveDisplay()!!.activeSize
getActiveDisplay()!!.physicalSize
}

private val appsPlayingMedia: List<String> by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class CreateConstraintUseCaseImpl @Inject constructor(
override fun getCurrentResolution(): SizeKM {
return displayAdapter.getDisplay(
displayAdapter.activityDisplayId ?: Display.DEFAULT_DISPLAY,
)!!.activeSize
)!!.physicalSize
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -73,6 +74,7 @@ fun ExpertModeSetupScreen(viewModel: ExpertModeSetupViewModel) {
onWatchTutorialClick = { },
onBackClick = viewModel::onBackClick,
onSamsungAutoBlockerWarningClick = viewModel::onSamsungAutoBlockerWarningClick,
onSkipDeveloperOptionsClick = viewModel::onSkipDeveloperOptionsClick,
)
}

Expand All @@ -85,6 +87,7 @@ fun ExpertModeSetupScreen(
onAssistantClick: () -> Unit = {},
onWatchTutorialClick: () -> Unit = {},
onSamsungAutoBlockerWarningClick: () -> Unit = {},
onSkipDeveloperOptionsClick: () -> Unit = {},
) {
Scaffold(
topBar = {
Expand All @@ -110,6 +113,7 @@ fun ExpertModeSetupScreen(
onWatchTutorialClick,
onStepButtonClick,
onSamsungAutoBlockerWarningClick,
onSkipDeveloperOptionsClick,
)
}
}
Expand All @@ -122,6 +126,7 @@ fun ExpertModeSetupScreenContent(
onWatchTutorialClick: () -> Unit,
onStepButtonClick: () -> Unit,
onSamsungAutoBlockerWarningClick: () -> Unit = {},
onSkipDeveloperOptionsClick: () -> Unit = {},
) {
when (state) {
State.Loading -> {
Expand Down Expand Up @@ -211,10 +216,6 @@ fun ExpertModeSetupScreenContent(
val isStarting = stepData is ExpertModeSetupState.StartService &&
stepData.isStarting

val showSamsungAutoBlockerWarning =
stepData is ExpertModeSetupState.WirelessDebugging &&
stepData.showSamsungAutoBlockerWarning

StepContent(
modifier = Modifier
.fillMaxWidth()
Expand All @@ -225,14 +226,26 @@ fun ExpertModeSetupScreenContent(
onButtonClick = onStepButtonClick,
iconTint = iconTint,
isLoading = isStarting,
warningContent = if (showSamsungAutoBlockerWarning) {
{
SamsungAutoBlockerWarningCard(
onClick = onSamsungAutoBlockerWarningClick,
)
warningContent = when {
stepData is ExpertModeSetupState.WirelessDebugging &&
stepData.showSamsungAutoBlockerWarning -> {
{
SamsungAutoBlockerWarningCard(
onClick = onSamsungAutoBlockerWarningClick,
)
}
}

stepData is ExpertModeSetupState.DeveloperOptions &&
stepData.showRedactedWarning -> {
{
DeveloperOptionsRedactedWarningCard(
onClick = onSkipDeveloperOptionsClick,
)
}
}
} else {
null

else -> null
},
)
}
Expand Down Expand Up @@ -331,6 +344,26 @@ private fun SamsungAutoBlockerWarningCard(modifier: Modifier = Modifier, onClick
)
}

@Composable
private fun DeveloperOptionsRedactedWarningCard(
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
SetupWarningCard(
modifier = modifier,
title = stringResource(
R.string.expert_mode_setup_wizard_developer_options_redacted_warning_title,
),
description = stringResource(
R.string.expert_mode_setup_wizard_developer_options_redacted_warning_description,
),
buttonText = stringResource(
R.string.expert_mode_setup_wizard_developer_options_redacted_warning_button,
),
onButtonClick = onClick,
)
}

@Composable
private fun SetupWarningCard(
modifier: Modifier = Modifier,
Expand All @@ -343,6 +376,7 @@ private fun SetupWarningCard(
Column(
modifier = Modifier
.fillMaxWidth()
.widthIn(max = 400.dp)
.padding(horizontal = 16.dp, vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expand Down Expand Up @@ -603,6 +637,7 @@ private fun ExpertModeSetupScreenDeveloperOptionsPreview() {
stepNumber = 5,
stepCount = 10,
isSetupAssistantChecked = false,
showRedactedWarning = true,
),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ sealed class ExpertModeSetupState {
override val stepNumber: Int,
override val stepCount: Int,
override val isSetupAssistantChecked: Boolean,
val showRedactedWarning: Boolean,
) : ExpertModeSetupState() {
override val isSetupAssistantButtonEnabled: Boolean = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor(
) {
val notification = NotificationModel(
// Use the same notification id for all so they overwrite each other.
id = NotificationController.Companion.ID_SETUP_ASSISTANT,
channel = NotificationController.Companion.CHANNEL_SETUP_ASSISTANT,
id = NotificationController.ID_SETUP_ASSISTANT,
channel = NotificationController.CHANNEL_SETUP_ASSISTANT,
title = title,
text = text,
icon = R.drawable.offline_bolt_24px,
Expand All @@ -322,7 +322,7 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor(
}

private fun dismissNotification() {
manageNotifications.dismiss(NotificationController.Companion.ID_SETUP_ASSISTANT)
manageNotifications.dismiss(NotificationController.ID_SETUP_ASSISTANT)
}

private fun findPairingCodeText(rootNode: AccessibilityNodeInfo): String? {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.sds100.keymapper.base.expertmode

import android.os.Build
import io.github.sds100.keymapper.common.utils.State
import io.github.sds100.keymapper.common.utils.dataOrNull
import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupStep
Expand Down Expand Up @@ -57,6 +58,10 @@ abstract class SystemBridgeSetupDelegateImpl(
useCase.openSamsungAutoBlockerSettings()
}

override fun onSkipDeveloperOptionsClick() {
useCase.skipDeveloperOptionsStep()
}

private fun buildState(
step: SystemBridgeSetupStep,
isSetupAssistantUserEnabled: Boolean,
Expand Down Expand Up @@ -99,6 +104,9 @@ abstract class SystemBridgeSetupDelegateImpl(
stepNumber = stepNumber,
stepCount = stepCount,
isSetupAssistantChecked = isSetupAssistantUserEnabled,
// See issue #2289. The developer options setting can be redacted on Android 17+.
showRedactedWarning =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN,
)

SystemBridgeSetupStep.WIFI_NETWORK ->
Expand Down Expand Up @@ -148,4 +156,5 @@ interface SystemBridgeSetupDelegate {
fun onSetupStepButtonClick()
fun onSetupAssistantClick()
fun onSamsungAutoBlockerWarningClick()
fun onSkipDeveloperOptionsClick()
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
systemBridgeSetupController.openAutoBlockerSettings()
}

/**
* See issue #2289. Android 17 can redact the developer options setting so it always
* reads as disabled. Let the user skip the step if they know it is enabled.
* Not persisted.
*/
private val isDeveloperOptionsSkipped = MutableStateFlow(false)

override fun skipDeveloperOptionsStep() {
isDeveloperOptionsSkipped.value = true
}

override val isWarningUnderstood: Flow<Boolean> =
preferences.get(Keys.isExpertModeWarningUnderstood).map { it ?: false }

Expand Down Expand Up @@ -169,7 +180,10 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
combine(
isNotificationPermissionGranted,
isLocalNetworkPermissionGranted,
systemBridgeSetupController.isDeveloperOptionsEnabled,
combine(
systemBridgeSetupController.isDeveloperOptionsEnabled,
isDeveloperOptionsSkipped,
) { isEnabled, isSkipped -> isEnabled || isSkipped },
networkAdapter.isWifiConnected,
systemBridgeSetupController.isWirelessDebuggingEnabled,
) {
Expand Down Expand Up @@ -368,7 +382,10 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(

needsSamsungAutoBlockerStep -> SystemBridgeSetupStep.SAMSUNG_AUTO_BLOCKER

!isDeveloperOptionsEnabled -> SystemBridgeSetupStep.DEVELOPER_OPTIONS
// Wireless debugging can not be enabled without developer options being enabled.
// Check both because the developer options setting can be redacted. See #2289.
!isDeveloperOptionsEnabled && !isWirelessDebuggingEnabled ->
SystemBridgeSetupStep.DEVELOPER_OPTIONS

!isWifiConnected -> SystemBridgeSetupStep.WIFI_NETWORK

Expand Down Expand Up @@ -425,6 +442,7 @@ interface SystemBridgeSetupUseCase {
fun stopSystemBridge()
fun enableAccessibilityService()
fun enableDeveloperOptions()
fun skipDeveloperOptionsStep()
fun launchDeveloperOptions()
fun connectWifiNetwork()
fun enableWirelessDebugging()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@ data class FloatingButtonData(
*/
@Serializable
data class Location(
/**
* The location in the orientation that the display was when this
* floating button was created.
*/
val x: Int,
/**
* The location in the orientation that the display was when this
* floating button was created.
*/
val y: Int,
/**
* The orientation the screen was in when the user picked the location of the button.
*/
val orientation: Orientation,
/**
* The size of the display this button was placed on.
* The size of the display in the orientation this button was placed in,
* not the natural size.
*/
val displaySize: SizeKM,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private fun PreviewOneItem() {
}
}

@Preview
@Preview(widthDp = 300)
@Composable
private fun PreviewMultipleItems() {
val ctx = LocalContext.current
Expand All @@ -267,7 +267,7 @@ private fun PreviewMultipleItems() {
),
ComposeChipModel.Error(
id = "2",
text = "Key Mapper not found",
text = "Very very very very long app name not found",
error = KMError.AppNotFound("io.github.sds100.keymapper"),
),
),
Expand Down
Loading
Loading