Skip to content

Commit dd2dcd4

Browse files
authored
Merge pull request #2136 from keymapperorg/develop
Version 4.1.0
2 parents be3c305 + 81430f7 commit dd2dcd4

62 files changed

Lines changed: 2295 additions & 152 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
description: Required checklist when adding a new Action
3+
alwaysApply: true
4+
---
5+
6+
# Adding A New Action
7+
8+
Before implementing, confirm whether the action is editable.
9+
- Branch names must follow `feature/123-branch-name` or `fix/123-branch-name`.
10+
11+
Then follow this sequence:
12+
13+
1. Add an ID in `ActionId`.
14+
2. Add a new `ActionData` sealed class variant.
15+
3. Map entity conversions in `ActionDataEntityMapper`.
16+
4. Assign category in `ActionUtils`.
17+
5. If editable, add it to `ActionUtils.isEditable`.
18+
6. Add title string in `strings.xml`.
19+
7. Add title and Compose icon in `ActionUtils` (ignore drawables).
20+
8. Add title in `ActionUiHelper`.
21+
9. Add execution handling in `PerformActionsUseCase`.
22+
10. Handle creation in `CreateActionDelegate`.
23+
24+
Do not delete existing action code; follow existing names and place additions near similar actions.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
description: Module boundaries and architectural conventions for KeyMapper
3+
alwaysApply: true
4+
---
5+
6+
# KeyMapper Architecture
7+
8+
- Branch names must follow `feature/123-branch-name` or `fix/123-branch-name`.
9+
- Treat the codebase as Clean Architecture + MVVM with unidirectional data flow.
10+
- Keep dependency direction as `:app` -> `:base` -> `:common`/`:data`/`:system`/`:sysbridge`.
11+
- Keep domain models in `:common` and persistence entities in `:data` with explicit mappers.
12+
- Put business logic in use cases and inject them into ViewModels.
13+
- Expose UI state via `Flow`/`StateFlow`; collect from UI and send events back to ViewModels.
14+
- Use Hilt for DI and preserve app-specific binding overrides in app modules.
15+
- Use coroutines for async work and `viewModelScope` for ViewModel-owned jobs.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
description: Build, test, and tooling commands for KeyMapper
3+
alwaysApply: true
4+
---
5+
6+
# KeyMapper Build And Tooling
7+
8+
- Branch names must follow `feature/123-branch-name` or `fix/123-branch-name`.
9+
- Use these common commands:
10+
- `./gradlew assembleDebug`
11+
- `./gradlew test`
12+
- `./gradlew connectedAndroidTest`
13+
- `./gradlew ktlintCheck` or `./gradlew ktlintFormat`
14+
- Release builds require `KEYSTORE_PASSWORD` and `KEY_PASSWORD` environment variables.
15+
- Target platform constraints: min SDK 26, target SDK 36, Java 11.
16+
- Dependency versions are managed in `gradle/libs.versions.toml`.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
description: Commit message format convention for KeyMapper
3+
alwaysApply: true
4+
---
5+
6+
# KeyMapper Commit Messages
7+
8+
Use this format for commits:
9+
10+
`#<issue_number> <type>: <description>`
11+
12+
Allowed types:
13+
14+
- `feat`
15+
- `fix`
16+
- `chore`
17+
- `refactor`
18+
- `style`
19+
20+
Example:
21+
22+
`#2025 feat: add button to report bug on home screen`
23+
24+
# Branch Naming Convention
25+
26+
- Use `feature/123-branch-name` for new features.
27+
- Use `fix/123-branch-name` for bug fixes.
28+
29+
# Pull Request Changelog Requirement
30+
31+
- When preparing a PR, update `CHANGELOG.md` for any user-facing fix/feature/change or notable
32+
internal change.
33+
- If no changelog entry is needed (for example, docs-only or CI-only), explicitly note that in the PR
34+
description.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
description: Compose coding conventions used in KeyMapper
3+
globs: "**/*.kt"
4+
alwaysApply: false
5+
---
6+
7+
# KeyMapper Compose Guidelines
8+
9+
Apply this when editing Compose code:
10+
11+
- Branch names must follow `feature/123-branch-name` or `fix/123-branch-name`.
12+
- Put `Modifier` as the first optional parameter after required parameters.
13+
- For non-standard colors, use `LocalCustomColorsPalette` instead of `MaterialTheme.colorScheme`.
14+
- Check `KeyMapperIcons` before introducing icons from other sources.
15+
- Use `LocalUriHandler.openUriSafe` for URL launching and do not hoist URL launching logic.
16+
- Prefer imports; avoid fully qualified names in Compose call sites.

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
## [4.1.0](https://github.com/sds100/KeyMapper/releases/tag/v4.1.0)
2+
3+
#### 10 May 2026
4+
5+
## Added
6+
7+
- #2067 add action to select all text in the focused field.
8+
- #2045 add action to input on-screen keyboard enter/send button.
9+
- #2106 disable the keyboard auto-switching setting when manually switching the keyboard in the Key Mapper homescreen menu.
10+
- #1029 add action to show a toast message.
11+
- #2081 add getevent debug screen.
12+
- #2087 small segmented button text is not readable in dark mode.
13+
- #2077 rename "This device" and "any device" to "This Android device" and "Any input device" to prevent confusion.
14+
15+
## Changed
16+
17+
- Use a more compact navigation bar on the home screen.
18+
19+
## Fixed
20+
21+
- #2091 show an error on the "open device assistant" action when no device assistant is installed.
22+
- #2107 clarify the crashed accessibility service dialog text and keep only Cancel/Restart actions.
23+
124
## [4.0.5](https://github.com/sds100/KeyMapper/releases/tag/v4.0.5)
225

