Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
aa735fd
bump version to 4.3.2
sds100 Aug 31, 2026
9c3a3a8
Update README.md
sds100 Aug 31, 2026
f807aba
#2099 do not spam notifications that Expert mode failed to start on W…
sds100 Aug 31, 2026
29b664a
Merge remote-tracking branch 'origin/develop' into develop
sds100 Aug 31, 2026
5f97528
#2220 fix: make invisible floating buttons more visible when editing
sds100 Aug 31, 2026
a44db1f
fix: expert mode works on 16KB page size systems.
sds100 Aug 31, 2026
a3241ee
fix: launching Wireless Debugging screen for Expert Mode setup works …
sds100 Aug 31, 2026
7292367
fix compilation error
sds100 Aug 31, 2026
86dfe5e
update changelog
sds100 Aug 31, 2026
e45856a
update gradle version and Android Gradle Plugin
sds100 Aug 31, 2026
b375aa8
update Roboelectric version for SDK 37 support
sds100 Sep 1, 2026
2fd5812
upgrade github actions workflows to JDK 21 so roboelectric works on S…
sds100 Sep 1, 2026
f5247a8
#2209 fix: multi-line shell command actions no longer fail with a syn…
sds100 Sep 2, 2026
4c34bad
#2219 fix: scale floating buttons when screen resolution changes.
sds100 Sep 2, 2026
77c6696
#2160 fix: keep send intent activity edits across screen recreation
claude Sep 3, 2026
12841d7
#2217 fix: scale tap, swipe and pinch screen actions to the display r…
sds100 Sep 4, 2026
8c7d514
#2194 do not use tap screen for clicking pairing code button in Wirel…
sds100 Sep 6, 2026
28b425a
#2227 fix: request access local network permission on Android 17+ to …
sds100 Sep 7, 2026
a075f46
#2160 simplify comments
sds100 Sep 7, 2026
fe80edf
Merge branch 'develop' into claude/issue-2160-send-intent-activity-edit
sds100 Sep 7, 2026
9d5fc33
Merge pull request #2221 from keymapperorg/claude/issue-2160-send-int…
sds100 Sep 7, 2026
85784d6
#2210 fix: Android TV DPAD center button behaves normally when access…
sds100 Sep 8, 2026
40d284d
reformat CHANGELOG.md
sds100 Sep 8, 2026
403f226
#2070 fix: long pressing a movable floating button now activates its …
sds100 Sep 9, 2026
37f07e3
fix: Floating Buttons immediately respond to toggling locked position.
sds100 Sep 9, 2026
da54e73
fix: Floating button options to show over keyboard and status bar app…
sds100 Sep 9, 2026
74ae0d7
#2098 #2233 export and import key maps on Android TV, where there is …
sds100 Sep 10, 2026
9d92611
#2231 fix: do not crash when trying to auto start system bridge and t…
sds100 Sep 10, 2026
f2c2835
fix: check correct google play track version code when releasing inte…
sds100 Sep 10, 2026
33398e0
bump version code
sds100 Sep 10, 2026
589cfab
bump version to 4.4.0
sds100 Sep 10, 2026
1ad2289
#2223 feat: one-finger and two-finger double tap Talkback actions
sds100 Sep 11, 2026
20dc9ee
#2238 feat: add a "Reduce app killing" card to Expert Mode on Xiaomi/…
sds100 Sep 11, 2026
f2ae9fd
#2211 feat: sort key maps by whether they are enabled
sds100 Sep 11, 2026
a07ca57
#2234 feat: add a cycle keyboard action
sds100 Sep 11, 2026
00c2b21
#2199 fix: fall back to opening the apps settings when fixing battery…
sds100 Sep 11, 2026
ed71b44
#2235 feat: show tip to use a dedicated action instead of some key codes
sds100 Sep 12, 2026
91be596
fix: tip card content fills horizontal space
sds100 Sep 12, 2026
11e5c44
fix: Actions screen scrolls when empty and tip does not fill up scree…
sds100 Sep 12, 2026
899eeee
fix: request notification listener permission for step media actions
sds100 Sep 12, 2026
a400328
fix: slider does not jiggle when sliding over the default value.
sds100 Sep 12, 2026
5633645
#2239 feat: add an option to set a custom seek amount for the Step me…
sds100 Sep 12, 2026
98d71d9
Merge branch 'develop' into fix/2217-scale-screen-coordinates
sds100 Sep 12, 2026
57f2324
#2097 fix: potentially fix activities from some apps being missing fr…
sds100 Sep 12, 2026
4264ded
fix: center actions screen list content
sds100 Sep 12, 2026
034c553
#2217 extract shared logic for picking screenshots when configuring g…
sds100 Sep 12, 2026
ade0177
Merge branch 'fix/2217-scale-screen-coordinates' into develop
sds100 Sep 12, 2026
b3920b2
#2212 fix: put warnings, group chips, breadcrumbs and group constrain…
sds100 Sep 12, 2026
5a12775
fix padding in empty/loading key map list
sds100 Sep 12, 2026
674bc75
bump version code
sds100 Sep 12, 2026
a692c1b
add release date to changelog
sds100 Sep 12, 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
18 changes: 9 additions & 9 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Unit tests
Expand All @@ -36,11 +36,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Ktlint check
Expand Down Expand Up @@ -101,11 +101,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Setup Android SDK
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Unit tests
Expand All @@ -42,11 +42,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Ktlint check
Expand Down Expand Up @@ -113,11 +113,11 @@ jobs:
property: VERSION_NUM
value: ${{ github.run_number }}

- name: set up JDK 17
uses: actions/setup-java@v3
- name: set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Setup Android SDK
Expand Down
1,620 changes: 540 additions & 1,080 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<noscript><a href="https://buy.stripe.com/00g16L9YEabldDWdQQ"><img alt="Donate using Stripe" src="https://img.shields.io/badge/Donate-Stripe-blueviolet?style=for-the-badge&logo=stripe"></a></noscript>
</p>

<p align="center">
<img width="700" src="https://github.com/user-attachments/assets/0411203c-4490-42ff-8aff-667cbde32f49" />
</p>

## Unleash your keys!

Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons!
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
android:name="android.hardware.touchscreen"
android:required="false" />

<!--
Requested only in this FOSS build. Android TV has no usable file picker
(system SAF/share intents resolve to fake stub activities that always
fail — see HomeKeyMapListScreen.kt's import/export handling), so
importing a backup requires browsing Downloads directly. Google Play
restricts MANAGE_EXTERNAL_STORAGE to file-manager apps, but Android TV
users overwhelmingly sideload this FOSS build rather than installing
from Play, so it's safe to request here. Do not add this to the Play
Store (app/) manifest.
-->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />

<!-- This is required because sysbridge has a min sdk of 29 since it depends
on the binder-ndk library. Building will fail without this because the min sdk
of the app is 26.-->
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/io/github/sds100/keymapper/AppHiltModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class AppHiltModule {
get() = BuildConfig.VERSION_CODE
override val sdkInt: Int
get() = Build.VERSION.SDK_INT

// Android TV users predominantly sideload this FOSS build rather than
// install from Google Play, which restricts this permission to
// file-manager apps, so it's safe to request it here.
override val canRequestAllFilesAccess: Boolean
get() = true
}

@Singleton
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.3.1
VERSION_CODE=259
VERSION_NAME=4.4.0
VERSION_CODE=264
21 changes: 21 additions & 0 deletions base/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ android {
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}

testOptions {
unitTests {
isIncludeAndroidResources = true
}

// Needed for Roboelectric to work on Java 17+
unitTests.all {
it.jvmArgs(
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.net=ALL-UNNAMED",
"--add-opens=java.base/java.security=ALL-UNNAMED",
"--add-opens=java.base/java.text=ALL-UNNAMED",
"--add-opens=java.base/jdk.internal.access=ALL-UNNAMED",
"--add-opens=java.desktop/java.awt.font=ALL-UNNAMED",
"--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
)
}
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.sds100.keymapper.base

import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
Expand All @@ -8,6 +9,7 @@ import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.view.MotionEvent
import android.widget.Toast
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
Expand Down Expand Up @@ -276,9 +278,27 @@ abstract class BaseMainActivity : AppCompatActivity() {
val fileUri =
IntentCompat.getParcelableExtra(intent, EXTRA_FILE_URI, Uri::class.java) ?: return

saveFileToUserChosenLocation(fileUri)
}

/**
* Let the user pick a location to save [fileUri] to using the system document picker.
* This is used as a fallback for exporting when there is no app installed that can
* receive a shared file, for example on Android TV.
*/
fun saveFileToUserChosenLocation(fileUri: Uri) {
val fileName = fileUri.toDocumentFile(this@BaseMainActivity)?.name ?: return

originalFileUri = fileUri
saveFileLauncher.launch(fileName)

try {
saveFileLauncher.launch(fileName)
} catch (_: ActivityNotFoundException) {
Toast.makeText(
this,
R.string.dialog_message_no_app_found_to_create_file,
Toast.LENGTH_LONG,
).show()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.github.sds100.keymapper.base.constraints.ChooseConstraintViewModel
import io.github.sds100.keymapper.base.debug.GetEventScreen
import io.github.sds100.keymapper.base.expertmode.ExpertModeScreen
import io.github.sds100.keymapper.base.expertmode.ExpertModeSetupScreen
import io.github.sds100.keymapper.base.expertmode.xiaomi.XiaomiOptimizationScreen
import io.github.sds100.keymapper.base.logging.LogScreen
import io.github.sds100.keymapper.base.onboarding.HandleAccessibilityServiceDialogs
import io.github.sds100.keymapper.base.onboarding.SetupAccessibilityServiceDelegateImpl
Expand Down Expand Up @@ -158,6 +159,13 @@ fun BaseMainNavHost(
)
}

composable<NavDestination.XiaomiOptimization> {
XiaomiOptimizationScreen(
modifier = Modifier.fillMaxSize(),
viewModel = hiltViewModel(),
)
}

composable<NavDestination.Log> {
LogScreen(
modifier = Modifier.fillMaxSize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import io.github.sds100.keymapper.base.expertmode.ExpertModeSetupDelegateImpl
import io.github.sds100.keymapper.base.expertmode.SystemBridgeSetupDelegate
import io.github.sds100.keymapper.base.expertmode.SystemBridgeSetupUseCase
import io.github.sds100.keymapper.base.expertmode.SystemBridgeSetupUseCaseImpl
import io.github.sds100.keymapper.base.expertmode.xiaomi.XiaomiOptimizationUseCase
import io.github.sds100.keymapper.base.expertmode.xiaomi.XiaomiOptimizationUseCaseImpl
import io.github.sds100.keymapper.base.home.ListKeyMapsUseCase
import io.github.sds100.keymapper.base.home.ListKeyMapsUseCaseImpl
import io.github.sds100.keymapper.base.home.ShowHomeScreenAlertsUseCase
Expand Down Expand Up @@ -200,4 +202,10 @@ abstract class BaseViewModelHiltModule {
abstract fun bindExpertModeSetupDelegate(
impl: ExpertModeSetupDelegateImpl,
): SystemBridgeSetupDelegate

@Binds
@ViewModelScoped
abstract fun bindXiaomiOptimizationUseCase(
impl: XiaomiOptimizationUseCaseImpl,
): XiaomiOptimizationUseCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.sds100.keymapper.common.models.ShellExecutionMode
import io.github.sds100.keymapper.common.utils.NodeInteractionType
import io.github.sds100.keymapper.common.utils.Orientation
import io.github.sds100.keymapper.common.utils.PinchScreenType
import io.github.sds100.keymapper.common.utils.SizeKM
import io.github.sds100.keymapper.system.camera.CameraLens
import io.github.sds100.keymapper.system.intents.IntentExtraModel
import io.github.sds100.keymapper.system.intents.IntentTarget
Expand Down Expand Up @@ -245,6 +246,11 @@ sealed class ActionData : Comparable<ActionData> {
}
}

@Serializable
data object CycleKeyboardLanguage : ActionData() {
override val id = ActionId.CYCLE_KEYBOARD_LANGUAGE
}

@Serializable
sealed class DoNotDisturb : ActionData() {

Expand Down Expand Up @@ -378,12 +384,18 @@ sealed class ActionData : Comparable<ActionData> {
}

@Serializable
data class StepForward(override val packageName: String) : ControlMediaForApp() {
data class StepForward(
override val packageName: String,
val stepDurationMs: Long? = null,
) : ControlMediaForApp() {
override val id = ActionId.STEP_FORWARD_PACKAGE
}

@Serializable
data class StepBackward(override val packageName: String) : ControlMediaForApp() {
data class StepBackward(
override val packageName: String,
val stepDurationMs: Long? = null,
) : ControlMediaForApp() {
override val id = ActionId.STEP_BACKWARD_PACKAGE
}
}
Expand Down Expand Up @@ -431,12 +443,12 @@ sealed class ActionData : Comparable<ActionData> {
}

@Serializable
data object StepForward : ControlMedia() {
data class StepForward(val stepDurationMs: Long? = null) : ControlMedia() {
override val id = ActionId.STEP_FORWARD
}

@Serializable
data object StepBackward : ControlMedia() {
data class StepBackward(val stepDurationMs: Long? = null) : ControlMedia() {
override val id = ActionId.STEP_BACKWARD
}
}
Expand All @@ -457,7 +469,16 @@ sealed class ActionData : Comparable<ActionData> {
}

@Serializable
data class TapScreen(val x: Int, val y: Int, val description: String?) : ActionData() {
data class TapScreen(
val x: Int,
val y: Int,
val description: String?,
/**
* The display size that the coordinates were picked for. See issue #2217. This is null for
* actions that were created before the resolution was saved and those are never scaled.
*/
val screenResolution: SizeKM? = null,
) : ActionData() {
override val id = ActionId.TAP_SCREEN

override fun compareTo(other: ActionData) = when (other) {
Expand All @@ -482,6 +503,11 @@ sealed class ActionData : Comparable<ActionData> {
val fingerCount: Int,
val duration: Int,
val description: String?,
/**
* The display size that the coordinates were picked for. See issue #2217. This is null for
* actions that were created before the resolution was saved and those are never scaled.
*/
val screenResolution: SizeKM? = null,
) : ActionData() {
override val id = ActionId.SWIPE_SCREEN

Expand Down Expand Up @@ -511,6 +537,12 @@ sealed class ActionData : Comparable<ActionData> {
val fingerCount: Int,
val duration: Int,
val description: String?,
/**
* The display size that the coordinates and distance were picked for. See issue #2217. This
* is null for actions that were created before the resolution was saved and those are never
* scaled.
*/
val screenResolution: SizeKM? = null,
) : ActionData() {
override val id = ActionId.PINCH_SCREEN

Expand Down
Loading
Loading