326
#### 26 February 2026

CLAUDE.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# KeyMapper
2+
3+
## Project Overview
4+
5+
Android app for custom key/gamepad remapping, macros, and on-screen buttons. Supports Android 8.0+ (minSdk 26), targets API 36. Distributed on Google Play and F-Droid (FOSS flavor).
6+
7+
- **Package:** `io.github.sds100.keymapper`
8+
- **Languages:** Kotlin (primary), Rust (event device handling), C++ (system bridge JNI)
9+
10+
## Build Commands
11+
12+
```bash
13+
./gradlew assembleDebug # debug APK
14+
./gradlew assembleCi # CI build (minified)
15+
./gradlew assembleRelease # release build (requires KEYSTORE_PASSWORD and KEY_PASSWORD env vars)
16+
./gradlew clean # clean build outputs
17+
```
18+
19+
## Test & Lint Commands
20+
21+
```bash
22+
# Kotlin unit tests
23+
./gradlew testDebugUnitTest
24+
./gradlew :base:testDebugUnitTest # single module
25+
26+
# Kotlin lint
27+
./gradlew ktlintCheck # check
28+
./gradlew ktlintFormat # auto-fix
29+
30+
# Rust (run from evdev/src/main/rust/evdev_manager)
31+
cargo fmt --check
32+
cargo test --package evdev_manager_core
33+
```
34+
35+
CI runs all of the above on every PR (see `.github/workflows/pull-request.yml`).
36+
37+
## Architecture & Modules
38+
39+
Multi-module Gradle project using MVVM, Hilt DI, Room, StateFlow/Flow, Jetpack Compose, and Coroutines.
40+
41+
| Module | Responsibility |
42+
|---|---|
43+
| `app/` | Entry point: `MainActivity`, `KeyMapperApp`, `MyAccessibilityService` |
44+
| `base/` | UI screens and ViewModels (actions, constraints, triggers, keymaps, settings) |
45+
| `common/` | Shared utilities and models |
46+
| `data/` | Room database, repositories, DataStore, migrations |
47+
| `system/` | Device APIs, permission management |
48+
| `sysbridge/` | C++ JNI via CMake for elevated system access |
49+
| `evdev/` | Rust event device handling + JNI bindings |
50+
| `api/` | AIDL public interface definitions |
51+
| `systemstubs/` | Mock stubs for system classes used in tests |
52+
53+
Most business logic lives in `base/src/main/java/io/github/sds100/keymapper/`.
54+
55+
## Code Style
56+
57+
- **Kotlin:** ktlint with android_studio code style (configured via `.editorconfig`)
58+
- No function expression bodies
59+
- Trailing commas allowed
60+
- Run `./gradlew ktlintFormat` before committing
61+
- **Rust:** `cargo fmt`
62+
63+
## Commit Message Convention
64+
65+
```
66+
#<issue_number> <type>: <description>
67+
```
68+
69+
Types: `feat`, `fix`, `chore`, `refactor`, `style`
70+
71+
Example: `#2025 feat: add button to report bug on home screen`
72+
73+
## Branch Naming Convention
74+
75+
- Use `feature/123-branch-name` for new features.
76+
- Use `fix/123-branch-name` for bug fixes.
77+
78+
## Pull Request Requirement
79+
80+
- Any user-facing behavior change, fix, feature, or notable internal change in a PR must include an
81+
update to `CHANGELOG.md`.
82+
- If there is truly no changelog impact (for example, docs-only or CI-only changes), explicitly state
83+
that in the PR description.
84+
85+
## Compose Guidelines
86+
87+
- `Modifier` is always the first parameter after required params
88+
- Use `LocalCustomColorsPalette` (not `MaterialTheme.colorScheme`) for non-standard colors
89+
- Check `KeyMapperIcons` before using other icon sources
90+
- Use `LocalUriHandler.openUriSafe` extension for URL launching — do not hoist URL launching logic up the call stack
91+
- Use import statements; never use fully qualified names in Compose code
92+
- Write `@Preview` composables for every screen
93+
94+
## Adding a New Action (10-step checklist)
95+
96+
First determine whether the action is editable. Then:
97+
98+
1. Add a new ID to `ActionId`
99+
2. Create a new `ActionData` sealed class variant
100+
3. Map to/from entity in `ActionDataEntityMapper`
101+
4. Assign a category in `ActionUtils`
102+
5. If editable, add to `isEditable` in `ActionUtils`
103+
6. Add a title string in `strings.xml`
104+
7. Add title and Compose icon in `ActionUtils` (ignore drawables)
105+
8. Add title in `ActionUiHelper`
106+
9. Stub out execution in `PerformActionsUseCase`
107+
10. Handle creation in `CreateActionDelegate`
108+
109+
Do not delete existing action code. Follow existing naming conventions exactly. Add new code near similar existing actions.
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,50 @@
11
package io.github.sds100.keymapper.purchasing
22

3-
import io.github.sds100.keymapper.base.purchasing.ProductId
43
import io.github.sds100.keymapper.base.purchasing.PurchasingError
54
import io.github.sds100.keymapper.base.purchasing.PurchasingManager
5+
import io.github.sds100.keymapper.base.purchasing.RevenueCatEntitlementId
66
import io.github.sds100.keymapper.common.utils.KMResult
77
import io.github.sds100.keymapper.common.utils.State
88
import kotlinx.coroutines.flow.Flow
99
import kotlinx.coroutines.flow.MutableSharedFlow
1010
import kotlinx.coroutines.flow.MutableStateFlow
1111

1212
class PurchasingManagerImpl : PurchasingManager {
13-
override val onCompleteProductPurchase: MutableSharedFlow<ProductId> = MutableSharedFlow()
14-
override val purchases: Flow<State<KMResult<Set<ProductId>>>> =
13+
override val onCompleteProductPurchase: MutableSharedFlow<RevenueCatEntitlementId> =
14+
MutableSharedFlow()
15+
override val entitlements: Flow<State<KMResult<Set<RevenueCatEntitlementId>>>> =
1516
MutableStateFlow(State.Data(PurchasingError.PurchasingNotImplemented))
1617

17-
override suspend fun launchPurchasingFlow(product: ProductId): KMResult<Unit> {
18+
override suspend fun launchPurchasingFlow(
19+
packageId: String,
20+
verifyEntitlements: Array<RevenueCatEntitlementId>,
21+
): KMResult<Unit> {
1822
return PurchasingError.PurchasingNotImplemented
1923
}
2024

21-
override suspend fun getProductPrice(product: ProductId): KMResult<String> {
25+
override suspend fun isPackagePurchased(packageId: String): KMResult<Boolean> {
2226
return PurchasingError.PurchasingNotImplemented
2327
}
2428

25-
override suspend fun isPurchased(product: ProductId): KMResult<Boolean> {
29+
override suspend fun getNonSubscriptionPurchaseCount(packageId: String): KMResult<Int> {
2630
return PurchasingError.PurchasingNotImplemented
2731
}
2832

29-
override suspend fun getMetadata(): KMResult<Map<String, Any>> {
33+
override suspend fun getPackagePrice(packageId: String): KMResult<String> {
34+
return PurchasingError.PurchasingNotImplemented
35+
}
36+
37+
override suspend fun hasEntitlement(entitlement: RevenueCatEntitlementId): KMResult<Boolean> {
38+
return PurchasingError.PurchasingNotImplemented
39+
}
40+
41+
override suspend fun getCurrentOfferingId(): KMResult<String?> {
3042
return PurchasingError.PurchasingNotImplemented
3143
}
3244

3345
override fun refresh() {}
46+
47+
override fun trackCustomPaywallImpression(paywallIdentifier: String) {
48+
// Purchasing is not available in FOSS.
49+
}
3450
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.github.sds100.keymapper.purchasing
2+
3+
/**
4+
* Placeholder for RevenueCat package identifiers.
5+
*
6+
* Purchasing is not available in the FOSS build.
7+
*/
8+
enum class RevenueCatPackageId

0 commit comments

Comments
 (0)